From 473f76746558b89681c2416956016baed5f6f41e Mon Sep 17 00:00:00 2001 From: Domagoj Kriskovic Date: Mon, 5 May 2025 11:49:00 +0200 Subject: [PATCH 001/194] Update event tracking for AI assist payment flow (#25222) GitOrigin-RevId: feb7987b1397d70b3a04c797bd2db92e42c325f5 --- .../preview-subscription-change/root.tsx | 37 +++++++++++-------- 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/services/web/frontend/js/features/subscription/components/preview-subscription-change/root.tsx b/services/web/frontend/js/features/subscription/components/preview-subscription-change/root.tsx index 513b1b14ba..cb9565e9e4 100644 --- a/services/web/frontend/js/features/subscription/components/preview-subscription-change/root.tsx +++ b/services/web/frontend/js/features/subscription/components/preview-subscription-change/root.tsx @@ -1,4 +1,4 @@ -import { useCallback } from 'react' +import { useCallback, useEffect } from 'react' import moment from 'moment' import { useTranslation, Trans } from 'react-i18next' import { @@ -30,28 +30,35 @@ function PreviewSubscriptionChange() { const payNowTask = useAsync() const location = useLocation() - const handlePayNowClick = useCallback(() => { - let addOnSegmentation: Record | null = null + useEffect(() => { if (preview.change.type === 'add-on-purchase') { - addOnSegmentation = { - addOn: preview.change.addOn.code, + eventTracking.sendMB('preview-subscription-change-view', { + plan: preview.change.addOn.code, upgradeType: 'add-on', - } - if (purchaseReferrer) { - addOnSegmentation.referrer = purchaseReferrer - } - eventTracking.sendMB('subscription-change-form-submit', addOnSegmentation) + referrer: purchaseReferrer, + }) + } + }, [preview.change, purchaseReferrer]) + + const handlePayNowClick = useCallback(() => { + if (preview.change.type === 'add-on-purchase') { + eventTracking.sendMB('subscription-change-form-submit', { + plan: preview.change.addOn.code, + upgradeType: 'add-on', + referrer: purchaseReferrer, + }) } eventTracking.sendMB('assistant-add-on-purchase') payNowTask .runAsync(payNow(preview)) .then(() => { - if (addOnSegmentation) { - eventTracking.sendMB( - 'subscription-change-form-success', - addOnSegmentation - ) + if (preview.change.type === 'add-on-purchase') { + eventTracking.sendMB('subscription-change-form-success', { + plan: preview.change.addOn.code, + upgradeType: 'add-on', + referrer: purchaseReferrer, + }) } location.replace('/user/subscription/thank-you') }) From c8a410d3587f6a28319e2f937a41e099d8b27b25 Mon Sep 17 00:00:00 2001 From: M Fahru Date: Mon, 5 May 2025 05:39:28 -0700 Subject: [PATCH 002/194] Merge pull request #25155 from overleaf/mf-use-stripe-v18 [web] Upgrade stripe to v18 GitOrigin-RevId: df522f73132e99e38f1716bf33e8ff4881bd5430 --- package-lock.json | 35 +++++++++++++++++++++-------------- services/web/package.json | 2 +- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/package-lock.json b/package-lock.json index 48f2da293a..639567368b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -38259,19 +38259,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/stripe": { - "version": "17.7.0", - "resolved": "https://registry.npmjs.org/stripe/-/stripe-17.7.0.tgz", - "integrity": "sha512-aT2BU9KkizY9SATf14WhhYVv2uOapBWX0OFWF4xvcj1mPaNotlSc2CsxpS4DS46ZueSppmCF5BX1sNYBtwBvfw==", - "license": "MIT", - "dependencies": { - "@types/node": ">=8.1.0", - "qs": "^6.11.0" - }, - "engines": { - "node": ">=12.*" - } - }, "node_modules/strnum": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", @@ -45549,7 +45536,7 @@ "request": "^2.88.2", "requestretry": "^7.1.0", "sanitize-html": "^2.8.1", - "stripe": "^17.7.0", + "stripe": "^18.1.0", "tough-cookie": "^4.0.0", "tsscmp": "^1.0.6", "uid-safe": "^2.1.5", @@ -46624,6 +46611,26 @@ "resolved": "https://registry.npmjs.org/stream-transform/-/stream-transform-3.2.1.tgz", "integrity": "sha512-ApK+WTJ5bCOf0A2tlec1qhvr8bGEBM/sgXXB7mysdCYgZJO5DZeaV3h3G+g0HnAQ372P5IhiGqnW29zoLOfTzQ==" }, + "services/web/node_modules/stripe": { + "version": "18.1.0", + "resolved": "https://registry.npmjs.org/stripe/-/stripe-18.1.0.tgz", + "integrity": "sha512-MLDiniPTHqcfIT3anyBPmOEcaiDhYa7/jRaNypQ3Rt2SJnayQZBvVbFghIziUCZdltGAndm/ZxVOSw6uuSCDig==", + "license": "MIT", + "dependencies": { + "qs": "^6.11.0" + }, + "engines": { + "node": ">=12.*" + }, + "peerDependencies": { + "@types/node": ">=12.x.x" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, "services/web/node_modules/teeny-request": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-8.0.2.tgz", diff --git a/services/web/package.json b/services/web/package.json index 679c226556..f849507460 100644 --- a/services/web/package.json +++ b/services/web/package.json @@ -165,7 +165,7 @@ "request": "^2.88.2", "requestretry": "^7.1.0", "sanitize-html": "^2.8.1", - "stripe": "^17.7.0", + "stripe": "^18.1.0", "tough-cookie": "^4.0.0", "tsscmp": "^1.0.6", "uid-safe": "^2.1.5", From a5e2708eaebf0daa7fd4c05e74783058376e1121 Mon Sep 17 00:00:00 2001 From: Jakob Ackermann Date: Mon, 5 May 2025 15:24:25 +0200 Subject: [PATCH 003/194] [document-updater] add safe rollback point for history-ot (#25283) GitOrigin-RevId: d7230dd14a379a27d2c6ab03a006463a18979d06 --- services/document-updater/app.js | 2 + services/document-updater/app/js/Errors.js | 2 + .../document-updater/app/js/RedisManager.js | 7 +++ .../acceptance/js/GettingADocumentTests.js | 58 +++++++++++++++++++ 4 files changed, 69 insertions(+) diff --git a/services/document-updater/app.js b/services/document-updater/app.js index 65c9895377..f425872da5 100644 --- a/services/document-updater/app.js +++ b/services/document-updater/app.js @@ -212,6 +212,8 @@ app.use((error, req, res, next) => { return res.status(422).json(error.info) } else if (error instanceof Errors.FileTooLargeError) { return res.sendStatus(413) + } else if (error instanceof Errors.ProjectMigratedToHistoryOTError) { + return res.status(422).send(error.message) } else if (error.statusCode === 413) { return res.status(413).send('request entity too large') } else { diff --git a/services/document-updater/app/js/Errors.js b/services/document-updater/app/js/Errors.js index a43f69ad35..0416581b30 100644 --- a/services/document-updater/app/js/Errors.js +++ b/services/document-updater/app/js/Errors.js @@ -5,6 +5,7 @@ class OpRangeNotAvailableError extends OError {} class ProjectStateChangedError extends OError {} class DeleteMismatchError extends OError {} class FileTooLargeError extends OError {} +class ProjectMigratedToHistoryOTError extends OError {} module.exports = { NotFoundError, @@ -12,4 +13,5 @@ module.exports = { ProjectStateChangedError, DeleteMismatchError, FileTooLargeError, + ProjectMigratedToHistoryOTError, } diff --git a/services/document-updater/app/js/RedisManager.js b/services/document-updater/app/js/RedisManager.js index f8e97f38b4..c361f34165 100644 --- a/services/document-updater/app/js/RedisManager.js +++ b/services/document-updater/app/js/RedisManager.js @@ -324,6 +324,13 @@ const RedisManager = { } catch (e) { return callback(e) } + if (docLines != null && !Array.isArray(docLines)) { + return callback( + new Errors.ProjectMigratedToHistoryOTError( + 'refusing to process doc that was migrated to history-ot' + ) + ) + } version = parseInt(version || 0, 10) // check doc is in requested project diff --git a/services/document-updater/test/acceptance/js/GettingADocumentTests.js b/services/document-updater/test/acceptance/js/GettingADocumentTests.js index 65298932d9..6f52f49fb9 100644 --- a/services/document-updater/test/acceptance/js/GettingADocumentTests.js +++ b/services/document-updater/test/acceptance/js/GettingADocumentTests.js @@ -13,6 +13,11 @@ const { expect } = require('chai') const MockWebApi = require('./helpers/MockWebApi') const DocUpdaterClient = require('./helpers/DocUpdaterClient') const DocUpdaterApp = require('./helpers/DocUpdaterApp') +const Settings = require('@overleaf/settings') +const docUpdaterRedis = require('@overleaf/redis-wrapper').createClient( + Settings.redis.documentupdater +) +const Keys = Settings.redis.documentupdater.key_schema describe('Getting a document', function () { before(function (done) { @@ -109,6 +114,59 @@ describe('Getting a document', function () { }) }) + describe('when the document is migrated (history-ot)', function () { + before(function (done) { + ;[this.project_id, this.doc_id] = Array.from([ + DocUpdaterClient.randomId(), + DocUpdaterClient.randomId(), + ]) + + MockWebApi.insertDoc(this.project_id, this.doc_id, { + lines: this.lines, + version: this.version, + }) + DocUpdaterClient.preloadDoc(this.project_id, this.doc_id, error => { + if (error != null) { + throw error + } + sinon.spy(MockWebApi, 'getDocument') + docUpdaterRedis.set( + Keys.docLines({ doc_id: this.doc_id }), + JSON.stringify({ content: this.lines.join('\n') }), + err => { + if (err) return done(err) + + DocUpdaterClient.getDoc( + this.project_id, + this.doc_id, + (error, res, body) => { + if (error) return done(error) + this.res = res + this.body = body + done() + } + ) + } + ) + }) + }) + + after(function () { + MockWebApi.getDocument.restore() + }) + + it('should not load the document from the web API', function () { + MockWebApi.getDocument.called.should.equal(false) + }) + + it('should return an error', function () { + expect(this.res.statusCode).to.equal(422) + expect(this.body).to.equal( + 'refusing to process doc that was migrated to history-ot' + ) + }) + }) + describe('when the request asks for some recent ops', function () { before(function (done) { ;[this.project_id, this.doc_id] = Array.from([ From d95340edbce607198cd83143fa5bc6a942d862ff Mon Sep 17 00:00:00 2001 From: Jimmy Domagala-Tang Date: Mon, 5 May 2025 09:41:59 -0400 Subject: [PATCH 004/194] Merge pull request #25145 from overleaf/jdt-wf-premium-src Add premium source to Writefull entitlement sync GitOrigin-RevId: bbebd7741efdf40a444768255b4aade857aca602 --- services/web/app/src/models/User.js | 1 + 1 file changed, 1 insertion(+) diff --git a/services/web/app/src/models/User.js b/services/web/app/src/models/User.js index c63647e914..d228c46b82 100644 --- a/services/web/app/src/models/User.js +++ b/services/web/app/src/models/User.js @@ -196,6 +196,7 @@ const UserSchema = new Schema( enabled: { type: Boolean, default: null }, autoCreatedAccount: { type: Boolean, default: false }, isPremium: { type: Boolean, default: false }, + premiumSource: { type: String, default: null }, }, aiErrorAssistant: { enabled: { type: Boolean, default: true }, From 07b225542660b6fb86a93e790e89c7c53fa7cee7 Mon Sep 17 00:00:00 2001 From: Jakob Ackermann Date: Tue, 6 May 2025 12:01:42 +0200 Subject: [PATCH 005/194] [misc] cleanup .dockerignore and .gitignore files (#25312) - Remove settings ignore, they are inconsistent and break local prettier - Remove .dockerignore files, only root ignore file is used - Move .idea/.run/*.swp/coverage to root - Remove .npmrc entries, we are no longer writing the rc file - Remove node_modules/.DS_Store, is contained in root - Remove cruft GitOrigin-RevId: 3025fd5acaef343312f55149466c638e759a6e1f --- .../access-token-encryptor/.dockerignore | 1 - libraries/access-token-encryptor/.gitignore | 46 ------------- libraries/fetch-utils/.dockerignore | 1 - libraries/fetch-utils/.gitignore | 3 - libraries/logger/.dockerignore | 1 - libraries/logger/.gitignore | 3 - libraries/metrics/.dockerignore | 1 - libraries/metrics/.gitignore | 3 - libraries/mongo-utils/.dockerignore | 1 - libraries/mongo-utils/.gitignore | 3 - libraries/o-error/.dockerignore | 1 - libraries/o-error/.gitignore | 5 -- libraries/object-persistor/.dockerignore | 1 - libraries/object-persistor/.gitignore | 4 -- libraries/overleaf-editor-core/.dockerignore | 1 - libraries/overleaf-editor-core/.gitignore | 5 -- libraries/promise-utils/.dockerignore | 1 - libraries/promise-utils/.gitignore | 3 - libraries/ranges-tracker/.dockerignore | 1 - libraries/ranges-tracker/.gitignore | 13 ---- libraries/redis-wrapper/.dockerignore | 1 - libraries/redis-wrapper/.gitignore | 13 ---- libraries/settings/.dockerignore | 1 - libraries/settings/.gitignore | 5 -- libraries/stream-utils/.dockerignore | 1 - libraries/stream-utils/.gitignore | 3 - services/chat/.gitignore | 12 ---- services/clsi/.gitignore | 11 --- services/contacts/.gitignore | 5 -- services/docstore/.gitignore | 8 --- services/document-updater/.gitignore | 52 -------------- services/filestore/.gitignore | 51 -------------- services/git-bridge/.gitignore | 49 +------------ services/history-v1/.gitignore | 3 - services/notifications/.gitignore | 54 --------------- services/project-history/.gitignore | 8 --- services/real-time/.gitignore | 5 -- services/web/.gitignore | 68 ------------------- 38 files changed, 1 insertion(+), 447 deletions(-) delete mode 100644 libraries/access-token-encryptor/.dockerignore delete mode 100644 libraries/access-token-encryptor/.gitignore delete mode 100644 libraries/fetch-utils/.dockerignore delete mode 100644 libraries/fetch-utils/.gitignore delete mode 100644 libraries/logger/.dockerignore delete mode 100644 libraries/logger/.gitignore delete mode 100644 libraries/metrics/.dockerignore delete mode 100644 libraries/metrics/.gitignore delete mode 100644 libraries/mongo-utils/.dockerignore delete mode 100644 libraries/mongo-utils/.gitignore delete mode 100644 libraries/o-error/.dockerignore delete mode 100644 libraries/o-error/.gitignore delete mode 100644 libraries/object-persistor/.dockerignore delete mode 100644 libraries/object-persistor/.gitignore delete mode 100644 libraries/overleaf-editor-core/.dockerignore delete mode 100644 libraries/overleaf-editor-core/.gitignore delete mode 100644 libraries/promise-utils/.dockerignore delete mode 100644 libraries/promise-utils/.gitignore delete mode 100644 libraries/ranges-tracker/.dockerignore delete mode 100644 libraries/ranges-tracker/.gitignore delete mode 100644 libraries/redis-wrapper/.dockerignore delete mode 100644 libraries/redis-wrapper/.gitignore delete mode 100644 libraries/settings/.dockerignore delete mode 100644 libraries/settings/.gitignore delete mode 100644 libraries/stream-utils/.dockerignore delete mode 100644 libraries/stream-utils/.gitignore delete mode 100644 services/chat/.gitignore delete mode 100644 services/contacts/.gitignore delete mode 100644 services/docstore/.gitignore delete mode 100644 services/document-updater/.gitignore delete mode 100644 services/history-v1/.gitignore delete mode 100644 services/notifications/.gitignore delete mode 100644 services/project-history/.gitignore delete mode 100644 services/real-time/.gitignore diff --git a/libraries/access-token-encryptor/.dockerignore b/libraries/access-token-encryptor/.dockerignore deleted file mode 100644 index c2658d7d1b..0000000000 --- a/libraries/access-token-encryptor/.dockerignore +++ /dev/null @@ -1 +0,0 @@ -node_modules/ diff --git a/libraries/access-token-encryptor/.gitignore b/libraries/access-token-encryptor/.gitignore deleted file mode 100644 index 66936c4121..0000000000 --- a/libraries/access-token-encryptor/.gitignore +++ /dev/null @@ -1,46 +0,0 @@ -compileFolder - -Compiled source # -################### -*.com -*.class -*.dll -*.exe -*.o -*.so - -# Packages # -############ -# it's better to unpack these files and commit the raw source -# git has its own built in compression methods -*.7z -*.dmg -*.gz -*.iso -*.jar -*.rar -*.tar -*.zip - -# Logs and databases # -###################### -*.log -*.sql -*.sqlite - -# OS generated files # -###################### -.DS_Store? -ehthumbs.db -Icon? -Thumbs.db - -/node_modules/* -data/*/* - -**.swp - -/log.json -hash_folder - -.npmrc diff --git a/libraries/fetch-utils/.dockerignore b/libraries/fetch-utils/.dockerignore deleted file mode 100644 index c2658d7d1b..0000000000 --- a/libraries/fetch-utils/.dockerignore +++ /dev/null @@ -1 +0,0 @@ -node_modules/ diff --git a/libraries/fetch-utils/.gitignore b/libraries/fetch-utils/.gitignore deleted file mode 100644 index edb0f85350..0000000000 --- a/libraries/fetch-utils/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ - -# managed by monorepo$ bin/update_build_scripts -.npmrc diff --git a/libraries/logger/.dockerignore b/libraries/logger/.dockerignore deleted file mode 100644 index c2658d7d1b..0000000000 --- a/libraries/logger/.dockerignore +++ /dev/null @@ -1 +0,0 @@ -node_modules/ diff --git a/libraries/logger/.gitignore b/libraries/logger/.gitignore deleted file mode 100644 index 2006c875a4..0000000000 --- a/libraries/logger/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -node_modules - -.npmrc diff --git a/libraries/metrics/.dockerignore b/libraries/metrics/.dockerignore deleted file mode 100644 index c2658d7d1b..0000000000 --- a/libraries/metrics/.dockerignore +++ /dev/null @@ -1 +0,0 @@ -node_modules/ diff --git a/libraries/metrics/.gitignore b/libraries/metrics/.gitignore deleted file mode 100644 index 2006c875a4..0000000000 --- a/libraries/metrics/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -node_modules - -.npmrc diff --git a/libraries/mongo-utils/.dockerignore b/libraries/mongo-utils/.dockerignore deleted file mode 100644 index c2658d7d1b..0000000000 --- a/libraries/mongo-utils/.dockerignore +++ /dev/null @@ -1 +0,0 @@ -node_modules/ diff --git a/libraries/mongo-utils/.gitignore b/libraries/mongo-utils/.gitignore deleted file mode 100644 index edb0f85350..0000000000 --- a/libraries/mongo-utils/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ - -# managed by monorepo$ bin/update_build_scripts -.npmrc diff --git a/libraries/o-error/.dockerignore b/libraries/o-error/.dockerignore deleted file mode 100644 index c2658d7d1b..0000000000 --- a/libraries/o-error/.dockerignore +++ /dev/null @@ -1 +0,0 @@ -node_modules/ diff --git a/libraries/o-error/.gitignore b/libraries/o-error/.gitignore deleted file mode 100644 index cf2f0ad3fb..0000000000 --- a/libraries/o-error/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -.nyc_output -coverage -node_modules/ - -.npmrc diff --git a/libraries/object-persistor/.dockerignore b/libraries/object-persistor/.dockerignore deleted file mode 100644 index c2658d7d1b..0000000000 --- a/libraries/object-persistor/.dockerignore +++ /dev/null @@ -1 +0,0 @@ -node_modules/ diff --git a/libraries/object-persistor/.gitignore b/libraries/object-persistor/.gitignore deleted file mode 100644 index 6a20893208..0000000000 --- a/libraries/object-persistor/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -/node_modules -*.swp - -.npmrc diff --git a/libraries/overleaf-editor-core/.dockerignore b/libraries/overleaf-editor-core/.dockerignore deleted file mode 100644 index c2658d7d1b..0000000000 --- a/libraries/overleaf-editor-core/.dockerignore +++ /dev/null @@ -1 +0,0 @@ -node_modules/ diff --git a/libraries/overleaf-editor-core/.gitignore b/libraries/overleaf-editor-core/.gitignore deleted file mode 100644 index 869500a2c7..0000000000 --- a/libraries/overleaf-editor-core/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -/coverage -/node_modules - -# managed by monorepo$ bin/update_build_scripts -.npmrc diff --git a/libraries/promise-utils/.dockerignore b/libraries/promise-utils/.dockerignore deleted file mode 100644 index c2658d7d1b..0000000000 --- a/libraries/promise-utils/.dockerignore +++ /dev/null @@ -1 +0,0 @@ -node_modules/ diff --git a/libraries/promise-utils/.gitignore b/libraries/promise-utils/.gitignore deleted file mode 100644 index edb0f85350..0000000000 --- a/libraries/promise-utils/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ - -# managed by monorepo$ bin/update_build_scripts -.npmrc diff --git a/libraries/ranges-tracker/.dockerignore b/libraries/ranges-tracker/.dockerignore deleted file mode 100644 index c2658d7d1b..0000000000 --- a/libraries/ranges-tracker/.dockerignore +++ /dev/null @@ -1 +0,0 @@ -node_modules/ diff --git a/libraries/ranges-tracker/.gitignore b/libraries/ranges-tracker/.gitignore deleted file mode 100644 index eac200248b..0000000000 --- a/libraries/ranges-tracker/.gitignore +++ /dev/null @@ -1,13 +0,0 @@ -**.swp - -app.js -app/js/ -test/unit/js/ -public/build/ - -node_modules/ - -/public/js/chat.js -plato/ - -.npmrc diff --git a/libraries/redis-wrapper/.dockerignore b/libraries/redis-wrapper/.dockerignore deleted file mode 100644 index c2658d7d1b..0000000000 --- a/libraries/redis-wrapper/.dockerignore +++ /dev/null @@ -1 +0,0 @@ -node_modules/ diff --git a/libraries/redis-wrapper/.gitignore b/libraries/redis-wrapper/.gitignore deleted file mode 100644 index eac200248b..0000000000 --- a/libraries/redis-wrapper/.gitignore +++ /dev/null @@ -1,13 +0,0 @@ -**.swp - -app.js -app/js/ -test/unit/js/ -public/build/ - -node_modules/ - -/public/js/chat.js -plato/ - -.npmrc diff --git a/libraries/settings/.dockerignore b/libraries/settings/.dockerignore deleted file mode 100644 index c2658d7d1b..0000000000 --- a/libraries/settings/.dockerignore +++ /dev/null @@ -1 +0,0 @@ -node_modules/ diff --git a/libraries/settings/.gitignore b/libraries/settings/.gitignore deleted file mode 100644 index 06d8e1ddb2..0000000000 --- a/libraries/settings/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -/.npmrc -/node_modules - -# managed by monorepo$ bin/update_build_scripts -.npmrc diff --git a/libraries/stream-utils/.dockerignore b/libraries/stream-utils/.dockerignore deleted file mode 100644 index c2658d7d1b..0000000000 --- a/libraries/stream-utils/.dockerignore +++ /dev/null @@ -1 +0,0 @@ -node_modules/ diff --git a/libraries/stream-utils/.gitignore b/libraries/stream-utils/.gitignore deleted file mode 100644 index edb0f85350..0000000000 --- a/libraries/stream-utils/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ - -# managed by monorepo$ bin/update_build_scripts -.npmrc diff --git a/services/chat/.gitignore b/services/chat/.gitignore deleted file mode 100644 index f0cf94b147..0000000000 --- a/services/chat/.gitignore +++ /dev/null @@ -1,12 +0,0 @@ -**.swp - -public/build/ - -node_modules/ - -plato/ - -**/*.map - -# managed by dev-environment$ bin/update_build_scripts -.npmrc diff --git a/services/clsi/.gitignore b/services/clsi/.gitignore index 360466227e..a85e6b757a 100644 --- a/services/clsi/.gitignore +++ b/services/clsi/.gitignore @@ -1,14 +1,3 @@ -**.swp -node_modules -test/acceptance/fixtures/tmp compiles output -.DS_Store -*~ cache -.vagrant -config/* -npm-debug.log - -# managed by dev-environment$ bin/update_build_scripts -.npmrc diff --git a/services/contacts/.gitignore b/services/contacts/.gitignore deleted file mode 100644 index 80bac793a7..0000000000 --- a/services/contacts/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -node_modules -forever - -# managed by dev-environment$ bin/update_build_scripts -.npmrc diff --git a/services/docstore/.gitignore b/services/docstore/.gitignore deleted file mode 100644 index 84bf300f7f..0000000000 --- a/services/docstore/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -node_modules -forever - -# managed by dev-environment$ bin/update_build_scripts -.npmrc - -# Jetbrains IDEs -.idea diff --git a/services/document-updater/.gitignore b/services/document-updater/.gitignore deleted file mode 100644 index 624e78f096..0000000000 --- a/services/document-updater/.gitignore +++ /dev/null @@ -1,52 +0,0 @@ -compileFolder - -Compiled source # -################### -*.com -*.class -*.dll -*.exe -*.o -*.so - -# Packages # -############ -# it's better to unpack these files and commit the raw source -# git has its own built in compression methods -*.7z -*.dmg -*.gz -*.iso -*.jar -*.rar -*.tar -*.zip - -# Logs and databases # -###################### -*.log -*.sql -*.sqlite - -# OS generated files # -###################### -.DS_Store? -ehthumbs.db -Icon? -Thumbs.db - -/node_modules/* - - - -forever/ - -**.swp - -# Redis cluster -**/appendonly.aof -**/dump.rdb -**/nodes.conf - -# managed by dev-environment$ bin/update_build_scripts -.npmrc diff --git a/services/filestore/.gitignore b/services/filestore/.gitignore index a2f4b5afb2..1772191882 100644 --- a/services/filestore/.gitignore +++ b/services/filestore/.gitignore @@ -1,54 +1,3 @@ -compileFolder - -Compiled source # -################### -*.com -*.class -*.dll -*.exe -*.o -*.so - -# Packages # -############ -# it's better to unpack these files and commit the raw source -# git has its own built in compression methods -*.7z -*.dmg -*.gz -*.iso -*.jar -*.rar -*.tar -*.zip - -# Logs and databases # -###################### -*.log -*.sql -*.sqlite - -# OS generated files # -###################### -.DS_Store? -ehthumbs.db -Icon? -Thumbs.db - -/node_modules/* -data/*/* - -**/*.map -cookies.txt uploads/* - user_files/* template_files/* - -**.swp - -/log.json -hash_folder - -# managed by dev-environment$ bin/update_build_scripts -.npmrc diff --git a/services/git-bridge/.gitignore b/services/git-bridge/.gitignore index 74a7f43d6e..f35e2ee038 100644 --- a/services/git-bridge/.gitignore +++ b/services/git-bridge/.gitignore @@ -1,53 +1,6 @@ -# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm -# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 - -# Let's not share anything because we're using Maven. - -.idea -*.iml - -# User-specific stuff: -.idea/workspace.xml -.idea/tasks.xml -.idea/dictionaries -.idea/vcs.xml -.idea/jsLibraryMappings.xml - -# Sensitive or high-churn files: -.idea/dataSources.ids -.idea/dataSources.xml -.idea/dataSources.local.xml -.idea/sqlDataSources.xml -.idea/dynamic.xml -.idea/uiDesigner.xml - -# Gradle: -.idea/gradle.xml -.idea/libraries - -# Mongo Explorer plugin: -.idea/mongoSettings.xml - -## File-based project format: -*.iws - -## Plugin-specific files: - -# IntelliJ +# Build output /out/ target/ -# mpeltonen/sbt-idea plugin -.idea_modules/ - -# JIRA plugin -atlassian-ide-plugin.xml - -# Crashlytics plugin (for Android Studio and IntelliJ) -com_crashlytics_export_strings.xml -crashlytics.properties -crashlytics-build.properties -fabric.properties - # Local configuration files conf/runtime.json diff --git a/services/history-v1/.gitignore b/services/history-v1/.gitignore deleted file mode 100644 index edb0f85350..0000000000 --- a/services/history-v1/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ - -# managed by monorepo$ bin/update_build_scripts -.npmrc diff --git a/services/notifications/.gitignore b/services/notifications/.gitignore deleted file mode 100644 index 8a030e9aff..0000000000 --- a/services/notifications/.gitignore +++ /dev/null @@ -1,54 +0,0 @@ -Compiled source # -################### -*.com -*.class -*.dll -*.exe -*.o -*.so - -# Packages # -############ -# it's better to unpack these files and commit the raw source -# git has its own built in compression methods -*.7z -*.dmg -*.gz -*.iso -*.jar -*.rar -*.tar -*.zip - -# Logs and databases # -###################### -*.log -*.sql -*.sqlite - -# OS generated files # -###################### -.DS_Store? -ehthumbs.db -Icon? -Thumbs.db - -node_modules/* -data/* - -cookies.txt -UserAndProjectPopulator.coffee - -public/stylesheets/style.css - -Gemfile.lock - -*.swp -.DS_Store - -app/views/external - -/modules/ - -# managed by dev-environment$ bin/update_build_scripts -.npmrc diff --git a/services/project-history/.gitignore b/services/project-history/.gitignore deleted file mode 100644 index 25328fed2e..0000000000 --- a/services/project-history/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -**.swp -node_modules/ -forever/ -.config -.npm - -# managed by dev-environment$ bin/update_build_scripts -.npmrc diff --git a/services/real-time/.gitignore b/services/real-time/.gitignore deleted file mode 100644 index 80bac793a7..0000000000 --- a/services/real-time/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -node_modules -forever - -# managed by dev-environment$ bin/update_build_scripts -.npmrc diff --git a/services/web/.gitignore b/services/web/.gitignore index 8bd23b7f0a..9946f23ae6 100644 --- a/services/web/.gitignore +++ b/services/web/.gitignore @@ -1,51 +1,6 @@ -# Compiled source # -################### -*.com -*.class -*.dll -*.exe -*.o -*.so - -# Packages # -############ -# it's better to unpack these files and commit the raw source -# git has its own built in compression methods -*.7z -*.dmg -*.gz -*.iso -*.jar -*.rar -*.tar -*.zip - -# Logs and databases # -###################### -*.log -*.sql -*.sqlite - -# OS generated files # -###################### -.DS_Store? -ehthumbs.db -Icon? -Thumbs.db - -# allow "icons" -![Ii]cons - -node_modules/* data/* coverage -cookies.txt -requestQueueWorker.js -TpdsWorker.js -BackgroundJobsWorker.js -UserAndProjectPopulator.coffee - public/manifest.json public/js @@ -54,22 +9,6 @@ public/stylesheets public/fonts public/images -Gemfile.lock - -*.swp -.DS_Store - -docker-shared.yml - -config/*.coffee -!config/settings.defaults.coffee -!config/settings.webpack.coffee -config/*.js -!config/settings.defaults.js -!config/settings.webpack.js -!config/settings.overrides.saas.js -!config/settings.overrides.server-pro.js - modules/**/Makefile # Precompiled pug files @@ -78,13 +17,6 @@ modules/**/Makefile # Sentry secrets file (injected by CI) .sentryclirc -# via dev-environment -.npmrc - -# Intellij -.idea -.run - # Cypress cypress/screenshots/ cypress/videos/ From 1cd8eba098aff6b2838d40dd4fb99dcf8a60bcc2 Mon Sep 17 00:00:00 2001 From: David <33458145+davidmcpowell@users.noreply.github.com> Date: Tue, 6 May 2025 11:51:36 +0100 Subject: [PATCH 006/194] Merge pull request #25249 from overleaf/dp-chat-message-read Mark messages as read when opening chat tab GitOrigin-RevId: d0e3290cad72716cbbdf5b6cc92f6c1d387a92c7 --- .../frontend/js/features/chat/context/chat-context.tsx | 9 ++++++++- .../js/features/ide-redesign/components/rail.tsx | 9 ++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/services/web/frontend/js/features/chat/context/chat-context.tsx b/services/web/frontend/js/features/chat/context/chat-context.tsx index d86171a451..9feca60579 100644 --- a/services/web/frontend/js/features/chat/context/chat-context.tsx +++ b/services/web/frontend/js/features/chat/context/chat-context.tsx @@ -20,6 +20,8 @@ import { useIdeContext } from '@/shared/context/ide-context' import getMeta from '@/utils/meta' import { debugConsole } from '@/utils/debugging' import { User } from '../../../../../types/user' +import { useRailContext } from '@/features/ide-redesign/contexts/rail-context' +import { useIsNewEditorEnabled } from '@/features/ide-redesign/utils/new-editor-utils' const PAGE_SIZE = 50 @@ -200,7 +202,12 @@ export const ChatProvider: FC = ({ children }) => { const user = useUserContext() const { _id: projectId } = useProjectContext() - const { chatIsOpen } = useLayoutContext() + const { chatIsOpen: chatIsOpenOldEditor } = useLayoutContext() + const { selectedTab: selectedRailTab, isOpen: railIsOpen } = useRailContext() + const newEditor = useIsNewEditorEnabled() + const chatIsOpen = newEditor + ? selectedRailTab === 'chat' && railIsOpen + : chatIsOpenOldEditor const { hasFocus: windowHasFocus, diff --git a/services/web/frontend/js/features/ide-redesign/components/rail.tsx b/services/web/frontend/js/features/ide-redesign/components/rail.tsx index 34cb7df3f4..c3e14edfcb 100644 --- a/services/web/frontend/js/features/ide-redesign/components/rail.tsx +++ b/services/web/frontend/js/features/ide-redesign/components/rail.tsx @@ -32,6 +32,7 @@ import { HistorySidebar } from '@/features/ide-react/components/history-sidebar' import DictionarySettingsModal from './settings/editor-settings/dictionary-settings-modal' import OLTooltip from '@/features/ui/components/ol/ol-tooltip' import OLIconButton from '@/features/ui/components/ol/ol-icon-button' +import { useChatContext } from '@/features/chat/context/chat-context' type RailElement = { icon: AvailableUnfilledIcon @@ -91,6 +92,8 @@ export const RailLayout = () => { const { view, setLeftMenuShown } = useLayoutContext() + const { markMessagesAsRead } = useChatContext() + const isHistoryView = view === 'history' const railTabs: RailElement[] = useMemo( @@ -163,9 +166,13 @@ export const RailLayout = () => { } // Change the selected tab and make sure it's open openTab((key ?? 'file-tree') as RailTabKey) + + if (key === 'chat') { + markMessagesAsRead() + } } }, - [openTab, togglePane, selectedTab, railTabs] + [openTab, togglePane, selectedTab, railTabs, markMessagesAsRead] ) const isReviewPanelOpen = selectedTab === 'review-panel' From 08c5b11689ff81042930c6779a27ce0ebd55a26a Mon Sep 17 00:00:00 2001 From: David <33458145+davidmcpowell@users.noreply.github.com> Date: Tue, 6 May 2025 11:51:45 +0100 Subject: [PATCH 007/194] Merge pull request #25248 from overleaf/dp-history-text-color Add explicit color for history text GitOrigin-RevId: 4b595ea824d75181c041d44fc48ea81fec864316 --- .../stylesheets/bootstrap-5/pages/editor/history.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/services/web/frontend/stylesheets/bootstrap-5/pages/editor/history.scss b/services/web/frontend/stylesheets/bootstrap-5/pages/editor/history.scss index b8e206b6ae..1caeb22c1d 100644 --- a/services/web/frontend/stylesheets/bootstrap-5/pages/editor/history.scss +++ b/services/web/frontend/stylesheets/bootstrap-5/pages/editor/history.scss @@ -154,6 +154,10 @@ history-root { } } + .history-version-main-details { + color: var(--content-primary); + } + .version-element-within-selected { background-color: var(--bg-light-secondary); border-left: var(--spacing-02) solid var(--green-50); From 0261d701a72d1a2b404f5e52e87890c4de7c411b Mon Sep 17 00:00:00 2001 From: David <33458145+davidmcpowell@users.noreply.github.com> Date: Tue, 6 May 2025 11:51:49 +0100 Subject: [PATCH 008/194] Merge pull request #25238 from overleaf/dp-tooltips Add tooltip to new editor home button GitOrigin-RevId: 91f47659caf64a7ee31ed156d4ee2d5c933e05b8 --- .../components/toolbar/toolbar.tsx | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/services/web/frontend/js/features/ide-redesign/components/toolbar/toolbar.tsx b/services/web/frontend/js/features/ide-redesign/components/toolbar/toolbar.tsx index ed1b2509ff..56c597451e 100644 --- a/services/web/frontend/js/features/ide-redesign/components/toolbar/toolbar.tsx +++ b/services/web/frontend/js/features/ide-redesign/components/toolbar/toolbar.tsx @@ -11,6 +11,7 @@ import { useLayoutContext } from '@/shared/context/layout-context' import BackToEditorButton from '@/features/editor-navigation-toolbar/components/back-to-editor-button' import { useCallback } from 'react' import * as eventTracking from '../../../../infrastructure/event-tracking' +import OLTooltip from '@/features/ui/components/ol/ol-tooltip' export const Toolbar = () => { const { view, setView } = useLayoutContext() @@ -45,12 +46,18 @@ const ToolbarMenus = () => { const { t } = useTranslation() return (
- + + +
) From 42eb4b277989cba3469fc3097648ae8737157aa4 Mon Sep 17 00:00:00 2001 From: David <33458145+davidmcpowell@users.noreply.github.com> Date: Tue, 6 May 2025 11:51:56 +0100 Subject: [PATCH 009/194] Merge pull request #25320 from overleaf/dp-review-panel-shortcut Fix open review panel shortcut in new editor GitOrigin-RevId: 3e4b65ad1f1943574ba937460722912ff382bc39 --- .../features/ide-redesign/contexts/rail-context.tsx | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/services/web/frontend/js/features/ide-redesign/contexts/rail-context.tsx b/services/web/frontend/js/features/ide-redesign/contexts/rail-context.tsx index 51c797fa1d..c02d17fb9b 100644 --- a/services/web/frontend/js/features/ide-redesign/contexts/rail-context.tsx +++ b/services/web/frontend/js/features/ide-redesign/contexts/rail-context.tsx @@ -1,4 +1,5 @@ import useCollapsiblePanel from '@/features/ide-react/hooks/use-collapsible-panel' +import useEventListener from '@/shared/hooks/use-event-listener' import { createContext, Dispatch, @@ -77,6 +78,17 @@ export const RailProvider: FC = ({ children }) => { [setIsOpen, setSelectedTab] ) + useEventListener( + 'ui.toggle-review-panel', + useCallback(() => { + if (isOpen && selectedTab === 'review-panel') { + handlePaneCollapse() + } else { + openTab('review-panel') + } + }, [handlePaneCollapse, selectedTab, isOpen, openTab]) + ) + const value = useMemo( () => ({ selectedTab, From aa97dbdbb6ee92f2a828172b0341398797f5b511 Mon Sep 17 00:00:00 2001 From: Tim Down <158919+timdown@users.noreply.github.com> Date: Tue, 6 May 2025 11:53:20 +0100 Subject: [PATCH 010/194] Merge pull request #25269 from overleaf/td-flaky-tags-test Add waits for flaky tag list test GitOrigin-RevId: 9d0bf2acd54d07e96fe6837296176e62bf981947 --- .../components/sidebar/tags-list.test.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/services/web/test/frontend/features/project-list/components/sidebar/tags-list.test.tsx b/services/web/test/frontend/features/project-list/components/sidebar/tags-list.test.tsx index b8e5768c99..7d68a8f7fe 100644 --- a/services/web/test/frontend/features/project-list/components/sidebar/tags-list.test.tsx +++ b/services/web/test/frontend/features/project-list/components/sidebar/tags-list.test.tsx @@ -41,20 +41,20 @@ describe('', function () { fetchMock.removeRoutes().clearHistory() }) - it('displays the tags list', function () { - const header = screen.getByTestId('organize-projects') + it('displays the tags list', async function () { + const header = await screen.findByTestId('organize-projects') expect(header.textContent).to.equal('Organize Tags') - screen.getByRole('button', { + await screen.findByRole('button', { name: 'New Tag', }) - screen.getByRole('button', { + await screen.findByRole('button', { name: 'Tag 1 (1)', }) - screen.getByRole('button', { + await screen.findByRole('button', { name: 'Another tag (2)', }) - screen.getByRole('button', { + await screen.findByRole('button', { name: 'Uncategorized (3)', }) }) From 5ce1685b5b5a58a46c7a1ae26ed00e0b9f5dc9fb Mon Sep 17 00:00:00 2001 From: Jakob Ackermann Date: Tue, 6 May 2025 13:32:00 +0200 Subject: [PATCH 011/194] [clsi-cache] shard each zone into three instances (#25301) * [clsi-cache] shard per zone into three instances Keep the old instance as read fallback. We can remove it in 4 days. Disk size: 2Ti gives us the maximum write throughput of 240MiB/s on a N2D instance with fewer than 8 vCPUs. * [clsi] fix format * [k8s] clsi-cache: bring back storage-classes * [k8s] clsi-cache: fix reference to zonal storage-classes * [k8s] clsi-cache: add logging configs * [clsi] improve sharding Co-authored-by: Brian Gough * [clsi] fix sharding Index needs to be positive. * [clsi] fix sharding The random part is static per machine/process. * [clsi] restrict clsi-cache to user projects Co-authored-by: Brian Gough * [k8s] clsi-cache: align CLSI_CACHE_NGINX_HOST with service LB --------- Co-authored-by: Brian Gough GitOrigin-RevId: 1efb1b3245c8194c305420b25e774ea735251fb3 --- services/clsi/app/js/CLSICacheHandler.js | 28 ++++++++++++++++--- services/clsi/app/js/CompileController.js | 4 ++- services/clsi/config/settings.defaults.js | 12 ++++++-- .../test/unit/js/CompileControllerTests.js | 8 ++++++ .../Features/Compile/ClsiCacheController.js | 7 +++-- .../src/Features/Compile/ClsiCacheHandler.js | 21 +++++++------- .../src/Features/Compile/ClsiCacheManager.js | 16 ++++++----- .../app/src/Features/Compile/ClsiManager.js | 2 ++ .../src/Features/Compile/CompileController.js | 4 ++- .../src/Features/Compile/CompileManager.js | 3 ++ .../support/shared/commands/compile.ts | 5 ++-- .../js/features/pdf-preview/util/file-list.ts | 3 +- .../features/pdf-preview/util/output-files.js | 1 + .../pdf-preview/util/pdf-caching-transport.js | 6 ++-- services/web/types/compile.ts | 1 + 15 files changed, 87 insertions(+), 34 deletions(-) diff --git a/services/clsi/app/js/CLSICacheHandler.js b/services/clsi/app/js/CLSICacheHandler.js index de6f512987..b9415ae3ec 100644 --- a/services/clsi/app/js/CLSICacheHandler.js +++ b/services/clsi/app/js/CLSICacheHandler.js @@ -20,6 +20,19 @@ const TIMING_BUCKETS = [ 0, 10, 100, 1000, 2000, 5000, 10000, 15000, 20000, 30000, ] const MAX_ENTRIES_IN_OUTPUT_TAR = 100 +const OBJECT_ID_REGEX = /^[0-9a-f]{24}$/ + +/** + * @param {string} projectId + * @return {{shard: string, url: string}} + */ +function getShard(projectId) { + // [timestamp 4bytes][random per machine 5bytes][counter 3bytes] + // [32bit 4bytes] + const last4Bytes = Buffer.from(projectId, 'hex').subarray(8, 12) + const idx = last4Bytes.readUInt32BE() % Settings.apis.clsiCache.shards.length + return Settings.apis.clsiCache.shards[idx] +} /** * @param {string} projectId @@ -29,6 +42,7 @@ const MAX_ENTRIES_IN_OUTPUT_TAR = 100 * @param {[{path: string}]} outputFiles * @param {string} compileGroup * @param {Record} options + * @return {string | undefined} */ function notifyCLSICacheAboutBuild({ projectId, @@ -39,14 +53,16 @@ function notifyCLSICacheAboutBuild({ compileGroup, options, }) { - if (!Settings.apis.clsiCache.enabled) return + if (!Settings.apis.clsiCache.enabled) return undefined + if (!OBJECT_ID_REGEX.test(projectId)) return undefined + const { url, shard } = getShard(projectId) /** * @param {[{path: string}]} files */ const enqueue = files => { Metrics.count('clsi_cache_enqueue_files', files.length) - fetchNothing(`${Settings.apis.clsiCache.url}/enqueue`, { + fetchNothing(`${url}/enqueue`, { method: 'POST', json: { projectId, @@ -97,6 +113,8 @@ function notifyCLSICacheAboutBuild({ 'build output.tar.gz for clsi cache failed' ) }) + + return shard } /** @@ -155,6 +173,7 @@ async function downloadOutputDotSynctexFromCompileCache( outputDir ) { if (!Settings.apis.clsiCache.enabled) return false + if (!OBJECT_ID_REGEX.test(projectId)) return false const timer = new Metrics.Timer( 'clsi_cache_download', @@ -165,7 +184,7 @@ async function downloadOutputDotSynctexFromCompileCache( let stream try { stream = await fetchStream( - `${Settings.apis.clsiCache.url}/project/${projectId}/${ + `${getShard(projectId).url}/project/${projectId}/${ userId ? `user/${userId}/` : '' }build/${editorId}-${buildId}/search/output/output.synctex.gz`, { @@ -205,8 +224,9 @@ async function downloadOutputDotSynctexFromCompileCache( */ async function downloadLatestCompileCache(projectId, userId, compileDir) { if (!Settings.apis.clsiCache.enabled) return false + if (!OBJECT_ID_REGEX.test(projectId)) return false - const url = `${Settings.apis.clsiCache.url}/project/${projectId}/${ + const url = `${getShard(projectId).url}/project/${projectId}/${ userId ? `user/${userId}/` : '' }latest/output/output.tar.gz` const timer = new Metrics.Timer( diff --git a/services/clsi/app/js/CompileController.js b/services/clsi/app/js/CompileController.js index 87a7db6ec2..c698ee2b75 100644 --- a/services/clsi/app/js/CompileController.js +++ b/services/clsi/app/js/CompileController.js @@ -112,12 +112,13 @@ function compile(req, res, next) { buildId = error.buildId } + let clsiCacheShard if ( status === 'success' && request.editorId && request.populateClsiCache ) { - notifyCLSICacheAboutBuild({ + clsiCacheShard = notifyCLSICacheAboutBuild({ projectId: request.project_id, userId: request.user_id, buildId: outputFiles[0].build, @@ -144,6 +145,7 @@ function compile(req, res, next) { stats, timings, buildId, + clsiCacheShard, outputUrlPrefix: Settings.apis.clsi.outputUrlPrefix, outputFiles: outputFiles.map(file => ({ url: diff --git a/services/clsi/config/settings.defaults.js b/services/clsi/config/settings.defaults.js index 6f16e01a89..17042498db 100644 --- a/services/clsi/config/settings.defaults.js +++ b/services/clsi/config/settings.defaults.js @@ -60,9 +60,15 @@ module.exports = { }`, }, clsiCache: { - enabled: !!process.env.CLSI_CACHE_HOST, - url: `http://${process.env.CLSI_CACHE_HOST}:3044`, - downloadURL: `http://${process.env.CLSI_CACHE_NGINX_HOST || process.env.CLSI_CACHE_HOST}:8080`, + enabled: !!(process.env.CLSI_CACHE_SHARDS || process.env.CLSI_CACHE_HOST), + shards: process.env.CLSI_CACHE_SHARDS + ? JSON.parse(process.env.CLSI_CACHE_SHARDS) + : [ + { + url: `http://${process.env.CLSI_CACHE_HOST}:3044`, + shard: 'cache', + }, + ], }, }, diff --git a/services/clsi/test/unit/js/CompileControllerTests.js b/services/clsi/test/unit/js/CompileControllerTests.js index e6d21aed9f..506b5f02dd 100644 --- a/services/clsi/test/unit/js/CompileControllerTests.js +++ b/services/clsi/test/unit/js/CompileControllerTests.js @@ -129,6 +129,7 @@ describe('CompileController', function () { url: `${this.Settings.apis.clsi.url}/project/${this.project_id}/build/${file.build}/output/${file.path}`, ...file, })), + clsiCacheShard: undefined, }, }) .should.equal(true) @@ -156,6 +157,7 @@ describe('CompileController', function () { url: `${this.Settings.apis.clsi.url}/project/${this.project_id}/build/${file.build}/output/${file.path}`, ...file, })), + clsiCacheShard: undefined, }, }) .should.equal(true) @@ -203,6 +205,7 @@ describe('CompileController', function () { url: `${this.Settings.apis.clsi.url}/project/${this.project_id}/build/${file.build}/output/${file.path}`, ...file, })), + clsiCacheShard: undefined, }, }) }) @@ -250,6 +253,7 @@ describe('CompileController', function () { url: `${this.Settings.apis.clsi.url}/project/${this.project_id}/build/${file.build}/output/${file.path}`, ...file, })), + clsiCacheShard: undefined, }, }) }) @@ -281,6 +285,7 @@ describe('CompileController', function () { buildId: this.buildId, stats: this.stats, timings: this.timings, + clsiCacheShard: undefined, }, }) .should.equal(true) @@ -315,6 +320,7 @@ describe('CompileController', function () { timings: this.timings, // JSON.stringify will omit these undefined values buildId: undefined, + clsiCacheShard: undefined, }, }) .should.equal(true) @@ -348,6 +354,7 @@ describe('CompileController', function () { timings: this.timings, // JSON.stringify will omit these undefined values buildId: undefined, + clsiCacheShard: undefined, }, }) .should.equal(true) @@ -379,6 +386,7 @@ describe('CompileController', function () { timings: this.timings, // JSON.stringify will omit these undefined values buildId: undefined, + clsiCacheShard: undefined, }, }) .should.equal(true) diff --git a/services/web/app/src/Features/Compile/ClsiCacheController.js b/services/web/app/src/Features/Compile/ClsiCacheController.js index 9795fd3ef2..42f037985d 100644 --- a/services/web/app/src/Features/Compile/ClsiCacheController.js +++ b/services/web/app/src/Features/Compile/ClsiCacheController.js @@ -110,8 +110,8 @@ async function getLatestBuildFromCache(req, res) { const userId = CompileController._getUserIdForCompile(req) try { const { - internal: { location: metaLocation, zone }, - external: { isUpToDate, allFiles }, + internal: { location: metaLocation }, + external: { isUpToDate, allFiles, zone, shard }, } = await ClsiCacheManager.getLatestBuildFromCache( projectId, userId, @@ -153,7 +153,7 @@ async function getLatestBuildFromCache(req, res) { size, editorId, }) - if (clsiServerId !== 'cache') { + if (clsiServerId !== shard) { // Enable PDF caching and attempt to download from VM first. // (clsi VMs do not have the editorId in the path on disk, omit it). Object.assign(f, { @@ -174,6 +174,7 @@ async function getLatestBuildFromCache(req, res) { outputFiles, compileGroup, clsiServerId, + clsiCacheShard: shard, pdfDownloadDomain, pdfCachingMinChunkSize, options, diff --git a/services/web/app/src/Features/Compile/ClsiCacheHandler.js b/services/web/app/src/Features/Compile/ClsiCacheHandler.js index 54ebd9e259..76b5d50f12 100644 --- a/services/web/app/src/Features/Compile/ClsiCacheHandler.js +++ b/services/web/app/src/Features/Compile/ClsiCacheHandler.js @@ -41,7 +41,7 @@ async function clearCache(projectId, userId) { path += '/output' await Promise.all( - Settings.apis.clsiCache.instances.map(async ({ url, zone }) => { + Settings.apis.clsiCache.instances.map(async ({ url, shard }) => { const u = new URL(url) u.pathname = path try { @@ -50,7 +50,7 @@ async function clearCache(projectId, userId) { signal: AbortSignal.timeout(15_000), }) } catch (err) { - throw OError.tag(err, 'clear clsi-cache', { url, zone }) + throw OError.tag(err, 'clear clsi-cache', { url, shard }) } }) ) @@ -64,7 +64,7 @@ async function clearCache(projectId, userId) { * @param buildId * @param filename * @param signal - * @return {Promise<{size: number, zone: string, location: string, lastModified: Date, allFiles: string[]}>} + * @return {Promise<{size: number, zone: string, shard: string, location: string, lastModified: Date, allFiles: string[]}>} */ async function getOutputFile( projectId, @@ -93,7 +93,7 @@ async function getOutputFile( * @param userId * @param filename * @param signal - * @return {Promise<{size: number, zone: string, location: string, lastModified: Date, allFiles: string[]}>} + * @return {Promise<{size: number, zone: string, shard: string, location: string, lastModified: Date, allFiles: string[]}>} */ async function getLatestOutputFile( projectId, @@ -125,7 +125,7 @@ async function getLatestOutputFile( * @param userId * @param path * @param signal - * @return {Promise<{size: number, zone: string, location: string, lastModified: Date, allFiles: string[]}>} + * @return {Promise<{size: number, zone: string, shard: string, location: string, lastModified: Date, allFiles: string[]}>} */ async function getRedirectWithFallback( projectId, @@ -135,7 +135,7 @@ async function getRedirectWithFallback( ) { // Avoid hitting the same instance first all the time. const instances = _.shuffle(Settings.apis.clsiCache.instances) - for (const { url, zone } of instances) { + for (const { url, shard } of instances) { const u = new URL(url) u.pathname = path try { @@ -149,6 +149,7 @@ async function getRedirectWithFallback( return { location, zone: headers.get('X-Zone'), + shard: headers.get('X-Shard') || 'cache', lastModified: new Date(headers.get('X-Last-Modified')), size: parseInt(headers.get('X-Content-Length'), 10), allFiles: JSON.parse(headers.get('X-All-Files')), @@ -158,7 +159,7 @@ async function getRedirectWithFallback( break // No clsi-cache instance has cached something for this project/user. } logger.warn( - { err, projectId, userId, url, zone }, + { err, projectId, userId, url, shard }, 'getLatestOutputFile from clsi-cache failed' ) // This clsi-cache instance is down, try the next backend. @@ -178,18 +179,18 @@ async function getRedirectWithFallback( * @param templateId * @param templateVersionId * @param lastUpdated - * @param zone + * @param shard * @param signal * @return {Promise} */ async function prepareCacheSource( projectId, userId, - { sourceProjectId, templateId, templateVersionId, lastUpdated, zone, signal } + { sourceProjectId, templateId, templateVersionId, lastUpdated, shard, signal } ) { const url = new URL( `/project/${projectId}/user/${userId}/import-from`, - Settings.apis.clsiCache.instances.find(i => i.zone === zone).url + Settings.apis.clsiCache.instances.find(i => i.shard === shard).url ) try { await fetchNothing(url, { diff --git a/services/web/app/src/Features/Compile/ClsiCacheManager.js b/services/web/app/src/Features/Compile/ClsiCacheManager.js index 3fe4b987c5..cf0665af56 100644 --- a/services/web/app/src/Features/Compile/ClsiCacheManager.js +++ b/services/web/app/src/Features/Compile/ClsiCacheManager.js @@ -1,9 +1,11 @@ +const _ = require('lodash') const { NotFoundError } = require('../Errors/Errors') const ClsiCacheHandler = require('./ClsiCacheHandler') const DocumentUpdaterHandler = require('../DocumentUpdater/DocumentUpdaterHandler') const ProjectGetter = require('../Project/ProjectGetter') const SplitTestHandler = require('../SplitTests/SplitTestHandler') const UserGetter = require('../User/UserGetter') +const Settings = require('@overleaf/settings') /** * Get the most recent build and metadata @@ -14,11 +16,11 @@ const UserGetter = require('../User/UserGetter') * @param userId * @param filename * @param signal - * @return {Promise<{internal: {zone: string, location: string}, external: {isUpToDate: boolean, lastUpdated: Date, size: number, allFiles: string[]}}>} + * @return {Promise<{internal: {location: string}, external: {zone: string, shard: string, isUpToDate: boolean, lastUpdated: Date, size: number, allFiles: string[]}}>} */ async function getLatestBuildFromCache(projectId, userId, filename, signal) { const [ - { location, lastModified: lastCompiled, zone, size, allFiles }, + { location, lastModified: lastCompiled, zone, shard, size, allFiles }, lastUpdatedInRedis, { lastUpdated: lastUpdatedInMongo }, ] = await Promise.all([ @@ -36,13 +38,14 @@ async function getLatestBuildFromCache(projectId, userId, filename, signal) { return { internal: { location, - zone, }, external: { isUpToDate, lastUpdated, size, allFiles, + shard, + zone, }, } } @@ -73,12 +76,11 @@ async function prepareClsiCache( const signal = AbortSignal.timeout(5_000) let lastUpdated - let zone = 'b' // populate template data on zone b + let shard = _.shuffle(Settings.apis.clsiCache.instances)[0].shard if (sourceProjectId) { try { ;({ - internal: { zone }, - external: { lastUpdated }, + external: { lastUpdated, shard }, } = await getLatestBuildFromCache( sourceProjectId, userId, @@ -95,7 +97,7 @@ async function prepareClsiCache( sourceProjectId, templateId, templateVersionId, - zone, + shard, lastUpdated, signal, }) diff --git a/services/web/app/src/Features/Compile/ClsiManager.js b/services/web/app/src/Features/Compile/ClsiManager.js index 2e32aa9622..6f11297248 100644 --- a/services/web/app/src/Features/Compile/ClsiManager.js +++ b/services/web/app/src/Features/Compile/ClsiManager.js @@ -207,6 +207,7 @@ async function _sendBuiltRequest(projectId, userId, req, options, callback) { stats: compile.stats, timings: compile.timings, outputUrlPrefix: compile.outputUrlPrefix, + clsiCacheShard: compile.clsiCacheShard, } } @@ -853,6 +854,7 @@ module.exports = { 'timings', 'outputUrlPrefix', 'buildId', + 'clsiCacheShard', ]), sendExternalRequest: callbackifyMultiResult(sendExternalRequest, [ 'status', diff --git a/services/web/app/src/Features/Compile/CompileController.js b/services/web/app/src/Features/Compile/CompileController.js index 5d2bbcda3e..9fb9d502a4 100644 --- a/services/web/app/src/Features/Compile/CompileController.js +++ b/services/web/app/src/Features/Compile/CompileController.js @@ -192,7 +192,8 @@ module.exports = CompileController = { stats, timings, outputUrlPrefix, - buildId + buildId, + clsiCacheShard ) => { if (error) { Metrics.inc('compile-error') @@ -236,6 +237,7 @@ module.exports = CompileController = { outputFilesArchive, compileGroup: limits?.compileGroup, clsiServerId, + clsiCacheShard, validationProblems, stats, timings, diff --git a/services/web/app/src/Features/Compile/CompileManager.js b/services/web/app/src/Features/Compile/CompileManager.js index 9b5404865a..974f573815 100644 --- a/services/web/app/src/Features/Compile/CompileManager.js +++ b/services/web/app/src/Features/Compile/CompileManager.js @@ -86,6 +86,7 @@ async function compile(projectId, userId, options = {}) { timings, outputUrlPrefix, buildId, + clsiCacheShard, } = await ClsiManager.promises.sendRequest(projectId, compileAsUser, options) return { @@ -98,6 +99,7 @@ async function compile(projectId, userId, options = {}) { timings, outputUrlPrefix, buildId, + clsiCacheShard, } } @@ -184,6 +186,7 @@ module.exports = CompileManager = { 'timings', 'outputUrlPrefix', 'buildId', + 'clsiCacheShard', ]), stopCompile: callbackify(stopCompile), diff --git a/services/web/cypress/support/shared/commands/compile.ts b/services/web/cypress/support/shared/commands/compile.ts index 9f7273c403..44ee9c0805 100644 --- a/services/web/cypress/support/shared/commands/compile.ts +++ b/services/web/cypress/support/shared/commands/compile.ts @@ -48,6 +48,7 @@ const compileFromCacheResponse = () => { fromCache: true, status: 'success', clsiServerId: 'foo', + clsiCacheShard: 'clsi-cache-zone-b-shard-1', compileGroup: 'priority', pdfDownloadDomain: 'https://clsi.test-overleaf.com', outputFiles: outputFiles(), @@ -166,10 +167,10 @@ export const waitForCompileOutput = ({ } = {}) => { cy.wait(`@${prefix}-log`) .its('request.query.clsiserverid') - .should('eq', cached ? 'cache' : 'foo') // straight from cache if cached + .should('eq', cached ? 'clsi-cache-zone-b-shard-1' : 'foo') // straight from cache if cached cy.wait(`@${prefix}-blg`) .its('request.query.clsiserverid') - .should('eq', cached ? 'cache' : 'foo') // straight from cache if cached + .should('eq', cached ? 'clsi-cache-zone-b-shard-1' : 'foo') // straight from cache if cached if (pdf) { cy.wait(`@${prefix}-pdf`) .its('request.query.clsiserverid') diff --git a/services/web/frontend/js/features/pdf-preview/util/file-list.ts b/services/web/frontend/js/features/pdf-preview/util/file-list.ts index 310fbb55fb..a8a37a9e2b 100644 --- a/services/web/frontend/js/features/pdf-preview/util/file-list.ts +++ b/services/web/frontend/js/features/pdf-preview/util/file-list.ts @@ -13,6 +13,7 @@ export function buildFileList( outputFiles: Map, { clsiServerId, + clsiCacheShard, compileGroup, outputFilesArchive, fromCache = false, @@ -24,7 +25,7 @@ export function buildFileList( const params = new URLSearchParams() if (fromCache) { - params.set('clsiserverid', 'cache') + params.set('clsiserverid', clsiCacheShard || 'cache') } else if (clsiServerId) { params.set('clsiserverid', clsiServerId) } diff --git a/services/web/frontend/js/features/pdf-preview/util/output-files.js b/services/web/frontend/js/features/pdf-preview/util/output-files.js index 6c93a02368..3ee0dc1180 100644 --- a/services/web/frontend/js/features/pdf-preview/util/output-files.js +++ b/services/web/frontend/js/features/pdf-preview/util/output-files.js @@ -17,6 +17,7 @@ export function handleOutputFiles(outputFiles, projectId, data) { if (!outputFile) return null outputFile.editorId = outputFile.editorId || EDITOR_SESSION_ID + outputFile.clsiCacheShard = data.clsiCacheShard || 'cache' // build the URL for viewing the PDF in the preview UI const params = new URLSearchParams() diff --git a/services/web/frontend/js/features/pdf-preview/util/pdf-caching-transport.js b/services/web/frontend/js/features/pdf-preview/util/pdf-caching-transport.js index 4497b57398..f568c634a4 100644 --- a/services/web/frontend/js/features/pdf-preview/util/pdf-caching-transport.js +++ b/services/web/frontend/js/features/pdf-preview/util/pdf-caching-transport.js @@ -116,7 +116,9 @@ export function generatePdfCachingTransportFactory() { return ( u.pathname.endsWith( `build/${this.pdfFile.editorId}-${this.pdfFile.build}/output/output.pdf` - ) && u.searchParams.get('clsiserverid') === 'cache' + ) && + (u.searchParams.get('clsiserverid') === 'cache' || + u.searchParams.get('clsiserverid')?.startsWith('clsi-cache-')) ) } const canTryFromCache = err => { @@ -127,7 +129,7 @@ export function generatePdfCachingTransportFactory() { const getOutputPDFURLFromCache = () => { if (usesCache(this.url)) return this.url const u = new URL(this.url) - u.searchParams.set('clsiserverid', 'cache') + u.searchParams.set('clsiserverid', this.pdfFile.clsiCacheShard) u.pathname = u.pathname.replace( /build\/[a-f0-9-]+\//, `build/${this.pdfFile.editorId}-${this.pdfFile.build}/` diff --git a/services/web/types/compile.ts b/services/web/types/compile.ts index 541d03149c..3038893529 100644 --- a/services/web/types/compile.ts +++ b/services/web/types/compile.ts @@ -23,6 +23,7 @@ export type CompileResponseData = { outputFiles: CompileOutputFile[] compileGroup?: string clsiServerId?: string + clsiCacheShard?: string pdfDownloadDomain?: string pdfCachingMinChunkSize: number validationProblems: any From c3368167d051abaaa61c7fcf27f8b79795518859 Mon Sep 17 00:00:00 2001 From: Alf Eaton Date: Tue, 6 May 2025 13:43:11 +0100 Subject: [PATCH 012/194] Remove z-index from outline elements (#25265) GitOrigin-RevId: 39b85a478b71bf42ebb6b886b6ae1b4ed6557570 --- .../frontend/stylesheets/bootstrap-5/pages/editor/outline.scss | 2 -- 1 file changed, 2 deletions(-) diff --git a/services/web/frontend/stylesheets/bootstrap-5/pages/editor/outline.scss b/services/web/frontend/stylesheets/bootstrap-5/pages/editor/outline.scss index dc83a234bc..cc815ea058 100644 --- a/services/web/frontend/stylesheets/bootstrap-5/pages/editor/outline.scss +++ b/services/web/frontend/stylesheets/bootstrap-5/pages/editor/outline.scss @@ -281,7 +281,6 @@ font-size: inherit; vertical-align: inherit; position: relative; - z-index: 1; color: var(--outline-item-carat-color); margin-right: calc(var(--spacing-03) * -1); border-radius: var(--border-radius-base); @@ -304,7 +303,6 @@ background-color: transparent; border: 0; position: relative; - z-index: 1; padding: 0 var(--spacing-03); line-height: var(--spacing-08); border-radius: var(--border-radius-base); From bfe42734bcc152c2deb778a22a81fb6fe0a5816c Mon Sep 17 00:00:00 2001 From: Alf Eaton Date: Tue, 6 May 2025 13:43:33 +0100 Subject: [PATCH 013/194] Merge pull request #25261 from overleaf/ae-textlayer-layer Move `will-change: transform` to textLayer GitOrigin-RevId: 15fdd919da54ed95e115d664156066e6fda36982 --- .../stylesheets/bootstrap-5/pages/editor/pdf.scss | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/services/web/frontend/stylesheets/bootstrap-5/pages/editor/pdf.scss b/services/web/frontend/stylesheets/bootstrap-5/pages/editor/pdf.scss index 8649eacd1c..df5c9e2b77 100644 --- a/services/web/frontend/stylesheets/bootstrap-5/pages/editor/pdf.scss +++ b/services/web/frontend/stylesheets/bootstrap-5/pages/editor/pdf.scss @@ -193,7 +193,6 @@ div.pdf-canvas { background: white; box-shadow: 0 0 10px rgb(0 0 0 / 50%); - will-change: transform; } div.pdf-canvas.pdfng-empty { @@ -237,6 +236,18 @@ outline: none; } + /* Avoid slowdown in Safari when text layers are reset on selection change */ + /* stylelint-disable-next-line selector-class-pattern */ + .textLayer { + will-change: transform; + } + + /* Avoid multiple small layers within annotation layer */ + /* stylelint-disable-next-line selector-class-pattern */ + .annotationLayer { + will-change: transform; + } + /* Avoids https://github.com/mozilla/pdf.js/issues/13840 in Chrome */ /* stylelint-disable-next-line selector-class-pattern */ .textLayer br::selection { From 6881ba956a062cbf0e1965d4f79e26dc4051e831 Mon Sep 17 00:00:00 2001 From: Jakob Ackermann Date: Tue, 6 May 2025 15:19:59 +0200 Subject: [PATCH 014/194] [clsi-cache] only use sharding from updated project editor tabs (#25326) GitOrigin-RevId: 1754276bed3186c0536055c983e32476cc90d416 --- services/clsi/app/js/CLSICacheHandler.js | 9 ++++++++- services/clsi/app/js/CompileController.js | 1 + services/clsi/app/js/RequestParser.js | 8 ++++++++ services/clsi/config/settings.defaults.js | 1 + services/web/app/src/Features/Compile/ClsiManager.js | 1 + .../web/app/src/Features/Compile/CompileController.js | 2 ++ .../frontend/js/features/pdf-preview/util/compiler.js | 1 + .../web/test/unit/src/Compile/CompileControllerTests.js | 4 ++++ 8 files changed, 26 insertions(+), 1 deletion(-) diff --git a/services/clsi/app/js/CLSICacheHandler.js b/services/clsi/app/js/CLSICacheHandler.js index b9415ae3ec..73137d23c3 100644 --- a/services/clsi/app/js/CLSICacheHandler.js +++ b/services/clsi/app/js/CLSICacheHandler.js @@ -41,6 +41,7 @@ function getShard(projectId) { * @param {string} editorId * @param {[{path: string}]} outputFiles * @param {string} compileGroup + * @param {boolean} clsiCacheSharded * @param {Record} options * @return {string | undefined} */ @@ -51,11 +52,17 @@ function notifyCLSICacheAboutBuild({ editorId, outputFiles, compileGroup, + clsiCacheSharded, options, }) { if (!Settings.apis.clsiCache.enabled) return undefined if (!OBJECT_ID_REGEX.test(projectId)) return undefined - const { url, shard } = getShard(projectId) + let { url, shard } = getShard(projectId) + if (!clsiCacheSharded) { + // Client is not aware of sharding yet. + url = Settings.apis.clsiCache.url + shard = 'cache' + } /** * @param {[{path: string}]} files diff --git a/services/clsi/app/js/CompileController.js b/services/clsi/app/js/CompileController.js index c698ee2b75..138801890a 100644 --- a/services/clsi/app/js/CompileController.js +++ b/services/clsi/app/js/CompileController.js @@ -125,6 +125,7 @@ function compile(req, res, next) { editorId: request.editorId, outputFiles, compileGroup: request.compileGroup, + clsiCacheSharded: request.clsiCacheSharded, options: { compiler: request.compiler, draft: request.draft, diff --git a/services/clsi/app/js/RequestParser.js b/services/clsi/app/js/RequestParser.js index 4e9d722921..f65d6940c8 100644 --- a/services/clsi/app/js/RequestParser.js +++ b/services/clsi/app/js/RequestParser.js @@ -90,6 +90,14 @@ function parse(body, callback) { type: 'boolean', } ) + response.clsiCacheSharded = _parseAttribute( + 'clsiCacheSharded', + compile.options.clsiCacheSharded, + { + default: false, + type: 'boolean', + } + ) response.check = _parseAttribute('check', compile.options.check, { type: 'string', }) diff --git a/services/clsi/config/settings.defaults.js b/services/clsi/config/settings.defaults.js index 17042498db..614644ac7b 100644 --- a/services/clsi/config/settings.defaults.js +++ b/services/clsi/config/settings.defaults.js @@ -61,6 +61,7 @@ module.exports = { }, clsiCache: { enabled: !!(process.env.CLSI_CACHE_SHARDS || process.env.CLSI_CACHE_HOST), + url: `http://${process.env.CLSI_CACHE_HOST}:3044`, shards: process.env.CLSI_CACHE_SHARDS ? JSON.parse(process.env.CLSI_CACHE_SHARDS) : [ diff --git a/services/web/app/src/Features/Compile/ClsiManager.js b/services/web/app/src/Features/Compile/ClsiManager.js index 6f11297248..011ba60759 100644 --- a/services/web/app/src/Features/Compile/ClsiManager.js +++ b/services/web/app/src/Features/Compile/ClsiManager.js @@ -781,6 +781,7 @@ function _finaliseRequest(projectId, options, project, docs, files) { imageName: project.imageName, draft: Boolean(options.draft), stopOnFirstError: Boolean(options.stopOnFirstError), + clsiCacheSharded: Boolean(options.clsiCacheSharded), check: options.check, syncType: options.syncType, syncState: options.syncState, diff --git a/services/web/app/src/Features/Compile/CompileController.js b/services/web/app/src/Features/Compile/CompileController.js index 9fb9d502a4..f1d37e7638 100644 --- a/services/web/app/src/Features/Compile/CompileController.js +++ b/services/web/app/src/Features/Compile/CompileController.js @@ -132,12 +132,14 @@ module.exports = CompileController = { const isAutoCompile = !!req.query.auto_compile const fileLineErrors = !!req.query.file_line_errors const stopOnFirstError = !!req.body.stopOnFirstError + const clsiCacheSharded = !!req.body.clsiCacheSharded const userId = SessionManager.getLoggedInUserId(req.session) const options = { isAutoCompile, fileLineErrors, stopOnFirstError, editorId: req.body.editorId, + clsiCacheSharded, } if (req.body.rootDoc_id) { diff --git a/services/web/frontend/js/features/pdf-preview/util/compiler.js b/services/web/frontend/js/features/pdf-preview/util/compiler.js index d938cb3893..cd052cb5a9 100644 --- a/services/web/frontend/js/features/pdf-preview/util/compiler.js +++ b/services/web/frontend/js/features/pdf-preview/util/compiler.js @@ -110,6 +110,7 @@ export default class DocumentCompiler { incrementalCompilesEnabled: !this.error, stopOnFirstError: options.stopOnFirstError, editorId: EDITOR_SESSION_ID, + clsiCacheSharded: true, } const data = await postJSON( diff --git a/services/web/test/unit/src/Compile/CompileControllerTests.js b/services/web/test/unit/src/Compile/CompileControllerTests.js index aefa197a17..07e433b5af 100644 --- a/services/web/test/unit/src/Compile/CompileControllerTests.js +++ b/services/web/test/unit/src/Compile/CompileControllerTests.js @@ -250,6 +250,7 @@ describe('CompileController', function () { fileLineErrors: false, stopOnFirstError: false, editorId: undefined, + clsiCacheSharded: false, } ) }) @@ -293,6 +294,7 @@ describe('CompileController', function () { fileLineErrors: false, stopOnFirstError: false, editorId: undefined, + clsiCacheSharded: false, } ) }) @@ -318,6 +320,7 @@ describe('CompileController', function () { fileLineErrors: false, stopOnFirstError: false, editorId: undefined, + clsiCacheSharded: false, } ) }) @@ -342,6 +345,7 @@ describe('CompileController', function () { fileLineErrors: false, stopOnFirstError: false, editorId: 'the-editor-id', + clsiCacheSharded: false, } ) }) From f0856c862f0939ceb3635eefa8bd557a9d213d3a Mon Sep 17 00:00:00 2001 From: Antoine Clausse Date: Tue, 6 May 2025 16:18:02 +0200 Subject: [PATCH 015/194] [web] Migrate `two-factor-authentication` module to BS5 (#25181) * Delete unused file error.pug * Revert-me: Enable 2FA locally * Migrate 2FA pages to BS5 * Add BS5 notification classes to hydrate-form.js * Revert "Revert-me: Enable 2FA locally" This reverts commit 2874bedb05e579623e5beb6cf518aa8608808802. * Fix: Re-add .text-capitalize on button * Use `notification` mixin for success state * Append complex notifications with icons in `showMessages` * Keep the BS3 version of the notification in `showMessages`, move the BS5 implementation to `createNotificationFromMessageBS5` Check the Boostrap version with `!window?.Frontend?.['bootstrap-3']`, which is a bit hacky * Update breakpoings in 2FA form to leave more room for error notification * Address PR comments: * Remove useless `createTextNode` * Use `isBootstrap5` * `Setup` -> `Set Up` GitOrigin-RevId: d7285853ea1191da7711b7bada8d65ff064bc27d --- .../js/features/form-helpers/hydrate-form.js | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/services/web/frontend/js/features/form-helpers/hydrate-form.js b/services/web/frontend/js/features/form-helpers/hydrate-form.js index 9c00dd43ae..3febf861b7 100644 --- a/services/web/frontend/js/features/form-helpers/hydrate-form.js +++ b/services/web/frontend/js/features/form-helpers/hydrate-form.js @@ -3,6 +3,7 @@ import { FetchError, postJSON } from '../../infrastructure/fetch-json' import { canSkipCaptcha, validateCaptchaV2 } from './captcha' import inputValidator from './input-validator' import { disableElement, enableElement } from '../utils/disableElement' +import { isBootstrap5 } from '@/features/utils/bootstrap-5' // Form helper(s) to handle: // - Attaching to the relevant form elements @@ -133,6 +134,66 @@ function hideFormElements(formEl) { } } +/** + * Creates a notification element from a message object, with BS5 classes. + * + * @param {Object} message + * @param {'error' | 'success' | 'warning' | 'info'} message.type + * @param {string} message.key + * @param {string} message.text + * @param {string[]} message.hints + * @returns {HTMLDivElement} + */ +function createNotificationFromMessageBS5(message) { + const messageEl = document.createElement('div') + messageEl.className = classNames('mb-3 notification', { + 'notification-type-error': message.type === 'error', + 'notification-type-success': message.type === 'success', + 'notification-type-warning': message.type === 'warning', + 'notification-type-info': message.type === 'info', + }) + messageEl.setAttribute('aria-live', 'assertive') + messageEl.setAttribute('role', message.type === 'error' ? 'alert' : 'status') + + const materialIcon = { + info: 'info', + success: 'check_circle', + error: 'error', + warning: 'warning', + }[message.type] + if (materialIcon) { + const iconEl = document.createElement('div') + iconEl.className = 'notification-icon' + const iconSpan = document.createElement('span') + iconSpan.className = 'material-symbols' + iconSpan.setAttribute('aria-hidden', 'true') + iconSpan.textContent = materialIcon + iconEl.append(iconSpan) + messageEl.append(iconEl) + } + + const contentAndCtaEl = document.createElement('div') + contentAndCtaEl.className = 'notification-content-and-cta' + + const contentEl = document.createElement('div') + contentEl.className = 'notification-content' + contentEl.append(message.text || `Error: ${message.key}`) + + if (message.hints && message.hints.length) { + const listEl = document.createElement('ul') + message.hints.forEach(hint => { + const listItemEl = document.createElement('li') + listItemEl.textContent = hint + listEl.append(listItemEl) + }) + contentEl.append(listEl) + } + contentAndCtaEl.append(contentEl) + messageEl.append(contentAndCtaEl) + + return messageEl +} + // TODO: remove the showMessages function after every form alerts are updated to use the new style // TODO: rename showMessagesNewStyle to showMessages after the above is done function showMessages(formEl, messageBag) { @@ -157,6 +218,9 @@ function showMessages(formEl, messageBag) { customErrorElements.forEach(el => { el.hidden = false }) + } else if (isBootstrap5()) { + const notification = createNotificationFromMessageBS5(message) + messagesEl.append(notification) } else { // No custom error element for key on page, append a new error message const messageEl = document.createElement('div') From 9a2847dbeefc09ca20d1343741d389f12fcb4d08 Mon Sep 17 00:00:00 2001 From: Antoine Clausse Date: Tue, 6 May 2025 16:18:14 +0200 Subject: [PATCH 016/194] [web] Add startup metrics (#25277) * [web] refactor startup sequence The primary objective here is to call loadGlobalBlobs() only once. But to get there, we need to reorder things and add extra try/catch sections to ensure we are not letting the global uncaughtException handler catch startup errors. Co-authored-by: Antoine Clausse * [web] add metrics for startup steps Co-authored-by: Antoine Clausse --------- Co-authored-by: Jakob Ackermann GitOrigin-RevId: c73edea02516e919d55b896588dcd1862835fedf --- libraries/metrics/initialize.js | 4 + services/web/app.mjs | 91 ++++++++++++------- .../web/app/src/infrastructure/Modules.js | 5 + .../web/app/src/infrastructure/Server.mjs | 4 + 4 files changed, 69 insertions(+), 35 deletions(-) diff --git a/libraries/metrics/initialize.js b/libraries/metrics/initialize.js index 1028ee06c3..f1a77666c7 100644 --- a/libraries/metrics/initialize.js +++ b/libraries/metrics/initialize.js @@ -5,6 +5,8 @@ * before any other module to support code instrumentation. */ +const metricsModuleImportStartTime = performance.now() + const APP_NAME = process.env.METRICS_APP_NAME || 'unknown' const BUILD_VERSION = process.env.BUILD_VERSION const ENABLE_PROFILE_AGENT = process.env.ENABLE_PROFILE_AGENT === 'true' @@ -103,3 +105,5 @@ function recordProcessStart() { const metrics = require('.') metrics.inc('process_startup') } + +module.exports = { metricsModuleImportStartTime } diff --git a/services/web/app.mjs b/services/web/app.mjs index 5ece02cf32..1538e60149 100644 --- a/services/web/app.mjs +++ b/services/web/app.mjs @@ -1,5 +1,5 @@ // Metrics must be initialized before importing anything else -import '@overleaf/metrics/initialize.js' +import { metricsModuleImportStartTime } from '@overleaf/metrics/initialize.js' import Modules from './app/src/infrastructure/Modules.js' import metrics from '@overleaf/metrics' @@ -20,6 +20,13 @@ import FileWriter from './app/src/infrastructure/FileWriter.js' import { fileURLToPath } from 'node:url' import Features from './app/src/infrastructure/Features.js' +metrics.gauge( + 'web_startup', + performance.now() - metricsModuleImportStartTime, + 1, + { path: 'imports' } +) + logger.initialize(process.env.METRICS_APP_NAME || 'web') logger.logger.serializers.user = Serializers.user logger.logger.serializers.docs = Serializers.docs @@ -58,6 +65,29 @@ if ( ) } +// handle SIGTERM for graceful shutdown in kubernetes +process.on('SIGTERM', function (signal) { + triggerGracefulShutdown(Server.server, signal) +}) + +const beforeWaitForMongoAndGlobalBlobs = performance.now() +try { + await Promise.all([ + mongodb.connectionPromise, + mongoose.connectionPromise, + HistoryManager.promises.loadGlobalBlobs(), + ]) +} catch (err) { + logger.fatal({ err }, 'Cannot connect to mongo. Exiting.') + process.exit(1) +} +metrics.gauge( + 'web_startup', + performance.now() - beforeWaitForMongoAndGlobalBlobs, + 1, + { path: 'waitForMongoAndGlobalBlobs' } +) + const port = Settings.port || Settings.internal.web.port || 3000 const host = Settings.internal.web.host || '127.0.0.1' if (process.argv[1] === fileURLToPath(import.meta.url)) { @@ -69,42 +99,33 @@ if (process.argv[1] === fileURLToPath(import.meta.url)) { PlansLocator.ensurePlansAreSetupCorrectly() - Promise.all([ - mongodb.connectionPromise, - mongoose.connectionPromise, - HistoryManager.promises.loadGlobalBlobs(), - ]) - .then(async () => { - Server.server.listen(port, host, function () { - logger.debug(`web starting up, listening on ${host}:${port}`) - logger.debug(`${http.globalAgent.maxSockets} sockets enabled`) - // wait until the process is ready before monitoring the event loop - metrics.event_loop.monitor(logger) - }) - QueueWorkers.start() - await Modules.start() - }) - .catch(err => { - logger.fatal({ err }, 'Cannot connect to mongo. Exiting.') - process.exit(1) - }) + Server.server.listen(port, host, function () { + logger.debug(`web starting up, listening on ${host}:${port}`) + logger.debug(`${http.globalAgent.maxSockets} sockets enabled`) + // wait until the process is ready before monitoring the event loop + metrics.event_loop.monitor(logger) + + // Record metrics for the total startup time before listening on HTTP. + metrics.gauge( + 'web_startup', + performance.now() - metricsModuleImportStartTime, + 1, + { path: 'metricsModuleImportToHTTPListen' } + ) + }) + try { + QueueWorkers.start() + } catch (err) { + logger.fatal({ err }, 'failed to start queue processing') + } + try { + await Modules.start() + } catch (err) { + logger.fatal({ err }, 'failed to start web module background jobs') + } } // initialise site admin tasks -Promise.all([ - mongodb.connectionPromise, - mongoose.connectionPromise, - HistoryManager.promises.loadGlobalBlobs(), -]) - .then(() => SiteAdminHandler.initialise()) - .catch(err => { - logger.fatal({ err }, 'Cannot connect to mongo. Exiting.') - process.exit(1) - }) - -// handle SIGTERM for graceful shutdown in kubernetes -process.on('SIGTERM', function (signal) { - triggerGracefulShutdown(Server.server, signal) -}) +SiteAdminHandler.initialise() export default Server.server diff --git a/services/web/app/src/infrastructure/Modules.js b/services/web/app/src/infrastructure/Modules.js index a21be431c4..f746519612 100644 --- a/services/web/app/src/infrastructure/Modules.js +++ b/services/web/app/src/infrastructure/Modules.js @@ -4,6 +4,7 @@ const { promisify, callbackify } = require('util') const Settings = require('@overleaf/settings') const Views = require('./Views') const _ = require('lodash') +const Metrics = require('@overleaf/metrics') const MODULE_BASE_PATH = Path.join(__dirname, '/../../../modules') @@ -15,7 +16,11 @@ let _viewIncludes = {} async function modules() { if (!_modulesLoaded) { + const beforeLoadModules = performance.now() await loadModules() + Metrics.gauge('web_startup', performance.now() - beforeLoadModules, 1, { + path: 'loadModules', + }) } return _modules } diff --git a/services/web/app/src/infrastructure/Server.mjs b/services/web/app/src/infrastructure/Server.mjs index 3c7fd752d6..9e548bdc9e 100644 --- a/services/web/app/src/infrastructure/Server.mjs +++ b/services/web/app/src/infrastructure/Server.mjs @@ -372,6 +372,10 @@ if (Settings.enabledServices.includes('web')) { metrics.injectMetricsRoute(webRouter) metrics.injectMetricsRoute(privateApiRouter) +const beforeRouterInitialize = performance.now() await Router.initialize(webRouter, privateApiRouter, publicApiRouter) +metrics.gauge('web_startup', performance.now() - beforeRouterInitialize, 1, { + path: 'Router.initialize', +}) export default { app, server } From 81941ff335f8ca19ab308c958d5c09c410e9d42d Mon Sep 17 00:00:00 2001 From: Antoine Clausse Date: Tue, 6 May 2025 16:18:47 +0200 Subject: [PATCH 017/194] Update some dependencies so they're compatible with Node 22 (#25317) * `"@google-cloud/profiler": "^6.0.3"` * `bin/npm update pprof` * `bin/npm update nan` * `bin/npm update @google-cloud/profiler` * Ignore false positive of `@typescript-eslint/return-await` > Returning an awaited value that is not a promise is not allowed Though the function was promisified GitOrigin-RevId: 24dbe3e8df2b55c0b9583ac79a61e0956ac3fac0 --- libraries/metrics/package.json | 2 +- libraries/object-persistor/src/FSPersistor.js | 2 + package-lock.json | 1355 ++++------------- 3 files changed, 323 insertions(+), 1036 deletions(-) diff --git a/libraries/metrics/package.json b/libraries/metrics/package.json index 384e58cfe5..19b566c2b0 100644 --- a/libraries/metrics/package.json +++ b/libraries/metrics/package.json @@ -9,7 +9,7 @@ "main": "index.js", "dependencies": { "@google-cloud/opentelemetry-cloud-trace-exporter": "^2.1.0", - "@google-cloud/profiler": "^6.0.0", + "@google-cloud/profiler": "^6.0.3", "@opentelemetry/api": "^1.4.1", "@opentelemetry/auto-instrumentations-node": "^0.39.1", "@opentelemetry/exporter-trace-otlp-http": "^0.41.2", diff --git a/libraries/object-persistor/src/FSPersistor.js b/libraries/object-persistor/src/FSPersistor.js index 01aab72800..38a81407df 100644 --- a/libraries/object-persistor/src/FSPersistor.js +++ b/libraries/object-persistor/src/FSPersistor.js @@ -305,8 +305,10 @@ module.exports = class FSPersistor extends AbstractPersistor { async _listDirectory(path) { if (this.useSubdirectories) { + // eslint-disable-next-line @typescript-eslint/return-await return await glob(Path.join(path, '**')) } else { + // eslint-disable-next-line @typescript-eslint/return-await return await glob(`${path}_*`) } } diff --git a/package-lock.json b/package-lock.json index 639567368b..fe1225f129 100644 --- a/package-lock.json +++ b/package-lock.json @@ -209,7 +209,7 @@ "version": "4.2.0", "dependencies": { "@google-cloud/opentelemetry-cloud-trace-exporter": "^2.1.0", - "@google-cloud/profiler": "^6.0.0", + "@google-cloud/profiler": "^6.0.3", "@opentelemetry/api": "^1.4.1", "@opentelemetry/auto-instrumentations-node": "^0.39.1", "@opentelemetry/exporter-trace-otlp-http": "^0.41.2", @@ -232,6 +232,15 @@ "@overleaf/logger": "*" } }, + "libraries/metrics/node_modules/@opentelemetry/api": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", + "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", + "license": "Apache-2.0", + "engines": { + "node": ">=8.0.0" + } + }, "libraries/metrics/node_modules/yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", @@ -4888,17 +4897,6 @@ "node": ">=10" } }, - "node_modules/@google-cloud/bigquery/node_modules/duplexify": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.2.tgz", - "integrity": "sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw==", - "dependencies": { - "end-of-stream": "^1.4.1", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1", - "stream-shift": "^1.0.0" - } - }, "node_modules/@google-cloud/bigquery/node_modules/uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", @@ -4926,17 +4924,6 @@ "node": ">=10" } }, - "node_modules/@google-cloud/common/node_modules/duplexify": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.2.tgz", - "integrity": "sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw==", - "dependencies": { - "end-of-stream": "^1.4.1", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1", - "stream-shift": "^1.0.0" - } - }, "node_modules/@google-cloud/logging": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/@google-cloud/logging/-/logging-11.1.0.tgz", @@ -5093,246 +5080,202 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "node_modules/@google-cloud/logging-min": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/@google-cloud/logging-min/-/logging-min-10.4.0.tgz", - "integrity": "sha512-TcblDYAATO9hHcDcWYFh+vqt3pAV7Qddaih1JK3cpkzLa+BWjD5gAVAWww8W9Wr5yxOX+8CkssanH/xSS4n76Q==", + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/@google-cloud/logging-min/-/logging-min-11.2.0.tgz", + "integrity": "sha512-o1mwzi1+9NMEjwYZJ0X3tK64obf9PzPVBAhzEJv65L0h7jVl3Fw7GswtsMUkdUvZexf96vAvlZZMvXB9jAIW2Q==", + "license": "Apache-2.0", "dependencies": { - "@google-cloud/common": "^4.0.0", - "@google-cloud/paginator": "^4.0.0", - "@google-cloud/projectify": "^3.0.0", - "@google-cloud/promisify": "^3.0.0", + "@google-cloud/common": "^5.0.0", + "@google-cloud/paginator": "^5.0.0", + "@google-cloud/projectify": "^4.0.0", + "@google-cloud/promisify": "^4.0.0", + "@opentelemetry/api": "^1.7.0", "arrify": "^2.0.1", "dot-prop": "^6.0.0", "eventid": "^2.0.0", "extend": "^3.0.2", - "gcp-metadata": "^4.0.0", - "google-auth-library": "^8.0.2", - "google-gax": "^3.5.2", + "gcp-metadata": "^6.0.0", + "google-auth-library": "^9.0.0", + "google-gax": "^4.0.3", "on-finished": "^2.3.0", "pumpify": "^2.0.1", "stream-events": "^1.0.5", "uuid": "^9.0.0" }, "engines": { - "node": ">=12.0.0" + "node": ">=14.0.0" } }, "node_modules/@google-cloud/logging-min/node_modules/@google-cloud/common": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-4.0.3.tgz", - "integrity": "sha512-fUoMo5b8iAKbrYpneIRV3z95AlxVJPrjpevxs4SKoclngWZvTXBSGpNisF5+x5m+oNGve7jfB1e6vNBZBUs7Fw==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-5.0.2.tgz", + "integrity": "sha512-V7bmBKYQyu0eVG2BFejuUjlBt+zrya6vtsKdY+JxMM/dNntPF41vZ9+LhOshEUH01zOHEqBSvI7Dad7ZS6aUeA==", + "license": "Apache-2.0", "dependencies": { - "@google-cloud/projectify": "^3.0.0", - "@google-cloud/promisify": "^3.0.0", + "@google-cloud/projectify": "^4.0.0", + "@google-cloud/promisify": "^4.0.0", "arrify": "^2.0.1", "duplexify": "^4.1.1", - "ent": "^2.2.0", "extend": "^3.0.2", - "google-auth-library": "^8.0.2", - "retry-request": "^5.0.0", - "teeny-request": "^8.0.0" + "google-auth-library": "^9.0.0", + "html-entities": "^2.5.2", + "retry-request": "^7.0.0", + "teeny-request": "^9.0.0" }, "engines": { - "node": ">=12.0.0" + "node": ">=14.0.0" } }, "node_modules/@google-cloud/logging-min/node_modules/@google-cloud/paginator": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-4.0.1.tgz", - "integrity": "sha512-6G1ui6bWhNyHjmbYwavdN7mpVPRBtyDg/bfqBTAlwr413On2TnFNfDxc9UhTJctkgoCDgQXEKiRPLPR9USlkbQ==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-5.0.2.tgz", + "integrity": "sha512-DJS3s0OVH4zFDB1PzjxAsHqJT6sKVbRwwML0ZBP9PbU7Yebtu/7SWMRzvO2J3nUi9pRNITCfu4LJeooM2w4pjg==", + "license": "Apache-2.0", "dependencies": { "arrify": "^2.0.0", "extend": "^3.0.2" }, "engines": { - "node": ">=12.0.0" + "node": ">=14.0.0" } }, "node_modules/@google-cloud/logging-min/node_modules/@google-cloud/projectify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-3.0.0.tgz", - "integrity": "sha512-HRkZsNmjScY6Li8/kb70wjGlDDyLkVk3KvoEo9uIoxSjYLJasGiCch9+PqRVDOCGUFvEIqyogl+BeqILL4OJHA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-4.0.0.tgz", + "integrity": "sha512-MmaX6HeSvyPbWGwFq7mXdo0uQZLGBYCwziiLIGq5JVX+/bdI3SAq6bP98trV5eTWfLuvsMcIC1YJOF2vfteLFA==", + "license": "Apache-2.0", "engines": { - "node": ">=12.0.0" + "node": ">=14.0.0" } }, "node_modules/@google-cloud/logging-min/node_modules/@google-cloud/promisify": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-3.0.1.tgz", - "integrity": "sha512-z1CjRjtQyBOYL+5Qr9DdYIfrdLBe746jRTYfaYU6MeXkqp7UfYs/jX16lFFVzZ7PGEJvqZNqYUEtb1mvDww4pA==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-4.1.0.tgz", + "integrity": "sha512-G/FQx5cE/+DqBbOpA5jKsegGwdPniU6PuIEMt+qxWgFxvxuFOzVmp6zYchtYuwAWV5/8Dgs0yAmjvNZv3uXLQg==", + "license": "Apache-2.0", "engines": { - "node": ">=12" + "node": ">=18" } }, - "node_modules/@google-cloud/logging-min/node_modules/duplexify": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.2.tgz", - "integrity": "sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw==", + "node_modules/@google-cloud/logging-min/node_modules/@opentelemetry/api": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", + "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", + "license": "Apache-2.0", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/@google-cloud/logging-min/node_modules/agent-base": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/@google-cloud/logging-min/node_modules/gaxios": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", + "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==", + "license": "Apache-2.0", "dependencies": { - "end-of-stream": "^1.4.1", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1", - "stream-shift": "^1.0.0" + "extend": "^3.0.2", + "https-proxy-agent": "^7.0.1", + "is-stream": "^2.0.0", + "node-fetch": "^2.6.9", + "uuid": "^9.0.1" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/@google-cloud/logging-min/node_modules/gaxios/node_modules/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" } }, "node_modules/@google-cloud/logging-min/node_modules/gcp-metadata": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-4.3.1.tgz", - "integrity": "sha512-x850LS5N7V1F3UcV7PoupzGsyD6iVwTVvsh3tbXfkctZnBnjW5yu5z1/3k3SehF7TyoTIe78rJs02GMMy+LF+A==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.1.tgz", + "integrity": "sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A==", + "license": "Apache-2.0", "dependencies": { - "gaxios": "^4.0.0", + "gaxios": "^6.1.1", + "google-logging-utils": "^0.0.2", "json-bigint": "^1.0.0" }, "engines": { - "node": ">=10" - } - }, - "node_modules/@google-cloud/logging-min/node_modules/gcp-metadata/node_modules/gaxios": { - "version": "4.3.3", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-4.3.3.tgz", - "integrity": "sha512-gSaYYIO1Y3wUtdfHmjDUZ8LWaxJQpiavzbF5Kq53akSzvmVg0RfyOcFDbO1KJ/KCGRFz2qG+lS81F0nkr7cRJA==", - "dependencies": { - "abort-controller": "^3.0.0", - "extend": "^3.0.2", - "https-proxy-agent": "^5.0.0", - "is-stream": "^2.0.0", - "node-fetch": "^2.6.7" - }, - "engines": { - "node": ">=10" + "node": ">=14" } }, "node_modules/@google-cloud/logging-min/node_modules/google-auth-library": { - "version": "8.9.0", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-8.9.0.tgz", - "integrity": "sha512-f7aQCJODJFmYWN6PeNKzgvy9LI2tYmXnzpNDHEjG5sDNPgGb2FXQyTBnXeSH+PAtpKESFD+LmHw3Ox3mN7e1Fg==", + "version": "9.15.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.15.1.tgz", + "integrity": "sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==", + "license": "Apache-2.0", "dependencies": { - "arrify": "^2.0.0", "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", - "fast-text-encoding": "^1.0.0", - "gaxios": "^5.0.0", - "gcp-metadata": "^5.3.0", - "gtoken": "^6.1.0", - "jws": "^4.0.0", - "lru-cache": "^6.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@google-cloud/logging-min/node_modules/google-auth-library/node_modules/gcp-metadata": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.3.0.tgz", - "integrity": "sha512-FNTkdNEnBdlqF2oatizolQqNANMrcqJt6AAYt99B3y1aLLC8Hc5IOBb+ZnnzllodEEf6xMBp6wRcBbc16fa65w==", - "dependencies": { - "gaxios": "^5.0.0", - "json-bigint": "^1.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@google-cloud/logging-min/node_modules/google-gax": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-3.6.1.tgz", - "integrity": "sha512-g/lcUjGcB6DSw2HxgEmCDOrI/CByOwqRvsuUvNalHUK2iPPPlmAIpbMbl62u0YufGMr8zgE3JL7th6dCb1Ry+w==", - "dependencies": { - "@grpc/grpc-js": "~1.8.0", - "@grpc/proto-loader": "^0.7.0", - "@types/long": "^4.0.0", - "@types/rimraf": "^3.0.2", - "abort-controller": "^3.0.0", - "duplexify": "^4.0.0", - "fast-text-encoding": "^1.0.3", - "google-auth-library": "^8.0.2", - "is-stream-ended": "^0.1.4", - "node-fetch": "^2.6.1", - "object-hash": "^3.0.0", - "proto3-json-serializer": "^1.0.0", - "protobufjs": "7.2.4", - "protobufjs-cli": "1.1.1", - "retry-request": "^5.0.0" - }, - "bin": { - "compileProtos": "build/tools/compileProtos.js", - "minifyProtoJson": "build/tools/minify.js" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/@google-cloud/logging-min/node_modules/google-p12-pem": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-4.0.1.tgz", - "integrity": "sha512-WPkN4yGtz05WZ5EhtlxNDWPhC4JIic6G8ePitwUWy4l+XPVYec+a0j0Ts47PDtW59y3RwAhUd9/h9ZZ63px6RQ==", - "dependencies": { - "node-forge": "^1.3.1" - }, - "bin": { - "gp12-pem": "build/src/bin/gp12-pem.js" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/@google-cloud/logging-min/node_modules/gtoken": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-6.1.2.tgz", - "integrity": "sha512-4ccGpzz7YAr7lxrT2neugmXQ3hP9ho2gcaityLVkiUecAiwiy60Ii8gRbZeOsXV19fYaRjgBSshs8kXw+NKCPQ==", - "dependencies": { - "gaxios": "^5.0.1", - "google-p12-pem": "^4.0.0", + "gaxios": "^6.1.1", + "gcp-metadata": "^6.1.0", + "gtoken": "^7.0.0", "jws": "^4.0.0" }, "engines": { - "node": ">=12.0.0" + "node": ">=14" } }, - "node_modules/@google-cloud/logging-min/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "node_modules/@google-cloud/logging-min/node_modules/gtoken": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz", + "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==", + "license": "MIT", "dependencies": { - "yallist": "^4.0.0" + "gaxios": "^6.0.0", + "jws": "^4.0.0" }, "engines": { - "node": ">=10" - } - }, - "node_modules/@google-cloud/logging-min/node_modules/object-hash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", - "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", - "engines": { - "node": ">= 6" + "node": ">=14.0.0" } }, "node_modules/@google-cloud/logging-min/node_modules/retry-request": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-5.0.2.tgz", - "integrity": "sha512-wfI3pk7EE80lCIXprqh7ym48IHYdwmAAzESdbU8Q9l7pnRCk9LEhpbOTNKjz6FARLm/Bl5m+4F0ABxOkYUujSQ==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-7.0.2.tgz", + "integrity": "sha512-dUOvLMJ0/JJYEn8NrpOaGNE7X3vpI5XlZS/u0ANjqtcZVKnIxP7IgCFwrKTxENw29emmwug53awKtaMm4i9g5w==", + "license": "MIT", "dependencies": { - "debug": "^4.1.1", - "extend": "^3.0.2" + "@types/request": "^2.48.8", + "extend": "^3.0.2", + "teeny-request": "^9.0.0" }, "engines": { - "node": ">=12" + "node": ">=14" } }, "node_modules/@google-cloud/logging-min/node_modules/teeny-request": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-8.0.3.tgz", - "integrity": "sha512-jJZpA5He2y52yUhA7pyAGZlgQpcB+xLjcN0eUFxr9c8hP/H7uOXbBNVo/O0C/xVfJLJs680jvkFgVJEEvk9+ww==", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-9.0.0.tgz", + "integrity": "sha512-resvxdc6Mgb7YEThw6G6bExlXKkv6+YbuzGg9xuXxSgxJF7Ozs+o8Y9+2R3sArdWdW8nOokoQb1yrpFB0pQK2g==", + "license": "Apache-2.0", "dependencies": { "http-proxy-agent": "^5.0.0", "https-proxy-agent": "^5.0.0", - "node-fetch": "^2.6.1", + "node-fetch": "^2.6.9", "stream-events": "^1.0.5", "uuid": "^9.0.0" }, "engines": { - "node": ">=12" + "node": ">=14" } }, "node_modules/@google-cloud/logging-min/node_modules/uuid": { @@ -5343,15 +5286,11 @@ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" ], + "license": "MIT", "bin": { "uuid": "dist/bin/uuid" } }, - "node_modules/@google-cloud/logging-min/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, "node_modules/@google-cloud/logging/node_modules/@google-cloud/common": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-5.0.2.tgz", @@ -5426,17 +5365,6 @@ } } }, - "node_modules/@google-cloud/logging/node_modules/duplexify": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.3.tgz", - "integrity": "sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==", - "dependencies": { - "end-of-stream": "^1.4.1", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1", - "stream-shift": "^1.0.2" - } - }, "node_modules/@google-cloud/logging/node_modules/gaxios": { "version": "6.6.0", "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.6.0.tgz", @@ -5598,22 +5526,24 @@ } }, "node_modules/@google-cloud/profiler": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@google-cloud/profiler/-/profiler-6.0.0.tgz", - "integrity": "sha512-EAxPbDiNRidAKOEnlUK3M+CcOlqG+REkUEZKirLtxFwzI/m7LmGqDzQvrVWTOSFSEYJ9qQRRnO+Q1osNGk3NUg==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@google-cloud/profiler/-/profiler-6.0.3.tgz", + "integrity": "sha512-Ey8li6Vc2CbfEzOTSZaqKolxPMGacxVUQuhChNT0Wi55a3nfImMiiuDgqYw1In/a9Q3Z62O7jUg2L8f1XwMN7Q==", + "license": "Apache-2.0", "dependencies": { "@google-cloud/common": "^5.0.0", - "@google-cloud/logging-min": "^10.0.0", + "@google-cloud/logging-min": "^11.0.0", + "@google-cloud/promisify": "~4.0.0", "@types/console-log-level": "^1.4.0", "@types/semver": "^7.0.0", "console-log-level": "^1.4.0", "delay": "^5.0.0", "extend": "^3.0.2", "gcp-metadata": "^6.0.0", - "parse-duration": "^1.0.0", - "pprof": "3.2.1", + "ms": "^2.1.3", + "pprof": "4.0.0", "pretty-ms": "^7.0.0", - "protobufjs": "~7.2.4", + "protobufjs": "~7.4.0", "semver": "^7.0.0", "teeny-request": "^9.0.0" }, @@ -5622,18 +5552,19 @@ } }, "node_modules/@google-cloud/profiler/node_modules/@google-cloud/common": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-5.0.0.tgz", - "integrity": "sha512-IsbTVr7Ag+04GMT87X738vDs85QU1rMvaesm2wEQrtTbZAR92tGmUQ8/D/kdnYgAi98Q4zmfhF+T8Xs/Lw4zAA==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-5.0.2.tgz", + "integrity": "sha512-V7bmBKYQyu0eVG2BFejuUjlBt+zrya6vtsKdY+JxMM/dNntPF41vZ9+LhOshEUH01zOHEqBSvI7Dad7ZS6aUeA==", + "license": "Apache-2.0", "dependencies": { "@google-cloud/projectify": "^4.0.0", "@google-cloud/promisify": "^4.0.0", "arrify": "^2.0.1", "duplexify": "^4.1.1", - "ent": "^2.2.0", "extend": "^3.0.2", "google-auth-library": "^9.0.0", - "retry-request": "^6.0.0", + "html-entities": "^2.5.2", + "retry-request": "^7.0.0", "teeny-request": "^9.0.0" }, "engines": { @@ -5644,6 +5575,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-4.0.0.tgz", "integrity": "sha512-MmaX6HeSvyPbWGwFq7mXdo0uQZLGBYCwziiLIGq5JVX+/bdI3SAq6bP98trV5eTWfLuvsMcIC1YJOF2vfteLFA==", + "license": "Apache-2.0", "engines": { "node": ">=14.0.0" } @@ -5652,68 +5584,43 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-4.0.0.tgz", "integrity": "sha512-Orxzlfb9c67A15cq2JQEyVc7wEsmFBmHjZWZYQMUyJ1qivXyMwdyNOs9odi79hze+2zqdTtu1E19IM/FtqZ10g==", + "license": "Apache-2.0", "engines": { "node": ">=14" } }, "node_modules/@google-cloud/profiler/node_modules/agent-base": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", - "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", - "dependencies": { - "debug": "^4.3.4" - }, + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", + "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", + "license": "MIT", "engines": { "node": ">= 14" } }, - "node_modules/@google-cloud/profiler/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@google-cloud/profiler/node_modules/duplexify": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.2.tgz", - "integrity": "sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw==", - "dependencies": { - "end-of-stream": "^1.4.1", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1", - "stream-shift": "^1.0.0" - } - }, "node_modules/@google-cloud/profiler/node_modules/gaxios": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.1.1.tgz", - "integrity": "sha512-bw8smrX+XlAoo9o1JAksBwX+hi/RG15J+NTSxmNPIclKC3ZVK6C2afwY8OSdRvOK0+ZLecUJYtj2MmjOt3Dm0w==", + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", + "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==", + "license": "Apache-2.0", "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^7.0.1", "is-stream": "^2.0.0", - "node-fetch": "^2.6.9" + "node-fetch": "^2.6.9", + "uuid": "^9.0.1" }, "engines": { "node": ">=14" } }, "node_modules/@google-cloud/profiler/node_modules/gaxios/node_modules/https-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", - "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", "dependencies": { - "agent-base": "^7.0.2", + "agent-base": "^7.1.2", "debug": "4" }, "engines": { @@ -5721,11 +5628,13 @@ } }, "node_modules/@google-cloud/profiler/node_modules/gcp-metadata": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.0.0.tgz", - "integrity": "sha512-Ozxyi23/1Ar51wjUT2RDklK+3HxqDr8TLBNK8rBBFQ7T85iIGnXnVusauj06QyqCXRFZig8LZC+TUddWbndlpQ==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.1.tgz", + "integrity": "sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A==", + "license": "Apache-2.0", "dependencies": { - "gaxios": "^6.0.0", + "gaxios": "^6.1.1", + "google-logging-utils": "^0.0.2", "json-bigint": "^1.0.0" }, "engines": { @@ -5733,26 +5642,27 @@ } }, "node_modules/@google-cloud/profiler/node_modules/google-auth-library": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.1.0.tgz", - "integrity": "sha512-1M9HdOcQNPV5BwSXqwwT238MTKodJFBxZ/V2JP397ieOLv4FjQdfYb9SooR7Mb+oUT2IJ92mLJQf804dyx0MJA==", + "version": "9.15.1", + "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.15.1.tgz", + "integrity": "sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==", + "license": "Apache-2.0", "dependencies": { "base64-js": "^1.3.0", "ecdsa-sig-formatter": "^1.0.11", - "gaxios": "^6.0.0", - "gcp-metadata": "^6.0.0", + "gaxios": "^6.1.1", + "gcp-metadata": "^6.1.0", "gtoken": "^7.0.0", - "jws": "^4.0.0", - "lru-cache": "^6.0.0" + "jws": "^4.0.0" }, "engines": { "node": ">=14" } }, "node_modules/@google-cloud/profiler/node_modules/gtoken": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.0.1.tgz", - "integrity": "sha512-KcFVtoP1CVFtQu0aSk3AyAt2og66PFhZAlkUOuWKwzMLoulHXG5W5wE5xAnHb+yl3/wEFoqGW7/cDGMU8igDZQ==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz", + "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==", + "license": "MIT", "dependencies": { "gaxios": "^6.0.0", "jws": "^4.0.0" @@ -5761,41 +5671,49 @@ "node": ">=14.0.0" } }, - "node_modules/@google-cloud/profiler/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "node_modules/@google-cloud/profiler/node_modules/protobufjs": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz", + "integrity": "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==", + "hasInstallScript": true, + "license": "BSD-3-Clause", "dependencies": { - "yallist": "^4.0.0" + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" }, "engines": { - "node": ">=10" + "node": ">=12.0.0" } }, - "node_modules/@google-cloud/profiler/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, "node_modules/@google-cloud/profiler/node_modules/retry-request": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-6.0.0.tgz", - "integrity": "sha512-24kaFMd3wCnT3n4uPnsQh90ZSV8OISpfTFXJ00Wi+/oD2OPrp63EQ8hznk6rhxdlpwx2QBhQSDz2Fg46ki852g==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-7.0.2.tgz", + "integrity": "sha512-dUOvLMJ0/JJYEn8NrpOaGNE7X3vpI5XlZS/u0ANjqtcZVKnIxP7IgCFwrKTxENw29emmwug53awKtaMm4i9g5w==", + "license": "MIT", "dependencies": { - "debug": "^4.1.1", - "extend": "^3.0.2" + "@types/request": "^2.48.8", + "extend": "^3.0.2", + "teeny-request": "^9.0.0" }, "engines": { "node": ">=14" } }, "node_modules/@google-cloud/profiler/node_modules/semver": { - "version": "7.5.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", - "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", - "dependencies": { - "lru-cache": "^6.0.0" - }, + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -5807,6 +5725,7 @@ "version": "9.0.0", "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-9.0.0.tgz", "integrity": "sha512-resvxdc6Mgb7YEThw6G6bExlXKkv6+YbuzGg9xuXxSgxJF7Ozs+o8Y9+2R3sArdWdW8nOokoQb1yrpFB0pQK2g==", + "license": "Apache-2.0", "dependencies": { "http-proxy-agent": "^5.0.0", "https-proxy-agent": "^5.0.0", @@ -5826,15 +5745,11 @@ "https://github.com/sponsors/broofa", "https://github.com/sponsors/ctavan" ], + "license": "MIT", "bin": { "uuid": "dist/bin/uuid" } }, - "node_modules/@google-cloud/profiler/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, "node_modules/@google-cloud/projectify": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-2.1.1.tgz", @@ -5905,17 +5820,6 @@ "node": ">=12" } }, - "node_modules/@google-cloud/storage/node_modules/duplexify": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.2.tgz", - "integrity": "sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw==", - "dependencies": { - "end-of-stream": "^1.4.1", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1", - "stream-shift": "^1.0.0" - } - }, "node_modules/@google-cloud/storage/node_modules/google-auth-library": { "version": "8.7.0", "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-8.7.0.tgz", @@ -6510,17 +6414,6 @@ "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==" }, - "node_modules/@jsdoc/salty": { - "version": "0.2.8", - "resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.8.tgz", - "integrity": "sha512-5e+SFVavj1ORKlKaKr2BmTOekmXbelU7dC0cDkQLqag7xfuTPuGMUFx7KWJuv4bYZrTsoL2Z18VVCOKYxzoHcg==", - "dependencies": { - "lodash": "^4.17.21" - }, - "engines": { - "node": ">=v12.0.0" - } - }, "node_modules/@jsonjoy.com/base64": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@jsonjoy.com/base64/-/base64-1.1.2.tgz", @@ -6692,14 +6585,15 @@ } }, "node_modules/@mapbox/node-pre-gyp": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.8.tgz", - "integrity": "sha512-CMGKi28CF+qlbXh26hDe6NxCd7amqeAzEqnS6IHeO6LoaKyM/n+Xw3HT1COdq8cuioOdlKdqn/hCmqPUOMOywg==", + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", + "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", + "license": "BSD-3-Clause", "dependencies": { - "detect-libc": "^1.0.3", + "detect-libc": "^2.0.0", "https-proxy-agent": "^5.0.0", "make-dir": "^3.1.0", - "node-fetch": "^2.6.5", + "node-fetch": "^2.6.7", "nopt": "^5.0.0", "npmlog": "^5.0.1", "rimraf": "^3.0.2", @@ -6743,20 +6637,6 @@ "semver": "bin/semver.js" } }, - "node_modules/@mapbox/node-pre-gyp/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/@mapbox/node-pre-gyp/node_modules/semver": { "version": "7.5.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", @@ -12294,15 +12174,6 @@ "@types/send": "*" } }, - "node_modules/@types/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@types/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-IO+MJPVhoqz+28h1qLAcBEH2+xHMK6MTyHJc7MTnnYb6wsoLR29POVGJ7LycmVXIqyy/4/2ShP5sUwTXuOwb/w==", - "dependencies": { - "@types/minimatch": "^5.1.2", - "@types/node": "*" - } - }, "node_modules/@types/glob-to-regexp": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/@types/glob-to-regexp/-/glob-to-regexp-0.4.4.tgz", @@ -12470,11 +12341,6 @@ "@types/node": "*" } }, - "node_modules/@types/linkify-it": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", - "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==" - }, "node_modules/@types/lodash": { "version": "4.14.178", "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.178.tgz", @@ -12486,20 +12352,6 @@ "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz", "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==" }, - "node_modules/@types/markdown-it": { - "version": "14.1.1", - "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.1.tgz", - "integrity": "sha512-4NpsnpYl2Gt1ljyBGrKMxFYAYvpqbnnkgP/i/g+NLpjEUa3obn1XJCur9YbEXKDAkaXqsR1LbDnGEJ0MmKFxfg==", - "dependencies": { - "@types/linkify-it": "^5", - "@types/mdurl": "^2" - } - }, - "node_modules/@types/mdurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", - "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==" - }, "node_modules/@types/mdx": { "version": "2.0.13", "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.13.tgz", @@ -12525,11 +12377,6 @@ "resolved": "https://registry.npmjs.org/@types/mime-db/-/mime-db-1.43.1.tgz", "integrity": "sha512-kGZJY+R+WnR5Rk+RPHUMERtb2qBRViIHCBdtUrY+NmwuGb8pQdfTqQiCKPrxpdoycl8KWm2DLdkpoSdt479XoQ==" }, - "node_modules/@types/minimatch": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", - "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==" - }, "node_modules/@types/mocha": { "version": "10.0.6", "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.6.tgz", @@ -12794,15 +12641,6 @@ "integrity": "sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==", "dev": true }, - "node_modules/@types/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-F3OznnSLAUxFrCEu/L5PY8+ny8DtcFRjx7fZZ9bycvXRi3KPTRS9HOitGZwvPg0juRhXFWIeKX58cnX5YqLohQ==", - "dependencies": { - "@types/glob": "*", - "@types/node": "*" - } - }, "node_modules/@types/semver": { "version": "7.5.0", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz", @@ -16506,6 +16344,7 @@ "version": "1.5.0", "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "license": "MIT", "dependencies": { "file-uri-to-path": "1.0.0" } @@ -17021,21 +16860,6 @@ "node": ">=10.12.0" } }, - "node_modules/c8/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/c8/node_modules/yargs": { "version": "16.2.0", "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", @@ -17259,17 +17083,6 @@ "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" }, - "node_modules/catharsis": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz", - "integrity": "sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==", - "dependencies": { - "lodash": "^4.17.15" - }, - "engines": { - "node": ">= 10" - } - }, "node_modules/celebrate": { "version": "15.0.3", "resolved": "https://registry.npmjs.org/celebrate/-/celebrate-15.0.3.tgz", @@ -20077,14 +19890,12 @@ } }, "node_modules/detect-libc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", - "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", - "bin": { - "detect-libc": "bin/detect-libc.js" - }, + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", + "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", + "license": "Apache-2.0", "engines": { - "node": ">=0.10" + "node": ">=8" } }, "node_modules/detect-node": { @@ -20410,6 +20221,18 @@ "node": ">=0.10" } }, + "node_modules/duplexify": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.3.tgz", + "integrity": "sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.4.1", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1", + "stream-shift": "^1.0.2" + } + }, "node_modules/duration": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/duration/-/duration-0.2.2.tgz", @@ -23227,7 +23050,8 @@ "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", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==" + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "license": "MIT" }, "node_modules/filelist": { "version": "1.0.4", @@ -23448,6 +23272,7 @@ "version": "2.2.3", "resolved": "https://registry.npmjs.org/findit2/-/findit2-2.2.3.tgz", "integrity": "sha512-lg/Moejf4qXovVutL0Lz4IsaPoNYMuxt4PA0nGqFxnJ1CTTGGlEO2wKgoDpwknhvZ8k4Q2F+eesgkLbG2Mxfog==", + "license": "MIT", "engines": { "node": ">=0.8.22" } @@ -23473,20 +23298,6 @@ "node": "^10.12.0 || >=12.0.0" } }, - "node_modules/flat-cache/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/flatted": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", @@ -24633,6 +24444,15 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/google-logging-utils": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-0.0.2.tgz", + "integrity": "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=14" + } + }, "node_modules/google-p12-pem": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-3.1.3.tgz", @@ -26707,11 +26527,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-stream-ended": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-stream-ended/-/is-stream-ended-0.1.4.tgz", - "integrity": "sha512-xj0XPvmr7bQFTvirqnFr50o0hQIh6ZItDqloxt5aJrR4NQsYeSsyFQERYGCAzfindAcnKjINnwEEgLx4IqVzQw==" - }, "node_modules/is-string": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", @@ -27312,14 +27127,6 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/js2xmlparser": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz", - "integrity": "sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==", - "dependencies": { - "xmlcreate": "^2.0.4" - } - }, "node_modules/jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", @@ -27391,34 +27198,6 @@ "node": ">=6.0.0" } }, - "node_modules/jsdoc": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.3.tgz", - "integrity": "sha512-Nu7Sf35kXJ1MWDZIMAuATRQTg1iIPdzh7tqJ6jjvaU/GfDf+qi5UV8zJR3Mo+/pYFvm8mzay4+6O5EWigaQBQw==", - "dependencies": { - "@babel/parser": "^7.20.15", - "@jsdoc/salty": "^0.2.1", - "@types/markdown-it": "^14.1.1", - "bluebird": "^3.7.2", - "catharsis": "^0.9.0", - "escape-string-regexp": "^2.0.0", - "js2xmlparser": "^4.0.2", - "klaw": "^3.0.0", - "markdown-it": "^14.1.0", - "markdown-it-anchor": "^8.6.7", - "marked": "^4.0.10", - "mkdirp": "^1.0.4", - "requizzle": "^0.2.3", - "strip-json-comments": "^3.1.0", - "underscore": "~1.13.2" - }, - "bin": { - "jsdoc": "jsdoc.js" - }, - "engines": { - "node": ">=12.0.0" - } - }, "node_modules/jsdoc-type-pratt-parser": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.1.0.tgz", @@ -27429,25 +27208,6 @@ "node": ">=12.0.0" } }, - "node_modules/jsdoc/node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "engines": { - "node": ">=8" - } - }, - "node_modules/jsdoc/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/jsdom": { "version": "19.0.0", "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-19.0.0.tgz", @@ -27909,14 +27669,6 @@ "node": ">=0.10.0" } }, - "node_modules/klaw": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", - "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", - "dependencies": { - "graceful-fs": "^4.1.9" - } - }, "node_modules/klaw-sync": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz", @@ -28302,19 +28054,6 @@ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "dev": true }, - "node_modules/linkify-it": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", - "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", - "dependencies": { - "uc.micro": "^2.0.0" - } - }, - "node_modules/linkify-it/node_modules/uc.micro": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", - "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==" - }, "node_modules/listr2": { "version": "3.14.0", "resolved": "https://registry.npmjs.org/listr2/-/listr2-3.14.0.tgz", @@ -28723,6 +28462,12 @@ "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=" }, + "node_modules/lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==", + "license": "MIT" + }, "node_modules/lodash.support": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/lodash.support/-/lodash.support-2.4.1.tgz", @@ -29088,47 +28833,6 @@ "node": ">=0.10.0" } }, - "node_modules/markdown-it": { - "version": "14.1.0", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", - "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", - "dependencies": { - "argparse": "^2.0.1", - "entities": "^4.4.0", - "linkify-it": "^5.0.0", - "mdurl": "^2.0.0", - "punycode.js": "^2.3.1", - "uc.micro": "^2.1.0" - }, - "bin": { - "markdown-it": "bin/markdown-it.mjs" - } - }, - "node_modules/markdown-it-anchor": { - "version": "8.6.7", - "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.7.tgz", - "integrity": "sha512-FlCHFwNnutLgVTflOYHPW2pPcl2AACqVzExlkGQNsi4CJgqOHN7YTgDd4LuhgN1BFO3TS0vLAruV1Td6dwWPJA==", - "peerDependencies": { - "@types/markdown-it": "*", - "markdown-it": "*" - } - }, - "node_modules/markdown-it/node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/markdown-it/node_modules/uc.micro": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", - "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==" - }, "node_modules/marked": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/marked/-/marked-4.1.0.tgz", @@ -29202,11 +28906,6 @@ "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==", "dev": true }, - "node_modules/mdurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", - "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==" - }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -30603,9 +30302,10 @@ "license": "MIT" }, "node_modules/nan": { - "version": "2.17.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.17.0.tgz", - "integrity": "sha512-2ZTgtl0nJsO0KQCjEpxcIr5D+Yv90plTitZt9JBfQvVJDS5seMl3FOvsh3+9CoYWXf/1l5OaZzzF6nDm4cagaQ==" + "version": "2.22.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.22.2.tgz", + "integrity": "sha512-DANghxFkS1plDdRsX0X9pm0Z6SJNN6gBdtXfanwoZ8hooC5gosGFSBGRYHUVPz1asKA/kMRqDRdHrluZ61SpBQ==", + "license": "MIT" }, "node_modules/nanoclone": { "version": "0.2.1", @@ -30876,16 +30576,6 @@ "node-gyp-build-optional-packages-test": "build-test.js" } }, - "node_modules/node-gyp-build-optional-packages/node_modules/detect-libc": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", - "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", - "license": "Apache-2.0", - "optional": true, - "engines": { - "node": ">=8" - } - }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", @@ -31483,53 +31173,6 @@ "node": ">=0.10" } }, - "node_modules/optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dependencies": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/optionator/node_modules/levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", - "dependencies": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/optionator/node_modules/prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/optionator/node_modules/type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", - "dependencies": { - "prelude-ls": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, "node_modules/options": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/options/-/options-0.0.6.tgz", @@ -31780,11 +31423,6 @@ "node": ">=8" } }, - "node_modules/parse-duration": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/parse-duration/-/parse-duration-1.1.0.tgz", - "integrity": "sha512-z6t9dvSJYaPoQq7quMzdEagSFtpGu+utzHqqxmpVWNNZRIXnvqyCvn9XsTdh7c/w0Bqmdz3RB3YnRaKtpRtEXQ==" - }, "node_modules/parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -33928,41 +33566,34 @@ } }, "node_modules/pprof": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/pprof/-/pprof-3.2.1.tgz", - "integrity": "sha512-KnextTM3EHQ2zqN8fUjB0VpE+njcVR7cOfo7DjJSLKzIbKTPelDtokI04ScR/Vd8CLDj+M99tsaKV+K6FHzpzA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pprof/-/pprof-4.0.0.tgz", + "integrity": "sha512-Yhfk7Y0G1MYsy97oXxmSG5nvbM1sCz9EALiNhW/isAv5Xf7svzP+1RfGeBlS6mLSgRJvgSLh6Mi5DaisQuPttw==", "hasInstallScript": true, + "license": "Apache-2.0", "dependencies": { - "@mapbox/node-pre-gyp": "^1.0.0", + "@mapbox/node-pre-gyp": "^1.0.9", "bindings": "^1.2.1", "delay": "^5.0.0", "findit2": "^2.2.3", - "nan": "^2.14.0", + "nan": "^2.17.0", "p-limit": "^3.0.0", - "pify": "^5.0.0", "protobufjs": "~7.2.4", - "source-map": "^0.7.3", + "source-map": "~0.8.0-beta.0", "split": "^1.0.1" }, "engines": { - "node": ">=10.4.1" - } - }, - "node_modules/pprof/node_modules/pify": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-5.0.0.tgz", - "integrity": "sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=14.0.0" } }, "node_modules/pprof/node_modules/source-map": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", - "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", + "version": "0.8.0-beta.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", + "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", + "license": "BSD-3-Clause", + "dependencies": { + "whatwg-url": "^7.0.0" + }, "engines": { "node": ">= 8" } @@ -34243,17 +33874,6 @@ "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", "dev": true }, - "node_modules/proto3-json-serializer": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-1.1.1.tgz", - "integrity": "sha512-AwAuY4g9nxx0u52DnSMkqqgyLHaW/XaPLtaAo3y/ZCfeaQB/g4YDH4kb8Wc/mWzWvu0YjOznVnfn373MVZZrgw==", - "dependencies": { - "protobufjs": "^7.0.0" - }, - "engines": { - "node": ">=12.0.0" - } - }, "node_modules/protobufjs": { "version": "7.2.5", "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.5.tgz", @@ -34277,151 +33897,6 @@ "node": ">=12.0.0" } }, - "node_modules/protobufjs-cli": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/protobufjs-cli/-/protobufjs-cli-1.1.1.tgz", - "integrity": "sha512-VPWMgIcRNyQwWUv8OLPyGQ/0lQY/QTQAVN5fh+XzfDwsVw1FZ2L3DM/bcBf8WPiRz2tNpaov9lPZfNcmNo6LXA==", - "dependencies": { - "chalk": "^4.0.0", - "escodegen": "^1.13.0", - "espree": "^9.0.0", - "estraverse": "^5.1.0", - "glob": "^8.0.0", - "jsdoc": "^4.0.0", - "minimist": "^1.2.0", - "semver": "^7.1.2", - "tmp": "^0.2.1", - "uglify-js": "^3.7.7" - }, - "bin": { - "pbjs": "bin/pbjs", - "pbts": "bin/pbts" - }, - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "protobufjs": "^7.0.0" - } - }, - "node_modules/protobufjs-cli/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/protobufjs-cli/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/protobufjs-cli/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/protobufjs-cli/node_modules/escodegen": { - "version": "1.14.3", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", - "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", - "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" - }, - "engines": { - "node": ">=4.0" - }, - "optionalDependencies": { - "source-map": "~0.6.1" - } - }, - "node_modules/protobufjs-cli/node_modules/escodegen/node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/protobufjs-cli/node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "deprecated": "Glob versions prior to v9 are no longer supported", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/protobufjs-cli/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/protobufjs-cli/node_modules/semver": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", - "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/protobufjs-cli/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -34594,17 +34069,6 @@ "pump": "^3.0.0" } }, - "node_modules/pumpify/node_modules/duplexify": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.2.tgz", - "integrity": "sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw==", - "dependencies": { - "end-of-stream": "^1.4.1", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1", - "stream-shift": "^1.0.0" - } - }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -34613,14 +34077,6 @@ "node": ">=6" } }, - "node_modules/punycode.js": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", - "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", - "engines": { - "node": ">=6" - } - }, "node_modules/qrcode": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.0.tgz", @@ -36012,14 +35468,6 @@ "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=" }, - "node_modules/requizzle": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.4.tgz", - "integrity": "sha512-JRrFk1D4OQ4SqovXOgdav+K8EAhSB/LJZqCz8tbX0KObcdeM15Ss59ozWMBWmmINMagCwmqn4ZNryUGpBsl6Jw==", - "dependencies": { - "lodash": "^4.17.21" - } - }, "node_modules/reselect": { "version": "4.1.8", "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz", @@ -36159,6 +35607,22 @@ "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", "dev": true }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/rndm": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/rndm/-/rndm-1.2.0.tgz", @@ -37805,6 +37269,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/split/-/split-1.0.1.tgz", "integrity": "sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg==", + "license": "MIT", "dependencies": { "through": "2" }, @@ -39755,6 +39220,7 @@ "version": "0.2.3", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", + "dev": true, "engines": { "node": ">=14.14" } @@ -39938,6 +39404,15 @@ "node": ">= 4.0.0" } }, + "node_modules/tr46": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", + "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==", + "license": "MIT", + "dependencies": { + "punycode": "^2.1.0" + } + }, "node_modules/traverse": { "version": "0.6.9", "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.9.tgz", @@ -40236,6 +39711,8 @@ "version": "3.15.0", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.15.0.tgz", "integrity": "sha512-x+xdeDWq7FiORDvyIJ0q/waWd4PhjBNOm5dQUOq2AKC0IEjxOS66Ha9tctiVDGcRQuh69K7fgU5oRuTK4cysSg==", + "dev": true, + "optional": true, "bin": { "uglifyjs": "bin/uglifyjs" }, @@ -41937,6 +41414,23 @@ "node": ">=12" } }, + "node_modules/whatwg-url": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", + "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", + "license": "MIT", + "dependencies": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + }, + "node_modules/whatwg-url/node_modules/webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", + "license": "BSD-2-Clause" + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -42103,6 +41597,7 @@ "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, "engines": { "node": ">=0.10.0" } @@ -42335,11 +41830,6 @@ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", "dev": true }, - "node_modules/xmlcreate": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz", - "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==" - }, "node_modules/xmlhttprequest": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz", @@ -42833,13 +42323,6 @@ "tar-stream": "^2.1.4" } }, - "services/clsi/node_modules/nan": { - "version": "2.22.2", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.22.2.tgz", - "integrity": "sha512-DANghxFkS1plDdRsX0X9pm0Z6SJNN6gBdtXfanwoZ8hooC5gosGFSBGRYHUVPz1asKA/kMRqDRdHrluZ61SpBQ==", - "license": "MIT", - "optional": true - }, "services/clsi/node_modules/protobufjs": { "version": "7.4.0", "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz", @@ -43667,197 +43150,6 @@ "@babel/highlight": "^7.10.4" } }, - "services/latexqc/node_modules/@babel/core": { - "version": "7.26.10", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz", - "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.26.2", - "@babel/generator": "^7.26.10", - "@babel/helper-compilation-targets": "^7.26.5", - "@babel/helper-module-transforms": "^7.26.0", - "@babel/helpers": "^7.26.10", - "@babel/parser": "^7.26.10", - "@babel/template": "^7.26.9", - "@babel/traverse": "^7.26.10", - "@babel/types": "^7.26.10", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "services/latexqc/node_modules/@babel/core/node_modules/@babel/code-frame": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", - "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.25.9", - "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "services/latexqc/node_modules/@babel/core/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "services/latexqc/node_modules/@babel/generator": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.0.tgz", - "integrity": "sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.27.0", - "@babel/types": "^7.27.0", - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.25", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "services/latexqc/node_modules/@babel/helpers": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.0.tgz", - "integrity": "sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.27.0", - "@babel/types": "^7.27.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "services/latexqc/node_modules/@babel/parser": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz", - "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.27.0" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "services/latexqc/node_modules/@babel/template": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz", - "integrity": "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.26.2", - "@babel/parser": "^7.27.0", - "@babel/types": "^7.27.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "services/latexqc/node_modules/@babel/template/node_modules/@babel/code-frame": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", - "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.25.9", - "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "services/latexqc/node_modules/@babel/traverse": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.0.tgz", - "integrity": "sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.26.2", - "@babel/generator": "^7.27.0", - "@babel/parser": "^7.27.0", - "@babel/template": "^7.27.0", - "@babel/types": "^7.27.0", - "debug": "^4.3.1", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "services/latexqc/node_modules/@babel/traverse/node_modules/@babel/code-frame": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", - "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.25.9", - "js-tokens": "^4.0.0", - "picocolors": "^1.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "services/latexqc/node_modules/@babel/traverse/node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "services/latexqc/node_modules/@babel/types": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz", - "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.25.9", - "@babel/helper-validator-identifier": "^7.25.9" - }, - "engines": { - "node": ">=6.9.0" - } - }, "services/latexqc/node_modules/@eslint/eslintrc": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", @@ -44167,13 +43459,6 @@ "node": ">= 16" } }, - "services/latexqc/node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT" - }, "services/latexqc/node_modules/debug": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", From e3dd47ba6e3e1bc644677df69dfed9819b29ecfe Mon Sep 17 00:00:00 2001 From: Antoine Clausse Date: Tue, 6 May 2025 16:19:01 +0200 Subject: [PATCH 018/194] [web] Fix date format in emails.createdAt, use `new Date()` instead of `Date.now()` (#25322) GitOrigin-RevId: c94700accb1df902926779c1e6321be63cf65235 --- services/web/scripts/remove_emails_with_commas.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/web/scripts/remove_emails_with_commas.mjs b/services/web/scripts/remove_emails_with_commas.mjs index 29d78b129c..f6f107edac 100644 --- a/services/web/scripts/remove_emails_with_commas.mjs +++ b/services/web/scripts/remove_emails_with_commas.mjs @@ -82,7 +82,7 @@ async function consumeCsvFileAndUpdate() { $addToSet: { emails: { email: newEmail, - createdAt: Date.now(), + createdAt: new Date(), reversedHostname: 'moc.faelrevo', }, }, From bc4c3c4ef8235446c2488e45638f3d391518c7fd Mon Sep 17 00:00:00 2001 From: Antoine Clausse Date: Tue, 6 May 2025 16:34:13 +0200 Subject: [PATCH 019/194] [web] Promisify ClsiCookieManager and CompileController (reapply again) (#25280) * Reapply "[web] Promisify ClsiCookieManager and CompileController (reapply and fix)" This reverts commit 98cb9127ff2b7c7c347c560766f749265d712490. * Fix: Use query parameters correctly (!!) * Add unit test on `checkIsLoadSheddingEvent` * Remove interference between tests: rename to `ClsiCookieManager2` when it's re-sandboxed * Add test: 'should report "cycle" when other is UP' GitOrigin-RevId: 3146b149954b908830226cb03b51d9adfa08ec2e --- .../src/Features/Compile/ClsiCookieManager.js | 402 ++++---- .../src/Features/Compile/CompileController.js | 876 ++++++++---------- services/web/app/src/router.mjs | 4 +- .../src/Compile/ClsiCookieManagerTests.js | 346 +++---- .../src/Compile/CompileControllerTests.js | 371 ++++---- 5 files changed, 940 insertions(+), 1059 deletions(-) diff --git a/services/web/app/src/Features/Compile/ClsiCookieManager.js b/services/web/app/src/Features/Compile/ClsiCookieManager.js index fc542fefaf..a1ac0741b9 100644 --- a/services/web/app/src/Features/Compile/ClsiCookieManager.js +++ b/services/web/app/src/Features/Compile/ClsiCookieManager.js @@ -1,12 +1,15 @@ const { URL, URLSearchParams } = require('url') const OError = require('@overleaf/o-error') const Settings = require('@overleaf/settings') -const request = require('request').defaults({ timeout: 30 * 1000 }) +const { + fetchNothing, + fetchStringWithResponse, + RequestFailedError, +} = require('@overleaf/fetch-utils') const RedisWrapper = require('../../infrastructure/RedisWrapper') const Cookie = require('cookie') const logger = require('@overleaf/logger') const Metrics = require('@overleaf/metrics') -const { promisifyAll } = require('@overleaf/promise-utils') const clsiCookiesEnabled = (Settings.clsiCookie?.key ?? '') !== '' @@ -16,235 +19,204 @@ if (Settings.redis.clsi_cookie_secondary != null) { rclientSecondary = RedisWrapper.client('clsi_cookie_secondary') } -module.exports = function (backendGroup) { - const cookieManager = { - buildKey(projectId, userId) { - if (backendGroup != null) { - return `clsiserver:${backendGroup}:${projectId}:${userId}` - } else { - return `clsiserver:${projectId}:${userId}` - } - }, +const ClsiCookieManagerFactory = function (backendGroup) { + function buildKey(projectId, userId) { + if (backendGroup != null) { + return `clsiserver:${backendGroup}:${projectId}:${userId}` + } else { + return `clsiserver:${projectId}:${userId}` + } + } - getServerId( - projectId, - userId, - compileGroup, - compileBackendClass, - callback - ) { - if (!clsiCookiesEnabled) { - return callback() - } - rclient.get(this.buildKey(projectId, userId), (err, serverId) => { - if (err) { - return callback(err) - } - if (serverId == null || serverId === '') { - this._populateServerIdViaRequest( - projectId, - userId, - compileGroup, - compileBackendClass, - callback - ) - } else { - callback(null, serverId) - } - }) - }, + async function getServerId( + projectId, + userId, + compileGroup, + compileBackendClass + ) { + if (!clsiCookiesEnabled) { + return + } + const serverId = await rclient.get(buildKey(projectId, userId)) - _populateServerIdViaRequest( - projectId, - userId, - compileGroup, - compileBackendClass, - callback - ) { - const u = new URL(`${Settings.apis.clsi.url}/project/${projectId}/status`) - u.search = new URLSearchParams({ + if (!serverId) { + return await cookieManager.promises._populateServerIdViaRequest( + projectId, + userId, compileGroup, - compileBackendClass, - }).toString() - request.post(u.href, (err, res, body) => { - if (err) { - OError.tag(err, 'error getting initial server id for project', { - project_id: projectId, - }) - return callback(err) - } - if (!clsiCookiesEnabled) { - return callback() - } - const serverId = this._parseServerIdFromResponse(res) - this.setServerId( - projectId, - userId, - compileGroup, - compileBackendClass, - serverId, - null, - function (err) { - if (err) { - logger.warn( - { err, projectId }, - 'error setting server id via populate request' - ) - } - callback(err, serverId) - } - ) - }) - }, - - _parseServerIdFromResponse(response) { - const cookies = Cookie.parse(response.headers['set-cookie']?.[0] || '') - return cookies?.[Settings.clsiCookie.key] - }, - - checkIsLoadSheddingEvent(clsiserverid, compileGroup, compileBackendClass) { - request.get( - { - url: `${Settings.apis.clsi.url}/instance-state`, - qs: { clsiserverid, compileGroup, compileBackendClass }, - }, - (err, res, body) => { - if (err) { - Metrics.inc('clsi-lb-switch-backend', 1, { - status: 'error', - }) - logger.warn({ err, clsiserverid }, 'cannot probe clsi VM') - return - } - const isStillRunning = - res.statusCode === 200 && body === `${clsiserverid},UP\n` - Metrics.inc('clsi-lb-switch-backend', 1, { - status: isStillRunning ? 'load-shedding' : 'cycle', - }) - } + compileBackendClass ) - }, + } else { + return serverId + } + } - _getTTLInSeconds(clsiServerId) { - return (clsiServerId || '').includes('-reg-') - ? Settings.clsiCookie.ttlInSecondsRegular - : Settings.clsiCookie.ttlInSeconds - }, - - setServerId( - projectId, - userId, + async function _populateServerIdViaRequest( + projectId, + userId, + compileGroup, + compileBackendClass + ) { + const u = new URL(`${Settings.apis.clsi.url}/project/${projectId}/status`) + u.search = new URLSearchParams({ compileGroup, compileBackendClass, - serverId, - previous, - callback - ) { - if (!clsiCookiesEnabled) { - return callback() - } - if (serverId == null) { - // We don't get a cookie back if it hasn't changed - return rclient.expire( - this.buildKey(projectId, userId), - this._getTTLInSeconds(previous), - err => callback(err) - ) - } - if (!previous) { - // Initial assignment of a user+project or after clearing cache. - Metrics.inc('clsi-lb-assign-initial-backend') - } else { - this.checkIsLoadSheddingEvent( - previous, - compileGroup, - compileBackendClass - ) - } - if (rclientSecondary != null) { - this._setServerIdInRedis( - rclientSecondary, - projectId, - userId, - serverId, - () => {} - ) - } - this._setServerIdInRedis(rclient, projectId, userId, serverId, err => - callback(err) - ) - }, - - _setServerIdInRedis(rclient, projectId, userId, serverId, callback) { - rclient.setex( - this.buildKey(projectId, userId), - this._getTTLInSeconds(serverId), - serverId, - callback - ) - }, - - clearServerId(projectId, userId, callback) { - if (!clsiCookiesEnabled) { - return callback() - } - rclient.del(this.buildKey(projectId, userId), err => { - if (err) { - // redis errors need wrapping as the instance may be shared - return callback( - new OError( - 'Failed to clear clsi persistence', - { projectId, userId }, - err - ) - ) - } else { - return callback() - } + }).toString() + let res + try { + res = await fetchNothing(u.href, { + method: 'POST', + signal: AbortSignal.timeout(30_000), }) - }, + } catch (err) { + OError.tag(err, 'error getting initial server id for project', { + project_id: projectId, + }) + throw err + } - getCookieJar( - projectId, - userId, - compileGroup, - compileBackendClass, - callback - ) { - if (!clsiCookiesEnabled) { - return callback(null, request.jar(), undefined) - } - this.getServerId( + if (!clsiCookiesEnabled) { + return + } + const serverId = cookieManager._parseServerIdFromResponse(res) + try { + await cookieManager.promises.setServerId( projectId, userId, compileGroup, compileBackendClass, - (err, serverId) => { - if (err != null) { - OError.tag(err, 'error getting server id', { - project_id: projectId, - }) - return callback(err) - } - const serverCookie = request.cookie( - `${Settings.clsiCookie.key}=${serverId}` - ) - const jar = request.jar() - jar.setCookie(serverCookie, Settings.apis.clsi.url) - callback(null, jar, serverId) + serverId, + null + ) + return serverId + } catch (err) { + logger.warn( + { err, projectId }, + 'error setting server id via populate request' + ) + throw err + } + } + + function _parseServerIdFromResponse(response) { + const cookies = Cookie.parse(response.headers['set-cookie']?.[0] || '') + return cookies?.[Settings.clsiCookie.key] + } + + async function checkIsLoadSheddingEvent( + clsiserverid, + compileGroup, + compileBackendClass + ) { + let status + try { + const params = new URLSearchParams({ + clsiserverid, + compileGroup, + compileBackendClass, + }).toString() + const { response, body } = await fetchStringWithResponse( + `${Settings.apis.clsi.url}/instance-state?${params}`, + { + method: 'GET', + signal: AbortSignal.timeout(30_000), } ) + status = + response.status === 200 && body === `${clsiserverid},UP\n` + ? 'load-shedding' + : 'cycle' + } catch (err) { + if (err instanceof RequestFailedError && err.response.status === 404) { + status = 'cycle' + } else { + status = 'error' + logger.warn({ err, clsiserverid }, 'cannot probe clsi VM') + } + } + Metrics.inc('clsi-lb-switch-backend', 1, { status }) + } + + function _getTTLInSeconds(clsiServerId) { + return (clsiServerId || '').includes('-reg-') + ? Settings.clsiCookie.ttlInSecondsRegular + : Settings.clsiCookie.ttlInSeconds + } + + async function setServerId( + projectId, + userId, + compileGroup, + compileBackendClass, + serverId, + previous + ) { + if (!clsiCookiesEnabled) { + return + } + if (serverId == null) { + // We don't get a cookie back if it hasn't changed + return await rclient.expire( + buildKey(projectId, userId), + _getTTLInSeconds(previous) + ) + } + if (!previous) { + // Initial assignment of a user+project or after clearing cache. + Metrics.inc('clsi-lb-assign-initial-backend') + } else { + await checkIsLoadSheddingEvent( + previous, + compileGroup, + compileBackendClass + ) + } + if (rclientSecondary != null) { + await _setServerIdInRedis( + rclientSecondary, + projectId, + userId, + serverId + ).catch(() => {}) + } + await _setServerIdInRedis(rclient, projectId, userId, serverId) + } + + async function _setServerIdInRedis(rclient, projectId, userId, serverId) { + await rclient.setex( + buildKey(projectId, userId), + _getTTLInSeconds(serverId), + serverId + ) + } + + async function clearServerId(projectId, userId) { + if (!clsiCookiesEnabled) { + return + } + try { + await rclient.del(buildKey(projectId, userId)) + } catch (err) { + // redis errors need wrapping as the instance may be shared + throw new OError( + 'Failed to clear clsi persistence', + { projectId, userId }, + err + ) + } + } + + const cookieManager = { + _parseServerIdFromResponse, + promises: { + getServerId, + clearServerId, + _populateServerIdViaRequest, + setServerId, }, } - cookieManager.promises = promisifyAll(cookieManager, { - without: [ - '_parseServerIdFromResponse', - 'checkIsLoadSheddingEvent', - '_getTTLInSeconds', - ], - multiResult: { - getCookieJar: ['jar', 'clsiServerId'], - }, - }) + return cookieManager } + +module.exports = ClsiCookieManagerFactory diff --git a/services/web/app/src/Features/Compile/CompileController.js b/services/web/app/src/Features/Compile/CompileController.js index f1d37e7638..e07fe49c80 100644 --- a/services/web/app/src/Features/Compile/CompileController.js +++ b/services/web/app/src/Features/Compile/CompileController.js @@ -1,4 +1,3 @@ -let CompileController const { URL, URLSearchParams } = require('url') const { pipeline } = require('stream/promises') const { Cookie } = require('tough-cookie') @@ -17,7 +16,7 @@ const ClsiCookieManager = require('./ClsiCookieManager')( const Path = require('path') const AnalyticsManager = require('../Analytics/AnalyticsManager') const SplitTestHandler = require('../SplitTests/SplitTestHandler') -const { callbackify } = require('@overleaf/promise-utils') +const { expressify } = require('@overleaf/promise-utils') const { fetchStreamWithResponse, RequestFailedError, @@ -34,17 +33,19 @@ function getOutputFilesArchiveSpecification(projectId, userId, buildId) { const fileName = 'output.zip' return { path: fileName, - url: CompileController._getFileUrl(projectId, userId, buildId, fileName), + url: _CompileController._getFileUrl(projectId, userId, buildId, fileName), type: 'zip', } } -function getImageNameForProject(projectId, callback) { - ProjectGetter.getProject(projectId, { imageName: 1 }, (err, project) => { - if (err) return callback(err) - if (!project) return callback(new Error('project not found')) - callback(null, project.imageName) +async function getImageNameForProject(projectId) { + const project = await ProjectGetter.promises.getProject(projectId, { + imageName: 1, }) + if (!project) { + throw new Error('project not found') + } + return project.imageName } async function getPdfCachingMinChunkSize(req, res) { @@ -53,7 +54,9 @@ async function getPdfCachingMinChunkSize(req, res) { res, 'pdf-caching-min-chunk-size' ) - if (variant === 'default') return 1_000_000 + if (variant === 'default') { + return 1_000_000 + } return parseInt(variant, 10) } @@ -123,10 +126,9 @@ async function _getSplitTestOptions(req, res) { pdfCachingMinChunkSize, } } -const getSplitTestOptionsCb = callbackify(_getSplitTestOptions) -module.exports = CompileController = { - compile(req, res, next) { +const _CompileController = { + async compile(req, res) { res.setTimeout(COMPILE_TIMEOUT_MS) const projectId = req.params.Project_id const isAutoCompile = !!req.query.auto_compile @@ -164,107 +166,95 @@ module.exports = CompileController = { options.incrementalCompilesEnabled = true } - getSplitTestOptionsCb(req, res, (err, splitTestOptions) => { - if (err) return next(err) - let { - compileFromClsiCache, - populateClsiCache, - enablePdfCaching, - pdfCachingMinChunkSize, - pdfDownloadDomain, - } = splitTestOptions - options.compileFromClsiCache = compileFromClsiCache - options.populateClsiCache = populateClsiCache - options.enablePdfCaching = enablePdfCaching - if (enablePdfCaching) { - options.pdfCachingMinChunkSize = pdfCachingMinChunkSize - } + let { + compileFromClsiCache, + populateClsiCache, + enablePdfCaching, + pdfCachingMinChunkSize, + pdfDownloadDomain, + } = await _getSplitTestOptions(req, res) + options.compileFromClsiCache = compileFromClsiCache + options.populateClsiCache = populateClsiCache + options.enablePdfCaching = enablePdfCaching + if (enablePdfCaching) { + options.pdfCachingMinChunkSize = pdfCachingMinChunkSize + } - CompileManager.compile( - projectId, - userId, - options, - ( - error, + const { + status, + outputFiles, + clsiServerId, + limits, + validationProblems, + stats, + timings, + outputUrlPrefix, + buildId, + clsiCacheShard, + } = await CompileManager.promises + .compile(projectId, userId, options) + .catch(error => { + Metrics.inc('compile-error') + throw error + }) + + Metrics.inc('compile-status', 1, { status }) + if (pdfDownloadDomain && outputUrlPrefix) { + pdfDownloadDomain += outputUrlPrefix + } + + if (limits) { + // For a compile request to be sent to clsi we need limits. + // If we get here without having the limits object populated, it is + // a reasonable assumption to make that nothing was compiled. + // We need to know the limits in order to make use of the events. + AnalyticsManager.recordEventForSession( + req.session, + 'compile-result-backend', + { + projectId, + ownerAnalyticsId: limits.ownerAnalyticsId, status, - outputFiles, - clsiServerId, - limits, - validationProblems, - stats, - timings, - outputUrlPrefix, - buildId, - clsiCacheShard - ) => { - if (error) { - Metrics.inc('compile-error') - return next(error) - } - Metrics.inc('compile-status', 1, { status }) - if (pdfDownloadDomain && outputUrlPrefix) { - pdfDownloadDomain += outputUrlPrefix - } - - if (limits) { - // For a compile request to be sent to clsi we need limits. - // If we get here without having the limits object populated, it is - // a reasonable assumption to make that nothing was compiled. - // We need to know the limits in order to make use of the events. - AnalyticsManager.recordEventForSession( - req.session, - 'compile-result-backend', - { - projectId, - ownerAnalyticsId: limits.ownerAnalyticsId, - status, - compileTime: timings?.compileE2E, - timeout: limits.timeout === 60 ? 'short' : 'long', - server: clsiServerId?.includes('-c2d-') ? 'faster' : 'normal', - isAutoCompile, - isInitialCompile: stats?.isInitialCompile === 1, - restoredClsiCache: stats?.restoredClsiCache === 1, - stopOnFirstError, - } - ) - } - - const outputFilesArchive = buildId - ? getOutputFilesArchiveSpecification(projectId, userId, buildId) - : null - - res.json({ - status, - outputFiles, - outputFilesArchive, - compileGroup: limits?.compileGroup, - clsiServerId, - clsiCacheShard, - validationProblems, - stats, - timings, - outputUrlPrefix, - pdfDownloadDomain, - pdfCachingMinChunkSize, - }) + compileTime: timings?.compileE2E, + timeout: limits.timeout === 60 ? 'short' : 'long', + server: clsiServerId?.includes('-c2d-') ? 'faster' : 'normal', + isAutoCompile, + isInitialCompile: stats?.isInitialCompile === 1, + restoredClsiCache: stats?.restoredClsiCache === 1, + stopOnFirstError, } ) + } + + const outputFilesArchive = buildId + ? getOutputFilesArchiveSpecification(projectId, userId, buildId) + : null + + res.json({ + status, + outputFiles, + outputFilesArchive, + compileGroup: limits?.compileGroup, + clsiServerId, + clsiCacheShard, + validationProblems, + stats, + timings, + outputUrlPrefix, + pdfDownloadDomain, + pdfCachingMinChunkSize, }) }, - stopCompile(req, res, next) { + async stopCompile(req, res) { const projectId = req.params.Project_id const userId = SessionManager.getLoggedInUserId(req.session) - CompileManager.stopCompile(projectId, userId, function (error) { - if (error) { - return next(error) - } - res.sendStatus(200) - }) + await CompileManager.promises.stopCompile(projectId, userId) + res.sendStatus(200) }, // Used for submissions through the public API - compileSubmission(req, res, next) { + async compileSubmission(req, res) { res.setTimeout(COMPILE_TIMEOUT_MS) const submissionId = req.params.submission_id const options = {} @@ -285,195 +275,163 @@ module.exports = CompileController = { options.compileBackendClass = Settings.apis.clsi.submissionBackendClass options.timeout = req.body?.timeout || Settings.defaultFeatures.compileTimeout - ClsiManager.sendExternalRequest( - submissionId, - req.body, - options, - function (error, status, outputFiles, clsiServerId, validationProblems) { - if (error) { - return next(error) - } - res.json({ - status, - outputFiles, - clsiServerId, - validationProblems, - }) - } - ) + const { status, outputFiles, clsiServerId, validationProblems } = + await ClsiManager.promises.sendExternalRequest( + submissionId, + req.body, + options + ) + res.json({ + status, + outputFiles, + clsiServerId, + validationProblems, + }) }, - _getSplitTestOptions, - _getUserIdForCompile(req) { if (!Settings.disablePerUserCompiles) { return SessionManager.getLoggedInUserId(req.session) } return null }, - _compileAsUser(req, callback) { - callback(null, CompileController._getUserIdForCompile(req)) - }, - _downloadAsUser(req, callback) { - callback(null, CompileController._getUserIdForCompile(req)) - }, - downloadPdf(req, res, next) { + async downloadPdf(req, res) { Metrics.inc('pdf-downloads') const projectId = req.params.Project_id - const rateLimit = function (callback) { + const rateLimit = () => pdfDownloadRateLimiter .consume(req.ip, 1, { method: 'ip' }) - .then(() => { - callback(null, true) - }) + .then(() => true) .catch(err => { if (err instanceof Error) { - callback(err) - } else { - callback(null, false) + throw err } + return false }) + + const project = await ProjectGetter.promises.getProject(projectId, { + name: 1, + }) + + res.contentType('application/pdf') + const filename = `${_CompileController._getSafeProjectName(project)}.pdf` + + if (req.query.popupDownload) { + res.setContentDisposition('attachment', { filename }) + } else { + res.setContentDisposition('inline', { filename }) } - ProjectGetter.getProject(projectId, { name: 1 }, function (err, project) { - if (err) { - return next(err) - } - res.contentType('application/pdf') - const filename = `${CompileController._getSafeProjectName(project)}.pdf` + let canContinue + try { + canContinue = await rateLimit() + } catch (err) { + logger.err({ err }, 'error checking rate limit for pdf download') + res.sendStatus(500) + return + } - if (req.query.popupDownload) { - res.setContentDisposition('attachment', { filename }) - } else { - res.setContentDisposition('inline', { filename }) - } + if (!canContinue) { + logger.debug({ projectId, ip: req.ip }, 'rate limit hit downloading pdf') + res.sendStatus(500) // should it be 429? + } else { + const userId = CompileController._getUserIdForCompile(req) - rateLimit(function (err, canContinue) { - if (err) { - logger.err({ err }, 'error checking rate limit for pdf download') - res.sendStatus(500) - } else if (!canContinue) { - logger.debug( - { projectId, ip: req.ip }, - 'rate limit hit downloading pdf' - ) - res.sendStatus(500) - } else { - CompileController._downloadAsUser(req, function (error, userId) { - if (error) { - return next(error) - } - const url = CompileController._getFileUrl( - projectId, - userId, - req.params.build_id, - 'output.pdf' - ) - CompileController.proxyToClsi( - projectId, - 'output-file', - url, - {}, - req, - res, - next - ) - }) - } - }) - }) + const url = _CompileController._getFileUrl( + projectId, + userId, + req.params.build_id, + 'output.pdf' + ) + await CompileController._proxyToClsi( + projectId, + 'output-file', + url, + {}, + req, + res + ) + } }, _getSafeProjectName(project) { return project.name.replace(/[^\p{L}\p{Nd}]/gu, '_') }, - deleteAuxFiles(req, res, next) { + async deleteAuxFiles(req, res) { const projectId = req.params.Project_id const { clsiserverid } = req.query - CompileController._compileAsUser(req, function (error, userId) { - if (error) { - return next(error) - } - CompileManager.deleteAuxFiles( - projectId, - userId, - clsiserverid, - function (error) { - if (error) { - return next(error) - } - res.sendStatus(200) - } - ) - }) + const userId = await CompileController._getUserIdForCompile(req) + await CompileManager.promises.deleteAuxFiles( + projectId, + userId, + clsiserverid + ) + res.sendStatus(200) }, // this is only used by templates, so is not called with a userId - compileAndDownloadPdf(req, res, next) { + async compileAndDownloadPdf(req, res) { const projectId = req.params.project_id - // pass userId as null, since templates are an "anonymous" compile - CompileManager.compile(projectId, null, {}, (err, _status, outputFiles) => { - if (err) { - logger.err( - { err, projectId }, - 'something went wrong compile and downloading pdf' - ) - res.sendStatus(500) - return - } - const pdf = outputFiles.find(f => f.path === 'output.pdf') - if (!pdf) { - logger.warn( - { projectId }, - 'something went wrong compile and downloading pdf: no pdf' - ) - res.sendStatus(500) - return - } - CompileController.proxyToClsi( - projectId, - 'output-file', - pdf.url, - {}, - req, - res, - next + + let outputFiles + try { + ;({ outputFiles } = await CompileManager.promises + // pass userId as null, since templates are an "anonymous" compile + .compile(projectId, null, {})) + } catch (err) { + logger.err( + { err, projectId }, + 'something went wrong compile and downloading pdf' ) - }) + res.sendStatus(500) + return + } + const pdf = outputFiles.find(f => f.path === 'output.pdf') + if (!pdf) { + logger.warn( + { projectId }, + 'something went wrong compile and downloading pdf: no pdf' + ) + res.sendStatus(500) + return + } + await CompileController._proxyToClsi( + projectId, + 'output-file', + pdf.url, + {}, + req, + res + ) }, - getFileFromClsi(req, res, next) { + async getFileFromClsi(req, res) { const projectId = req.params.Project_id - CompileController._downloadAsUser(req, function (error, userId) { - if (error) { - return next(error) - } + const userId = CompileController._getUserIdForCompile(req) - const qs = {} + const qs = {} - const url = CompileController._getFileUrl( - projectId, - userId, - req.params.build_id, - req.params.file - ) - CompileController.proxyToClsi( - projectId, - 'output-file', - url, - qs, - req, - res, - next - ) - }) + const url = _CompileController._getFileUrl( + projectId, + userId, + req.params.build_id, + req.params.file + ) + await CompileController._proxyToClsi( + projectId, + 'output-file', + url, + qs, + req, + res + ) }, - getFileFromClsiWithoutUser(req, res, next) { + async getFileFromClsiWithoutUser(req, res) { const submissionId = req.params.submission_id - const url = CompileController._getFileUrl( + const url = _CompileController._getFileUrl( submissionId, null, req.params.build_id, @@ -486,15 +444,14 @@ module.exports = CompileController = { Settings.defaultFeatures.compileGroup, compileBackendClass: Settings.apis.clsi.submissionBackendClass, } - CompileController.proxyToClsiWithLimits( + await CompileController._proxyToClsiWithLimits( submissionId, 'output-file', url, {}, limits, req, - res, - next + res ) }, @@ -522,51 +479,42 @@ module.exports = CompileController = { return `${path}/${action}` }, - proxySyncPdf(req, res, next) { + async proxySyncPdf(req, res) { const projectId = req.params.Project_id const { page, h, v, editorId, buildId } = req.query if (!page?.match(/^\d+$/)) { - return next(new Error('invalid page parameter')) + throw new Error('invalid page parameter') } if (!h?.match(/^-?\d+\.\d+$/)) { - return next(new Error('invalid h parameter')) + throw new Error('invalid h parameter') } if (!v?.match(/^-?\d+\.\d+$/)) { - return next(new Error('invalid v parameter')) + throw new Error('invalid v parameter') } // whether this request is going to a per-user container - CompileController._compileAsUser(req, function (error, userId) { - if (error) { - return next(error) - } - getImageNameForProject(projectId, (error, imageName) => { - if (error) return next(error) + const userId = CompileController._getUserIdForCompile(req) - getSplitTestOptionsCb(req, res, (error, splitTestOptions) => { - if (error) return next(error) - const { compileFromClsiCache } = splitTestOptions + const imageName = await getImageNameForProject(projectId) - const url = CompileController._getUrl(projectId, userId, 'sync/pdf') + const { compileFromClsiCache } = await _getSplitTestOptions(req, res) - CompileController.proxyToClsi( - projectId, - 'sync-to-pdf', - url, - { page, h, v, imageName, editorId, buildId, compileFromClsiCache }, - req, - res, - next - ) - }) - }) - }) + const url = _CompileController._getUrl(projectId, userId, 'sync/pdf') + + await CompileController._proxyToClsi( + projectId, + 'sync-to-pdf', + url, + { page, h, v, imageName, editorId, buildId, compileFromClsiCache }, + req, + res + ) }, - proxySyncCode(req, res, next) { + async proxySyncCode(req, res) { const projectId = req.params.Project_id const { file, line, column, editorId, buildId } = req.query if (file == null) { - return next(new Error('missing file parameter')) + throw new Error('missing file parameter') } // Check that we are dealing with a simple file path (this is not // strictly needed because synctex uses this parameter as a label @@ -575,225 +523,219 @@ module.exports = CompileController = { // allow those by replacing /./ with / const testPath = file.replace('/./', '/') if (Path.resolve('/', testPath) !== `/${testPath}`) { - return next(new Error('invalid file parameter')) + throw new Error('invalid file parameter') } if (!line?.match(/^\d+$/)) { - return next(new Error('invalid line parameter')) + throw new Error('invalid line parameter') } if (!column?.match(/^\d+$/)) { - return next(new Error('invalid column parameter')) + throw new Error('invalid column parameter') } - CompileController._compileAsUser(req, function (error, userId) { - if (error) { - return next(error) - } - getImageNameForProject(projectId, (error, imageName) => { - if (error) return next(error) + const userId = CompileController._getUserIdForCompile(req) - getSplitTestOptionsCb(req, res, (error, splitTestOptions) => { - if (error) return next(error) - const { compileFromClsiCache } = splitTestOptions + const imageName = await getImageNameForProject(projectId) - const url = CompileController._getUrl(projectId, userId, 'sync/code') - CompileController.proxyToClsi( - projectId, - 'sync-to-code', - url, - { - file, - line, - column, - imageName, - editorId, - buildId, - compileFromClsiCache, - }, - req, - res, - next - ) - }) - }) - }) - }, + const { compileFromClsiCache } = await _getSplitTestOptions(req, res) - proxyToClsi(projectId, action, url, qs, req, res, next) { - CompileManager.getProjectCompileLimits(projectId, function (error, limits) { - if (error) { - return next(error) - } - CompileController.proxyToClsiWithLimits( - projectId, - action, - url, - qs, - limits, - req, - res, - next - ) - }) - }, - - proxyToClsiWithLimits(projectId, action, url, qs, limits, req, res, next) { - _getPersistenceOptions( - req, + const url = _CompileController._getUrl(projectId, userId, 'sync/code') + await CompileController._proxyToClsi( projectId, - limits.compileGroup, - limits.compileBackendClass, - (err, persistenceOptions) => { - if (err) { - OError.tag(err, 'error getting cookie jar for clsi request') - return next(err) - } - url = new URL(`${Settings.apis.clsi.url}${url}`) - url.search = new URLSearchParams({ - ...persistenceOptions.qs, - ...qs, - }).toString() - const timer = new Metrics.Timer( - 'proxy_to_clsi', - 1, - { path: action }, - [0, 100, 1000, 2000, 5000, 10000, 15000, 20000, 30000, 45000, 60000] - ) - Metrics.inc('proxy_to_clsi', 1, { path: action, status: 'start' }) - fetchStreamWithResponse(url.href, { - method: req.method, - signal: AbortSignal.timeout(60 * 1000), - headers: persistenceOptions.headers, - }) - .then(({ stream, response }) => { - if (req.destroyed) { - // The client has disconnected already, avoid trying to write into the broken connection. - Metrics.inc('proxy_to_clsi', 1, { - path: action, - status: 'req-aborted', - }) - return - } - Metrics.inc('proxy_to_clsi', 1, { - path: action, - status: response.status, - }) - - for (const key of ['Content-Length', 'Content-Type']) { - if (response.headers.has(key)) { - res.setHeader(key, response.headers.get(key)) - } - } - res.writeHead(response.status) - return pipeline(stream, res) - }) - .then(() => { - timer.labels.status = 'success' - timer.done() - }) - .catch(err => { - const reqAborted = Boolean(req.destroyed) - const status = reqAborted ? 'req-aborted-late' : 'error' - timer.labels.status = status - const duration = timer.done() - Metrics.inc('proxy_to_clsi', 1, { path: action, status }) - const streamingStarted = Boolean(res.headersSent) - if (!streamingStarted) { - if (err instanceof RequestFailedError) { - res.sendStatus(err.response.status) - } else { - res.sendStatus(500) - } - } - if ( - streamingStarted && - reqAborted && - err.code === 'ERR_STREAM_PREMATURE_CLOSE' - ) { - // Ignore noisy spurious error - return - } - if ( - err instanceof RequestFailedError && - ['sync-to-code', 'sync-to-pdf', 'output-file'].includes(action) - ) { - // Ignore noisy error - // https://github.com/overleaf/internal/issues/15201 - return - } - logger.warn( - { - err, - projectId, - url, - action, - reqAborted, - streamingStarted, - duration, - }, - 'CLSI proxy error' - ) - }) - } + 'sync-to-code', + url, + { + file, + line, + column, + imageName, + editorId, + buildId, + compileFromClsiCache, + }, + req, + res ) }, - wordCount(req, res, next) { + async _proxyToClsi(projectId, action, url, qs, req, res) { + const limits = + await CompileManager.promises.getProjectCompileLimits(projectId) + return CompileController._proxyToClsiWithLimits( + projectId, + action, + url, + qs, + limits, + req, + res + ) + }, + + async _proxyToClsiWithLimits(projectId, action, url, qs, limits, req, res) { + const persistenceOptions = await _getPersistenceOptions( + req, + projectId, + limits.compileGroup, + limits.compileBackendClass + ).catch(err => { + OError.tag(err, 'error getting cookie jar for clsi request') + throw err + }) + + url = new URL(`${Settings.apis.clsi.url}${url}`) + url.search = new URLSearchParams({ + ...persistenceOptions.qs, + ...qs, + }).toString() + const timer = new Metrics.Timer( + 'proxy_to_clsi', + 1, + { path: action }, + [0, 100, 1000, 2000, 5000, 10000, 15000, 20000, 30000, 45000, 60000] + ) + Metrics.inc('proxy_to_clsi', 1, { path: action, status: 'start' }) + try { + const { stream, response } = await fetchStreamWithResponse(url.href, { + method: req.method, + signal: AbortSignal.timeout(60 * 1000), + headers: persistenceOptions.headers, + }) + if (req.destroyed) { + // The client has disconnected already, avoid trying to write into the broken connection. + Metrics.inc('proxy_to_clsi', 1, { + path: action, + status: 'req-aborted', + }) + return + } + Metrics.inc('proxy_to_clsi', 1, { + path: action, + status: response.status, + }) + + for (const key of ['Content-Length', 'Content-Type']) { + if (response.headers.has(key)) { + res.setHeader(key, response.headers.get(key)) + } + } + res.writeHead(response.status) + await pipeline(stream, res) + timer.labels.status = 'success' + timer.done() + } catch (err) { + const reqAborted = Boolean(req.destroyed) + const status = reqAborted ? 'req-aborted-late' : 'error' + timer.labels.status = status + const duration = timer.done() + Metrics.inc('proxy_to_clsi', 1, { path: action, status }) + const streamingStarted = Boolean(res.headersSent) + if (!streamingStarted) { + if (err instanceof RequestFailedError) { + res.sendStatus(err.response.status) + } else { + res.sendStatus(500) + } + } + if ( + streamingStarted && + reqAborted && + err.code === 'ERR_STREAM_PREMATURE_CLOSE' + ) { + // Ignore noisy spurious error + return + } + if ( + err instanceof RequestFailedError && + ['sync-to-code', 'sync-to-pdf', 'output-file'].includes(action) + ) { + // Ignore noisy error + // https://github.com/overleaf/internal/issues/15201 + return + } + logger.warn( + { + err, + projectId, + url, + action, + reqAborted, + streamingStarted, + duration, + }, + 'CLSI proxy error' + ) + } + }, + + async wordCount(req, res) { const projectId = req.params.Project_id const file = req.query.file || false const { clsiserverid } = req.query - CompileController._compileAsUser(req, function (error, userId) { - if (error) { - return next(error) - } - CompileManager.wordCount( - projectId, - userId, - file, - clsiserverid, - function (error, body) { - if (error) { - return next(error) - } - res.json(body) - } - ) - }) + const userId = CompileController._getUserIdForCompile(req) + + const body = await CompileManager.promises.wordCount( + projectId, + userId, + file, + clsiserverid + ) + res.json(body) }, } -function _getPersistenceOptions( +async function _getPersistenceOptions( req, projectId, compileGroup, - compileBackendClass, - callback + compileBackendClass ) { const { clsiserverid } = req.query const userId = SessionManager.getLoggedInUserId(req) if (clsiserverid && typeof clsiserverid === 'string') { - callback(null, { + return { qs: { clsiserverid, compileGroup, compileBackendClass }, headers: {}, - }) + } } else { - ClsiCookieManager.getServerId( + const clsiServerId = await ClsiCookieManager.promises.getServerId( projectId, userId, compileGroup, - compileBackendClass, - (err, clsiServerId) => { - if (err) return callback(err) - callback(null, { - qs: { compileGroup, compileBackendClass }, - headers: clsiServerId - ? { - Cookie: new Cookie({ - key: Settings.clsiCookie.key, - value: clsiServerId, - }).cookieString(), - } - : {}, - }) - } + compileBackendClass ) + return { + qs: { compileGroup, compileBackendClass }, + headers: clsiServerId + ? { + Cookie: new Cookie({ + key: Settings.clsiCookie.key, + value: clsiServerId, + }).cookieString(), + } + : {}, + } } } + +const CompileController = { + compile: expressify(_CompileController.compile), + stopCompile: expressify(_CompileController.stopCompile), + compileSubmission: expressify(_CompileController.compileSubmission), + downloadPdf: expressify(_CompileController.downloadPdf), // + compileAndDownloadPdf: expressify(_CompileController.compileAndDownloadPdf), + deleteAuxFiles: expressify(_CompileController.deleteAuxFiles), + getFileFromClsi: expressify(_CompileController.getFileFromClsi), + getFileFromClsiWithoutUser: expressify( + _CompileController.getFileFromClsiWithoutUser + ), + proxySyncPdf: expressify(_CompileController.proxySyncPdf), + proxySyncCode: expressify(_CompileController.proxySyncCode), + wordCount: expressify(_CompileController.wordCount), + + _getSafeProjectName: _CompileController._getSafeProjectName, + _getSplitTestOptions, + _getUserIdForCompile: _CompileController._getUserIdForCompile, + _proxyToClsi: _CompileController._proxyToClsi, + _proxyToClsiWithLimits: _CompileController._proxyToClsiWithLimits, +} + +module.exports = CompileController diff --git a/services/web/app/src/router.mjs b/services/web/app/src/router.mjs index 5e1a21c063..9201ad4c55 100644 --- a/services/web/app/src/router.mjs +++ b/services/web/app/src/router.mjs @@ -1187,7 +1187,9 @@ async function initialize(webRouter, privateApiRouter, publicApiRouter) { const sendRes = _.once(function (statusCode, message) { res.status(statusCode) plainTextResponse(res, message) - ClsiCookieManager.clearServerId(projectId, testUserId, () => {}) + ClsiCookieManager.promises + .clearServerId(projectId, testUserId) + .catch(() => {}) }) // force every compile to a new server // set a timeout let handler = setTimeout(function () { diff --git a/services/web/test/unit/src/Compile/ClsiCookieManagerTests.js b/services/web/test/unit/src/Compile/ClsiCookieManagerTests.js index b61991a100..082262c853 100644 --- a/services/web/test/unit/src/Compile/ClsiCookieManagerTests.js +++ b/services/web/test/unit/src/Compile/ClsiCookieManagerTests.js @@ -1,26 +1,22 @@ const sinon = require('sinon') -const { assert, expect } = require('chai') +const { expect } = require('chai') const modulePath = '../../../../app/src/Features/Compile/ClsiCookieManager.js' const SandboxedModule = require('sandboxed-module') -const realRequst = require('request') describe('ClsiCookieManager', function () { beforeEach(function () { this.redis = { auth() {}, get: sinon.stub(), - setex: sinon.stub().callsArg(3), + setex: sinon.stub().resolves(), } this.project_id = '123423431321-proj-id' this.user_id = 'abc-user-id' - this.request = { - post: sinon.stub(), - cookie: realRequst.cookie, - jar: realRequst.jar, - defaults: () => { - return this.request - }, + this.fetchUtils = { + fetchNothing: sinon.stub().returns(Promise.resolve()), + fetchStringWithResponse: sinon.stub().returns(Promise.resolve()), } + this.metrics = { inc: sinon.stub() } this.settings = { redis: { web: 'redis.something', @@ -41,7 +37,8 @@ describe('ClsiCookieManager', function () { client: () => this.redis, }), '@overleaf/settings': this.settings, - request: this.request, + '@overleaf/fetch-utils': this.fetchUtils, + '@overleaf/metrics': this.metrics, } this.ClsiCookieManager = SandboxedModule.require(modulePath, { requires: this.requires, @@ -49,74 +46,56 @@ describe('ClsiCookieManager', function () { }) describe('getServerId', function () { - it('should call get for the key', function (done) { - this.redis.get.callsArgWith(1, null, 'clsi-7') - this.ClsiCookieManager.getServerId( + it('should call get for the key', async function () { + this.redis.get.resolves('clsi-7') + const serverId = await this.ClsiCookieManager.promises.getServerId( this.project_id, this.user_id, '', - 'e2', - (err, serverId) => { - if (err) { - return done(err) - } - this.redis.get - .calledWith(`clsiserver:${this.project_id}:${this.user_id}`) - .should.equal(true) - serverId.should.equal('clsi-7') - done() - } + 'e2' ) + this.redis.get + .calledWith(`clsiserver:${this.project_id}:${this.user_id}`) + .should.equal(true) + serverId.should.equal('clsi-7') }) - it('should _populateServerIdViaRequest if no key is found', function (done) { - this.ClsiCookieManager._populateServerIdViaRequest = sinon + it('should _populateServerIdViaRequest if no key is found', async function () { + this.ClsiCookieManager.promises._populateServerIdViaRequest = sinon .stub() - .yields(null) - this.redis.get.callsArgWith(1, null) - this.ClsiCookieManager.getServerId( + .resolves() + this.redis.get.resolves(null) + await this.ClsiCookieManager.promises.getServerId( this.project_id, this.user_id, - '', - (err, serverId) => { - if (err) { - return done(err) - } - this.ClsiCookieManager._populateServerIdViaRequest - .calledWith(this.project_id, this.user_id) - .should.equal(true) - done() - } + '' ) + this.ClsiCookieManager.promises._populateServerIdViaRequest + .calledWith(this.project_id, this.user_id) + .should.equal(true) }) - it('should _populateServerIdViaRequest if no key is blank', function (done) { - this.ClsiCookieManager._populateServerIdViaRequest = sinon + it('should _populateServerIdViaRequest if no key is blank', async function () { + this.ClsiCookieManager.promises._populateServerIdViaRequest = sinon .stub() - .yields(null) - this.redis.get.callsArgWith(1, null, '') - this.ClsiCookieManager.getServerId( + .resolves(null) + this.redis.get.resolves('') + await this.ClsiCookieManager.promises.getServerId( this.project_id, this.user_id, '', - 'e2', - (err, serverId) => { - if (err) { - return done(err) - } - this.ClsiCookieManager._populateServerIdViaRequest - .calledWith(this.project_id, this.user_id) - .should.equal(true) - done() - } + 'e2' ) + this.ClsiCookieManager.promises._populateServerIdViaRequest + .calledWith(this.project_id, this.user_id) + .should.equal(true) }) }) describe('_populateServerIdViaRequest', function () { beforeEach(function () { this.clsiServerId = 'server-id' - this.ClsiCookieManager.setServerId = sinon.stub().yields() + this.ClsiCookieManager.promises.setServerId = sinon.stub().resolves() }) describe('with a server id in the response', function () { @@ -128,71 +107,54 @@ describe('ClsiCookieManager', function () { ], }, } - this.request.post.callsArgWith(1, null, this.response) + this.fetchUtils.fetchNothing.returns(this.response) }) - it('should make a request to the clsi', function (done) { - this.ClsiCookieManager._populateServerIdViaRequest( + it('should make a request to the clsi', async function () { + await this.ClsiCookieManager.promises._populateServerIdViaRequest( this.project_id, this.user_id, 'standard', - 'e2', - (err, serverId) => { - if (err) { - return done(err) - } - const args = this.ClsiCookieManager.setServerId.args[0] - args[0].should.equal(this.project_id) - args[1].should.equal(this.user_id) - args[2].should.equal('standard') - args[3].should.equal('e2') - args[4].should.deep.equal(this.clsiServerId) - done() - } + 'e2' ) + const args = this.ClsiCookieManager.promises.setServerId.args[0] + args[0].should.equal(this.project_id) + args[1].should.equal(this.user_id) + args[2].should.equal('standard') + args[3].should.equal('e2') + args[4].should.deep.equal(this.clsiServerId) }) - it('should return the server id', function (done) { - this.ClsiCookieManager._populateServerIdViaRequest( - this.project_id, - this.user_id, - '', - 'e2', - (err, serverId) => { - if (err) { - return done(err) - } - serverId.should.equal(this.clsiServerId) - done() - } - ) + it('should return the server id', async function () { + const serverId = + await this.ClsiCookieManager.promises._populateServerIdViaRequest( + this.project_id, + this.user_id, + '', + 'e2' + ) + serverId.should.equal(this.clsiServerId) }) }) describe('without a server id in the response', function () { beforeEach(function () { this.response = { headers: {} } - this.request.post.yields(null, this.response) + this.fetchUtils.fetchNothing.returns(this.response) }) - it('should not set the server id there is no server id in the response', function (done) { + it('should not set the server id there is no server id in the response', async function () { this.ClsiCookieManager._parseServerIdFromResponse = sinon .stub() .returns(null) - this.ClsiCookieManager.setServerId( + await this.ClsiCookieManager.promises.setServerId( this.project_id, this.user_id, 'standard', 'e2', this.clsiServerId, - null, - err => { - if (err) { - return done(err) - } - this.redis.setex.called.should.equal(false) - done() - } + null ) + this.redis.setex.called.should.equal(false) }) }) }) @@ -205,162 +167,148 @@ describe('ClsiCookieManager', function () { .returns('clsi-8') }) - it('should set the server id with a ttl', function (done) { - this.ClsiCookieManager.setServerId( + it('should set the server id with a ttl', async function () { + await this.ClsiCookieManager.promises.setServerId( this.project_id, this.user_id, 'standard', 'e2', this.clsiServerId, - null, - err => { - if (err) { - return done(err) - } - this.redis.setex.should.have.been.calledWith( - `clsiserver:${this.project_id}:${this.user_id}`, - this.settings.clsiCookie.ttlInSeconds, - this.clsiServerId - ) - done() - } + null + ) + this.redis.setex.should.have.been.calledWith( + `clsiserver:${this.project_id}:${this.user_id}`, + this.settings.clsiCookie.ttlInSeconds, + this.clsiServerId ) }) - it('should set the server id with the regular ttl for reg instance', function (done) { + it('should set the server id with the regular ttl for reg instance', async function () { this.clsiServerId = 'clsi-reg-8' - this.ClsiCookieManager.setServerId( + await this.ClsiCookieManager.promises.setServerId( this.project_id, this.user_id, 'standard', 'e2', this.clsiServerId, - null, - err => { - if (err) { - return done(err) - } - expect(this.redis.setex).to.have.been.calledWith( - `clsiserver:${this.project_id}:${this.user_id}`, - this.settings.clsiCookie.ttlInSecondsRegular, - this.clsiServerId - ) - done() - } + null + ) + expect(this.redis.setex).to.have.been.calledWith( + `clsiserver:${this.project_id}:${this.user_id}`, + this.settings.clsiCookie.ttlInSecondsRegular, + this.clsiServerId ) }) - it('should not set the server id if clsiCookies are not enabled', function (done) { + it('should not set the server id if clsiCookies are not enabled', async function () { delete this.settings.clsiCookie.key - this.ClsiCookieManager = SandboxedModule.require(modulePath, { + this.ClsiCookieManager2 = SandboxedModule.require(modulePath, { globals: { console, }, requires: this.requires, })() - this.ClsiCookieManager.setServerId( + await this.ClsiCookieManager2.promises.setServerId( this.project_id, this.user_id, 'standard', 'e2', this.clsiServerId, - null, - err => { - if (err) { - return done(err) - } - this.redis.setex.called.should.equal(false) - done() - } + null ) + this.redis.setex.called.should.equal(false) }) - it('should also set in the secondary if secondary redis is enabled', function (done) { - this.redis_secondary = { setex: sinon.stub().callsArg(3) } + it('should also set in the secondary if secondary redis is enabled', async function () { + this.redis_secondary = { setex: sinon.stub().resolves() } this.settings.redis.clsi_cookie_secondary = {} this.RedisWrapper.client = sinon.stub() this.RedisWrapper.client.withArgs('clsi_cookie').returns(this.redis) this.RedisWrapper.client .withArgs('clsi_cookie_secondary') .returns(this.redis_secondary) - this.ClsiCookieManager = SandboxedModule.require(modulePath, { + this.ClsiCookieManager2 = SandboxedModule.require(modulePath, { globals: { console, }, requires: this.requires, })() - this.ClsiCookieManager._parseServerIdFromResponse = sinon + this.ClsiCookieManager2._parseServerIdFromResponse = sinon .stub() .returns('clsi-8') - this.ClsiCookieManager.setServerId( + await this.ClsiCookieManager2.promises.setServerId( this.project_id, this.user_id, 'standard', 'e2', this.clsiServerId, - null, - err => { - if (err) { - return done(err) - } - this.redis_secondary.setex.should.have.been.calledWith( - `clsiserver:${this.project_id}:${this.user_id}`, - this.settings.clsiCookie.ttlInSeconds, - this.clsiServerId + null + ) + this.redis_secondary.setex.should.have.been.calledWith( + `clsiserver:${this.project_id}:${this.user_id}`, + this.settings.clsiCookie.ttlInSeconds, + this.clsiServerId + ) + }) + + describe('checkIsLoadSheddingEvent', function () { + beforeEach(function () { + this.fetchUtils.fetchStringWithResponse.reset() + this.call = async () => { + await this.ClsiCookieManager.promises.setServerId( + this.project_id, + this.user_id, + 'standard', + 'e2', + this.clsiServerId, + 'previous-clsi-server-id' + ) + expect( + this.fetchUtils.fetchStringWithResponse + ).to.have.been.calledWith( + `${this.settings.apis.clsi.url}/instance-state?clsiserverid=previous-clsi-server-id&compileGroup=standard&compileBackendClass=e2`, + { method: 'GET', signal: sinon.match.instanceOf(AbortSignal) } ) - done() } - ) - }) - }) + }) - describe('getCookieJar', function () { - beforeEach(function () { - this.ClsiCookieManager.getServerId = sinon.stub().yields(null, 'clsi-11') - }) + it('should report "load-shedding" when previous is UP', async function () { + this.fetchUtils.fetchStringWithResponse.resolves({ + response: { status: 200 }, + body: 'previous-clsi-server-id,UP\n', + }) + await this.call() + expect(this.metrics.inc).to.have.been.calledWith( + 'clsi-lb-switch-backend', + 1, + { status: 'load-shedding' } + ) + }) - it('should return a jar with the cookie set populated from redis', function (done) { - this.ClsiCookieManager.getCookieJar( - this.project_id, - this.user_id, - '', - 'e2', - (err, jar) => { - if (err) { - return done(err) - } - jar._jar.store.idx['clsi.example.com']['/'][ - this.settings.clsiCookie.key - ].key.should.equal - jar._jar.store.idx['clsi.example.com']['/'][ - this.settings.clsiCookie.key - ].value.should.equal('clsi-11') - done() - } - ) - }) + it('should report "cycle" when other is UP', async function () { + this.fetchUtils.fetchStringWithResponse.resolves({ + response: { status: 200 }, + body: 'other-clsi-server-id,UP\n', + }) + await this.call() + expect(this.metrics.inc).to.have.been.calledWith( + 'clsi-lb-switch-backend', + 1, + { status: 'cycle' } + ) + }) - it('should return empty cookie jar if clsiCookies are not enabled', function (done) { - delete this.settings.clsiCookie.key - this.ClsiCookieManager = SandboxedModule.require(modulePath, { - globals: { - console, - }, - requires: this.requires, - })() - this.ClsiCookieManager.getCookieJar( - this.project_id, - this.user_id, - '', - 'e2', - (err, jar) => { - if (err) { - return done(err) - } - assert.deepEqual(jar, realRequst.jar()) - done() - } - ) + it('should report "cycle" when previous is 404', async function () { + this.fetchUtils.fetchStringWithResponse.resolves({ + response: { status: 404 }, + }) + await this.call() + expect(this.metrics.inc).to.have.been.calledWith( + 'clsi-lb-switch-backend', + 1, + { status: 'cycle' } + ) + }) }) }) }) diff --git a/services/web/test/unit/src/Compile/CompileControllerTests.js b/services/web/test/unit/src/Compile/CompileControllerTests.js index 07e433b5af..df7276131c 100644 --- a/services/web/test/unit/src/Compile/CompileControllerTests.js +++ b/services/web/test/unit/src/Compile/CompileControllerTests.js @@ -1,4 +1,3 @@ -/* eslint-disable mocha/handle-done-callback */ const sinon = require('sinon') const { expect } = require('chai') const modulePath = '../../../../app/src/Features/Compile/CompileController.js' @@ -19,8 +18,15 @@ describe('CompileController', function () { compileTimeout: 100, }, } - this.CompileManager = { compile: sinon.stub() } - this.ClsiManager = {} + this.CompileManager = { + promises: { + compile: sinon.stub(), + getProjectCompileLimits: sinon.stub(), + }, + } + this.ClsiManager = { + promises: {}, + } this.UserGetter = { getUser: sinon.stub() } this.rateLimiter = { consume: sinon.stub().resolves(), @@ -47,10 +53,11 @@ describe('CompileController', function () { }, } this.ClsiCookieManager = { - getServerId: sinon.stub().yields(null, 'clsi-server-id-from-redis'), + promises: { + getServerId: sinon.stub().resolves('clsi-server-id-from-redis'), + }, } this.SessionManager = { - getLoggedInUser: sinon.stub().callsArgWith(1, null, this.user), getLoggedInUserId: sinon.stub().returns(this.user_id), getSessionUser: sinon.stub().returns(this.user), isUserLoggedIn: sinon.stub().returns(true), @@ -76,8 +83,9 @@ describe('CompileController', function () { 'stream/promises': { pipeline: this.pipeline }, '@overleaf/settings': this.settings, '@overleaf/fetch-utils': this.fetchUtils, - request: (this.request = sinon.stub()), - '../Project/ProjectGetter': (this.ProjectGetter = {}), + '../Project/ProjectGetter': (this.ProjectGetter = { + promises: {}, + }), '@overleaf/metrics': (this.Metrics = { inc: sinon.stub(), Timer: class { @@ -121,25 +129,23 @@ describe('CompileController', function () { beforeEach(function () { this.req.params = { Project_id: this.projectId } this.req.session = {} - this.CompileManager.compile = sinon.stub().callsArgWith( - 3, - null, - (this.status = 'success'), - (this.outputFiles = [ + this.CompileManager.promises.compile = sinon.stub().resolves({ + status: (this.status = 'success'), + outputFiles: (this.outputFiles = [ { path: 'output.pdf', url: `/project/${this.projectId}/user/${this.user_id}/build/id/output.pdf`, type: 'pdf', }, ]), - undefined, - undefined, - undefined, - undefined, - undefined, - undefined, - this.build_id - ) + clsiServerId: undefined, + limits: undefined, + validationProblems: undefined, + stats: undefined, + timings: undefined, + outputUrlPrefix: undefined, + buildId: this.build_id, + }) }) describe('pdfDownloadDomain', function () { @@ -148,9 +154,8 @@ describe('CompileController', function () { }) describe('when clsi does not emit zone prefix', function () { - beforeEach(function (done) { - this.res.callback = done - this.CompileController.compile(this.req, this.res, this.next) + beforeEach(async function () { + await this.CompileController.compile(this.req, this.res, this.next) }) it('should add domain verbatim', function () { @@ -177,28 +182,25 @@ describe('CompileController', function () { }) describe('when clsi emits a zone prefix', function () { - beforeEach(function (done) { - this.res.callback = done - this.CompileManager.compile = sinon.stub().callsArgWith( - 3, - null, - (this.status = 'success'), - (this.outputFiles = [ + beforeEach(async function () { + this.CompileManager.promises.compile = sinon.stub().resolves({ + status: (this.status = 'success'), + outputFiles: (this.outputFiles = [ { path: 'output.pdf', url: `/project/${this.projectId}/user/${this.user_id}/build/id/output.pdf`, type: 'pdf', }, ]), - undefined, // clsiServerId - undefined, // limits - undefined, // validationProblems - undefined, // stats - undefined, // timings - '/zone/b', - this.build_id - ) - this.CompileController.compile(this.req, this.res, this.next) + clsiServerId: undefined, + limits: undefined, + validationProblems: undefined, + stats: undefined, + timings: undefined, + outputUrlPrefix: '/zone/b', + buildId: this.build_id, + }) + await this.CompileController.compile(this.req, this.res, this.next) }) it('should add the zone prefix', function () { @@ -227,9 +229,8 @@ describe('CompileController', function () { }) describe('when not an auto compile', function () { - beforeEach(function (done) { - this.res.callback = done - this.CompileController.compile(this.req, this.res, this.next) + beforeEach(async function () { + await this.CompileController.compile(this.req, this.res, this.next) }) it('should look up the user id', function () { @@ -239,7 +240,7 @@ describe('CompileController', function () { }) it('should do the compile without the auto compile flag', function () { - this.CompileManager.compile.should.have.been.calledWith( + this.CompileManager.promises.compile.should.have.been.calledWith( this.projectId, this.user_id, { @@ -276,14 +277,13 @@ describe('CompileController', function () { }) describe('when an auto compile', function () { - beforeEach(function (done) { - this.res.callback = done + beforeEach(async function () { this.req.query = { auto_compile: 'true' } - this.CompileController.compile(this.req, this.res, this.next) + await this.CompileController.compile(this.req, this.res, this.next) }) it('should do the compile with the auto compile flag', function () { - this.CompileManager.compile.should.have.been.calledWith( + this.CompileManager.promises.compile.should.have.been.calledWith( this.projectId, this.user_id, { @@ -301,14 +301,13 @@ describe('CompileController', function () { }) describe('with the draft attribute', function () { - beforeEach(function (done) { - this.res.callback = done + beforeEach(async function () { this.req.body = { draft: true } - this.CompileController.compile(this.req, this.res, this.next) + await this.CompileController.compile(this.req, this.res, this.next) }) it('should do the compile without the draft compile flag', function () { - this.CompileManager.compile.should.have.been.calledWith( + this.CompileManager.promises.compile.should.have.been.calledWith( this.projectId, this.user_id, { @@ -327,14 +326,13 @@ describe('CompileController', function () { }) describe('with an editor id', function () { - beforeEach(function (done) { - this.res.callback = done + beforeEach(async function () { this.req.body = { editorId: 'the-editor-id' } - this.CompileController.compile(this.req, this.res, this.next) + await this.CompileController.compile(this.req, this.res, this.next) }) it('should pass the editor id to the compiler', function () { - this.CompileManager.compile.should.have.been.calledWith( + this.CompileManager.promises.compile.should.have.been.calledWith( this.projectId, this.user_id, { @@ -357,25 +355,29 @@ describe('CompileController', function () { this.submission_id = 'sub-1234' this.req.params = { submission_id: this.submission_id } this.req.body = {} - this.ClsiManager.sendExternalRequest = sinon - .stub() - .callsArgWith( - 3, - null, - (this.status = 'success'), - (this.outputFiles = ['mock-output-files']), - (this.clsiServerId = 'mock-server-id'), - (this.validationProblems = null) - ) + this.ClsiManager.promises.sendExternalRequest = sinon.stub().resolves({ + status: (this.status = 'success'), + outputFiles: (this.outputFiles = ['mock-output-files']), + clsiServerId: 'mock-server-id', + validationProblems: null, + }) }) - it('should set the content-type of the response to application/json', function () { - this.CompileController.compileSubmission(this.req, this.res, this.next) + it('should set the content-type of the response to application/json', async function () { + await this.CompileController.compileSubmission( + this.req, + this.res, + this.next + ) this.res.contentType.calledWith('application/json').should.equal(true) }) - it('should send a successful response reporting the status and files', function () { - this.CompileController.compileSubmission(this.req, this.res, this.next) + it('should send a successful response reporting the status and files', async function () { + await this.CompileController.compileSubmission( + this.req, + this.res, + this.next + ) this.res.statusCode.should.equal(200) this.res.body.should.equal( JSON.stringify({ @@ -397,7 +399,7 @@ describe('CompileController', function () { }) it('should use the supplied values', function () { - this.ClsiManager.sendExternalRequest.should.have.been.calledWith( + this.ClsiManager.promises.sendExternalRequest.should.have.been.calledWith( this.submission_id, { compileGroup: 'special', timeout: 600 }, { compileGroup: 'special', compileBackendClass: 'n2d', timeout: 600 } @@ -417,7 +419,7 @@ describe('CompileController', function () { }) it('should use the other options but default values for compileGroup and timeout', function () { - this.ClsiManager.sendExternalRequest.should.have.been.calledWith( + this.ClsiManager.promises.sendExternalRequest.should.have.been.calledWith( this.submission_id, { rootResourcePath: 'main.tex', @@ -441,24 +443,21 @@ describe('CompileController', function () { describe('downloadPdf', function () { beforeEach(function () { + this.CompileController._proxyToClsi = sinon.stub().resolves() this.req.params = { Project_id: this.projectId } - this.project = { name: 'test namè; 1' } - this.ProjectGetter.getProject = sinon + this.ProjectGetter.promises.getProject = sinon .stub() - .callsArgWith(2, null, this.project) + .resolves(this.project) }) describe('when downloading for embedding', function () { - beforeEach(function (done) { - this.CompileController.proxyToClsi = sinon - .stub() - .callsFake(() => done()) - this.CompileController.downloadPdf(this.req, this.res, this.next) + beforeEach(async function () { + await this.CompileController.downloadPdf(this.req, this.res, this.next) }) it('should look up the project', function () { - this.ProjectGetter.getProject + this.ProjectGetter.promises.getProject .calledWith(this.projectId, { name: 1 }) .should.equal(true) }) @@ -478,43 +477,66 @@ describe('CompileController', function () { }) it('should proxy the PDF from the CLSI', function () { - this.CompileController.proxyToClsi + this.CompileController._proxyToClsi .calledWith( this.projectId, 'output-file', `/project/${this.projectId}/user/${this.user_id}/output/output.pdf`, {}, this.req, - this.res, - this.next + this.res ) .should.equal(true) }) }) describe('when a build-id is provided', function () { - beforeEach(function (done) { + beforeEach(async function () { this.req.params.build_id = this.build_id - this.CompileController.proxyToClsi = sinon - .stub() - .callsFake(() => done()) - this.CompileController.downloadPdf(this.req, this.res, this.next) + await this.CompileController.downloadPdf(this.req, this.res, this.next) }) it('should proxy the PDF from the CLSI, with a build-id', function () { - this.CompileController.proxyToClsi + this.CompileController._proxyToClsi .calledWith( this.projectId, 'output-file', `/project/${this.projectId}/user/${this.user_id}/build/${this.build_id}/output/output.pdf`, {}, this.req, - this.res, - this.next + this.res ) .should.equal(true) }) }) + + describe('when rate-limited', function () { + beforeEach(async function () { + this.rateLimiter.consume.rejects({ + msBeforeNext: 250, + remainingPoints: 0, + consumedPoints: 5, + isFirstInDuration: false, + }) + }) + it('should return 500', async function () { + await this.CompileController.downloadPdf(this.req, this.res, this.next) + // should it be 429 instead? + this.res.sendStatus.calledWith(500).should.equal(true) + this.CompileController._proxyToClsi.should.not.have.been.called + }) + }) + + describe('when rate-limit errors', function () { + beforeEach(async function () { + this.rateLimiter.consume.rejects(new Error('uh oh')) + }) + it('should return 500', async function () { + await this.CompileController.downloadPdf(this.req, this.res, this.next) + this.res.sendStatus.calledWith(500).should.equal(true) + this.CompileController._proxyToClsi.should.not.have.been.called + }) + }) }) describe('getFileFromClsiWithoutUser', function () { @@ -528,12 +550,12 @@ describe('CompileController', function () { } this.req.body = {} this.expected_url = `/project/${this.submission_id}/build/${this.build_id}/output/${this.file}` - this.CompileController.proxyToClsiWithLimits = sinon.stub() + this.CompileController._proxyToClsiWithLimits = sinon.stub() }) describe('without limits specified', function () { - beforeEach(function () { - this.CompileController.getFileFromClsiWithoutUser( + beforeEach(async function () { + await this.CompileController.getFileFromClsiWithoutUser( this.req, this.res, this.next @@ -541,15 +563,12 @@ describe('CompileController', function () { }) it('should proxy to CLSI with correct URL and default limits', function () { - this.CompileController.proxyToClsiWithLimits.should.have.been.calledWith( + this.CompileController._proxyToClsiWithLimits.should.have.been.calledWith( this.submission_id, 'output-file', this.expected_url, {}, - { - compileGroup: 'standard', - compileBackendClass: 'n2d', - } + { compileGroup: 'standard', compileBackendClass: 'n2d' } ) }) }) @@ -565,7 +584,7 @@ describe('CompileController', function () { }) it('should proxy to CLSI with correct URL and specified limits', function () { - this.CompileController.proxyToClsiWithLimits.should.have.been.calledWith( + this.CompileController._proxyToClsiWithLimits.should.have.been.calledWith( this.submission_id, 'output-file', this.expected_url, @@ -581,7 +600,7 @@ describe('CompileController', function () { describe('proxySyncCode', function () { let file, line, column, imageName, editorId, buildId - beforeEach(function (done) { + beforeEach(async function () { this.req.params = { Project_id: this.projectId } file = 'main.tex' line = String(Date.now()) @@ -591,17 +610,17 @@ describe('CompileController', function () { this.req.query = { file, line, column, editorId, buildId } imageName = 'foo/bar:tag-0' - this.ProjectGetter.getProject = sinon.stub().yields(null, { imageName }) + this.ProjectGetter.promises.getProject = sinon + .stub() + .resolves({ imageName }) - this.next.callsFake(done) - this.res.callback = done - this.CompileController.proxyToClsi = sinon.stub().callsFake(() => done()) + this.CompileController._proxyToClsi = sinon.stub().resolves() - this.CompileController.proxySyncCode(this.req, this.res, this.next) + await this.CompileController.proxySyncCode(this.req, this.res, this.next) }) it('should proxy the request with an imageName', function () { - expect(this.CompileController.proxyToClsi).to.have.been.calledWith( + expect(this.CompileController._proxyToClsi).to.have.been.calledWith( this.projectId, 'sync-to-code', `/project/${this.projectId}/user/${this.user_id}/sync/code`, @@ -615,8 +634,7 @@ describe('CompileController', function () { compileFromClsiCache: false, }, this.req, - this.res, - this.next + this.res ) }) }) @@ -624,7 +642,7 @@ describe('CompileController', function () { describe('proxySyncPdf', function () { let page, h, v, imageName, editorId, buildId - beforeEach(function (done) { + beforeEach(async function () { this.req.params = { Project_id: this.projectId } page = String(Date.now()) h = String(Math.random()) @@ -634,17 +652,17 @@ describe('CompileController', function () { this.req.query = { page, h, v, editorId, buildId } imageName = 'foo/bar:tag-1' - this.ProjectGetter.getProject = sinon.stub().yields(null, { imageName }) + this.ProjectGetter.promises.getProject = sinon + .stub() + .resolves({ imageName }) - this.next.callsFake(done) - this.res.callback = done - this.CompileController.proxyToClsi = sinon.stub().callsFake(() => done()) + this.CompileController._proxyToClsi = sinon.stub() - this.CompileController.proxySyncPdf(this.req, this.res, this.next) + await this.CompileController.proxySyncPdf(this.req, this.res, this.next) }) it('should proxy the request with an imageName', function () { - expect(this.CompileController.proxyToClsi).to.have.been.calledWith( + expect(this.CompileController._proxyToClsi).to.have.been.calledWith( this.projectId, 'sync-to-pdf', `/project/${this.projectId}/user/${this.user_id}/sync/pdf`, @@ -658,13 +676,12 @@ describe('CompileController', function () { compileFromClsiCache: false, }, this.req, - this.res, - this.next + this.res ) }) }) - describe('proxyToClsi', function () { + describe('_proxyToClsi', function () { beforeEach(function () { this.req.method = 'mock-method' this.req.headers = { @@ -677,15 +694,14 @@ describe('CompileController', function () { describe('old pdf viewer', function () { describe('user with standard priority', function () { - beforeEach(function (done) { - this.res.callback = done - this.CompileManager.getProjectCompileLimits = sinon + beforeEach(async function () { + this.CompileManager.promises.getProjectCompileLimits = sinon .stub() - .callsArgWith(1, null, { + .resolves({ compileGroup: 'standard', compileBackendClass: 'e2', }) - this.CompileController.proxyToClsi( + await this.CompileController._proxyToClsi( this.projectId, 'output-file', (this.url = '/test'), @@ -708,15 +724,14 @@ describe('CompileController', function () { }) describe('user with priority compile', function () { - beforeEach(function (done) { - this.res.callback = done - this.CompileManager.getProjectCompileLimits = sinon + beforeEach(async function () { + this.CompileManager.promises.getProjectCompileLimits = sinon .stub() - .callsArgWith(1, null, { + .resolves({ compileGroup: 'priority', compileBackendClass: 'c2d', }) - this.CompileController.proxyToClsi( + await this.CompileController._proxyToClsi( this.projectId, 'output-file', (this.url = '/test'), @@ -735,16 +750,15 @@ describe('CompileController', function () { }) describe('user with standard priority via query string', function () { - beforeEach(function (done) { - this.res.callback = done + beforeEach(async function () { this.req.query = { compileGroup: 'standard' } - this.CompileManager.getProjectCompileLimits = sinon + this.CompileManager.promises.getProjectCompileLimits = sinon .stub() - .callsArgWith(1, null, { + .resolves({ compileGroup: 'standard', compileBackendClass: 'e2', }) - this.CompileController.proxyToClsi( + await this.CompileController._proxyToClsi( this.projectId, 'output-file', (this.url = '/test'), @@ -767,16 +781,15 @@ describe('CompileController', function () { }) describe('user with non-existent priority via query string', function () { - beforeEach(function (done) { - this.res.callback = done + beforeEach(async function () { this.req.query = { compileGroup: 'foobar' } - this.CompileManager.getProjectCompileLimits = sinon + this.CompileManager.promises.getProjectCompileLimits = sinon .stub() - .callsArgWith(1, null, { + .resolves({ compileGroup: 'standard', compileBackendClass: 'e2', }) - this.CompileController.proxyToClsi( + await this.CompileController._proxyToClsi( this.projectId, 'output-file', (this.url = '/test'), @@ -795,16 +808,15 @@ describe('CompileController', function () { }) describe('user with build parameter via query string', function () { - beforeEach(function (done) { - this.res.callback = done - this.CompileManager.getProjectCompileLimits = sinon + beforeEach(async function () { + this.CompileManager.promises.getProjectCompileLimits = sinon .stub() - .callsArgWith(1, null, { + .resolves({ compileGroup: 'standard', compileBackendClass: 'e2', }) this.req.query = { build: 1234 } - this.CompileController.proxyToClsi( + await this.CompileController._proxyToClsi( this.projectId, 'output-file', (this.url = '/test'), @@ -825,16 +837,16 @@ describe('CompileController', function () { }) describe('deleteAuxFiles', function () { - beforeEach(function () { - this.CompileManager.deleteAuxFiles = sinon.stub().yields() + beforeEach(async function () { + this.CompileManager.promises.deleteAuxFiles = sinon.stub().resolves() this.req.params = { Project_id: this.projectId } this.req.query = { clsiserverid: 'node-1' } this.res.sendStatus = sinon.stub() - this.CompileController.deleteAuxFiles(this.req, this.res, this.next) + await this.CompileController.deleteAuxFiles(this.req, this.res, this.next) }) it('should proxy to the CLSI', function () { - this.CompileManager.deleteAuxFiles + this.CompileManager.promises.deleteAuxFiles .calledWith(this.projectId, this.user_id, 'node-1') .should.equal(true) }) @@ -852,26 +864,25 @@ describe('CompileController', function () { }, } this.downloadPath = `/project/${this.projectId}/build/123/output/output.pdf` - this.CompileManager.compile.callsArgWith(3, null, 'success', [ - { - path: 'output.pdf', - url: this.downloadPath, - }, - ]) - this.CompileController.proxyToClsi = sinon.stub() + this.CompileManager.promises.compile.resolves({ + status: 'success', + outputFiles: [{ path: 'output.pdf', url: this.downloadPath }], + }) + this.CompileController._proxyToClsi = sinon.stub() this.res = { send: () => {}, sendStatus: sinon.stub() } }) - it('should call compile in the compile manager', function (done) { - this.CompileController.compileAndDownloadPdf(this.req, this.res) - this.CompileManager.compile.calledWith(this.projectId).should.equal(true) - done() + it('should call compile in the compile manager', async function () { + await this.CompileController.compileAndDownloadPdf(this.req, this.res) + this.CompileManager.promises.compile + .calledWith(this.projectId) + .should.equal(true) }) - it('should proxy the res to the clsi with correct url', function (done) { - this.CompileController.compileAndDownloadPdf(this.req, this.res) + it('should proxy the res to the clsi with correct url', async function () { + await this.CompileController.compileAndDownloadPdf(this.req, this.res) sinon.assert.calledWith( - this.CompileController.proxyToClsi, + this.CompileController._proxyToClsi, this.projectId, 'output-file', this.downloadPath, @@ -880,7 +891,7 @@ describe('CompileController', function () { this.res ) - this.CompileController.proxyToClsi + this.CompileController._proxyToClsi .calledWith( this.projectId, 'output-file', @@ -890,38 +901,44 @@ describe('CompileController', function () { this.res ) .should.equal(true) - done() }) - it('should not download anything on compilation failures', function () { - this.CompileManager.compile.yields(new Error('failed')) - this.CompileController.compileAndDownloadPdf(this.req, this.res) + it('should not download anything on compilation failures', async function () { + this.CompileManager.promises.compile.rejects(new Error('failed')) + await this.CompileController.compileAndDownloadPdf( + this.req, + this.res, + this.next + ) this.res.sendStatus.should.have.been.calledWith(500) - this.CompileController.proxyToClsi.should.not.have.been.called + this.CompileController._proxyToClsi.should.not.have.been.called }) - it('should not download anything on missing pdf', function () { - this.CompileManager.compile.yields(null, 'success', []) - this.CompileController.compileAndDownloadPdf(this.req, this.res) + it('should not download anything on missing pdf', async function () { + this.CompileManager.promises.compile.resolves({ + status: 'success', + outputFiles: [], + }) + await this.CompileController.compileAndDownloadPdf(this.req, this.res) this.res.sendStatus.should.have.been.calledWith(500) - this.CompileController.proxyToClsi.should.not.have.been.called + this.CompileController._proxyToClsi.should.not.have.been.called }) }) describe('wordCount', function () { - beforeEach(function () { - this.CompileManager.wordCount = sinon + beforeEach(async function () { + this.CompileManager.promises.wordCount = sinon .stub() - .yields(null, { content: 'body' }) + .resolves({ content: 'body' }) this.req.params = { Project_id: this.projectId } this.req.query = { clsiserverid: 'node-42' } this.res.json = sinon.stub() this.res.contentType = sinon.stub() - this.CompileController.wordCount(this.req, this.res, this.next) + await this.CompileController.wordCount(this.req, this.res, this.next) }) it('should proxy to the CLSI', function () { - this.CompileManager.wordCount + this.CompileManager.promises.wordCount .calledWith(this.projectId, this.user_id, false, 'node-42') .should.equal(true) }) From 59275eeb84e76878829a843896625a54444888d2 Mon Sep 17 00:00:00 2001 From: Jessica Lawshe <5312836+lawshe@users.noreply.github.com> Date: Tue, 6 May 2025 10:23:14 -0500 Subject: [PATCH 020/194] Merge pull request #24919 from overleaf/jel-create-group-audit-log [web] Add group audit log GitOrigin-RevId: b59c38c57f555f18cdfa5dd697ad38d78b590996 --- .../web/app/src/infrastructure/mongodb.js | 1 + .../web/app/src/models/GroupAuditLogEntry.js | 23 ++++++++++++ services/web/frontend/js/utils/meta.ts | 1 + .../20250409155536_group_audit_log_index.mjs | 35 +++++++++++++++++++ .../test/acceptance/src/helpers/groupSSO.mjs | 18 +++++----- 5 files changed, 70 insertions(+), 8 deletions(-) create mode 100644 services/web/app/src/models/GroupAuditLogEntry.js create mode 100644 services/web/migrations/20250409155536_group_audit_log_index.mjs diff --git a/services/web/app/src/infrastructure/mongodb.js b/services/web/app/src/infrastructure/mongodb.js index aa7aa4ac44..7fc1039140 100644 --- a/services/web/app/src/infrastructure/mongodb.js +++ b/services/web/app/src/infrastructure/mongodb.js @@ -49,6 +49,7 @@ const db = { githubSyncUserCredentials: internalDb.collection('githubSyncUserCredentials'), globalMetrics: internalDb.collection('globalMetrics'), grouppolicies: internalDb.collection('grouppolicies'), + groupAuditLogEntries: internalDb.collection('groupAuditLogEntries'), institutions: internalDb.collection('institutions'), messages: internalDb.collection('messages'), migrations: internalDb.collection('migrations'), diff --git a/services/web/app/src/models/GroupAuditLogEntry.js b/services/web/app/src/models/GroupAuditLogEntry.js new file mode 100644 index 0000000000..3bda4ebf95 --- /dev/null +++ b/services/web/app/src/models/GroupAuditLogEntry.js @@ -0,0 +1,23 @@ +const mongoose = require('../infrastructure/Mongoose') +const { Schema } = mongoose + +const GroupAuditLogEntrySchema = new Schema( + { + groupId: { type: Schema.Types.ObjectId, index: true }, + info: { type: Object }, + initiatorId: { type: Schema.Types.ObjectId }, + ipAddress: { type: String }, + operation: { type: String }, + timestamp: { type: Date, default: Date.now }, + }, + { + collection: 'groupAuditLogEntries', + minimize: false, + } +) + +exports.GroupAuditLogEntry = mongoose.model( + 'GroupAuditLogEntry', + GroupAuditLogEntrySchema +) +exports.GroupAuditLogEntrySchema = GroupAuditLogEntrySchema diff --git a/services/web/frontend/js/utils/meta.ts b/services/web/frontend/js/utils/meta.ts index 7aab88b050..6c7209a5bb 100644 --- a/services/web/frontend/js/utils/meta.ts +++ b/services/web/frontend/js/utils/meta.ts @@ -103,6 +103,7 @@ export interface Meta { 'ol-gitBridgeEnabled': boolean 'ol-gitBridgePublicBaseUrl': string 'ol-github': { enabled: boolean; error: boolean } + 'ol-groupAuditLogs': [] 'ol-groupId': string 'ol-groupName': string 'ol-groupPlans': GroupPlans diff --git a/services/web/migrations/20250409155536_group_audit_log_index.mjs b/services/web/migrations/20250409155536_group_audit_log_index.mjs new file mode 100644 index 0000000000..282b3c6d2d --- /dev/null +++ b/services/web/migrations/20250409155536_group_audit_log_index.mjs @@ -0,0 +1,35 @@ +/* eslint-disable no-unused-vars */ + +import Helpers from './lib/helpers.mjs' + +const tags = ['saas'] + +const indexes = [ + { + key: { + groupId: 1, + timestamp: 1, + }, + name: 'groupId_1_timestamp_1', + }, +] + +const migrate = async client => { + const { db } = client + await Helpers.addIndexesToCollection(db.groupAuditLogEntries, indexes) +} + +const rollback = async client => { + const { db } = client + try { + await Helpers.dropIndexesFromCollection(db.groupAuditLogEntries, indexes) + } catch (err) { + console.error('Something went wrong rolling back the migrations', err) + } +} + +export default { + tags, + migrate, + rollback, +} diff --git a/services/web/test/acceptance/src/helpers/groupSSO.mjs b/services/web/test/acceptance/src/helpers/groupSSO.mjs index f7efeb9e63..c5bde77236 100644 --- a/services/web/test/acceptance/src/helpers/groupSSO.mjs +++ b/services/web/test/acceptance/src/helpers/groupSSO.mjs @@ -34,7 +34,7 @@ export const baseSsoConfig = { userIdAttribute, } // the database also sets enabled and validated, but we cannot set that in the POST request for /manage/groups/:ID/settings/sso -export async function createGroupSSO() { +export async function createGroupSSO(SSOConfigValidated = true) { const nonSSOMemberHelper = await UserHelper.createUser() const nonSSOMember = nonSSOMemberHelper.user @@ -47,7 +47,7 @@ export async function createGroupSSO() { const ssoConfig = new SSOConfig({ ...baseSsoConfig, enabled: true, - validated: true, + validated: SSOConfigValidated, }) await ssoConfig.save() @@ -68,12 +68,14 @@ export async function createGroupSSO() { const enrollmentUrl = getEnrollmentUrl(subscriptionId) const internalProviderId = getProviderId(subscriptionId) - await linkGroupMember( - memberUser.email, - memberUser.password, - subscriptionId, - 'mock@email.com' - ) + if (SSOConfigValidated) { + await linkGroupMember( + memberUser.email, + memberUser.password, + subscriptionId, + 'mock@email.com' + ) + } const userHelper = new UserHelper() From f29bd47911c97c08ca76755da099410a1e69777d Mon Sep 17 00:00:00 2001 From: Jimmy Domagala-Tang Date: Tue, 6 May 2025 11:23:50 -0400 Subject: [PATCH 021/194] Merge pull request #25252 from overleaf/jdt-add-addon-to-cancellation-mssg-in-subs Show Assist Add-on for pending and cancelled subscriptions GitOrigin-RevId: df733d7078c231a5de989bc070b37e3c250fdb37 --- .../states/active/pending-plan-change.tsx | 16 ++++--------- .../components/dashboard/states/canceled.tsx | 18 +++++++++++++++ .../subscription/data/add-on-codes.ts | 23 +++++++++++++++++++ 3 files changed, 46 insertions(+), 11 deletions(-) diff --git a/services/web/frontend/js/features/subscription/components/dashboard/states/active/pending-plan-change.tsx b/services/web/frontend/js/features/subscription/components/dashboard/states/active/pending-plan-change.tsx index 46d5a1cc90..cf7452941d 100644 --- a/services/web/frontend/js/features/subscription/components/dashboard/states/active/pending-plan-change.tsx +++ b/services/web/frontend/js/features/subscription/components/dashboard/states/active/pending-plan-change.tsx @@ -1,7 +1,9 @@ import { Trans } from 'react-i18next' import { PaidSubscription } from '../../../../../../../../types/subscription/dashboard/subscription' -import { PendingPaymentProviderPlan } from '../../../../../../../../types/subscription/plan' -import { AI_ADD_ON_CODE, ADD_ON_NAME } from '../../../../data/add-on-codes' +import { + hasPendingAiAddonCancellation, + ADD_ON_NAME, +} from '../../../../data/add-on-codes' export function PendingPlanChange({ subscription, @@ -10,15 +12,7 @@ export function PendingPlanChange({ }) { if (!subscription.pendingPlan) return null - const pendingPlan = subscription.pendingPlan as PendingPaymentProviderPlan - - const hasAiAddon = subscription.addOns?.some( - addOn => addOn.addOnCode === AI_ADD_ON_CODE - ) - - const pendingAiAddonCancellation = - hasAiAddon && - !pendingPlan.addOns?.some(addOn => addOn.code === AI_ADD_ON_CODE) + const pendingAiAddonCancellation = hasPendingAiAddonCancellation(subscription) const pendingAdditionalLicenses = (subscription.payment.pendingAdditionalLicenses && diff --git a/services/web/frontend/js/features/subscription/components/dashboard/states/canceled.tsx b/services/web/frontend/js/features/subscription/components/dashboard/states/canceled.tsx index 569aaeba04..12838970d2 100644 --- a/services/web/frontend/js/features/subscription/components/dashboard/states/canceled.tsx +++ b/services/web/frontend/js/features/subscription/components/dashboard/states/canceled.tsx @@ -1,5 +1,9 @@ import { useTranslation, Trans } from 'react-i18next' import { PaidSubscription } from '../../../../../../../types/subscription/dashboard/subscription' +import { + hasPendingAiAddonCancellation, + ADD_ON_NAME, +} from '../../../data/add-on-codes' import ReactivateSubscription from '../reactivate-subscription' import OLButton from '@/features/ui/components/ol/ol-button' @@ -9,6 +13,7 @@ export function CanceledSubscription({ subscription: PaidSubscription }) { const { t } = useTranslation() + const pendingAiAddonCancellation = hasPendingAiAddonCancellation(subscription) return ( <> @@ -26,6 +31,19 @@ export function CanceledSubscription({ ]} />

+ {pendingAiAddonCancellation && ( +

+ }} + /> +

+ )}

addOn.addOnCode === AI_ADD_ON_CODE + ) + + // cancellation of entire plan counts as removing the add-on + if(hasAiAddon && !pendingPlan){ + return true + } + + return hasAiAddon && + !pendingPlan.addOns?.some(addOn => addOn.code === AI_ADD_ON_CODE) + +} \ No newline at end of file From c060358cd8b4390dc8152ee21bc72a45295fe6d2 Mon Sep 17 00:00:00 2001 From: Jimmy Domagala-Tang Date: Tue, 6 May 2025 11:24:01 -0400 Subject: [PATCH 022/194] Merge pull request #25223 from overleaf/jdt-dk-commons-toggle-annual-discount-bundle Allow for commons to toggle annual for the AI Assist bundle GitOrigin-RevId: 719dbb4944e3a447e03aa5c3fee7d0f5a0ce005b --- services/web/frontend/extracted-translations.json | 3 +-- services/web/locales/en.json | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json index 7df8553f55..7db0fc6d4f 100644 --- a/services/web/frontend/extracted-translations.json +++ b/services/web/frontend/extracted-translations.json @@ -82,12 +82,11 @@ "add_on": "", "add_ons": "", "add_or_remove_project_from_tag": "", - "add_overleaf_assist_to_your_group_subscription": "", - "add_overleaf_assist_to_your_institution": "", "add_people": "", "add_role_and_department": "", "add_to_dictionary": "", "add_to_tag": "", + "add_unlimited_ai_to_overleaf": "", "add_unlimited_ai_to_your_overleaf_plan": "", "add_your_comment_here": "", "add_your_first_group_member_now": "", diff --git a/services/web/locales/en.json b/services/web/locales/en.json index 389e3078b5..1a610957e6 100644 --- a/services/web/locales/en.json +++ b/services/web/locales/en.json @@ -97,12 +97,11 @@ "add_on": "Add-on", "add_ons": "Add-ons", "add_or_remove_project_from_tag": "Add or remove project from tag __tagName__", - "add_overleaf_assist_to_your_group_subscription": "Add Overleaf Assist to your group subscription", - "add_overleaf_assist_to_your_institution": "Add Overleaf Assist to your institution", "add_people": "Add people", "add_role_and_department": "Add role and department", "add_to_dictionary": "Add to Dictionary", "add_to_tag": "Add to tag", + "add_unlimited_ai_to_overleaf": "Add unlimited AI* to Overleaf", "add_unlimited_ai_to_your_overleaf_plan": "Add unlimited AI* to your Overleaf __planName__ plan", "add_your_comment_here": "Add your comment here", "add_your_first_group_member_now": "Add your first group members now", From f72a34f25b86cca345afbe867b4ee431f1bf8257 Mon Sep 17 00:00:00 2001 From: Tim Down <158919+timdown@users.noreply.github.com> Date: Tue, 6 May 2025 16:45:56 +0100 Subject: [PATCH 023/194] Merge pull request #25348 from overleaf/td-react-18-flaky-tests Attempt to fix two flaky frontend project dashboard tests GitOrigin-RevId: 1d5c3a05f7439ad3e22e5de96da8628ad8dd27c5 --- .../table/project-list-table.test.tsx | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/services/web/test/frontend/features/project-list/components/table/project-list-table.test.tsx b/services/web/test/frontend/features/project-list/components/table/project-list-table.test.tsx index f1d4a0755d..f8f9143c16 100644 --- a/services/web/test/frontend/features/project-list/components/table/project-list-table.test.tsx +++ b/services/web/test/frontend/features/project-list/components/table/project-list-table.test.tsx @@ -154,17 +154,24 @@ describe('', function () { }) }) - it('unselects all projects when select all checkbox uchecked', async function () { + it('unselects all projects when select all checkbox unchecked', async function () { renderWithProjectListContext() await fetchMock.callHistory.flush(true) const checkbox = await screen.findByLabelText('Select all projects') - fireEvent.click(checkbox) + fireEvent.click(checkbox) await waitFor(() => { const allCheckboxes = screen.queryAllByRole('checkbox') const allCheckboxesChecked = allCheckboxes.filter(c => c.checked) - expect(allCheckboxesChecked.length).to.equal(0) + expect(allCheckboxesChecked).to.have.length(currentProjects.length + 1) + }) + + fireEvent.click(checkbox) + + await waitFor(() => { + const allCheckboxes = screen.queryAllByRole('checkbox') + expect(allCheckboxes.every(c => !c.checked)).to.be.true }) }) @@ -174,12 +181,14 @@ describe('', function () { const checkbox = await screen.findByLabelText('Select all projects') fireEvent.click(checkbox) + // make sure we are unchecking a project checkbox and that it is already + // checked await waitFor(() => { expect( screen - .getAllByRole('checkbox')[1] + .getAllByRole('checkbox', { checked: true })[1] .getAttribute('data-project-id') - ).to.exist // make sure we are unchecking a project checkbox + ).to.exist }) fireEvent.click(screen.getAllByRole('checkbox')[1]) From 12939b91b3c29aa0dff625ec97cd2ff5b99356f1 Mon Sep 17 00:00:00 2001 From: Jessica Lawshe <5312836+lawshe@users.noreply.github.com> Date: Tue, 6 May 2025 10:53:26 -0500 Subject: [PATCH 024/194] Merge pull request #25351 from overleaf/revert-24919-jel-create-group-audit-log Revert "[web] Add group audit log" GitOrigin-RevId: cf192bbe3ebdb693f18bab9c1c5d08da18ed34c0 --- .../web/app/src/infrastructure/mongodb.js | 1 - .../web/app/src/models/GroupAuditLogEntry.js | 23 ------------ services/web/frontend/js/utils/meta.ts | 1 - .../20250409155536_group_audit_log_index.mjs | 35 ------------------- .../test/acceptance/src/helpers/groupSSO.mjs | 18 +++++----- 5 files changed, 8 insertions(+), 70 deletions(-) delete mode 100644 services/web/app/src/models/GroupAuditLogEntry.js delete mode 100644 services/web/migrations/20250409155536_group_audit_log_index.mjs diff --git a/services/web/app/src/infrastructure/mongodb.js b/services/web/app/src/infrastructure/mongodb.js index 7fc1039140..aa7aa4ac44 100644 --- a/services/web/app/src/infrastructure/mongodb.js +++ b/services/web/app/src/infrastructure/mongodb.js @@ -49,7 +49,6 @@ const db = { githubSyncUserCredentials: internalDb.collection('githubSyncUserCredentials'), globalMetrics: internalDb.collection('globalMetrics'), grouppolicies: internalDb.collection('grouppolicies'), - groupAuditLogEntries: internalDb.collection('groupAuditLogEntries'), institutions: internalDb.collection('institutions'), messages: internalDb.collection('messages'), migrations: internalDb.collection('migrations'), diff --git a/services/web/app/src/models/GroupAuditLogEntry.js b/services/web/app/src/models/GroupAuditLogEntry.js deleted file mode 100644 index 3bda4ebf95..0000000000 --- a/services/web/app/src/models/GroupAuditLogEntry.js +++ /dev/null @@ -1,23 +0,0 @@ -const mongoose = require('../infrastructure/Mongoose') -const { Schema } = mongoose - -const GroupAuditLogEntrySchema = new Schema( - { - groupId: { type: Schema.Types.ObjectId, index: true }, - info: { type: Object }, - initiatorId: { type: Schema.Types.ObjectId }, - ipAddress: { type: String }, - operation: { type: String }, - timestamp: { type: Date, default: Date.now }, - }, - { - collection: 'groupAuditLogEntries', - minimize: false, - } -) - -exports.GroupAuditLogEntry = mongoose.model( - 'GroupAuditLogEntry', - GroupAuditLogEntrySchema -) -exports.GroupAuditLogEntrySchema = GroupAuditLogEntrySchema diff --git a/services/web/frontend/js/utils/meta.ts b/services/web/frontend/js/utils/meta.ts index 6c7209a5bb..7aab88b050 100644 --- a/services/web/frontend/js/utils/meta.ts +++ b/services/web/frontend/js/utils/meta.ts @@ -103,7 +103,6 @@ export interface Meta { 'ol-gitBridgeEnabled': boolean 'ol-gitBridgePublicBaseUrl': string 'ol-github': { enabled: boolean; error: boolean } - 'ol-groupAuditLogs': [] 'ol-groupId': string 'ol-groupName': string 'ol-groupPlans': GroupPlans diff --git a/services/web/migrations/20250409155536_group_audit_log_index.mjs b/services/web/migrations/20250409155536_group_audit_log_index.mjs deleted file mode 100644 index 282b3c6d2d..0000000000 --- a/services/web/migrations/20250409155536_group_audit_log_index.mjs +++ /dev/null @@ -1,35 +0,0 @@ -/* eslint-disable no-unused-vars */ - -import Helpers from './lib/helpers.mjs' - -const tags = ['saas'] - -const indexes = [ - { - key: { - groupId: 1, - timestamp: 1, - }, - name: 'groupId_1_timestamp_1', - }, -] - -const migrate = async client => { - const { db } = client - await Helpers.addIndexesToCollection(db.groupAuditLogEntries, indexes) -} - -const rollback = async client => { - const { db } = client - try { - await Helpers.dropIndexesFromCollection(db.groupAuditLogEntries, indexes) - } catch (err) { - console.error('Something went wrong rolling back the migrations', err) - } -} - -export default { - tags, - migrate, - rollback, -} diff --git a/services/web/test/acceptance/src/helpers/groupSSO.mjs b/services/web/test/acceptance/src/helpers/groupSSO.mjs index c5bde77236..f7efeb9e63 100644 --- a/services/web/test/acceptance/src/helpers/groupSSO.mjs +++ b/services/web/test/acceptance/src/helpers/groupSSO.mjs @@ -34,7 +34,7 @@ export const baseSsoConfig = { userIdAttribute, } // the database also sets enabled and validated, but we cannot set that in the POST request for /manage/groups/:ID/settings/sso -export async function createGroupSSO(SSOConfigValidated = true) { +export async function createGroupSSO() { const nonSSOMemberHelper = await UserHelper.createUser() const nonSSOMember = nonSSOMemberHelper.user @@ -47,7 +47,7 @@ export async function createGroupSSO(SSOConfigValidated = true) { const ssoConfig = new SSOConfig({ ...baseSsoConfig, enabled: true, - validated: SSOConfigValidated, + validated: true, }) await ssoConfig.save() @@ -68,14 +68,12 @@ export async function createGroupSSO(SSOConfigValidated = true) { const enrollmentUrl = getEnrollmentUrl(subscriptionId) const internalProviderId = getProviderId(subscriptionId) - if (SSOConfigValidated) { - await linkGroupMember( - memberUser.email, - memberUser.password, - subscriptionId, - 'mock@email.com' - ) - } + await linkGroupMember( + memberUser.email, + memberUser.password, + subscriptionId, + 'mock@email.com' + ) const userHelper = new UserHelper() From 6c3cc794a4bfa1e24b8861be8ba07232cd730b54 Mon Sep 17 00:00:00 2001 From: M Fahru Date: Tue, 6 May 2025 09:49:37 -0700 Subject: [PATCH 025/194] Merge pull request #25161 from overleaf/mf-stripe-webhook [web] Implement stripe webhook for `customer.subscription.created` event type GitOrigin-RevId: f32e7607ddf900211efbe487bcd1f09172100178 --- services/web/types/stripe/webhook-event.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 services/web/types/stripe/webhook-event.ts diff --git a/services/web/types/stripe/webhook-event.ts b/services/web/types/stripe/webhook-event.ts new file mode 100644 index 0000000000..6be2ea1e95 --- /dev/null +++ b/services/web/types/stripe/webhook-event.ts @@ -0,0 +1,13 @@ +type CustomerSubscriptionCreated = { + type: 'customer.subscription.created' + data: { + object: { + id: string + metadata: { + adminUserId?: string + } + } + } +} + +export type WebhookEvent = CustomerSubscriptionCreated From 661aa20c095c0bdcada9e409d4c26365b24488f9 Mon Sep 17 00:00:00 2001 From: M Fahru Date: Tue, 6 May 2025 09:49:51 -0700 Subject: [PATCH 026/194] Merge pull request #25288 from overleaf/mf-stripe-webhook-subscription-updated [web] Handle `customer.subscription.updated` stripe webhook event type GitOrigin-RevId: 821baee5d5a45b92ee7bce47598a5e3ea5aa95ea --- services/web/types/stripe/webhook-event.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/services/web/types/stripe/webhook-event.ts b/services/web/types/stripe/webhook-event.ts index 6be2ea1e95..648f26b8dc 100644 --- a/services/web/types/stripe/webhook-event.ts +++ b/services/web/types/stripe/webhook-event.ts @@ -1,4 +1,4 @@ -type CustomerSubscriptionCreated = { +export type CustomerSubscriptionWebhookEvent = { type: 'customer.subscription.created' data: { object: { @@ -9,5 +9,3 @@ type CustomerSubscriptionCreated = { } } } - -export type WebhookEvent = CustomerSubscriptionCreated From eddeca294294e8d6c8d76db3cd14c8899d4661a2 Mon Sep 17 00:00:00 2001 From: Antoine Clausse Date: Wed, 7 May 2025 09:29:50 +0200 Subject: [PATCH 027/194] [history-v1] Update `config` from `1.31.0` to `3.3.12` (#25077) This removes some DeprecationWarnings in history-v1 The update should be safe: ``` 3.0.0 / 2018-11-20 Ensure config array items and objects are sealed @fgheorghe This required a major version bump in case someone relied on the ability to mutate non-sealed data. 2.0.0 / 2018-07-26 Potential for backward incompatibility requiring a major version bump. Safe to upgrade to major version 2 if you're using a recent NodeJS version and you're not trying to mutate config arrays. Added array immutability - jacobemerick Removed Node V.4 support ``` https://github.com/node-config/node-config/blob/master/History.md GitOrigin-RevId: 8384247d1ad2cd659703b4ba50edf7212076dcf3 --- package-lock.json | 37 +++++++++++--------------------- services/history-v1/package.json | 2 +- 2 files changed, 14 insertions(+), 25 deletions(-) diff --git a/package-lock.json b/package-lock.json index fe1225f129..8d0b9cbead 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17961,17 +17961,6 @@ "dev": true, "license": "MIT" }, - "node_modules/config": { - "version": "1.31.0", - "resolved": "https://registry.npmjs.org/config/-/config-1.31.0.tgz", - "integrity": "sha512-Ep/l9Rd1J9IPueztJfpbOqVzuKHQh4ZODMNt9xqTYdBBNRXbV4oTu34kCkkfdRVcDq0ohtpaeXGgb+c0LQxFRA==", - "dependencies": { - "json5": "^1.0.1" - }, - "engines": { - "node": ">= 4.0.0" - } - }, "node_modules/config-chain": { "version": "1.1.13", "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", @@ -17982,17 +17971,6 @@ "proto-list": "~1.2.1" } }, - "node_modules/config/node_modules/json5": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", - "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, "node_modules/connect-flash": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/connect-flash/-/connect-flash-0.1.1.tgz", @@ -27453,7 +27431,6 @@ "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, "bin": { "json5": "lib/cli.js" }, @@ -42802,7 +42779,7 @@ "bunyan": "^1.8.12", "check-types": "^11.1.2", "command-line-args": "^3.0.3", - "config": "^1.19.0", + "config": "^3.3.12", "express": "^4.21.2", "fs-extra": "^9.0.1", "generic-pool": "^2.1.1", @@ -42896,6 +42873,18 @@ "command-line-args": "bin.js" } }, + "services/history-v1/node_modules/config": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/config/-/config-3.3.12.tgz", + "integrity": "sha512-Vmx389R/QVM3foxqBzXO8t2tUikYZP64Q6vQxGrsMpREeJc/aWRnPRERXWsYzOHAumx/AOoILWe6nU3ZJL+6Sw==", + "license": "MIT", + "dependencies": { + "json5": "^2.2.3" + }, + "engines": { + "node": ">= 10.0.0" + } + }, "services/history-v1/node_modules/cron-parser": { "version": "4.9.0", "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-4.9.0.tgz", diff --git a/services/history-v1/package.json b/services/history-v1/package.json index 3219be9af4..1fdfd95c45 100644 --- a/services/history-v1/package.json +++ b/services/history-v1/package.json @@ -24,7 +24,7 @@ "bunyan": "^1.8.12", "check-types": "^11.1.2", "command-line-args": "^3.0.3", - "config": "^1.19.0", + "config": "^3.3.12", "express": "^4.21.2", "fs-extra": "^9.0.1", "generic-pool": "^2.1.1", From e7329b9660044253d6bd98f8079333b6c16ebe4a Mon Sep 17 00:00:00 2001 From: Antoine Clausse Date: Wed, 7 May 2025 09:30:27 +0200 Subject: [PATCH 028/194] [web] Remove script remove_emails_with_commas.mjs (#25356) It ran in prod and updated 112 users GitOrigin-RevId: 730f6544e7a5bb4d08095b48fb697b5c8e7a08be --- .../web/scripts/remove_emails_with_commas.mjs | 124 ---------- .../src/RemoveEmailsWithCommasScriptTest.mjs | 226 ------------------ 2 files changed, 350 deletions(-) delete mode 100644 services/web/scripts/remove_emails_with_commas.mjs delete mode 100644 services/web/test/acceptance/src/RemoveEmailsWithCommasScriptTest.mjs diff --git a/services/web/scripts/remove_emails_with_commas.mjs b/services/web/scripts/remove_emails_with_commas.mjs deleted file mode 100644 index f6f107edac..0000000000 --- a/services/web/scripts/remove_emails_with_commas.mjs +++ /dev/null @@ -1,124 +0,0 @@ -// @ts-check - -import minimist from 'minimist' -import fs from 'node:fs/promises' -import * as csv from 'csv' -import { promisify } from 'node:util' -import UserAuditLogHandler from '../app/src/Features/User/UserAuditLogHandler.js' -import { db } from '../app/src/infrastructure/mongodb.js' - -const CSV_FILENAME = '/tmp/emails-with-commas.csv' - -/** - * @type {(csvString: string) => Promise} - */ -const parseAsync = promisify(csv.parse) - -function usage() { - console.log('Usage: node remove_emails_with_commas.mjs') - console.log(`Read emails from ${CSV_FILENAME} and remove them from users.`) - console.log('Add support+@overleaf.com instead.') - console.log('Options:') - console.log(' --commit apply the changes\n') - process.exit(0) -} - -const { commit, help } = minimist(process.argv.slice(2), { - boolean: ['commit', 'help'], - alias: { help: 'h' }, - default: { commit: false }, -}) - -async function consumeCsvFileAndUpdate() { - console.time('remove_emails_with_commas') - - const csvContent = await fs.readFile(CSV_FILENAME, 'utf8') - const rows = await parseAsync(csvContent) - const emailsWithComma = rows.map(row => row[0]) - - console.log('Total emails in the CSV:', emailsWithComma.length) - - const unexpectedValidEmails = emailsWithComma.filter( - str => !str.includes(',') - ) - if (unexpectedValidEmails.length > 0) { - throw new Error( - 'CSV file contains unexpected valid emails: ' + - JSON.stringify(emailsWithComma) - ) - } - - let updatedUsersCount = 0 - for (const oldEmail of emailsWithComma) { - const encodedEmail = oldEmail - .replaceAll('_', '_5f') - .replaceAll('@', '_40') - .replaceAll(',', '_2c') - .replaceAll('<', '_60') - .replaceAll('>', '_62') - - const newEmail = `support+${encodedEmail}@overleaf.com` - - console.log(oldEmail, '->', newEmail) - - const user = await db.users.findOne({ email: oldEmail }) - - if (!user) { - console.log('User not found for email:', oldEmail) - continue - } - - if (commit) { - await db.users.updateOne( - { _id: user._id }, - { - $set: { email: newEmail }, - $pull: { emails: { email: oldEmail } }, - } - ) - await db.users.updateOne( - { _id: user._id }, - { - $addToSet: { - emails: { - email: newEmail, - createdAt: new Date(), - reversedHostname: 'moc.faelrevo', - }, - }, - } - ) - - await UserAuditLogHandler.promises.addEntry( - user._id, - 'remove-email', - undefined, - undefined, - { - removedEmail: oldEmail, - script: true, - note: 'remove primary email containing commas', - } - ) - updatedUsersCount++ - } - } - - console.log('Updated users:', updatedUsersCount) - - if (!commit) { - console.log('Note: this was a dry-run. No changes were made.') - } - console.log() - console.timeEnd('remove_emails_with_commas') - console.log() -} - -try { - if (help) usage() - else await consumeCsvFileAndUpdate() - process.exit(0) -} catch (error) { - console.error(error) - process.exit(1) -} diff --git a/services/web/test/acceptance/src/RemoveEmailsWithCommasScriptTest.mjs b/services/web/test/acceptance/src/RemoveEmailsWithCommasScriptTest.mjs deleted file mode 100644 index f50f8f19df..0000000000 --- a/services/web/test/acceptance/src/RemoveEmailsWithCommasScriptTest.mjs +++ /dev/null @@ -1,226 +0,0 @@ -import { promisify } from 'node:util' -import { exec } from 'node:child_process' -import { expect } from 'chai' -import { filterOutput } from './helpers/settings.mjs' -import { db, ObjectId } from '../../../app/src/infrastructure/mongodb.js' -import fs from 'node:fs/promises' - -const CSV_FILENAME = '/tmp/emails-with-commas.csv' - -async function runScript(commit) { - const result = await promisify(exec)( - ['node', 'scripts/remove_emails_with_commas.mjs', commit && '--commit'] - .filter(Boolean) - .join(' ') - ) - return { - ...result, - stdout: result.stdout.split('\n').filter(filterOutput), - } -} - -function createUser(email, emails) { - return { - _id: new ObjectId(), - email, - emails, - } -} - -describe('scripts/remove_emails_with_commas', function () { - let user, unchangedUser - - beforeEach(async function () { - await fs.writeFile( - CSV_FILENAME, - '"user,email@test.com"\n"user,another@test.com"\n' - ) - }) - - afterEach(async function () { - try { - await fs.unlink(CSV_FILENAME) - } catch (err) { - // Ignore errors if file doesn't exist - } - }) - - describe('when removing email addresses with commas', function () { - beforeEach(async function () { - user = createUser('user,email@test.com', [ - { - email: 'user,email@test.com', - createdAt: new Date(), - reversedHostname: 'moc.tset', - }, - ]) - await db.users.insertOne(user) - - unchangedUser = createUser('john.doe@example.com', [ - { - email: 'john.doe@example.com', - createdAt: new Date(), - reversedHostname: 'moc.elpmaxe', - }, - ]) - await db.users.insertOne(unchangedUser) - }) - - afterEach(async function () { - await db.users.deleteOne({ _id: user._id }) - }) - - it('should replace emails with commas with encoded support emails', async function () { - const r = await runScript(true) - - expect(r.stdout).to.include( - 'user,email@test.com -> support+user_2cemail_40test.com@overleaf.com' - ) - expect(r.stdout).to.include('Updated users: 1') - - const updatedUser = await db.users.findOne({ _id: user._id }) - expect(updatedUser.email).to.equal( - 'support+user_2cemail_40test.com@overleaf.com' - ) - expect(updatedUser.emails).to.have.length(1) - expect(updatedUser.emails[0].email).to.equal( - 'support+user_2cemail_40test.com@overleaf.com' - ) - expect(updatedUser.emails[0].reversedHostname).to.equal('moc.faelrevo') - - const unchanged = await db.users.findOne({ _id: unchangedUser._id }) - - expect(unchanged.emails).to.have.length(1) - expect(unchanged.email).to.equal('john.doe@example.com') - expect(unchanged.emails[0].email).to.equal('john.doe@example.com') - }) - - it('should not modify anything in dry run mode', async function () { - const r = await runScript(false) - - expect(r.stdout).to.include( - 'user,email@test.com -> support+user_2cemail_40test.com@overleaf.com' - ) - expect(r.stdout).to.include( - 'Note: this was a dry-run. No changes were made.' - ) - - const updatedUser = await db.users.findOne({ _id: user._id }) - expect(updatedUser.email).to.equal('user,email@test.com') - expect(updatedUser.emails).to.have.length(1) - expect(updatedUser.emails[0].email).to.equal('user,email@test.com') - }) - }) - - describe('when handling multiple email replacements', function () { - beforeEach(async function () { - user = createUser('user,email@test.com', [ - { - email: 'user,email@test.com', - createdAt: new Date(), - reversedHostname: 'moc.tset', - }, - { - email: 'normal@test.com', - createdAt: new Date(), - reversedHostname: 'moc.tset', - }, - ]) - await db.users.insertOne(user) - }) - - afterEach(async function () { - await db.users.deleteOne({ _id: user._id }) - }) - - it('should only replace primary email with comma and keep other emails', async function () { - const r = await runScript(true) - - expect(r.stdout).to.include( - 'user,email@test.com -> support+user_2cemail_40test.com@overleaf.com' - ) - expect(r.stdout).to.include('Updated users: 1') - - const updatedUser = await db.users.findOne({ _id: user._id }) - expect(updatedUser.email).to.equal( - 'support+user_2cemail_40test.com@overleaf.com' - ) - expect(updatedUser.emails).to.have.length(2) - expect(updatedUser.emails[0].email).to.equal('normal@test.com') - expect(updatedUser.emails[1].email).to.equal( - 'support+user_2cemail_40test.com@overleaf.com' - ) - }) - }) - - describe('when handling special characters in emails', function () { - beforeEach(async function () { - await fs.writeFile( - CSV_FILENAME, - '"user,email@test.com"\n","\n"user_special@test.co,"\n' - ) - - user = createUser('user,email@test.com', [ - { - email: 'user,email@test.com', - createdAt: new Date(), - reversedHostname: 'moc.tset', - }, - ]) - - await db.users.insertOne(user) - - const user2 = createUser('user<>@test.com', [ - { - email: 'user<>@test.com', - createdAt: new Date(), - reversedHostname: 'moc.tset', - }, - ]) - - await db.users.insertOne(user2) - }) - - afterEach(async function () { - await db.users.deleteMany({ - email: { - $in: [ - 'support+user_2cemail_40test.com@overleaf.com', - 'support+user_60_62_40test.com@overleaf.com', - ], - }, - }) - }) - - it('should correctly encode various special characters', async function () { - const r = await runScript(true) - - expect(r.stdout).to.include( - 'user,email@test.com -> support+user_2cemail_40test.com@overleaf.com' - ) - expect(r.stdout).to.include( - ', -> support+_2c_60user_40test.com_62@overleaf.com' - ) - - const updatedUser1 = await db.users.findOne({ _id: user._id }) - expect(updatedUser1.email).to.equal( - 'support+user_2cemail_40test.com@overleaf.com' - ) - }) - }) - - describe('when user does not exist', function () { - beforeEach(async function () { - await fs.writeFile(CSV_FILENAME, '"nonexistent,email@test.com"\n') - }) - - it('should handle missing users gracefully', async function () { - const r = await runScript(true) - - expect(r.stdout).to.include( - 'User not found for email: nonexistent,email@test.com' - ) - expect(r.stdout).to.include('Updated users: 0') - }) - }) -}) From 07b37abcb323345b6c482527268364b8fe26d064 Mon Sep 17 00:00:00 2001 From: Antoine Clausse Date: Wed, 7 May 2025 09:30:47 +0200 Subject: [PATCH 029/194] [web] Improve FileTooLargeError handling in FileWriter.js (#25278) * Improve FileTooLargeError handling in FileWriter.js * handle errors on passThrough stream * unlink files on error * fail `writeUrlToDisk` if content-length header is too large With Node 22, the test `Open In Overleaf - when POSTing a snip_uri for a file that is too large` fails. I initially tried replacing it with a check of the `content-length` header. But then I managed to make the old test pass by adding a handler (`passThrough.on('error', ...)`) * Unlink files asynchronously, add stream destroys on error * Remove eslint disables * Remove `stream.on('error', ...)` and `passThrough.on('error', ...)` * Revert `Content-Length` check * Re-add `stream.on('error', errorHandler)`; Remove it on 'response' * Only report unlink errors there is an error(!!) that's not ENOENT GitOrigin-RevId: fefe49519ec6f54df5eef69a2c2a75518f9d3748 --- .../web/app/src/infrastructure/FileWriter.js | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/services/web/app/src/infrastructure/FileWriter.js b/services/web/app/src/infrastructure/FileWriter.js index 2c98028f37..1a56f5fa26 100644 --- a/services/web/app/src/infrastructure/FileWriter.js +++ b/services/web/app/src/infrastructure/FileWriter.js @@ -1,9 +1,4 @@ -/* eslint-disable - n/handle-callback-err, - max-len, -*/ // TODO: This file was created by bulk-decaffeinate. -// Fix any style issues and re-enable lint. /* * decaffeinate suggestions: * DS102: Remove unnecessary code created because of implicit returns @@ -122,6 +117,16 @@ const FileWriter = { }).withCause(err || {}) } if (err) { + stream.destroy() + writeStream.destroy() + fs.unlink(fsPath, error => { + if (error && error.code !== 'ENOENT') { + logger.warn( + { error, fsPath }, + 'Failed to delete partial file after error' + ) + } + }) OError.tag( err, '[writeStreamToDisk] something went wrong writing the stream to disk', @@ -153,14 +158,16 @@ const FileWriter = { callback = _.once(callback) const stream = request.get(url) - stream.on('error', function (err) { + const errorHandler = function (err) { logger.warn( { err, identifier, url }, '[writeUrlToDisk] something went wrong with writing to disk' ) callback(err) - }) + } + stream.on('error', errorHandler) stream.on('response', function (response) { + stream.removeListener('error', errorHandler) if (response.statusCode >= 200 && response.statusCode < 300) { FileWriter.writeStreamToDisk(identifier, stream, options, callback) } else { From 5cc0895c563b7aef6fe93684055baa31b1c17d17 Mon Sep 17 00:00:00 2001 From: Jakob Ackermann Date: Wed, 7 May 2025 09:52:35 +0200 Subject: [PATCH 030/194] [clsi] enable keepAlive on global HTTP agents (#25350) GitOrigin-RevId: c9478b405ac32ca55aeb3bcf9f24052477464667 --- services/clsi/config/settings.defaults.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/services/clsi/config/settings.defaults.js b/services/clsi/config/settings.defaults.js index 614644ac7b..5edaec4a8a 100644 --- a/services/clsi/config/settings.defaults.js +++ b/services/clsi/config/settings.defaults.js @@ -1,10 +1,6 @@ const Path = require('node:path') -const http = require('node:http') -const https = require('node:https') const os = require('node:os') -http.globalAgent.keepAlive = false -https.globalAgent.keepAlive = false const isPreEmptible = process.env.PREEMPTIBLE === 'TRUE' const CLSI_SERVER_ID = os.hostname().replace('-ctr', '') From f9b36cd5be96a91db7b5659f30dabd0203ce78d7 Mon Sep 17 00:00:00 2001 From: Brian Gough Date: Wed, 7 May 2025 09:27:15 +0100 Subject: [PATCH 031/194] Merge pull request #25241 from overleaf/bg-remove-existing-chunk-buffer remove existing chunk redis backend and chunk buffer GitOrigin-RevId: 28fb02d1802312de6892e2fb7dd59191e3fc8914 --- services/history-v1/storage/index.js | 1 - .../storage/lib/chunk_buffer/index.js | 39 - .../storage/lib/chunk_store/errors.js | 6 + .../storage/lib/chunk_store/redis.js | 1124 ++++++----- .../storage/scripts/expire_redis_chunks.js | 58 +- .../acceptance/js/api/project_updates.test.js | 5 - .../js/storage/chunk_buffer.test.js | 351 ---- .../storage/chunk_store_redis_backend.test.js | 1704 +++++++++-------- .../js/storage/expire_redis_chunks.test.js | 209 ++ 9 files changed, 1845 insertions(+), 1652 deletions(-) delete mode 100644 services/history-v1/storage/lib/chunk_buffer/index.js delete mode 100644 services/history-v1/test/acceptance/js/storage/chunk_buffer.test.js create mode 100644 services/history-v1/test/acceptance/js/storage/expire_redis_chunks.test.js diff --git a/services/history-v1/storage/index.js b/services/history-v1/storage/index.js index 5fe283a34c..2aa492f46e 100644 --- a/services/history-v1/storage/index.js +++ b/services/history-v1/storage/index.js @@ -1,7 +1,6 @@ exports.BatchBlobStore = require('./lib/batch_blob_store') exports.blobHash = require('./lib/blob_hash') exports.HashCheckBlobStore = require('./lib/hash_check_blob_store') -exports.chunkBuffer = require('./lib/chunk_buffer') exports.chunkStore = require('./lib/chunk_store') exports.historyStore = require('./lib/history_store').historyStore exports.knex = require('./lib/knex') diff --git a/services/history-v1/storage/lib/chunk_buffer/index.js b/services/history-v1/storage/lib/chunk_buffer/index.js deleted file mode 100644 index 5ef533ddba..0000000000 --- a/services/history-v1/storage/lib/chunk_buffer/index.js +++ /dev/null @@ -1,39 +0,0 @@ -'use strict' - -/** - * @module storage/lib/chunk_buffer - */ - -const chunkStore = require('../chunk_store') -const redisBackend = require('../chunk_store/redis') -const metrics = require('@overleaf/metrics') -/** - * Load the latest Chunk stored for a project, including blob metadata. - * - * @param {string} projectId - * @return {Promise.} - */ -async function loadLatest(projectId) { - const chunkRecord = await chunkStore.loadLatestRaw(projectId) - const cachedChunk = await redisBackend.getCurrentChunkIfValid( - projectId, - chunkRecord - ) - if (cachedChunk) { - metrics.inc('chunk_buffer.loadLatest', 1, { - status: 'cache-hit', - }) - return cachedChunk - } else { - metrics.inc('chunk_buffer.loadLatest', 1, { - status: 'cache-miss', - }) - const chunk = await chunkStore.loadLatest(projectId) - await redisBackend.setCurrentChunk(projectId, chunk) - return chunk - } -} - -module.exports = { - loadLatest, -} diff --git a/services/history-v1/storage/lib/chunk_store/errors.js b/services/history-v1/storage/lib/chunk_store/errors.js index 5f0eba6aac..fc37dbe2a1 100644 --- a/services/history-v1/storage/lib/chunk_store/errors.js +++ b/services/history-v1/storage/lib/chunk_store/errors.js @@ -1,7 +1,13 @@ const OError = require('@overleaf/o-error') class ChunkVersionConflictError extends OError {} +class BaseVersionConflictError extends OError {} +class JobNotFoundError extends OError {} +class JobNotReadyError extends OError {} module.exports = { ChunkVersionConflictError, + BaseVersionConflictError, + JobNotFoundError, + JobNotReadyError, } diff --git a/services/history-v1/storage/lib/chunk_store/redis.js b/services/history-v1/storage/lib/chunk_store/redis.js index d9c423861d..5c62db5387 100644 --- a/services/history-v1/storage/lib/chunk_store/redis.js +++ b/services/history-v1/storage/lib/chunk_store/redis.js @@ -1,20 +1,28 @@ const metrics = require('@overleaf/metrics') -const logger = require('@overleaf/logger') +const OError = require('@overleaf/o-error') const redis = require('../redis') const rclient = redis.rclientHistory // -const { Snapshot, Change, History, Chunk } = require('overleaf-editor-core') +const { Change, Snapshot } = require('overleaf-editor-core') +const { + BaseVersionConflictError, + JobNotFoundError, + JobNotReadyError, +} = require('./errors') -const TEMPORARY_CACHE_LIFETIME = 300 // 5 minutes +const MAX_PERSISTED_CHANGES = 100 // Maximum number of persisted changes to keep in the buffer for clients that need to catch up. +const PROJECT_TTL_MS = 3600 * 1000 // Amount of time a project can stay inactive before it gets expired +const MAX_PERSIST_DELAY_MS = 300 * 1000 // Maximum amount of time before a change is persisted +const RETRY_DELAY_MS = 120 * 1000 // Time before a claimed job is considered stale and a worker can retry it. const keySchema = { - snapshot({ projectId }) { - return `snapshot:{${projectId}}` + head({ projectId }) { + return `head:{${projectId}}` }, - startVersion({ projectId }) { - return `snapshot-version:{${projectId}}` + headVersion({ projectId }) { + return `head-version:{${projectId}}` }, - changes({ projectId }) { - return `changes:{${projectId}}` + persistedVersion({ projectId }) { + return `persisted-version:{${projectId}}` }, expireTime({ projectId }) { return `expire-time:{${projectId}}` @@ -22,457 +30,689 @@ const keySchema = { persistTime({ projectId }) { return `persist-time:{${projectId}}` }, + changes({ projectId }) { + return `changes:{${projectId}}` + }, } -rclient.defineCommand('get_current_chunk', { - numberOfKeys: 3, - lua: ` - local startVersionValue = redis.call('GET', KEYS[2]) - if not startVersionValue then - return nil -- this is a cache-miss - end - local snapshotValue = redis.call('GET', KEYS[1]) - local changesValues = redis.call('LRANGE', KEYS[3], 0, -1) - return {snapshotValue, startVersionValue, changesValues} - `, -}) - -/** - * Retrieves the current chunk of project history from Redis storage - * @param {string} projectId - The unique identifier of the project - * @returns {Promise} A Promise that resolves to a Chunk object containing project history, - * or null if retrieval fails - * @throws {Error} If Redis operations fail - */ -async function getCurrentChunk(projectId) { - try { - const result = await rclient.get_current_chunk( - keySchema.snapshot({ projectId }), - keySchema.startVersion({ projectId }), - keySchema.changes({ projectId }) - ) - if (!result) { - return null // cache-miss - } - const snapshot = Snapshot.fromRaw(JSON.parse(result[0])) - const startVersion = JSON.parse(result[1]) - const changes = result[2].map(c => Change.fromRaw(JSON.parse(c))) - const history = new History(snapshot, changes) - const chunk = new Chunk(history, startVersion) - metrics.inc('chunk_store.redis.get_current_chunk', 1, { status: 'success' }) - return chunk - } catch (err) { - logger.error({ err, projectId }, 'error getting current chunk from redis') - metrics.inc('chunk_store.redis.get_current_chunk', 1, { status: 'error' }) - return null - } -} - -rclient.defineCommand('get_current_chunk_if_valid', { - numberOfKeys: 3, - lua: ` - local expectedStartVersion = ARGV[1] - local expectedChangesCount = tonumber(ARGV[2]) - local startVersionValue = redis.call('GET', KEYS[2]) - if not startVersionValue then - return nil -- this is a cache-miss - end - if startVersionValue ~= expectedStartVersion then - return nil -- this is a cache-miss - end - local changesCount = redis.call('LLEN', KEYS[3]) - if changesCount ~= expectedChangesCount then - return nil -- this is a cache-miss - end - local snapshotValue = redis.call('GET', KEYS[1]) - local changesValues = redis.call('LRANGE', KEYS[3], 0, -1) - return {snapshotValue, startVersionValue, changesValues} - `, -}) - -async function getCurrentChunkIfValid(projectId, chunkRecord) { - try { - const changesCount = chunkRecord.endVersion - chunkRecord.startVersion - const result = await rclient.get_current_chunk_if_valid( - keySchema.snapshot({ projectId }), - keySchema.startVersion({ projectId }), - keySchema.changes({ projectId }), - chunkRecord.startVersion, - changesCount - ) - if (!result) { - return null // cache-miss - } - const snapshot = Snapshot.fromRaw(JSON.parse(result[0])) - const startVersion = parseInt(result[1], 10) - const changes = result[2].map(c => Change.fromRaw(JSON.parse(c))) - const history = new History(snapshot, changes) - const chunk = new Chunk(history, startVersion) - metrics.inc('chunk_store.redis.get_current_chunk_if_valid', 1, { - status: 'success', - }) - return chunk - } catch (err) { - logger.error( - { err, projectId, chunkRecord }, - 'error getting current chunk from redis' - ) - metrics.inc('chunk_store.redis.get_current_chunk_if_valid', 1, { - status: 'error', - }) - return null - } -} - -rclient.defineCommand('get_current_chunk_metadata', { +rclient.defineCommand('get_head_snapshot', { numberOfKeys: 2, lua: ` - local startVersionValue = redis.call('GET', KEYS[1]) - if not startVersionValue then - return nil -- this is a cache-miss + local headSnapshotKey = KEYS[1] + local headVersionKey = KEYS[2] + + -- Check if the head version exists. If not, consider it a cache miss. + local version = redis.call('GET', headVersionKey) + if not version then + return nil end - local changesCount = redis.call('LLEN', KEYS[2]) - return {startVersionValue, changesCount} + + -- Retrieve the snapshot value + local snapshot = redis.call('GET', headSnapshotKey) + return {snapshot, version} `, }) /** - * Retrieves the current chunk metadata for a given project from Redis - * @param {string} projectId - The ID of the project to get metadata for - * @returns {Promise} Object containing startVersion and changesCount if found, null on error or cache miss - * @property {number} startVersion - The starting version information - * @property {number} changesCount - The number of changes in the chunk + * Retrieves the head snapshot from Redis storage + * @param {string} projectId - The unique identifier of the project + * @returns {Promise<{version: number, snapshot: Snapshot}|null>} A Promise that resolves to an object containing the version and Snapshot, + * or null if retrieval fails or cache miss + * @throws {Error} If Redis operations fail */ -async function getCurrentChunkMetadata(projectId) { +async function getHeadSnapshot(projectId) { try { - const result = await rclient.get_current_chunk_metadata( - keySchema.startVersion({ projectId }), - keySchema.changes({ projectId }) + const result = await rclient.get_head_snapshot( + keySchema.head({ projectId }), + keySchema.headVersion({ projectId }) ) if (!result) { + metrics.inc('chunk_store.redis.get_head_snapshot', 1, { + status: 'cache-miss', + }) return null // cache-miss } - const startVersion = JSON.parse(result[0]) - const changesCount = parseInt(result[1], 10) - return { startVersion, changesCount } + const snapshot = Snapshot.fromRaw(JSON.parse(result[0])) + const version = parseInt(result[1], 10) + metrics.inc('chunk_store.redis.get_head_snapshot', 1, { + status: 'success', + }) + return { version, snapshot } } catch (err) { - return null - } -} - -rclient.defineCommand('set_current_chunk', { - numberOfKeys: 4, - lua: ` - local snapshotValue = ARGV[1] - local startVersionValue = ARGV[2] - local expireTime = ARGV[3] - redis.call('SET', KEYS[1], snapshotValue) - redis.call('SET', KEYS[2], startVersionValue) - redis.call('SET', KEYS[3], expireTime) - redis.call('DEL', KEYS[4]) -- clear the old changes list - if #ARGV >= 4 then - redis.call('RPUSH', KEYS[4], unpack(ARGV, 4)) - end - - `, -}) - -/** - * Stores the current chunk of project history in Redis - * @param {string} projectId - The ID of the project - * @param {Chunk} chunk - The chunk object containing history data - * @returns {Promise<*>} Returns the result of the Redis operation, or null if an error occurs - * @throws {Error} May throw Redis-related errors which are caught internally - */ -async function setCurrentChunk(projectId, chunk) { - try { - const snapshotKey = keySchema.snapshot({ projectId }) - const startVersionKey = keySchema.startVersion({ projectId }) - const changesKey = keySchema.changes({ projectId }) - const expireTimeKey = keySchema.expireTime({ projectId }) - - const snapshot = chunk.history.snapshot - const startVersion = chunk.startVersion - const changes = chunk.history.changes - const expireTime = Date.now() + TEMPORARY_CACHE_LIFETIME * 1000 - - await rclient.set_current_chunk( - snapshotKey, // KEYS[1] - startVersionKey, // KEYS[2] - expireTimeKey, // KEYS[3] - changesKey, // KEYS[4] - JSON.stringify(snapshot.toRaw()), // ARGV[1] - startVersion, // ARGV[2] - expireTime, // ARGV[3] - ...changes.map(c => JSON.stringify(c.toRaw())) // ARGV[4..] - ) - metrics.inc('chunk_store.redis.set_current_chunk', 1, { status: 'success' }) - } catch (err) { - logger.error( - { err, projectId, chunk }, - 'error setting current chunk in redis' - ) - metrics.inc('chunk_store.redis.set_current_chunk', 1, { status: 'error' }) - return null // while testing we will suppress any errors - } -} - -/** - * Checks whether a cached chunk's version metadata matches the current chunk's metadata - * @param {Chunk} cachedChunk - The chunk retrieved from cache - * @param {Chunk} currentChunk - The current chunk to compare against - * @returns {boolean} - Returns true if the chunks have matching start and end versions, false otherwise - */ -function checkCacheValidity(cachedChunk, currentChunk) { - return Boolean( - cachedChunk && - cachedChunk.getStartVersion() === currentChunk.getStartVersion() && - cachedChunk.getEndVersion() === currentChunk.getEndVersion() - ) -} - -/** - * Validates if a cached chunk matches the current chunk metadata by comparing versions - * @param {Object} cachedChunk - The cached chunk object to validate - * @param {Object} currentChunkMetadata - The current chunk metadata to compare against - * @param {number} currentChunkMetadata.startVersion - The starting version number - * @param {number} currentChunkMetadata.endVersion - The ending version number - * @returns {boolean} - True if the cached chunk is valid, false otherwise - */ -function checkCacheValidityWithMetadata(cachedChunk, currentChunkMetadata) { - return Boolean( - cachedChunk && - cachedChunk.getStartVersion() === currentChunkMetadata.startVersion && - cachedChunk.getEndVersion() === currentChunkMetadata.endVersion - ) -} - -/** - * Compares two chunks for equality using stringified JSON comparison - * @param {string} projectId - The ID of the project - * @param {Chunk} cachedChunk - The cached chunk to compare - * @param {Chunk} currentChunk - The current chunk to compare against - * @returns {boolean} - Returns false if either chunk is null/undefined, otherwise returns the comparison result - */ -function compareChunks(projectId, cachedChunk, currentChunk) { - if (!cachedChunk || !currentChunk) { - return false - } - const identical = JSON.stringify(cachedChunk) === JSON.stringify(currentChunk) - if (!identical) { - try { - logger.error( - { - projectId, - cachedChunkStartVersion: cachedChunk.getStartVersion(), - cachedChunkEndVersion: cachedChunk.getEndVersion(), - currentChunkStartVersion: currentChunk.getStartVersion(), - currentChunkEndVersion: currentChunk.getEndVersion(), - }, - 'chunk cache mismatch' - ) - } catch (err) { - // ignore errors while logging - } - } - metrics.inc('chunk_store.redis.compare_chunks', 1, { - status: identical ? 'success' : 'fail', - }) - return identical -} - -// Define Lua script for atomic cache clearing -rclient.defineCommand('expire_chunk_cache', { - numberOfKeys: 5, - lua: ` - local persistTimeExists = redis.call('EXISTS', KEYS[5]) - if persistTimeExists == 1 then - return nil -- chunk has changes pending, do not expire - end - local currentTime = tonumber(ARGV[1]) - local expireTimeValue = redis.call('GET', KEYS[4]) - if not expireTimeValue then - return nil -- this is a cache-miss - end - local expireTime = tonumber(expireTimeValue) - if currentTime < expireTime then - return nil -- cache is still valid - end - -- Cache is expired and all changes are persisted, proceed to delete the keys atomically - redis.call('DEL', KEYS[1]) -- snapshot key - redis.call('DEL', KEYS[2]) -- startVersion key - redis.call('DEL', KEYS[3]) -- changes key - redis.call('DEL', KEYS[4]) -- expireTime key - return 1 - `, -}) - -/** - * Expire cache entries for a project's chunk data if needed - * @param {string} projectId - The ID of the project whose cache should be cleared - * @returns {Promise} A promise that resolves to true if successful, false on error - */ -async function expireCurrentChunk(projectId, currentTime) { - try { - const snapshotKey = keySchema.snapshot({ projectId }) - const startVersionKey = keySchema.startVersion({ projectId }) - const changesKey = keySchema.changes({ projectId }) - const expireTimeKey = keySchema.expireTime({ projectId }) - const persistTimeKey = keySchema.persistTime({ projectId }) - const result = await rclient.expire_chunk_cache( - snapshotKey, - startVersionKey, - changesKey, - expireTimeKey, - persistTimeKey, - currentTime || Date.now() - ) - if (!result) { - logger.debug( - { projectId }, - 'chunk cache not expired due to pending changes' - ) - metrics.inc('chunk_store.redis.expire_cache', 1, { - status: 'skip-due-to-pending-changes', - }) - return false // not expired - } - metrics.inc('chunk_store.redis.expire_cache', 1, { status: 'success' }) - return true - } catch (err) { - logger.error({ err, projectId }, 'error clearing chunk cache from redis') - metrics.inc('chunk_store.redis.expire_cache', 1, { status: 'error' }) - return false - } -} - -// Define Lua script for atomic cache clearing -rclient.defineCommand('clear_chunk_cache', { - numberOfKeys: 5, - lua: ` - local persistTimeExists = redis.call('EXISTS', KEYS[5]) - if persistTimeExists == 1 then - return nil -- chunk has changes pending, do not clear - end - -- Delete all keys related to a project's chunk cache atomically - redis.call('DEL', KEYS[1]) -- snapshot key - redis.call('DEL', KEYS[2]) -- startVersion key - redis.call('DEL', KEYS[3]) -- changes key - redis.call('DEL', KEYS[4]) -- expireTime key - return 1 - `, -}) - -/** - * Clears all cache entries for a project's chunk data - * @param {string} projectId - The ID of the project whose cache should be cleared - * @returns {Promise} A promise that resolves to true if successful, false on error - */ -async function clearCache(projectId) { - try { - const snapshotKey = keySchema.snapshot({ projectId }) - const startVersionKey = keySchema.startVersion({ projectId }) - const changesKey = keySchema.changes({ projectId }) - const expireTimeKey = keySchema.expireTime({ projectId }) - const persistTimeKey = keySchema.persistTime({ projectId }) // Add persistTimeKey - - const result = await rclient.clear_chunk_cache( - snapshotKey, - startVersionKey, - changesKey, - expireTimeKey, - persistTimeKey - ) - if (result === null) { - logger.debug( - { projectId }, - 'chunk cache not cleared due to pending changes' - ) - metrics.inc('chunk_store.redis.clear_cache', 1, { - status: 'skip-due-to-pending-changes', - }) - return false - } - metrics.inc('chunk_store.redis.clear_cache', 1, { status: 'success' }) - return true - } catch (err) { - logger.error({ err, projectId }, 'error clearing chunk cache from redis') - metrics.inc('chunk_store.redis.clear_cache', 1, { status: 'error' }) - return false - } -} - -// Define Lua script for getting chunk status -rclient.defineCommand('get_chunk_status', { - numberOfKeys: 2, // expireTimeKey, persistTimeKey - lua: ` - local expireTimeValue = redis.call('GET', KEYS[1]) - local persistTimeValue = redis.call('GET', KEYS[2]) - return {expireTimeValue, persistTimeValue} - `, -}) - -/** - * Retrieves the current chunk status for a given project from Redis - * @param {string} projectId - The ID of the project to get status for - * @returns {Promise} Object containing expireTime and persistTime, or nulls on error - * @property {number|null} expireTime - The expiration time of the chunk - * @property {number|null} persistTime - The persistence time of the chunk - */ -async function getCurrentChunkStatus(projectId) { - try { - const expireTimeKey = keySchema.expireTime({ projectId }) - const persistTimeKey = keySchema.persistTime({ projectId }) - - const result = await rclient.get_chunk_status(expireTimeKey, persistTimeKey) - - // Lua script returns an array [expireTimeValue, persistTimeValue] - // Redis nil replies are converted to null by ioredis - const [expireTime, persistTime] = result - - return { - expireTime: expireTime ? parseInt(expireTime, 10) : null, // Parse to number or null - persistTime: persistTime ? parseInt(persistTime, 10) : null, // Parse to number or null - } - } catch (err) { - logger.warn({ err, projectId }, 'error getting chunk status from redis') - return { expireTime: null, persistTime: null } // Return nulls on error - } -} - -/** - * Sets the persist time for a project's chunk cache. - * This is primarily intended for testing purposes. - * @param {string} projectId - The ID of the project. - * @param {number} timestamp - The timestamp to set as the persist time. - * @returns {Promise} - */ -async function setPersistTime(projectId, timestamp) { - try { - const persistTimeKey = keySchema.persistTime({ projectId }) - await rclient.set(persistTimeKey, timestamp) - metrics.inc('chunk_store.redis.set_persist_time', 1, { status: 'success' }) - } catch (err) { - logger.error( - { err, projectId, timestamp }, - 'error setting persist time in redis' - ) - metrics.inc('chunk_store.redis.set_persist_time', 1, { status: 'error' }) - // Re-throw the error so the test fails if setting fails + metrics.inc('chunk_store.redis.get_head_snapshot', 1, { status: 'error' }) throw err } } -module.exports = { - getCurrentChunk, - getCurrentChunkIfValid, - setCurrentChunk, - getCurrentChunkMetadata, - checkCacheValidity, - checkCacheValidityWithMetadata, - compareChunks, - expireCurrentChunk, - clearCache, - getCurrentChunkStatus, - setPersistTime, // Export the new function +rclient.defineCommand('queue_changes', { + numberOfKeys: 5, + lua: ` + local headSnapshotKey = KEYS[1] + local headVersionKey = KEYS[2] + local changesKey = KEYS[3] + local expireTimeKey = KEYS[4] + local persistTimeKey = KEYS[5] + + local baseVersion = tonumber(ARGV[1]) + local head = ARGV[2] + local persistTime = tonumber(ARGV[3]) + local expireTime = tonumber(ARGV[4]) + -- Changes start from ARGV[5] + + local headVersion = tonumber(redis.call('GET', headVersionKey)) + if headVersion and headVersion ~= baseVersion then + return 'conflict' + end + + -- Check if there are any changes to queue + if #ARGV < 5 then + return 'no_changes_provided' + end + + -- Store the changes + -- RPUSH changesKey change1 change2 ... + redis.call('RPUSH', changesKey, unpack(ARGV, 5, #ARGV)) + + -- Update head snapshot only if changes were successfully pushed + redis.call('SET', headSnapshotKey, head) + + -- Update the head version + local numChanges = #ARGV - 4 + local newHeadVersion = baseVersion + numChanges + redis.call('SET', headVersionKey, newHeadVersion) + + -- Update the persist time if the new time is sooner + local currentPersistTime = tonumber(redis.call('GET', persistTimeKey)) + if not currentPersistTime or persistTime < currentPersistTime then + redis.call('SET', persistTimeKey, persistTime) + end + + -- Update the expire time + redis.call('SET', expireTimeKey, expireTime) + + return 'ok' + `, +}) + +/** + * Atomically queues changes to the project history in Redis if the baseVersion matches. + * Updates head snapshot, version, persist time, and expire time. + * + * @param {string} projectId - The project identifier. + * @param {Snapshot} headSnapshot - The new head snapshot after applying changes. + * @param {number} baseVersion - The expected current head version. + * @param {Change[]} changes - An array of Change objects to queue. + * @param {number} persistTime - Timestamp (ms since epoch) when the oldest change in the buffer should be persisted. + * @param {number} expireTime - Timestamp (ms since epoch) when the project buffer should expire if inactive. + * @returns {Promise} Resolves on success. + * @throws {BaseVersionConflictError} If the baseVersion does not match the current head version in Redis. + * @throws {Error} If changes array is empty or if Redis operations fail. + */ +async function queueChanges( + projectId, + headSnapshot, + baseVersion, + changes, + persistTime, + expireTime +) { + if (!changes || changes.length === 0) { + throw new Error('Cannot queue empty changes array') + } + + try { + const keys = [ + keySchema.head({ projectId }), + keySchema.headVersion({ projectId }), + keySchema.changes({ projectId }), + keySchema.expireTime({ projectId }), + keySchema.persistTime({ projectId }), + ] + + const args = [ + baseVersion.toString(), + JSON.stringify(headSnapshot.toRaw()), + persistTime.toString(), + expireTime.toString(), + ...changes.map(change => JSON.stringify(change.toRaw())), // Serialize changes + ] + + const status = await rclient.queue_changes(keys, args) + metrics.inc('chunk_store.redis.queue_changes', 1, { status }) + if (status === 'ok') { + return + } + if (status === 'conflict') { + throw new BaseVersionConflictError('base version mismatch', { + projectId, + baseVersion, + }) + } else { + throw new Error(`unexpected result queuing changes: ${status}`) + } + } catch (err) { + if (err instanceof BaseVersionConflictError) { + // Re-throw conflict errors directly + throw err + } + metrics.inc('chunk_store.redis.queue_changes', 1, { status: 'error' }) + throw err + } +} + +rclient.defineCommand('get_state', { + numberOfKeys: 6, // Number of keys defined in keySchema + lua: ` + local headSnapshotKey = KEYS[1] + local headVersionKey = KEYS[2] + local persistedVersionKey = KEYS[3] + local expireTimeKey = KEYS[4] + local persistTimeKey = KEYS[5] + local changesKey = KEYS[6] + + local headSnapshot = redis.call('GET', headSnapshotKey) + local headVersion = redis.call('GET', headVersionKey) + local persistedVersion = redis.call('GET', persistedVersionKey) + local expireTime = redis.call('GET', expireTimeKey) + local persistTime = redis.call('GET', persistTimeKey) + local changes = redis.call('LRANGE', changesKey, 0, -1) -- Get all changes in the list + + return {headSnapshot, headVersion, persistedVersion, expireTime, persistTime, changes} + `, +}) + +/** + * Retrieves the entire state associated with a project from Redis atomically. + * @param {string} projectId - The unique identifier of the project. + * @returns {Promise} A Promise that resolves to an object containing the project state, + * or null if the project state does not exist (e.g., head version is missing). + * @throws {Error} If Redis operations fail. + */ +async function getState(projectId) { + const keys = [ + keySchema.head({ projectId }), + keySchema.headVersion({ projectId }), + keySchema.persistedVersion({ projectId }), + keySchema.expireTime({ projectId }), + keySchema.persistTime({ projectId }), + keySchema.changes({ projectId }), + ] + + // Pass keys individually, not as an array + const result = await rclient.get_state(...keys) + + const [ + rawHeadSnapshot, + rawHeadVersion, + rawPersistedVersion, + rawExpireTime, + rawPersistTime, + rawChanges, + ] = result + + // Safely parse values, providing defaults or nulls if necessary + const headSnapshot = rawHeadSnapshot + ? JSON.parse(rawHeadSnapshot) + : rawHeadSnapshot + const headVersion = rawHeadVersion ? parseInt(rawHeadVersion, 10) : null // Should always exist if result is not null + const persistedVersion = rawPersistedVersion + ? parseInt(rawPersistedVersion, 10) + : null + const expireTime = rawExpireTime ? parseInt(rawExpireTime, 10) : null + const persistTime = rawPersistTime ? parseInt(rawPersistTime, 10) : null + const changes = rawChanges ? rawChanges.map(JSON.parse) : null + + return { + headSnapshot, + headVersion, + persistedVersion, + expireTime, + persistTime, + changes, + } +} + +rclient.defineCommand('get_changes_since_version', { + numberOfKeys: 2, + lua: ` + local headVersionKey = KEYS[1] + local changesKey = KEYS[2] + + local requestedVersion = tonumber(ARGV[1]) + + -- Check if head version exists + local headVersion = tonumber(redis.call('GET', headVersionKey)) + if not headVersion then + return {'not_found'} + end + + -- If requested version equals head version, return empty array + if requestedVersion == headVersion then + return {'ok', {}} + end + + -- If requested version is greater than head version, return error + if requestedVersion > headVersion then + return {'out_of_bounds'} + end + + -- Get length of changes list + local changesCount = redis.call('LLEN', changesKey) + + -- Check if requested version is too old (changes already removed from buffer) + if requestedVersion < (headVersion - changesCount) then + return {'out_of_bounds'} + end + + -- Calculate the starting index, using negative indexing to count backwards + -- from the end of the list + local startIndex = requestedVersion - headVersion + + -- Get changes using LRANGE + local changes = redis.call('LRANGE', changesKey, startIndex, -1) + + return {'ok', changes} + `, +}) + +/** + * Retrieves changes since a specific version for a project from Redis. + * + * @param {string} projectId - The unique identifier of the project. + * @param {number} version - The version number to retrieve changes since. + * @returns {Promise<{status: string, changes?: Array}>} A Promise that resolves to an object containing: + * - status: 'OK', 'NOT_FOUND', or 'OUT_OF_BOUNDS' + * - changes: Array of Change objects (only when status is 'OK') + * @throws {Error} If Redis operations fail. + */ +async function getChangesSinceVersion(projectId, version) { + try { + const keys = [ + keySchema.headVersion({ projectId }), + keySchema.changes({ projectId }), + ] + + const args = [version.toString()] + + const result = await rclient.get_changes_since_version(keys, args) + const status = result[0] + + if (status === 'ok') { + // If status is OK, parse the changes + const changes = result[1] + ? result[1].map(rawChange => + typeof rawChange === 'string' ? JSON.parse(rawChange) : rawChange + ) + : [] + + metrics.inc('chunk_store.redis.get_changes_since_version', 1, { + status: 'success', + }) + return { status, changes } + } else { + // For other statuses, just return the status + metrics.inc('chunk_store.redis.get_changes_since_version', 1, { + status, + }) + return { status } + } + } catch (err) { + metrics.inc('chunk_store.redis.get_changes_since_version', 1, { + status: 'error', + }) + throw err + } +} + +rclient.defineCommand('get_non_persisted_changes', { + numberOfKeys: 3, + lua: ` + local headVersionKey = KEYS[1] + local persistedVersionKey = KEYS[2] + local changesKey = KEYS[3] + + -- Check if head version exists + local headVersion = tonumber(redis.call('GET', headVersionKey)) + if not headVersion then + return {} + end + + -- Check if persisted version exists + local persistedVersion = tonumber(redis.call('GET', persistedVersionKey)) + + local startIndex + if not persistedVersion then + -- None of the changes in Redis have been persisted + startIndex = 0 + elseif persistedVersion > headVersion then + -- This should never happen + return redis.error_reply('HEAD_VERSION_BEHIND_PERSISTED_VERSION') + elseif persistedVersion == headVersion then + return {} + else + -- startIndex is negative and counts from the end of the list of changes + startIndex = persistedVersion - headVersion + end + + -- Get changes using LRANGE + local changes = redis.call('LRANGE', changesKey, startIndex, -1) + + return changes + `, +}) + +/** + * Retrieves non-persisted changes for a project from Redis. + * + * @param {string} projectId - The unique identifier of the project. + * @returns {Promise} A Promise that resolves to an array of non-persisted Change objects. + * @throws {Error} If Redis operations fail. + */ +async function getNonPersistedChanges(projectId) { + try { + const keys = [ + keySchema.headVersion({ projectId }), + keySchema.persistedVersion({ projectId }), + keySchema.changes({ projectId }), + ] + + const result = await rclient.get_non_persisted_changes(keys) + + // Parse the changes + const changes = result?.map(json => Change.fromRaw(JSON.parse(json))) ?? [] + + metrics.inc('chunk_store.redis.get_non_persisted_changes', 1, { + status: 'success', + }) + return changes + } catch (err) { + metrics.inc('chunk_store.redis.get_non_persisted_changes', 1, { + status: 'error', + }) + throw err + } +} + +rclient.defineCommand('set_persisted_version', { + numberOfKeys: 3, + lua: ` + local headVersionKey = KEYS[1] + local persistedVersionKey = KEYS[2] + local changesKey = KEYS[3] + + local newPersistedVersion = tonumber(ARGV[1]) + local maxPersistedChanges = tonumber(ARGV[2]) + + -- Check if head version exists + local headVersion = tonumber(redis.call('GET', headVersionKey)) + if not headVersion then + return 'not_found' + end + + -- Set the persisted version + redis.call('SET', persistedVersionKey, newPersistedVersion) + + -- Calculate the starting index, to keep only maxPersistedChanges beyond the persisted version + -- Using negative indexing to count backwards from the end of the list + local startIndex = newPersistedVersion - headVersion - maxPersistedChanges + + -- Trim the changes list to keep only the specified number of changes beyond persisted version + if startIndex < 0 then + redis.call('LTRIM', changesKey, startIndex, -1) + end + + return 'ok' + `, +}) + +/** + * Sets the persisted version for a project in Redis and trims the changes list. + * + * @param {string} projectId - The unique identifier of the project. + * @param {number} persistedVersion - The version number to set as persisted. + * @returns {Promise} A Promise that resolves to 'OK' or 'NOT_FOUND'. + * @throws {Error} If Redis operations fail. + */ +async function setPersistedVersion(projectId, persistedVersion) { + try { + const keys = [ + keySchema.headVersion({ projectId }), + keySchema.persistedVersion({ projectId }), + keySchema.changes({ projectId }), + ] + + const args = [persistedVersion.toString(), MAX_PERSISTED_CHANGES.toString()] + + const status = await rclient.set_persisted_version(keys, args) + + metrics.inc('chunk_store.redis.set_persisted_version', 1, { + status, + }) + + return status + } catch (err) { + metrics.inc('chunk_store.redis.set_persisted_version', 1, { + status: 'error', + }) + throw err + } +} + +rclient.defineCommand('set_expire_time', { + numberOfKeys: 2, + lua: ` + local expireTimeKey = KEYS[1] + local headVersionKey = KEYS[2] + local expireTime = tonumber(ARGV[1]) + + -- Only set the expire time if the project is loaded in Redis + local headVersion = redis.call('GET', headVersionKey) + if headVersion then + redis.call('SET', expireTimeKey, expireTime) + end + `, +}) + +/** + * Sets the expire version for a project in Redis + * + * @param {string} projectId + * @param {number} expireTime - Timestamp (ms since epoch) when the project + * buffer should expire if inactive + */ +async function setExpireTime(projectId, expireTime) { + try { + await rclient.set_expire_time( + keySchema.expireTime({ projectId }), + keySchema.headVersion({ projectId }), + expireTime.toString() + ) + metrics.inc('chunk_store.redis.set_expire_time', 1, { status: 'success' }) + } catch (err) { + metrics.inc('chunk_store.redis.set_expire_time', 1, { status: 'error' }) + throw err + } +} + +rclient.defineCommand('expire_project', { + numberOfKeys: 6, + lua: ` + local headKey = KEYS[1] + local headVersionKey = KEYS[2] + local changesKey = KEYS[3] + local persistedVersionKey = KEYS[4] + local persistTimeKey = KEYS[5] + local expireTimeKey = KEYS[6] + + local headVersion = tonumber(redis.call('GET', headVersionKey)) + if not headVersion then + return 'not-found' + end + + local persistedVersion = tonumber(redis.call('GET', persistedVersionKey)) + if not persistedVersion or persistedVersion ~= headVersion then + return 'not-persisted' + end + + redis.call('DEL', + headKey, + headVersionKey, + changesKey, + persistedVersionKey, + persistTimeKey, + expireTimeKey + ) + return 'success' + `, +}) + +async function expireProject(projectId) { + try { + const status = await rclient.expire_project( + keySchema.head({ projectId }), + keySchema.headVersion({ projectId }), + keySchema.changes({ projectId }), + keySchema.persistedVersion({ projectId }), + keySchema.persistTime({ projectId }), + keySchema.expireTime({ projectId }) + ) + metrics.inc('chunk_store.redis.set_persisted_version', 1, { + status, + }) + } catch (err) { + metrics.inc('chunk_store.redis.set_persisted_version', 1, { + status: 'error', + }) + throw err + } +} + +rclient.defineCommand('claim_job', { + numberOfKeys: 1, + lua: ` + local jobTimeKey = KEYS[1] + local currentTime = tonumber(ARGV[1]) + local retryDelay = tonumber(ARGV[2]) + + local jobTime = tonumber(redis.call('GET', jobTimeKey)) + if not jobTime then + return {'no-job'} + end + + local msUntilReady = jobTime - currentTime + if msUntilReady <= 0 then + local retryTime = currentTime + retryDelay + redis.call('SET', jobTimeKey, retryTime) + return {'ok', retryTime} + else + return {'wait', msUntilReady} + end + `, +}) + +rclient.defineCommand('close_job', { + numberOfKeys: 1, + lua: ` + local jobTimeKey = KEYS[1] + local expectedJobTime = tonumber(ARGV[1]) + + local jobTime = tonumber(redis.call('GET', jobTimeKey)) + if jobTime and jobTime == expectedJobTime then + redis.call('DEL', jobTimeKey) + end + `, +}) + +/** + * Claim an expire job + * + * @param {string} projectId + * @return {Promise} + */ +async function claimExpireJob(projectId) { + return await claimJob(keySchema.expireTime({ projectId })) +} + +/** + * Claim a persist job + * + * @param {string} projectId + * @return {Promise} + */ +async function claimPersistJob(projectId) { + return await claimJob(keySchema.persistTime({ projectId })) +} + +/** + * Claim a persist or expire job + * + * @param {string} jobKey - the Redis key containing the time at which the job + * is ready + * @return {Promise} + */ +async function claimJob(jobKey) { + let result, status + try { + result = await rclient.claim_job(jobKey, Date.now(), RETRY_DELAY_MS) + status = result[0] + metrics.inc('chunk_store.redis.claim_job', 1, { status }) + } catch (err) { + metrics.inc('chunk_store.redis.claim_job', 1, { status: 'error' }) + throw err + } + + if (status === 'ok') { + return new Job(jobKey, parseInt(result[1], 10)) + } else if (status === 'wait') { + throw new JobNotReadyError('job not ready', { + jobKey, + retryTime: result[1], + }) + } else if (status === 'no-job') { + throw new JobNotFoundError('job not found', { jobKey }) + } else { + throw new OError('unknown status for claim_job', { jobKey, status }) + } +} + +/** + * Handle for a claimed job + */ +class Job { + /** + * @param {string} redisKey + * @param {number} claimTimestamp + */ + constructor(redisKey, claimTimestamp) { + this.redisKey = redisKey + this.claimTimestamp = claimTimestamp + } + + async close() { + try { + await rclient.close_job(this.redisKey, this.claimTimestamp.toString()) + metrics.inc('chunk_store.redis.close_job', 1, { status: 'success' }) + } catch (err) { + metrics.inc('chunk_store.redis.close_job', 1, { status: 'error' }) + throw err + } + } +} + +module.exports = { + getHeadSnapshot, + queueChanges, + getState, + getChangesSinceVersion, + getNonPersistedChanges, + setPersistedVersion, + setExpireTime, + expireProject, + claimExpireJob, + claimPersistJob, + MAX_PERSISTED_CHANGES, + MAX_PERSIST_DELAY_MS, + PROJECT_TTL_MS, + RETRY_DELAY_MS, + keySchema, } diff --git a/services/history-v1/storage/scripts/expire_redis_chunks.js b/services/history-v1/storage/scripts/expire_redis_chunks.js index 11b34101da..de4e130ed4 100644 --- a/services/history-v1/storage/scripts/expire_redis_chunks.js +++ b/services/history-v1/storage/scripts/expire_redis_chunks.js @@ -2,7 +2,7 @@ const logger = require('@overleaf/logger') const commandLineArgs = require('command-line-args') // Add this line const redis = require('../lib/redis') const { scanRedisCluster, extractKeyId } = require('../lib/scan') -const { expireCurrentChunk } = require('../lib/chunk_store/redis') +const { expireProject, claimExpireJob } = require('../lib/chunk_store/redis') const rclient = redis.rclientHistory const EXPIRE_TIME_KEY_PATTERN = `expire-time:{*}` @@ -30,24 +30,42 @@ function isExpiredKey(expireTimestamp, currentTime) { return currentTime > expireTime } -async function processKeysBatch(keysBatch, rclient) { +async function fetchTimestamps(projectIds, rclient) { + const expireTimeKeys = projectIds.map(id => `expire-time:{${id}}`) + // For efficiency, we use MGET to fetch all the timestamps in a single request + const expireTimestamps = await rclient.mget(expireTimeKeys) + // Return an array of objects with projectId and expireTimestamp + const results = projectIds.map((projectId, index) => ({ + projectId, + expireTimestamp: expireTimestamps[index], + })) + return results +} + +async function processKeysBatch(projectIds, rclient) { let clearedKeyCount = 0 - if (keysBatch.length === 0) { + if (projectIds.length === 0) { return 0 } - // For efficiency, we use MGET to fetch all the timestamps in a single request - const expireTimestamps = await rclient.mget(keysBatch) + const projects = await fetchTimestamps(projectIds, rclient) const currentTime = Date.now() - for (let i = 0; i < keysBatch.length; i++) { - const key = keysBatch[i] + + for (const project of projects) { + const { projectId, expireTimestamp } = project // For each key, do a quick check to see if the key is expired before calling // the LUA script to expire the chunk atomically. - if (isExpiredKey(expireTimestamps[i], currentTime)) { - const projectId = extractKeyId(key) + if (isExpiredKey(expireTimestamp, currentTime)) { if (DRY_RUN) { logger.info({ projectId }, '[Dry Run] Would expire chunk for project') } else { - await expireCurrentChunk(projectId) + try { + const job = await claimExpireJob(projectId) + await expireProject(projectId) + await job.close() + } catch (err) { + logger.error({ projectId, err }, 'error expiring chunk for project') + continue + } } clearedKeyCount++ } @@ -61,7 +79,6 @@ async function expireRedisChunks() { const START_TIME = Date.now() if (DRY_RUN) { - // Use global DRY_RUN logger.info({}, 'starting expireRedisChunks scan in DRY RUN mode') } else { logger.info({}, 'starting expireRedisChunks scan') @@ -72,7 +89,10 @@ async function expireRedisChunks() { EXPIRE_TIME_KEY_PATTERN )) { scannedKeyCount += keysBatch.length - clearedKeyCount += await processKeysBatch(keysBatch, rclient) + clearedKeyCount += await processKeysBatch( + keysBatch.map(extractKeyId), + rclient + ) if (scannedKeyCount % 1000 === 0) { logger.info( { scannedKeyCount, clearedKeyCount }, @@ -92,7 +112,13 @@ async function expireRedisChunks() { await redis.disconnect() } -expireRedisChunks().catch(err => { - logger.fatal({ err }, 'unhandled error in expireRedisChunks') - process.exit(1) -}) +// Check if the script is being run directly +if (require.main === module) { + expireRedisChunks().catch(err => { + logger.fatal({ err }, 'unhandled error in expireRedisChunks') + process.exit(1) + }) +} else { + // Export the function for module usage + module.exports = { expireRedisChunks } +} diff --git a/services/history-v1/test/acceptance/js/api/project_updates.test.js b/services/history-v1/test/acceptance/js/api/project_updates.test.js index eb7b1703a7..f50f3677b5 100644 --- a/services/history-v1/test/acceptance/js/api/project_updates.test.js +++ b/services/history-v1/test/acceptance/js/api/project_updates.test.js @@ -22,7 +22,6 @@ const TextOperation = core.TextOperation const V2DocVersions = core.V2DocVersions const knex = require('../../../../storage').knex -const redis = require('../../../../storage/lib/chunk_store/redis') describe('history import', function () { beforeEach(cleanup.everything) @@ -595,10 +594,6 @@ describe('history import', function () { testFiles.NULL_CHARACTERS_TXT_BYTE_LENGTH ) }) - .then(() => { - // Now clear the cache because we have changed the string length in the database - return redis.clearCache(testProjectId) - }) .then(importChanges) .then(getLatestContent) .then(response => { diff --git a/services/history-v1/test/acceptance/js/storage/chunk_buffer.test.js b/services/history-v1/test/acceptance/js/storage/chunk_buffer.test.js deleted file mode 100644 index 841282a8e4..0000000000 --- a/services/history-v1/test/acceptance/js/storage/chunk_buffer.test.js +++ /dev/null @@ -1,351 +0,0 @@ -'use strict' - -const { expect } = require('chai') -const sinon = require('sinon') -const { - Chunk, - Snapshot, - History, - File, - AddFileOperation, - EditFileOperation, - AddCommentOperation, - TextOperation, - Range, - TrackingProps, - Change, -} = require('overleaf-editor-core') -const cleanup = require('./support/cleanup') -const fixtures = require('./support/fixtures') -const chunkBuffer = require('../../../../storage/lib/chunk_buffer') -const chunkStore = require('../../../../storage/lib/chunk_store') -const redisBackend = require('../../../../storage/lib/chunk_store/redis') -const metrics = require('@overleaf/metrics') - -describe('chunk buffer', function () { - beforeEach(cleanup.everything) - beforeEach(fixtures.create) - beforeEach(function () { - sinon.spy(metrics, 'inc') - }) - afterEach(function () { - metrics.inc.restore() - }) - - const projectId = '123456' - - describe('loadLatest', function () { - // Initialize project and create a test chunk - beforeEach(async function () { - // Initialize project in chunk store - await chunkStore.initializeProject(projectId) - }) - - describe('with an existing chunk', function () { - beforeEach(async function () { - // Create a sample chunk with some content - const snapshot = new Snapshot() - const changes = [ - new Change( - [new AddFileOperation('test.tex', File.fromString('Hello World'))], - new Date(), - [] - ), - ] - const history = new History(snapshot, changes) - const chunk = new Chunk(history, 1) // startVersion 1 - - // Store the chunk directly in the chunk store using create method - // which internally calls uploadChunk - await chunkStore.create(projectId, chunk) - - // Clear any existing cache - await redisBackend.clearCache(projectId) - }) - - it('should load from chunk store and update cache on first access (cache miss)', async function () { - // Load the underlying chunk from the chunk store for verification - const storedChunk = await chunkStore.loadLatest(projectId) - - // First access should load from chunk store and populate cache - const firstResult = await chunkBuffer.loadLatest(projectId) - - // Verify the chunk is correct - expect(firstResult).to.not.be.null - expect(firstResult.getStartVersion()).to.equal(1) - expect(firstResult.getEndVersion()).to.equal(2) - - // Verify the chunk is the same as the one in the store - expect(firstResult).to.deep.equal(storedChunk) - - // Verify that we got a cache miss metric - expect( - metrics.inc.calledWith('chunk_buffer.loadLatest', 1, { - status: 'cache-miss', - }) - ).to.be.true - - // Reset the metrics spy - metrics.inc.resetHistory() - - // Second access should hit the cache - const secondResult = await chunkBuffer.loadLatest(projectId) - - // Verify we got the same chunk - expect(secondResult).to.not.be.null - expect(secondResult.getStartVersion()).to.equal(1) - expect(secondResult.getEndVersion()).to.equal(2) - - // Verify the chunk is the same as the one in the store - expect(secondResult).to.deep.equal(storedChunk) - - // Verify that we got a cache hit metric - expect( - metrics.inc.calledWith('chunk_buffer.loadLatest', 1, { - status: 'cache-hit', - }) - ).to.be.true - - // Verify both chunks are equivalent - expect(secondResult.getStartVersion()).to.equal( - firstResult.getStartVersion() - ) - expect(secondResult.getEndVersion()).to.equal( - firstResult.getEndVersion() - ) - expect(secondResult).to.deep.equal(firstResult) - }) - - it('should refresh the cache when chunk changes in the store', async function () { - // First access to load into cache - const firstResult = await chunkBuffer.loadLatest(projectId) - expect(firstResult.getStartVersion()).to.equal(1) - - // Reset metrics spy - metrics.inc.resetHistory() - - // Create a new chunk with different content - const newSnapshot = new Snapshot() - const newChanges = [ - new Change( - [ - new AddFileOperation( - 'updated.tex', - File.fromString('Updated content') - ), - ], - new Date(), - [] - ), - ] - const newHistory = new History(newSnapshot, newChanges) - const newChunk = new Chunk(newHistory, 2) // Different start version - - // Store the new chunk directly in the chunk store - await chunkStore.create(projectId, newChunk) - - // Load the underlying chunk from the chunk store for verification - const storedChunk = await chunkStore.loadLatest(projectId) - - // Access again - should detect the change and refresh cache - const secondResult = await chunkBuffer.loadLatest(projectId) - - // Verify we got the updated chunk - expect(secondResult.getStartVersion()).to.equal(2) - expect(secondResult.getEndVersion()).to.equal(3) - // Verify that the chunk content is the same - expect(secondResult).to.deep.equal(storedChunk) - - // Verify that we got a cache miss metric (since the cached chunk was invalidated) - expect( - metrics.inc.calledWith('chunk_buffer.loadLatest', 1, { - status: 'cache-miss', - }) - ).to.be.true - }) - - it('should continue using cache when chunk in store has not changed', async function () { - // Load the underlying chunk from the chunk store for verification - const storedChunk = await chunkStore.loadLatest(projectId) - - // First access to load into cache - await chunkBuffer.loadLatest(projectId) - - // Reset metrics spy - metrics.inc.resetHistory() - - // Access again without changing the underlying chunk - const result = await chunkBuffer.loadLatest(projectId) - - // Verify we got the same chunk - expect(result.getStartVersion()).to.equal(1) - expect(result.getEndVersion()).to.equal(2) - expect(result).to.deep.equal(storedChunk) - - // Verify that we got a cache hit metric - expect( - metrics.inc.calledWith('chunk_buffer.loadLatest', 1, { - status: 'cache-hit', - }) - ).to.be.true - }) - }) - - it('should handle a chunk with metadata, comments and tracked changes', async function () { - // Create a snapshot and initial file - const snapshot = new Snapshot() - const initialFileOp = new AddFileOperation( - 'test.tex', - File.fromString('Initial line.\\nSecond line.', { - meta1: 'abc', - meta2: 'def', - }) - ) - const initialChange = new Change([initialFileOp], new Date(), []) - - // Add a comment - const commentOp = new AddCommentOperation( - 'comment1', - [new Range(0, 7)] // Range for "Initial" - ) - const commentChange = new Change( - [new EditFileOperation('test.tex', commentOp)], - new Date(), - [] - ) - - // Tracked insert - const trackedInsertOp = new TextOperation() - .retain(14) - .insert('Hello', { - commentIds: ['comment1'], - tracking: TrackingProps.fromRaw({ - ts: '2024-01-01T00:00:00.000Z', - type: 'insert', - userId: 'user1', - }), - }) - .retain(12) - const insertChange = new Change( - [new EditFileOperation('test.tex', trackedInsertOp)], - new Date(), - [] - ) - - // Tracked delete - const trackedDeleteOp = new TextOperation().retain(14, { - tracking: TrackingProps.fromRaw({ - ts: '2024-01-01T00:00:00.000Z', - type: 'delete', - userId: 'user1', - }), - }) - const deleteChange = new Change( - [new EditFileOperation('test.tex', trackedDeleteOp)], - new Date(), - [] - ) - - // Combine changes into history and create chunk - const history = new History(snapshot, [ - initialChange, - commentChange, - insertChange, - deleteChange, - ]) - const chunk = new Chunk(history, 1) // Start version 0 - // Store the chunk - await chunkStore.create(projectId, chunk) - // Clear the cache - await redisBackend.clearCache(projectId) - metrics.inc.resetHistory() - - // Load the underlying chunk from the chunk store for verification - const storedChunk = await chunkStore.loadLatest(projectId) - - // Load the chunk via buffer (cache miss) - const firstResult = await chunkBuffer.loadLatest(projectId) - - // Verify chunk details - expect(firstResult.getStartVersion()).to.equal(1) - expect(firstResult.getEndVersion()).to.equal(5) // 4 changes - expect(firstResult.history.changes.length).to.equal(4) - expect(firstResult).to.deep.equal(storedChunk) - - // Verify cache miss metric - expect( - metrics.inc.calledWith('chunk_buffer.loadLatest', 1, { - status: 'cache-miss', - }) - ).to.be.true - - // Reset metrics - metrics.inc.resetHistory() - - // Second access should hit the cache - const secondResult = await chunkBuffer.loadLatest(projectId) - - // Verify we got the same chunk - expect(secondResult.getStartVersion()).to.equal(1) - expect(secondResult.getEndVersion()).to.equal(5) - expect(secondResult.history.changes.length).to.equal(4) - expect(secondResult).to.deep.equal(storedChunk) - - // Verify cache hit metric - expect( - metrics.inc.calledWith('chunk_buffer.loadLatest', 1, { - status: 'cache-hit', - }) - ).to.be.true - }) - - describe('with an empty project', function () { - it('should handle a case with empty chunks (no changes)', async function () { - // Clear the cache - await redisBackend.clearCache(projectId) - - // Load the underlying chunk from the chunk store for verification - const storedChunk = await chunkStore.loadLatest(projectId) - - // Load the initial empty chunk via buffer - const result = await chunkBuffer.loadLatest(projectId) - - // Verify we got the empty chunk - expect(result.getStartVersion()).to.equal(0) - expect(result.getEndVersion()).to.equal(0) // Start equals end for empty chunks - expect(result.history.changes.length).to.equal(0) - - // Verify that the chunk is the same as the one in the store - expect(result).to.deep.equal(storedChunk) - - // Verify cache miss metric - expect( - metrics.inc.calledWith('chunk_buffer.loadLatest', 1, { - status: 'cache-miss', - }) - ).to.be.true - - // Reset metrics - metrics.inc.resetHistory() - - // Second access should hit the cache - const secondResult = await chunkBuffer.loadLatest(projectId) - - // Verify we got the same empty chunk - expect(secondResult.getStartVersion()).to.equal(0) - expect(secondResult.getEndVersion()).to.equal(0) - expect(secondResult.history.changes.length).to.equal(0) - - // Verify that the chunk is the same as the one in the store - expect(secondResult).to.deep.equal(storedChunk) - - // Verify cache hit metric - expect( - metrics.inc.calledWith('chunk_buffer.loadLatest', 1, { - status: 'cache-hit', - }) - ).to.be.true - }) - }) - }) -}) diff --git a/services/history-v1/test/acceptance/js/storage/chunk_store_redis_backend.test.js b/services/history-v1/test/acceptance/js/storage/chunk_store_redis_backend.test.js index 612e802ff1..514ae617cf 100644 --- a/services/history-v1/test/acceptance/js/storage/chunk_store_redis_backend.test.js +++ b/services/history-v1/test/acceptance/js/storage/chunk_store_redis_backend.test.js @@ -2,923 +2,1031 @@ const { expect } = require('chai') const { - Chunk, Snapshot, - History, - File, - AddFileOperation, - Origin, Change, - V2DocVersions, + AddFileOperation, + File, } = require('overleaf-editor-core') const cleanup = require('./support/cleanup') const redisBackend = require('../../../../storage/lib/chunk_store/redis') +const { + JobNotReadyError, + JobNotFoundError, +} = require('../../../../storage/lib/chunk_store/errors') +const redis = require('../../../../storage/lib/redis') +const rclient = redis.rclientHistory +const keySchema = redisBackend.keySchema -describe('chunk store Redis backend', function () { +describe('chunk buffer Redis backend', function () { beforeEach(cleanup.everything) - const projectId = '123456' + const projectId = 'project123' - describe('getCurrentChunk', function () { + describe('getHeadSnapshot', function () { it('should return null on cache miss', async function () { - const chunk = await redisBackend.getCurrentChunk(projectId) - expect(chunk).to.be.null + const result = await redisBackend.getHeadSnapshot(projectId) + expect(result).to.be.null }) - it('should return the cached chunk', async function () { - // Create a sample chunk + it('should return the cached head snapshot and version', async function () { + // Create a sample snapshot and version const snapshot = new Snapshot() - const changes = [ - new Change( - [new AddFileOperation('test.tex', File.fromString('Hello World'))], - new Date(), - [] - ), - ] - const history = new History(snapshot, changes) - const chunk = new Chunk(history, 5) // startVersion 5 + const version = 42 + const rawSnapshot = JSON.stringify(snapshot.toRaw()) - // Cache the chunk - await redisBackend.setCurrentChunk(projectId, chunk) + // Manually set the data in Redis + await rclient.set(keySchema.head({ projectId }), rawSnapshot) + await rclient.set( + keySchema.headVersion({ projectId }), + version.toString() + ) - // Retrieve the cached chunk - const cachedChunk = await redisBackend.getCurrentChunk(projectId) + // Retrieve the cached snapshot + const result = await redisBackend.getHeadSnapshot(projectId) - expect(cachedChunk).to.not.be.null - expect(cachedChunk.getStartVersion()).to.equal(5) - expect(cachedChunk.getEndVersion()).to.equal(6) - expect(cachedChunk).to.deep.equal(chunk) + expect(result).to.not.be.null + expect(result.version).to.equal(version) + expect(result.snapshot).to.deep.equal(snapshot) // Use deep equal for object comparison + }) + + it('should return null if the version is missing', async function () { + // Create a sample snapshot + const snapshot = new Snapshot() + const rawSnapshot = JSON.stringify(snapshot.toRaw()) + + // Manually set only the snapshot data in Redis + await rclient.set(keySchema.head({ projectId }), rawSnapshot) + + // Attempt to retrieve the snapshot + const result = await redisBackend.getHeadSnapshot(projectId) + + expect(result).to.be.null }) }) - describe('setCurrentChunk', function () { - it('should successfully cache a chunk', async function () { - // Create a sample chunk - const snapshot = new Snapshot() - const changes = [ - new Change( - [new AddFileOperation('test.tex', File.fromString('Hello World'))], - new Date(), - [] - ), - ] - const history = new History(snapshot, changes) - const chunk = new Chunk(history, 5) // startVersion 5 + describe('queueChanges', function () { + it('should queue changes when the base version matches head version', async function () { + // Create base version + const baseVersion = 0 - // Cache the chunk - await redisBackend.setCurrentChunk(projectId, chunk) + // Create a new head snapshot that will be set after changes + const headSnapshot = new Snapshot() - // Verify the chunk was cached correctly by retrieving it - const cachedChunk = await redisBackend.getCurrentChunk(projectId) - expect(cachedChunk).to.not.be.null - expect(cachedChunk.getStartVersion()).to.equal(5) - expect(cachedChunk.getEndVersion()).to.equal(6) - expect(cachedChunk).to.deep.equal(chunk) + // Create changes + const timestamp = new Date() + const change = new Change([], timestamp, []) - // Verify that the chunk was stored correctly using the chunk metadata - const chunkMetadata = - await redisBackend.getCurrentChunkMetadata(projectId) - expect(chunkMetadata).to.not.be.null - expect(chunkMetadata.startVersion).to.equal(5) - expect(chunkMetadata.changesCount).to.equal(1) + // Set times + const now = Date.now() + const persistTime = now + 30 * 1000 // 30 seconds from now + const expireTime = now + 60 * 60 * 1000 // 1 hour from now + + // Queue the changes + await redisBackend.queueChanges( + projectId, + headSnapshot, + baseVersion, + [change], + persistTime, + expireTime + ) + + // Get the state to verify the changes + const state = await redisBackend.getState(projectId) + + // Verify the result + expect(state).to.exist + expect(state.headVersion).to.equal(baseVersion + 1) + expect(state.headSnapshot).to.deep.equal(headSnapshot.toRaw()) + expect(state.persistTime).to.equal(persistTime) + expect(state.expireTime).to.equal(expireTime) }) - it('should correctly handle a chunk with zero changes', async function () { - // Create a sample chunk with no changes - const snapshot = new Snapshot() - const changes = [] - const history = new History(snapshot, changes) - const chunk = new Chunk(history, 10) // startVersion 10 + it('should throw BaseVersionConflictError when base version does not match head version', async function () { + // Create a mismatch scenario + const headSnapshot = new Snapshot() + const baseVersion = 0 - // Cache the chunk - await redisBackend.setCurrentChunk(projectId, chunk) + // Manually set a different head version in Redis + await rclient.set(keySchema.headVersion({ projectId }), '5') - // Retrieve the cached chunk - const cachedChunk = await redisBackend.getCurrentChunk(projectId) + // Create changes + const timestamp = new Date() + const change = new Change([], timestamp, []) - expect(cachedChunk).to.not.be.null - expect(cachedChunk.getStartVersion()).to.equal(10) - expect(cachedChunk.getEndVersion()).to.equal(10) // End version should equal start version with no changes - expect(cachedChunk.history.changes.length).to.equal(0) - expect(cachedChunk).to.deep.equal(chunk) + // Set times + const now = Date.now() + const persistTime = now + 30 * 1000 + const expireTime = now + 60 * 60 * 1000 + + // Attempt to queue the changes with a mismatched base version + // This should throw a BaseVersionConflictError + try { + await redisBackend.queueChanges( + projectId, + headSnapshot, + baseVersion, + [change], + persistTime, + expireTime + ) + // If we get here, the test should fail + expect.fail('Expected BaseVersionConflictError but no error was thrown') + } catch (err) { + expect(err.name).to.equal('BaseVersionConflictError') + expect(err.info).to.deep.include({ + projectId, + baseVersion, + }) + } + }) + + it('should throw error when given an empty changes array', async function () { + // Create a valid scenario but with empty changes + const headSnapshot = new Snapshot() + const baseVersion = 0 + + // Set times + const now = Date.now() + const persistTime = now + 30 * 1000 + const expireTime = now + 60 * 60 * 1000 + + // Attempt to queue with empty changes array + try { + await redisBackend.queueChanges( + projectId, + headSnapshot, + baseVersion, + [], // Empty changes array + persistTime, + expireTime + ) + // If we get here, the test should fail + expect.fail('Expected Error but no error was thrown') + } catch (err) { + expect(err.message).to.equal('Cannot queue empty changes array') + } + }) + + it('should queue multiple changes and increment version correctly', async function () { + // Create base version + const baseVersion = 0 + + // Create a new head snapshot + const headSnapshot = new Snapshot() + + // Create multiple changes + const timestamp = new Date() + const change1 = new Change([], timestamp) + const change2 = new Change([], timestamp) + const change3 = new Change([], timestamp) + + // Set times + const now = Date.now() + const persistTime = now + 30 * 1000 + const expireTime = now + 60 * 60 * 1000 + + // Queue the changes + await redisBackend.queueChanges( + projectId, + headSnapshot, + baseVersion, + [change1, change2, change3], // Multiple changes + persistTime, + expireTime + ) + + // Get the state to verify the changes + const state = await redisBackend.getState(projectId) + + // Verify that version was incremented by the number of changes + expect(state.headVersion).to.equal(baseVersion + 3) + expect(state.headSnapshot).to.deep.equal(headSnapshot.toRaw()) + }) + + it('should use the provided persistTime only if it is sooner than existing time', async function () { + // Create base version + const baseVersion = 0 + + // Create a new head snapshot + const headSnapshot = new Snapshot() + + // Create changes + const timestamp = new Date() + const change = new Change([], timestamp) + + // Set times + const now = Date.now() + const earlierPersistTime = now + 15 * 1000 // 15 seconds from now + const laterPersistTime = now + 30 * 1000 // 30 seconds from now + const expireTime = now + 60 * 60 * 1000 // 1 hour from now + + // First queue changes with the later persist time + await redisBackend.queueChanges( + projectId, + headSnapshot, + baseVersion, + [change], + laterPersistTime, + expireTime + ) + + // Get the state to verify the first persist time was set + let state = await redisBackend.getState(projectId) + expect(state.persistTime).to.equal(laterPersistTime) + + // Queue more changes with an earlier persist time + const newerHeadSnapshot = new Snapshot() + await redisBackend.queueChanges( + projectId, + newerHeadSnapshot, + baseVersion + 1, // Updated base version + [change], + earlierPersistTime, // Earlier time should replace the later one + expireTime + ) + + // Get the state to verify the persist time was updated to the earlier time + state = await redisBackend.getState(projectId) + expect(state.persistTime).to.equal(earlierPersistTime) + + // Queue more changes with another later persist time + const evenNewerHeadSnapshot = new Snapshot() + await redisBackend.queueChanges( + projectId, + evenNewerHeadSnapshot, + baseVersion + 2, // Updated base version + [change], + laterPersistTime, // Later time should not replace the earlier one + expireTime + ) + + // Get the state to verify the persist time remains at the earlier time + state = await redisBackend.getState(projectId) + expect(state.persistTime).to.equal(earlierPersistTime) // Should still be the earlier time }) }) - describe('updating already cached chunks', function () { - it('should replace a chunk with a longer chunk', async function () { - // Set initial chunk with one change - const snapshotA = new Snapshot() - const changesA = [ - new Change( - [ - new AddFileOperation( - 'test.tex', - File.fromString('Initial content') - ), - ], - new Date(), - [] - ), - ] - const historyA = new History(snapshotA, changesA) - const chunkA = new Chunk(historyA, 10) - - await redisBackend.setCurrentChunk(projectId, chunkA) - - // Verify the initial chunk was cached - const cachedChunkA = await redisBackend.getCurrentChunk(projectId) - expect(cachedChunkA.getStartVersion()).to.equal(10) - expect(cachedChunkA.getEndVersion()).to.equal(11) - expect(cachedChunkA.history.changes.length).to.equal(1) - - // Create a longer chunk (with more changes) - const snapshotB = new Snapshot() - const changesB = [ - new Change( - [new AddFileOperation('test1.tex', File.fromString('Content 1'))], - new Date(), - [] - ), - new Change( - [new AddFileOperation('test2.tex', File.fromString('Content 2'))], - new Date(), - [] - ), - new Change( - [new AddFileOperation('test3.tex', File.fromString('Content 3'))], - new Date(), - [] - ), - ] - const historyB = new History(snapshotB, changesB) - const chunkB = new Chunk(historyB, 15) - - // Replace the cached chunk - await redisBackend.setCurrentChunk(projectId, chunkB) - - // Verify the new chunk replaced the old one - const cachedChunkB = await redisBackend.getCurrentChunk(projectId) - expect(cachedChunkB).to.not.be.null - expect(cachedChunkB.getStartVersion()).to.equal(15) - expect(cachedChunkB.getEndVersion()).to.equal(18) - expect(cachedChunkB.history.changes.length).to.equal(3) - expect(cachedChunkB).to.deep.equal(chunkB) - - // Verify the metadata was updated - const updatedMetadata = - await redisBackend.getCurrentChunkMetadata(projectId) - expect(updatedMetadata.startVersion).to.equal(15) - expect(updatedMetadata.changesCount).to.equal(3) + describe('getChangesSinceVersion', function () { + it('should return not_found when project does not exist', async function () { + const result = await redisBackend.getChangesSinceVersion(projectId, 1) + expect(result.status).to.equal('not_found') }) - it('should replace a chunk with a shorter chunk', async function () { - // Set initial chunk with three changes - const snapshotA = new Snapshot() - const changesA = [ - new Change( - [new AddFileOperation('file1.tex', File.fromString('Content 1'))], - new Date(), - [] - ), - new Change( - [new AddFileOperation('file2.tex', File.fromString('Content 2'))], - new Date(), - [] - ), - new Change( - [new AddFileOperation('file3.tex', File.fromString('Content 3'))], - new Date(), - [] - ), - ] - const historyA = new History(snapshotA, changesA) - const chunkA = new Chunk(historyA, 20) + it('should return empty array when requested version equals head version', async function () { + // Set head version + const headVersion = 5 + await rclient.set( + keySchema.headVersion({ projectId }), + headVersion.toString() + ) - await redisBackend.setCurrentChunk(projectId, chunkA) + // Request changes since the current head version + const result = await redisBackend.getChangesSinceVersion( + projectId, + headVersion + ) - // Verify the initial chunk was cached - const cachedChunkA = await redisBackend.getCurrentChunk(projectId) - expect(cachedChunkA.getStartVersion()).to.equal(20) - expect(cachedChunkA.getEndVersion()).to.equal(23) - expect(cachedChunkA.history.changes.length).to.equal(3) - - // Create a shorter chunk (with fewer changes) - const snapshotB = new Snapshot() - const changesB = [ - new Change( - [new AddFileOperation('new.tex', File.fromString('New content'))], - new Date(), - [] - ), - ] - const historyB = new History(snapshotB, changesB) - const chunkB = new Chunk(historyB, 30) - - // Replace the cached chunk - await redisBackend.setCurrentChunk(projectId, chunkB) - - // Verify the new chunk replaced the old one - const cachedChunkB = await redisBackend.getCurrentChunk(projectId) - expect(cachedChunkB).to.not.be.null - expect(cachedChunkB.getStartVersion()).to.equal(30) - expect(cachedChunkB.getEndVersion()).to.equal(31) - expect(cachedChunkB.history.changes.length).to.equal(1) - expect(cachedChunkB).to.deep.equal(chunkB) - - // Verify the metadata was updated - const updatedMetadata = - await redisBackend.getCurrentChunkMetadata(projectId) - expect(updatedMetadata.startVersion).to.equal(30) - expect(updatedMetadata.changesCount).to.equal(1) + expect(result.status).to.equal('ok') + expect(result.changes).to.be.an('array').that.is.empty }) - it('should replace a chunk with a zero-length chunk', async function () { - // Set initial chunk with changes - const snapshotA = new Snapshot() - const changesA = [ - new Change( - [new AddFileOperation('file1.tex', File.fromString('Content 1'))], - new Date(), - [] - ), - new Change( - [new AddFileOperation('file2.tex', File.fromString('Content 2'))], - new Date(), - [] - ), - ] - const historyA = new History(snapshotA, changesA) - const chunkA = new Chunk(historyA, 25) + it('should return out_of_bounds when requested version is greater than head version', async function () { + // Set head version + const headVersion = 5 + await rclient.set( + keySchema.headVersion({ projectId }), + headVersion.toString() + ) - await redisBackend.setCurrentChunk(projectId, chunkA) + // Request changes with version larger than head + const result = await redisBackend.getChangesSinceVersion( + projectId, + headVersion + 1 + ) - // Verify the initial chunk was cached - const cachedChunkA = await redisBackend.getCurrentChunk(projectId) - expect(cachedChunkA.getStartVersion()).to.equal(25) - expect(cachedChunkA.getEndVersion()).to.equal(27) - expect(cachedChunkA.history.changes.length).to.equal(2) - - // Create a zero-length chunk (with no changes) - const snapshotB = new Snapshot() - const changesB = [] - const historyB = new History(snapshotB, changesB) - const chunkB = new Chunk(historyB, 40) - - // Replace the cached chunk - await redisBackend.setCurrentChunk(projectId, chunkB) - - // Verify the new chunk replaced the old one - const cachedChunkB = await redisBackend.getCurrentChunk(projectId) - expect(cachedChunkB).to.not.be.null - expect(cachedChunkB.getStartVersion()).to.equal(40) - expect(cachedChunkB.getEndVersion()).to.equal(40) // Start version equals end version with no changes - expect(cachedChunkB.history.changes.length).to.equal(0) - expect(cachedChunkB).to.deep.equal(chunkB) - - // Verify the metadata was updated - const updatedMetadata = - await redisBackend.getCurrentChunkMetadata(projectId) - expect(updatedMetadata.startVersion).to.equal(40) - expect(updatedMetadata.changesCount).to.equal(0) + expect(result.status).to.equal('out_of_bounds') }) - it('should replace a zero-length chunk with a non-empty chunk', async function () { - // Set initial empty chunk - const snapshotA = new Snapshot() - const changesA = [] - const historyA = new History(snapshotA, changesA) - const chunkA = new Chunk(historyA, 50) + it('should return out_of_bounds when requested version is too old', async function () { + // Set head version + const headVersion = 10 + await rclient.set( + keySchema.headVersion({ projectId }), + headVersion.toString() + ) - await redisBackend.setCurrentChunk(projectId, chunkA) + // Create a few changes but less than what we'd need to reach requested version + const timestamp = new Date() + const change1 = new Change([], timestamp) + const change2 = new Change([], timestamp) + await rclient.rpush( + keySchema.changes({ projectId }), + JSON.stringify(change1.toRaw()), + JSON.stringify(change2.toRaw()) + ) - // Verify the initial chunk was cached - const cachedChunkA = await redisBackend.getCurrentChunk(projectId) - expect(cachedChunkA.getStartVersion()).to.equal(50) - expect(cachedChunkA.getEndVersion()).to.equal(50) - expect(cachedChunkA.history.changes.length).to.equal(0) + // Request changes from version 5, which is too old (headVersion - changesCount = 10 - 2 = 8) + const result = await redisBackend.getChangesSinceVersion(projectId, 5) - // Create a non-empty chunk - const snapshotB = new Snapshot() - const changesB = [ - new Change( - [new AddFileOperation('newfile.tex', File.fromString('New content'))], - new Date(), - [] - ), - new Change( - [ - new AddFileOperation( - 'another.tex', - File.fromString('Another file') - ), - ], - new Date(), - [] - ), - ] - const historyB = new History(snapshotB, changesB) - const chunkB = new Chunk(historyB, 60) + expect(result.status).to.equal('out_of_bounds') + }) - // Replace the cached chunk - await redisBackend.setCurrentChunk(projectId, chunkB) + it('should return changes since requested version', async function () { + // Set head version + const headVersion = 5 + await rclient.set( + keySchema.headVersion({ projectId }), + headVersion.toString() + ) - // Verify the new chunk replaced the old one - const cachedChunkB = await redisBackend.getCurrentChunk(projectId) - expect(cachedChunkB).to.not.be.null - expect(cachedChunkB.getStartVersion()).to.equal(60) - expect(cachedChunkB.getEndVersion()).to.equal(62) - expect(cachedChunkB.history.changes.length).to.equal(2) - expect(cachedChunkB).to.deep.equal(chunkB) + // Create changes + const timestamp = new Date() + const change1 = new Change([], timestamp) + const change2 = new Change([], timestamp) + const change3 = new Change([], timestamp) - // Verify the metadata was updated - const updatedMetadata = - await redisBackend.getCurrentChunkMetadata(projectId) - expect(updatedMetadata.startVersion).to.equal(60) - expect(updatedMetadata.changesCount).to.equal(2) + // Push changes to Redis (representing versions 3, 4, and 5) + await rclient.rpush( + keySchema.changes({ projectId }), + JSON.stringify(change1.toRaw()), + JSON.stringify(change2.toRaw()), + JSON.stringify(change3.toRaw()) + ) + + // Request changes since version 3 (should return changes for versions 4 and 5) + const result = await redisBackend.getChangesSinceVersion(projectId, 3) + + expect(result.status).to.equal('ok') + expect(result.changes).to.be.an('array').with.lengthOf(2) + + // The changes array should contain the raw changes + // Note: We're comparing raw objects, not the Change instances + expect(result.changes[0]).to.deep.equal(change2.toRaw()) + expect(result.changes[1]).to.deep.equal(change3.toRaw()) + }) + + it('should return all changes when requested version is earliest available', async function () { + // Set head version to 5 + const headVersion = 5 + await rclient.set( + keySchema.headVersion({ projectId }), + headVersion.toString() + ) + + // Create changes + const timestamp = new Date() + const change1 = new Change([], timestamp) + const change2 = new Change([], timestamp) + const change3 = new Change([], timestamp) + + // Push changes to Redis (representing versions 3, 4, and 5) + await rclient.rpush( + keySchema.changes({ projectId }), + JSON.stringify(change1.toRaw()), + JSON.stringify(change2.toRaw()), + JSON.stringify(change3.toRaw()) + ) + + // Request changes since version 2 (should return all 3 changes) + const result = await redisBackend.getChangesSinceVersion(projectId, 2) + + expect(result.status).to.equal('ok') + expect(result.changes).to.be.an('array').with.lengthOf(3) + expect(result.changes[0]).to.deep.equal(change1.toRaw()) + expect(result.changes[1]).to.deep.equal(change2.toRaw()) + expect(result.changes[2]).to.deep.equal(change3.toRaw()) }) }) - describe('checkCacheValidity', function () { - it('should return true when versions match', function () { - const snapshotA = new Snapshot() - const historyA = new History(snapshotA, []) - const chunkA = new Chunk(historyA, 10) - chunkA.pushChanges([ - new Change( - [new AddFileOperation('test.tex', File.fromString('Hello'))], - new Date(), - [] - ), - ]) - - const snapshotB = new Snapshot() - const historyB = new History(snapshotB, []) - const chunkB = new Chunk(historyB, 10) - chunkB.pushChanges([ - new Change( - [new AddFileOperation('test.tex', File.fromString('Hello'))], - new Date(), - [] - ), - ]) - - const isValid = redisBackend.checkCacheValidity(chunkA, chunkB) - expect(isValid).to.be.true + describe('getNonPersistedChanges', function () { + it('should return empty array when project does not exist', async function () { + const changes = await redisBackend.getNonPersistedChanges(projectId) + expect(changes).to.be.an('array').that.is.empty }) - it('should return false when start versions differ', function () { - const snapshotA = new Snapshot() - const historyA = new History(snapshotA, []) - const chunkA = new Chunk(historyA, 10) + it('should return all changes when persisted version is not set', async function () { + const changes = [makeChange(), makeChange(), makeChange()] + queueChanges(projectId, changes) - const snapshotB = new Snapshot() - const historyB = new History(snapshotB, []) - const chunkB = new Chunk(historyB, 11) - - const isValid = redisBackend.checkCacheValidity(chunkA, chunkB) - expect(isValid).to.be.false + const nonPersistedChanges = + await redisBackend.getNonPersistedChanges(projectId) + expect(nonPersistedChanges.map(change => change.toRaw())).to.deep.equal( + changes.map(change => change.toRaw()) + ) }) - it('should return false when end versions differ', function () { - const snapshotA = new Snapshot() - const historyA = new History(snapshotA, []) - const chunkA = new Chunk(historyA, 10) - chunkA.pushChanges([ - new Change( - [new AddFileOperation('test.tex', File.fromString('Hello'))], - new Date(), - [] - ), - ]) + it('should return empty array when persisted version equals head version', async function () { + // Set both head and persisted versions to be equal + const version = 5 + await rclient.set( + keySchema.headVersion({ projectId }), + version.toString() + ) + await rclient.set( + keySchema.persistedVersion({ projectId }), + version.toString() + ) - const snapshotB = new Snapshot() - const historyB = new History(snapshotB, []) - const chunkB = new Chunk(historyB, 10) - chunkB.pushChanges([ - new Change( - [new AddFileOperation('test.tex', File.fromString('Hello'))], - new Date(), - [] - ), - new Change( - [new AddFileOperation('other.tex', File.fromString('World'))], - new Date(), - [] - ), - ]) - - const isValid = redisBackend.checkCacheValidity(chunkA, chunkB) - expect(isValid).to.be.false + const changes = await redisBackend.getNonPersistedChanges(projectId) + expect(changes).to.be.an('array').that.is.empty }) - it('should return false when cached chunk is null', function () { - const snapshotB = new Snapshot() - const historyB = new History(snapshotB, []) - const chunkB = new Chunk(historyB, 10) + it('should return all non-persisted changes', async function () { + // Set head version to 5 and persisted version to 2 + const headVersion = 5 + const persistedVersion = 2 + await rclient.set( + keySchema.headVersion({ projectId }), + headVersion.toString() + ) + await rclient.set( + keySchema.persistedVersion({ projectId }), + persistedVersion.toString() + ) - const isValid = redisBackend.checkCacheValidity(null, chunkB) - expect(isValid).to.be.false + // Create changes for versions 3, 4, 5 + const timestamp = new Date() + const change1 = new Change([], timestamp) // Version 3 + const change2 = new Change([], timestamp) // Version 4 + const change3 = new Change([], timestamp) // Version 5 + + // Push changes to Redis + await rclient.rpush( + keySchema.changes({ projectId }), + JSON.stringify(change1.toRaw()), + JSON.stringify(change2.toRaw()), + JSON.stringify(change3.toRaw()) + ) + + // Get non-persisted changes + const nonPersistedChanges = + await redisBackend.getNonPersistedChanges(projectId) + + // Should return changes for versions 3, 4, 5 + expect(nonPersistedChanges).to.be.an('array').with.lengthOf(3) + expect(nonPersistedChanges[0].toRaw()).to.deep.equal(change1.toRaw()) + expect(nonPersistedChanges[1].toRaw()).to.deep.equal(change2.toRaw()) + expect(nonPersistedChanges[2].toRaw()).to.deep.equal(change3.toRaw()) + }) + + it('should return a subset of changes when some are persisted', async function () { + // Set head version to 5 and persisted version to 3 + // This means versions 4 and 5 are not persisted + const headVersion = 5 + const persistedVersion = 3 + await rclient.set( + keySchema.headVersion({ projectId }), + headVersion.toString() + ) + await rclient.set( + keySchema.persistedVersion({ projectId }), + persistedVersion.toString() + ) + + // Create changes for versions 1, 2, 3, 4, 5 + const timestamp = new Date() + const change1 = new Change([], timestamp) // Version 1 + const change2 = new Change([], timestamp) // Version 2 + const change3 = new Change([], timestamp) // Version 3 + const change4 = new Change([], timestamp) // Version 4 + const change5 = new Change([], timestamp) // Version 5 + + // Push changes to Redis + await rclient.rpush( + keySchema.changes({ projectId }), + JSON.stringify(change1.toRaw()), + JSON.stringify(change2.toRaw()), + JSON.stringify(change3.toRaw()), + JSON.stringify(change4.toRaw()), + JSON.stringify(change5.toRaw()) + ) + + // Get non-persisted changes + const nonPersistedChanges = + await redisBackend.getNonPersistedChanges(projectId) + + // Should return only changes for versions 4 and 5 + expect(nonPersistedChanges).to.be.an('array').with.lengthOf(2) + expect(nonPersistedChanges[0].toRaw()).to.deep.equal(change4.toRaw()) + expect(nonPersistedChanges[1].toRaw()).to.deep.equal(change5.toRaw()) + }) + + it('should throw an error when persisted version is higher than head version', async function () { + // This is an unusual case that should not happen in practice + // The system should throw an error to indicate this abnormal state + const headVersion = 3 + const persistedVersion = 5 + await rclient.set( + keySchema.headVersion({ projectId }), + headVersion.toString() + ) + await rclient.set( + keySchema.persistedVersion({ projectId }), + persistedVersion.toString() + ) + + // Create changes + const timestamp = new Date() + const change1 = new Change([], timestamp) + const change2 = new Change([], timestamp) + const change3 = new Change([], timestamp) + + // Push changes to Redis + await rclient.rpush( + keySchema.changes({ projectId }), + JSON.stringify(change1.toRaw()), + JSON.stringify(change2.toRaw()), + JSON.stringify(change3.toRaw()) + ) + + // Use chai-as-promised for cleaner async error assertion + await expect( + redisBackend.getNonPersistedChanges(projectId) + ).to.be.rejectedWith(/HEAD_VERSION_BEHIND_PERSISTED_VERSION/) + }) + + it('should handle case where persisted version is before start of changes list', async function () { + // Setup: head version is 5, persisted version is 1 + // But changes list only starts from version 3 + const headVersion = 5 + const persistedVersion = 1 + await rclient.set( + keySchema.headVersion({ projectId }), + headVersion.toString() + ) + await rclient.set( + keySchema.persistedVersion({ projectId }), + persistedVersion.toString() + ) + + // Create changes for versions 3, 4, 5 only + const timestamp = new Date() + const change3 = new Change([], timestamp) // Version 3 + const change4 = new Change([], timestamp) // Version 4 + const change5 = new Change([], timestamp) // Version 5 + + // Push changes to Redis + await rclient.rpush( + keySchema.changes({ projectId }), + JSON.stringify(change3.toRaw()), + JSON.stringify(change4.toRaw()), + JSON.stringify(change5.toRaw()) + ) + + // Get non-persisted changes + const nonPersistedChanges = + await redisBackend.getNonPersistedChanges(projectId) + + // Should return all changes since the persisted version is before the start of the list + expect(nonPersistedChanges).to.be.an('array').with.lengthOf(3) + expect(nonPersistedChanges[0].toRaw()).to.deep.equal(change3.toRaw()) + expect(nonPersistedChanges[1].toRaw()).to.deep.equal(change4.toRaw()) + expect(nonPersistedChanges[2].toRaw()).to.deep.equal(change5.toRaw()) }) }) - describe('compareChunks', function () { - it('should return true when chunks are identical', function () { - // Create two identical chunks - const snapshot = new Snapshot() - const changes = [ - new Change( - [new AddFileOperation('test.tex', File.fromString('Hello World'))], - new Date('2025-04-10T12:00:00Z'), // Using fixed date for consistent comparison - [] - ), - ] - const history1 = new History(snapshot, changes) - const chunk1 = new Chunk(history1, 5) - - // Create a separate but identical chunk - const snapshot2 = new Snapshot() - const changes2 = [ - new Change( - [new AddFileOperation('test.tex', File.fromString('Hello World'))], - new Date('2025-04-10T12:00:00Z'), // Using same fixed date - [] - ), - ] - const history2 = new History(snapshot2, changes2) - const chunk2 = new Chunk(history2, 5) - - const result = redisBackend.compareChunks(projectId, chunk1, chunk2) - expect(result).to.be.true + describe('setPersistedVersion', function () { + it('should return not_found when project does not exist', async function () { + const result = await redisBackend.setPersistedVersion(projectId, 5) + expect(result).to.equal('not_found') }) - it('should return false when chunks differ', function () { - // Create first chunk - const snapshot1 = new Snapshot() - const changes1 = [ - new Change( - [new AddFileOperation('test.tex', File.fromString('Hello World'))], - new Date('2025-04-10T12:00:00Z'), - [] - ), - ] - const history1 = new History(snapshot1, changes1) - const chunk1 = new Chunk(history1, 5) - - // Create a different chunk (different content) - const snapshot2 = new Snapshot() - const changes2 = [ - new Change( - [ - new AddFileOperation( - 'test.tex', - File.fromString('Different content') - ), - ], - new Date('2025-04-10T12:00:00Z'), - [] - ), - ] - const history2 = new History(snapshot2, changes2) - const chunk2 = new Chunk(history2, 5) - - const result = redisBackend.compareChunks(projectId, chunk1, chunk2) - expect(result).to.be.false - }) - - it('should return false when one chunk is null', function () { - // Create a chunk - const snapshot = new Snapshot() - const changes = [ - new Change( - [new AddFileOperation('test.tex', File.fromString('Hello World'))], - new Date('2025-04-10T12:00:00Z'), - [] - ), - ] - const history = new History(snapshot, changes) - const chunk = new Chunk(history, 5) - - const resultWithNullCached = redisBackend.compareChunks( - projectId, - null, - chunk + it('should set the persisted version', async function () { + // Set head version + const headVersion = 5 + await rclient.set( + keySchema.headVersion({ projectId }), + headVersion.toString() ) - expect(resultWithNullCached).to.be.false - const resultWithNullCurrent = redisBackend.compareChunks( + // Set persisted version + const persistedVersion = 3 + const result = await redisBackend.setPersistedVersion( projectId, - chunk, - null + persistedVersion ) - expect(resultWithNullCurrent).to.be.false + + expect(result).to.equal('ok') + + // Verify the persisted version was set + const persistedVersionRedis = await rclient.get( + keySchema.persistedVersion({ projectId }) + ) + expect(parseInt(persistedVersionRedis, 10)).to.equal(persistedVersion) }) - it('should return false when chunks have different start versions', function () { - // Create first chunk with start version 5 - const snapshot1 = new Snapshot() - const changes1 = [ - new Change( - [new AddFileOperation('test.tex', File.fromString('Hello World'))], - new Date('2025-04-10T12:00:00Z'), - [] - ), - ] - const history1 = new History(snapshot1, changes1) - const chunk1 = new Chunk(history1, 5) + it('should trim the changes list to keep only MAX_PERSISTED_CHANGES beyond persisted version', async function () { + // Get MAX_PERSISTED_CHANGES to ensure our test data is larger + const maxPersistedChanges = redisBackend.MAX_PERSISTED_CHANGES - // Create second chunk with identical content but different start version (10) - const snapshot2 = new Snapshot() - const changes2 = [ - new Change( - [new AddFileOperation('test.tex', File.fromString('Hello World'))], - new Date('2025-04-10T12:00:00Z'), - [] - ), - ] - const history2 = new History(snapshot2, changes2) - const chunk2 = new Chunk(history2, 10) + // Create a larger number of changes for the test + // Using MAX_PERSISTED_CHANGES + 10 to ensure we have enough changes to trigger trimming + const totalChanges = maxPersistedChanges + 10 - const result = redisBackend.compareChunks(projectId, chunk1, chunk2) - expect(result).to.be.false + // Set head version to match total number of changes + const headVersion = totalChanges + await rclient.set( + keySchema.headVersion({ projectId }), + headVersion.toString() + ) + + // Create changes for versions 1 through totalChanges + const timestamp = new Date() + const changes = Array.from( + { length: totalChanges }, + (_, idx) => + new Change( + [new AddFileOperation(`file${idx}.tex`, File.fromString('hello'))], + timestamp + ) + ) + + // Push changes to Redis + await rclient.rpush( + keySchema.changes({ projectId }), + ...changes.map(change => JSON.stringify(change.toRaw())) + ) + + // Set persisted version to somewhere near the head version + const persistedVersion = headVersion - 5 + + // Set the persisted version + const result = await redisBackend.setPersistedVersion( + projectId, + persistedVersion + ) + expect(result).to.equal('ok') + + // Get all changes that remain in Redis + const remainingChanges = await rclient.lrange( + keySchema.changes({ projectId }), + 0, + -1 + ) + + // Calculate the expected number of changes to remain + expect(remainingChanges).to.have.lengthOf( + maxPersistedChanges + (headVersion - persistedVersion) + ) + + // Check that remaining changes are the expected ones + const expectedChanges = changes.slice( + persistedVersion - maxPersistedChanges, + totalChanges + ) + expect(remainingChanges).to.deep.equal( + expectedChanges.map(change => JSON.stringify(change.toRaw())) + ) + }) + + it('should keep all changes when there are fewer than MAX_PERSISTED_CHANGES', async function () { + // Set head version to 5 + const headVersion = 5 + await rclient.set( + keySchema.headVersion({ projectId }), + headVersion.toString() + ) + + // Create changes for versions 1 through 5 + const timestamp = new Date() + const changes = Array.from({ length: 5 }, () => new Change([], timestamp)) + + // Push changes to Redis + await rclient.rpush( + keySchema.changes({ projectId }), + ...changes.map(change => JSON.stringify(change.toRaw())) + ) + + // Set persisted version to 3 + // All changes should remain since total count is small + const persistedVersion = 3 + + // Ensure MAX_PERSISTED_CHANGES is larger than our test dataset + expect(redisBackend.MAX_PERSISTED_CHANGES).to.be.greaterThan( + 5, + 'MAX_PERSISTED_CHANGES should be greater than 5 for this test' + ) + + // Set the persisted version + const result = await redisBackend.setPersistedVersion( + projectId, + persistedVersion + ) + expect(result).to.equal('ok') + + // Get all changes that remain in Redis + const remainingChanges = await rclient.lrange( + keySchema.changes({ projectId }), + 0, + -1 + ) + + // All changes should remain + expect(remainingChanges).to.have.lengthOf(5) }) }) - describe('integration with redis', function () { - it('should store and retrieve complex chunks correctly', async function () { - // Create a more complex chunk + describe('getState', function () { + it('should return complete project state from Redis', async function () { + // Set up the test data in Redis const snapshot = new Snapshot() - const changes = [ - new Change( - [new AddFileOperation('file1.tex', File.fromString('Content 1'))], - new Date(), - [1234] - ), - new Change( - [new AddFileOperation('file2.tex', File.fromString('Content 2'))], - new Date(), - null, - new Origin('test-origin'), - ['5a296963ad5e82432674c839', null], - '123.4', - new V2DocVersions({ - 'random-doc-id': { pathname: 'file2.tex', v: 123 }, - }) - ), - new Change( - [new AddFileOperation('file3.tex', File.fromString('Content 3'))], - new Date(), - [] - ), - ] - const history = new History(snapshot, changes) - const chunk = new Chunk(history, 20) + const rawSnapshot = JSON.stringify(snapshot.toRaw()) + const headVersion = 42 + const persistedVersion = 40 + const now = Date.now() + const expireTime = now + 60 * 60 * 1000 // 1 hour from now + const persistTime = now + 30 * 1000 // 30 seconds from now - // Cache the chunk - await redisBackend.setCurrentChunk(projectId, chunk) + // Create a change + const timestamp = new Date() + const change = new Change([], timestamp) + const serializedChange = JSON.stringify(change.toRaw()) - // Retrieve the cached chunk - const cachedChunk = await redisBackend.getCurrentChunk(projectId) - - expect(cachedChunk.getStartVersion()).to.equal(20) - expect(cachedChunk.getEndVersion()).to.equal(23) - expect(cachedChunk).to.deep.equal(chunk) - expect(cachedChunk.history.changes.length).to.equal(3) - - // Check that the operations were preserved correctly - const retrievedChanges = cachedChunk.history.changes - expect(retrievedChanges[0].getOperations()[0].getPathname()).to.equal( - 'file1.tex' + // Set everything in Redis + await rclient.set(keySchema.head({ projectId }), rawSnapshot) + await rclient.set( + keySchema.headVersion({ projectId }), + headVersion.toString() ) - expect(retrievedChanges[1].getOperations()[0].getPathname()).to.equal( - 'file2.tex' + await rclient.set( + keySchema.persistedVersion({ projectId }), + persistedVersion.toString() ) - expect(retrievedChanges[2].getOperations()[0].getPathname()).to.equal( - 'file3.tex' + await rclient.set( + keySchema.expireTime({ projectId }), + expireTime.toString() + ) + await rclient.set( + keySchema.persistTime({ projectId }), + persistTime.toString() + ) + await rclient.rpush(keySchema.changes({ projectId }), serializedChange) + + // Get the state + const state = await redisBackend.getState(projectId) + + // Verify everything matches + expect(state).to.exist + expect(state.headSnapshot).to.deep.equal(snapshot.toRaw()) + expect(state.headVersion).to.equal(headVersion) + expect(state.persistedVersion).to.equal(persistedVersion) + expect(state.expireTime).to.equal(expireTime) + expect(state.persistTime).to.equal(persistTime) + }) + + it('should return proper defaults for missing fields', async function () { + // Only set the head snapshot and version, leave others unset + const snapshot = new Snapshot() + const rawSnapshot = JSON.stringify(snapshot.toRaw()) + const headVersion = 42 + + await rclient.set(keySchema.head({ projectId }), rawSnapshot) + await rclient.set( + keySchema.headVersion({ projectId }), + headVersion.toString() ) - // Check that the chunk was stored correctly using the chunk metadata - const chunkMetadata = - await redisBackend.getCurrentChunkMetadata(projectId) - expect(chunkMetadata).to.not.be.null - expect(chunkMetadata.startVersion).to.equal(20) - expect(chunkMetadata.changesCount).to.equal(3) + // Get the state + const state = await redisBackend.getState(projectId) + + // Verify only what we set exists, and other fields have correct defaults + expect(state).to.exist + expect(state.headSnapshot).to.deep.equal(snapshot.toRaw()) + expect(state.headVersion).to.equal(headVersion) + expect(state.persistedVersion).to.be.null + expect(state.expireTime).to.be.null + expect(state.persistTime).to.be.null }) }) - describe('getCurrentChunkIfValid', function () { - it('should return the chunk when versions and changes count match', async function () { - // Create and cache a sample chunk - const snapshot = new Snapshot() - const changes = [ - new Change( - [new AddFileOperation('test.tex', File.fromString('Valid content'))], - new Date(), - [] - ), - ] - const history = new History(snapshot, changes) - const chunk = new Chunk(history, 7) // startVersion 7, endVersion 8 - await redisBackend.setCurrentChunk(projectId, chunk) + describe('setExpireTime', function () { + it('should set the expire time on an active project', async function () { + // Load a fake project in Redis + const change = makeChange() + await queueChanges(projectId, [change], { expireTime: 123 }) - // Prepare chunkRecord matching the cached chunk - const chunkRecord = { startVersion: 7, endVersion: 8 } + // Check that the right expire time was recorded + let state = await redisBackend.getState(projectId) + expect(state.expireTime).to.equal(123) - // Retrieve using getCurrentChunkIfValid - const validChunk = await redisBackend.getCurrentChunkIfValid( - projectId, - chunkRecord - ) - - expect(validChunk).to.not.be.null - expect(validChunk.getStartVersion()).to.equal(7) - expect(validChunk.getEndVersion()).to.equal(8) - expect(validChunk).to.deep.equal(chunk) + // Set the expire time to something else + await redisBackend.setExpireTime(projectId, 456) + state = await redisBackend.getState(projectId) + expect(state.expireTime).to.equal(456) }) - it('should return null when no chunk is cached', async function () { - // No chunk is cached for this projectId yet - const chunkRecord = { startVersion: 1, endVersion: 2 } - const validChunk = await redisBackend.getCurrentChunkIfValid( - projectId, - chunkRecord - ) - expect(validChunk).to.be.null - }) + it('should not set an expire time on an inactive project', async function () { + let state = await redisBackend.getState(projectId) + expect(state.expireTime).to.be.null - it('should return null when start version mismatches', async function () { - // Cache a chunk with startVersion 10 - const snapshot = new Snapshot() - const changes = [ - new Change( - [new AddFileOperation('test.tex', File.fromString('Content'))], - new Date(), - [] - ), - ] - const history = new History(snapshot, changes) - const chunk = new Chunk(history, 10) // startVersion 10, endVersion 11 - await redisBackend.setCurrentChunk(projectId, chunk) - - // Attempt to retrieve with a different startVersion - const chunkRecord = { startVersion: 9, endVersion: 10 } // Incorrect startVersion - const validChunk = await redisBackend.getCurrentChunkIfValid( - projectId, - chunkRecord - ) - expect(validChunk).to.be.null - }) - - it('should return null when changes count mismatches', async function () { - // Cache a chunk with one change (startVersion 15, endVersion 16) - const snapshot = new Snapshot() - const changes = [ - new Change( - [new AddFileOperation('test.tex', File.fromString('Content'))], - new Date(), - [] - ), - ] - const history = new History(snapshot, changes) - const chunk = new Chunk(history, 15) - await redisBackend.setCurrentChunk(projectId, chunk) - - // Attempt to retrieve with correct startVersion but incorrect endVersion (implying wrong changes count) - const chunkRecord = { startVersion: 15, endVersion: 17 } // Incorrect endVersion (implies 2 changes) - const validChunk = await redisBackend.getCurrentChunkIfValid( - projectId, - chunkRecord - ) - expect(validChunk).to.be.null - }) - - it('should return the chunk when versions and changes count match for a zero-change chunk', async function () { - // Cache a chunk with zero changes - const snapshot = new Snapshot() - const changes = [] - const history = new History(snapshot, changes) - const chunk = new Chunk(history, 20) // startVersion 20, endVersion 20 - await redisBackend.setCurrentChunk(projectId, chunk) - - // Prepare chunkRecord matching the zero-change chunk - const chunkRecord = { startVersion: 20, endVersion: 20 } - - // Retrieve using getCurrentChunkIfValid - const validChunk = await redisBackend.getCurrentChunkIfValid( - projectId, - chunkRecord - ) - - expect(validChunk).to.not.be.null - expect(validChunk.getStartVersion()).to.equal(20) - expect(validChunk.getEndVersion()).to.equal(20) - expect(validChunk.history.changes.length).to.equal(0) - expect(validChunk).to.deep.equal(chunk) - }) - - it('should return null when start version matches but changes count is wrong for zero-change chunk', async function () { - // Cache a chunk with zero changes - const snapshot = new Snapshot() - const changes = [] - const history = new History(snapshot, changes) - const chunk = new Chunk(history, 25) // startVersion 25, endVersion 25 - await redisBackend.setCurrentChunk(projectId, chunk) - - // Attempt to retrieve with correct startVersion but incorrect endVersion - const chunkRecord = { startVersion: 25, endVersion: 26 } // Incorrect endVersion (implies 1 change) - const validChunk = await redisBackend.getCurrentChunkIfValid( - projectId, - chunkRecord - ) - expect(validChunk).to.be.null + await redisBackend.setExpireTime(projectId, 456) + state = await redisBackend.getState(projectId) + expect(state.expireTime).to.be.null }) }) - describe('getCurrentChunkMetadata', function () { - it('should return metadata for a cached chunk', async function () { - // Cache a chunk - const snapshot = new Snapshot() - const history = new History(snapshot, [ - new Change( - [new AddFileOperation('test.tex', File.fromString('Hello'))], - new Date(), - [] - ), - new Change( - [new AddFileOperation('other.tex', File.fromString('Bonjour'))], - new Date(), - [] - ), - ]) - const chunk = new Chunk(history, 10) - await redisBackend.setCurrentChunk(projectId, chunk) + describe('expireProject', function () { + it('should expire a persisted project', async function () { + // Load and persist a project in Redis + const change = makeChange() + await queueChanges(projectId, [change]) + await redisBackend.setPersistedVersion(projectId, 1) - const metadata = await redisBackend.getCurrentChunkMetadata(projectId) - expect(metadata).to.deep.equal({ startVersion: 10, changesCount: 2 }) + // Check that the project is loaded + let state = await redisBackend.getState(projectId) + expect(state.headVersion).to.equal(1) + expect(state.persistedVersion).to.equal(1) + + // Expire the project + await redisBackend.expireProject(projectId) + state = await redisBackend.getState(projectId) + expect(state.headVersion).to.be.null }) - it('should return null if no chunk is cached for the project', async function () { - const metadata = await redisBackend.getCurrentChunkMetadata( - 'non-existent-project-id' - ) - expect(metadata).to.be.null + it('should not expire a non-persisted project', async function () { + // Load a project in Redis + const change = makeChange() + await queueChanges(projectId, [change]) + + // Check that the project is loaded + let state = await redisBackend.getState(projectId) + expect(state.headVersion).to.equal(1) + expect(state.persistedVersion).to.equal(null) + + // Expire the project + await redisBackend.expireProject(projectId) + state = await redisBackend.getState(projectId) + expect(state.headVersion).to.equal(1) }) - it('should return metadata with zero changes for a zero-change chunk', async function () { - // Cache a chunk with no changes - const snapshot = new Snapshot() - const history = new History(snapshot, []) - const chunk = new Chunk(history, 5) - await redisBackend.setCurrentChunk(projectId, chunk) + it('should not expire a partially persisted project', async function () { + // Load a fake project in Redis + const change1 = makeChange() + const change2 = makeChange() + await queueChanges(projectId, [change1, change2]) - const metadata = await redisBackend.getCurrentChunkMetadata(projectId) - expect(metadata).to.deep.equal({ startVersion: 5, changesCount: 0 }) + // Persist the first change + await redisBackend.setPersistedVersion(projectId, 1) + + // Check that the project is loaded + let state = await redisBackend.getState(projectId) + expect(state.headVersion).to.equal(2) + expect(state.persistedVersion).to.equal(1) + + // Expire the project + await redisBackend.expireProject(projectId) + state = await redisBackend.getState(projectId) + expect(state.headVersion).to.equal(2) + }) + + it('should handle a project that is not loaded', async function () { + // Check that the project is not loaded + let state = await redisBackend.getState(projectId) + expect(state.headVersion).to.be.null + + // Expire the project + await redisBackend.expireProject(projectId) + state = await redisBackend.getState(projectId) + expect(state.headVersion).to.be.null }) }) - describe('expireCurrentChunk', function () { - const TEMPORARY_CACHE_LIFETIME_MS = 300 * 1000 // Match the value in redis.js + describe('claimExpireJob', function () { + it("should claim the expire job when it's ready", async function () { + // Load a project in Redis + const change = makeChange() + const now = Date.now() + const expireTime = now - 1000 + await queueChanges(projectId, [change], { expireTime }) - it('should return false and not expire a non-expired chunk', async function () { - // Cache a chunk - const snapshot = new Snapshot() - const history = new History(snapshot, []) - const chunk = new Chunk(history, 10) - await redisBackend.setCurrentChunk(projectId, chunk) + // Check that the expire time has been set correctly + let state = await redisBackend.getState(projectId) + expect(state.expireTime).to.equal(expireTime) - // Attempt to expire immediately (should not be expired yet) - const expired = await redisBackend.expireCurrentChunk(projectId) - expect(expired).to.be.false + // Claim the job + await redisBackend.claimExpireJob(projectId) - // Verify the chunk still exists - const cachedChunk = await redisBackend.getCurrentChunk(projectId) - expect(cachedChunk).to.not.be.null - expect(cachedChunk.getStartVersion()).to.equal(10) + // Check the job expires in the future + state = await redisBackend.getState(projectId) + expect(state.expireTime).to.satisfy(time => time > now) }) - it('should return true and expire an expired chunk using currentTime', async function () { - // Cache a chunk - const snapshot = new Snapshot() - const history = new History(snapshot, []) - const chunk = new Chunk(history, 10) - await redisBackend.setCurrentChunk(projectId, chunk) + it('should throw an error when the job is not ready', async function () { + // Load a project in Redis + const change = makeChange() + const now = Date.now() + const expireTime = now + 100_000 + await queueChanges(projectId, [change], { expireTime }) - // Calculate a time far enough in the future to ensure expiry - const futureTime = Date.now() + TEMPORARY_CACHE_LIFETIME_MS + 5000 // 5 seconds past expiry - - // Attempt to expire using the future time - const expired = await redisBackend.expireCurrentChunk( - projectId, - futureTime + // Claim the job + await expect(redisBackend.claimExpireJob(projectId)).to.be.rejectedWith( + JobNotReadyError ) - expect(expired).to.be.true - - // Verify the chunk is gone - const cachedChunk = await redisBackend.getCurrentChunk(projectId) - expect(cachedChunk).to.be.null - - // Verify metadata is also gone - const metadata = await redisBackend.getCurrentChunkMetadata(projectId) - expect(metadata).to.be.null }) - it('should return false if no chunk is cached for the project', async function () { - const expired = await redisBackend.expireCurrentChunk( - 'non-existent-project' + it('should throw an error when the job is not found', async function () { + // Claim a job on a project that is not loaded + await expect(redisBackend.claimExpireJob(projectId)).to.be.rejectedWith( + JobNotFoundError ) - expect(expired).to.be.false - }) - - it('should return false if called with a currentTime before the expiry time', async function () { - // Cache a chunk - const snapshot = new Snapshot() - const history = new History(snapshot, []) - const chunk = new Chunk(history, 10) - await redisBackend.setCurrentChunk(projectId, chunk) - - // Use a time *before* the cache would normally expire - const pastTime = Date.now() - 10000 // 10 seconds ago - - // Attempt to expire using the past time - const expired = await redisBackend.expireCurrentChunk(projectId, pastTime) - expect(expired).to.be.false - - // Verify the chunk still exists - const cachedChunk = await redisBackend.getCurrentChunk(projectId) - expect(cachedChunk).to.not.be.null }) }) - describe('with a persist-time timestamp', function () { - const persistTimestamp = Date.now() + 1000 * 60 * 60 // 1 hour in the future + describe('claimPersistJob', function () { + it("should claim the persist job when it's ready", async function () { + // Load a project in Redis + const change = makeChange() + const now = Date.now() + const persistTime = now - 1000 + await queueChanges(projectId, [change], { persistTime }) + + // Check that the persist time has been set correctly + let state = await redisBackend.getState(projectId) + expect(state.persistTime).to.equal(persistTime) + + // Claim the job + await redisBackend.claimPersistJob(projectId) + + // Check the job is not ready + state = await redisBackend.getState(projectId) + expect(state.persistTime).to.satisfy(time => time > now) + }) + + it('should throw an error when the job is not ready', async function () { + // Load a project in Redis + const change = makeChange() + const now = Date.now() + const persistTime = now + 100_000 + await queueChanges(projectId, [change], { persistTime }) + + // Claim the job + await expect(redisBackend.claimPersistJob(projectId)).to.be.rejectedWith( + JobNotReadyError + ) + }) + + it('should throw an error when the job is not found', async function () { + // Claim a job on a project that is not loaded + await expect(redisBackend.claimExpireJob(projectId)).to.be.rejectedWith( + JobNotFoundError + ) + }) + }) + + describe('closing a job', function () { + let job beforeEach(async function () { - // Ensure a chunk exists before each test in this block - const snapshot = new Snapshot() - const changes = [ - new Change( - [new AddFileOperation('test.tex', File.fromString('Persist Test'))], - new Date(), - [] - ), - ] - const history = new History(snapshot, changes) - const chunk = new Chunk(history, 100) - await redisBackend.setCurrentChunk(projectId, chunk) + // Load a project in Redis + const change = makeChange() + const now = Date.now() + const expireTime = now - 1000 + await queueChanges(projectId, [change], { expireTime }) + + // Check that the expire time has been set correctly + const state = await redisBackend.getState(projectId) + expect(state.expireTime).to.equal(expireTime) + + // Claim the job + job = await redisBackend.claimExpireJob(projectId) }) - it('should not clear a chunk if persist-time is set', async function () { - // Set persist time - await redisBackend.setPersistTime(projectId, persistTimestamp) - - // Attempt to clear the cache - const cleared = await redisBackend.clearCache(projectId) - expect(cleared).to.be.false // Expect clearCache to return false - - // Verify the chunk still exists - const chunk = await redisBackend.getCurrentChunk(projectId) - expect(chunk).to.not.be.null - expect(chunk.getStartVersion()).to.equal(100) + it("should delete the key if it hasn't changed", async function () { + await job.close() + const state = await redisBackend.getState(projectId) + expect(state.expireTime).to.be.null }) - it('should not expire a chunk if persist-time is set, even if expire-time has passed', async function () { - // Set persist time - await redisBackend.setPersistTime(projectId, persistTimestamp) - - // Attempt to expire the chunk with a time far in the future - const farFutureTime = Date.now() + 1000 * 60 * 60 * 24 // 24 hours in the future - const expired = await redisBackend.expireCurrentChunk( - projectId, - farFutureTime - ) - expect(expired).to.be.false // Expect expireCurrentChunk to return false - - // Verify the chunk still exists - const chunk = await redisBackend.getCurrentChunk(projectId) - expect(chunk).to.not.be.null - expect(chunk.getStartVersion()).to.equal(100) - }) - - it('getCurrentChunkStatus should return persist-time when set', async function () { - // Set persist time - await redisBackend.setPersistTime(projectId, persistTimestamp) - - const status = await redisBackend.getCurrentChunkStatus(projectId) - expect(status.persistTime).to.equal(persistTimestamp) - expect(status.expireTime).to.be.a('number') // expireTime is set by setCurrentChunk - }) - - it('getCurrentChunkStatus should return null for persist-time when not set', async function () { - const status = await redisBackend.getCurrentChunkStatus(projectId) - expect(status.persistTime).to.be.null - expect(status.expireTime).to.be.a('number') - }) - - it('getCurrentChunkStatus should return nulls after cache is cleared (without persist-time)', async function () { - // Clear cache (persistTime is not set here) - await redisBackend.clearCache(projectId) - - const status = await redisBackend.getCurrentChunkStatus(projectId) - expect(status.persistTime).to.be.null - expect(status.expireTime).to.be.null + it('should keep the key if it has changed', async function () { + const newTimestamp = job.claimTimestamp + 1000 + await redisBackend.setExpireTime(projectId, newTimestamp) + await job.close() + const state = await redisBackend.getState(projectId) + expect(state.expireTime).to.equal(newTimestamp) }) }) }) + +async function queueChanges(projectId, changes, opts = {}) { + const baseVersion = 0 + const headSnapshot = new Snapshot() + + // Set times + const now = Date.now() + const persistTime = opts.persistTime ?? now + 30 * 1000 // 30 seconds from now + const expireTime = opts.expireTime ?? now + 60 * 60 * 1000 // 1 hour from now + + await redisBackend.queueChanges( + projectId, + headSnapshot, + baseVersion, + changes, + persistTime, + expireTime + ) +} + +function makeChange() { + const timestamp = new Date() + return new Change([], timestamp) +} diff --git a/services/history-v1/test/acceptance/js/storage/expire_redis_chunks.test.js b/services/history-v1/test/acceptance/js/storage/expire_redis_chunks.test.js new file mode 100644 index 0000000000..b657991dda --- /dev/null +++ b/services/history-v1/test/acceptance/js/storage/expire_redis_chunks.test.js @@ -0,0 +1,209 @@ +'use strict' + +const { expect } = require('chai') +const { promisify } = require('node:util') +const { execFile } = require('node:child_process') +const { Snapshot, Author, Change } = require('overleaf-editor-core') +const cleanup = require('./support/cleanup') +const redisBackend = require('../../../../storage/lib/chunk_store/redis') +const redis = require('../../../../storage/lib/redis') +const rclient = redis.rclientHistory +const keySchema = redisBackend.keySchema + +const SCRIPT_PATH = 'storage/scripts/expire_redis_chunks.js' + +async function runExpireScript() { + const TIMEOUT = 10 * 1000 // 10 seconds + let result + try { + result = await promisify(execFile)('node', [SCRIPT_PATH], { + encoding: 'utf-8', + timeout: TIMEOUT, + env: { + ...process.env, + LOG_LEVEL: 'debug', // Override LOG_LEVEL for script output + }, + }) + result.status = 0 + } catch (err) { + const { stdout, stderr, code } = err + if (typeof code !== 'number') { + console.error('Error running expire script:', err) + throw err + } + result = { stdout, stderr, status: code } + } + // The script might exit with status 1 if it finds no keys to process, which is ok + if (result.status !== 0 && result.status !== 1) { + console.error('Expire script failed:', result.stderr) + throw new Error(`expire script failed with status ${result.status}`) + } + return result +} + +// Helper to set up a basic project state in Redis +async function setupProjectState( + projectId, + { + headVersion = 0, + persistedVersion = null, + expireTime = null, + persistTime = null, + changes = [], + } +) { + const headSnapshot = new Snapshot() + await rclient.set( + keySchema.head({ projectId }), + JSON.stringify(headSnapshot.toRaw()) + ) + await rclient.set( + keySchema.headVersion({ projectId }), + headVersion.toString() + ) + + if (persistedVersion !== null) { + await rclient.set( + keySchema.persistedVersion({ projectId }), + persistedVersion.toString() + ) + } + if (expireTime !== null) { + await rclient.set( + keySchema.expireTime({ projectId }), + expireTime.toString() + ) + } + if (persistTime !== null) { + await rclient.set( + keySchema.persistTime({ projectId }), + persistTime.toString() + ) + } + if (changes.length > 0) { + const rawChanges = changes.map(c => JSON.stringify(c.toRaw())) + await rclient.rpush(keySchema.changes({ projectId }), ...rawChanges) + } +} + +function makeChange() { + const timestamp = new Date() + const author = new Author(123, 'test@example.com', 'Test User') + return new Change([], timestamp, [author]) +} + +describe('expire_redis_chunks script', function () { + beforeEach(cleanup.everything) + + let now, past, future + + // Setup all projects and run the script once before tests + beforeEach(async function () { + now = Date.now() + past = now - 10000 // 10 seconds ago + future = now + 60000 // 1 minute in the future + + // Setup all project states explicitly + await setupProjectState('expired_persisted', { + headVersion: 2, + persistedVersion: 2, + expireTime: past, + }) + await setupProjectState('expired_initial_state', { + headVersion: 0, + persistedVersion: 0, + expireTime: past, + }) + await setupProjectState('expired_persisted_with_job', { + headVersion: 2, + persistedVersion: 2, + expireTime: past, + persistTime: future, + }) + await setupProjectState('expired_not_persisted', { + headVersion: 3, + persistedVersion: 2, + expireTime: past, + changes: [makeChange()], + }) + await setupProjectState('expired_no_persisted_version', { + headVersion: 1, + persistedVersion: null, + expireTime: past, + changes: [makeChange()], + }) + await setupProjectState('future_expired_persisted', { + headVersion: 2, + persistedVersion: 2, + expireTime: future, + }) + await setupProjectState('future_expired_not_persisted', { + headVersion: 3, + persistedVersion: 2, + expireTime: future, + changes: [makeChange()], + }) + await setupProjectState('no_expire_time', { + headVersion: 1, + persistedVersion: 1, + expireTime: null, + }) + + // Run the expire script once after all projects are set up + await runExpireScript() + }) + + async function checkProjectStatus(projectId) { + const exists = + (await rclient.exists(keySchema.headVersion({ projectId }))) === 1 + return exists ? 'exists' : 'deleted' + } + + it('should expire a project when expireTime is past and it is fully persisted', async function () { + const projectId = 'expired_persisted' + const status = await checkProjectStatus(projectId) + expect(status).to.equal('deleted') + }) + + it('should expire a project when expireTime is past and it has no changes (initial state)', async function () { + const projectId = 'expired_initial_state' + const status = await checkProjectStatus(projectId) + expect(status).to.equal('deleted') + }) + + it('should expire a project when expireTime is past and it is fully persisted even if persistTime is set', async function () { + const projectId = 'expired_persisted_with_job' + const status = await checkProjectStatus(projectId) + expect(status).to.equal('deleted') + }) + + it('should not expire a project when expireTime is past but it is not fully persisted', async function () { + const projectId = 'expired_not_persisted' + const status = await checkProjectStatus(projectId) + expect(status).to.equal('exists') + }) + + it('should not expire a project when expireTime is past but persistedVersion is not set', async function () { + const projectId = 'expired_no_persisted_version' + const status = await checkProjectStatus(projectId) + expect(status).to.equal('exists') + }) + + it('should not expire a project when expireTime is in the future (even if fully persisted)', async function () { + const projectId = 'future_expired_persisted' + const status = await checkProjectStatus(projectId) + expect(status).to.equal('exists') + }) + + it('should not expire a project when expireTime is in the future (if not fully persisted)', async function () { + const projectId = 'future_expired_not_persisted' + const status = await checkProjectStatus(projectId) + expect(status).to.equal('exists') + }) + + it('should not expire a project when expireTime is not set', async function () { + const projectId = 'no_expire_time' + const status = await checkProjectStatus(projectId) + expect(status).to.equal('exists') + }) +}) From e4156e19b84bcfa156ccd97f80e034a1a76aa8bf Mon Sep 17 00:00:00 2001 From: Jakob Ackermann Date: Wed, 7 May 2025 11:05:40 +0200 Subject: [PATCH 032/194] [web] use raw compile timeout in compile-result-backend event (#25141) The alias was broken following the 60s -> 20s compile timeout change. GitOrigin-RevId: c2172090e17be60490adaae245a1f0b045e93cf1 --- services/web/app/src/Features/Compile/CompileController.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/web/app/src/Features/Compile/CompileController.js b/services/web/app/src/Features/Compile/CompileController.js index e07fe49c80..20356f7a7a 100644 --- a/services/web/app/src/Features/Compile/CompileController.js +++ b/services/web/app/src/Features/Compile/CompileController.js @@ -216,7 +216,7 @@ const _CompileController = { ownerAnalyticsId: limits.ownerAnalyticsId, status, compileTime: timings?.compileE2E, - timeout: limits.timeout === 60 ? 'short' : 'long', + timeout: limits.timeout, server: clsiServerId?.includes('-c2d-') ? 'faster' : 'normal', isAutoCompile, isInitialCompile: stats?.isInitialCompile === 1, From efb66b4d2f052c333bbc8a2d0d39cd7e688f90c8 Mon Sep 17 00:00:00 2001 From: David <33458145+davidmcpowell@users.noreply.github.com> Date: Wed, 7 May 2025 10:59:13 +0100 Subject: [PATCH 033/194] Merge pull request #25327 from overleaf/dp-word-count Add word count to file menu in new editor GitOrigin-RevId: a5cb4d6cd37c46775056f696c0f19fcabd3f4131 --- .../web/frontend/extracted-translations.json | 1 + .../components/toolbar/menu-bar.tsx | 143 ++++++++++-------- services/web/locales/en.json | 1 + 3 files changed, 84 insertions(+), 61 deletions(-) diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json index 7db0fc6d4f..91900cd22a 100644 --- a/services/web/frontend/extracted-translations.json +++ b/services/web/frontend/extracted-translations.json @@ -2058,6 +2058,7 @@ "will_lose_edit_access_on_date": "", "with_premium_subscription_you_also_get": "", "word_count": "", + "word_count_lower": "", "work_in_vim_or_emacs_emulation_mode": "", "work_offline": "", "work_offline_pull_to_overleaf": "", diff --git a/services/web/frontend/js/features/ide-redesign/components/toolbar/menu-bar.tsx b/services/web/frontend/js/features/ide-redesign/components/toolbar/menu-bar.tsx index d75dfeb632..d0e3a8dbf5 100644 --- a/services/web/frontend/js/features/ide-redesign/components/toolbar/menu-bar.tsx +++ b/services/web/frontend/js/features/ide-redesign/components/toolbar/menu-bar.tsx @@ -7,7 +7,7 @@ import { MenuBarDropdown } from '@/shared/components/menu-bar/menu-bar-dropdown' import { MenuBarOption } from '@/shared/components/menu-bar/menu-bar-option' import { useTranslation } from 'react-i18next' import ChangeLayoutOptions from './change-layout-options' -import { MouseEventHandler, useCallback, useMemo } from 'react' +import { MouseEventHandler, useCallback, useMemo, useState } from 'react' import { useIdeRedesignSwitcherContext } from '@/features/ide-react/context/ide-redesign-switcher-context' import { useSwitchEnableNewEditorState } from '../../hooks/use-switch-enable-new-editor-state' import MaterialIcon from '@/shared/components/material-icon' @@ -21,6 +21,9 @@ import CommandDropdown, { } from './command-dropdown' import { useUserSettingsContext } from '@/shared/context/user-settings-context' import { useRailContext } from '../../contexts/rail-context' +import WordCountModal from '@/features/word-count-modal/components/word-count-modal' +import { isSplitTestEnabled } from '@/utils/splitTestUtils' +import { useDetachCompileContext as useCompileContext } from '@/shared/context/detach-compile-context' export const ToolbarMenuBar = () => { const { t } = useTranslation() @@ -29,6 +32,9 @@ export const ToolbarMenuBar = () => { setShowSwitcherModal(true) }, [setShowSwitcherModal]) const { setView, view } = useLayoutContext() + const { pdfUrl } = useCompileContext() + const wordCountEnabled = pdfUrl || isSplitTestEnabled('word-count-client') + const [showWordCountModal, setShowWordCountModal] = useState(false) useCommandProvider( () => [ @@ -40,8 +46,17 @@ export const ToolbarMenuBar = () => { }, id: 'show_version_history', }, + { + type: 'command', + label: t('word_count_lower'), + disabled: !wordCountEnabled, + handler: () => { + setShowWordCountModal(true) + }, + id: 'word_count', + }, ], - [t, setView, view] + [t, setView, view, wordCountEnabled] ) const fileMenuStructure: MenuStructure = useMemo( () => [ @@ -49,7 +64,7 @@ export const ToolbarMenuBar = () => { id: 'file-file-tree', children: ['new_file', 'new_folder', 'upload_file'], }, - { id: 'file-history', children: ['show_version_history'] }, + { id: 'file-tools', children: ['show_version_history', 'word_count'] }, { id: 'file-download', children: ['download-as-source-zip', 'download-pdf'], @@ -175,67 +190,73 @@ export const ToolbarMenuBar = () => { setActiveModal('contact-us') }, [setActiveModal]) return ( - - - - - + - - Editor settings - + + - - - + + Editor settings + + + + + + + + + + + + + + + + setShowWordCountModal(false)} /> - - - - - - - - - - - + ) } diff --git a/services/web/locales/en.json b/services/web/locales/en.json index 1a610957e6..1a00873eda 100644 --- a/services/web/locales/en.json +++ b/services/web/locales/en.json @@ -2609,6 +2609,7 @@ "will_need_to_log_out_from_and_in_with": "You will need to log out from your __email1__ account and then log in with __email2__.", "with_premium_subscription_you_also_get": "With an Overleaf Premium subscription you also get", "word_count": "Word Count", + "word_count_lower": "Word count", "work_in_vim_or_emacs_emulation_mode": "Work in Vim or Emacs emulation mode", "work_offline": "Work offline", "work_offline_pull_to_overleaf": "Work offline, then pull to __appName__", From 4d93187e587dc5ee2109ffd41491c7fdb67f06c7 Mon Sep 17 00:00:00 2001 From: David <33458145+davidmcpowell@users.noreply.github.com> Date: Wed, 7 May 2025 10:59:21 +0100 Subject: [PATCH 034/194] Merge pull request #25354 from overleaf/dp-editor-redesign-modal Update editor switcher modal contents GitOrigin-RevId: 98772328004303c43ff3f9f0edbf8b0725041c60 --- .../web/frontend/extracted-translations.json | 6 +++--- .../components/switcher-modal/modal.tsx | 12 ++++++------ .../editor/ide-redesign-switcher-modal.scss | 16 ++++++++++++++-- services/web/locales/en.json | 6 +++--- 4 files changed, 26 insertions(+), 14 deletions(-) diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json index 91900cd22a..e11bb8002a 100644 --- a/services/web/frontend/extracted-translations.json +++ b/services/web/frontend/extracted-translations.json @@ -428,6 +428,7 @@ "done": "", "dont_forget_you_currently_have": "", "dont_reload_or_close_this_tab": "", + "double_clicking_on_the_pdf_shows": "", "download": "", "download_all": "", "download_as_pdf": "", @@ -883,6 +884,7 @@ "last_used": "", "latam_discount_modal_info": "", "latam_discount_modal_title": "", + "latest_updates": "", "latex_in_thirty_minutes": "", "latex_places_figures_according_to_a_special_algorithm": "", "latex_places_tables_according_to_a_special_algorithm": "", @@ -1023,7 +1025,6 @@ "month_plural": "", "more": "", "more_actions": "", - "more_changes_based_on_your_feedback": "", "more_collabs_per_project": "", "more_comments": "", "more_compile_time": "", @@ -2048,8 +2049,7 @@ "what_does_this_mean_for_you": "", "what_happens_when_sso_is_enabled": "", "what_should_we_call_you": "", - "whats_new": "", - "whats_next": "", + "whats_different_in_the_new_editor": "", "when_you_tick_the_include_caption_box": "", "why_latex": "", "why_might_this_happen": "", diff --git a/services/web/frontend/js/features/ide-redesign/components/switcher-modal/modal.tsx b/services/web/frontend/js/features/ide-redesign/components/switcher-modal/modal.tsx index fb5898fcb0..dd063fc115 100644 --- a/services/web/frontend/js/features/ide-redesign/components/switcher-modal/modal.tsx +++ b/services/web/frontend/js/features/ide-redesign/components/switcher-modal/modal.tsx @@ -147,7 +147,12 @@ const SwitcherWhatsNew = () => { const { t } = useTranslation() return (
-

{t('whats_new')}

+

{t('latest_updates')}

+
    +
  • {t('double_clicking_on_the_pdf_shows')}
  • +
+
+

{t('whats_different_in_the_new_editor')}

  • {t('new_look_and_feel')}
  • @@ -157,11 +162,6 @@ const SwitcherWhatsNew = () => {
  • {t('improved_dark_mode')}
  • {t('review_panel_and_error_logs_moved_to_the_left')}
-
-

{t('whats_next')}

-
    -
  • {t('more_changes_based_on_your_feedback')}
  • -
) } diff --git a/services/web/frontend/stylesheets/bootstrap-5/pages/editor/ide-redesign-switcher-modal.scss b/services/web/frontend/stylesheets/bootstrap-5/pages/editor/ide-redesign-switcher-modal.scss index 4bba755e44..bb9c930848 100644 --- a/services/web/frontend/stylesheets/bootstrap-5/pages/editor/ide-redesign-switcher-modal.scss +++ b/services/web/frontend/stylesheets/bootstrap-5/pages/editor/ide-redesign-switcher-modal.scss @@ -13,8 +13,20 @@ padding: var(--spacing-05); margin: var(--spacing-05) 0; - ul li:not(:last-child) { - margin-bottom: var(--spacing-04); + hr { + margin: var(--spacing-04) 0; + } + + h4 { + margin-top: 0; + } + + ul { + margin-bottom: 0; + + li:not(:last-child) { + margin-bottom: var(--spacing-04); + } } } diff --git a/services/web/locales/en.json b/services/web/locales/en.json index 1a00873eda..5080d71bfb 100644 --- a/services/web/locales/en.json +++ b/services/web/locales/en.json @@ -557,6 +557,7 @@ "dont_forget_you_currently_have": "Don’t forget, you currently have:", "dont_have_account": "Don’t have an account?", "dont_reload_or_close_this_tab": "Don’t reload or close this tab.", + "double_clicking_on_the_pdf_shows": "Double clicking on the PDF shows the corresponding location in code. Added word count (7 May 2025)", "download": "Download", "download_all": "Download all", "download_as_pdf": "Download as PDF", @@ -1152,6 +1153,7 @@ "latam_discount_modal_info": "Unlock the full potential of Overleaf with a __discount__% discount on premium subscriptions paid in __currencyName__. Get a longer compile timeout, full document history, track changes, additional collaborators, and more.", "latam_discount_modal_title": "Premium subscription discount", "latam_discount_offer_plans_page_banner": "__flag__ We’ve applied a __discount__ discount to premium plans on this page for our users in __country__. Check out the new lower prices (in __currency__).", + "latest_updates": "Latest updates", "latex_articles_page_title": "Articles - Papers, Presentations, Reports and more", "latex_examples": "LaTeX examples", "latex_examples_page_title": "Examples - Equations, Formatting, TikZ, Packages and More", @@ -1342,7 +1344,6 @@ "monthly": "Monthly", "more": "More", "more_actions": "More actions", - "more_changes_based_on_your_feedback": "More changes based on your feedback!", "more_collabs_per_project": "More collaborators per project", "more_comments": "More comments", "more_compile_time": "More compile time", @@ -2597,8 +2598,7 @@ "what_does_this_mean_for_you": "This means:", "what_happens_when_sso_is_enabled": "What happens when SSO is enabled?", "what_should_we_call_you": "What should we call you?", - "whats_new": "What’s new?", - "whats_next": "What’s next?", + "whats_different_in_the_new_editor": "What’s different in the new editor?", "when_you_join_labs": "When you join Labs, you can choose which experiments you want to be part of. Once you’ve done that, you can use Overleaf as normal, but you’ll see any labs features marked with this badge:", "when_you_tick_the_include_caption_box": "When you tick the box “Include caption” the image will be inserted into your document with a placeholder caption. To edit it, you simply select the placeholder text and type to replace it with your own.", "why_latex": "Why LaTeX?", From e8b5ee2ff9e4385cb95f9a452b03593956eaaacf Mon Sep 17 00:00:00 2001 From: Jakob Ackermann Date: Wed, 7 May 2025 12:53:12 +0200 Subject: [PATCH 035/194] [history-ot] initial implementation of using doc-level history-ot (#25054) * [history-v1-ot] initial implementation of using doc-level history-v1-ot * [web] fix advancing of the otMigrationStage Use 'nextStage' for the user provided, desired stage when advancing. Co-authored-by: Brian Gough * [document-updater] document size check in editor-core * [history-ot] rename history-v1-ot to history-ot and add types * [history-ot] apply review feedback - remove extra !! - merge variable assignment when processing diff-match-match output - add helper function for getting docstore lines view of StringFileData Co-authored-by: Alf Eaton * Revert "[document-updater] add safe rollback point for history-ot (#25283)" This reverts commit d7230dd14a379a27d2c6ab03a006463a18979d06 Signed-off-by: Jakob Ackermann --------- Signed-off-by: Jakob Ackermann Co-authored-by: Brian Gough Co-authored-by: Alf Eaton GitOrigin-RevId: 89c497782adb0427635d50d02263d6f535b12481 --- libraries/overleaf-editor-core/index.js | 6 + .../lib/file_data/string_file_data.js | 8 + .../lib/operation/edit_operation_builder.js | 14 + package-lock.json | 1 + services/document-updater/app.js | 2 - services/document-updater/app/js/DiffCodec.js | 24 +- .../app/js/DocumentManager.js | 89 ++- services/document-updater/app/js/Errors.js | 12 +- .../app/js/HistoryV1OTUpdateManager.js | 158 +++++ .../document-updater/app/js/HttpController.js | 14 +- .../app/js/PersistenceManager.js | 7 + services/document-updater/app/js/Profiler.js | 102 ++-- .../document-updater/app/js/RedisManager.js | 19 +- .../document-updater/app/js/UpdateManager.js | 13 +- services/document-updater/app/js/types.ts | 10 + services/document-updater/package.json | 1 + .../js/ApplyingUpdatesToADocTests.js | 544 ++++++++++++++++++ .../acceptance/js/GettingADocumentTests.js | 58 -- .../acceptance/js/SettingADocumentTests.js | 161 ++++++ services/document-updater/test/setup.js | 1 + .../DocumentManager/DocumentManagerTests.js | 10 + .../js/HttpController/HttpControllerTests.js | 51 +- .../js/UpdateManager/UpdateManagerTests.js | 2 + .../app/js/UpdateCompressor.js | 22 +- .../app/js/UpdateTranslator.js | 28 +- services/project-history/app/js/types.ts | 15 +- .../real-time/app/js/ConnectedUsersManager.js | 4 + .../app/js/DocumentUpdaterManager.js | 5 +- .../real-time/app/js/HttpApiController.js | 15 + services/real-time/app/js/Router.js | 4 + .../real-time/app/js/WebsocketController.js | 72 ++- .../test/acceptance/js/ClientTrackingTests.js | 74 +++ .../test/acceptance/js/JoinDocTests.js | 143 ++++- .../acceptance/js/helpers/FixturesManager.js | 6 +- .../acceptance/js/helpers/RealTimeClient.js | 10 + .../unit/js/DocumentUpdaterManagerTests.js | 2 +- .../Features/Documents/DocumentController.mjs | 6 + .../web/app/src/Features/Errors/Errors.js | 14 + .../Features/History/HistoryOTMigration.mjs | 56 ++ .../Features/Project/ProjectOptionsHandler.js | 17 + .../References/RealTime/RealTimeHandler.mjs | 9 + services/web/app/src/models/Project.js | 1 + .../ide-react/editor/document-container.ts | 56 +- .../features/ide-react/editor/share-js-doc.ts | 31 +- .../editor/share-js-history-ot-type.ts | 131 +++++ .../ide-react/editor/types/document.ts | 21 +- services/web/test/unit/bootstrap.js | 1 + .../src/Documents/DocumentControllerTests.mjs | 1 + 48 files changed, 1828 insertions(+), 223 deletions(-) create mode 100644 services/document-updater/app/js/HistoryV1OTUpdateManager.js create mode 100644 services/web/app/src/Features/History/HistoryOTMigration.mjs create mode 100644 services/web/app/src/Features/References/RealTime/RealTimeHandler.mjs create mode 100644 services/web/frontend/js/features/ide-react/editor/share-js-history-ot-type.ts diff --git a/libraries/overleaf-editor-core/index.js b/libraries/overleaf-editor-core/index.js index df3548c2ed..33b3dcf5dc 100644 --- a/libraries/overleaf-editor-core/index.js +++ b/libraries/overleaf-editor-core/index.js @@ -18,6 +18,7 @@ const MoveFileOperation = require('./lib/operation/move_file_operation') const SetCommentStateOperation = require('./lib/operation/set_comment_state_operation') const EditFileOperation = require('./lib/operation/edit_file_operation') const EditNoOperation = require('./lib/operation/edit_no_operation') +const EditOperationTransformer = require('./lib/operation/edit_operation_transformer') const SetFileMetadataOperation = require('./lib/operation/set_file_metadata_operation') const NoOperation = require('./lib/operation/no_operation') const Operation = require('./lib/operation') @@ -43,6 +44,8 @@ const TrackingProps = require('./lib/file_data/tracking_props') const Range = require('./lib/range') const CommentList = require('./lib/file_data/comment_list') const LazyStringFileData = require('./lib/file_data/lazy_string_file_data') +const StringFileData = require('./lib/file_data/string_file_data') +const EditOperationBuilder = require('./lib/operation/edit_operation_builder') exports.AddCommentOperation = AddCommentOperation exports.Author = Author @@ -58,6 +61,7 @@ exports.DeleteCommentOperation = DeleteCommentOperation exports.File = File exports.FileMap = FileMap exports.LazyStringFileData = LazyStringFileData +exports.StringFileData = StringFileData exports.History = History exports.Label = Label exports.AddFileOperation = AddFileOperation @@ -65,6 +69,8 @@ exports.MoveFileOperation = MoveFileOperation exports.SetCommentStateOperation = SetCommentStateOperation exports.EditFileOperation = EditFileOperation exports.EditNoOperation = EditNoOperation +exports.EditOperationBuilder = EditOperationBuilder +exports.EditOperationTransformer = EditOperationTransformer exports.SetFileMetadataOperation = SetFileMetadataOperation exports.NoOperation = NoOperation exports.Operation = Operation diff --git a/libraries/overleaf-editor-core/lib/file_data/string_file_data.js b/libraries/overleaf-editor-core/lib/file_data/string_file_data.js index 2613c30ebc..48df633461 100644 --- a/libraries/overleaf-editor-core/lib/file_data/string_file_data.js +++ b/libraries/overleaf-editor-core/lib/file_data/string_file_data.js @@ -88,6 +88,14 @@ class StringFileData extends FileData { return content } + /** + * Return docstore view of a doc: each line separated + * @return {string[]} + */ + getLines() { + return this.getContent({ filterTrackedDeletes: true }).split('\n') + } + /** @inheritdoc */ getByteLength() { return Buffer.byteLength(this.content) diff --git a/libraries/overleaf-editor-core/lib/operation/edit_operation_builder.js b/libraries/overleaf-editor-core/lib/operation/edit_operation_builder.js index febdebc034..7d5bb81aae 100644 --- a/libraries/overleaf-editor-core/lib/operation/edit_operation_builder.js +++ b/libraries/overleaf-editor-core/lib/operation/edit_operation_builder.js @@ -36,6 +36,20 @@ class EditOperationBuilder { } throw new Error('Unsupported operation in EditOperationBuilder.fromJSON') } + + /** + * @param {unknown} raw + * @return {raw is RawEditOperation} + */ + static isValid(raw) { + return ( + isTextOperation(raw) || + isRawAddCommentOperation(raw) || + isRawDeleteCommentOperation(raw) || + isRawSetCommentStateOperation(raw) || + isRawEditNoOperation(raw) + ) + } } /** diff --git a/package-lock.json b/package-lock.json index 8d0b9cbead..e47938157d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -42539,6 +42539,7 @@ "lodash": "^4.17.21", "minimist": "^1.2.8", "mongodb-legacy": "6.1.3", + "overleaf-editor-core": "*", "request": "^2.88.2", "requestretry": "^7.1.0" }, diff --git a/services/document-updater/app.js b/services/document-updater/app.js index f425872da5..65c9895377 100644 --- a/services/document-updater/app.js +++ b/services/document-updater/app.js @@ -212,8 +212,6 @@ app.use((error, req, res, next) => { return res.status(422).json(error.info) } else if (error instanceof Errors.FileTooLargeError) { return res.sendStatus(413) - } else if (error instanceof Errors.ProjectMigratedToHistoryOTError) { - return res.status(422).send(error.message) } else if (error.statusCode === 413) { return res.status(413).send('request entity too large') } else { diff --git a/services/document-updater/app/js/DiffCodec.js b/services/document-updater/app/js/DiffCodec.js index 245903ca13..8c574cff70 100644 --- a/services/document-updater/app/js/DiffCodec.js +++ b/services/document-updater/app/js/DiffCodec.js @@ -1,4 +1,5 @@ const DMP = require('diff-match-patch') +const { TextOperation } = require('overleaf-editor-core') const dmp = new DMP() // Do not attempt to produce a diff for more than 100ms @@ -16,8 +17,7 @@ module.exports = { const ops = [] let position = 0 for (const diff of diffs) { - const type = diff[0] - const content = diff[1] + const [type, content] = diff if (type === this.ADDED) { ops.push({ i: content, @@ -37,4 +37,24 @@ module.exports = { } return ops }, + + diffAsHistoryV1EditOperation(before, after) { + const diffs = dmp.diff_main(before, after) + dmp.diff_cleanupSemantic(diffs) + + const op = new TextOperation() + for (const diff of diffs) { + const [type, content] = diff + if (type === this.ADDED) { + op.insert(content) + } else if (type === this.REMOVED) { + op.remove(content.length) + } else if (type === this.UNCHANGED) { + op.retain(content.length) + } else { + throw new Error('Unknown type') + } + } + return op + }, } diff --git a/services/document-updater/app/js/DocumentManager.js b/services/document-updater/app/js/DocumentManager.js index dc20c27d7f..2ff31b8c41 100644 --- a/services/document-updater/app/js/DocumentManager.js +++ b/services/document-updater/app/js/DocumentManager.js @@ -11,10 +11,16 @@ const RangesManager = require('./RangesManager') const { extractOriginOrSource } = require('./Utils') const { getTotalSizeOfLines } = require('./Limits') const Settings = require('@overleaf/settings') +const { StringFileData } = require('overleaf-editor-core') const MAX_UNFLUSHED_AGE = 300 * 1000 // 5 mins, document should be flushed to mongo this time after a change const DocumentManager = { + /** + * @param {string} projectId + * @param {string} docId + * @return {Promise<{lines: (string[] | StringFileRawData), version: number, ranges: Ranges, resolvedCommentIds: any[], pathname: string, projectHistoryId: string, unflushedTime: any, alreadyLoaded: boolean, historyRangesSupport: boolean, type: OTType}>} + */ async getDoc(projectId, docId) { const { lines, @@ -75,6 +81,7 @@ const DocumentManager = { unflushedTime: null, alreadyLoaded: false, historyRangesSupport, + type: Array.isArray(lines) ? 'sharejs-text-ot' : 'history-ot', } } else { return { @@ -87,16 +94,25 @@ const DocumentManager = { unflushedTime, alreadyLoaded: true, historyRangesSupport, + type: Array.isArray(lines) ? 'sharejs-text-ot' : 'history-ot', } } }, async getDocAndRecentOps(projectId, docId, fromVersion) { - const { lines, version, ranges, pathname, projectHistoryId } = + const { lines, version, ranges, pathname, projectHistoryId, type } = await DocumentManager.getDoc(projectId, docId) if (fromVersion === -1) { - return { lines, version, ops: [], ranges, pathname, projectHistoryId } + return { + lines, + version, + ops: [], + ranges, + pathname, + projectHistoryId, + type, + } } else { const ops = await RedisManager.promises.getPreviousDocOps( docId, @@ -110,15 +126,21 @@ const DocumentManager = { ranges, pathname, projectHistoryId, + type, } } }, async appendToDoc(projectId, docId, linesToAppend, originOrSource, userId) { - const { lines: currentLines } = await DocumentManager.getDoc( + let { lines: currentLines, type } = await DocumentManager.getDoc( projectId, docId ) + if (type === 'history-ot') { + const file = StringFileData.fromRaw(currentLines) + // TODO(24596): tc support for history-ot + currentLines = file.getLines() + } const currentLineSize = getTotalSizeOfLines(currentLines) const addedSize = getTotalSizeOfLines(linesToAppend) const newlineSize = '\n'.length @@ -153,22 +175,42 @@ const DocumentManager = { throw new Error('No lines were provided to setDoc') } + // Circular dependencies. Import at runtime. + const HistoryV1OTUpdateManager = require('./HistoryV1OTUpdateManager') const UpdateManager = require('./UpdateManager') + const { lines: oldLines, version, alreadyLoaded, + type, } = await DocumentManager.getDoc(projectId, docId) logger.debug( { docId, projectId, oldLines, newLines }, 'setting a document via http' ) - const op = DiffCodec.diffAsShareJsOp(oldLines, newLines) - if (undoing) { - for (const o of op || []) { - o.u = true - } // Turn on undo flag for each op for track changes + + let op + if (type === 'history-ot') { + const file = StringFileData.fromRaw(oldLines) + const operation = DiffCodec.diffAsHistoryV1EditOperation( + // TODO(24596): tc support for history-ot + file.getContent({ filterTrackedDeletes: true }), + newLines.join('\n') + ) + if (operation.isNoop()) { + op = [] + } else { + op = [operation.toJSON()] + } + } else { + op = DiffCodec.diffAsShareJsOp(oldLines, newLines) + if (undoing) { + for (const o of op || []) { + o.u = true + } // Turn on undo flag for each op for track changes + } } const { origin, source } = extractOriginOrSource(originOrSource) @@ -203,7 +245,11 @@ const DocumentManager = { // this update, otherwise the doc would never be // removed from redis. if (op.length > 0) { - await UpdateManager.promises.applyUpdate(projectId, docId, update) + if (type === 'history-ot') { + await HistoryV1OTUpdateManager.applyUpdate(projectId, docId, update) + } else { + await UpdateManager.promises.applyUpdate(projectId, docId, update) + } } // If the document was loaded already, then someone has it open @@ -224,7 +270,7 @@ const DocumentManager = { }, async flushDocIfLoaded(projectId, docId) { - const { + let { lines, version, ranges, @@ -245,6 +291,11 @@ const DocumentManager = { logger.debug({ projectId, docId, version }, 'flushing doc') Metrics.inc('flush-doc-if-loaded', 1, { status: 'modified' }) + if (!Array.isArray(lines)) { + const file = StringFileData.fromRaw(lines) + // TODO(24596): tc support for history-ot + lines = file.getLines() + } const result = await PersistenceManager.promises.setDoc( projectId, docId, @@ -294,6 +345,7 @@ const DocumentManager = { throw new Errors.NotFoundError(`document not found: ${docId}`) } + // TODO(24596): tc support for history-ot const newRanges = RangesManager.acceptChanges( projectId, docId, @@ -360,6 +412,7 @@ const DocumentManager = { }, async getComment(projectId, docId, commentId) { + // TODO(24596): tc support for history-ot const { ranges } = await DocumentManager.getDoc(projectId, docId) const comment = ranges?.comments?.find(comment => comment.id === commentId) @@ -381,6 +434,7 @@ const DocumentManager = { throw new Errors.NotFoundError(`document not found: ${docId}`) } + // TODO(24596): tc support for history-ot const newRanges = RangesManager.deleteComment(commentId, ranges) await RedisManager.promises.updateDocument( @@ -420,7 +474,7 @@ const DocumentManager = { }, async getDocAndFlushIfOld(projectId, docId) { - const { lines, version, unflushedTime, alreadyLoaded } = + let { lines, version, unflushedTime, alreadyLoaded } = await DocumentManager.getDoc(projectId, docId) // if doc was already loaded see if it needs to be flushed @@ -432,6 +486,12 @@ const DocumentManager = { await DocumentManager.flushDocIfLoaded(projectId, docId) } + if (!Array.isArray(lines)) { + const file = StringFileData.fromRaw(lines) + // TODO(24596): tc support for history-ot + lines = file.getLines() + } + return { lines, version } }, @@ -476,6 +536,11 @@ const DocumentManager = { if (opts.historyRangesMigration) { historyRangesSupport = opts.historyRangesMigration === 'forwards' } + if (!Array.isArray(lines)) { + const file = StringFileData.fromRaw(lines) + // TODO(24596): tc support for history-ot + lines = file.getLines() + } await ProjectHistoryRedisManager.promises.queueResyncDocContent( projectId, @@ -684,6 +749,7 @@ module.exports = { 'ranges', 'pathname', 'projectHistoryId', + 'type', ], getDocAndRecentOpsWithLock: [ 'lines', @@ -692,6 +758,7 @@ module.exports = { 'ranges', 'pathname', 'projectHistoryId', + 'type', ], getCommentWithLock: ['comment'], }, diff --git a/services/document-updater/app/js/Errors.js b/services/document-updater/app/js/Errors.js index 0416581b30..ac1f5875fa 100644 --- a/services/document-updater/app/js/Errors.js +++ b/services/document-updater/app/js/Errors.js @@ -5,7 +5,15 @@ class OpRangeNotAvailableError extends OError {} class ProjectStateChangedError extends OError {} class DeleteMismatchError extends OError {} class FileTooLargeError extends OError {} -class ProjectMigratedToHistoryOTError extends OError {} +class OTTypeMismatchError extends OError { + /** + * @param {OTType} got + * @param {OTType} want + */ + constructor(got, want) { + super('ot type mismatch', { got, want }) + } +} module.exports = { NotFoundError, @@ -13,5 +21,5 @@ module.exports = { ProjectStateChangedError, DeleteMismatchError, FileTooLargeError, - ProjectMigratedToHistoryOTError, + OTTypeMismatchError, } diff --git a/services/document-updater/app/js/HistoryV1OTUpdateManager.js b/services/document-updater/app/js/HistoryV1OTUpdateManager.js new file mode 100644 index 0000000000..ca3183ec62 --- /dev/null +++ b/services/document-updater/app/js/HistoryV1OTUpdateManager.js @@ -0,0 +1,158 @@ +// @ts-check + +const Profiler = require('./Profiler') +const DocumentManager = require('./DocumentManager') +const Errors = require('./Errors') +const RedisManager = require('./RedisManager') +const { + EditOperationBuilder, + StringFileData, + EditOperationTransformer, +} = require('overleaf-editor-core') +const Metrics = require('./Metrics') +const ProjectHistoryRedisManager = require('./ProjectHistoryRedisManager') +const HistoryManager = require('./HistoryManager') +const RealTimeRedisManager = require('./RealTimeRedisManager') + +/** + * @typedef {import("./types").Update} Update + * @typedef {import("./types").HistoryV1OTEditOperationUpdate} HistoryV1OTEditOperationUpdate + */ + +/** + * @param {Update} update + * @return {update is HistoryV1OTEditOperationUpdate} + */ +function isHistoryOTEditOperationUpdate(update) { + return ( + update && + 'doc' in update && + 'op' in update && + 'v' in update && + Array.isArray(update.op) && + EditOperationBuilder.isValid(update.op[0]) + ) +} + +/** + * Try to apply an update to the given document + * + * @param {string} projectId + * @param {string} docId + * @param {HistoryV1OTEditOperationUpdate} update + * @param {Profiler} profiler + */ +async function tryApplyUpdate(projectId, docId, update, profiler) { + let { lines, version, pathname, type } = + await DocumentManager.promises.getDoc(projectId, docId) + profiler.log('getDoc') + + if (lines == null || version == null) { + throw new Errors.NotFoundError(`document not found: ${docId}`) + } + if (type !== 'history-ot') { + throw new Errors.OTTypeMismatchError(type, 'history-ot') + } + + let op = EditOperationBuilder.fromJSON(update.op[0]) + if (version !== update.v) { + const transformUpdates = await RedisManager.promises.getPreviousDocOps( + docId, + update.v, + version + ) + for (const transformUpdate of transformUpdates) { + if (!isHistoryOTEditOperationUpdate(transformUpdate)) { + throw new Errors.OTTypeMismatchError('sharejs-text-ot', 'history-ot') + } + + if ( + transformUpdate.meta.source && + update.dupIfSource?.includes(transformUpdate.meta.source) + ) { + update.dup = true + break + } + const other = EditOperationBuilder.fromJSON(transformUpdate.op[0]) + op = EditOperationTransformer.transform(op, other)[0] + } + update.op = [op.toJSON()] + } + + if (!update.dup) { + const file = StringFileData.fromRaw(lines) + file.edit(op) + version += 1 + update.meta.ts = Date.now() + await RedisManager.promises.updateDocument( + projectId, + docId, + file.toRaw(), + version, + [update], + {}, + update.meta + ) + + Metrics.inc('history-queue', 1, { status: 'project-history' }) + try { + const projectOpsLength = + await ProjectHistoryRedisManager.promises.queueOps(projectId, [ + JSON.stringify({ + ...update, + meta: { + ...update.meta, + pathname, + }, + }), + ]) + HistoryManager.recordAndFlushHistoryOps( + projectId, + [update], + projectOpsLength + ) + profiler.log('recordAndFlushHistoryOps') + } catch (err) { + // The full project history can re-sync a project in case + // updates went missing. + // Just record the error here and acknowledge the write-op. + Metrics.inc('history-queue-error') + } + } + RealTimeRedisManager.sendData({ + project_id: projectId, + doc_id: docId, + op: update, + }) +} + +/** + * Apply an update to the given document + * + * @param {string} projectId + * @param {string} docId + * @param {HistoryV1OTEditOperationUpdate} update + */ +async function applyUpdate(projectId, docId, update) { + const profiler = new Profiler('applyUpdate', { + project_id: projectId, + doc_id: docId, + type: 'history-ot', + }) + + try { + await tryApplyUpdate(projectId, docId, update, profiler) + } catch (error) { + RealTimeRedisManager.sendData({ + project_id: projectId, + doc_id: docId, + error: error instanceof Error ? error.message : error, + }) + profiler.log('sendData') + throw error + } finally { + profiler.end() + } +} + +module.exports = { isHistoryOTEditOperationUpdate, applyUpdate } diff --git a/services/document-updater/app/js/HttpController.js b/services/document-updater/app/js/HttpController.js index 95fe9b7ba9..2a7a40d8b5 100644 --- a/services/document-updater/app/js/HttpController.js +++ b/services/document-updater/app/js/HttpController.js @@ -9,6 +9,7 @@ const Metrics = require('./Metrics') const DeleteQueueManager = require('./DeleteQueueManager') const { getTotalSizeOfLines } = require('./Limits') const async = require('async') +const { StringFileData } = require('overleaf-editor-core') function getDoc(req, res, next) { let fromVersion @@ -27,7 +28,7 @@ function getDoc(req, res, next) { projectId, docId, fromVersion, - (error, lines, version, ops, ranges, pathname) => { + (error, lines, version, ops, ranges, pathname, _projectHistoryId, type) => { timer.done() if (error) { return next(error) @@ -36,6 +37,11 @@ function getDoc(req, res, next) { if (lines == null || version == null) { return next(new Errors.NotFoundError('document not found')) } + if (!Array.isArray(lines) && req.query.historyV1OTSupport !== 'true') { + const file = StringFileData.fromRaw(lines) + // TODO(24596): tc support for history-ot + lines = file.getLines() + } res.json({ id: docId, lines, @@ -44,6 +50,7 @@ function getDoc(req, res, next) { ranges, pathname, ttlInS: RedisManager.DOC_OPS_TTL, + type, }) } ) @@ -84,6 +91,11 @@ function peekDoc(req, res, next) { if (lines == null || version == null) { return next(new Errors.NotFoundError('document not found')) } + if (!Array.isArray(lines) && req.query.historyV1OTSupport !== 'true') { + const file = StringFileData.fromRaw(lines) + // TODO(24596): tc support for history-ot + lines = file.getLines() + } res.json({ id: docId, lines, version }) }) } diff --git a/services/document-updater/app/js/PersistenceManager.js b/services/document-updater/app/js/PersistenceManager.js index b08994ae41..6e832f9aa7 100644 --- a/services/document-updater/app/js/PersistenceManager.js +++ b/services/document-updater/app/js/PersistenceManager.js @@ -95,6 +95,13 @@ function getDoc(projectId, docId, options = {}, _callback) { status: body.pathname === '' ? 'zero-length' : 'undefined', }) } + + if (body.otMigrationStage > 0) { + // Use history-ot + body.lines = { content: body.lines.join('\n') } + body.ranges = {} + } + callback( null, body.lines, diff --git a/services/document-updater/app/js/Profiler.js b/services/document-updater/app/js/Profiler.js index 8daac4ca41..aac8a9706e 100644 --- a/services/document-updater/app/js/Profiler.js +++ b/services/document-updater/app/js/Profiler.js @@ -1,68 +1,52 @@ -/* eslint-disable - no-unused-vars, -*/ -// TODO: This file was created by bulk-decaffeinate. -// Fix any style issues and re-enable lint. -/* - * decaffeinate suggestions: - * DS206: Consider reworking classes to avoid initClass - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md - */ -let Profiler -const Settings = require('@overleaf/settings') const logger = require('@overleaf/logger') -const deltaMs = function (ta, tb) { +function deltaMs(ta, tb) { const nanoSeconds = (ta[0] - tb[0]) * 1e9 + (ta[1] - tb[1]) const milliSeconds = Math.floor(nanoSeconds * 1e-6) return milliSeconds } -module.exports = Profiler = (function () { - Profiler = class Profiler { - static initClass() { - this.prototype.LOG_CUTOFF_TIME = 15 * 1000 - this.prototype.LOG_SYNC_CUTOFF_TIME = 1000 - } +class Profiler { + LOG_CUTOFF_TIME = 15 * 1000 + LOG_SYNC_CUTOFF_TIME = 1000 - constructor(name, args) { - this.name = name - this.args = args - this.t0 = this.t = process.hrtime() - this.start = new Date() - this.updateTimes = [] - this.totalSyncTime = 0 - } - - log(label, options = {}) { - const t1 = process.hrtime() - const dtMilliSec = deltaMs(t1, this.t) - this.t = t1 - this.totalSyncTime += options.sync ? dtMilliSec : 0 - this.updateTimes.push([label, dtMilliSec]) // timings in ms - return this // make it chainable - } - - end(message) { - const totalTime = deltaMs(this.t, this.t0) - const exceedsCutoff = totalTime > this.LOG_CUTOFF_TIME - const exceedsSyncCutoff = this.totalSyncTime > this.LOG_SYNC_CUTOFF_TIME - if (exceedsCutoff || exceedsSyncCutoff) { - // log anything greater than cutoffs - const args = {} - for (const k in this.args) { - const v = this.args[k] - args[k] = v - } - args.updateTimes = this.updateTimes - args.start = this.start - args.end = new Date() - args.status = { exceedsCutoff, exceedsSyncCutoff } - logger.warn(args, this.name) - } - return totalTime - } + constructor(name, args) { + this.name = name + this.args = args + this.t0 = this.t = process.hrtime() + this.start = new Date() + this.updateTimes = [] + this.totalSyncTime = 0 } - Profiler.initClass() - return Profiler -})() + + log(label, options = {}) { + const t1 = process.hrtime() + const dtMilliSec = deltaMs(t1, this.t) + this.t = t1 + this.totalSyncTime += options.sync ? dtMilliSec : 0 + this.updateTimes.push([label, dtMilliSec]) // timings in ms + return this // make it chainable + } + + end() { + const totalTime = deltaMs(this.t, this.t0) + const exceedsCutoff = totalTime > this.LOG_CUTOFF_TIME + const exceedsSyncCutoff = this.totalSyncTime > this.LOG_SYNC_CUTOFF_TIME + if (exceedsCutoff || exceedsSyncCutoff) { + // log anything greater than cutoffs + const args = {} + for (const k in this.args) { + const v = this.args[k] + args[k] = v + } + args.updateTimes = this.updateTimes + args.start = this.start + args.end = new Date() + args.status = { exceedsCutoff, exceedsSyncCutoff } + logger.warn(args, this.name) + } + return totalTime + } +} + +module.exports = Profiler diff --git a/services/document-updater/app/js/RedisManager.js b/services/document-updater/app/js/RedisManager.js index c361f34165..7f86036427 100644 --- a/services/document-updater/app/js/RedisManager.js +++ b/services/document-updater/app/js/RedisManager.js @@ -48,6 +48,7 @@ const RedisManager = { timer.done() _callback(error) } + const shareJSTextOT = Array.isArray(docLines) const docLinesArray = docLines docLines = JSON.stringify(docLines) if (docLines.indexOf('\u0000') !== -1) { @@ -60,7 +61,10 @@ const RedisManager = { // Do an optimised size check on the docLines using the serialised // length as an upper bound const sizeBound = docLines.length - if (docIsTooLarge(sizeBound, docLinesArray, Settings.max_doc_length)) { + if ( + shareJSTextOT && // editor-core has a size check in TextOperation.apply and TextOperation.applyToLength. + docIsTooLarge(sizeBound, docLinesArray, Settings.max_doc_length) + ) { const docSize = docLines.length const err = new Error('blocking doc insert into redis: doc is too large') logger.error({ projectId, docId, err, docSize }, err.message) @@ -324,13 +328,6 @@ const RedisManager = { } catch (e) { return callback(e) } - if (docLines != null && !Array.isArray(docLines)) { - return callback( - new Errors.ProjectMigratedToHistoryOTError( - 'refusing to process doc that was migrated to history-ot' - ) - ) - } version = parseInt(version || 0, 10) // check doc is in requested project @@ -468,6 +465,7 @@ const RedisManager = { if (appliedOps == null) { appliedOps = [] } + const shareJSTextOT = Array.isArray(docLines) RedisManager.getDocVersion(docId, (error, currentVersion) => { if (error) { return callback(error) @@ -507,7 +505,10 @@ const RedisManager = { // Do an optimised size check on the docLines using the serialised // length as an upper bound const sizeBound = newDocLines.length - if (docIsTooLarge(sizeBound, docLines, Settings.max_doc_length)) { + if ( + shareJSTextOT && // editor-core has a size check in TextOperation.apply and TextOperation.applyToLength. + docIsTooLarge(sizeBound, docLines, Settings.max_doc_length) + ) { const err = new Error('blocking doc update: doc is too large') const docSize = newDocLines.length logger.error({ projectId, docId, err, docSize }, err.message) diff --git a/services/document-updater/app/js/UpdateManager.js b/services/document-updater/app/js/UpdateManager.js index 1f58a751f7..ba37018a14 100644 --- a/services/document-updater/app/js/UpdateManager.js +++ b/services/document-updater/app/js/UpdateManager.js @@ -15,9 +15,10 @@ const RangesManager = require('./RangesManager') const SnapshotManager = require('./SnapshotManager') const Profiler = require('./Profiler') const { isInsert, isDelete, getDocLength, computeDocHash } = require('./Utils') +const HistoryV1OTUpdateManager = require('./HistoryV1OTUpdateManager') /** - * @import { DeleteOp, InsertOp, Op, Ranges, Update, HistoryUpdate } from "./types" + * @import { Ranges, Update, HistoryUpdate } from "./types" */ const UpdateManager = { @@ -80,7 +81,11 @@ const UpdateManager = { profile.log('getPendingUpdatesForDoc') for (const update of updates) { - await UpdateManager.applyUpdate(projectId, docId, update) + if (HistoryV1OTUpdateManager.isHistoryOTEditOperationUpdate(update)) { + await HistoryV1OTUpdateManager.applyUpdate(projectId, docId, update) + } else { + await UpdateManager.applyUpdate(projectId, docId, update) + } profile.log('applyUpdate') } profile.log('async done').end() @@ -110,12 +115,16 @@ const UpdateManager = { pathname, projectHistoryId, historyRangesSupport, + type, } = await DocumentManager.promises.getDoc(projectId, docId) profile.log('getDoc') if (lines == null || version == null) { throw new Errors.NotFoundError(`document not found: ${docId}`) } + if (type !== 'sharejs-text-ot') { + throw new Errors.OTTypeMismatchError(type, 'sharejs-text-ot') + } const previousVersion = version const incomingUpdateVersion = update.v diff --git a/services/document-updater/app/js/types.ts b/services/document-updater/app/js/types.ts index b3085adb1b..d630e7fc82 100644 --- a/services/document-updater/app/js/types.ts +++ b/services/document-updater/app/js/types.ts @@ -1,12 +1,17 @@ import { TrackingPropsRawData, ClearTrackingPropsRawData, + RawEditOperation, } from 'overleaf-editor-core/lib/types' +export type OTType = 'sharejs-text-ot' | 'history-ot' + /** * An update coming from the editor */ export type Update = { + dup?: boolean + dupIfSource?: string[] doc: string op: Op[] v: number @@ -18,6 +23,11 @@ export type Update = { projectHistoryId?: string } +export type HistoryV1OTEditOperationUpdate = Omit & { + op: RawEditOperation[] + meta: Update['meta'] & { source: string } +} + export type Op = InsertOp | DeleteOp | CommentOp | RetainOp export type InsertOp = { diff --git a/services/document-updater/package.json b/services/document-updater/package.json index 4fb45d6cd4..7d892689e9 100644 --- a/services/document-updater/package.json +++ b/services/document-updater/package.json @@ -34,6 +34,7 @@ "lodash": "^4.17.21", "minimist": "^1.2.8", "mongodb-legacy": "6.1.3", + "overleaf-editor-core": "*", "request": "^2.88.2", "requestretry": "^7.1.0" }, diff --git a/services/document-updater/test/acceptance/js/ApplyingUpdatesToADocTests.js b/services/document-updater/test/acceptance/js/ApplyingUpdatesToADocTests.js index 0df2e72a08..32a94f37cb 100644 --- a/services/document-updater/test/acceptance/js/ApplyingUpdatesToADocTests.js +++ b/services/document-updater/test/acceptance/js/ApplyingUpdatesToADocTests.js @@ -31,6 +31,12 @@ describe('Applying updates to a doc', function () { op: [this.op], v: this.version, } + this.historyV1OTUpdate = { + doc: this.doc_id, + op: [{ textOperation: [4, 'one and a half\n', 9] }], + v: this.version, + meta: { source: 'random-publicId' }, + } this.result = ['one', 'one and a half', 'two', 'three'] DocUpdaterApp.ensureRunning(done) }) @@ -284,6 +290,260 @@ describe('Applying updates to a doc', function () { }) }) + describe('when the document is not loaded (history-ot)', function () { + beforeEach(function (done) { + this.startTime = Date.now() + MockWebApi.insertDoc(this.project_id, this.doc_id, { + lines: this.lines, + version: this.version, + otMigrationStage: 1, + }) + DocUpdaterClient.sendUpdate( + this.project_id, + this.doc_id, + this.historyV1OTUpdate, + error => { + if (error != null) { + throw error + } + setTimeout(() => { + rclientProjectHistory.get( + ProjectHistoryKeys.projectHistoryFirstOpTimestamp({ + project_id: this.project_id, + }), + (error, result) => { + if (error != null) { + throw error + } + result = parseInt(result, 10) + this.firstOpTimestamp = result + done() + } + ) + }, 200) + } + ) + }) + + it('should load the document from the web API', function () { + MockWebApi.getDocument + .calledWith(this.project_id, this.doc_id) + .should.equal(true) + }) + + it('should update the doc', function (done) { + DocUpdaterClient.getDoc( + this.project_id, + this.doc_id, + (error, res, doc) => { + if (error) done(error) + doc.lines.should.deep.equal(this.result) + done() + } + ) + }) + + it('should push the applied updates to the project history changes api', function (done) { + rclientProjectHistory.lrange( + ProjectHistoryKeys.projectHistoryOps({ project_id: this.project_id }), + 0, + -1, + (error, updates) => { + if (error != null) { + throw error + } + JSON.parse(updates[0]).op.should.deep.equal(this.historyV1OTUpdate.op) + JSON.parse(updates[0]).meta.pathname.should.equal('/a/b/c.tex') + + done() + } + ) + }) + + it('should set the first op timestamp', function () { + this.firstOpTimestamp.should.be.within(this.startTime, Date.now()) + }) + + it('should yield last updated time', function (done) { + DocUpdaterClient.getProjectLastUpdatedAt( + this.project_id, + (error, res, body) => { + if (error != null) { + throw error + } + res.statusCode.should.equal(200) + body.lastUpdatedAt.should.be.within(this.startTime, Date.now()) + done() + } + ) + }) + + it('should yield no last updated time for another project', function (done) { + DocUpdaterClient.getProjectLastUpdatedAt( + DocUpdaterClient.randomId(), + (error, res, body) => { + if (error != null) { + throw error + } + res.statusCode.should.equal(200) + body.should.deep.equal({}) + done() + } + ) + }) + + describe('when sending another update', function () { + beforeEach(function (done) { + this.timeout(10000) + this.second_update = Object.assign({}, this.historyV1OTUpdate) + this.second_update.op = [ + { + textOperation: [4, 'one and a half\n', 24], + }, + ] + this.second_update.v = this.version + 1 + this.secondStartTime = Date.now() + DocUpdaterClient.sendUpdate( + this.project_id, + this.doc_id, + this.second_update, + error => { + if (error != null) { + throw error + } + setTimeout(done, 200) + } + ) + }) + + it('should update the doc', function (done) { + DocUpdaterClient.getDoc( + this.project_id, + this.doc_id, + (error, res, doc) => { + if (error) done(error) + doc.lines.should.deep.equal([ + 'one', + 'one and a half', + 'one and a half', + 'two', + 'three', + ]) + done() + } + ) + }) + + it('should not change the first op timestamp', function (done) { + rclientProjectHistory.get( + ProjectHistoryKeys.projectHistoryFirstOpTimestamp({ + project_id: this.project_id, + }), + (error, result) => { + if (error != null) { + throw error + } + result = parseInt(result, 10) + result.should.equal(this.firstOpTimestamp) + done() + } + ) + }) + + it('should yield last updated time', function (done) { + DocUpdaterClient.getProjectLastUpdatedAt( + this.project_id, + (error, res, body) => { + if (error != null) { + throw error + } + res.statusCode.should.equal(200) + body.lastUpdatedAt.should.be.within( + this.secondStartTime, + Date.now() + ) + done() + } + ) + }) + }) + + describe('when another client is sending a concurrent update', function () { + beforeEach(function (done) { + this.timeout(10000) + this.otherUpdate = { + doc: this.doc_id, + op: [{ textOperation: [8, 'two and a half\n', 5] }], + v: this.version, + meta: { source: 'other-random-publicId' }, + } + this.secondStartTime = Date.now() + DocUpdaterClient.sendUpdate( + this.project_id, + this.doc_id, + this.otherUpdate, + error => { + if (error != null) { + throw error + } + setTimeout(done, 200) + } + ) + }) + + it('should update the doc', function (done) { + DocUpdaterClient.getDoc( + this.project_id, + this.doc_id, + (error, res, doc) => { + if (error) done(error) + doc.lines.should.deep.equal([ + 'one', + 'one and a half', + 'two', + 'two and a half', + 'three', + ]) + done() + } + ) + }) + + it('should not change the first op timestamp', function (done) { + rclientProjectHistory.get( + ProjectHistoryKeys.projectHistoryFirstOpTimestamp({ + project_id: this.project_id, + }), + (error, result) => { + if (error != null) { + throw error + } + result = parseInt(result, 10) + result.should.equal(this.firstOpTimestamp) + done() + } + ) + }) + + it('should yield last updated time', function (done) { + DocUpdaterClient.getProjectLastUpdatedAt( + this.project_id, + (error, res, body) => { + if (error != null) { + throw error + } + res.statusCode.should.equal(200) + body.lastUpdatedAt.should.be.within( + this.secondStartTime, + Date.now() + ) + done() + } + ) + }) + }) + }) + describe('when the document is loaded', function () { beforeEach(function (done) { MockWebApi.insertDoc(this.project_id, this.doc_id, { @@ -390,6 +650,58 @@ describe('Applying updates to a doc', function () { }) }) + describe('when the document is loaded (history-ot)', function () { + beforeEach(function (done) { + MockWebApi.insertDoc(this.project_id, this.doc_id, { + lines: this.lines, + version: this.version, + otMigrationStage: 1, + }) + DocUpdaterClient.preloadDoc(this.project_id, this.doc_id, error => { + if (error != null) { + throw error + } + DocUpdaterClient.sendUpdate( + this.project_id, + this.doc_id, + this.historyV1OTUpdate, + error => { + if (error != null) { + throw error + } + setTimeout(done, 200) + } + ) + }) + }) + + it('should update the doc', function (done) { + DocUpdaterClient.getDoc( + this.project_id, + this.doc_id, + (error, res, doc) => { + if (error) return done(error) + doc.lines.should.deep.equal(this.result) + done() + } + ) + }) + + it('should push the applied updates to the project history changes api', function (done) { + rclientProjectHistory.lrange( + ProjectHistoryKeys.projectHistoryOps({ project_id: this.project_id }), + 0, + -1, + (error, updates) => { + if (error) return done(error) + JSON.parse(updates[0]).op.should.deep.equal(this.historyV1OTUpdate.op) + JSON.parse(updates[0]).meta.pathname.should.equal('/a/b/c.tex') + done() + } + ) + }) + }) + describe('when the document has been deleted', function () { describe('when the ops come in a single linear order', function () { beforeEach(function (done) { @@ -596,6 +908,160 @@ describe('Applying updates to a doc', function () { }) }) + describe('with a broken update (history-ot)', function () { + beforeEach(function (done) { + this.broken_update = { + doc: this.doc_id, + v: this.version, + op: [{ textOperation: [99, -1] }], + meta: { source: '42' }, + } + MockWebApi.insertDoc(this.project_id, this.doc_id, { + lines: this.lines, + version: this.version, + otMigrationStage: 1, + }) + + DocUpdaterClient.subscribeToAppliedOps( + (this.messageCallback = sinon.stub()) + ) + + DocUpdaterClient.sendUpdate( + this.project_id, + this.doc_id, + this.broken_update, + error => { + if (error != null) { + throw error + } + setTimeout(done, 200) + } + ) + }) + + it('should not update the doc', function (done) { + DocUpdaterClient.getDoc( + this.project_id, + this.doc_id, + (error, res, doc) => { + if (error) return done(error) + doc.lines.should.deep.equal(this.lines) + done() + } + ) + }) + + it('should send a message with an error', function () { + this.messageCallback.called.should.equal(true) + const [channel, message] = this.messageCallback.args[0] + channel.should.equal('applied-ops') + JSON.parse(message).should.deep.include({ + project_id: this.project_id, + doc_id: this.doc_id, + error: + "The operation's base length must be equal to the string's length.", + }) + }) + }) + + describe('when mixing ot types (sharejs-text-ot -> history-ot)', function () { + beforeEach(function (done) { + MockWebApi.insertDoc(this.project_id, this.doc_id, { + lines: this.lines, + version: this.version, + otMigrationStage: 0, + }) + + DocUpdaterClient.subscribeToAppliedOps( + (this.messageCallback = sinon.stub()) + ) + + DocUpdaterClient.sendUpdate( + this.project_id, + this.doc_id, + this.historyV1OTUpdate, + error => { + if (error != null) { + throw error + } + setTimeout(done, 200) + } + ) + }) + + it('should not update the doc', function (done) { + DocUpdaterClient.getDoc( + this.project_id, + this.doc_id, + (error, res, doc) => { + if (error) return done(error) + doc.lines.should.deep.equal(this.lines) + done() + } + ) + }) + + it('should send a message with an error', function () { + this.messageCallback.called.should.equal(true) + const [channel, message] = this.messageCallback.args[0] + channel.should.equal('applied-ops') + JSON.parse(message).should.deep.include({ + project_id: this.project_id, + doc_id: this.doc_id, + error: 'ot type mismatch', + }) + }) + }) + + describe('when mixing ot types (history-ot -> sharejs-text-ot)', function () { + beforeEach(function (done) { + MockWebApi.insertDoc(this.project_id, this.doc_id, { + lines: this.lines, + version: this.version, + otMigrationStage: 1, + }) + + DocUpdaterClient.subscribeToAppliedOps( + (this.messageCallback = sinon.stub()) + ) + + DocUpdaterClient.sendUpdate( + this.project_id, + this.doc_id, + this.update, + error => { + if (error != null) { + throw error + } + setTimeout(done, 200) + } + ) + }) + + it('should not update the doc', function (done) { + DocUpdaterClient.getDoc( + this.project_id, + this.doc_id, + (error, res, doc) => { + if (error) return done(error) + doc.lines.should.deep.equal(this.lines) + done() + } + ) + }) + + it('should send a message with an error', function () { + this.messageCallback.called.should.equal(true) + const [channel, message] = this.messageCallback.args[0] + channel.should.equal('applied-ops') + JSON.parse(message).should.deep.include({ + project_id: this.project_id, + doc_id: this.doc_id, + error: 'ot type mismatch', + }) + }) + }) + describe('when there is no version in Mongo', function () { beforeEach(function (done) { MockWebApi.insertDoc(this.project_id, this.doc_id, { @@ -716,6 +1182,84 @@ describe('Applying updates to a doc', function () { }) }) + describe('when sending duplicate ops (history-ot)', function () { + beforeEach(function (done) { + MockWebApi.insertDoc(this.project_id, this.doc_id, { + lines: this.lines, + version: this.version, + otMigrationStage: 1, + }) + + DocUpdaterClient.subscribeToAppliedOps( + (this.messageCallback = sinon.stub()) + ) + + // One user delete 'one', the next turns it into 'once'. The second becomes a NOP. + DocUpdaterClient.sendUpdate( + this.project_id, + this.doc_id, + { + doc: this.doc_id, + op: [{ textOperation: [4, 'one and a half\n', 9] }], + v: this.version, + meta: { + source: 'ikHceq3yfAdQYzBo4-xZ', + }, + }, + error => { + if (error != null) { + throw error + } + setTimeout(() => { + DocUpdaterClient.sendUpdate( + this.project_id, + this.doc_id, + { + doc: this.doc_id, + op: [ + { + textOperation: [4, 'one and a half\n', 9], + }, + ], + v: this.version, + dupIfSource: ['ikHceq3yfAdQYzBo4-xZ'], + meta: { + source: 'ikHceq3yfAdQYzBo4-xZ', + }, + }, + error => { + if (error != null) { + throw error + } + setTimeout(done, 200) + } + ) + }, 200) + } + ) + }) + + it('should update the doc', function (done) { + DocUpdaterClient.getDoc( + this.project_id, + this.doc_id, + (error, res, doc) => { + if (error) return done(error) + doc.lines.should.deep.equal(this.result) + done() + } + ) + }) + + it('should return a message about duplicate ops', function () { + this.messageCallback.calledTwice.should.equal(true) + this.messageCallback.args[0][0].should.equal('applied-ops') + expect(JSON.parse(this.messageCallback.args[0][1]).op.dup).to.be.undefined + this.messageCallback.args[1][0].should.equal('applied-ops') + expect(JSON.parse(this.messageCallback.args[1][1]).op.dup).to.equal(true) + }) + }) + describe('when sending updates for a non-existing doc id', function () { beforeEach(function (done) { this.non_existing = { diff --git a/services/document-updater/test/acceptance/js/GettingADocumentTests.js b/services/document-updater/test/acceptance/js/GettingADocumentTests.js index 6f52f49fb9..65298932d9 100644 --- a/services/document-updater/test/acceptance/js/GettingADocumentTests.js +++ b/services/document-updater/test/acceptance/js/GettingADocumentTests.js @@ -13,11 +13,6 @@ const { expect } = require('chai') const MockWebApi = require('./helpers/MockWebApi') const DocUpdaterClient = require('./helpers/DocUpdaterClient') const DocUpdaterApp = require('./helpers/DocUpdaterApp') -const Settings = require('@overleaf/settings') -const docUpdaterRedis = require('@overleaf/redis-wrapper').createClient( - Settings.redis.documentupdater -) -const Keys = Settings.redis.documentupdater.key_schema describe('Getting a document', function () { before(function (done) { @@ -114,59 +109,6 @@ describe('Getting a document', function () { }) }) - describe('when the document is migrated (history-ot)', function () { - before(function (done) { - ;[this.project_id, this.doc_id] = Array.from([ - DocUpdaterClient.randomId(), - DocUpdaterClient.randomId(), - ]) - - MockWebApi.insertDoc(this.project_id, this.doc_id, { - lines: this.lines, - version: this.version, - }) - DocUpdaterClient.preloadDoc(this.project_id, this.doc_id, error => { - if (error != null) { - throw error - } - sinon.spy(MockWebApi, 'getDocument') - docUpdaterRedis.set( - Keys.docLines({ doc_id: this.doc_id }), - JSON.stringify({ content: this.lines.join('\n') }), - err => { - if (err) return done(err) - - DocUpdaterClient.getDoc( - this.project_id, - this.doc_id, - (error, res, body) => { - if (error) return done(error) - this.res = res - this.body = body - done() - } - ) - } - ) - }) - }) - - after(function () { - MockWebApi.getDocument.restore() - }) - - it('should not load the document from the web API', function () { - MockWebApi.getDocument.called.should.equal(false) - }) - - it('should return an error', function () { - expect(this.res.statusCode).to.equal(422) - expect(this.body).to.equal( - 'refusing to process doc that was migrated to history-ot' - ) - }) - }) - describe('when the request asks for some recent ops', function () { before(function (done) { ;[this.project_id, this.doc_id] = Array.from([ diff --git a/services/document-updater/test/acceptance/js/SettingADocumentTests.js b/services/document-updater/test/acceptance/js/SettingADocumentTests.js index 5b0c4ab281..83e662185a 100644 --- a/services/document-updater/test/acceptance/js/SettingADocumentTests.js +++ b/services/document-updater/test/acceptance/js/SettingADocumentTests.js @@ -196,6 +196,167 @@ describe('Setting a document', function () { }) }) + describe('when the updated doc exists in the doc updater (history-ot)', function () { + before(function (done) { + numberOfReceivedUpdates = 0 + this.project_id = DocUpdaterClient.randomId() + this.doc_id = DocUpdaterClient.randomId() + this.historyV1OTUpdate = { + doc: this.doc_id, + op: [{ textOperation: [4, 'one and a half\n', 9] }], + v: this.version, + meta: { source: 'random-publicId' }, + } + MockWebApi.insertDoc(this.project_id, this.doc_id, { + lines: this.lines, + version: this.version, + otMigrationStage: 1, + }) + DocUpdaterClient.preloadDoc(this.project_id, this.doc_id, error => { + if (error) { + throw error + } + DocUpdaterClient.sendUpdate( + this.project_id, + this.doc_id, + this.historyV1OTUpdate, + error => { + if (error) { + throw error + } + setTimeout(() => { + DocUpdaterClient.setDocLines( + this.project_id, + this.doc_id, + this.newLines, + this.source, + this.user_id, + false, + (error, res, body) => { + if (error) { + return done(error) + } + this.statusCode = res.statusCode + this.body = body + done() + } + ) + }, 200) + } + ) + }) + }) + + after(function () { + MockProjectHistoryApi.flushProject.resetHistory() + MockWebApi.setDocument.resetHistory() + }) + + it('should return a 200 status code', function () { + this.statusCode.should.equal(200) + }) + + it('should emit two updates (from sendUpdate and setDocLines)', function () { + expect(numberOfReceivedUpdates).to.equal(2) + }) + + it('should send the updated doc lines and version to the web api', function () { + MockWebApi.setDocument + .calledWith(this.project_id, this.doc_id, this.newLines) + .should.equal(true) + }) + + it('should update the lines in the doc updater', function (done) { + DocUpdaterClient.getDoc( + this.project_id, + this.doc_id, + (error, res, doc) => { + if (error) { + return done(error) + } + doc.lines.should.deep.equal(this.newLines) + done() + } + ) + }) + + it('should bump the version in the doc updater', function (done) { + DocUpdaterClient.getDoc( + this.project_id, + this.doc_id, + (error, res, doc) => { + if (error) { + return done(error) + } + doc.version.should.equal(this.version + 2) + done() + } + ) + }) + + it('should leave the document in redis', function (done) { + docUpdaterRedis.get( + Keys.docLines({ doc_id: this.doc_id }), + (error, lines) => { + if (error) { + throw error + } + expect(JSON.parse(lines)).to.deep.equal({ + content: this.newLines.join('\n'), + }) + done() + } + ) + }) + + it('should return the mongo rev in the json response', function () { + this.body.should.deep.equal({ rev: '123' }) + }) + + describe('when doc has the same contents', function () { + beforeEach(function (done) { + numberOfReceivedUpdates = 0 + DocUpdaterClient.setDocLines( + this.project_id, + this.doc_id, + this.newLines, + this.source, + this.user_id, + false, + (error, res, body) => { + if (error) { + return done(error) + } + this.statusCode = res.statusCode + this.body = body + done() + } + ) + }) + + it('should not bump the version in doc updater', function (done) { + DocUpdaterClient.getDoc( + this.project_id, + this.doc_id, + (error, res, doc) => { + if (error) { + return done(error) + } + doc.version.should.equal(this.version + 2) + done() + } + ) + }) + + it('should not emit any updates', function (done) { + setTimeout(() => { + expect(numberOfReceivedUpdates).to.equal(0) + done() + }, 100) // delay by 100ms: make sure we do not check too early! + }) + }) + }) + describe('when the updated doc does not exist in the doc updater', function () { before(function (done) { this.project_id = DocUpdaterClient.randomId() diff --git a/services/document-updater/test/setup.js b/services/document-updater/test/setup.js index 1099724329..8ba17d922f 100644 --- a/services/document-updater/test/setup.js +++ b/services/document-updater/test/setup.js @@ -31,6 +31,7 @@ SandboxedModule.configure({ requires: { '@overleaf/logger': stubs.logger, 'mongodb-legacy': require('mongodb-legacy'), // for ObjectId comparisons + 'overleaf-editor-core': require('overleaf-editor-core'), // does not play nice with sandbox }, globals: { Buffer, JSON, Math, console, process }, sourceTransformers: { diff --git a/services/document-updater/test/unit/js/DocumentManager/DocumentManagerTests.js b/services/document-updater/test/unit/js/DocumentManager/DocumentManagerTests.js index e9d68ee414..f0effb4c24 100644 --- a/services/document-updater/test/unit/js/DocumentManager/DocumentManagerTests.js +++ b/services/document-updater/test/unit/js/DocumentManager/DocumentManagerTests.js @@ -49,6 +49,9 @@ describe('DocumentManager', function () { applyUpdate: sinon.stub().resolves(), }, } + this.HistoryV1OTUpdateManager = { + applyUpdate: sinon.stub().resolves(), + } this.RangesManager = { acceptChanges: sinon.stub(), deleteComment: sinon.stub(), @@ -66,6 +69,7 @@ describe('DocumentManager', function () { './Metrics': this.Metrics, './DiffCodec': this.DiffCodec, './UpdateManager': this.UpdateManager, + './HistoryV1OTUpdateManager': this.HistoryV1OTUpdateManager, './RangesManager': this.RangesManager, './Errors': Errors, '@overleaf/settings': this.Settings, @@ -222,6 +226,7 @@ describe('DocumentManager', function () { ranges: this.ranges, pathname: this.pathname, projectHistoryId: this.projectHistoryId, + type: 'sharejs-text-ot', }) this.RedisManager.promises.getPreviousDocOps.resolves(this.ops) this.result = await this.DocumentManager.promises.getDocAndRecentOps( @@ -251,6 +256,7 @@ describe('DocumentManager', function () { ranges: this.ranges, pathname: this.pathname, projectHistoryId: this.projectHistoryId, + type: 'sharejs-text-ot', }) }) }) @@ -263,6 +269,7 @@ describe('DocumentManager', function () { ranges: this.ranges, pathname: this.pathname, projectHistoryId: this.projectHistoryId, + type: 'sharejs-text-ot', }) this.RedisManager.promises.getPreviousDocOps.resolves(this.ops) this.result = await this.DocumentManager.promises.getDocAndRecentOps( @@ -290,6 +297,7 @@ describe('DocumentManager', function () { ranges: this.ranges, pathname: this.pathname, projectHistoryId: this.projectHistoryId, + type: 'sharejs-text-ot', }) }) }) @@ -333,6 +341,7 @@ describe('DocumentManager', function () { unflushedTime: this.unflushedTime, alreadyLoaded: true, historyRangesSupport: this.historyRangesSupport, + type: 'sharejs-text-ot', }) }) }) @@ -400,6 +409,7 @@ describe('DocumentManager', function () { unflushedTime: null, alreadyLoaded: false, historyRangesSupport: this.historyRangesSupport, + type: 'sharejs-text-ot', }) }) }) diff --git a/services/document-updater/test/unit/js/HttpController/HttpControllerTests.js b/services/document-updater/test/unit/js/HttpController/HttpControllerTests.js index 2b8d288ef8..333da10d15 100644 --- a/services/document-updater/test/unit/js/HttpController/HttpControllerTests.js +++ b/services/document-updater/test/unit/js/HttpController/HttpControllerTests.js @@ -26,6 +26,7 @@ describe('HttpController', function () { this.Metrics.Timer.prototype.done = sinon.stub() this.project_id = 'project-id-123' + this.projectHistoryId = '123' this.doc_id = 'doc-id-123' this.source = 'editor' this.next = sinon.stub() @@ -65,7 +66,9 @@ describe('HttpController', function () { this.version, [], this.ranges, - this.pathname + this.pathname, + this.projectHistoryId, + 'sharejs-text-ot' ) this.HttpController.getDoc(this.req, this.res, this.next) }) @@ -77,17 +80,16 @@ describe('HttpController', function () { }) it('should return the doc as JSON', function () { - this.res.json - .calledWith({ - id: this.doc_id, - lines: this.lines, - version: this.version, - ops: [], - ranges: this.ranges, - pathname: this.pathname, - ttlInS: 42, - }) - .should.equal(true) + this.res.json.should.have.been.calledWith({ + id: this.doc_id, + lines: this.lines, + version: this.version, + ops: [], + ranges: this.ranges, + pathname: this.pathname, + ttlInS: 42, + type: 'sharejs-text-ot', + }) }) it('should log the request', function () { @@ -115,7 +117,9 @@ describe('HttpController', function () { this.version, this.ops, this.ranges, - this.pathname + this.pathname, + this.projectHistoryId, + 'sharejs-text-ot' ) this.req.query = { fromVersion: `${this.fromVersion}` } this.HttpController.getDoc(this.req, this.res, this.next) @@ -128,17 +132,16 @@ describe('HttpController', function () { }) it('should return the doc as JSON', function () { - this.res.json - .calledWith({ - id: this.doc_id, - lines: this.lines, - version: this.version, - ops: this.ops, - ranges: this.ranges, - pathname: this.pathname, - ttlInS: 42, - }) - .should.equal(true) + this.res.json.should.have.been.calledWith({ + id: this.doc_id, + lines: this.lines, + version: this.version, + ops: this.ops, + ranges: this.ranges, + pathname: this.pathname, + ttlInS: 42, + type: 'sharejs-text-ot', + }) }) it('should log the request', function () { diff --git a/services/document-updater/test/unit/js/UpdateManager/UpdateManagerTests.js b/services/document-updater/test/unit/js/UpdateManager/UpdateManagerTests.js index dba66456b7..912707e01d 100644 --- a/services/document-updater/test/unit/js/UpdateManager/UpdateManagerTests.js +++ b/services/document-updater/test/unit/js/UpdateManager/UpdateManagerTests.js @@ -331,6 +331,7 @@ describe('UpdateManager', function () { pathname: this.pathname, projectHistoryId: this.projectHistoryId, historyRangesSupport: false, + type: 'sharejs-text-ot', }) this.RangesManager.applyUpdate.returns({ newRanges: this.updated_ranges, @@ -502,6 +503,7 @@ describe('UpdateManager', function () { pathname: this.pathname, projectHistoryId: this.projectHistoryId, historyRangesSupport: true, + type: 'sharejs-text-ot', }) await this.UpdateManager.promises.applyUpdate( this.project_id, diff --git a/services/project-history/app/js/UpdateCompressor.js b/services/project-history/app/js/UpdateCompressor.js index b548b7529e..726cb5fff9 100644 --- a/services/project-history/app/js/UpdateCompressor.js +++ b/services/project-history/app/js/UpdateCompressor.js @@ -2,6 +2,7 @@ import OError from '@overleaf/o-error' import DMP from 'diff-match-patch' +import { EditOperationBuilder } from 'overleaf-editor-core' /** * @import { DeleteOp, InsertOp, Op, Update } from './types' @@ -230,6 +231,15 @@ function _concatTwoUpdates(firstUpdate, secondUpdate) { return [firstUpdate, secondUpdate] } + const firstUpdateIsHistoryV1OT = EditOperationBuilder.isValid(firstUpdate.op) + const secondUpdateIsHistoryV1OT = EditOperationBuilder.isValid( + secondUpdate.op + ) + if (firstUpdateIsHistoryV1OT !== secondUpdateIsHistoryV1OT) { + // cannot merge mix of sharejs-text-op and history-ot, should not happen. + return [firstUpdate, secondUpdate] + } + if ( firstUpdate.doc !== secondUpdate.doc || firstUpdate.pathname !== secondUpdate.pathname @@ -276,6 +286,15 @@ function _concatTwoUpdates(firstUpdate, secondUpdate) { return [firstUpdate, secondUpdate] } + if (firstUpdateIsHistoryV1OT && secondUpdateIsHistoryV1OT) { + const op1 = EditOperationBuilder.fromJSON(firstUpdate.op) + const op2 = EditOperationBuilder.fromJSON(secondUpdate.op) + if (!op1.canBeComposedWith(op2)) return [firstUpdate, secondUpdate] + return [ + mergeUpdatesWithOp(firstUpdate, secondUpdate, op1.compose(op2).toJSON()), + ] + } + if ( firstUpdate.op.trackedDeleteRejection || secondUpdate.op.trackedDeleteRejection @@ -440,8 +459,7 @@ export function diffAsShareJsOps(before, after) { const ops = [] let position = 0 for (const diff of diffs) { - const type = diff[0] - const content = diff[1] + const [type, content] = diff if (type === ADDED) { ops.push({ i: content, diff --git a/services/project-history/app/js/UpdateTranslator.js b/services/project-history/app/js/UpdateTranslator.js index 38e65f6968..c1443ab1cd 100644 --- a/services/project-history/app/js/UpdateTranslator.js +++ b/services/project-history/app/js/UpdateTranslator.js @@ -7,7 +7,7 @@ import * as OperationsCompressor from './OperationsCompressor.js' import { isInsert, isRetain, isDelete, isComment } from './Utils.js' /** - * @import { AddDocUpdate, AddFileUpdate, DeleteCommentUpdate, Op, RawScanOp } from './types' + * @import { AddDocUpdate, AddFileUpdate, DeleteCommentUpdate, HistoryV1OTEditOperationUpdate, Op, RawScanOp } from './types' * @import { RenameUpdate, TextUpdate, TrackingDirective, TrackingProps } from './types' * @import { SetCommentStateUpdate, SetFileMetadataOperation, Update, UpdateWithBlob } from './types' */ @@ -60,6 +60,16 @@ function _convertToChange(projectId, updateWithBlob) { } operations = [op] projectVersion = update.version + } else if (isHistoryOTEditOperationUpdate(update)) { + let { pathname } = update.meta + pathname = _convertPathname(pathname) + if (update.v != null) { + v2DocVersions[update.doc] = { pathname, v: update.v } + } + operations = update.op.map(op => { + // Turn EditOperation into EditFileOperation by adding the pathname field. + return { pathname, ...op } + }) } else if (isTextUpdate(update)) { const docLength = update.meta.history_doc_length ?? update.meta.doc_length let pathname = update.meta.pathname @@ -194,6 +204,22 @@ export function isTextUpdate(update) { ) } +/** + * @param {Update} update + * @returns {update is HistoryV1OTEditOperationUpdate} + */ +export function isHistoryOTEditOperationUpdate(update) { + return ( + 'doc' in update && + update.doc != null && + 'op' in update && + update.op != null && + 'pathname' in update.meta && + update.meta.pathname != null && + Core.EditOperationBuilder.isValid(update.op[0]) + ) +} + export function isProjectStructureUpdate(update) { return isAddUpdate(update) || _isRenameUpdate(update) } diff --git a/services/project-history/app/js/types.ts b/services/project-history/app/js/types.ts index c2b0d83728..23911b0086 100644 --- a/services/project-history/app/js/types.ts +++ b/services/project-history/app/js/types.ts @@ -1,5 +1,9 @@ import { HistoryRanges } from '../../../document-updater/app/js/types' -import { LinkedFileData, RawOrigin } from 'overleaf-editor-core/lib/types' +import { + LinkedFileData, + RawEditOperation, + RawOrigin, +} from 'overleaf-editor-core/lib/types' export type Update = | TextUpdate @@ -40,6 +44,15 @@ export type TextUpdate = { } } +export type HistoryV1OTEditOperationUpdate = { + doc: string + op: RawEditOperation[] + v: number + meta: UpdateMeta & { + pathname: string + } +} + export type SetCommentStateUpdate = { pathname: string commentId: string diff --git a/services/real-time/app/js/ConnectedUsersManager.js b/services/real-time/app/js/ConnectedUsersManager.js index 1421e8eeef..9b26add4c9 100644 --- a/services/real-time/app/js/ConnectedUsersManager.js +++ b/services/real-time/app/js/ConnectedUsersManager.js @@ -29,6 +29,10 @@ function recordProjectNotEmptySinceMetric(res, status) { } module.exports = { + countConnectedClients(projectId, callback) { + rclient.scard(Keys.clientsInProject({ project_id: projectId }), callback) + }, + // Use the same method for when a user connects, and when a user sends a cursor // update. This way we don't care if the connected_user key has expired when // we receive a cursor update. diff --git a/services/real-time/app/js/DocumentUpdaterManager.js b/services/real-time/app/js/DocumentUpdaterManager.js index 0a9a12c99d..72a37d0f1d 100644 --- a/services/real-time/app/js/DocumentUpdaterManager.js +++ b/services/real-time/app/js/DocumentUpdaterManager.js @@ -19,7 +19,7 @@ const Keys = settings.redis.documentupdater.key_schema const DocumentUpdaterManager = { getDocument(projectId, docId, fromVersion, callback) { const timer = new metrics.Timer('get-document') - const url = `${settings.apis.documentupdater.url}/project/${projectId}/doc/${docId}?fromVersion=${fromVersion}` + const url = `${settings.apis.documentupdater.url}/project/${projectId}/doc/${docId}?fromVersion=${fromVersion}&historyV1OTSupport=true` logger.debug( { projectId, docId, fromVersion }, 'getting doc from document updater' @@ -48,7 +48,8 @@ const DocumentUpdaterManager = { body.version, body.ranges, body.ops, - body.ttlInS + body.ttlInS, + body.type ) } else if (res.statusCode === 422 && body?.firstVersionInRedis) { callback(new ClientRequestedMissingOpsError(422, body)) diff --git a/services/real-time/app/js/HttpApiController.js b/services/real-time/app/js/HttpApiController.js index 122f1838be..5e75fe3601 100644 --- a/services/real-time/app/js/HttpApiController.js +++ b/services/real-time/app/js/HttpApiController.js @@ -1,8 +1,23 @@ const WebsocketLoadBalancer = require('./WebsocketLoadBalancer') const DrainManager = require('./DrainManager') +const ConnectedUsersManager = require('./ConnectedUsersManager') const logger = require('@overleaf/logger') module.exports = { + countConnectedClients(req, res) { + const { projectId } = req.params + ConnectedUsersManager.countConnectedClients( + projectId, + (err, nConnectedClients) => { + if (err) { + logger.err({ err, projectId }, 'count connected clients failed') + return res.sendStatus(500) + } + res.json({ nConnectedClients }) + } + ) + }, + sendMessage(req, res) { logger.debug({ message: req.params.message }, 'sending message') if (Array.isArray(req.body)) { diff --git a/services/real-time/app/js/Router.js b/services/real-time/app/js/Router.js index 238dc386a3..943453bc13 100644 --- a/services/real-time/app/js/Router.js +++ b/services/real-time/app/js/Router.js @@ -113,6 +113,10 @@ module.exports = Router = { bodyParser.json({ limit: '5mb' }), HttpApiController.sendMessage ) + app.get( + '/project/:projectId/count-connected-clients', + HttpApiController.countConnectedClients + ) app.post('/drain', HttpApiController.startDrain) app.post( diff --git a/services/real-time/app/js/WebsocketController.js b/services/real-time/app/js/WebsocketController.js index dec567709a..17186eb92e 100644 --- a/services/real-time/app/js/WebsocketController.js +++ b/services/real-time/app/js/WebsocketController.js @@ -8,6 +8,7 @@ const ConnectedUsersManager = require('./ConnectedUsersManager') const WebsocketLoadBalancer = require('./WebsocketLoadBalancer') const RoomManager = require('./RoomManager') const { + CodedError, JoinLeaveEpochMismatchError, NotAuthorizedError, NotJoinedError, @@ -283,7 +284,7 @@ module.exports = WebsocketController = { projectId, docId, fromVersion, - function (error, lines, version, ranges, ops, ttlInS) { + function (error, lines, version, ranges, ops, ttlInS, type) { if (error) { if (error instanceof ClientRequestedMissingOpsError) { emitJoinDocCatchUpMetrics('missing', error.info) @@ -307,36 +308,53 @@ module.exports = WebsocketController = { // See http://ecmanaut.blogspot.co.uk/2006/07/encoding-decoding-utf8-in-javascript.html const encodeForWebsockets = text => unescape(encodeURIComponent(text)) - const escapedLines = [] - for (let line of lines) { - try { - line = encodeForWebsockets(line) - } catch (err) { - OError.tag(err, 'error encoding line uri component', { line }) - return callback(err) + metrics.inc('client_supports_history_v1_ot', 1, { + status: options.supportsHistoryV1OT ? 'success' : 'failure', + }) + let escapedLines + if (type === 'history-ot') { + if (!options.supportsHistoryV1OT) { + RoomManager.leaveDoc(client, docId) + // TODO(24596): ask the user to reload the editor page (via out-of-sync modal when there are pending ops). + return callback( + new CodedError('client does not support history-ot') + ) } - escapedLines.push(line) - } - if (options.encodeRanges) { - try { - for (const comment of (ranges && ranges.comments) || []) { - if (comment.op.c) { - comment.op.c = encodeForWebsockets(comment.op.c) - } + escapedLines = lines + } else { + escapedLines = [] + for (let line of lines) { + try { + line = encodeForWebsockets(line) + } catch (err) { + OError.tag(err, 'error encoding line uri component', { + line, + }) + return callback(err) } - for (const change of (ranges && ranges.changes) || []) { - if (change.op.i) { - change.op.i = encodeForWebsockets(change.op.i) + escapedLines.push(line) + } + if (options.encodeRanges) { + try { + for (const comment of (ranges && ranges.comments) || []) { + if (comment.op.c) { + comment.op.c = encodeForWebsockets(comment.op.c) + } } - if (change.op.d) { - change.op.d = encodeForWebsockets(change.op.d) + for (const change of (ranges && ranges.changes) || []) { + if (change.op.i) { + change.op.i = encodeForWebsockets(change.op.i) + } + if (change.op.d) { + change.op.d = encodeForWebsockets(change.op.d) + } } + } catch (err) { + OError.tag(err, 'error encoding range uri component', { + ranges, + }) + return callback(err) } - } catch (err) { - OError.tag(err, 'error encoding range uri component', { - ranges, - }) - return callback(err) } } @@ -351,7 +369,7 @@ module.exports = WebsocketController = { }, 'client joined doc' ) - callback(null, escapedLines, version, ops, ranges) + callback(null, escapedLines, version, ops, ranges, type) } ) }) diff --git a/services/real-time/test/acceptance/js/ClientTrackingTests.js b/services/real-time/test/acceptance/js/ClientTrackingTests.js index 415e9ad662..d4b484c0a8 100644 --- a/services/real-time/test/acceptance/js/ClientTrackingTests.js +++ b/services/real-time/test/acceptance/js/ClientTrackingTests.js @@ -19,6 +19,80 @@ const FixturesManager = require('./helpers/FixturesManager') const async = require('async') describe('clientTracking', function () { + describe('when another logged in user joins a project', function () { + before(function (done) { + return async.series( + [ + cb => { + return FixturesManager.setUpProject( + { + privilegeLevel: 'owner', + project: { name: 'Test Project' }, + }, + (error, { user_id: userId, project_id: projectId }) => { + if (error) return done(error) + this.user_id = userId + this.project_id = projectId + return cb() + } + ) + }, + + cb => { + return FixturesManager.setUpDoc( + this.project_id, + { lines: this.lines, version: this.version, ops: this.ops }, + (e, { doc_id: docId }) => { + this.doc_id = docId + return cb(e) + } + ) + }, + + cb => { + this.clientA = RealTimeClient.connect(this.project_id, cb) + }, + + cb => { + RealTimeClient.countConnectedClients( + this.project_id, + (err, body) => { + if (err) return cb(err) + expect(body).to.deep.equal({ nConnectedClients: 1 }) + cb() + } + ) + }, + + cb => { + this.clientB = RealTimeClient.connect(this.project_id, cb) + }, + ], + done + ) + }) + + it('should record the initial state in getConnectedUsers', function (done) { + this.clientA.emit('clientTracking.getConnectedUsers', (error, users) => { + if (error) return done(error) + for (const user of Array.from(users)) { + if (user.client_id === this.clientB.publicId) { + expect(user.cursorData).to.not.exist + return done() + } + } + throw new Error('other user was never found') + }) + }) + it('should list both clients via HTTP', function (done) { + RealTimeClient.countConnectedClients(this.project_id, (err, body) => { + if (err) return done(err) + expect(body).to.deep.equal({ nConnectedClients: 2 }) + done() + }) + }) + }) + describe('when a client updates its cursor location', function () { before(function (done) { return async.series( diff --git a/services/real-time/test/acceptance/js/JoinDocTests.js b/services/real-time/test/acceptance/js/JoinDocTests.js index 547691d358..cdb5489ec9 100644 --- a/services/real-time/test/acceptance/js/JoinDocTests.js +++ b/services/real-time/test/acceptance/js/JoinDocTests.js @@ -89,6 +89,7 @@ describe('joinDoc', function () { this.version, this.ops, this.ranges, + 'sharejs-text-ot', ]) }) @@ -168,6 +169,7 @@ describe('joinDoc', function () { this.version, this.ops, this.ranges, + 'sharejs-text-ot', ]) }) @@ -247,6 +249,7 @@ describe('joinDoc', function () { this.version, this.ops, this.ranges, + 'sharejs-text-ot', ]) }) @@ -408,6 +411,7 @@ describe('joinDoc', function () { this.version, this.ops, this.ranges, + 'sharejs-text-ot', ]) }) @@ -489,6 +493,7 @@ describe('joinDoc', function () { this.version, this.ops, this.ranges, + 'sharejs-text-ot', ]) }) @@ -504,7 +509,7 @@ describe('joinDoc', function () { }) }) - return describe('with fromVersion and options', function () { + describe('with fromVersion and options', function () { before(function (done) { this.fromVersion = 36 this.options = { encodeRanges: true } @@ -572,6 +577,7 @@ describe('joinDoc', function () { this.version, this.ops, this.ranges, + 'sharejs-text-ot', ]) }) @@ -586,4 +592,139 @@ describe('joinDoc', function () { ) }) }) + + describe('with type=history-ot', function () { + before(function (done) { + async.series( + [ + cb => { + FixturesManager.setUpProject( + { privilegeLevel: 'owner' }, + (e, { project_id: projectId, user_id: userId }) => { + this.project_id = projectId + this.user_id = userId + cb(e) + } + ) + }, + + cb => { + FixturesManager.setUpDoc( + this.project_id, + { + lines: this.lines, + version: this.version, + ops: this.ops, + ranges: this.ranges, + type: 'history-ot', + }, + (e, { doc_id: docId }) => { + this.doc_id = docId + cb(e) + } + ) + }, + ], + done + ) + }) + + describe('when support is indicated', function () { + before(function (done) { + MockDocUpdaterServer.getDocument.resetHistory() + async.series( + [ + cb => { + this.client = RealTimeClient.connect(this.project_id, cb) + }, + cb => + this.client.emit( + 'joinDoc', + this.doc_id, + { supportsHistoryV1OT: true }, + (error, ...rest) => { + ;[...this.returnedArgs] = Array.from(rest) + cb(error) + } + ), + ], + done + ) + }) + + it('should get the doc from the doc updater', function () { + MockDocUpdaterServer.getDocument + .calledWith(this.project_id, this.doc_id, -1) + .should.equal(true) + }) + + it('should return the doc lines, version, ranges and ops', function () { + this.returnedArgs.should.deep.equal([ + this.lines, + this.version, + this.ops, + this.ranges, + 'history-ot', + ]) + }) + + it('should have joined the doc room', function (done) { + RealTimeClient.getConnectedClient( + this.client.socket.sessionid, + (error, client) => { + if (error) return done(error) + expect(client.rooms).to.deep.equal([this.project_id, this.doc_id]) + done() + } + ) + }) + }) + + describe('when support is not indicated', function () { + before(function (done) { + MockDocUpdaterServer.getDocument.resetHistory() + async.series( + [ + cb => { + this.client = RealTimeClient.connect(this.project_id, cb) + }, + cb => + this.client.emit('joinDoc', this.doc_id, (error, ...rest) => { + this.error = error + ;[...this.returnedArgs] = Array.from(rest) + cb() + }), + ], + done + ) + }) + + it('should get the doc from the doc updater', function () { + MockDocUpdaterServer.getDocument + .calledWith(this.project_id, this.doc_id, -1) + .should.equal(true) + }) + + it('should return an error', function () { + expect(this.error).to.deep.equal({ + message: 'client does not support history-ot', + }) + }) + + it('should not return the doc lines, version, ranges and ops', function () { + this.returnedArgs.should.deep.equal([]) + }) + + it('should leave the doc room again', function (done) { + RealTimeClient.getConnectedClient( + this.client.socket.sessionid, + (error, client) => { + if (error) return done(error) + expect(client.rooms).to.deep.equal([this.project_id]) + done() + } + ) + }) + }) + }) }) diff --git a/services/real-time/test/acceptance/js/helpers/FixturesManager.js b/services/real-time/test/acceptance/js/helpers/FixturesManager.js index 1db0c684c1..66e3072532 100644 --- a/services/real-time/test/acceptance/js/helpers/FixturesManager.js +++ b/services/real-time/test/acceptance/js/helpers/FixturesManager.js @@ -108,13 +108,17 @@ module.exports = FixturesManager = { if (!options.ops) { options.ops = ['mock', 'ops'] } - const { doc_id: docId, lines, version, ops, ranges } = options + if (!options.type) { + options.type = 'sharejs-text-ot' + } + const { doc_id: docId, lines, version, ops, ranges, type } = options MockDocUpdaterServer.createMockDoc(projectId, docId, { lines, version, ops, ranges, + type, }) return MockDocUpdaterServer.run(error => { if (error != null) { diff --git a/services/real-time/test/acceptance/js/helpers/RealTimeClient.js b/services/real-time/test/acceptance/js/helpers/RealTimeClient.js index 7b53f5d5c4..6cc7001896 100644 --- a/services/real-time/test/acceptance/js/helpers/RealTimeClient.js +++ b/services/real-time/test/acceptance/js/helpers/RealTimeClient.js @@ -123,6 +123,16 @@ module.exports = Client = { ) }, + countConnectedClients(projectId, callback) { + request.get( + { + url: `http://127.0.0.1:3026/project/${projectId}/count-connected-clients`, + json: true, + }, + (error, response, data) => callback(error, data) + ) + }, + getConnectedClient(clientId, callback) { if (callback == null) { callback = function () {} diff --git a/services/real-time/test/unit/js/DocumentUpdaterManagerTests.js b/services/real-time/test/unit/js/DocumentUpdaterManagerTests.js index 6dea5401f0..d9755fef57 100644 --- a/services/real-time/test/unit/js/DocumentUpdaterManagerTests.js +++ b/services/real-time/test/unit/js/DocumentUpdaterManagerTests.js @@ -79,7 +79,7 @@ describe('DocumentUpdaterManager', function () { }) it('should get the document from the document updater', function () { - const url = `${this.settings.apis.documentupdater.url}/project/${this.project_id}/doc/${this.doc_id}?fromVersion=${this.fromVersion}` + const url = `${this.settings.apis.documentupdater.url}/project/${this.project_id}/doc/${this.doc_id}?fromVersion=${this.fromVersion}&historyV1OTSupport=true` return this.request.get.calledWith(url).should.equal(true) }) diff --git a/services/web/app/src/Features/Documents/DocumentController.mjs b/services/web/app/src/Features/Documents/DocumentController.mjs index 6886414291..6998c0b36a 100644 --- a/services/web/app/src/Features/Documents/DocumentController.mjs +++ b/services/web/app/src/Features/Documents/DocumentController.mjs @@ -52,6 +52,11 @@ async function getDocument(req, res) { 'overleaf.history.rangesSupportEnabled', false ) + const otMigrationStage = _.get( + project, + 'overleaf.history.otMigrationStage', + 0 + ) // all projects are now migrated to Full Project History, keeping the field // for API compatibility @@ -65,6 +70,7 @@ async function getDocument(req, res) { projectHistoryId, projectHistoryType, historyRangesSupport, + otMigrationStage, resolvedCommentIds, }) } diff --git a/services/web/app/src/Features/Errors/Errors.js b/services/web/app/src/Features/Errors/Errors.js index 8a21b6042a..618c1c234c 100644 --- a/services/web/app/src/Features/Errors/Errors.js +++ b/services/web/app/src/Features/Errors/Errors.js @@ -300,6 +300,18 @@ class NonDeletableEntityError extends OError { } } +class FoundConnectedClientsError extends OError { + constructor(nConnectedClients) { + super(`found ${nConnectedClients} remaining connected clients`) + } +} + +class ConcurrentLoadingOfDocsDetectedError extends OError { + constructor() { + super('concurrent loading of docs detected') + } +} + module.exports = { OError, BackwardCompatibleError, @@ -356,4 +368,6 @@ module.exports = { InvalidEmailError, InvalidInstitutionalEmailError, NonDeletableEntityError, + FoundConnectedClientsError, + ConcurrentLoadingOfDocsDetectedError, } diff --git a/services/web/app/src/Features/History/HistoryOTMigration.mjs b/services/web/app/src/Features/History/HistoryOTMigration.mjs new file mode 100644 index 0000000000..a55fb3bbfd --- /dev/null +++ b/services/web/app/src/Features/History/HistoryOTMigration.mjs @@ -0,0 +1,56 @@ +import ProjectGetter from '../Project/ProjectGetter.js' +import DocumentUpdaterHandler from '../DocumentUpdater/DocumentUpdaterHandler.js' +import HistoryManager from '../History/HistoryManager.js' +import * as RealTimeHandler from '../References/RealTime/RealTimeHandler.mjs' +import ProjectOptionsHandler from '../Project/ProjectOptionsHandler.js' +import { + NotFoundError, + FoundConnectedClientsError, + ConcurrentLoadingOfDocsDetectedError, +} from '../Errors/Errors.js' + +async function ensureNoConnectedClients(projectId) { + const n = await RealTimeHandler.countConnectedClients(projectId) + if (n > 0) throw new FoundConnectedClientsError(n) +} + +/** + * @param {string} projectId + * @param {number} nextStage + * @return {Promise<{otMigrationStage: number}>} + */ +export async function advanceOTMigrationStage(projectId, nextStage) { + const project = await ProjectGetter.promises.getProject(projectId, { + overleaf: true, + }) + if (!project) throw new NotFoundError() + const { otMigrationStage } = project?.overleaf?.history || {} + if (otMigrationStage >= nextStage) return { otMigrationStage } + + // NOTE: For the single connected client case, we could emit a pub/sub event here asking any (inactive) client without pending edits to disconnect briefly. + // e.g. EditorRealTimeController.emitToRoom(projectId, 'attempt-history-ot-migration') + + // Ensure we can perform the hard migration + await ensureNoConnectedClients(projectId) + + // Flush ahead of migrating to keep the time under lock down. + await DocumentUpdaterHandler.promises.flushProjectToMongoAndDelete(projectId) + // Avoid mixing update types + await HistoryManager.promises.flushProject(projectId) + + // Obtain lock + if (!(await DocumentUpdaterHandler.promises.blockProject(projectId))) { + throw new ConcurrentLoadingOfDocsDetectedError() + } + + try { + // Perform the mongo update and tell caller about the latest stage. + return await ProjectOptionsHandler.promises.setOTMigrationStage( + projectId, + nextStage + ) + } finally { + // Unlock again (The lock will expire after 30s otherwise) + await DocumentUpdaterHandler.promises.unblockProject(projectId) + } +} diff --git a/services/web/app/src/Features/Project/ProjectOptionsHandler.js b/services/web/app/src/Features/Project/ProjectOptionsHandler.js index 5ca89ce145..c0c11c396c 100644 --- a/services/web/app/src/Features/Project/ProjectOptionsHandler.js +++ b/services/web/app/src/Features/Project/ProjectOptionsHandler.js @@ -2,6 +2,8 @@ const { Project } = require('../../models/Project') const settings = require('@overleaf/settings') const { callbackify } = require('util') const { db, ObjectId } = require('../../infrastructure/mongodb') +const Errors = require('../Errors/Errors') +const { ReturnDocument } = require('mongodb-legacy') const safeCompilers = ['xelatex', 'pdflatex', 'latex', 'lualatex'] const ProjectOptionsHandler = { @@ -73,6 +75,21 @@ const ProjectOptionsHandler = { // because rangesSupportEnabled is not part of the schema? return db.projects.updateOne(conditions, update) }, + + async setOTMigrationStage(projectId, nextStage) { + const project = await db.projects.findOneAndUpdate( + { _id: new ObjectId(projectId) }, + // Use $max to ensure that we never downgrade the migration stage. + { $max: { 'overleaf.history.otMigrationStage': nextStage } }, + { + returnDocument: ReturnDocument.AFTER, + projection: { 'overleaf.history.otMigrationStage': 1 }, + } + ) + if (!project) throw new Errors.NotFoundError('project does not exist') + const { otMigrationStage } = project.overleaf.history + return { otMigrationStage } + }, } module.exports = { diff --git a/services/web/app/src/Features/References/RealTime/RealTimeHandler.mjs b/services/web/app/src/Features/References/RealTime/RealTimeHandler.mjs new file mode 100644 index 0000000000..3c797a9003 --- /dev/null +++ b/services/web/app/src/Features/References/RealTime/RealTimeHandler.mjs @@ -0,0 +1,9 @@ +import Settings from '@overleaf/settings' +import { fetchJson } from '@overleaf/fetch-utils' + +export async function countConnectedClients(projectId) { + const url = new URL(Settings.apis.realTime.url) + url.pathname = `/project/${projectId}/count-connected-clients` + const { nConnectedClients } = await fetchJson(url) + return nConnectedClients +} diff --git a/services/web/app/src/models/Project.js b/services/web/app/src/models/Project.js index 8da4b888d3..145c8f9023 100644 --- a/services/web/app/src/models/Project.js +++ b/services/web/app/src/models/Project.js @@ -99,6 +99,7 @@ const ProjectSchema = new Schema( allowDowngrade: { type: Boolean }, zipFileArchivedInProject: { type: Boolean }, rangesSupportEnabled: { type: Boolean }, + otMigrationStage: { type: Number }, }, }, collabratecUsers: [ diff --git a/services/web/frontend/js/features/ide-react/editor/document-container.ts b/services/web/frontend/js/features/ide-react/editor/document-container.ts index 9172ec9ff8..1770894584 100644 --- a/services/web/frontend/js/features/ide-react/editor/document-container.ts +++ b/services/web/frontend/js/features/ide-react/editor/document-container.ts @@ -2,7 +2,7 @@ // Migrated from services/web/frontend/js/ide/editor/Document.js import RangesTracker from '@overleaf/ranges-tracker' -import { ShareJsDoc } from './share-js-doc' +import { OTType, ShareJsDoc } from './share-js-doc' import { debugConsole } from '@/utils/debugging' import { Socket } from '@/features/ide-react/connection/types/socket' import { IdeEventEmitter } from '@/features/ide-react/create-ide-event-emitter' @@ -28,6 +28,7 @@ import { } from '@/features/ide-react/editor/types/document' import { ThreadId } from '../../../../../types/review-panel/review-panel' import getMeta from '@/utils/meta' +import OError from '@overleaf/o-error' const MAX_PENDING_OP_SIZE = 64 @@ -447,16 +448,36 @@ export class DocumentContainer extends EventEmitter { 'joinDoc', this.doc_id, this.doc.getVersion(), - { encodeRanges: true, age: this.doc.getTimeSinceLastServerActivity() }, - (error, docLines, version, updates, ranges) => { + { + encodeRanges: true, + age: this.doc.getTimeSinceLastServerActivity(), + supportsHistoryV1OT: true, + }, + ( + error, + docLines, + version, + updates, + ranges, + type = 'sharejs-text-ot' + ) => { if (error) { callback?.(error) return } this.joined = true this.doc?.catchUp(updates) - this.decodeRanges(ranges) - this.catchUpRanges(ranges?.changes, ranges?.comments) + if (this.doc?.getType() !== type) { + // TODO(24596): page reload after checking for pending ops? + throw new OError('ot type mismatch', { + got: type, + want: this.doc?.getType(), + }) + } + if (type === 'sharejs-text-ot') { + this.decodeRanges(ranges) + this.catchUpRanges(ranges?.changes, ranges?.comments) + } callback?.() } ) @@ -464,8 +485,18 @@ export class DocumentContainer extends EventEmitter { this.socket.emit( 'joinDoc', this.doc_id, - { encodeRanges: true }, - (error, docLines, version, updates, ranges) => { + { + encodeRanges: true, + supportsHistoryV1OT: true, + }, + ( + error, + docLines, + version, + updates, + ranges, + type: OTType = 'sharejs-text-ot' + ) => { if (error) { callback?.(error) return @@ -477,9 +508,12 @@ export class DocumentContainer extends EventEmitter { version, this.socket, this.globalEditorWatchdogManager, - this.ideEventEmitter + this.ideEventEmitter, + type ) - this.decodeRanges(ranges) + if (type === 'sharejs-text-ot') { + this.decodeRanges(ranges) + } this.ranges = new RangesTracker(ranges?.changes, ranges?.comments) this.bindToShareJsDocEvents() callback?.() @@ -580,7 +614,9 @@ export class DocumentContainer extends EventEmitter { this.doc.on( 'change', (ops: AnyOperation[], oldSnapshot: any, msg: Message) => { - this.applyOpsToRanges(ops, msg) + if (this.getType() === 'sharejs-text-ot') { + this.applyOpsToRanges(ops, msg) + } if (docChangedTimeout) { window.clearTimeout(docChangedTimeout) } diff --git a/services/web/frontend/js/features/ide-react/editor/share-js-doc.ts b/services/web/frontend/js/features/ide-react/editor/share-js-doc.ts index 7b4e3492f8..96e866afec 100644 --- a/services/web/frontend/js/features/ide-react/editor/share-js-doc.ts +++ b/services/web/frontend/js/features/ide-react/editor/share-js-doc.ts @@ -2,7 +2,7 @@ // Migrated from services/web/frontend/js/ide/editor/ShareJsDoc.js import EventEmitter from '../../../utils/EventEmitter' -import { Doc } from '@/vendor/libs/sharejs' +import sharejs, { Doc } from '@/vendor/libs/sharejs' import { Socket } from '@/features/ide-react/connection/types/socket' import { debugConsole } from '@/utils/debugging' import { decodeUtf8 } from '@/utils/decode-utf8' @@ -12,11 +12,18 @@ import { Message, ShareJsConnectionState, ShareJsOperation, + ShareJsTextType, TrackChangesIdSeeds, } from '@/features/ide-react/editor/types/document' import { EditorFacade } from '@/features/source-editor/extensions/realtime' import { recordDocumentFirstChangeEvent } from '@/features/event-tracking/document-first-change-event' import getMeta from '@/utils/meta' +import { HistoryOTType } from './share-js-history-ot-type' +import { StringFileData } from 'overleaf-editor-core/index' +import { + RawEditOperation, + StringFileRawData, +} from 'overleaf-editor-core/lib/types' // All times below are in milliseconds const SINGLE_USER_FLUSH_DELAY = 2000 @@ -27,6 +34,7 @@ const FATAL_OP_TIMEOUT = 45000 const RECENT_ACK_LIMIT = 2 * SINGLE_USER_FLUSH_DELAY type Update = Record +export type OTType = 'sharejs-text-ot' | 'history-ot' type Connection = { send: (update: Update) => void @@ -35,7 +43,6 @@ type Connection = { } export class ShareJsDoc extends EventEmitter { - type: string track_changes = false track_changes_id_seeds: TrackChangesIdSeeds | null = null connection: Connection @@ -57,12 +64,24 @@ export class ShareJsDoc extends EventEmitter { version: number, readonly socket: Socket, private readonly globalEditorWatchdogManager: EditorWatchdogManager, - private readonly eventEmitter: IdeEventEmitter + private readonly eventEmitter: IdeEventEmitter, + readonly type: OTType = 'sharejs-text-ot' ) { super() - this.type = 'text' + let sharejsType: ShareJsTextType = sharejs.types.text // Decode any binary bits of data - const snapshot = docLines.map(line => decodeUtf8(line)).join('\n') + let snapshot: string | StringFileData + if (this.type === 'history-ot') { + snapshot = StringFileData.fromRaw( + docLines as unknown as StringFileRawData + ) + sharejsType = new HistoryOTType(snapshot) as ShareJsTextType< + StringFileData, + RawEditOperation[] + > + } else { + snapshot = docLines.map(line => decodeUtf8(line)).join('\n') + } this.connection = { send: (update: Update) => { @@ -89,7 +108,7 @@ export class ShareJsDoc extends EventEmitter { } this._doc = new Doc(this.connection, this.doc_id, { - type: this.type, + type: sharejsType, }) this._doc.setFlushDelay(SINGLE_USER_FLUSH_DELAY) this._doc.on('change', (...args: any[]) => { diff --git a/services/web/frontend/js/features/ide-react/editor/share-js-history-ot-type.ts b/services/web/frontend/js/features/ide-react/editor/share-js-history-ot-type.ts new file mode 100644 index 0000000000..cec1983037 --- /dev/null +++ b/services/web/frontend/js/features/ide-react/editor/share-js-history-ot-type.ts @@ -0,0 +1,131 @@ +import EventEmitter from '@/utils/EventEmitter' +import { + EditOperationBuilder, + InsertOp, + RemoveOp, + RetainOp, + StringFileData, + TextOperation, +} from 'overleaf-editor-core' +import { RawEditOperation } from 'overleaf-editor-core/lib/types' + +function loadTextOperation(raw: RawEditOperation): TextOperation { + const operation = EditOperationBuilder.fromJSON(raw) + if (!(operation instanceof TextOperation)) { + throw new Error(`operation not supported: ${operation.constructor.name}`) + } + return operation +} + +export class HistoryOTType extends EventEmitter { + // stub interface, these are actually on the Doc + api: HistoryOTType + snapshot: StringFileData + + constructor(snapshot: StringFileData) { + super() + this.api = this + this.snapshot = snapshot + } + + transformX(raw1: RawEditOperation[], raw2: RawEditOperation[]) { + const [a, b] = TextOperation.transform( + loadTextOperation(raw1[0]), + loadTextOperation(raw2[0]) + ) + return [[a.toJSON()], [b.toJSON()]] + } + + apply(snapshot: StringFileData, rawEditOperation: RawEditOperation[]) { + const operation = loadTextOperation(rawEditOperation[0]) + const afterFile = StringFileData.fromRaw(snapshot.toRaw()) + afterFile.edit(operation) + this.snapshot = afterFile + return afterFile + } + + compose(op1: RawEditOperation[], op2: RawEditOperation[]) { + return [ + loadTextOperation(op1[0]).compose(loadTextOperation(op2[0])).toJSON(), + ] + } + + // Do not provide normalize, used by submitOp to fixup bad input. + // normalize(op: TextOperation) {} + + // Do not provide invert, only needed for reverting a rejected update. + // We are displaying an out-of-sync modal when an op is rejected. + // invert(op: TextOperation) {} + + // API + insert(pos: number, text: string, fromUndo: boolean) { + const old = this.getText() + const op = new TextOperation() + op.retain(pos) + op.insert(text) + op.retain(old.length - pos) + this.submitOp([op.toJSON()]) + } + + del(pos: number, length: number, fromUndo: boolean) { + const old = this.getText() + const op = new TextOperation() + op.retain(pos) + op.remove(length) + op.retain(old.length - pos - length) + this.submitOp([op.toJSON()]) + } + + getText() { + return this.snapshot.getContent({ filterTrackedDeletes: true }) + } + + getLength() { + return this.getText().length + } + + _register() { + this.on( + 'remoteop', + (rawEditOperation: RawEditOperation[], oldSnapshot: StringFileData) => { + const operation = loadTextOperation(rawEditOperation[0]) + const str = oldSnapshot.getContent() + if (str.length !== operation.baseLength) + throw new TextOperation.ApplyError( + "The operation's base length must be equal to the string's length.", + operation, + str + ) + + let outputCursor = 0 + let inputCursor = 0 + for (const op of operation.ops) { + if (op instanceof RetainOp) { + inputCursor += op.length + outputCursor += op.length + } else if (op instanceof InsertOp) { + this.emit('insert', outputCursor, op.insertion, op.insertion.length) + outputCursor += op.insertion.length + } else if (op instanceof RemoveOp) { + this.emit( + 'delete', + outputCursor, + str.slice(inputCursor, inputCursor + op.length) + ) + inputCursor += op.length + } + } + + if (inputCursor !== str.length) + throw new TextOperation.ApplyError( + "The operation didn't operate on the whole string.", + operation, + str + ) + } + ) + } + + // stub-interface, provided by sharejs.Doc + submitOp(op: RawEditOperation[]) {} +} diff --git a/services/web/frontend/js/features/ide-react/editor/types/document.ts b/services/web/frontend/js/features/ide-react/editor/types/document.ts index 44d36c0e48..fbed3ab8f1 100644 --- a/services/web/frontend/js/features/ide-react/editor/types/document.ts +++ b/services/web/frontend/js/features/ide-react/editor/types/document.ts @@ -1,3 +1,4 @@ +import { StringFileData } from 'overleaf-editor-core' import { AnyOperation } from '../../../../../../types/change' export type Version = number @@ -8,6 +9,23 @@ export type ShareJsOperation = AnyOperation[] export type TrackChangesIdSeeds = { inflight: string; pending: string } +export interface ShareJsTextType { + transformX(op1: Operation, op2: Operation): Operation[] + apply(snapshot: Snapshot, op: Operation): Snapshot + compose(op1: Operation, op2: Operation): Operation + + api: { + insert(pos: number, text: string, fromUndo: boolean): void + del(pos: number, length: number, fromUndo: boolean): void + getText(): string + getLength(): number + _register(): void + } + + // stub-interface, provided by sharejs.Doc + submitOp(op: Operation): void +} + // TODO: check the properties of this type export type Message = { v: Version @@ -16,5 +34,6 @@ export type Message = { type?: string } doc?: string - snapshot?: string + snapshot?: string | StringFileData + type?: ShareJsTextType } diff --git a/services/web/test/unit/bootstrap.js b/services/web/test/unit/bootstrap.js index fa2a7a5b76..ee4a022c15 100644 --- a/services/web/test/unit/bootstrap.js +++ b/services/web/test/unit/bootstrap.js @@ -88,6 +88,7 @@ function getSandboxedModuleRequires() { 'sshpk', 'xml2js', 'mongodb', + 'mongodb-legacy', ] for (const modulePath of internalModules) { requires[Path.resolve(__dirname, modulePath)] = require(modulePath) diff --git a/services/web/test/unit/src/Documents/DocumentControllerTests.mjs b/services/web/test/unit/src/Documents/DocumentControllerTests.mjs index 29d03f152c..813e8d65f3 100644 --- a/services/web/test/unit/src/Documents/DocumentControllerTests.mjs +++ b/services/web/test/unit/src/Documents/DocumentControllerTests.mjs @@ -126,6 +126,7 @@ describe('DocumentController', function () { projectHistoryType: 'project-history', resolvedCommentIds: ['comment2'], historyRangesSupport: false, + otMigrationStage: 0, }) }) }) From 6973ba4244f0aafdae6537d009bd34f327cbe74e Mon Sep 17 00:00:00 2001 From: Jimmy Domagala-Tang Date: Wed, 7 May 2025 10:13:10 -0400 Subject: [PATCH 036/194] Merge pull request #25090 from overleaf/jdt-align-wf-rebrand-split-test Align Writefull bundle changes to same split test GitOrigin-RevId: 28eb7c0835a38d4989461d941efc3e8c0cdcfecb --- services/web/app/src/Features/Project/ProjectController.js | 1 - .../features/source-editor/components/toolbar/math-dropdown.tsx | 2 +- .../features/source-editor/components/toolbar/toolbar-items.tsx | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/services/web/app/src/Features/Project/ProjectController.js b/services/web/app/src/Features/Project/ProjectController.js index f033436fdd..c82c65c5da 100644 --- a/services/web/app/src/Features/Project/ProjectController.js +++ b/services/web/app/src/Features/Project/ProjectController.js @@ -354,7 +354,6 @@ const _ProjectController = { 'editor-redesign', 'paywall-change-compile-timeout', 'overleaf-assist-bundle', - 'wf-feature-rebrand', 'word-count-client', 'editor-popup-ux-survey', ].filter(Boolean) diff --git a/services/web/frontend/js/features/source-editor/components/toolbar/math-dropdown.tsx b/services/web/frontend/js/features/source-editor/components/toolbar/math-dropdown.tsx index 710f42c07e..b34a61c69d 100644 --- a/services/web/frontend/js/features/source-editor/components/toolbar/math-dropdown.tsx +++ b/services/web/frontend/js/features/source-editor/components/toolbar/math-dropdown.tsx @@ -20,7 +20,7 @@ export const MathDropdown = memo(function MathDropdown() { const view = useCodeMirrorViewContext() const { writefullInstance } = useEditorContext() - const wfRebrandEnabled = isSplitTestEnabled('wf-feature-rebrand') + const wfRebrandEnabled = isSplitTestEnabled('overleaf-assist-bundle') return ( !overflowed || overflowed.has(group) - const wfRebrandEnabled = isSplitTestEnabled('wf-feature-rebrand') + const wfRebrandEnabled = isSplitTestEnabled('overleaf-assist-bundle') return ( <> From fbbba7a3dfc2b04bc42a97b847808d6228ae9a69 Mon Sep 17 00:00:00 2001 From: Jimmy Domagala-Tang Date: Wed, 7 May 2025 10:13:22 -0400 Subject: [PATCH 037/194] Merge pull request #24816 from overleaf/jdt-update-checkout-for-bundle Update checkout pages for AI Assist bundle content GitOrigin-RevId: e2e1b705dd92e0858835d18eb6d8c5750030e550 --- services/web/frontend/extracted-translations.json | 5 +++-- .../dashboard/states/active/active-new.tsx | 8 ++++++-- .../components/dashboard/states/active/add-ons.tsx | 13 +++++++------ .../components/preview-subscription-change/root.tsx | 2 +- .../js/features/subscription/data/add-on-codes.ts | 6 +++--- services/web/locales/en.json | 6 +++--- 6 files changed, 23 insertions(+), 17 deletions(-) diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json index e11bb8002a..828fedb323 100644 --- a/services/web/frontend/extracted-translations.json +++ b/services/web/frontend/extracted-translations.json @@ -62,6 +62,9 @@ "add_additional_certificate": "", "add_affiliation": "", "add_ai_assist": "", + "add_ai_assist_annual_and_get_unlimited_access": "", + "add_ai_assist_monthly_and_get_unlimited_access": "", + "add_ai_assist_to_your_plan": "", "add_another_address_line": "", "add_another_email": "", "add_another_token": "", @@ -72,8 +75,6 @@ "add_company_details": "", "add_email_address": "", "add_email_to_claim_features": "", - "add_error_assist_annual_to_your_projects": "", - "add_error_assist_to_your_projects": "", "add_files": "", "add_more_collaborators": "", "add_more_licenses_to_my_plan": "", diff --git a/services/web/frontend/js/features/subscription/components/dashboard/states/active/active-new.tsx b/services/web/frontend/js/features/subscription/components/dashboard/states/active/active-new.tsx index 2bd8639f6a..2f01474251 100644 --- a/services/web/frontend/js/features/subscription/components/dashboard/states/active/active-new.tsx +++ b/services/web/frontend/js/features/subscription/components/dashboard/states/active/active-new.tsx @@ -16,7 +16,7 @@ import isInFreeTrial from '../../../../util/is-in-free-trial' import AddOns from '@/features/subscription/components/dashboard/states/active/add-ons' import { AI_ADD_ON_CODE, - AI_STANDALONE_PLAN_CODE, + AI_ASSIST_STANDALONE_MONTHLY_PLAN_CODE, isStandaloneAiPlanCode, } from '@/features/subscription/data/add-on-codes' import getMeta from '@/utils/meta' @@ -74,7 +74,11 @@ export function ActiveSubscriptionNew({ const handlePlanChange = () => setModalIdShown('change-plan') const handleManageOnWritefull = () => setModalIdShown('manage-on-writefull') const handleCancelClick = (addOnCode: string) => { - if ([AI_STANDALONE_PLAN_CODE, AI_ADD_ON_CODE].includes(addOnCode)) { + if ( + [AI_ASSIST_STANDALONE_MONTHLY_PLAN_CODE, AI_ADD_ON_CODE].includes( + addOnCode + ) + ) { setModalIdShown('cancel-ai-add-on') } } diff --git a/services/web/frontend/js/features/subscription/components/dashboard/states/active/add-ons.tsx b/services/web/frontend/js/features/subscription/components/dashboard/states/active/add-ons.tsx index bd23fabad3..9d7a511ab0 100644 --- a/services/web/frontend/js/features/subscription/components/dashboard/states/active/add-ons.tsx +++ b/services/web/frontend/js/features/subscription/components/dashboard/states/active/add-ons.tsx @@ -6,8 +6,8 @@ import MaterialIcon from '@/shared/components/material-icon' import { ADD_ON_NAME, AI_ADD_ON_CODE, - AI_STANDALONE_ANNUAL_PLAN_CODE, - AI_STANDALONE_PLAN_CODE, + AI_ASSIST_STANDALONE_ANNUAL_PLAN_CODE, + AI_ASSIST_STANDALONE_MONTHLY_PLAN_CODE, } from '@/features/subscription/data/add-on-codes' import sparkle from '@/shared/svgs/sparkle.svg' import { PaidSubscription } from '../../../../../../../../types/subscription/dashboard/subscription' @@ -32,8 +32,8 @@ type AddOnProps = { function resolveAddOnName(addOnCode: string) { switch (addOnCode) { case AI_ADD_ON_CODE: - case AI_STANDALONE_ANNUAL_PLAN_CODE: - case AI_STANDALONE_PLAN_CODE: + case AI_ASSIST_STANDALONE_ANNUAL_PLAN_CODE: + case AI_ASSIST_STANDALONE_MONTHLY_PLAN_CODE: return ADD_ON_NAME } } @@ -156,11 +156,12 @@ function AddOns({ const hasAiAssistViaWritefull = getMeta('ol-hasAiAssistViaWritefull') const addOnsDisplayPrices = onStandalonePlan ? { - [AI_STANDALONE_PLAN_CODE]: subscription.payment.displayPrice, + [AI_ASSIST_STANDALONE_MONTHLY_PLAN_CODE]: + subscription.payment.displayPrice, } : subscription.payment.addOnDisplayPricesWithoutAdditionalLicense const addOnsToDisplay = onStandalonePlan - ? [{ addOnCode: AI_STANDALONE_PLAN_CODE }] + ? [{ addOnCode: AI_ASSIST_STANDALONE_MONTHLY_PLAN_CODE }] : subscription.addOns?.filter(addOn => addOn.addOnCode !== LICENSE_ADD_ON) const hasAddons = diff --git a/services/web/frontend/js/features/subscription/components/preview-subscription-change/root.tsx b/services/web/frontend/js/features/subscription/components/preview-subscription-change/root.tsx index cb9565e9e4..705af73e27 100644 --- a/services/web/frontend/js/features/subscription/components/preview-subscription-change/root.tsx +++ b/services/web/frontend/js/features/subscription/components/preview-subscription-change/root.tsx @@ -108,7 +108,7 @@ function PreviewSubscriptionChange() { {aiAddOnChange && (
AI Assist Annual and get unlimited* access to Overleaf and Writefull AI features.", + "add_ai_assist_monthly_and_get_unlimited_access": "Add AI Assist Monthly and get unlimited* access to Overleaf and Writefull AI features.", + "add_ai_assist_to_your_plan": "Add AI Assist to your plan and get unlimited* access to Overleaf and Writefull AI features.", "add_another_address_line": "Add another address line", "add_another_email": "Add another email", "add_another_token": "Add another token", @@ -87,8 +90,6 @@ "add_email": "Add Email", "add_email_address": "Add email address", "add_email_to_claim_features": "Add an institutional email address to claim your features.", - "add_error_assist_annual_to_your_projects": "Add Error Assist Annual to your projects and get unlimited AI help to fix LaTeX errors faster.", - "add_error_assist_to_your_projects": "Add Error Assist to your projects and get unlimited AI help to fix LaTeX errors faster.", "add_files": "Add Files", "add_more_collaborators": "Add more collaborators", "add_more_licenses_to_my_plan": "Add more licenses to my plan", @@ -818,7 +819,6 @@ "generic_something_went_wrong": "Sorry, something went wrong", "get_collaborative_benefits": "Get the collaborative benefits from __appName__, even if you prefer to work offline", "get_discounted_plan": "Get discounted plan", - "get_error_assist": "Get Error Assist", "get_exclusive_access_to_labs": "Get exclusive access to early-stage experiments when you join Overleaf Labs. All we ask in return is your honest feedback to help us develop and improve.", "get_in_touch": "Get in touch", "get_in_touch_having_problems": "Get in touch with support if you’re having problems", From 9f0f910a83241b56dcfb5c88104d4af4781ec170 Mon Sep 17 00:00:00 2001 From: Jimmy Domagala-Tang Date: Wed, 7 May 2025 10:13:35 -0400 Subject: [PATCH 038/194] Merge pull request #25251 from overleaf/jdt-show-wf-provided-bundle-on-free-plans Show AI Assist entitlment via Writefull on free user's subscriptions page GitOrigin-RevId: 20a456a231f60df279b949057972125735166904 --- .../components/dashboard/free-plan.tsx | 11 +++ .../dashboard/personal-subscription.tsx | 22 +----- .../components/dashboard/redirect-alerts.tsx | 24 +++++++ .../dashboard/states/active/active-new.tsx | 4 -- .../dashboard/states/active/add-ons.tsx | 55 +-------------- .../writefull-bundle-management-modal.tsx | 68 ++++++++++++++++++- 6 files changed, 105 insertions(+), 79 deletions(-) create mode 100644 services/web/frontend/js/features/subscription/components/dashboard/redirect-alerts.tsx diff --git a/services/web/frontend/js/features/subscription/components/dashboard/free-plan.tsx b/services/web/frontend/js/features/subscription/components/dashboard/free-plan.tsx index ce4c2baaa5..a8cf7dcf7b 100644 --- a/services/web/frontend/js/features/subscription/components/dashboard/free-plan.tsx +++ b/services/web/frontend/js/features/subscription/components/dashboard/free-plan.tsx @@ -1,10 +1,15 @@ import { useTranslation, Trans } from 'react-i18next' +import WritefullManagedBundleAddOn from '@/features/subscription/components/dashboard/states/active/change-plan/modals/writefull-bundle-management-modal' +import RedirectAlerts from './redirect-alerts' +import getMeta from '@/utils/meta' function FreePlan() { const { t } = useTranslation() + const hasAiAssistViaWritefull = getMeta('ol-hasAiAssistViaWritefull') return ( <> + {t('upgrade_now')} + {hasAiAssistViaWritefull && ( + <> +

{t('add_ons')}

+ + + )} ) } diff --git a/services/web/frontend/js/features/subscription/components/dashboard/personal-subscription.tsx b/services/web/frontend/js/features/subscription/components/dashboard/personal-subscription.tsx index d6a511af75..2173ea45d3 100644 --- a/services/web/frontend/js/features/subscription/components/dashboard/personal-subscription.tsx +++ b/services/web/frontend/js/features/subscription/components/dashboard/personal-subscription.tsx @@ -7,6 +7,7 @@ import { ExpiredSubscription } from './states/expired' import { useSubscriptionDashboardContext } from '../../context/subscription-dashboard-context' import PersonalSubscriptionRecurlySyncEmail from './personal-subscription-recurly-sync-email' import OLNotification from '@/features/ui/components/ol/ol-notification' +import RedirectAlerts from './redirect-alerts' function PastDueSubscriptionAlert({ subscription, @@ -33,27 +34,6 @@ function PastDueSubscriptionAlert({ ) } -function RedirectAlerts() { - const queryParams = new URLSearchParams(window.location.search) - const redirectReason = queryParams.get('redirect-reason') - const { t } = useTranslation() - - if (!redirectReason) { - return null - } - - let warning - if (redirectReason === 'writefull-entitled') { - warning = t('good_news_you_are_already_receiving_this_add_on_via_writefull') - } else if (redirectReason === 'double-buy') { - warning = t('good_news_you_already_purchased_this_add_on') - } else { - return null - } - - return {warning}} /> -} - function PersonalSubscriptionStates({ subscription, }: { diff --git a/services/web/frontend/js/features/subscription/components/dashboard/redirect-alerts.tsx b/services/web/frontend/js/features/subscription/components/dashboard/redirect-alerts.tsx new file mode 100644 index 0000000000..be5bab484e --- /dev/null +++ b/services/web/frontend/js/features/subscription/components/dashboard/redirect-alerts.tsx @@ -0,0 +1,24 @@ +import { useTranslation } from 'react-i18next' +import OLNotification from '@/features/ui/components/ol/ol-notification' + +export function RedirectAlerts() { + const queryParams = new URLSearchParams(window.location.search) + const redirectReason = queryParams.get('redirect-reason') + const { t } = useTranslation() + + if (!redirectReason) { + return null + } + + let warning + if (redirectReason === 'writefull-entitled') { + warning = t('good_news_you_are_already_receiving_this_add_on_via_writefull') + } else if (redirectReason === 'double-buy') { + warning = t('good_news_you_already_purchased_this_add_on') + } else { + return null + } + + return {warning}} /> +} +export default RedirectAlerts diff --git a/services/web/frontend/js/features/subscription/components/dashboard/states/active/active-new.tsx b/services/web/frontend/js/features/subscription/components/dashboard/states/active/active-new.tsx index 2f01474251..dfc6448fb0 100644 --- a/services/web/frontend/js/features/subscription/components/dashboard/states/active/active-new.tsx +++ b/services/web/frontend/js/features/subscription/components/dashboard/states/active/active-new.tsx @@ -10,7 +10,6 @@ import { ConfirmChangePlanModal } from './change-plan/modals/confirm-change-plan import { KeepCurrentPlanModal } from './change-plan/modals/keep-current-plan-modal' import { ChangeToGroupModal } from './change-plan/modals/change-to-group-modal' import { CancelAiAddOnModal } from '@/features/subscription/components/dashboard/states/active/change-plan/modals/cancel-ai-add-on-modal' -import { WritefullBundleManagementModal } from '@/features/subscription/components/dashboard/states/active/change-plan/modals/writefull-bundle-management-modal' import OLButton from '@/features/ui/components/ol/ol-button' import isInFreeTrial from '../../../../util/is-in-free-trial' import AddOns from '@/features/subscription/components/dashboard/states/active/add-ons' @@ -72,7 +71,6 @@ export function ActiveSubscriptionNew({ } const handlePlanChange = () => setModalIdShown('change-plan') - const handleManageOnWritefull = () => setModalIdShown('manage-on-writefull') const handleCancelClick = (addOnCode: string) => { if ( [AI_ASSIST_STANDALONE_MONTHLY_PLAN_CODE, AI_ADD_ON_CODE].includes( @@ -266,7 +264,6 @@ export function ActiveSubscriptionNew({ subscription={subscription} onStandalonePlan={onStandalonePlan} handleCancelClick={handleCancelClick} - handleManageOnWritefull={handleManageOnWritefull} /> @@ -274,7 +271,6 @@ export function ActiveSubscriptionNew({ - ) diff --git a/services/web/frontend/js/features/subscription/components/dashboard/states/active/add-ons.tsx b/services/web/frontend/js/features/subscription/components/dashboard/states/active/add-ons.tsx index 9d7a511ab0..521f670e5f 100644 --- a/services/web/frontend/js/features/subscription/components/dashboard/states/active/add-ons.tsx +++ b/services/web/frontend/js/features/subscription/components/dashboard/states/active/add-ons.tsx @@ -12,12 +12,12 @@ import { import sparkle from '@/shared/svgs/sparkle.svg' import { PaidSubscription } from '../../../../../../../../types/subscription/dashboard/subscription' import { LICENSE_ADD_ON } from '@/features/group-management/components/upgrade-subscription/upgrade-subscription-plan-details' +import WritefullManagedBundleAddOn from './change-plan/modals/writefull-bundle-management-modal' type AddOnsProps = { subscription: PaidSubscription onStandalonePlan: boolean handleCancelClick: (code: string) => void - handleManageOnWritefull: () => void } type AddOnProps = { @@ -100,57 +100,10 @@ function AddOn({ ) } -function WritefullGrantedAddOn({ - handleManageOnWritefull, -}: { - handleManageOnWritefull: () => void -}) { - const { t } = useTranslation() - return ( -
-
- -
-
-
{ADD_ON_NAME}
-
- {t('included_as_part_of_your_writefull_subscription')} -
-
- -
- - - - - - - {t('manage_subscription')} - - - -
-
- ) -} - function AddOns({ subscription, onStandalonePlan, handleCancelClick, - handleManageOnWritefull, }: AddOnsProps) { const { t } = useTranslation() const hasAiAssistViaWritefull = getMeta('ol-hasAiAssistViaWritefull') @@ -187,11 +140,7 @@ function AddOns({ nextBillingDate={subscription.payment.nextPaymentDueDate} /> ))} - {hasAiAssistViaWritefull && ( - - )} + {hasAiAssistViaWritefull && } ) : (

{t('you_dont_have_any_add_ons_on_your_account')}

diff --git a/services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/modals/writefull-bundle-management-modal.tsx b/services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/modals/writefull-bundle-management-modal.tsx index ef512ddec5..d5933d137a 100644 --- a/services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/modals/writefull-bundle-management-modal.tsx +++ b/services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/modals/writefull-bundle-management-modal.tsx @@ -8,8 +8,13 @@ import OLModal, { OLModalTitle, } from '@/features/ui/components/ol/ol-modal' import OLButton from '@/features/ui/components/ol/ol-button' +import sparkle from '@/shared/svgs/sparkle.svg' +import { Dropdown, DropdownMenu, DropdownToggle } from 'react-bootstrap-5' +import OLDropdownMenuItem from '@/features/ui/components/ol/ol-dropdown-menu-item' +import MaterialIcon from '@/shared/components/material-icon' +import { ADD_ON_NAME } from '@/features/subscription/data/add-on-codes' -export function WritefullBundleManagementModal() { +function WritefullBundleManagementModal() { const modalId: SubscriptionDashModalIds = 'manage-on-writefull' const { t } = useTranslation() const { handleCloseModal, modalIdShown } = useSubscriptionDashboardContext() @@ -49,3 +54,64 @@ export function WritefullBundleManagementModal() { ) } + +function WritefullGrantedAddOn({ + handleManageOnWritefull, +}: { + handleManageOnWritefull: () => void +}) { + const { t } = useTranslation() + return ( +
+
+ +
+
+
{ADD_ON_NAME}
+
+ {t('included_as_part_of_your_writefull_subscription')} +
+
+ +
+ + + + + + + {t('manage_subscription')} + + + +
+
+ ) +} + +export function WritefullManagedBundleAddOn() { + const { setModalIdShown } = useSubscriptionDashboardContext() + const handleManageOnWritefull = () => setModalIdShown('manage-on-writefull') + return ( + <> + + + + ) +} + +export default WritefullManagedBundleAddOn From ec91c120b1a7ebb01ff15f7d7fc9dbe961e95784 Mon Sep 17 00:00:00 2001 From: Brian Gough Date: Wed, 7 May 2025 15:42:51 +0100 Subject: [PATCH 039/194] Merge pull request #25284 from overleaf/em-queue-changes-verification Exercise the Redis buffer when persisting changes GitOrigin-RevId: a649b9808b6472e7c5dd9c8bfa6e3c98fb6ef4d4 --- .../storage/lib/chunk_store/redis.js | 43 +++-- .../history-v1/storage/lib/persist_changes.js | 62 +++++++- .../storage/chunk_store_redis_backend.test.js | 149 +++++++++++++++--- 3 files changed, 220 insertions(+), 34 deletions(-) diff --git a/services/history-v1/storage/lib/chunk_store/redis.js b/services/history-v1/storage/lib/chunk_store/redis.js index 5c62db5387..58ee0d15a7 100644 --- a/services/history-v1/storage/lib/chunk_store/redis.js +++ b/services/history-v1/storage/lib/chunk_store/redis.js @@ -1,3 +1,5 @@ +// @ts-check + const metrics = require('@overleaf/metrics') const OError = require('@overleaf/o-error') const redis = require('../redis') @@ -97,27 +99,36 @@ rclient.defineCommand('queue_changes', { local head = ARGV[2] local persistTime = tonumber(ARGV[3]) local expireTime = tonumber(ARGV[4]) - -- Changes start from ARGV[5] + local onlyIfExists = ARGV[5] + local changesIndex = 6 -- Changes start here local headVersion = tonumber(redis.call('GET', headVersionKey)) + + -- Check if updates should only be queued if the project already exists (used for gradual rollouts) + if not headVersion and onlyIfExists == 'true' then + return 'ignore' + end + + -- Check that the supplied baseVersion matches the head version + -- If headVersion is nil, it means the project does not exist yet and will be created. if headVersion and headVersion ~= baseVersion then return 'conflict' end -- Check if there are any changes to queue - if #ARGV < 5 then + if #ARGV < changesIndex then return 'no_changes_provided' end -- Store the changes -- RPUSH changesKey change1 change2 ... - redis.call('RPUSH', changesKey, unpack(ARGV, 5, #ARGV)) + redis.call('RPUSH', changesKey, unpack(ARGV, changesIndex, #ARGV)) -- Update head snapshot only if changes were successfully pushed redis.call('SET', headSnapshotKey, head) -- Update the head version - local numChanges = #ARGV - 4 + local numChanges = #ARGV - changesIndex + 1 local newHeadVersion = baseVersion + numChanges redis.call('SET', headVersionKey, newHeadVersion) @@ -142,9 +153,14 @@ rclient.defineCommand('queue_changes', { * @param {Snapshot} headSnapshot - The new head snapshot after applying changes. * @param {number} baseVersion - The expected current head version. * @param {Change[]} changes - An array of Change objects to queue. - * @param {number} persistTime - Timestamp (ms since epoch) when the oldest change in the buffer should be persisted. - * @param {number} expireTime - Timestamp (ms since epoch) when the project buffer should expire if inactive. - * @returns {Promise} Resolves on success. + * @param {object} [opts] + * @param {number} [opts.persistTime] - Timestamp (ms since epoch) when the + * oldest change in the buffer should be persisted. + * @param {number} [opts.expireTime] - Timestamp (ms since epoch) when the + * project buffer should expire if inactive. + * @param {boolean} [opts.onlyIfExists] - If true, only queue changes if the + * project already exists in Redis, otherwise ignore. + * @returns {Promise} Resolves on success to either 'ok' or 'ignore'. * @throws {BaseVersionConflictError} If the baseVersion does not match the current head version in Redis. * @throws {Error} If changes array is empty or if Redis operations fail. */ @@ -153,13 +169,16 @@ async function queueChanges( headSnapshot, baseVersion, changes, - persistTime, - expireTime + opts = {} ) { if (!changes || changes.length === 0) { throw new Error('Cannot queue empty changes array') } + const persistTime = opts.persistTime ?? Date.now() + MAX_PERSIST_DELAY_MS + const expireTime = opts.expireTime ?? Date.now() + PROJECT_TTL_MS + const onlyIfExists = Boolean(opts.onlyIfExists) + try { const keys = [ keySchema.head({ projectId }), @@ -174,13 +193,17 @@ async function queueChanges( JSON.stringify(headSnapshot.toRaw()), persistTime.toString(), expireTime.toString(), + onlyIfExists.toString(), // Only queue changes if the snapshot already exists ...changes.map(change => JSON.stringify(change.toRaw())), // Serialize changes ] const status = await rclient.queue_changes(keys, args) metrics.inc('chunk_store.redis.queue_changes', 1, { status }) if (status === 'ok') { - return + return status + } + if (status === 'ignore') { + return status // skip changes when project does not exist and onlyIfExists is true } if (status === 'conflict') { throw new BaseVersionConflictError('base version mismatch', { diff --git a/services/history-v1/storage/lib/persist_changes.js b/services/history-v1/storage/lib/persist_changes.js index 8a848aa214..4d832f4541 100644 --- a/services/history-v1/storage/lib/persist_changes.js +++ b/services/history-v1/storage/lib/persist_changes.js @@ -4,6 +4,7 @@ const _ = require('lodash') const logger = require('@overleaf/logger') +const metrics = require('@overleaf/metrics') const core = require('overleaf-editor-core') const Chunk = core.Chunk @@ -14,6 +15,7 @@ const chunkStore = require('./chunk_store') const { BlobStore } = require('./blob_store') const { InvalidChangeError } = require('./errors') const { getContentHash } = require('./content_hash') +const redisBackend = require('./chunk_store/redis') function countChangeBytes(change) { // Note: This is not quite accurate, because the raw change may contain raw @@ -179,7 +181,7 @@ async function persistChanges(projectId, allChanges, limits, clientEndVersion) { } } - async function extendLastChunkIfPossible() { + async function loadLatestChunk() { const latestChunk = await chunkStore.loadLatest(projectId) currentChunk = latestChunk @@ -192,9 +194,49 @@ async function persistChanges(projectId, allChanges, limits, clientEndVersion) { } currentSnapshot = latestChunk.getSnapshot().clone() - const timer = new Timer() - currentSnapshot.applyAll(latestChunk.getChanges()) + currentSnapshot.applyAll(currentChunk.getChanges()) + } + async function queueChangesInRedis() { + const hollowSnapshot = currentSnapshot.clone() + // We're transforming a lazy snapshot to a hollow snapshot, so loadFiles() + // doesn't really need a blobStore, but its signature still requires it. + const blobStore = new BlobStore(projectId) + await hollowSnapshot.loadFiles('hollow', blobStore) + hollowSnapshot.applyAll(changesToPersist) + const baseVersion = currentChunk.getEndVersion() + await redisBackend.queueChanges( + projectId, + hollowSnapshot, + baseVersion, + changesToPersist, + { onlyIfExists: true } + ) + } + + async function fakePersistRedisChanges() { + const nonPersistedChanges = + await redisBackend.getNonPersistedChanges(projectId) + + if ( + serializeChanges(nonPersistedChanges) === + serializeChanges(changesToPersist) + ) { + metrics.inc('persist_redis_changes_verification', 1, { status: 'match' }) + } else { + logger.warn({ projectId }, 'mismatch of non-persisted changes from Redis') + metrics.inc('persist_redis_changes_verification', 1, { + status: 'mismatch', + }) + } + + const baseVersion = currentChunk.getEndVersion() + const persistedVersion = baseVersion + nonPersistedChanges.length + await redisBackend.setPersistedVersion(projectId, persistedVersion) + } + + async function extendLastChunkIfPossible() { + const timer = new Timer() const changesPushed = await fillChunk(currentChunk, changesToPersist) if (!changesPushed) { return @@ -245,6 +287,13 @@ async function persistChanges(projectId, allChanges, limits, clientEndVersion) { changesToPersist = oldChanges const numberOfChangesToPersist = oldChanges.length + await loadLatestChunk() + try { + await queueChangesInRedis() + await fakePersistRedisChanges() + } catch (err) { + logger.error({ err }, 'Chunk buffer verification failed') + } await extendLastChunkIfPossible() await createNewChunksAsNeeded() @@ -258,4 +307,11 @@ async function persistChanges(projectId, allChanges, limits, clientEndVersion) { } } +/** + * @param {core.Change[]} changes + */ +function serializeChanges(changes) { + return JSON.stringify(changes.map(change => change.toRaw())) +} + module.exports = persistChanges diff --git a/services/history-v1/test/acceptance/js/storage/chunk_store_redis_backend.test.js b/services/history-v1/test/acceptance/js/storage/chunk_store_redis_backend.test.js index 514ae617cf..91b7e3a5f4 100644 --- a/services/history-v1/test/acceptance/js/storage/chunk_store_redis_backend.test.js +++ b/services/history-v1/test/acceptance/js/storage/chunk_store_redis_backend.test.js @@ -86,8 +86,7 @@ describe('chunk buffer Redis backend', function () { headSnapshot, baseVersion, [change], - persistTime, - expireTime + { persistTime, expireTime } ) // Get the state to verify the changes @@ -126,8 +125,7 @@ describe('chunk buffer Redis backend', function () { headSnapshot, baseVersion, [change], - persistTime, - expireTime + { persistTime, expireTime } ) // If we get here, the test should fail expect.fail('Expected BaseVersionConflictError but no error was thrown') @@ -157,8 +155,7 @@ describe('chunk buffer Redis backend', function () { headSnapshot, baseVersion, [], // Empty changes array - persistTime, - expireTime + { persistTime, expireTime } ) // If we get here, the test should fail expect.fail('Expected Error but no error was thrown') @@ -191,8 +188,7 @@ describe('chunk buffer Redis backend', function () { headSnapshot, baseVersion, [change1, change2, change3], // Multiple changes - persistTime, - expireTime + { persistTime, expireTime } ) // Get the state to verify the changes @@ -226,8 +222,7 @@ describe('chunk buffer Redis backend', function () { headSnapshot, baseVersion, [change], - laterPersistTime, - expireTime + { persistTime: laterPersistTime, expireTime } ) // Get the state to verify the first persist time was set @@ -241,8 +236,10 @@ describe('chunk buffer Redis backend', function () { newerHeadSnapshot, baseVersion + 1, // Updated base version [change], - earlierPersistTime, // Earlier time should replace the later one - expireTime + { + persistTime: earlierPersistTime, // Earlier time should replace the later one + expireTime, + } ) // Get the state to verify the persist time was updated to the earlier time @@ -256,14 +253,127 @@ describe('chunk buffer Redis backend', function () { evenNewerHeadSnapshot, baseVersion + 2, // Updated base version [change], - laterPersistTime, // Later time should not replace the earlier one - expireTime + { + persistTime: laterPersistTime, // Later time should not replace the earlier one + expireTime, + } ) // Get the state to verify the persist time remains at the earlier time state = await redisBackend.getState(projectId) expect(state.persistTime).to.equal(earlierPersistTime) // Should still be the earlier time }) + + it('should ignore changes when onlyIfExists is true and project does not exist', async function () { + // Create base version + const baseVersion = 10 + + // Create a new head snapshot + const headSnapshot = new Snapshot() + + // Create changes + const timestamp = new Date() + const change = new Change([], timestamp) + + // Set times + const now = Date.now() + const persistTime = now + 30 * 1000 + const expireTime = now + 60 * 60 * 1000 + + // Queue changes with onlyIfExists set to true + const result = await redisBackend.queueChanges( + projectId, + headSnapshot, + baseVersion, + [change], + { persistTime, expireTime, onlyIfExists: true } + ) + + // Should return 'ignore' status + expect(result).to.equal('ignore') + + // Get the state - should be empty/null + const state = await redisBackend.getState(projectId) + expect(state.headVersion).to.be.null + expect(state.headSnapshot).to.be.null + }) + + it('should queue changes when onlyIfExists is true and project exists', async function () { + // First create the project + const headSnapshot = new Snapshot() + const baseVersion = 10 + const timestamp = new Date() + const change1 = new Change([], timestamp) + + // Set times + const now = Date.now() + const persistTime = now + 30 * 1000 + const expireTime = now + 60 * 60 * 1000 + + // Create the project first + await redisBackend.queueChanges( + projectId, + headSnapshot, + baseVersion, + [change1], + { persistTime, expireTime } + ) + + // Now create another change with onlyIfExists set to true + const newerSnapshot = new Snapshot() + const change2 = new Change([], timestamp) + + // Queue changes with onlyIfExists set to true + const result = await redisBackend.queueChanges( + projectId, + newerSnapshot, + baseVersion + 1, // Version should be 1 after the first change + [change2], + { persistTime, expireTime, onlyIfExists: true } + ) + + // Should return 'ok' status + expect(result).to.equal('ok') + + // Get the state to verify the changes were applied + const state = await redisBackend.getState(projectId) + expect(state.headVersion).to.equal(baseVersion + 2) // Should be 2 after both changes + expect(state.headSnapshot).to.deep.equal(newerSnapshot.toRaw()) + }) + + it('should queue changes when onlyIfExists is false and project does not exist', async function () { + // Create base version + const baseVersion = 10 + + // Create a new head snapshot + const headSnapshot = new Snapshot() + + // Create changes + const timestamp = new Date() + const change = new Change([], timestamp) + + // Set times + const now = Date.now() + const persistTime = now + 30 * 1000 + const expireTime = now + 60 * 60 * 1000 + + // Queue changes with onlyIfExists explicitly set to false + const result = await redisBackend.queueChanges( + projectId, + headSnapshot, + baseVersion, + [change], + { persistTime, expireTime, onlyIfExists: false } + ) + + // Should return 'ok' status + expect(result).to.equal('ok') + + // Get the state to verify the project was created + const state = await redisBackend.getState(projectId) + expect(state.headVersion).to.equal(baseVersion + 1) + expect(state.headSnapshot).to.deep.equal(headSnapshot.toRaw()) + }) }) describe('getChangesSinceVersion', function () { @@ -1011,18 +1121,15 @@ async function queueChanges(projectId, changes, opts = {}) { const baseVersion = 0 const headSnapshot = new Snapshot() - // Set times - const now = Date.now() - const persistTime = opts.persistTime ?? now + 30 * 1000 // 30 seconds from now - const expireTime = opts.expireTime ?? now + 60 * 60 * 1000 // 1 hour from now - await redisBackend.queueChanges( projectId, headSnapshot, baseVersion, changes, - persistTime, - expireTime + { + persistTime: opts.persistTime, + expireTime: opts.expireTime, + } ) } From ad94c296596d5bd8b24cb0f8dde2cfabe30e40ad Mon Sep 17 00:00:00 2001 From: Brian Gough Date: Wed, 7 May 2025 16:03:44 +0100 Subject: [PATCH 040/194] Merge pull request #25391 from overleaf/em-queue-changes-verification-rollout-stage-2 queue changes verification rollout stage 2 GitOrigin-RevId: c79a5a252c6fc8caf6fd164a31e6e360b6fc3e73 --- services/history-v1/storage/lib/persist_changes.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/services/history-v1/storage/lib/persist_changes.js b/services/history-v1/storage/lib/persist_changes.js index 4d832f4541..b17d7d8b37 100644 --- a/services/history-v1/storage/lib/persist_changes.js +++ b/services/history-v1/storage/lib/persist_changes.js @@ -209,8 +209,7 @@ async function persistChanges(projectId, allChanges, limits, clientEndVersion) { projectId, hollowSnapshot, baseVersion, - changesToPersist, - { onlyIfExists: true } + changesToPersist ) } From 4b5f31ac9567c8d12fad409c10aa99e16e62f145 Mon Sep 17 00:00:00 2001 From: Jessica Lawshe <5312836+lawshe@users.noreply.github.com> Date: Wed, 7 May 2025 10:15:36 -0500 Subject: [PATCH 041/194] Merge pull request #25353 from overleaf/revert-25351-revert-24919-jel-create-group-audit-log Revert "Revert "[web] Add group audit log"" GitOrigin-RevId: 4d61cfd9e8a7dac1f5837a4028aff95fa19c308a --- .../web/app/src/infrastructure/mongodb.js | 1 + .../web/app/src/models/GroupAuditLogEntry.js | 23 ++++++++++++ services/web/frontend/js/utils/meta.ts | 1 + .../20250409155536_group_audit_log_index.mjs | 35 +++++++++++++++++++ .../test/acceptance/src/helpers/groupSSO.mjs | 18 +++++----- 5 files changed, 70 insertions(+), 8 deletions(-) create mode 100644 services/web/app/src/models/GroupAuditLogEntry.js create mode 100644 services/web/migrations/20250409155536_group_audit_log_index.mjs diff --git a/services/web/app/src/infrastructure/mongodb.js b/services/web/app/src/infrastructure/mongodb.js index aa7aa4ac44..7fc1039140 100644 --- a/services/web/app/src/infrastructure/mongodb.js +++ b/services/web/app/src/infrastructure/mongodb.js @@ -49,6 +49,7 @@ const db = { githubSyncUserCredentials: internalDb.collection('githubSyncUserCredentials'), globalMetrics: internalDb.collection('globalMetrics'), grouppolicies: internalDb.collection('grouppolicies'), + groupAuditLogEntries: internalDb.collection('groupAuditLogEntries'), institutions: internalDb.collection('institutions'), messages: internalDb.collection('messages'), migrations: internalDb.collection('migrations'), diff --git a/services/web/app/src/models/GroupAuditLogEntry.js b/services/web/app/src/models/GroupAuditLogEntry.js new file mode 100644 index 0000000000..3bda4ebf95 --- /dev/null +++ b/services/web/app/src/models/GroupAuditLogEntry.js @@ -0,0 +1,23 @@ +const mongoose = require('../infrastructure/Mongoose') +const { Schema } = mongoose + +const GroupAuditLogEntrySchema = new Schema( + { + groupId: { type: Schema.Types.ObjectId, index: true }, + info: { type: Object }, + initiatorId: { type: Schema.Types.ObjectId }, + ipAddress: { type: String }, + operation: { type: String }, + timestamp: { type: Date, default: Date.now }, + }, + { + collection: 'groupAuditLogEntries', + minimize: false, + } +) + +exports.GroupAuditLogEntry = mongoose.model( + 'GroupAuditLogEntry', + GroupAuditLogEntrySchema +) +exports.GroupAuditLogEntrySchema = GroupAuditLogEntrySchema diff --git a/services/web/frontend/js/utils/meta.ts b/services/web/frontend/js/utils/meta.ts index 7aab88b050..6c7209a5bb 100644 --- a/services/web/frontend/js/utils/meta.ts +++ b/services/web/frontend/js/utils/meta.ts @@ -103,6 +103,7 @@ export interface Meta { 'ol-gitBridgeEnabled': boolean 'ol-gitBridgePublicBaseUrl': string 'ol-github': { enabled: boolean; error: boolean } + 'ol-groupAuditLogs': [] 'ol-groupId': string 'ol-groupName': string 'ol-groupPlans': GroupPlans diff --git a/services/web/migrations/20250409155536_group_audit_log_index.mjs b/services/web/migrations/20250409155536_group_audit_log_index.mjs new file mode 100644 index 0000000000..282b3c6d2d --- /dev/null +++ b/services/web/migrations/20250409155536_group_audit_log_index.mjs @@ -0,0 +1,35 @@ +/* eslint-disable no-unused-vars */ + +import Helpers from './lib/helpers.mjs' + +const tags = ['saas'] + +const indexes = [ + { + key: { + groupId: 1, + timestamp: 1, + }, + name: 'groupId_1_timestamp_1', + }, +] + +const migrate = async client => { + const { db } = client + await Helpers.addIndexesToCollection(db.groupAuditLogEntries, indexes) +} + +const rollback = async client => { + const { db } = client + try { + await Helpers.dropIndexesFromCollection(db.groupAuditLogEntries, indexes) + } catch (err) { + console.error('Something went wrong rolling back the migrations', err) + } +} + +export default { + tags, + migrate, + rollback, +} diff --git a/services/web/test/acceptance/src/helpers/groupSSO.mjs b/services/web/test/acceptance/src/helpers/groupSSO.mjs index f7efeb9e63..c5bde77236 100644 --- a/services/web/test/acceptance/src/helpers/groupSSO.mjs +++ b/services/web/test/acceptance/src/helpers/groupSSO.mjs @@ -34,7 +34,7 @@ export const baseSsoConfig = { userIdAttribute, } // the database also sets enabled and validated, but we cannot set that in the POST request for /manage/groups/:ID/settings/sso -export async function createGroupSSO() { +export async function createGroupSSO(SSOConfigValidated = true) { const nonSSOMemberHelper = await UserHelper.createUser() const nonSSOMember = nonSSOMemberHelper.user @@ -47,7 +47,7 @@ export async function createGroupSSO() { const ssoConfig = new SSOConfig({ ...baseSsoConfig, enabled: true, - validated: true, + validated: SSOConfigValidated, }) await ssoConfig.save() @@ -68,12 +68,14 @@ export async function createGroupSSO() { const enrollmentUrl = getEnrollmentUrl(subscriptionId) const internalProviderId = getProviderId(subscriptionId) - await linkGroupMember( - memberUser.email, - memberUser.password, - subscriptionId, - 'mock@email.com' - ) + if (SSOConfigValidated) { + await linkGroupMember( + memberUser.email, + memberUser.password, + subscriptionId, + 'mock@email.com' + ) + } const userHelper = new UserHelper() From b3a134154556be8edfdfc69928ed66999236e62f Mon Sep 17 00:00:00 2001 From: Jakob Ackermann Date: Wed, 7 May 2025 17:30:13 +0200 Subject: [PATCH 042/194] [web] settle on a single split-test for the clsi-cache rollout (#25399) * [web] settle on a single split-test for the clsi-cache rollout Use the split-test that was used for rolling out the writes so that we can use their already populated caches. * [clsi-cache] fix non-sharded clsi-cache in dev-env GitOrigin-RevId: 6ebd6369183342fe6d5e325b760d011fd1d57516 --- .../web/app/src/Features/Compile/CompileController.js | 8 +------- .../web/app/src/Features/Project/ProjectController.js | 3 +-- .../js/features/pdf-preview/util/pdf-caching-flags.js | 2 +- .../frontend/js/shared/context/local-compile-context.tsx | 2 +- .../frontend/components/pdf-preview/pdf-preview.spec.tsx | 2 +- 5 files changed, 5 insertions(+), 12 deletions(-) diff --git a/services/web/app/src/Features/Compile/CompileController.js b/services/web/app/src/Features/Compile/CompileController.js index 20356f7a7a..34d92a4e59 100644 --- a/services/web/app/src/Features/Compile/CompileController.js +++ b/services/web/app/src/Features/Compile/CompileController.js @@ -72,13 +72,6 @@ async function _getSplitTestOptions(req, res) { // Lookup the clsi-cache flag in the backend. // We may need to turn off the feature on a short notice, without requiring // all users to reload their editor page to disable the feature. - const { variant: compileFromClsiCacheVariant } = - await SplitTestHandler.promises.getAssignment( - editorReq, - res, - 'compile-from-clsi-cache' - ) - const compileFromClsiCache = compileFromClsiCacheVariant === 'enabled' const { variant: populateClsiCacheVariant } = await SplitTestHandler.promises.getAssignment( editorReq, @@ -86,6 +79,7 @@ async function _getSplitTestOptions(req, res) { 'populate-clsi-cache' ) const populateClsiCache = populateClsiCacheVariant === 'enabled' + const compileFromClsiCache = populateClsiCache // use same split-test const pdfDownloadDomain = Settings.pdfDownloadDomain diff --git a/services/web/app/src/Features/Project/ProjectController.js b/services/web/app/src/Features/Project/ProjectController.js index c82c65c5da..2d949a8753 100644 --- a/services/web/app/src/Features/Project/ProjectController.js +++ b/services/web/app/src/Features/Project/ProjectController.js @@ -338,8 +338,7 @@ const _ProjectController = { 'external-socket-heartbeat', 'full-project-search', 'null-test-share-modal', - 'fall-back-to-clsi-cache', - 'initial-compile-from-clsi-cache', + 'populate-clsi-cache', 'pdf-caching-cached-url-lookup', 'pdf-caching-mode', 'pdf-caching-prefetch-large', diff --git a/services/web/frontend/js/features/pdf-preview/util/pdf-caching-flags.js b/services/web/frontend/js/features/pdf-preview/util/pdf-caching-flags.js index dd7ed2c1b5..fb2a5b12b2 100644 --- a/services/web/frontend/js/features/pdf-preview/util/pdf-caching-flags.js +++ b/services/web/frontend/js/features/pdf-preview/util/pdf-caching-flags.js @@ -30,4 +30,4 @@ export const projectOwnerHasPremiumOnPageLoad = getMeta( 'ol-projectOwnerHasPremiumOnPageLoad' ) export const fallBackToClsiCache = - projectOwnerHasPremiumOnPageLoad && isFlagEnabled('fall-back-to-clsi-cache') + projectOwnerHasPremiumOnPageLoad && isFlagEnabled('populate-clsi-cache') diff --git a/services/web/frontend/js/shared/context/local-compile-context.tsx b/services/web/frontend/js/shared/context/local-compile-context.tsx index ba63b406bd..e16fc53114 100644 --- a/services/web/frontend/js/shared/context/local-compile-context.tsx +++ b/services/web/frontend/js/shared/context/local-compile-context.tsx @@ -204,7 +204,7 @@ export const LocalCompileProvider: FC = ({ // fetch initial compile response from cache const [initialCompileFromCache, setInitialCompileFromCache] = useState( getMeta('ol-projectOwnerHasPremiumOnPageLoad') && - isSplitTestEnabled('initial-compile-from-clsi-cache') && + isSplitTestEnabled('populate-clsi-cache') && // Avoid fetching the initial compile from cache in PDF detach tab role !== 'detached' ) diff --git a/services/web/test/frontend/components/pdf-preview/pdf-preview.spec.tsx b/services/web/test/frontend/components/pdf-preview/pdf-preview.spec.tsx index db637c1f77..fd2075236c 100644 --- a/services/web/test/frontend/components/pdf-preview/pdf-preview.spec.tsx +++ b/services/web/test/frontend/components/pdf-preview/pdf-preview.spec.tsx @@ -47,7 +47,7 @@ describe('', function () { 'https://compiles-user.dev-overleaf.com' ) window.metaAttributesCache.set('ol-splitTestVariants', { - 'initial-compile-from-clsi-cache': 'enabled', + 'populate-clsi-cache': 'enabled', }) window.metaAttributesCache.set('ol-projectOwnerHasPremiumOnPageLoad', true) cy.interceptEvents() From fa553128a4ea8507b657740d4f26d06f52c17b62 Mon Sep 17 00:00:00 2001 From: M Fahru Date: Wed, 7 May 2025 10:29:41 -0700 Subject: [PATCH 043/194] Merge pull request #25289 from overleaf/kh-rm-dead-coupon-code [web] rm unused couponCode parameter GitOrigin-RevId: c8c262322d74214e43870e67758aaa98aaa60c79 --- .../Subscription/SubscriptionHandler.js | 15 +---- .../Subscription/SubscriptionHandlerTests.js | 60 +------------------ 2 files changed, 4 insertions(+), 71 deletions(-) diff --git a/services/web/app/src/Features/Subscription/SubscriptionHandler.js b/services/web/app/src/Features/Subscription/SubscriptionHandler.js index 9cff487aec..4db44b7a25 100644 --- a/services/web/app/src/Features/Subscription/SubscriptionHandler.js +++ b/services/web/app/src/Features/Subscription/SubscriptionHandler.js @@ -83,9 +83,8 @@ async function previewSubscriptionChange(userId, planCode) { /** * @param user * @param planCode - * @param couponCode */ -async function updateSubscription(user, planCode, couponCode) { +async function updateSubscription(user, planCode) { let hasSubscription = false let subscription @@ -108,18 +107,6 @@ async function updateSubscription(user, planCode, couponCode) { } const recurlySubscriptionId = subscription.recurlySubscription_id - if (couponCode) { - const usersSubscription = await RecurlyWrapper.promises.getSubscription( - recurlySubscriptionId, - { includeAccount: true } - ) - - await RecurlyWrapper.promises.redeemCoupon( - usersSubscription.account.account_code, - couponCode - ) - } - const recurlySubscription = await RecurlyClient.promises.getSubscription( recurlySubscriptionId ) diff --git a/services/web/test/unit/src/Subscription/SubscriptionHandlerTests.js b/services/web/test/unit/src/Subscription/SubscriptionHandlerTests.js index 0517e451d4..32b9ece455 100644 --- a/services/web/test/unit/src/Subscription/SubscriptionHandlerTests.js +++ b/services/web/test/unit/src/Subscription/SubscriptionHandlerTests.js @@ -274,8 +274,7 @@ describe('SubscriptionHandler', function () { }) await this.SubscriptionHandler.promises.updateSubscription( this.user, - this.plan_code, - null + this.plan_code ) }) @@ -324,8 +323,7 @@ describe('SubscriptionHandler', function () { expect( this.SubscriptionHandler.promises.updateSubscription( this.user, - 'unknown-plan', - null + 'unknown-plan' ) ).to.be.rejected this.RecurlyClient.promises.applySubscriptionChangeRequest.called.should.equal( @@ -339,8 +337,7 @@ describe('SubscriptionHandler', function () { this.LimitationsManager.promises.userHasSubscription.resolves(false) await this.SubscriptionHandler.promises.updateSubscription( this.user, - this.plan_code, - null + this.plan_code ) }) @@ -353,57 +350,6 @@ describe('SubscriptionHandler', function () { ) }) }) - - describe('with a coupon code', function () { - beforeEach(async function () { - this.user.id = this.activeRecurlySubscription.account.account_code - - this.User.findById = (userId, projection) => ({ - exec: () => { - userId.should.equal(this.user.id) - return Promise.resolve(this.user) - }, - }) - this.plan_code = 'collaborator' - this.coupon_code = '1231312' - this.LimitationsManager.promises.userHasSubscription.resolves({ - hasSubscription: true, - subscription: this.subscription, - }) - await this.SubscriptionHandler.promises.updateSubscription( - this.user, - this.plan_code, - this.coupon_code - ) - }) - - it('should get the users account', function () { - this.RecurlyWrapper.promises.getSubscription - .calledWith(this.activeRecurlySubscription.uuid) - .should.equal(true) - }) - - it('should redeem the coupon', function () { - this.RecurlyWrapper.promises.redeemCoupon - .calledWith( - this.activeRecurlySubscription.account.account_code, - this.coupon_code - ) - .should.equal(true) - }) - - it('should update the subscription', function () { - expect( - this.RecurlyClient.promises.applySubscriptionChangeRequest - ).to.be.calledWith( - new PaymentProviderSubscriptionChangeRequest({ - subscription: this.activeRecurlyClientSubscription, - timeframe: 'now', - planCode: this.plan_code, - }) - ) - }) - }) }) describe('cancelSubscription', function () { From 0335367c75eaa11061985907411542623842bfbe Mon Sep 17 00:00:00 2001 From: M Fahru Date: Wed, 7 May 2025 10:30:20 -0700 Subject: [PATCH 044/194] Merge pull request #25296 from overleaf/kh-support-upgrading-stripe-subscription [web] support upgrading Stripe subscription GitOrigin-RevId: 2663ca8f1c028a45cf47d3ab37c387c4f4b39f9a --- .../Subscription/SubscriptionHandler.js | 14 +- .../Subscription/SubscriptionHandlerTests.js | 133 +++++++----------- 2 files changed, 57 insertions(+), 90 deletions(-) diff --git a/services/web/app/src/Features/Subscription/SubscriptionHandler.js b/services/web/app/src/Features/Subscription/SubscriptionHandler.js index 4db44b7a25..5d328c9c59 100644 --- a/services/web/app/src/Features/Subscription/SubscriptionHandler.js +++ b/services/web/app/src/Features/Subscription/SubscriptionHandler.js @@ -101,18 +101,18 @@ async function updateSubscription(user, planCode) { if ( !hasSubscription || subscription == null || - subscription.recurlySubscription_id == null + (subscription.recurlySubscription_id == null && + subscription.paymentProvider?.subscriptionId == null) ) { return } - const recurlySubscriptionId = subscription.recurlySubscription_id - const recurlySubscription = await RecurlyClient.promises.getSubscription( - recurlySubscriptionId + await Modules.promises.hooks.fire( + 'updatePaidSubscription', + subscription, + planCode, + user._id ) - const changeRequest = recurlySubscription.getRequestForPlanChange(planCode) - await RecurlyClient.promises.applySubscriptionChangeRequest(changeRequest) - await syncSubscription({ uuid: recurlySubscriptionId }, user._id) } /** diff --git a/services/web/test/unit/src/Subscription/SubscriptionHandlerTests.js b/services/web/test/unit/src/Subscription/SubscriptionHandlerTests.js index 32b9ece455..7814cc560d 100644 --- a/services/web/test/unit/src/Subscription/SubscriptionHandlerTests.js +++ b/services/web/test/unit/src/Subscription/SubscriptionHandlerTests.js @@ -4,7 +4,6 @@ const chai = require('chai') const { expect } = chai const { PaymentProviderSubscription, - PaymentProviderSubscriptionChangeRequest, } = require('../../../../app/src/Features/Subscription/PaymentProviderEntities') const MODULE_PATH = @@ -258,97 +257,65 @@ describe('SubscriptionHandler', function () { }) describe('updateSubscription', function () { - describe('with a user with a subscription', function () { - beforeEach(async function () { - this.user.id = this.activeRecurlySubscription.account.account_code - this.User.findById = (userId, projection) => ({ - exec: () => { - userId.should.equal(this.user.id) - return Promise.resolve(this.user) - }, - }) - this.plan_code = 'professional' - this.LimitationsManager.promises.userHasSubscription.resolves({ - hasSubscription: true, - subscription: this.subscription, - }) - await this.SubscriptionHandler.promises.updateSubscription( - this.user, - this.plan_code - ) - }) - - it('should update the subscription', function () { - expect( - this.RecurlyClient.promises.applySubscriptionChangeRequest - ).to.have.been.calledWith( - new PaymentProviderSubscriptionChangeRequest({ - subscription: this.activeRecurlyClientSubscription, - timeframe: 'now', - planCode: this.plan_code, - }) - ) - }) - - it('should sync the new subscription to the user', function () { - expect(this.SubscriptionUpdater.promises.syncSubscription).to.have.been - .called - - this.SubscriptionUpdater.promises.syncSubscription.args[0][0].should.deep.equal( - this.activeRecurlySubscription - ) - this.SubscriptionUpdater.promises.syncSubscription.args[0][1].should.deep.equal( - this.user._id - ) + beforeEach(function () { + this.user.id = this.activeRecurlySubscription.account.account_code + this.User.findById = (userId, projection) => ({ + exec: () => { + userId.should.equal(this.user.id) + return Promise.resolve(this.user) + }, }) }) - describe('when plan(s) could not be located in settings', function () { - beforeEach(async function () { - this.user.id = this.activeRecurlySubscription.account.account_code - this.User.findById = (userId, projection) => ({ - exec: () => { - userId.should.equal(this.user.id) - return Promise.resolve(this.user) - }, - }) - - this.LimitationsManager.promises.userHasSubscription.resolves({ - hasSubscription: true, - subscription: this.subscription, - }) - }) - - it('should be rejected and should not update the subscription', function () { - expect( - this.SubscriptionHandler.promises.updateSubscription( - this.user, - 'unknown-plan' - ) - ).to.be.rejected - this.RecurlyClient.promises.applySubscriptionChangeRequest.called.should.equal( - false - ) + it('should not fire updatePaidSubscription hook if user has no subscription', async function () { + this.LimitationsManager.promises.userHasSubscription.resolves({ + hasSubscription: false, + subscription: null, }) + await this.SubscriptionHandler.promises.updateSubscription( + this.user, + this.plan_code + ) + expect(this.Modules.promises.hooks.fire).to.not.have.been.calledWith( + 'updatePaidSubscription', + sinon.match.any, + sinon.match.any, + sinon.match.any + ) }) - describe('with a user without a subscription', function () { - beforeEach(async function () { - this.LimitationsManager.promises.userHasSubscription.resolves(false) - await this.SubscriptionHandler.promises.updateSubscription( - this.user, - this.plan_code - ) + it('should not fire updatePaidSubscription hook if user has custom subscription', async function () { + this.LimitationsManager.promises.userHasSubscription.resolves({ + hasSubscription: true, + subscription: { customAccount: true }, }) + await this.SubscriptionHandler.promises.updateSubscription( + this.user, + this.plan_code + ) + expect(this.Modules.promises.hooks.fire).to.not.have.been.calledWith( + 'updatePaidSubscription', + sinon.match.any, + sinon.match.any, + sinon.match.any + ) + }) - it('should redirect to the subscription dashboard', function () { - this.RecurlyClient.promises.applySubscriptionChangeRequest.called.should.equal( - false - ) - this.SubscriptionUpdater.promises.syncSubscription.called.should.equal( - false - ) + it('should fire updatePaidSubscription to update a valid subscription', async function () { + this.LimitationsManager.promises.userHasSubscription.resolves({ + hasSubscription: true, + subscription: this.subscription, }) + await this.SubscriptionHandler.promises.updateSubscription( + this.user, + this.plan_code + ) + expect(this.Modules.promises.hooks.fire).to.have.been.calledWith( + 'updatePaidSubscription', + this.subscription, + this.plan_code, + this.user._id + ) }) }) From d39d92cce8f4b29f02587984a5ad5485e38925bd Mon Sep 17 00:00:00 2001 From: CloudBuild Date: Thu, 8 May 2025 01:03:47 +0000 Subject: [PATCH 045/194] auto update translation GitOrigin-RevId: b57698529938bdf3227696cf8fbfde7f763a43d5 --- services/web/locales/zh-CN.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/services/web/locales/zh-CN.json b/services/web/locales/zh-CN.json index 4f9399d132..9d3e0befea 100644 --- a/services/web/locales/zh-CN.json +++ b/services/web/locales/zh-CN.json @@ -95,8 +95,6 @@ "add_on": "插件", "add_ons": "插件", "add_or_remove_project_from_tag": "根据标记 __tagName__ 来添加或移除项目", - "add_overleaf_assist_to_your_group_subscription": "将 Overleaf Assist 添加到您的团体订阅", - "add_overleaf_assist_to_your_institution": "将 Overleaf Assist 添加到您的机构", "add_people": "添加人员", "add_role_and_department": "添加角色和部门", "add_to_dictionary": "添加到词典", From 2ccdb74d201c535da49a6080bfc676bff2f6c543 Mon Sep 17 00:00:00 2001 From: ilkin-overleaf <100852799+ilkin-overleaf@users.noreply.github.com> Date: Thu, 8 May 2025 11:54:02 +0300 Subject: [PATCH 046/194] Merge pull request #25318 from overleaf/ii-flexible-licensing-manually-collected-3 [web] Add seats feature for manually collected subscriptions improvements GitOrigin-RevId: 4fbd93097590d97ad6464d1988471a78bf7cb9e2 --- .../app/src/Features/Subscription/Errors.js | 3 + .../Subscription/PaymentProviderEntities.js | 2 + .../Features/Subscription/RecurlyClient.js | 23 +----- .../Subscription/SubscriptionController.js | 1 + .../SubscriptionGroupController.mjs | 22 +++++- .../Subscription/SubscriptionGroupHandler.js | 25 +++++- .../Subscription/SubscriptionRouter.mjs | 3 +- .../web/frontend/extracted-translations.json | 6 ++ .../components/add-seats/add-seats.tsx | 46 ++++++++++- .../components/add-seats/cost-summary.tsx | 21 ++++- .../components/add-seats/po-number.tsx | 18 ++++- .../manually-collected-subscription.tsx | 35 +++++++-- .../dashboard/states/active/active-new.tsx | 2 +- .../manually-collected-subscription.tsx | 7 +- services/web/locales/en.json | 5 ++ .../components/add-seats.spec.tsx | 76 +++++++++++++++++-- .../PaymentProviderEntitiesTest.js | 1 + .../src/Subscription/RecurlyClientTests.js | 27 +------ .../SubscriptionGroupControllerTests.mjs | 19 +++++ .../SubscriptionGroupHandlerTests.js | 33 ++++++-- .../subscription-change-preview.ts | 1 + 21 files changed, 293 insertions(+), 83 deletions(-) diff --git a/services/web/app/src/Features/Subscription/Errors.js b/services/web/app/src/Features/Subscription/Errors.js index 53ecf7ba12..cbcd0014f7 100644 --- a/services/web/app/src/Features/Subscription/Errors.js +++ b/services/web/app/src/Features/Subscription/Errors.js @@ -24,6 +24,8 @@ class InactiveError extends OError {} class SubtotalLimitExceededError extends OError {} +class HasPastDueInvoiceError extends OError {} + module.exports = { RecurlyTransactionError, DuplicateAddOnError, @@ -33,4 +35,5 @@ module.exports = { PendingChangeError, InactiveError, SubtotalLimitExceededError, + HasPastDueInvoiceError, } diff --git a/services/web/app/src/Features/Subscription/PaymentProviderEntities.js b/services/web/app/src/Features/Subscription/PaymentProviderEntities.js index 8cc15f6f2e..298480a972 100644 --- a/services/web/app/src/Features/Subscription/PaymentProviderEntities.js +++ b/services/web/app/src/Features/Subscription/PaymentProviderEntities.js @@ -30,6 +30,7 @@ class PaymentProviderSubscription { * @param {Date} props.periodStart * @param {Date} props.periodEnd * @param {string} props.collectionMethod + * @param {number} [props.netTerms] * @param {string} [props.poNumber] * @param {string} [props.termsAndConditions] * @param {PaymentProviderSubscriptionChange} [props.pendingChange] @@ -55,6 +56,7 @@ class PaymentProviderSubscription { this.periodStart = props.periodStart this.periodEnd = props.periodEnd this.collectionMethod = props.collectionMethod + this.netTerms = props.netTerms ?? 0 this.poNumber = props.poNumber ?? '' this.termsAndConditions = props.termsAndConditions ?? '' this.pendingChange = props.pendingChange ?? null diff --git a/services/web/app/src/Features/Subscription/RecurlyClient.js b/services/web/app/src/Features/Subscription/RecurlyClient.js index f5f2e5f31f..fdb3b023e6 100644 --- a/services/web/app/src/Features/Subscription/RecurlyClient.js +++ b/services/web/app/src/Features/Subscription/RecurlyClient.js @@ -384,25 +384,6 @@ async function getPlan(planCode) { return planFromApi(plan) } -/** - * Get the country code for given user - * - * @param {string} userId - * @return {Promise} - */ -async function getCountryCode(userId) { - const account = await client.getAccount(`code-${userId}`) - const countryCode = account.address?.country - - if (!countryCode) { - throw new OError('Country code not found', { - userId, - }) - } - - return countryCode -} - function subscriptionIsCanceledOrExpired(subscription) { const state = subscription?.recurlyStatus?.state return state === 'canceled' || state === 'expired' @@ -467,6 +448,7 @@ function subscriptionFromApi(apiSubscription) { apiSubscription.currentPeriodStartedAt == null || apiSubscription.currentPeriodEndsAt == null || apiSubscription.collectionMethod == null || + apiSubscription.netTerms == null || // The values below could be null initially if the subscription has never updated !('poNumber' in apiSubscription) || !('termsAndConditions' in apiSubscription) @@ -491,6 +473,7 @@ function subscriptionFromApi(apiSubscription) { periodStart: apiSubscription.currentPeriodStartedAt, periodEnd: apiSubscription.currentPeriodEndsAt, collectionMethod: apiSubscription.collectionMethod, + netTerms: apiSubscription.netTerms ?? 0, poNumber: apiSubscription.poNumber ?? '', termsAndConditions: apiSubscription.termsAndConditions ?? '', service: 'recurly', @@ -720,7 +703,6 @@ module.exports = { getPaymentMethod: callbackify(getPaymentMethod), getAddOn: callbackify(getAddOn), getPlan: callbackify(getPlan), - getCountryCode: callbackify(getCountryCode), subscriptionIsCanceledOrExpired, pauseSubscriptionByUuid: callbackify(pauseSubscriptionByUuid), resumeSubscriptionByUuid: callbackify(resumeSubscriptionByUuid), @@ -744,6 +726,5 @@ module.exports = { getPaymentMethod, getAddOn, getPlan, - getCountryCode, }, } diff --git a/services/web/app/src/Features/Subscription/SubscriptionController.js b/services/web/app/src/Features/Subscription/SubscriptionController.js index 885784d10d..72efe77980 100644 --- a/services/web/app/src/Features/Subscription/SubscriptionController.js +++ b/services/web/app/src/Features/Subscription/SubscriptionController.js @@ -740,6 +740,7 @@ function makeChangePreview( currency: subscription.currency, immediateCharge: { ...subscriptionChange.immediateCharge }, paymentMethod: paymentMethod?.toString(), + netTerms: subscription.netTerms, nextPlan: { annual: nextPlan.annual ?? false, }, diff --git a/services/web/app/src/Features/Subscription/SubscriptionGroupController.mjs b/services/web/app/src/Features/Subscription/SubscriptionGroupController.mjs index 6ce552ec75..ce1207cded 100644 --- a/services/web/app/src/Features/Subscription/SubscriptionGroupController.mjs +++ b/services/web/app/src/Features/Subscription/SubscriptionGroupController.mjs @@ -18,6 +18,7 @@ import { PendingChangeError, InactiveError, SubtotalLimitExceededError, + HasPastDueInvoiceError, } from './Errors.js' import RecurlyClient from './RecurlyClient.js' @@ -142,6 +143,9 @@ async function addSeatsToGroupSubscription(req, res) { await SubscriptionGroupHandler.promises.ensureSubscriptionIsActive( subscription ) + await SubscriptionGroupHandler.promises.ensureSubscriptionHasNoPastDueInvoice( + subscription + ) const { variant: flexibleLicensingForManuallyBilledSubscriptionsVariant } = await SplitTestHandler.promises.getAssignment( @@ -183,7 +187,11 @@ async function addSeatsToGroupSubscription(req, res) { ) } - if (error instanceof PendingChangeError || error instanceof InactiveError) { + if ( + error instanceof PendingChangeError || + error instanceof InactiveError || + error instanceof HasPastDueInvoiceError + ) { return res.redirect('/user/subscription') } @@ -216,7 +224,8 @@ async function previewAddSeatsSubscriptionChange(req, res) { error instanceof MissingBillingInfoError || error instanceof ManuallyCollectedError || error instanceof PendingChangeError || - error instanceof InactiveError + error instanceof InactiveError || + error instanceof HasPastDueInvoiceError ) { return res.status(422).end() } @@ -258,7 +267,8 @@ async function createAddSeatsSubscriptionChange(req, res) { error instanceof MissingBillingInfoError || error instanceof ManuallyCollectedError || error instanceof PendingChangeError || - error instanceof InactiveError + error instanceof InactiveError || + error instanceof HasPastDueInvoiceError ) { return res.status(422).end() } @@ -395,6 +405,12 @@ async function manuallyCollectedSubscription(req, res) { const subscription = await SubscriptionLocator.promises.getUsersSubscription(userId) + await SplitTestHandler.promises.getAssignment( + req, + res, + 'flexible-group-licensing-for-manually-billed-subscriptions' + ) + res.render('subscriptions/manually-collected-subscription', { groupName: subscription.teamName, }) diff --git a/services/web/app/src/Features/Subscription/SubscriptionGroupHandler.js b/services/web/app/src/Features/Subscription/SubscriptionGroupHandler.js index b92ce807f6..5772946b8a 100644 --- a/services/web/app/src/Features/Subscription/SubscriptionGroupHandler.js +++ b/services/web/app/src/Features/Subscription/SubscriptionGroupHandler.js @@ -17,6 +17,7 @@ const { ManuallyCollectedError, PendingChangeError, InactiveError, + HasPastDueInvoiceError, } = require('./Errors') const EmailHelper = require('../Helpers/EmailHelper') const { InvalidEmailError } = require('../Errors/Errors') @@ -103,6 +104,22 @@ async function ensureSubscriptionHasNoPendingChanges(recurlySubscription) { } } +async function ensureSubscriptionHasNoPastDueInvoice(subscription) { + const [paymentRecord] = await Modules.promises.hooks.fire( + 'getPaymentFromRecord', + subscription + ) + + if (paymentRecord.account.hasPastDueInvoice) { + throw new HasPastDueInvoiceError( + 'This subscription has a past due invoice', + { + subscriptionId: subscription._id.toString(), + } + ) + } +} + async function getUsersGroupSubscriptionDetails(userId) { const subscription = await SubscriptionLocator.promises.getUsersSubscription(userId) @@ -144,6 +161,7 @@ async function _addSeatsSubscriptionChange(userId, adding) { await ensureSubscriptionIsActive(subscription) await ensureSubscriptionHasNoPendingChanges(recurlySubscription) await checkBillingInfoExistence(recurlySubscription, userId) + await ensureSubscriptionHasNoPastDueInvoice(subscription) const currentAddonQuantity = recurlySubscription.addOns.find( @@ -259,10 +277,9 @@ async function updateSubscriptionPaymentTerms( recurlySubscription, poNumber ) { - const countryCode = await RecurlyClient.promises.getCountryCode(userId) const [termsAndConditions] = await Modules.promises.hooks.fire( 'generateTermsAndConditions', - { countryCode, poNumber } + { currency: recurlySubscription.currency, poNumber } ) const updateRequest = poNumber @@ -464,6 +481,9 @@ module.exports = { ensureSubscriptionHasNoPendingChanges: callbackify( ensureSubscriptionHasNoPendingChanges ), + ensureSubscriptionHasNoPastDueInvoice: callbackify( + ensureSubscriptionHasNoPastDueInvoice + ), getTotalConfirmedUsersInGroup: callbackify(getTotalConfirmedUsersInGroup), isUserPartOfGroup: callbackify(isUserPartOfGroup), getGroupPlanUpgradePreview: callbackify(getGroupPlanUpgradePreview), @@ -477,6 +497,7 @@ module.exports = { ensureSubscriptionIsActive, ensureSubscriptionCollectionMethodIsNotManual, ensureSubscriptionHasNoPendingChanges, + ensureSubscriptionHasNoPastDueInvoice, getTotalConfirmedUsersInGroup, isUserPartOfGroup, getUsersGroupSubscriptionDetails, diff --git a/services/web/app/src/Features/Subscription/SubscriptionRouter.mjs b/services/web/app/src/Features/Subscription/SubscriptionRouter.mjs index 54523b0004..154a1882b2 100644 --- a/services/web/app/src/Features/Subscription/SubscriptionRouter.mjs +++ b/services/web/app/src/Features/Subscription/SubscriptionRouter.mjs @@ -19,11 +19,12 @@ const subscriptionRateLimiter = new RateLimiter('subscription', { }) const MAX_NUMBER_OF_USERS = 20 +const MAX_NUMBER_OF_PO_NUMBER_CHARACTERS = 50 const addSeatsValidateSchema = { body: Joi.object({ adding: Joi.number().integer().min(1).max(MAX_NUMBER_OF_USERS).required(), - poNumber: Joi.string(), + poNumber: Joi.string().max(MAX_NUMBER_OF_PO_NUMBER_CHARACTERS), }), } diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json index 828fedb323..eeb661d392 100644 --- a/services/web/frontend/extracted-translations.json +++ b/services/web/frontend/extracted-translations.json @@ -849,6 +849,7 @@ "issued_on": "", "it_looks_like_that_didnt_work_you_can_try_again_or_get_in_touch": "", "it_looks_like_your_account_is_billed_manually": "", + "it_looks_like_your_account_is_billed_manually_upgrading_subscription": "", "it_looks_like_your_payment_details_are_missing_please_update_your_billing_information": "", "italics": "", "join_beta_program": "", @@ -1237,6 +1238,9 @@ "plus_more": "", "plus_x_additional_licenses_for_a_total_of_y_licenses": "", "po_number": "", + "po_number_can_include_digits_and_letters_only": "", + "po_number_must_not_exceed_x_characters": "", + "po_number_must_not_exceed_x_characters_plural": "", "postal_code": "", "premium": "", "premium_feature": "", @@ -1850,6 +1854,7 @@ "tooltip_show_filetree": "", "tooltip_show_panel": "", "tooltip_show_pdf": "", + "total_due_in_x_days": "", "total_due_today": "", "total_per_month": "", "total_per_year": "", @@ -2034,6 +2039,7 @@ "we_sent_new_code": "", "we_will_charge_you_now_for_the_cost_of_your_additional_licenses_based_on_remaining_months": "", "we_will_charge_you_now_for_your_new_plan_based_on_the_remaining_months_of_your_current_subscription": "", + "we_will_invoice_you_now_for_the_additional_licenses_based_on_remaining_months": "", "we_will_use_your_existing_payment_method": "", "webinars": "", "website_status": "", diff --git a/services/web/frontend/js/features/group-management/components/add-seats/add-seats.tsx b/services/web/frontend/js/features/group-management/components/add-seats/add-seats.tsx index 988876a4a0..79f20ff60f 100644 --- a/services/web/frontend/js/features/group-management/components/add-seats/add-seats.tsx +++ b/services/web/frontend/js/features/group-management/components/add-seats/add-seats.tsx @@ -33,6 +33,7 @@ import { sendMB } from '../../../../infrastructure/event-tracking' import { useFeatureFlag } from '@/shared/context/split-test-context' export const MAX_NUMBER_OF_USERS = 20 +export const MAX_NUMBER_OF_PO_NUMBER_CHARACTERS = 50 type CostSummaryData = MergeAndOverride< SubscriptionChangePreview, @@ -47,6 +48,7 @@ function AddSeats() { const isProfessional = getMeta('ol-isProfessional') const isCollectionMethodManual = getMeta('ol-isCollectionMethodManual') const [addSeatsInputError, setAddSeatsInputError] = useState() + const [poNumberInputError, setPoNumberInputError] = useState() const [shouldContactSales, setShouldContactSales] = useState(false) const isFlexibleGroupLicensingForManuallyBilledSubscriptions = useFeatureFlag( 'flexible-group-licensing-for-manually-billed-subscriptions' @@ -125,6 +127,38 @@ function AddSeats() { } } + const poNumberValidationSchema = useMemo(() => { + return yup + .string() + .matches( + /^[\p{L}\p{N}]*$/u, + t('po_number_can_include_digits_and_letters_only') + ) + .max( + MAX_NUMBER_OF_PO_NUMBER_CHARACTERS, + t('po_number_must_not_exceed_x_characters', { + count: MAX_NUMBER_OF_PO_NUMBER_CHARACTERS, + }) + ) + }, [t]) + + const validatePoNumber = async (value: string | undefined) => { + try { + await poNumberValidationSchema.validate(value) + setPoNumberInputError(undefined) + + return true + } catch (error) { + if (error instanceof yup.ValidationError) { + setPoNumberInputError(error.errors[0]) + } else { + debugConsole.error(error) + } + + return false + } + } + const handleSeatsChange = async (e: React.ChangeEvent) => { const value = e.target.value === '' ? undefined : e.target.value const isValidSeatsNumber = await validateSeats(value) @@ -161,7 +195,10 @@ function AddSeats() { ? undefined : (formData.get('po_number') as string) - if (!(await validateSeats(rawSeats))) { + if ( + !(await validateSeats(rawSeats)) || + !(await validatePoNumber(poNumber)) + ) { return } @@ -337,7 +374,12 @@ function AddSeats() { )} {isFlexibleGroupLicensingForManuallyBilledSubscriptions && - isCollectionMethodManual && } + isCollectionMethodManual && ( + + )}
- {t('total_due_today')} + + {isCollectionMethodManual + ? t('total_due_in_x_days', { + days: subscriptionChange.netTerms, + }) + : t('total_due_today')} + {formatCurrency( subscriptionChange.immediateCharge.total, @@ -121,9 +129,14 @@ function CostSummary({ subscriptionChange, totalLicenses }: CostSummaryProps) {
- {t( - 'we_will_charge_you_now_for_the_cost_of_your_additional_licenses_based_on_remaining_months' - )} + {isCollectionMethodManual + ? t( + 'we_will_invoice_you_now_for_the_additional_licenses_based_on_remaining_months', + { days: subscriptionChange.netTerms } + ) + : t( + 'we_will_charge_you_now_for_the_cost_of_your_additional_licenses_based_on_remaining_months' + )}
{t( diff --git a/services/web/frontend/js/features/group-management/components/add-seats/po-number.tsx b/services/web/frontend/js/features/group-management/components/add-seats/po-number.tsx index f72d7857a4..c66f5cd3fd 100644 --- a/services/web/frontend/js/features/group-management/components/add-seats/po-number.tsx +++ b/services/web/frontend/js/features/group-management/components/add-seats/po-number.tsx @@ -1,9 +1,15 @@ import { useState } from 'react' import { useTranslation } from 'react-i18next' import { FormControl, FormGroup, FormLabel } from 'react-bootstrap-5' +import FormText from '@/features/ui/components/bootstrap-5/form/form-text' import OLFormCheckbox from '@/features/ui/components/ol/ol-form-checkbox' -function PoNumber() { +type PoNumberProps = { + error: string | undefined + validate: (value: string | undefined) => Promise +} + +function PoNumber({ error, validate }: PoNumberProps) { const { t } = useTranslation() const [isPoNumberChecked, setIsPoNumberChecked] = useState(false) @@ -20,7 +26,15 @@ function PoNumber() { {isPoNumberChecked && ( {t('po_number')} - + await validate(e.target.value)} + isInvalid={Boolean(error)} + /> + {Boolean(error) && {error}} )} diff --git a/services/web/frontend/js/features/group-management/components/manually-collected-subscription.tsx b/services/web/frontend/js/features/group-management/components/manually-collected-subscription.tsx index 2b2c111716..971d4fa791 100644 --- a/services/web/frontend/js/features/group-management/components/manually-collected-subscription.tsx +++ b/services/web/frontend/js/features/group-management/components/manually-collected-subscription.tsx @@ -1,9 +1,20 @@ import { Trans, useTranslation } from 'react-i18next' import OLNotification from '@/features/ui/components/ol/ol-notification' import Card from '@/features/group-management/components/card' +import useWaitForI18n from '@/shared/hooks/use-wait-for-i18n' +import { useFeatureFlag } from '@/shared/context/split-test-context' function ManuallyCollectedSubscription() { const { t } = useTranslation() + const isFlexibleGroupLicensingForManuallyBilledSubscriptions = useFeatureFlag( + 'flexible-group-licensing-for-manually-billed-subscriptions' + ) + + const { isReady } = useWaitForI18n() + + if (!isReady) { + return null + } return ( @@ -11,13 +22,23 @@ function ManuallyCollectedSubscription() { type="error" title={t('account_billed_manually')} content={ - , - ]} - /> + isFlexibleGroupLicensingForManuallyBilledSubscriptions ? ( + , + ]} + /> + ) : ( + , + ]} + /> + ) } className="m-0" /> diff --git a/services/web/frontend/js/features/subscription/components/dashboard/states/active/active-new.tsx b/services/web/frontend/js/features/subscription/components/dashboard/states/active/active-new.tsx index dfc6448fb0..465fc60ef7 100644 --- a/services/web/frontend/js/features/subscription/components/dashboard/states/active/active-new.tsx +++ b/services/web/frontend/js/features/subscription/components/dashboard/states/active/active-new.tsx @@ -354,7 +354,7 @@ function FlexibleGroupLicensingActions({ }) { const { t } = useTranslation() - if (subscription.pendingPlan) { + if (subscription.pendingPlan || subscription.payment.hasPastDueInvoice) { return null } diff --git a/services/web/frontend/js/pages/user/subscription/group-management/manually-collected-subscription.tsx b/services/web/frontend/js/pages/user/subscription/group-management/manually-collected-subscription.tsx index 49aee93ca0..dedeffe15d 100644 --- a/services/web/frontend/js/pages/user/subscription/group-management/manually-collected-subscription.tsx +++ b/services/web/frontend/js/pages/user/subscription/group-management/manually-collected-subscription.tsx @@ -1,9 +1,14 @@ import '../base' import { createRoot } from 'react-dom/client' import ManuallyCollectedSubscription from '@/features/group-management/components/manually-collected-subscription' +import { SplitTestProvider } from '@/shared/context/split-test-context' const element = document.getElementById('manually-collected-subscription-root') if (element) { const root = createRoot(element) - root.render() + root.render( + + + + ) } diff --git a/services/web/locales/en.json b/services/web/locales/en.json index b9cf8163b7..f752e1aae3 100644 --- a/services/web/locales/en.json +++ b/services/web/locales/en.json @@ -1105,6 +1105,7 @@ "it": "Italian", "it_looks_like_that_didnt_work_you_can_try_again_or_get_in_touch": "It looks like that didn’t work. You can try again or <0>get in touch with our Support team for more help.", "it_looks_like_your_account_is_billed_manually": "It looks like your account is being billed manually - adding seats or upgrading your subscription can only be done by the Support team. Please <0>get in touch for help.", + "it_looks_like_your_account_is_billed_manually_upgrading_subscription": "It looks like your account is being billed manually - upgrading your subscription can only be done by the Support team. Please <0>get in touch for help.", "it_looks_like_your_payment_details_are_missing_please_update_your_billing_information": "It looks like your payment details are missing. Please <0>update your billing information, or <1>get in touch with our Support team for more help.", "italics": "Italics", "ja": "Japanese", @@ -1642,6 +1643,8 @@ "plus_more": "plus more", "plus_x_additional_licenses_for_a_total_of_y_licenses": "Plus <0>__additionalLicenses__ additional license(s) for a total of <1>__count__ licenses", "po_number": "PO Number", + "po_number_can_include_digits_and_letters_only": "PO number can include digits and letters only", + "po_number_must_not_exceed_x_characters": "PO number must not exceed __count__ characters", "popular_tags": "Popular Tags", "portal_add_affiliation_to_join": "It looks like you are already logged in to __appName__. If you have a __portalTitle__ email you can add it now.", "position": "Position", @@ -2377,6 +2380,7 @@ "tooltip_show_pdf": "Click to show the PDF", "top_pick": "Top pick", "total": "Total", + "total_due_in_x_days": "Total due in __days__ days", "total_due_today": "Total due today", "total_per_month": "Total per month", "total_per_year": "Total per year", @@ -2581,6 +2585,7 @@ "we_sent_new_code": "We’ve sent a new code. If it doesn’t arrive, make sure to check your spam and any promotions folders.", "we_will_charge_you_now_for_the_cost_of_your_additional_licenses_based_on_remaining_months": "We’ll charge you now for the cost of your additional licenses based on the remaining months of your current subscription.", "we_will_charge_you_now_for_your_new_plan_based_on_the_remaining_months_of_your_current_subscription": "We’ll charge you now for your new plan based on the remaining months of your current subscription.", + "we_will_invoice_you_now_for_the_additional_licenses_based_on_remaining_months": "We’ll invoice you now for the additional licences based on the remaining months of your current subscription, and payment will be due in __days__ days.", "we_will_use_your_existing_payment_method": "We’ll use your existing payment method __paymentMethod__.", "webinars": "Webinars", "website_status": "Website status", diff --git a/services/web/test/frontend/features/group-management/components/add-seats.spec.tsx b/services/web/test/frontend/features/group-management/components/add-seats.spec.tsx index 91f2dd9841..211714db2b 100644 --- a/services/web/test/frontend/features/group-management/components/add-seats.spec.tsx +++ b/services/web/test/frontend/features/group-management/components/add-seats.spec.tsx @@ -1,5 +1,6 @@ import AddSeats, { MAX_NUMBER_OF_USERS, + MAX_NUMBER_OF_PO_NUMBER_CHARACTERS, } from '@/features/group-management/components/add-seats/add-seats' import { SplitTestProvider } from '@/shared/context/split-test-context' @@ -97,6 +98,30 @@ describe('', function () { cy.findByLabelText(/i want to add a po number/i).check() cy.findByLabelText(/^po number$/i) }) + + describe('validation', function () { + beforeEach(function () { + cy.findByLabelText(/i want to add a po number/i).check() + }) + + it('should show max characters error', function () { + const totalCharacters = 'a'.repeat( + MAX_NUMBER_OF_PO_NUMBER_CHARACTERS + 1 + ) + cy.findByLabelText(/^po number$/i).type(totalCharacters) + cy.findByText( + new RegExp( + `po number must not exceed ${MAX_NUMBER_OF_PO_NUMBER_CHARACTERS} characters`, + 'i' + ) + ) + }) + + it('should show letters and numbers only error', function () { + cy.findByLabelText(/^po number$/i).type('🚧') + cy.findByText(/po number can include digits and letters only/i) + }) + }) }) describe('"Upgrade my plan" link', function () { @@ -251,6 +276,7 @@ describe('', function () { }, }, currency: 'USD', + netTerms: 30, immediateCharge: { subtotal: 100, tax: 20, @@ -276,13 +302,16 @@ describe('', function () { cy.findByRole('button', { name: /send request/i }).should('not.exist') }) - it('renders the preview data', function () { + function makeRequest(body: object, inputValue: string) { cy.intercept('POST', '/user/subscription/group/add-users/preview', { statusCode: 200, - body: this.body, + body, }).as('addUsersRequest') - cy.get('@input').type(this.adding.toString()) + cy.get('@input').type(inputValue) + } + it('renders common preview data content', function () { + makeRequest(this.body, this.adding.toString()) cy.findByTestId('cost-summary').within(() => { cy.contains( new RegExp( @@ -314,22 +343,55 @@ describe('', function () { cy.findByTestId('discount').should('not.exist') cy.findByTestId('total').within(() => { - cy.findByText(/total due today/i) cy.findByTestId('price').should( 'have.text', `$${this.body.immediateCharge.total}.00` ) }) - cy.findByText( - /we’ll charge you now for the cost of your additional licenses based on the remaining months of your current subscription/i - ) cy.findByText( /after that, we’ll bill you \$1,000\.00 \(\$895\.00 \+ \$105\.00 tax\) annually on December 1, unless you cancel/i ) }) }) + it('renders the preview data with manually billed subscription', function () { + makeRequest(this.body, this.adding.toString()) + cy.findByTestId('cost-summary').within(() => { + cy.findByTestId('total').within(() => { + cy.findByText( + new RegExp(`total due in ${this.body.netTerms} days`, 'i') + ) + }) + }) + cy.findByText( + new RegExp( + `we’ll invoice you now for the additional licences based on the remaining months of your current subscription, and payment will be due in ${this.body.netTerms} days`, + 'i' + ) + ) + }) + + it('renders the preview data with automatically billed subscription', function () { + cy.window().then(win => { + win.metaAttributesCache.set('ol-isCollectionMethodManual', false) + }) + cy.mount( + + + + ) + makeRequest(this.body, this.adding.toString()) + cy.findByTestId('cost-summary').within(() => { + cy.findByTestId('total').within(() => { + cy.findByText(/total due today/i) + }) + }) + cy.findByText( + /we’ll charge you now for the cost of your additional licenses based on the remaining months of your current subscription/i + ) + }) + it('renders the preview data with discount', function () { this.body.immediateCharge.discount = 50 diff --git a/services/web/test/unit/src/Subscription/PaymentProviderEntitiesTest.js b/services/web/test/unit/src/Subscription/PaymentProviderEntitiesTest.js index 1a28130b94..c6593da28d 100644 --- a/services/web/test/unit/src/Subscription/PaymentProviderEntitiesTest.js +++ b/services/web/test/unit/src/Subscription/PaymentProviderEntitiesTest.js @@ -412,6 +412,7 @@ describe('PaymentProviderEntities', function () { periodStart: new Date(), periodEnd: new Date(), collectionMethod: 'automatic', + netTerms: 0, poNumber: '012345', termsAndConditions: 'T&C copy', }) diff --git a/services/web/test/unit/src/Subscription/RecurlyClientTests.js b/services/web/test/unit/src/Subscription/RecurlyClientTests.js index 4ae415dca5..97088e9944 100644 --- a/services/web/test/unit/src/Subscription/RecurlyClientTests.js +++ b/services/web/test/unit/src/Subscription/RecurlyClientTests.js @@ -58,6 +58,7 @@ describe('RecurlyClient', function () { periodStart: new Date(), periodEnd: new Date(), collectionMethod: 'automatic', + netTerms: 0, poNumber: '', termsAndConditions: '', }) @@ -90,6 +91,7 @@ describe('RecurlyClient', function () { currentPeriodStartedAt: this.subscription.periodStart, currentPeriodEndsAt: this.subscription.periodEnd, collectionMethod: this.subscription.collectionMethod, + netTerms: this.subscription.netTerms, poNumber: this.subscription.poNumber, termsAndConditions: this.subscription.termsAndConditions, } @@ -690,29 +692,4 @@ describe('RecurlyClient', function () { ).to.be.rejectedWith(Error) }) }) - - describe('getCountryCode', function () { - it('should return the country code from the account info', async function () { - this.client.getAccount = sinon.stub().resolves({ - address: { - country: 'GB', - }, - }) - const countryCode = await this.RecurlyClient.promises.getCountryCode( - this.user._id - ) - expect(countryCode).to.equal('GB') - }) - - it('should throw if country code doesn’t exist', async function () { - this.client.getAccount = sinon.stub().resolves({ - address: { - country: '', - }, - }) - await expect( - this.RecurlyClient.promises.getCountryCode(this.user._id) - ).to.be.rejectedWith(Error, 'Country code not found') - }) - }) }) diff --git a/services/web/test/unit/src/Subscription/SubscriptionGroupControllerTests.mjs b/services/web/test/unit/src/Subscription/SubscriptionGroupControllerTests.mjs index 1d86199f9d..4376e752e7 100644 --- a/services/web/test/unit/src/Subscription/SubscriptionGroupControllerTests.mjs +++ b/services/web/test/unit/src/Subscription/SubscriptionGroupControllerTests.mjs @@ -67,6 +67,7 @@ describe('SubscriptionGroupController', function () { ensureSubscriptionIsActive: sinon.stub().resolves(), ensureSubscriptionCollectionMethodIsNotManual: sinon.stub().resolves(), ensureSubscriptionHasNoPendingChanges: sinon.stub().resolves(), + ensureSubscriptionHasNoPastDueInvoice: sinon.stub().resolves(), getGroupPlanUpgradePreview: sinon .stub() .resolves(this.previewSubscriptionChangeData), @@ -138,6 +139,7 @@ describe('SubscriptionGroupController', function () { PendingChangeError: class extends Error {}, InactiveError: class extends Error {}, SubtotalLimitExceededError: class extends Error {}, + HasPastDueInvoiceError: class extends Error {}, } this.Controller = await esmock.strict(modulePath, { @@ -370,6 +372,9 @@ describe('SubscriptionGroupController', function () { this.SubscriptionGroupHandler.promises.ensureSubscriptionIsActive .calledWith(this.subscription) .should.equal(true) + this.SubscriptionGroupHandler.promises.ensureSubscriptionHasNoPastDueInvoice + .calledWith(this.subscription) + .should.equal(true) this.SubscriptionGroupHandler.promises.checkBillingInfoExistence .calledWith(this.recurlySubscription, this.adminUserId) .should.equal(true) @@ -459,6 +464,20 @@ describe('SubscriptionGroupController', function () { this.Controller.addSeatsToGroupSubscription(this.req, res) }) + + it('should redirect to subscription page when subscription has pending invoice', function (done) { + this.SubscriptionGroupHandler.promises.ensureSubscriptionHasNoPastDueInvoice = + sinon.stub().rejects() + + const res = { + redirect: url => { + url.should.equal('/user/subscription') + done() + }, + } + + this.Controller.addSeatsToGroupSubscription(this.req, res) + }) }) describe('previewAddSeatsSubscriptionChange', function () { diff --git a/services/web/test/unit/src/Subscription/SubscriptionGroupHandlerTests.js b/services/web/test/unit/src/Subscription/SubscriptionGroupHandlerTests.js index d9e42d645c..0c47db3e14 100644 --- a/services/web/test/unit/src/Subscription/SubscriptionGroupHandlerTests.js +++ b/services/web/test/unit/src/Subscription/SubscriptionGroupHandlerTests.js @@ -146,7 +146,6 @@ describe('SubscriptionGroupHandler', function () { applySubscriptionChangeRequest: sinon .stub() .resolves(this.applySubscriptionChange), - getCountryCode: sinon.stub().resolves('BG'), updateSubscriptionDetails: sinon.stub().resolves(), }, } @@ -198,6 +197,11 @@ describe('SubscriptionGroupHandler', function () { if (hookName === 'generateTermsAndConditions') { return Promise.resolve(['T&Cs']) } + if (hookName === 'getPaymentFromRecord') { + return Promise.resolve([ + { account: { hasPastDueInvoice: false } }, + ]) + } return Promise.resolve() }), }, @@ -504,9 +508,6 @@ describe('SubscriptionGroupHandler', function () { describe('updateSubscriptionPaymentTerms', function () { describe('accounts with PO number', function () { it('should update the subscription PO number and T&C', async function () { - this.RecurlyClient.promises.getCountryCode = sinon - .stub() - .resolves('GB') await this.Handler.promises.updateSubscriptionPaymentTerms( this.adminUser_id, this.recurlySubscription, @@ -526,9 +527,6 @@ describe('SubscriptionGroupHandler', function () { describe('accounts with no PO number', function () { it('should update the subscription T&C only', async function () { - this.RecurlyClient.promises.getCountryCode = sinon - .stub() - .resolves('GB') await this.Handler.promises.updateSubscriptionPaymentTerms( this.adminUser_id, this.recurlySubscription @@ -758,6 +756,27 @@ describe('SubscriptionGroupHandler', function () { }) }) + describe('ensureSubscriptionHasNoPastDueInvoice', function () { + it('should throw if the subscription has past due invoice', async function () { + this.Modules.promises.hooks.fire + .withArgs('getPaymentFromRecord') + .resolves([{ account: { hasPastDueInvoice: true } }]) + await expect( + this.Handler.promises.ensureSubscriptionHasNoPastDueInvoice( + this.subscription + ) + ).to.be.rejectedWith('This subscription has a past due invoice') + }) + + it('should not throw if the subscription has no past due invoice', async function () { + await expect( + this.Handler.promises.ensureSubscriptionHasNoPastDueInvoice( + this.subscription + ) + ).to.not.be.rejected + }) + }) + describe('upgradeGroupPlan', function () { it('should upgrade the subscription for flexible licensing group plans', async function () { this.SubscriptionLocator.promises.getUsersSubscription = sinon diff --git a/services/web/types/subscription/subscription-change-preview.ts b/services/web/types/subscription/subscription-change-preview.ts index 096820a2f6..5152b80e6e 100644 --- a/services/web/types/subscription/subscription-change-preview.ts +++ b/services/web/types/subscription/subscription-change-preview.ts @@ -2,6 +2,7 @@ export type SubscriptionChangePreview = { change: SubscriptionChangeDescription currency: string paymentMethod: string | undefined + netTerms: number nextPlan: { annual: boolean } From 6eada92966c189160b28d9e95dc8bb9aec5aa016 Mon Sep 17 00:00:00 2001 From: Brian Gough Date: Thu, 8 May 2025 10:12:34 +0100 Subject: [PATCH 047/194] Merge pull request #25406 from overleaf/em-content-hash-validation-resync Resync project when content hash validation fails GitOrigin-RevId: ea9b5a78f89c55276fd67835bc262717bc778e92 --- .../api/controllers/project_import.js | 4 +- .../history-v1/storage/lib/persist_changes.js | 13 ++-- .../acceptance/js/api/project_import.test.js | 2 +- .../js/storage/persist_changes.test.js | 15 +++- .../app/js/HistoryStoreManager.js | 4 +- .../app/js/UpdatesProcessor.js | 74 +++++++++++++++---- .../UpdatesManager/UpdatesProcessorTests.js | 4 +- 7 files changed, 84 insertions(+), 32 deletions(-) diff --git a/services/history-v1/api/controllers/project_import.js b/services/history-v1/api/controllers/project_import.js index 5dec84d843..9d47fe06a9 100644 --- a/services/history-v1/api/controllers/project_import.js +++ b/services/history-v1/api/controllers/project_import.js @@ -130,7 +130,9 @@ async function importChanges(req, res, next) { } if (returnSnapshot === 'none') { - res.status(HTTPStatus.CREATED).json({}) + res.status(HTTPStatus.CREATED).json({ + resyncNeeded: result.resyncNeeded, + }) } else { const rawSnapshot = await buildResultSnapshot(result && result.currentChunk) res.status(HTTPStatus.CREATED).json(rawSnapshot) diff --git a/services/history-v1/storage/lib/persist_changes.js b/services/history-v1/storage/lib/persist_changes.js index b17d7d8b37..47798531b2 100644 --- a/services/history-v1/storage/lib/persist_changes.js +++ b/services/history-v1/storage/lib/persist_changes.js @@ -82,6 +82,7 @@ async function persistChanges(projectId, allChanges, limits, clientEndVersion) { let originalEndVersion let changesToPersist + let resyncNeeded = false limits = limits || {} _.defaults(limits, { @@ -166,12 +167,11 @@ async function persistChanges(projectId, allChanges, limits, clientEndVersion) { const actualHash = content != null ? getContentHash(content) : null logger.debug({ expectedHash, actualHash }, 'validating content hash') if (actualHash !== expectedHash) { - throw new InvalidChangeError('content hash mismatch', { - projectId, - path, - expectedHash, - actualHash, - }) + logger.warn( + { projectId, path, expectedHash, actualHash }, + 'content hash mismatch' + ) + resyncNeeded = true } // Remove the content hash from the change before storing it in the chunk. @@ -300,6 +300,7 @@ async function persistChanges(projectId, allChanges, limits, clientEndVersion) { numberOfChangesPersisted: numberOfChangesToPersist, originalEndVersion, currentChunk, + resyncNeeded, } } else { return null diff --git a/services/history-v1/test/acceptance/js/api/project_import.test.js b/services/history-v1/test/acceptance/js/api/project_import.test.js index 216fb527fa..fb173238f8 100644 --- a/services/history-v1/test/acceptance/js/api/project_import.test.js +++ b/services/history-v1/test/acceptance/js/api/project_import.test.js @@ -52,6 +52,6 @@ describe('project import', function () { }) expect(importResponse.status).to.equal(HTTPStatus.CREATED) - expect(importResponse.obj).to.deep.equal({}) + expect(importResponse.obj).to.deep.equal({ resyncNeeded: false }) }) }) diff --git a/services/history-v1/test/acceptance/js/storage/persist_changes.test.js b/services/history-v1/test/acceptance/js/storage/persist_changes.test.js index aa56dc8c2a..0bb8836cc1 100644 --- a/services/history-v1/test/acceptance/js/storage/persist_changes.test.js +++ b/services/history-v1/test/acceptance/js/storage/persist_changes.test.js @@ -58,6 +58,7 @@ describe('persistChanges', function () { numberOfChangesPersisted: 1, originalEndVersion: 0, currentChunk, + resyncNeeded: false, }) const chunk = await chunkStore.loadLatest(projectId) @@ -106,6 +107,7 @@ describe('persistChanges', function () { numberOfChangesPersisted: 2, originalEndVersion: 0, currentChunk, + resyncNeeded: false, }) const chunk = await chunkStore.loadLatest(projectId) @@ -147,6 +149,7 @@ describe('persistChanges', function () { numberOfChangesPersisted: 2, originalEndVersion: 0, currentChunk, + resyncNeeded: false, }) const chunk = await chunkStore.loadLatest(projectId) @@ -213,7 +216,7 @@ describe('persistChanges', function () { expect(result.numberOfChangesPersisted).to.equal(1) }) - it('rejects a change with an invalid hash', async function () { + it('turns on the resyncNeeded flag if content hash validation fails', async function () { const limitsToPersistImmediately = { minChangeTimestamp: farFuture, maxChangeTimestamp: farFuture, @@ -235,9 +238,13 @@ describe('persistChanges', function () { ) const changes = [change] - await expect( - persistChanges(projectId, changes, limitsToPersistImmediately, 0) - ).to.be.rejectedWith(storage.InvalidChangeError) + const result = await persistChanges( + projectId, + changes, + limitsToPersistImmediately, + 0 + ) + expect(result.resyncNeeded).to.be.true }) }) }) diff --git a/services/project-history/app/js/HistoryStoreManager.js b/services/project-history/app/js/HistoryStoreManager.js index fe9c9e3d2d..bb41dfb3c0 100644 --- a/services/project-history/app/js/HistoryStoreManager.js +++ b/services/project-history/app/js/HistoryStoreManager.js @@ -249,7 +249,7 @@ export function sendChanges( method: 'POST', json: changes, }, - error => { + (error, response) => { if (error) { OError.tag(error, 'failed to send changes to v1', { projectId, @@ -261,7 +261,7 @@ export function sendChanges( }) return callback(error) } - callback() + callback(null, { resyncNeeded: response?.resyncNeeded ?? false }) } ) } diff --git a/services/project-history/app/js/UpdatesProcessor.js b/services/project-history/app/js/UpdatesProcessor.js index b52fac7af6..a76241d7ca 100644 --- a/services/project-history/app/js/UpdatesProcessor.js +++ b/services/project-history/app/js/UpdatesProcessor.js @@ -85,7 +85,7 @@ export function startResyncAndProcessUpdatesUnderLock( }) }) }, - (flushError, queueSize) => { + (flushError, { queueSize } = {}) => { if (flushError) { OError.tag(flushError) ErrorRecorder.record(projectId, queueSize, flushError, recordError => { @@ -132,7 +132,7 @@ export function processUpdatesForProject(projectId, callback) { releaseLock ) }, - (flushError, queueSize) => { + (flushError, { queueSize, resyncNeeded } = {}) => { if (flushError) { OError.tag(flushError) ErrorRecorder.record( @@ -167,7 +167,15 @@ export function processUpdatesForProject(projectId, callback) { 'failed to clear error' ) } - callback() + if (resyncNeeded) { + logger.warn( + { projectId }, + 'Resyncing project as requested by full project history' + ) + resyncProject(projectId, callback) + } else { + callback() + } }) } if (queueSize > 0) { @@ -198,7 +206,7 @@ export function resyncProject(projectId, callback) { releaseLock ) }, - (flushError, queueSize) => { + (flushError, { queueSize } = {}) => { if (flushError) { ErrorRecorder.record( projectId, @@ -247,7 +255,7 @@ export function processUpdatesForProjectUsingBisect( releaseLock ) }, - (flushError, queueSize) => { + (flushError, { queueSize } = {}) => { if (amountToProcess === 0 || queueSize === 0) { // no further processing possible if (flushError != null) { @@ -298,7 +306,7 @@ export function processSingleUpdateForProject(projectId, callback) { ) => { _countAndProcessUpdates(projectId, extendLock, 1, releaseLock) }, - (flushError, queueSize) => { + (flushError, { queueSize } = {}) => { // no need to clear the flush marker when single stepping // it will be cleared up on the next background flush if // the queue is empty @@ -339,18 +347,34 @@ _mocks._countAndProcessUpdates = ( } if (queueSize > 0) { logger.debug({ projectId, queueSize }, 'processing uncompressed updates') + + let resyncNeeded = false RedisManager.getUpdatesInBatches( projectId, batchSize, (updates, cb) => { - _processUpdatesBatch(projectId, updates, extendLock, cb) + _processUpdatesBatch( + projectId, + updates, + extendLock, + (err, flushResponse) => { + if (err) { + return cb(err) + } + + if (flushResponse.resyncNeeded) { + resyncNeeded = true + } + cb() + } + ) }, error => { // Unconventional callback signature. The caller needs the queue size // even when an error is thrown in order to record the queue size in // the projectHistoryFailures collection. We'll have to find another // way to achieve this when we promisify. - callback(error, queueSize) + callback(error, { queueSize, resyncNeeded }) } ) } else { @@ -376,15 +400,21 @@ function _processUpdatesBatch(projectId, updates, extendLock, callback) { { projectId }, 'discarding updates as project does not use history' ) - return callback() + return callback(null, {}) } - _processUpdates(projectId, historyId, updates, extendLock, error => { - if (error != null) { - return callback(OError.tag(error)) + _processUpdates( + projectId, + historyId, + updates, + extendLock, + (error, flushResponse) => { + if (error != null) { + return callback(OError.tag(error)) + } + callback(null, flushResponse) } - callback() - }) + ) }) } @@ -536,6 +566,8 @@ export function _processUpdates( if (error != null) { return callback(error) } + + let resyncNeeded = false async.waterfall( [ cb => { @@ -646,7 +678,13 @@ export function _processUpdates( projectHistoryId, changes, baseVersion, - cb + (err, response) => { + if (err) { + return cb(err) + } + resyncNeeded = response.resyncNeeded + cb() + } ) }) }, @@ -657,7 +695,11 @@ export function _processUpdates( ], error => { profile.end() - callback(error) + if (error) { + callback(error) + } else { + callback(null, { resyncNeeded }) + } } ) } diff --git a/services/project-history/test/unit/js/UpdatesManager/UpdatesProcessorTests.js b/services/project-history/test/unit/js/UpdatesManager/UpdatesProcessorTests.js index 137169bfcf..6f148e5a8d 100644 --- a/services/project-history/test/unit/js/UpdatesManager/UpdatesProcessorTests.js +++ b/services/project-history/test/unit/js/UpdatesManager/UpdatesProcessorTests.js @@ -13,7 +13,7 @@ describe('UpdatesProcessor', function () { } this.HistoryStoreManager = { getMostRecentVersion: sinon.stub(), - sendChanges: sinon.stub().yields(), + sendChanges: sinon.stub().yields(null, {}), } this.LockManager = { runWithLock: sinon.spy((key, runner, callback) => @@ -109,7 +109,7 @@ describe('UpdatesProcessor', function () { this.queueSize = 445 this.UpdatesProcessor._mocks._countAndProcessUpdates = sinon .stub() - .callsArgWith(3, this.error, this.queueSize) + .callsArgWith(3, this.error, { queueSize: this.queueSize }) }) describe('when there is no existing error', function () { From 9cb2b48c1e0f21df46c1307e6a67cf3558169966 Mon Sep 17 00:00:00 2001 From: Mathias Jakobsen Date: Thu, 8 May 2025 12:50:33 +0100 Subject: [PATCH 048/194] Merge pull request #25373 from overleaf/mj-reorder-tpr [web] Reorder third party reference managers GitOrigin-RevId: 283d50674fdcc60b7a32e4e7846c6638c591937a --- services/web/locales/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/web/locales/en.json b/services/web/locales/en.json index f752e1aae3..df4478b114 100644 --- a/services/web/locales/en.json +++ b/services/web/locales/en.json @@ -839,7 +839,7 @@ "git_bridge_modal_review_access": "<0>You have review access to this project. This means you can pull from __appName__ but you can’t push any changes you make back to this project.", "git_bridge_modal_see_once": "You’ll only see this token once. To delete it or generate a new one, visit Account Settings. For detailed instructions and troubleshooting, read our <0>help page.", "git_bridge_modal_use_previous_token": "If you’re prompted for a password, you can use a previously generated Git authentication token. Or you can generate a new one in Account Settings. For more support, read our <0>help page.", - "git_gitHub_dropbox_mendeley_papers_and_zotero_integrations": "Git, GitHub, Dropbox, Mendeley, Papers, and Zotero integrations", + "git_gitHub_dropbox_mendeley_papers_and_zotero_integrations": "Git, GitHub, Dropbox, Papers, Zotero, and Mendeley integrations", "git_integration": "Git Integration", "git_integration_info": "With Git integration, you can clone your Overleaf projects with Git. For full instructions on how to do this, read <0>our help page.", "github": "GitHub", From 1b5d31941ecac467b5e5538ee2f5268e81967ddc Mon Sep 17 00:00:00 2001 From: Mathias Jakobsen Date: Thu, 8 May 2025 12:51:25 +0100 Subject: [PATCH 049/194] Merge pull request #25383 from overleaf/mj-ide-long-title [web] Editor redesign: Allow project name to shrink GitOrigin-RevId: 4d25291437fae9672f0d0d4d20bde269f771020a --- .../components/toolbar/project-title.tsx | 6 ++--- .../pages/editor/toolbar-redesign.scss | 22 +++++++++++++++++++ 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/services/web/frontend/js/features/ide-redesign/components/toolbar/project-title.tsx b/services/web/frontend/js/features/ide-redesign/components/toolbar/project-title.tsx index 648fcb0b8f..68860da4ea 100644 --- a/services/web/frontend/js/features/ide-redesign/components/toolbar/project-title.tsx +++ b/services/web/frontend/js/features/ide-redesign/components/toolbar/project-title.tsx @@ -52,12 +52,12 @@ export const ToolbarProjectTitle = () => { } return ( - + - {name} + {name} Date: Thu, 8 May 2025 12:52:28 +0100 Subject: [PATCH 050/194] Merge pull request #25411 from overleaf/dp-clone-project-translation Add missing translation for clone project modal name placeholder GitOrigin-RevId: 7ee99d811f31eaa0441d2e9b9f579f29ff1cb368 --- services/web/frontend/extracted-translations.json | 1 + .../components/clone-project-modal-content.jsx | 2 +- services/web/locales/en.json | 1 + .../editor-left-menu/components/actions-copy-project.test.jsx | 4 ++-- 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json index eeb661d392..608e758298 100644 --- a/services/web/frontend/extracted-translations.json +++ b/services/web/frontend/extracted-translations.json @@ -1063,6 +1063,7 @@ "new_overleaf_editor": "", "new_password": "", "new_project": "", + "new_project_name": "", "new_subscription_will_be_billed_immediately": "", "new_tag": "", "new_tag_name": "", diff --git a/services/web/frontend/js/features/clone-project-modal/components/clone-project-modal-content.jsx b/services/web/frontend/js/features/clone-project-modal/components/clone-project-modal-content.jsx index 84d8151f0f..404af82ca4 100644 --- a/services/web/frontend/js/features/clone-project-modal/components/clone-project-modal-content.jsx +++ b/services/web/frontend/js/features/clone-project-modal/components/clone-project-modal-content.jsx @@ -91,7 +91,7 @@ export default function CloneProjectModalContent({ {t('new_name')} setClonedProjectName(event.target.value)} diff --git a/services/web/locales/en.json b/services/web/locales/en.json index df4478b114..c2401427b7 100644 --- a/services/web/locales/en.json +++ b/services/web/locales/en.json @@ -1390,6 +1390,7 @@ "new_overleaf_editor": "New Overleaf editor", "new_password": "New password", "new_project": "New Project", + "new_project_name": "New project name", "new_snippet_project": "Untitled", "new_subscription_will_be_billed_immediately": "Your new subscription will be billed immediately to your current payment method.", "new_tag": "New Tag", diff --git a/services/web/test/frontend/features/editor-left-menu/components/actions-copy-project.test.jsx b/services/web/test/frontend/features/editor-left-menu/components/actions-copy-project.test.jsx index aea2592bad..066858f1ba 100644 --- a/services/web/test/frontend/features/editor-left-menu/components/actions-copy-project.test.jsx +++ b/services/web/test/frontend/features/editor-left-menu/components/actions-copy-project.test.jsx @@ -23,7 +23,7 @@ describe('', function () { fireEvent.click(screen.getByRole('button', { name: 'Copy Project' })) - screen.getByPlaceholderText('New Project Name') + screen.getByPlaceholderText('New project name') }) it('loads the project page when submitted', async function () { @@ -38,7 +38,7 @@ describe('', function () { fireEvent.click(screen.getByRole('button', { name: 'Copy Project' })) - const input = screen.getByPlaceholderText('New Project Name') + const input = screen.getByPlaceholderText('New project name') fireEvent.change(input, { target: { value: 'New Project' } }) const button = screen.getByRole('button', { name: 'Copy' }) From 3242376d195628e0e811d4122a05b0a7bbcf4b78 Mon Sep 17 00:00:00 2001 From: Jakob Ackermann Date: Thu, 8 May 2025 14:06:38 +0200 Subject: [PATCH 051/194] [server-ce] add notice for CE users on disabling sandboxed compiles (#25425) Co-authored-by: Mathew Evans GitOrigin-RevId: 977625975af6ac68a33356dc4c39af98791e8708 --- docker-compose.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docker-compose.yml b/docker-compose.yml index 2d43c2db18..e257716789 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -73,7 +73,11 @@ services: ## Server Pro ## ################ - ## Sandboxed Compiles: https://github.com/overleaf/overleaf/wiki/Server-Pro:-Sandboxed-Compiles + ## The Community Edition is intended for use in environments where all users are trusted and is not appropriate for + ## scenarios where isolation of users is required. Sandboxed Compiles are not available in the Community Edition, + ## so the following environment variables must be commented out to avoid compile issues. + ## + ## Sandboxed Compiles: https://docs.overleaf.com/on-premises/configuration/overleaf-toolkit/server-pro-only-configuration/sandboxed-compiles SANDBOXED_COMPILES: 'true' ### Bind-mount source for /var/lib/overleaf/data/compiles inside the container. SANDBOXED_COMPILES_HOST_DIR_COMPILES: '/home/user/sharelatex_data/data/compiles' From 9cf284aefa2e91a4d7fafc3792beb813f696cb59 Mon Sep 17 00:00:00 2001 From: Brian Gough Date: Thu, 8 May 2025 15:32:36 +0100 Subject: [PATCH 052/194] Merge pull request #25414 from overleaf/bg-history-only-log-content-mismatch-once-per-request log warning for content hash mismatch only on first occurrence GitOrigin-RevId: ff09f8c262461488bd564ea0644d414bb32ff17e --- services/history-v1/storage/lib/persist_changes.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/services/history-v1/storage/lib/persist_changes.js b/services/history-v1/storage/lib/persist_changes.js index 47798531b2..07ac100790 100644 --- a/services/history-v1/storage/lib/persist_changes.js +++ b/services/history-v1/storage/lib/persist_changes.js @@ -167,10 +167,13 @@ async function persistChanges(projectId, allChanges, limits, clientEndVersion) { const actualHash = content != null ? getContentHash(content) : null logger.debug({ expectedHash, actualHash }, 'validating content hash') if (actualHash !== expectedHash) { - logger.warn( - { projectId, path, expectedHash, actualHash }, - 'content hash mismatch' - ) + // only log a warning on the first mismatch in each persistChanges call + if (!resyncNeeded) { + logger.warn( + { projectId, path, expectedHash, actualHash }, + 'content hash mismatch' + ) + } resyncNeeded = true } From dc73a18ca407bf1ad9e373b9bd39d6093f9f5e74 Mon Sep 17 00:00:00 2001 From: Brian Gough Date: Thu, 8 May 2025 15:38:33 +0100 Subject: [PATCH 053/194] Merge pull request #25432 from overleaf/em-redis-buffer-strict-apply Use strict validation for the Redis buffer GitOrigin-RevId: 43e73af5deabbf3de9f5eed14f062acc5fa35e36 --- services/history-v1/storage/lib/persist_changes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/history-v1/storage/lib/persist_changes.js b/services/history-v1/storage/lib/persist_changes.js index 07ac100790..20080d20a6 100644 --- a/services/history-v1/storage/lib/persist_changes.js +++ b/services/history-v1/storage/lib/persist_changes.js @@ -206,7 +206,7 @@ async function persistChanges(projectId, allChanges, limits, clientEndVersion) { // doesn't really need a blobStore, but its signature still requires it. const blobStore = new BlobStore(projectId) await hollowSnapshot.loadFiles('hollow', blobStore) - hollowSnapshot.applyAll(changesToPersist) + hollowSnapshot.applyAll(changesToPersist, { strict: true }) const baseVersion = currentChunk.getEndVersion() await redisBackend.queueChanges( projectId, From 8d4f2584943b38a967aaacb306cd7ce0226a393c Mon Sep 17 00:00:00 2001 From: Jakob Ackermann Date: Thu, 8 May 2025 17:01:48 +0200 Subject: [PATCH 054/194] [web] retry fetching initial compile from cache response (#25436) * [web] move building of compile from cache response into manager * [web] retry fetching initial compile from cache response GitOrigin-RevId: b4dc89f1b91d99e869c0c7789881dc72d8a5761f --- .../Features/Compile/ClsiCacheController.js | 68 +++---------- .../src/Features/Compile/ClsiCacheManager.js | 96 ++++++++++++++++++- .../web/app/src/Features/Errors/Errors.js | 3 + 3 files changed, 109 insertions(+), 58 deletions(-) diff --git a/services/web/app/src/Features/Compile/ClsiCacheController.js b/services/web/app/src/Features/Compile/ClsiCacheController.js index 42f037985d..d76f0a02bd 100644 --- a/services/web/app/src/Features/Compile/ClsiCacheController.js +++ b/services/web/app/src/Features/Compile/ClsiCacheController.js @@ -1,8 +1,7 @@ -const { NotFoundError } = require('../Errors/Errors') +const { NotFoundError, ResourceGoneError } = require('../Errors/Errors') const { fetchStreamWithResponse, RequestFailedError, - fetchJson, } = require('@overleaf/fetch-utils') const Path = require('path') const { pipeline } = require('stream/promises') @@ -110,61 +109,14 @@ async function getLatestBuildFromCache(req, res) { const userId = CompileController._getUserIdForCompile(req) try { const { - internal: { location: metaLocation }, - external: { isUpToDate, allFiles, zone, shard }, - } = await ClsiCacheManager.getLatestBuildFromCache( - projectId, - userId, - 'output.overleaf.json' - ) + zone, + outputFiles, + compileGroup, + clsiServerId, + clsiCacheShard, + options, + } = await ClsiCacheManager.getLatestCompileResult(projectId, userId) - if (!isUpToDate) return res.sendStatus(410) - - const meta = await fetchJson(metaLocation, { - signal: AbortSignal.timeout(5 * 1000), - }) - - const [, editorId, buildId] = metaLocation.match( - /\/build\/([a-f0-9-]+?)-([a-f0-9]+-[a-f0-9]+)\// - ) - - let baseURL = `/project/${projectId}` - if (userId) { - baseURL += `/user/${userId}` - } - - const { ranges, contentId, clsiServerId, compileGroup, size, options } = - meta - - const outputFiles = allFiles - .filter( - path => path !== 'output.overleaf.json' && path !== 'output.tar.gz' - ) - .map(path => { - const f = { - url: `${baseURL}/build/${editorId}-${buildId}/output/${path}`, - downloadURL: `/download/project/${projectId}/build/${editorId}-${buildId}/output/cached/${path}`, - build: buildId, - path, - type: path.split('.').pop(), - } - if (path === 'output.pdf') { - Object.assign(f, { - size, - editorId, - }) - if (clsiServerId !== shard) { - // Enable PDF caching and attempt to download from VM first. - // (clsi VMs do not have the editorId in the path on disk, omit it). - Object.assign(f, { - url: `${baseURL}/build/${buildId}/output/output.pdf`, - ranges, - contentId, - }) - } - } - return f - }) let { pdfCachingMinChunkSize, pdfDownloadDomain } = await CompileController._getSplitTestOptions(req, res) pdfDownloadDomain += `/zone/${zone}` @@ -174,7 +126,7 @@ async function getLatestBuildFromCache(req, res) { outputFiles, compileGroup, clsiServerId, - clsiCacheShard: shard, + clsiCacheShard, pdfDownloadDomain, pdfCachingMinChunkSize, options, @@ -182,6 +134,8 @@ async function getLatestBuildFromCache(req, res) { } catch (err) { if (err instanceof NotFoundError) { res.sendStatus(404) + } else if (err instanceof ResourceGoneError) { + res.sendStatus(410) } else { throw err } diff --git a/services/web/app/src/Features/Compile/ClsiCacheManager.js b/services/web/app/src/Features/Compile/ClsiCacheManager.js index cf0665af56..45970f4619 100644 --- a/services/web/app/src/Features/Compile/ClsiCacheManager.js +++ b/services/web/app/src/Features/Compile/ClsiCacheManager.js @@ -1,11 +1,12 @@ const _ = require('lodash') -const { NotFoundError } = require('../Errors/Errors') +const { NotFoundError, ResourceGoneError } = require('../Errors/Errors') const ClsiCacheHandler = require('./ClsiCacheHandler') const DocumentUpdaterHandler = require('../DocumentUpdater/DocumentUpdaterHandler') const ProjectGetter = require('../Project/ProjectGetter') const SplitTestHandler = require('../SplitTests/SplitTestHandler') const UserGetter = require('../User/UserGetter') const Settings = require('@overleaf/settings') +const { fetchJson, RequestFailedError } = require('@overleaf/fetch-utils') /** * Get the most recent build and metadata @@ -50,6 +51,98 @@ async function getLatestBuildFromCache(projectId, userId, filename, signal) { } } +class MetaFileExpiredError extends NotFoundError {} + +async function getLatestCompileResult(projectId, userId) { + const signal = AbortSignal.timeout(15_000) + for (let attempt = 0; attempt < 3; attempt++) { + try { + return await tryGetLatestCompileResult(projectId, userId, signal) + } catch (err) { + if (err instanceof MetaFileExpiredError) { + continue + } + throw err + } + } + throw new NotFoundError() +} + +async function tryGetLatestCompileResult(projectId, userId, signal) { + const { + internal: { location: metaLocation }, + external: { isUpToDate, allFiles, zone, shard: clsiCacheShard }, + } = await getLatestBuildFromCache( + projectId, + userId, + 'output.overleaf.json', + signal + ) + if (!isUpToDate) throw new ResourceGoneError() + + let meta + try { + meta = await fetchJson(metaLocation, { + signal: AbortSignal.timeout(5 * 1000), + }) + } catch (err) { + if (err instanceof RequestFailedError && err.response.status === 404) { + throw new MetaFileExpiredError( + 'build expired between listing and reading' + ) + } + throw err + } + + const [, editorId, buildId] = metaLocation.match( + /\/build\/([a-f0-9-]+?)-([a-f0-9]+-[a-f0-9]+)\// + ) + const { ranges, contentId, clsiServerId, compileGroup, size, options } = meta + + let baseURL = `/project/${projectId}` + if (userId) { + baseURL += `/user/${userId}` + } + + const outputFiles = allFiles + .filter(path => path !== 'output.overleaf.json' && path !== 'output.tar.gz') + .map(path => { + const f = { + url: `${baseURL}/build/${editorId}-${buildId}/output/${path}`, + downloadURL: `/download/project/${projectId}/build/${editorId}-${buildId}/output/cached/${path}`, + build: buildId, + path, + type: path.split('.').pop(), + } + if (path === 'output.pdf') { + Object.assign(f, { + size, + editorId, + }) + if (clsiServerId !== clsiCacheShard) { + // Enable PDF caching and attempt to download from VM first. + // (clsi VMs do not have the editorId in the path on disk, omit it). + Object.assign(f, { + url: `${baseURL}/build/${buildId}/output/output.pdf`, + ranges, + contentId, + }) + } + } + return f + }) + + return { + allFiles, + zone, + outputFiles, + compileGroup, + clsiServerId, + clsiCacheShard, + options, + } +} + /** * Collect metadata and prepare the clsi-cache for the given project. * @@ -109,5 +202,6 @@ async function prepareClsiCache( module.exports = { getLatestBuildFromCache, + getLatestCompileResult, prepareClsiCache, } diff --git a/services/web/app/src/Features/Errors/Errors.js b/services/web/app/src/Features/Errors/Errors.js index 618c1c234c..4b1b7dd064 100644 --- a/services/web/app/src/Features/Errors/Errors.js +++ b/services/web/app/src/Features/Errors/Errors.js @@ -41,6 +41,8 @@ class ServiceNotConfiguredError extends BackwardCompatibleError {} class TooManyRequestsError extends BackwardCompatibleError {} +class ResourceGoneError extends BackwardCompatibleError {} + class DuplicateNameError extends OError {} class InvalidNameError extends BackwardCompatibleError {} @@ -319,6 +321,7 @@ module.exports = { ForbiddenError, ServiceNotConfiguredError, TooManyRequestsError, + ResourceGoneError, DuplicateNameError, InvalidNameError, UnsupportedFileTypeError, From d489e35782420880708a4918c216e767c897b6fe Mon Sep 17 00:00:00 2001 From: Jakob Ackermann Date: Thu, 8 May 2025 17:08:38 +0200 Subject: [PATCH 055/194] [web] emit event when synctex mapping was downloaded from clsi-cache (#25424) * [clsi] tell frontend when synctex mapping was downloaded from clsi-cache * [web] emit event when synctex mapping was downloaded from clsi-cache GitOrigin-RevId: 1f6b7e0faaa7dd76449aad566802da971a4cf9ed --- services/clsi/app/js/CompileController.js | 6 +- services/clsi/app/js/CompileManager.js | 60 ++++++++--- .../acceptance/js/AllowedImageNamesTests.js | 2 + .../clsi/test/acceptance/js/SynctexTests.js | 2 + .../test/unit/js/CompileControllerTests.js | 6 +- .../clsi/test/unit/js/CompileManagerTests.js | 102 ++++++++++++++++-- .../features/pdf-preview/hooks/use-synctex.ts | 13 +++ 7 files changed, 160 insertions(+), 31 deletions(-) diff --git a/services/clsi/app/js/CompileController.js b/services/clsi/app/js/CompileController.js index 138801890a..e102b19dad 100644 --- a/services/clsi/app/js/CompileController.js +++ b/services/clsi/app/js/CompileController.js @@ -204,12 +204,13 @@ function syncFromCode(req, res, next) { line, column, { imageName, editorId, buildId, compileFromClsiCache }, - function (error, pdfPositions) { + function (error, pdfPositions, downloadedFromCache) { if (error) { return next(error) } res.json({ pdf: pdfPositions, + downloadedFromCache, }) } ) @@ -229,12 +230,13 @@ function syncFromPdf(req, res, next) { h, v, { imageName, editorId, buildId, compileFromClsiCache }, - function (error, codePositions) { + function (error, codePositions, downloadedFromCache) { if (error) { return next(error) } res.json({ code: codePositions, + downloadedFromCache, }) } ) diff --git a/services/clsi/app/js/CompileManager.js b/services/clsi/app/js/CompileManager.js index b65fb3cd02..1b66927412 100644 --- a/services/clsi/app/js/CompileManager.js +++ b/services/clsi/app/js/CompileManager.js @@ -23,6 +23,7 @@ const { downloadLatestCompileCache, downloadOutputDotSynctexFromCompileCache, } = require('./CLSICacheHandler') +const { callbackifyMultiResult } = require('@overleaf/promise-utils') const COMPILE_TIME_BUCKETS = [ // NOTE: These buckets are locked in per metric name. @@ -447,12 +448,20 @@ async function syncFromCode(projectId, userId, filename, line, column, opts) { '-o', outputFilePath, ] - const stdout = await _runSynctex(projectId, userId, command, opts) + const { stdout, downloadedFromCache } = await _runSynctex( + projectId, + userId, + command, + opts + ) logger.debug( { projectId, userId, filename, line, column, command, stdout }, 'synctex code output' ) - return SynctexOutputParser.parseViewOutput(stdout) + return { + codePositions: SynctexOutputParser.parseViewOutput(stdout), + downloadedFromCache, + } } async function syncFromPdf(projectId, userId, page, h, v, opts) { @@ -465,9 +474,17 @@ async function syncFromPdf(projectId, userId, page, h, v, opts) { '-o', `${page}:${h}:${v}:${outputFilePath}`, ] - const stdout = await _runSynctex(projectId, userId, command, opts) + const { stdout, downloadedFromCache } = await _runSynctex( + projectId, + userId, + command, + opts + ) logger.debug({ projectId, userId, page, h, v, stdout }, 'synctex pdf output') - return SynctexOutputParser.parseEditOutput(stdout, baseDir) + return { + pdfPositions: SynctexOutputParser.parseEditOutput(stdout, baseDir), + downloadedFromCache, + } } async function _checkFileExists(dir, filename) { @@ -522,9 +539,10 @@ async function _runSynctex(projectId, userId, command, opts) { return await OutputCacheManager.promises.queueDirOperation( outputDir, /** - * @return {Promise} + * @return {Promise<{stdout: string, downloadedFromCache: boolean}>} */ async () => { + let downloadedFromCache = false try { await _checkFileExists(directory, 'output.synctex.gz') } catch (err) { @@ -535,13 +553,14 @@ async function _runSynctex(projectId, userId, command, opts) { buildId ) { try { - await downloadOutputDotSynctexFromCompileCache( - projectId, - userId, - editorId, - buildId, - directory - ) + downloadedFromCache = + await downloadOutputDotSynctexFromCompileCache( + projectId, + userId, + editorId, + buildId, + directory + ) } catch (err) { logger.warn( { err, projectId, userId, editorId, buildId }, @@ -554,7 +573,7 @@ async function _runSynctex(projectId, userId, command, opts) { } } try { - const output = await CommandRunner.promises.run( + const { stdout } = await CommandRunner.promises.run( compileName, command, directory, @@ -563,7 +582,10 @@ async function _runSynctex(projectId, userId, command, opts) { {}, compileGroup ) - return output.stdout + return { + stdout, + downloadedFromCache, + } } catch (error) { throw OError.tag(error, 'error running synctex', { command, @@ -686,8 +708,14 @@ module.exports = { stopCompile: callbackify(stopCompile), clearProject: callbackify(clearProject), clearExpiredProjects: callbackify(clearExpiredProjects), - syncFromCode: callbackify(syncFromCode), - syncFromPdf: callbackify(syncFromPdf), + syncFromCode: callbackifyMultiResult(syncFromCode, [ + 'codePositions', + 'downloadedFromCache', + ]), + syncFromPdf: callbackifyMultiResult(syncFromPdf, [ + 'pdfPositions', + 'downloadedFromCache', + ]), wordcount: callbackify(wordcount), promises: { doCompileWithLock, diff --git a/services/clsi/test/acceptance/js/AllowedImageNamesTests.js b/services/clsi/test/acceptance/js/AllowedImageNamesTests.js index 897f5d9c85..9cd7a65930 100644 --- a/services/clsi/test/acceptance/js/AllowedImageNamesTests.js +++ b/services/clsi/test/acceptance/js/AllowedImageNamesTests.js @@ -109,6 +109,7 @@ Hello world width: 343.71106, }, ], + downloadedFromCache: false, }) done() } @@ -146,6 +147,7 @@ Hello world expect(error).to.not.exist expect(result).to.deep.equal({ code: [{ file: 'main.tex', line: 3, column: -1 }], + downloadedFromCache: false, }) done() } diff --git a/services/clsi/test/acceptance/js/SynctexTests.js b/services/clsi/test/acceptance/js/SynctexTests.js index 5ba5bb5b5f..049f260259 100644 --- a/services/clsi/test/acceptance/js/SynctexTests.js +++ b/services/clsi/test/acceptance/js/SynctexTests.js @@ -67,6 +67,7 @@ Hello world width: 343.71106, }, ], + downloadedFromCache: false, }) return done() } @@ -87,6 +88,7 @@ Hello world } expect(codePositions).to.deep.equal({ code: [{ file: 'main.tex', line: 3, column: -1 }], + downloadedFromCache: false, }) return done() } diff --git a/services/clsi/test/unit/js/CompileControllerTests.js b/services/clsi/test/unit/js/CompileControllerTests.js index 506b5f02dd..2ac8d9c2d7 100644 --- a/services/clsi/test/unit/js/CompileControllerTests.js +++ b/services/clsi/test/unit/js/CompileControllerTests.js @@ -410,7 +410,7 @@ describe('CompileController', function () { this.CompileManager.syncFromCode = sinon .stub() - .yields(null, (this.pdfPositions = ['mock-positions'])) + .yields(null, (this.pdfPositions = ['mock-positions']), true) this.CompileController.syncFromCode(this.req, this.res, this.next) }) @@ -430,6 +430,7 @@ describe('CompileController', function () { this.res.json .calledWith({ pdf: this.pdfPositions, + downloadedFromCache: true, }) .should.equal(true) }) @@ -451,7 +452,7 @@ describe('CompileController', function () { this.CompileManager.syncFromPdf = sinon .stub() - .yields(null, (this.codePositions = ['mock-positions'])) + .yields(null, (this.codePositions = ['mock-positions']), true) this.CompileController.syncFromPdf(this.req, this.res, this.next) }) @@ -465,6 +466,7 @@ describe('CompileController', function () { this.res.json .calledWith({ code: this.codePositions, + downloadedFromCache: true, }) .should.equal(true) }) diff --git a/services/clsi/test/unit/js/CompileManagerTests.js b/services/clsi/test/unit/js/CompileManagerTests.js index 33a43ae029..30ef538ac3 100644 --- a/services/clsi/test/unit/js/CompileManagerTests.js +++ b/services/clsi/test/unit/js/CompileManagerTests.js @@ -35,7 +35,7 @@ describe('CompileManager', function () { build: 1234, }, ] - this.buildId = 'build-id-123' + this.buildId = '00000000000-0000000000000000' this.commandOutput = 'Dummy output' this.compileBaseDir = '/compile/dir' this.outputBaseDir = '/output/dir' @@ -61,6 +61,8 @@ describe('CompileManager', function () { }, } this.OutputCacheManager = { + BUILD_REGEX: /^[0-9a-f]+-[0-9a-f]+$/, + CACHE_SUBDIR: 'generated-files', promises: { queueDirOperation: sinon.stub().callsArg(1), saveOutputFiles: sinon @@ -88,9 +90,10 @@ describe('CompileManager', function () { execFile: sinon.stub().yields(), } this.CommandRunner = { + canRunSyncTeXInOutputDir: sinon.stub().returns(false), promises: { run: sinon.stub().callsFake((_1, _2, _3, _4, _5, _6, compileGroup) => { - if (compileGroup === 'synctex') { + if (compileGroup === 'synctex' || compileGroup === 'synctex-output') { return Promise.resolve({ stdout: this.commandOutput }) } else { return Promise.resolve({ @@ -141,6 +144,12 @@ describe('CompileManager', function () { .withArgs(Path.join(this.compileDir, 'output.synctex.gz')) .resolves(this.fileStats) + this.CLSICacheHandler = { + notifyCLSICacheAboutBuild: sinon.stub(), + downloadLatestCompileCache: sinon.stub().resolves(), + downloadOutputDotSynctexFromCompileCache: sinon.stub().resolves(), + } + this.CompileManager = SandboxedModule.require(MODULE_PATH, { requires: { './LatexRunner': this.LatexRunner, @@ -161,11 +170,7 @@ describe('CompileManager', function () { './LockManager': this.LockManager, './SynctexOutputParser': this.SynctexOutputParser, 'fs/promises': this.fsPromises, - './CLSICacheHandler': { - notifyCLSICacheAboutBuild: sinon.stub(), - downloadLatestCompileCache: sinon.stub().resolves(), - downloadOutputDotSynctexFromCompileCache: sinon.stub().resolves(), - }, + './CLSICacheHandler': this.CLSICacheHandler, }, }) }) @@ -462,12 +467,83 @@ describe('CompileManager', function () { this.compileDir, this.Settings.clsi.docker.image, 60000, - {} + {}, + 'synctex' ) }) it('should return the parsed output', function () { - expect(this.result).to.deep.equal(this.records) + expect(this.result).to.deep.equal({ + codePositions: this.records, + downloadedFromCache: false, + }) + }) + }) + + describe('from cache in docker', function () { + beforeEach(async function () { + this.CommandRunner.canRunSyncTeXInOutputDir.returns(true) + this.Settings.path.synctexBaseDir + .withArgs(`${this.projectId}-${this.userId}`) + .returns('/compile') + + const errNotFound = new Error() + errNotFound.code = 'ENOENT' + this.outputDir = `${this.outputBaseDir}/${this.projectId}-${this.userId}/${this.OutputCacheManager.CACHE_SUBDIR}/${this.buildId}` + const filename = Path.join(this.outputDir, 'output.synctex.gz') + this.fsPromises.stat + .withArgs(this.outputDir) + .onFirstCall() + .rejects(errNotFound) + this.fsPromises.stat + .withArgs(this.outputDir) + .onSecondCall() + .resolves(this.dirStats) + this.fsPromises.stat.withArgs(filename).resolves(this.fileStats) + this.CLSICacheHandler.downloadOutputDotSynctexFromCompileCache.resolves( + true + ) + this.result = await this.CompileManager.promises.syncFromCode( + this.projectId, + this.userId, + this.filename, + this.line, + this.column, + { + imageName: 'image', + editorId: '00000000-0000-0000-0000-000000000000', + buildId: this.buildId, + compileFromClsiCache: true, + } + ) + }) + + it('should run in output dir', function () { + const outputFilePath = '/compile/output.pdf' + const inputFilePath = `/compile/${this.filename}` + expect(this.CommandRunner.promises.run).to.have.been.calledWith( + `${this.projectId}-${this.userId}`, + [ + 'synctex', + 'view', + '-i', + `${this.line}:${this.column}:${inputFilePath}`, + '-o', + outputFilePath, + ], + this.outputDir, + 'image', + 60000, + {}, + 'synctex-output' + ) + }) + + it('should return the parsed output', function () { + expect(this.result).to.deep.equal({ + codePositions: this.records, + downloadedFromCache: true, + }) }) }) @@ -500,7 +576,8 @@ describe('CompileManager', function () { this.compileDir, customImageName, 60000, - {} + {}, + 'synctex' ) }) }) @@ -544,7 +621,10 @@ describe('CompileManager', function () { }) it('should return the parsed output', function () { - expect(this.result).to.deep.equal(this.records) + expect(this.result).to.deep.equal({ + pdfPositions: this.records, + downloadedFromCache: false, + }) }) }) diff --git a/services/web/frontend/js/features/pdf-preview/hooks/use-synctex.ts b/services/web/frontend/js/features/pdf-preview/hooks/use-synctex.ts index 686d988dee..77986bfeac 100644 --- a/services/web/frontend/js/features/pdf-preview/hooks/use-synctex.ts +++ b/services/web/frontend/js/features/pdf-preview/hooks/use-synctex.ts @@ -18,6 +18,7 @@ import { CursorPosition } from '@/features/ide-react/types/cursor-position' import { isValidTeXFile } from '@/main/is-valid-tex-file' import { PdfScrollPosition } from '@/shared/hooks/use-pdf-scroll-position' import { showFileErrorToast } from '@/features/pdf-preview/components/synctex-toasts' +import { sendMB } from '@/infrastructure/event-tracking' export default function useSynctex(): { syncToPdf: () => void @@ -115,6 +116,12 @@ export default function useSynctex(): { .then(data => { setShowLogs(false) setHighlights(data.pdf) + if (data.downloadedFromCache) { + sendMB('synctex-downloaded-from-cache', { + projectId, + method: 'code', + }) + } }) .catch(debugConsole.error) .finally(() => { @@ -223,6 +230,12 @@ export default function useSynctex(): { .then(data => { const [{ file, line }] = data.code goToCodeLine(file, line) + if (data.downloadedFromCache) { + sendMB('synctex-downloaded-from-cache', { + projectId, + method: 'pdf', + }) + } }) .catch(debugConsole.error) .finally(() => { From 7ea1b690f24f7acc0065b698b7da65d999d6f8cc Mon Sep 17 00:00:00 2001 From: Eric Mc Sween <5454374+emcsween@users.noreply.github.com> Date: Thu, 8 May 2025 11:13:59 -0400 Subject: [PATCH 056/194] Configure PKCE support in OAuth clients (#25300) This flag will control whether or not a particular client is allowed to use PKCE instead of a client secret when requesting an access token. GitOrigin-RevId: b9471112a485233308410e0cb7f20e20a613a971 --- .../web/scripts/oauth/register_client.mjs | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/services/web/scripts/oauth/register_client.mjs b/services/web/scripts/oauth/register_client.mjs index a3b798155b..8ca97f7321 100644 --- a/services/web/scripts/oauth/register_client.mjs +++ b/services/web/scripts/oauth/register_client.mjs @@ -34,31 +34,45 @@ async function upsertApplication(opts) { const key = { id: opts.id } const defaults = {} const updates = {} + if (opts.name != null) { updates.name = opts.name } + if (opts.secret != null) { updates.clientSecret = hashSecret(opts.secret) } + if (opts.grants != null) { updates.grants = opts.grants } else { defaults.grants = [] } + if (opts.scopes != null) { updates.scopes = opts.scopes } else { defaults.scopes = [] } + if (opts.redirectUris != null) { updates.redirectUris = opts.redirectUris } else { defaults.redirectUris = [] } + if (opts.mongoId != null) { defaults._id = new ObjectId(opts.mongoId) } + if (opts.enablePkce) { + updates.pkceEnabled = true + } + + if (opts.disablePkce) { + updates.pkceEnabled = false + } + await db.oauthApplications.updateOne( key, { @@ -71,17 +85,24 @@ async function upsertApplication(opts) { function parseArgs() { const args = minimist(process.argv.slice(2), { - boolean: ['help'], + boolean: ['help', 'enable-pkce', 'disable-pkce'], }) + if (args.help) { usage() process.exit(0) } + if (args._.length !== 1) { usage() process.exit(1) } + if (args['enable-pkce'] && args['disable-pkce']) { + console.error('Options --enable-pkce and --disable-pkce are exclusive') + process.exit(1) + } + return { id: args._[0], mongoId: args['mongo-id'], @@ -90,6 +111,8 @@ function parseArgs() { scopes: toArray(args.scope), grants: toArray(args.grant), redirectUris: toArray(args['redirect-uri']), + enablePkce: args['enable-pkce'], + disablePkce: args['disable-pkce'], } } @@ -105,6 +128,8 @@ Options: --grant Accepted grant type (can be given more than once) --redirect-uri Accepted redirect URI (can be given more than once) --mongo-id Mongo ID to use if the configuration is created (optional) + --enable-pkce Enable PKCE + --disable-pkce Disable PKCE `) } From 5717ea7f5c819d4dc420dbd930d90db4138b1014 Mon Sep 17 00:00:00 2001 From: Eric Mc Sween <5454374+emcsween@users.noreply.github.com> Date: Thu, 8 May 2025 11:27:17 -0400 Subject: [PATCH 057/194] Merge pull request #25306 from overleaf/em-redis-buffer-read-operations Add changes from Redis when reading chunks from the chunk store GitOrigin-RevId: c0ebf0669b91eb2efc5d1091d025e81efdff9fe4 --- .../api/controllers/project_import.js | 4 +- .../storage/lib/backupGenerator.mjs | 3 +- .../storage/lib/chunk_store/errors.js | 2 + .../storage/lib/chunk_store/index.js | 82 +++- .../storage/lib/chunk_store/redis.js | 87 +++-- .../history-v1/storage/lib/persist_changes.js | 12 +- .../storage/scripts/recover_doc_versions.js | 2 +- services/history-v1/storage/scripts/show.mjs | 25 +- .../storage/tasks/fix_duplicate_versions.js | 2 +- .../acceptance/js/api/backupVerifier.test.mjs | 2 +- .../acceptance/js/storage/chunk_store.test.js | 127 +++++- .../storage/chunk_store_redis_backend.test.js | 363 ++++++++++-------- 12 files changed, 487 insertions(+), 224 deletions(-) diff --git a/services/history-v1/api/controllers/project_import.js b/services/history-v1/api/controllers/project_import.js index 9d47fe06a9..edffb19a25 100644 --- a/services/history-v1/api/controllers/project_import.js +++ b/services/history-v1/api/controllers/project_import.js @@ -95,7 +95,9 @@ async function importChanges(req, res, next) { } async function buildResultSnapshot(resultChunk) { - const chunk = resultChunk || (await chunkStore.loadLatest(projectId)) + const chunk = + resultChunk || + (await chunkStore.loadLatest(projectId, { persistedOnly: true })) const snapshot = chunk.getSnapshot() snapshot.applyAll(chunk.getChanges()) const rawSnapshot = await snapshot.store(hashCheckBlobStore) diff --git a/services/history-v1/storage/lib/backupGenerator.mjs b/services/history-v1/storage/lib/backupGenerator.mjs index 4c18929d54..d8f1b0e99a 100644 --- a/services/history-v1/storage/lib/backupGenerator.mjs +++ b/services/history-v1/storage/lib/backupGenerator.mjs @@ -31,7 +31,8 @@ async function lookBehindForSeenBlobs( // so we find the set of backed up blobs from the previous chunk const previousChunk = await chunkStore.loadAtVersion( projectId, - lastBackedUpVersion + lastBackedUpVersion, + { persistedOnly: true } ) const previousChunkHistory = previousChunk.getHistory() previousChunkHistory.findBlobHashes(seenBlobs) diff --git a/services/history-v1/storage/lib/chunk_store/errors.js b/services/history-v1/storage/lib/chunk_store/errors.js index fc37dbe2a1..75b830f9a0 100644 --- a/services/history-v1/storage/lib/chunk_store/errors.js +++ b/services/history-v1/storage/lib/chunk_store/errors.js @@ -4,10 +4,12 @@ class ChunkVersionConflictError extends OError {} class BaseVersionConflictError extends OError {} class JobNotFoundError extends OError {} class JobNotReadyError extends OError {} +class VersionOutOfBoundsError extends OError {} module.exports = { ChunkVersionConflictError, BaseVersionConflictError, JobNotFoundError, JobNotReadyError, + VersionOutOfBoundsError, } diff --git a/services/history-v1/storage/lib/chunk_store/index.js b/services/history-v1/storage/lib/chunk_store/index.js index f75c017552..89efaf87ca 100644 --- a/services/history-v1/storage/lib/chunk_store/index.js +++ b/services/history-v1/storage/lib/chunk_store/index.js @@ -32,7 +32,15 @@ const { BlobStore } = require('../blob_store') const { historyStore } = require('../history_store') const mongoBackend = require('./mongo') const postgresBackend = require('./postgres') -const { ChunkVersionConflictError } = require('./errors') +const redisBackend = require('./redis') +const { + ChunkVersionConflictError, + VersionOutOfBoundsError, +} = require('./errors') + +/** + * @import { Change } from 'overleaf-editor-core' + */ const DEFAULT_DELETE_BATCH_SIZE = parseInt(config.get('maxDeleteKeys'), 10) const DEFAULT_DELETE_TIMEOUT_SECS = 3000 // 50 minutes @@ -103,12 +111,23 @@ async function loadLatestRaw(projectId, opts) { * Load the latest Chunk stored for a project, including blob metadata. * * @param {string} projectId - * @return {Promise.} + * @param {object} [opts] + * @param {boolean} [opts.persistedOnly] - only include persisted changes + * @return {Promise} */ -async function loadLatest(projectId) { +async function loadLatest(projectId, opts = {}) { const chunkRecord = await loadLatestRaw(projectId) const rawHistory = await historyStore.loadRaw(projectId, chunkRecord.id) const history = History.fromRaw(rawHistory) + + if (!opts.persistedOnly) { + const nonPersistedChanges = await getChunkExtension( + projectId, + chunkRecord.endVersion + ) + history.pushChanges(nonPersistedChanges) + } + const blobStore = new BlobStore(projectId) const batchBlobStore = new BatchBlobStore(blobStore) await lazyLoadHistoryFiles(history, batchBlobStore) @@ -117,8 +136,13 @@ async function loadLatest(projectId) { /** * Load the the chunk that contains the given version, including blob metadata. + * + * @param {string} projectId + * @param {number} version + * @param {object} [opts] + * @param {boolean} [opts.persistedOnly] - only include persisted changes */ -async function loadAtVersion(projectId, version) { +async function loadAtVersion(projectId, version, opts = {}) { assert.projectId(projectId, 'bad projectId') assert.integer(version, 'bad version') @@ -129,6 +153,15 @@ async function loadAtVersion(projectId, version) { const chunkRecord = await backend.getChunkForVersion(projectId, version) const rawHistory = await historyStore.loadRaw(projectId, chunkRecord.id) const history = History.fromRaw(rawHistory) + + if (!opts.persistedOnly) { + const nonPersistedChanges = await getChunkExtension( + projectId, + chunkRecord.endVersion + ) + history.pushChanges(nonPersistedChanges) + } + await lazyLoadHistoryFiles(history, batchBlobStore) return new Chunk(history, chunkRecord.endVersion - history.countChanges()) } @@ -136,8 +169,13 @@ async function loadAtVersion(projectId, version) { /** * Load the chunk that contains the version that was current at the given * timestamp, including blob metadata. + * + * @param {string} projectId + * @param {Date} timestamp + * @param {object} [opts] + * @param {boolean} [opts.persistedOnly] - only include persisted changes */ -async function loadAtTimestamp(projectId, timestamp) { +async function loadAtTimestamp(projectId, timestamp, opts = {}) { assert.projectId(projectId, 'bad projectId') assert.date(timestamp, 'bad timestamp') @@ -148,6 +186,15 @@ async function loadAtTimestamp(projectId, timestamp) { const chunkRecord = await backend.getChunkForTimestamp(projectId, timestamp) const rawHistory = await historyStore.loadRaw(projectId, chunkRecord.id) const history = History.fromRaw(rawHistory) + + if (!opts.persistedOnly) { + const nonPersistedChanges = await getChunkExtension( + projectId, + chunkRecord.endVersion + ) + history.pushChanges(nonPersistedChanges) + } + await lazyLoadHistoryFiles(history, batchBlobStore) return new Chunk(history, chunkRecord.endVersion - history.countChanges()) } @@ -418,6 +465,31 @@ function getBackend(projectId) { } } +/** + * Gets non-persisted changes that could extend a chunk + * + * @param {string} projectId + * @param {number} chunkEndVersion - end version of the chunk to extend + * + * @return {Promise} + */ +async function getChunkExtension(projectId, chunkEndVersion) { + try { + const changes = await redisBackend.getNonPersistedChanges( + projectId, + chunkEndVersion + ) + return changes + } catch (err) { + if (err instanceof VersionOutOfBoundsError) { + // If we can't extend the chunk, simply return an empty list + return [] + } else { + throw err + } + } +} + class AlreadyInitialized extends OError { constructor(projectId) { super('Project is already initialized', { projectId }) diff --git a/services/history-v1/storage/lib/chunk_store/redis.js b/services/history-v1/storage/lib/chunk_store/redis.js index 58ee0d15a7..6858127a0c 100644 --- a/services/history-v1/storage/lib/chunk_store/redis.js +++ b/services/history-v1/storage/lib/chunk_store/redis.js @@ -2,13 +2,14 @@ const metrics = require('@overleaf/metrics') const OError = require('@overleaf/o-error') -const redis = require('../redis') -const rclient = redis.rclientHistory // const { Change, Snapshot } = require('overleaf-editor-core') +const redis = require('../redis') +const rclient = redis.rclientHistory const { BaseVersionConflictError, JobNotFoundError, JobNotReadyError, + VersionOutOfBoundsError, } = require('./errors') const MAX_PERSISTED_CHANGES = 100 // Maximum number of persisted changes to keep in the buffer for clients that need to catch up. @@ -393,34 +394,36 @@ rclient.defineCommand('get_non_persisted_changes', { local headVersionKey = KEYS[1] local persistedVersionKey = KEYS[2] local changesKey = KEYS[3] + local baseVersion = tonumber(ARGV[1]) -- Check if head version exists local headVersion = tonumber(redis.call('GET', headVersionKey)) if not headVersion then - return {} + return {'not_found'} end -- Check if persisted version exists local persistedVersion = tonumber(redis.call('GET', persistedVersionKey)) - - local startIndex if not persistedVersion then - -- None of the changes in Redis have been persisted - startIndex = 0 - elseif persistedVersion > headVersion then - -- This should never happen - return redis.error_reply('HEAD_VERSION_BEHIND_PERSISTED_VERSION') - elseif persistedVersion == headVersion then - return {} - else - -- startIndex is negative and counts from the end of the list of changes - startIndex = persistedVersion - headVersion + local changesCount = tonumber(redis.call('LLEN', changesKey)) + persistedVersion = headVersion - changesCount end - -- Get changes using LRANGE - local changes = redis.call('LRANGE', changesKey, startIndex, -1) + if baseVersion < persistedVersion or baseVersion > headVersion then + return {'out_of_bounds'} + elseif baseVersion == headVersion then + return {'ok', {}} + else + local numChanges = headVersion - baseVersion + local changes = redis.call('LRANGE', changesKey, -numChanges, -1) - return changes + if #changes < numChanges then + -- We didn't get as many changes as we expected + return {'out_of_bounds'} + end + + return {'ok', changes} + end `, }) @@ -428,32 +431,52 @@ rclient.defineCommand('get_non_persisted_changes', { * Retrieves non-persisted changes for a project from Redis. * * @param {string} projectId - The unique identifier of the project. - * @returns {Promise} A Promise that resolves to an array of non-persisted Change objects. + * @param {number} baseVersion - The version on top of which the changes should + * be applied. + * @returns {Promise} Changes that can be applied on top of + * baseVersion. An empty array means that the project doesn't have + * changes to persist. A null value means that the non-persisted + * changes can't be applied to the given base version. + * * @throws {Error} If Redis operations fail. */ -async function getNonPersistedChanges(projectId) { +async function getNonPersistedChanges(projectId, baseVersion) { + let result try { - const keys = [ + result = await rclient.get_non_persisted_changes( keySchema.headVersion({ projectId }), keySchema.persistedVersion({ projectId }), keySchema.changes({ projectId }), - ] - - const result = await rclient.get_non_persisted_changes(keys) - - // Parse the changes - const changes = result?.map(json => Change.fromRaw(JSON.parse(json))) ?? [] - - metrics.inc('chunk_store.redis.get_non_persisted_changes', 1, { - status: 'success', - }) - return changes + baseVersion.toString() + ) } catch (err) { metrics.inc('chunk_store.redis.get_non_persisted_changes', 1, { status: 'error', }) throw err } + + const status = result[0] + metrics.inc('chunk_store.redis.get_non_persisted_changes', 1, { + status, + }) + + if (status === 'ok') { + return result[1].map(json => Change.fromRaw(JSON.parse(json))) + } else if (status === 'not_found') { + return [] + } else if (status === 'out_of_bounds') { + throw new VersionOutOfBoundsError( + "Non-persisted changes can't be applied to base version", + { projectId, baseVersion } + ) + } else { + throw new OError('unknown status for get_non_persisted_changes', { + projectId, + baseVersion, + status, + }) + } } rclient.defineCommand('set_persisted_version', { diff --git a/services/history-v1/storage/lib/persist_changes.js b/services/history-v1/storage/lib/persist_changes.js index 20080d20a6..09d41382d4 100644 --- a/services/history-v1/storage/lib/persist_changes.js +++ b/services/history-v1/storage/lib/persist_changes.js @@ -185,7 +185,9 @@ async function persistChanges(projectId, allChanges, limits, clientEndVersion) { } async function loadLatestChunk() { - const latestChunk = await chunkStore.loadLatest(projectId) + const latestChunk = await chunkStore.loadLatest(projectId, { + persistedOnly: true, + }) currentChunk = latestChunk originalEndVersion = latestChunk.getEndVersion() @@ -217,8 +219,11 @@ async function persistChanges(projectId, allChanges, limits, clientEndVersion) { } async function fakePersistRedisChanges() { - const nonPersistedChanges = - await redisBackend.getNonPersistedChanges(projectId) + const baseVersion = currentChunk.getEndVersion() + const nonPersistedChanges = await redisBackend.getNonPersistedChanges( + projectId, + baseVersion + ) if ( serializeChanges(nonPersistedChanges) === @@ -232,7 +237,6 @@ async function persistChanges(projectId, allChanges, limits, clientEndVersion) { }) } - const baseVersion = currentChunk.getEndVersion() const persistedVersion = baseVersion + nonPersistedChanges.length await redisBackend.setPersistedVersion(projectId, persistedVersion) } diff --git a/services/history-v1/storage/scripts/recover_doc_versions.js b/services/history-v1/storage/scripts/recover_doc_versions.js index f121c60afd..650fb20324 100644 --- a/services/history-v1/storage/scripts/recover_doc_versions.js +++ b/services/history-v1/storage/scripts/recover_doc_versions.js @@ -279,7 +279,7 @@ async function processProject(project, summary) { async function getHistoryDocVersions(project) { const historyId = project.overleaf.history.id - const chunk = await chunkStore.loadLatest(historyId) + const chunk = await chunkStore.loadLatest(historyId, { persistedOnly: true }) if (chunk == null) { return [] } diff --git a/services/history-v1/storage/scripts/show.mjs b/services/history-v1/storage/scripts/show.mjs index b4ae1664e3..51697dc38f 100644 --- a/services/history-v1/storage/scripts/show.mjs +++ b/services/history-v1/storage/scripts/show.mjs @@ -48,7 +48,16 @@ async function listChunks(historyId) { async function fetchChunkLocal(historyId, version) { const chunkRecord = await getChunkMetadataForVersion(historyId, version) const chunk = await loadAtVersion(historyId, version) - return { key: version, chunk, metadata: chunkRecord, source: 'local storage' } + const persistedChunk = await loadAtVersion(historyId, version, { + persistedOnly: true, + }) + return { + key: version, + chunk, + persistedChunk, + metadata: chunkRecord, + source: 'local storage', + } } async function fetchChunkRemote(historyId, version) { @@ -73,7 +82,7 @@ async function fetchChunkRemote(historyId, version) { } async function displayChunk(historyId, version, options) { - const { key, chunk, metadata, source } = await (options.remote + const { key, chunk, persistedChunk, metadata, source } = await (options.remote ? fetchChunkRemote(historyId, version) : fetchChunkLocal(historyId, version)) console.log('Source:', source) @@ -81,6 +90,18 @@ async function displayChunk(historyId, version, options) { console.log('Key', key) // console.log('Number of changes', chunk.getChanges().length) console.log(JSON.stringify(chunk)) + if ( + persistedChunk && + persistedChunk.getChanges().length !== chunk.getChanges().length + ) { + console.warn( + 'Warning: Local chunk and persisted chunk have different number of changes:', + chunk.getChanges().length, + 'local (including buffer) vs', + persistedChunk.getChanges().length, + 'persisted' + ) + } } async function fetchBlobRemote(historyId, blobHash) { diff --git a/services/history-v1/storage/tasks/fix_duplicate_versions.js b/services/history-v1/storage/tasks/fix_duplicate_versions.js index a7db4b2765..ae9dcb4965 100755 --- a/services/history-v1/storage/tasks/fix_duplicate_versions.js +++ b/services/history-v1/storage/tasks/fix_duplicate_versions.js @@ -34,7 +34,7 @@ async function main() { async function processProject(projectId, save) { console.log(`Project ${projectId}:`) - const chunk = await chunkStore.loadLatest(projectId) + const chunk = await chunkStore.loadLatest(projectId, { persistedOnly: true }) let numChanges = 0 numChanges += removeDuplicateProjectVersions(chunk) numChanges += removeDuplicateDocVersions(chunk) diff --git a/services/history-v1/test/acceptance/js/api/backupVerifier.test.mjs b/services/history-v1/test/acceptance/js/api/backupVerifier.test.mjs index 2b4001a9f0..8c351a2652 100644 --- a/services/history-v1/test/acceptance/js/api/backupVerifier.test.mjs +++ b/services/history-v1/test/acceptance/js/api/backupVerifier.test.mjs @@ -149,7 +149,7 @@ async function addFileInNewChunk( historyId, { creationDate = new Date() } ) { - const chunk = await chunkStore.loadLatest(historyId) + const chunk = await chunkStore.loadLatest(historyId, { persistedOnly: true }) const operation = Operation.addFile( `${historyId}.txt`, File.fromString(fileContents) diff --git a/services/history-v1/test/acceptance/js/storage/chunk_store.test.js b/services/history-v1/test/acceptance/js/storage/chunk_store.test.js index 50341fdcb5..60710b2407 100644 --- a/services/history-v1/test/acceptance/js/storage/chunk_store.test.js +++ b/services/history-v1/test/acceptance/js/storage/chunk_store.test.js @@ -18,7 +18,8 @@ const { EditFileOperation, TextOperation, } = require('overleaf-editor-core') -const { chunkStore, historyStore } = require('../../../../storage') +const { chunkStore, historyStore, BlobStore } = require('../../../../storage') +const redisBackend = require('../../../../storage/lib/chunk_store/redis') describe('chunkStore', function () { beforeEach(cleanup.everything) @@ -42,6 +43,7 @@ describe('chunkStore', function () { describe(scenario.description, function () { let projectId let projectRecord + let blobStore beforeEach(async function () { projectId = await scenario.createProject() @@ -49,6 +51,7 @@ describe('chunkStore', function () { projectRecord = await projects.insertOne({ overleaf: { history: { id: scenario.idMapping(projectId) } }, }) + blobStore = new BlobStore(projectId) }) it('loads empty latest chunk for a new project', async function () { @@ -62,10 +65,11 @@ describe('chunkStore', function () { const pendingChangeTimestamp = new Date('2014-01-01T00:00:00') const lastChangeTimestamp = new Date('2015-01-01T00:00:00') beforeEach(async function () { + const blob = await blobStore.putString('abc') const chunk = makeChunk( [ makeChange( - Operation.addFile('main.tex', File.fromString('abc')), + Operation.addFile('main.tex', File.createLazyFromBlobs(blob)), lastChangeTimestamp ), ], @@ -98,8 +102,11 @@ describe('chunkStore', function () { beforeEach(async function () { const chunk = await chunkStore.loadLatest(projectId) const oldEndVersion = chunk.getEndVersion() + const blob = await blobStore.putString('') const changes = [ - makeChange(Operation.addFile(testPathname, File.fromString(''))), + makeChange( + Operation.addFile(testPathname, File.createLazyFromBlobs(blob)) + ), makeChange(Operation.editFile(testPathname, testTextOperation)), ] lastChangeTimestamp = changes[1].getTimestamp() @@ -181,14 +188,15 @@ describe('chunkStore', function () { let firstChunk, secondChunk, thirdChunk beforeEach(async function () { + const blob = await blobStore.putString('') firstChunk = makeChunk( [ makeChange( - Operation.addFile('foo.tex', File.fromString('')), + Operation.addFile('foo.tex', File.createLazyFromBlobs(blob)), new Date(firstChunkTimestamp - 5000) ), makeChange( - Operation.addFile('bar.tex', File.fromString('')), + Operation.addFile('bar.tex', File.createLazyFromBlobs(blob)), firstChunkTimestamp ), ], @@ -205,11 +213,11 @@ describe('chunkStore', function () { secondChunk = makeChunk( [ makeChange( - Operation.addFile('baz.tex', File.fromString('')), + Operation.addFile('baz.tex', File.createLazyFromBlobs(blob)), new Date(secondChunkTimestamp - 5000) ), makeChange( - Operation.addFile('qux.tex', File.fromString('')), + Operation.addFile('qux.tex', File.createLazyFromBlobs(blob)), secondChunkTimestamp ), ], @@ -221,7 +229,7 @@ describe('chunkStore', function () { thirdChunk = makeChunk( [ makeChange( - Operation.addFile('quux.tex', File.fromString('')), + Operation.addFile('quux.tex', File.createLazyFromBlobs(blob)), thirdChunkTimestamp ), ], @@ -317,11 +325,15 @@ describe('chunkStore', function () { let newChunk beforeEach(async function () { + const blob = await blobStore.putString('') newChunk = makeChunk( [ ...thirdChunk.getChanges(), makeChange( - Operation.addFile('onemore.tex', File.fromString('')), + Operation.addFile( + 'onemore.tex', + File.createLazyFromBlobs(blob) + ), thirdChunkTimestamp ), ], @@ -368,6 +380,79 @@ describe('chunkStore', function () { }) }) + describe('with changes queued in the Redis buffer', function () { + let queuedChanges + + beforeEach(async function () { + const snapshot = thirdChunk.getSnapshot() + snapshot.applyAll(thirdChunk.getChanges()) + const blob = await blobStore.putString('zzz') + queuedChanges = [ + makeChange( + Operation.addFile( + 'in-redis.tex', + File.createLazyFromBlobs(blob) + ), + new Date() + ), + ] + await redisBackend.queueChanges( + projectId, + snapshot, + thirdChunk.getEndVersion(), + queuedChanges + ) + }) + + it('includes the queued changes when getting the latest chunk', async function () { + const chunk = await chunkStore.loadLatest(projectId) + const expectedChanges = thirdChunk + .getChanges() + .concat(queuedChanges) + expect(chunk.getChanges()).to.deep.equal(expectedChanges) + }) + + it('includes the queued changes when getting the latest chunk by timestamp', async function () { + const chunk = await chunkStore.loadAtTimestamp( + projectId, + thirdChunkTimestamp + ) + const expectedChanges = thirdChunk + .getChanges() + .concat(queuedChanges) + expect(chunk.getChanges()).to.deep.equal(expectedChanges) + }) + + it("doesn't include the queued changes when getting another chunk by timestamp", async function () { + const chunk = await chunkStore.loadAtTimestamp( + projectId, + secondChunkTimestamp + ) + const expectedChanges = secondChunk.getChanges() + expect(chunk.getChanges()).to.deep.equal(expectedChanges) + }) + + it('includes the queued changes when getting the latest chunk by version', async function () { + const chunk = await chunkStore.loadAtVersion( + projectId, + thirdChunk.getEndVersion() + ) + const expectedChanges = thirdChunk + .getChanges() + .concat(queuedChanges) + expect(chunk.getChanges()).to.deep.equal(expectedChanges) + }) + + it("doesn't include the queued changes when getting another chunk by version", async function () { + const chunk = await chunkStore.loadAtVersion( + projectId, + secondChunk.getEndVersion() + ) + const expectedChanges = secondChunk.getChanges() + expect(chunk.getChanges()).to.deep.equal(expectedChanges) + }) + }) + describe('when iterating the chunks with getProjectChunksFromVersion', function () { // The first chunk has startVersion:0 and endVersion:2 for (let startVersion = 0; startVersion <= 2; startVersion++) { @@ -470,8 +555,11 @@ describe('chunkStore', function () { let chunk = await chunkStore.loadLatest(projectId) expect(chunk.getEndVersion()).to.equal(oldEndVersion) + const blob = await blobStore.putString('') const changes = [ - makeChange(Operation.addFile(testPathname, File.fromString(''))), + makeChange( + Operation.addFile(testPathname, File.createLazyFromBlobs(blob)) + ), makeChange(Operation.editFile(testPathname, testTextOperation)), ] chunk.pushChanges(changes) @@ -487,9 +575,12 @@ describe('chunkStore', function () { describe('version checks', function () { beforeEach(async function () { // Create a chunk with start version 0, end version 3 + const blob = await blobStore.putString('abc') const chunk = makeChunk( [ - makeChange(Operation.addFile('main.tex', File.fromString('abc'))), + makeChange( + Operation.addFile('main.tex', File.createLazyFromBlobs(blob)) + ), makeChange( Operation.editFile( 'main.tex', @@ -509,8 +600,13 @@ describe('chunkStore', function () { }) it('refuses to create a chunk with the same start version', async function () { + const blob = await blobStore.putString('abc') const chunk = makeChunk( - [makeChange(Operation.addFile('main.tex', File.fromString('abc')))], + [ + makeChange( + Operation.addFile('main.tex', File.createLazyFromBlobs(blob)) + ), + ], 0 ) await expect(chunkStore.create(projectId, chunk)).to.be.rejectedWith( @@ -519,8 +615,13 @@ describe('chunkStore', function () { }) it("allows creating chunks that don't have version conflicts", async function () { + const blob = await blobStore.putString('abc') const chunk = makeChunk( - [makeChange(Operation.addFile('main.tex', File.fromString('abc')))], + [ + makeChange( + Operation.addFile('main.tex', File.createLazyFromBlobs(blob)) + ), + ], 3 ) await chunkStore.create(projectId, chunk) diff --git a/services/history-v1/test/acceptance/js/storage/chunk_store_redis_backend.test.js b/services/history-v1/test/acceptance/js/storage/chunk_store_redis_backend.test.js index 91b7e3a5f4..5e226ff9e2 100644 --- a/services/history-v1/test/acceptance/js/storage/chunk_store_redis_backend.test.js +++ b/services/history-v1/test/acceptance/js/storage/chunk_store_redis_backend.test.js @@ -12,6 +12,7 @@ const redisBackend = require('../../../../storage/lib/chunk_store/redis') const { JobNotReadyError, JobNotFoundError, + VersionOutOfBoundsError, } = require('../../../../storage/lib/chunk_store/errors') const redis = require('../../../../storage/lib/redis') const rclient = redis.rclientHistory @@ -509,189 +510,191 @@ describe('chunk buffer Redis backend', function () { }) describe('getNonPersistedChanges', function () { - it('should return empty array when project does not exist', async function () { - const changes = await redisBackend.getNonPersistedChanges(projectId) - expect(changes).to.be.an('array').that.is.empty + describe('project not loaded', function () { + it('should return empty array', async function () { + const changes = await redisBackend.getNonPersistedChanges(projectId, 0) + expect(changes).to.be.an('array').that.is.empty + }) + + it('should handle any base version', async function () { + const changes = await redisBackend.getNonPersistedChanges(projectId, 2) + expect(changes).to.be.an('array').that.is.empty + }) }) - it('should return all changes when persisted version is not set', async function () { - const changes = [makeChange(), makeChange(), makeChange()] - queueChanges(projectId, changes) + describe('project never persisted', function () { + let changes - const nonPersistedChanges = - await redisBackend.getNonPersistedChanges(projectId) - expect(nonPersistedChanges.map(change => change.toRaw())).to.deep.equal( - changes.map(change => change.toRaw()) - ) + beforeEach(async function () { + changes = await setupState(projectId, { + headVersion: 5, + persistedVersion: null, + changes: 3, + }) + }) + + it('should return all changes if requested', async function () { + const nonPersistedChanges = await redisBackend.getNonPersistedChanges( + projectId, + 2 + ) + expect(nonPersistedChanges).to.deep.equal(changes) + }) + + it('should return part of the changes if requested', async function () { + const nonPersistedChanges = await redisBackend.getNonPersistedChanges( + projectId, + 3 + ) + expect(nonPersistedChanges).to.deep.equal(changes.slice(1)) + }) + + it('should error if the base version requested is too low', async function () { + await expect( + redisBackend.getNonPersistedChanges(projectId, 0) + ).to.be.rejectedWith(VersionOutOfBoundsError) + }) + + it('should return an empty array if the base version is the head version', async function () { + const nonPersistedChanges = await redisBackend.getNonPersistedChanges( + projectId, + 5 + ) + expect(nonPersistedChanges).to.deep.equal([]) + }) + + it('should error if the base version requested is too high', async function () { + await expect( + redisBackend.getNonPersistedChanges(projectId, 6) + ).to.be.rejectedWith(VersionOutOfBoundsError) + }) }) - it('should return empty array when persisted version equals head version', async function () { - // Set both head and persisted versions to be equal - const version = 5 - await rclient.set( - keySchema.headVersion({ projectId }), - version.toString() - ) - await rclient.set( - keySchema.persistedVersion({ projectId }), - version.toString() - ) + describe('fully persisted changes', function () { + beforeEach(async function () { + await setupState(projectId, { + headVersion: 5, + persistedVersion: 5, + changes: 3, + }) + }) - const changes = await redisBackend.getNonPersistedChanges(projectId) - expect(changes).to.be.an('array').that.is.empty + it('should return an empty array when asked for the head version', async function () { + const nonPersistedChanges = await redisBackend.getNonPersistedChanges( + projectId, + 5 + ) + expect(nonPersistedChanges).to.deep.equal([]) + }) + + it('should throw an error when asked for an older version', async function () { + await expect( + redisBackend.getNonPersistedChanges(projectId, 4) + ).to.be.rejectedWith(VersionOutOfBoundsError) + }) + + it('should throw an error when asked for a newer version', async function () { + await expect( + redisBackend.getNonPersistedChanges(projectId, 6) + ).to.be.rejectedWith(VersionOutOfBoundsError) + }) }) - it('should return all non-persisted changes', async function () { - // Set head version to 5 and persisted version to 2 - const headVersion = 5 - const persistedVersion = 2 - await rclient.set( - keySchema.headVersion({ projectId }), - headVersion.toString() - ) - await rclient.set( - keySchema.persistedVersion({ projectId }), - persistedVersion.toString() - ) + describe('partially persisted project', async function () { + let changes - // Create changes for versions 3, 4, 5 - const timestamp = new Date() - const change1 = new Change([], timestamp) // Version 3 - const change2 = new Change([], timestamp) // Version 4 - const change3 = new Change([], timestamp) // Version 5 + beforeEach(async function () { + changes = await setupState(projectId, { + headVersion: 10, + persistedVersion: 7, + changes: 6, + }) + }) - // Push changes to Redis - await rclient.rpush( - keySchema.changes({ projectId }), - JSON.stringify(change1.toRaw()), - JSON.stringify(change2.toRaw()), - JSON.stringify(change3.toRaw()) - ) + it('should return all non-persisted changes if requested', async function () { + const nonPersistedChanges = await redisBackend.getNonPersistedChanges( + projectId, + 7 + ) + expect(nonPersistedChanges).to.deep.equal(changes.slice(3)) + }) - // Get non-persisted changes - const nonPersistedChanges = - await redisBackend.getNonPersistedChanges(projectId) + it('should return part of the changes if requested', async function () { + const nonPersistedChanges = await redisBackend.getNonPersistedChanges( + projectId, + 8 + ) + expect(nonPersistedChanges).to.deep.equal(changes.slice(4)) + }) - // Should return changes for versions 3, 4, 5 - expect(nonPersistedChanges).to.be.an('array').with.lengthOf(3) - expect(nonPersistedChanges[0].toRaw()).to.deep.equal(change1.toRaw()) - expect(nonPersistedChanges[1].toRaw()).to.deep.equal(change2.toRaw()) - expect(nonPersistedChanges[2].toRaw()).to.deep.equal(change3.toRaw()) + it('should error if the base version requested is too low', async function () { + await expect( + redisBackend.getNonPersistedChanges(projectId, 5) + ).to.be.rejectedWith(VersionOutOfBoundsError) + }) + + it('should return an empty array if the base version is the head version', async function () { + const nonPersistedChanges = await redisBackend.getNonPersistedChanges( + projectId, + 10 + ) + expect(nonPersistedChanges).to.deep.equal([]) + }) + + it('should error if the base version requested is too high', async function () { + await expect( + redisBackend.getNonPersistedChanges(projectId, 12) + ).to.be.rejectedWith(VersionOutOfBoundsError) + }) }) - it('should return a subset of changes when some are persisted', async function () { - // Set head version to 5 and persisted version to 3 - // This means versions 4 and 5 are not persisted - const headVersion = 5 - const persistedVersion = 3 - await rclient.set( - keySchema.headVersion({ projectId }), - headVersion.toString() - ) - await rclient.set( - keySchema.persistedVersion({ projectId }), - persistedVersion.toString() - ) + // This case should never happen, but we'll handle it anyway + describe('persisted version before start of changes list', async function () { + let changes - // Create changes for versions 1, 2, 3, 4, 5 - const timestamp = new Date() - const change1 = new Change([], timestamp) // Version 1 - const change2 = new Change([], timestamp) // Version 2 - const change3 = new Change([], timestamp) // Version 3 - const change4 = new Change([], timestamp) // Version 4 - const change5 = new Change([], timestamp) // Version 5 + beforeEach(async function () { + changes = await setupState(projectId, { + headVersion: 5, + persistedVersion: 1, + changes: 3, + }) + }) - // Push changes to Redis - await rclient.rpush( - keySchema.changes({ projectId }), - JSON.stringify(change1.toRaw()), - JSON.stringify(change2.toRaw()), - JSON.stringify(change3.toRaw()), - JSON.stringify(change4.toRaw()), - JSON.stringify(change5.toRaw()) - ) + it('should return all non-persisted changes if requested', async function () { + const nonPersistedChanges = await redisBackend.getNonPersistedChanges( + projectId, + 2 + ) + expect(nonPersistedChanges).to.deep.equal(changes) + }) - // Get non-persisted changes - const nonPersistedChanges = - await redisBackend.getNonPersistedChanges(projectId) + it('should return part of the changes if requested', async function () { + const nonPersistedChanges = await redisBackend.getNonPersistedChanges( + projectId, + 3 + ) + expect(nonPersistedChanges).to.deep.equal(changes.slice(1)) + }) - // Should return only changes for versions 4 and 5 - expect(nonPersistedChanges).to.be.an('array').with.lengthOf(2) - expect(nonPersistedChanges[0].toRaw()).to.deep.equal(change4.toRaw()) - expect(nonPersistedChanges[1].toRaw()).to.deep.equal(change5.toRaw()) - }) + it('should error if the base version requested is too low', async function () { + await expect( + redisBackend.getNonPersistedChanges(projectId, 1) + ).to.be.rejectedWith(VersionOutOfBoundsError) + }) - it('should throw an error when persisted version is higher than head version', async function () { - // This is an unusual case that should not happen in practice - // The system should throw an error to indicate this abnormal state - const headVersion = 3 - const persistedVersion = 5 - await rclient.set( - keySchema.headVersion({ projectId }), - headVersion.toString() - ) - await rclient.set( - keySchema.persistedVersion({ projectId }), - persistedVersion.toString() - ) + it('should return an empty array if the base version is the head version', async function () { + const nonPersistedChanges = await redisBackend.getNonPersistedChanges( + projectId, + 5 + ) + expect(nonPersistedChanges).to.deep.equal([]) + }) - // Create changes - const timestamp = new Date() - const change1 = new Change([], timestamp) - const change2 = new Change([], timestamp) - const change3 = new Change([], timestamp) - - // Push changes to Redis - await rclient.rpush( - keySchema.changes({ projectId }), - JSON.stringify(change1.toRaw()), - JSON.stringify(change2.toRaw()), - JSON.stringify(change3.toRaw()) - ) - - // Use chai-as-promised for cleaner async error assertion - await expect( - redisBackend.getNonPersistedChanges(projectId) - ).to.be.rejectedWith(/HEAD_VERSION_BEHIND_PERSISTED_VERSION/) - }) - - it('should handle case where persisted version is before start of changes list', async function () { - // Setup: head version is 5, persisted version is 1 - // But changes list only starts from version 3 - const headVersion = 5 - const persistedVersion = 1 - await rclient.set( - keySchema.headVersion({ projectId }), - headVersion.toString() - ) - await rclient.set( - keySchema.persistedVersion({ projectId }), - persistedVersion.toString() - ) - - // Create changes for versions 3, 4, 5 only - const timestamp = new Date() - const change3 = new Change([], timestamp) // Version 3 - const change4 = new Change([], timestamp) // Version 4 - const change5 = new Change([], timestamp) // Version 5 - - // Push changes to Redis - await rclient.rpush( - keySchema.changes({ projectId }), - JSON.stringify(change3.toRaw()), - JSON.stringify(change4.toRaw()), - JSON.stringify(change5.toRaw()) - ) - - // Get non-persisted changes - const nonPersistedChanges = - await redisBackend.getNonPersistedChanges(projectId) - - // Should return all changes since the persisted version is before the start of the list - expect(nonPersistedChanges).to.be.an('array').with.lengthOf(3) - expect(nonPersistedChanges[0].toRaw()).to.deep.equal(change3.toRaw()) - expect(nonPersistedChanges[1].toRaw()).to.deep.equal(change4.toRaw()) - expect(nonPersistedChanges[2].toRaw()).to.deep.equal(change5.toRaw()) + it('should error if the base version requested is too high', async function () { + await expect( + redisBackend.getNonPersistedChanges(projectId, 6) + ).to.be.rejectedWith(VersionOutOfBoundsError) + }) }) }) @@ -1137,3 +1140,37 @@ function makeChange() { const timestamp = new Date() return new Change([], timestamp) } + +/** + * Setup Redis buffer state for tests + * + * @param {string} projectId + * @param {object} params + * @param {number} params.headVersion + * @param {number | null} params.persistedVersion + * @param {number} params.changes - number of changes to create + * @return {Promise} dummy changes that have been created + */ +async function setupState(projectId, params) { + await rclient.set(keySchema.headVersion({ projectId }), params.headVersion) + if (params.persistedVersion) { + await rclient.set( + keySchema.persistedVersion({ projectId }), + params.persistedVersion + ) + } + + const changes = [] + for (let i = 1; i <= params.changes; i++) { + const change = new Change( + [new AddFileOperation(`file${i}.tex`, File.createHollow(i, i))], + new Date() + ) + changes.push(change) + } + await rclient.rpush( + keySchema.changes({ projectId }), + changes.map(change => JSON.stringify(change.toRaw())) + ) + return changes +} From 391fca9e83e2c5580a4cfb55fe0a8ae873ce6b1e Mon Sep 17 00:00:00 2001 From: Eric Mc Sween <5454374+emcsween@users.noreply.github.com> Date: Thu, 8 May 2025 11:27:39 -0400 Subject: [PATCH 058/194] Merge pull request #25361 from overleaf/em-load-latest-raw Rename loadLatestRaw() to getLatestChunkMetadata() GitOrigin-RevId: e089dcfa57cbbc43df8666b51eca0d81d595a5a7 --- .../history-v1/api/controllers/projects.js | 2 +- .../storage/lib/chunk_store/index.js | 20 +++++++++---------- .../history-v1/storage/scripts/backup.mjs | 6 +++--- .../acceptance/js/api/backupVerifier.test.mjs | 6 +++--- .../acceptance/js/storage/backup.test.mjs | 12 +++++------ .../acceptance/js/storage/chunk_store.test.js | 14 +++++++------ 6 files changed, 31 insertions(+), 29 deletions(-) diff --git a/services/history-v1/api/controllers/projects.js b/services/history-v1/api/controllers/projects.js index d42e29ab81..5ae74cb6da 100644 --- a/services/history-v1/api/controllers/projects.js +++ b/services/history-v1/api/controllers/projects.js @@ -91,7 +91,7 @@ async function getLatestHistoryRaw(req, res, next) { const readOnly = req.swagger.params.readOnly.value try { const { startVersion, endVersion, endTimestamp } = - await chunkStore.loadLatestRaw(projectId, { readOnly }) + await chunkStore.getLatestChunkMetadata(projectId, { readOnly }) res.json({ startVersion, endVersion, diff --git a/services/history-v1/storage/lib/chunk_store/index.js b/services/history-v1/storage/lib/chunk_store/index.js index 89efaf87ca..12c0924707 100644 --- a/services/history-v1/storage/lib/chunk_store/index.js +++ b/services/history-v1/storage/lib/chunk_store/index.js @@ -96,15 +96,15 @@ async function lazyLoadHistoryFiles(history, batchBlobStore) { * @param {boolean} [opts.readOnly] * @return {Promise<{id: string, startVersion: number, endVersion: number, endTimestamp: Date}>} */ -async function loadLatestRaw(projectId, opts) { +async function getLatestChunkMetadata(projectId, opts) { assert.projectId(projectId, 'bad projectId') const backend = getBackend(projectId) - const chunkRecord = await backend.getLatestChunk(projectId, opts) - if (chunkRecord == null) { + const chunkMetadata = await backend.getLatestChunk(projectId, opts) + if (chunkMetadata == null) { throw new Chunk.NotFoundError(projectId) } - return chunkRecord + return chunkMetadata } /** @@ -116,14 +116,14 @@ async function loadLatestRaw(projectId, opts) { * @return {Promise} */ async function loadLatest(projectId, opts = {}) { - const chunkRecord = await loadLatestRaw(projectId) - const rawHistory = await historyStore.loadRaw(projectId, chunkRecord.id) + const chunkMetadata = await getLatestChunkMetadata(projectId) + const rawHistory = await historyStore.loadRaw(projectId, chunkMetadata.id) const history = History.fromRaw(rawHistory) if (!opts.persistedOnly) { const nonPersistedChanges = await getChunkExtension( projectId, - chunkRecord.endVersion + chunkMetadata.endVersion ) history.pushChanges(nonPersistedChanges) } @@ -131,7 +131,7 @@ async function loadLatest(projectId, opts = {}) { const blobStore = new BlobStore(projectId) const batchBlobStore = new BatchBlobStore(blobStore) await lazyLoadHistoryFiles(history, batchBlobStore) - return new Chunk(history, chunkRecord.startVersion) + return new Chunk(history, chunkMetadata.startVersion) } /** @@ -354,7 +354,7 @@ async function loadByChunkRecord(projectId, chunkRecord) { */ async function* getProjectChunksFromVersion(projectId, version) { const backend = getBackend(projectId) - const latestChunkMetadata = await loadLatestRaw(projectId) + const latestChunkMetadata = await getLatestChunkMetadata(projectId) if (!latestChunkMetadata || version > latestChunkMetadata.endVersion) { return } @@ -500,7 +500,7 @@ module.exports = { getBackend, initializeProject, loadLatest, - loadLatestRaw, + getLatestChunkMetadata, loadAtVersion, loadAtTimestamp, loadByChunkRecord, diff --git a/services/history-v1/storage/scripts/backup.mjs b/services/history-v1/storage/scripts/backup.mjs index 9ae6101105..94df24f66d 100644 --- a/services/history-v1/storage/scripts/backup.mjs +++ b/services/history-v1/storage/scripts/backup.mjs @@ -5,7 +5,7 @@ import commandLineArgs from 'command-line-args' import { Chunk, History, Snapshot } from 'overleaf-editor-core' import { getProjectChunks, - loadLatestRaw, + getLatestChunkMetadata, create, } from '../lib/chunk_store/index.js' import { client } from '../lib/mongodb.js' @@ -444,7 +444,7 @@ async function analyseBackupStatus(projectId) { await getBackupStatus(projectId) // TODO: when we have confidence that the latestChunkMetadata always matches // the values from the backupStatus we can skip loading it here - const latestChunkMetadata = await loadLatestRaw(historyId, { + const latestChunkMetadata = await getLatestChunkMetadata(historyId, { readOnly: Boolean(USE_SECONDARY), }) if ( @@ -454,7 +454,7 @@ async function analyseBackupStatus(projectId) { // compare the current end version with the latest chunk metadata to check that // the updates to the project collection are reliable // expect some failures due to the time window between getBackupStatus and - // loadLatestRaw where the project is being actively edited. + // getLatestChunkMetadata where the project is being actively edited. logger.warn( { projectId, diff --git a/services/history-v1/test/acceptance/js/api/backupVerifier.test.mjs b/services/history-v1/test/acceptance/js/api/backupVerifier.test.mjs index 8c351a2652..44e1a2652b 100644 --- a/services/history-v1/test/acceptance/js/api/backupVerifier.test.mjs +++ b/services/history-v1/test/acceptance/js/api/backupVerifier.test.mjs @@ -119,17 +119,17 @@ async function verifyBlobHTTP(historyId, hash) { } async function backupChunk(historyId) { - const newChunk = await chunkStore.loadLatestRaw(historyId) + const newChunkMetadata = await chunkStore.getLatestChunkMetadata(historyId) const { buffer: chunkBuffer } = await historyStore.loadRawWithBuffer( historyId, - newChunk.id + newChunkMetadata.id ) const md5 = Crypto.createHash('md5').update(chunkBuffer) await backupPersistor.sendStream( chunksBucket, path.join( projectKey.format(historyId), - projectKey.pad(newChunk.startVersion) + projectKey.pad(newChunkMetadata.startVersion) ), Stream.Readable.from([chunkBuffer]), { diff --git a/services/history-v1/test/acceptance/js/storage/backup.test.mjs b/services/history-v1/test/acceptance/js/storage/backup.test.mjs index 83087a1384..fdca1ce294 100644 --- a/services/history-v1/test/acceptance/js/storage/backup.test.mjs +++ b/services/history-v1/test/acceptance/js/storage/backup.test.mjs @@ -169,8 +169,8 @@ describe('backup script', function () { makeChunkKey(historyId, 0) ) const chunkContent = await text(chunkStream.pipe(createGunzip())) - const chunk = await ChunkStore.loadLatestRaw(historyId) - const rawHistory = await historyStore.loadRaw(historyId, chunk.id) + const chunkMetadata = await ChunkStore.getLatestChunkMetadata(historyId) + const rawHistory = await historyStore.loadRaw(historyId, chunkMetadata.id) expect(JSON.parse(chunkContent)).to.deep.equal(rawHistory) // Unrelated entries from backedUpBlobs should be not cleared @@ -299,8 +299,8 @@ describe('backup script', function () { makeChunkKey(historyId, 0) ) const chunkContent = await text(chunkStream.pipe(createGunzip())) - const chunk = await ChunkStore.loadLatestRaw(historyId) - const rawHistory = await historyStore.loadRaw(historyId, chunk.id) + const chunkMetadata = await ChunkStore.getLatestChunkMetadata(historyId) + const rawHistory = await historyStore.loadRaw(historyId, chunkMetadata.id) expect(JSON.parse(chunkContent)).to.deep.equal(rawHistory) // Unrelated entries from backedUpBlobs should be not cleared @@ -399,8 +399,8 @@ describe('backup script', function () { makeChunkKey(historyId, 0) ) const chunkContent = await text(chunkStream.pipe(createGunzip())) - const chunk = await ChunkStore.loadLatestRaw(historyId) - const rawHistory = await historyStore.loadRaw(historyId, chunk.id) + const chunkMetadata = await ChunkStore.getLatestChunkMetadata(historyId) + const rawHistory = await historyStore.loadRaw(historyId, chunkMetadata.id) expect(JSON.parse(chunkContent)).to.deep.equal(rawHistory) // Verify that the demoted global blob was backed up diff --git a/services/history-v1/test/acceptance/js/storage/chunk_store.test.js b/services/history-v1/test/acceptance/js/storage/chunk_store.test.js index 60710b2407..48c142817d 100644 --- a/services/history-v1/test/acceptance/js/storage/chunk_store.test.js +++ b/services/history-v1/test/acceptance/js/storage/chunk_store.test.js @@ -120,8 +120,9 @@ describe('chunkStore', function () { }) it('records the correct metadata in db readOnly=false', async function () { - const raw = await chunkStore.loadLatestRaw(projectId) - expect(raw).to.deep.include({ + const chunkMetadata = + await chunkStore.getLatestChunkMetadata(projectId) + expect(chunkMetadata).to.deep.include({ startVersion: 0, endVersion: 2, endTimestamp: lastChangeTimestamp, @@ -129,10 +130,11 @@ describe('chunkStore', function () { }) it('records the correct metadata in db readOnly=true', async function () { - const raw = await chunkStore.loadLatestRaw(projectId, { - readOnly: true, - }) - expect(raw).to.deep.include({ + const chunkMetadata = await chunkStore.getLatestChunkMetadata( + projectId, + { readOnly: true } + ) + expect(chunkMetadata).to.deep.include({ startVersion: 0, endVersion: 2, endTimestamp: lastChangeTimestamp, From c50bd6af89d76c84a83be9fd563b8e020366b270 Mon Sep 17 00:00:00 2001 From: M Fahru Date: Thu, 8 May 2025 09:28:05 -0700 Subject: [PATCH 059/194] Merge pull request #25372 from overleaf/kh-support-canceling-pending-stripe-change [web] cancel pending Stripe subscription change GitOrigin-RevId: c1d21a7d1c3962c20d589b1dd10f6c2a4c8e4be4 --- .../Subscription/SubscriptionHandler.js | 5 ++- .../Subscription/SubscriptionHandlerTests.js | 42 +++++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/services/web/app/src/Features/Subscription/SubscriptionHandler.js b/services/web/app/src/Features/Subscription/SubscriptionHandler.js index 5d328c9c59..ecf05695da 100644 --- a/services/web/app/src/Features/Subscription/SubscriptionHandler.js +++ b/services/web/app/src/Features/Subscription/SubscriptionHandler.js @@ -123,8 +123,9 @@ async function cancelPendingSubscriptionChange(user) { await LimitationsManager.promises.userHasSubscription(user) if (hasSubscription && subscription != null) { - await RecurlyClient.promises.removeSubscriptionChangeByUuid( - subscription.recurlySubscription_id + await Modules.promises.hooks.fire( + 'cancelPendingPaidSubscriptionChange', + subscription ) } } diff --git a/services/web/test/unit/src/Subscription/SubscriptionHandlerTests.js b/services/web/test/unit/src/Subscription/SubscriptionHandlerTests.js index 7814cc560d..ed5ed2f6d1 100644 --- a/services/web/test/unit/src/Subscription/SubscriptionHandlerTests.js +++ b/services/web/test/unit/src/Subscription/SubscriptionHandlerTests.js @@ -319,6 +319,48 @@ describe('SubscriptionHandler', function () { }) }) + describe('cancelPendingSubscriptionChange', function () { + beforeEach(function () { + this.user.id = this.activeRecurlySubscription.account.account_code + this.User.findById = (userId, projection) => ({ + exec: () => { + userId.should.equal(this.user.id) + return Promise.resolve(this.user) + }, + }) + }) + + it('should not fire cancelPendingPaidSubscriptionChange hook if user has no subscription', async function () { + this.LimitationsManager.promises.userHasSubscription.resolves({ + hasSubscription: false, + subscription: null, + }) + await this.SubscriptionHandler.promises.cancelPendingSubscriptionChange( + this.user, + this.plan_code + ) + expect(this.Modules.promises.hooks.fire).to.not.have.been.calledWith( + 'cancelPendingPaidSubscriptionChange', + sinon.match.any + ) + }) + + it('should fire cancelPendingPaidSubscriptionChange to update a valid subscription', async function () { + this.LimitationsManager.promises.userHasSubscription.resolves({ + hasSubscription: true, + subscription: this.subscription, + }) + await this.SubscriptionHandler.promises.cancelPendingSubscriptionChange( + this.user, + this.plan_code + ) + expect(this.Modules.promises.hooks.fire).to.have.been.calledWith( + 'cancelPendingPaidSubscriptionChange', + this.subscription + ) + }) + }) + describe('cancelSubscription', function () { describe('with a user without a subscription', function () { beforeEach(async function () { From 5ba31ab14fed0fe996024ef4e831223f435019f3 Mon Sep 17 00:00:00 2001 From: M Fahru Date: Thu, 8 May 2025 09:28:22 -0700 Subject: [PATCH 060/194] Merge pull request #25363 from overleaf/mf-stripe-webhook-delete-subscription [web] Delete "expired" subscription in Stripe (in Stripe, this is called "canceled" status) GitOrigin-RevId: 847cf431c2f6edd7ec6c4e17137d163e450dc4f1 --- services/web/types/stripe/webhook-event.ts | 7 ++++++- services/web/types/subscription/dashboard/subscription.ts | 5 +++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/services/web/types/stripe/webhook-event.ts b/services/web/types/stripe/webhook-event.ts index 648f26b8dc..a53c03f440 100644 --- a/services/web/types/stripe/webhook-event.ts +++ b/services/web/types/stripe/webhook-event.ts @@ -1,5 +1,10 @@ +type StripeWebhookEventType = + | 'customer.subscription.created' + | 'customer.subscription.updated' + | 'customer.subscription.deleted' + export type CustomerSubscriptionWebhookEvent = { - type: 'customer.subscription.created' + type: StripeWebhookEventType data: { object: { id: string diff --git a/services/web/types/subscription/dashboard/subscription.ts b/services/web/types/subscription/dashboard/subscription.ts index 1272f33eb0..4b9c9de57c 100644 --- a/services/web/types/subscription/dashboard/subscription.ts +++ b/services/web/types/subscription/dashboard/subscription.ts @@ -111,3 +111,8 @@ export type PaymentProvider = { trialStartedAt?: Nullable trialEndsAt?: Nullable } + +export type SubscriptionRequesterData = { + id?: string + ip?: string +} From 0a0dc1303053529d456782e99f1ff7e002c82984 Mon Sep 17 00:00:00 2001 From: CloudBuild Date: Fri, 9 May 2025 01:07:31 +0000 Subject: [PATCH 061/194] auto update translation GitOrigin-RevId: 4dbc6d69264f37aa2532aab2a92db943f90e0947 --- services/web/locales/zh-CN.json | 5 ----- 1 file changed, 5 deletions(-) diff --git a/services/web/locales/zh-CN.json b/services/web/locales/zh-CN.json index 9d3e0befea..8b088d7cce 100644 --- a/services/web/locales/zh-CN.json +++ b/services/web/locales/zh-CN.json @@ -85,8 +85,6 @@ "add_email": "添加电子邮件", "add_email_address": "添加邮件地址", "add_email_to_claim_features": "添加一个机构电子邮件地址来声明您的功能。", - "add_error_assist_annual_to_your_projects": "将 Error Assist Annual 添加到您的项目中并获得无限的 AI 修复帮助,以更快地修复 LaTeX 错误。", - "add_error_assist_to_your_projects": "将错误辅助 添加到您的项目中并获得无限的 AI 帮助,以更快地修复 LaTeX 错误。", "add_files": "添加文件", "add_more_collaborators": "添加更多协作者", "add_more_licenses_to_my_plan": "为我的计划添加更多许可证", @@ -808,7 +806,6 @@ "generic_something_went_wrong": "抱歉,出错了", "get_collaborative_benefits": "从 __appName__ 获得协作优势,即使你喜欢离线工作", "get_discounted_plan": "获得折扣计划", - "get_error_assist": "获取错误帮助", "get_exclusive_access_to_labs": "加入 Overleaf Labs 后,即可获得早期实验的独家访问权。我们唯一的要求就是您提供真实的反馈,以帮助我们发展和改进。", "get_in_touch": "联系", "get_in_touch_having_problems": "如果遇到问题,请与支持部门联系", @@ -2565,8 +2562,6 @@ "what_does_this_mean_for_you": "这意味着:", "what_happens_when_sso_is_enabled": "开启单点登录后会发生什么?", "what_should_we_call_you": "我们该怎么称呼你?", - "whats_new": "有什么新功能?", - "whats_next": "接下来?", "when_you_join_labs": "加入实验室后,您可以选择要参与的实验。完成此操作后,您可以正常使用 Overleaf,但您会看到所有实验室功能都标有此徽章:", "when_you_tick_the_include_caption_box": "当您勾选“包含标题”框时,图像将带有占位符标题插入到文档中。 要编辑它,您只需选择占位符文本并键入以将其替换为您自己的文本。", "why_latex": "为何用 LaTeX?", From 9e07549ecbb5a40229e77060a19e3ab6e61a7816 Mon Sep 17 00:00:00 2001 From: Brian Gough Date: Fri, 9 May 2025 09:05:43 +0100 Subject: [PATCH 062/194] Merge pull request #25449 from overleaf/bg-histoy-redis-refactor-expire-worker refactor the expire worker to make it easier to extend GitOrigin-RevId: 7b277b243ed51ab3b46316d98b7157af95a9e42b --- services/history-v1/storage/lib/scan.js | 134 +++++++++++++++++- .../storage/scripts/expire_redis_chunks.js | 130 ++++------------- 2 files changed, 162 insertions(+), 102 deletions(-) diff --git a/services/history-v1/storage/lib/scan.js b/services/history-v1/storage/lib/scan.js index 45d0c327fe..925b0590c4 100644 --- a/services/history-v1/storage/lib/scan.js +++ b/services/history-v1/storage/lib/scan.js @@ -1,3 +1,5 @@ +const logger = require('@overleaf/logger') + const BATCH_SIZE = 1000 // Default batch size for SCAN /** @@ -49,4 +51,134 @@ function extractKeyId(key) { return null } -module.exports = { scanRedisCluster, extractKeyId } +/** + * Fetches timestamps for a list of project IDs based on a given key name. + * + * @param {string[]} projectIds - Array of project identifiers. + * @param {object} rclient - The Redis client instance. + * @param {string} keyName - The base name for the Redis keys storing the timestamps (e.g., "expire-time", "persist-time"). + * @param {number} currentTime - The current time (timestamp in milliseconds) to compare against. + * @returns {Promise>} + * A promise that resolves to an array of objects, each containing a projectId and + * its corresponding timestampValue, for due projects only. + */ +async function fetchOverdueProjects(projectIds, rclient, keyName, currentTime) { + if (!projectIds || projectIds.length === 0) { + return [] + } + const timestampKeys = projectIds.map(id => `${keyName}:{${id}}`) + const timestamps = await rclient.mget(timestampKeys) + + const dueProjects = [] + for (let i = 0; i < projectIds.length; i++) { + const projectId = projectIds[i] + const timestampValue = timestamps[i] + + if (timestampValue !== null) { + const timestamp = parseInt(timestampValue, 10) + if (!isNaN(timestamp) && currentTime > timestamp) { + dueProjects.push({ projectId, timestampValue }) + } + } + } + return dueProjects +} + +/** + * Scans Redis for keys matching a pattern derived from keyName, identifies items that are "due" based on a timestamp, + * and performs a specified action on them. + * + * @param {object} rclient - The Redis client instance. + * @param {string} taskName - A descriptive name for the task (used in logging). + * @param {string} keyName - The base name for the Redis keys (e.g., "expire-time", "persist-time"). + * The function will derive the key prefix as `${keyName}:` and scan pattern as `${keyName}:{*}`. + * @param {function(string): Promise} actionFn - An async function that takes a projectId and performs an action. + * @param {boolean} DRY_RUN - If true, logs actions that would be taken without performing them. + * @returns {Promise<{scannedKeyCount: number, processedKeyCount: number}>} Counts of scanned and processed keys. + */ +async function scanAndProcessDueItems( + rclient, + taskName, + keyName, + actionFn, + DRY_RUN +) { + let scannedKeyCount = 0 + let processedKeyCount = 0 + const START_TIME = Date.now() + const logContext = { taskName, dryRun: DRY_RUN } + + const scanPattern = `${keyName}:{*}` + + if (DRY_RUN) { + logger.info(logContext, `Starting ${taskName} scan in DRY RUN mode`) + } else { + logger.info(logContext, `Starting ${taskName} scan`) + } + + for await (const keysBatch of scanRedisCluster(rclient, scanPattern)) { + scannedKeyCount += keysBatch.length + const projectIds = keysBatch.map(extractKeyId).filter(id => id != null) + + if (projectIds.length === 0) { + continue + } + + const currentTime = Date.now() + const overdueProjects = await fetchOverdueProjects( + projectIds, + rclient, + keyName, + currentTime + ) + + for (const project of overdueProjects) { + const { projectId } = project + if (DRY_RUN) { + logger.info( + { ...logContext, projectId }, + `[Dry Run] Would perform ${taskName} for project` + ) + } else { + try { + await actionFn(projectId) + logger.info( + { ...logContext, projectId }, + `Successfully performed ${taskName} for project` + ) + } catch (err) { + logger.error( + { ...logContext, projectId, err }, + `Error performing ${taskName} for project` + ) + continue + } + } + processedKeyCount++ + + if (processedKeyCount % 1000 === 0 && processedKeyCount > 0) { + logger.info( + { ...logContext, scannedKeyCount, processedKeyCount }, + `${taskName} scan progress` + ) + } + } + } + + logger.info( + { + ...logContext, + scannedKeyCount, + processedKeyCount, + elapsedTimeInSeconds: Math.floor((Date.now() - START_TIME) / 1000), + }, + `${taskName} scan complete` + ) + return { scannedKeyCount, processedKeyCount } +} + +module.exports = { + scanRedisCluster, + extractKeyId, + scanAndProcessDueItems, +} diff --git a/services/history-v1/storage/scripts/expire_redis_chunks.js b/services/history-v1/storage/scripts/expire_redis_chunks.js index de4e130ed4..af2be097b6 100644 --- a/services/history-v1/storage/scripts/expire_redis_chunks.js +++ b/services/history-v1/storage/scripts/expire_redis_chunks.js @@ -1,11 +1,10 @@ const logger = require('@overleaf/logger') -const commandLineArgs = require('command-line-args') // Add this line +const commandLineArgs = require('command-line-args') const redis = require('../lib/redis') -const { scanRedisCluster, extractKeyId } = require('../lib/scan') +const { scanAndProcessDueItems } = require('../lib/scan') const { expireProject, claimExpireJob } = require('../lib/chunk_store/redis') const rclient = redis.rclientHistory -const EXPIRE_TIME_KEY_PATTERN = `expire-time:{*}` const optionDefinitions = [{ name: 'dry-run', alias: 'd', type: Boolean }] const options = commandLineArgs(optionDefinitions) @@ -13,112 +12,41 @@ const DRY_RUN = options['dry-run'] || false logger.initialize('expire-redis-chunks') -function isExpiredKey(expireTimestamp, currentTime) { - const expireTime = parseInt(expireTimestamp, 10) - if (isNaN(expireTime)) { - return false - } - logger.debug( - { - expireTime, - currentTime, - expireIn: expireTime - currentTime, - expired: currentTime > expireTime, - }, - 'Checking if key is expired' - ) - return currentTime > expireTime -} - -async function fetchTimestamps(projectIds, rclient) { - const expireTimeKeys = projectIds.map(id => `expire-time:{${id}}`) - // For efficiency, we use MGET to fetch all the timestamps in a single request - const expireTimestamps = await rclient.mget(expireTimeKeys) - // Return an array of objects with projectId and expireTimestamp - const results = projectIds.map((projectId, index) => ({ - projectId, - expireTimestamp: expireTimestamps[index], - })) - return results -} - -async function processKeysBatch(projectIds, rclient) { - let clearedKeyCount = 0 - if (projectIds.length === 0) { - return 0 - } - const projects = await fetchTimestamps(projectIds, rclient) - const currentTime = Date.now() - - for (const project of projects) { - const { projectId, expireTimestamp } = project - // For each key, do a quick check to see if the key is expired before calling - // the LUA script to expire the chunk atomically. - if (isExpiredKey(expireTimestamp, currentTime)) { - if (DRY_RUN) { - logger.info({ projectId }, '[Dry Run] Would expire chunk for project') - } else { - try { - const job = await claimExpireJob(projectId) - await expireProject(projectId) - await job.close() - } catch (err) { - logger.error({ projectId, err }, 'error expiring chunk for project') - continue - } - } - clearedKeyCount++ +async function expireProjectAction(projectId) { + const job = await claimExpireJob(projectId) + try { + await expireProject(projectId) + } finally { + if (job && job.close) { + await job.close() } } - return clearedKeyCount } -async function expireRedisChunks() { - let scannedKeyCount = 0 - let clearedKeyCount = 0 - const START_TIME = Date.now() - - if (DRY_RUN) { - logger.info({}, 'starting expireRedisChunks scan in DRY RUN mode') - } else { - logger.info({}, 'starting expireRedisChunks scan') - } - - for await (const keysBatch of scanRedisCluster( +async function runExpireChunks() { + await scanAndProcessDueItems( rclient, - EXPIRE_TIME_KEY_PATTERN - )) { - scannedKeyCount += keysBatch.length - clearedKeyCount += await processKeysBatch( - keysBatch.map(extractKeyId), - rclient - ) - if (scannedKeyCount % 1000 === 0) { - logger.info( - { scannedKeyCount, clearedKeyCount }, - 'expireRedisChunks scan progress' - ) - } - } - logger.info( - { - scannedKeyCount, - clearedKeyCount, - elapsedTimeInSeconds: Math.floor((Date.now() - START_TIME) / 1000), - dryRun: DRY_RUN, - }, - 'expireRedisChunks scan complete' + 'expireChunks', + 'expire-time', + expireProjectAction, + DRY_RUN ) - await redis.disconnect() } -// Check if the script is being run directly if (require.main === module) { - expireRedisChunks().catch(err => { - logger.fatal({ err }, 'unhandled error in expireRedisChunks') - process.exit(1) - }) + runExpireChunks() + .catch(err => { + logger.fatal( + { err, taskName: 'expireChunks' }, + 'Unhandled error in runExpireChunks' + ) + process.exit(1) + }) + .finally(async () => { + await redis.disconnect() + }) } else { - // Export the function for module usage - module.exports = { expireRedisChunks } + module.exports = { + runExpireChunks, + } } From fba8f776a1558acab3868a382718651d588f880a Mon Sep 17 00:00:00 2001 From: Jakob Ackermann Date: Fri, 9 May 2025 10:13:33 +0200 Subject: [PATCH 063/194] [web] avoid trying to fetch synctex.gz from clsi-cache in free projects (#25445) * [web] avoid trying to fetch synctex.gz from clsi-cache in free projects * [clsi] parse boolean query parameter GitOrigin-RevId: 99c98aac8147a626b704e9a888b7fc660cc5ab17 --- services/clsi/app/js/CompileController.js | 6 ++++-- services/web/app/src/Features/Compile/CompileController.js | 6 ++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/services/clsi/app/js/CompileController.js b/services/clsi/app/js/CompileController.js index e102b19dad..4c411a54cf 100644 --- a/services/clsi/app/js/CompileController.js +++ b/services/clsi/app/js/CompileController.js @@ -191,7 +191,8 @@ function clearCache(req, res, next) { } function syncFromCode(req, res, next) { - const { file, editorId, buildId, compileFromClsiCache } = req.query + const { file, editorId, buildId } = req.query + const compileFromClsiCache = req.query.compileFromClsiCache === 'true' const line = parseInt(req.query.line, 10) const column = parseInt(req.query.column, 10) const { imageName } = req.query @@ -220,7 +221,8 @@ function syncFromPdf(req, res, next) { const page = parseInt(req.query.page, 10) const h = parseFloat(req.query.h) const v = parseFloat(req.query.v) - const { imageName, editorId, buildId, compileFromClsiCache } = req.query + const { imageName, editorId, buildId } = req.query + const compileFromClsiCache = req.query.compileFromClsiCache === 'true' const projectId = req.params.project_id const userId = req.params.user_id CompileManager.syncFromPdf( diff --git a/services/web/app/src/Features/Compile/CompileController.js b/services/web/app/src/Features/Compile/CompileController.js index 34d92a4e59..25f8190374 100644 --- a/services/web/app/src/Features/Compile/CompileController.js +++ b/services/web/app/src/Features/Compile/CompileController.js @@ -553,6 +553,12 @@ const _CompileController = { async _proxyToClsi(projectId, action, url, qs, req, res) { const limits = await CompileManager.promises.getProjectCompileLimits(projectId) + if ( + qs?.compileFromClsiCache && + !['alpha', 'priority'].includes(limits.compileGroup) + ) { + qs.compileFromClsiCache = false + } return CompileController._proxyToClsiWithLimits( projectId, action, From bc4550c1f9981b34712f2da8c184f81ef2f3a4c1 Mon Sep 17 00:00:00 2001 From: Jakob Ackermann Date: Fri, 9 May 2025 11:06:49 +0200 Subject: [PATCH 064/194] [CI] temporarily disable flaky tests (#25443) GitOrigin-RevId: 4ed83e7b79d7aee0d7fab4594d4f7c8697e0cab4 --- server-ce/test/project-sharing.spec.ts | 25 +++++++++++-------- server-ce/test/sandboxed-compiles.spec.ts | 4 ++- server-ce/test/templates.spec.ts | 4 ++- .../table/project-list-table.test.tsx | 4 ++- 4 files changed, 23 insertions(+), 14 deletions(-) diff --git a/server-ce/test/project-sharing.spec.ts b/server-ce/test/project-sharing.spec.ts index e26439264b..ab71cb7dc9 100644 --- a/server-ce/test/project-sharing.spec.ts +++ b/server-ce/test/project-sharing.spec.ts @@ -36,13 +36,14 @@ describe('Project Sharing', function () { login('user@example.com') createProject(projectName) - // Add chat message - cy.findByText('Chat').click() - // wait for lazy loading of the chat pane - cy.findByText('Send your first message to your collaborators') - cy.get( - 'textarea[placeholder="Send a message to your collaborators…"]' - ).type('New Chat Message{enter}') + // TODO(25342): re-enable + // // Add chat message + // cy.findByText('Chat').click() + // // wait for lazy loading of the chat pane + // cy.findByText('Send your first message to your collaborators') + // cy.get( + // 'textarea[placeholder="Send a message to your collaborators…"]' + // ).type('New Chat Message{enter}') // Get link sharing links enableLinkSharing().then( @@ -94,8 +95,9 @@ describe('Project Sharing', function () { } function expectChatAccess() { - cy.findByText('Chat').click() - cy.findByText('New Chat Message') + // TODO(25342): re-enable + // cy.findByText('Chat').click() + // cy.findByText('New Chat Message') } function expectHistoryAccess() { @@ -109,8 +111,9 @@ describe('Project Sharing', function () { } function expectNoChatAccess() { - cy.findByText('Layout') // wait for lazy loading - cy.findByText('Chat').should('not.exist') + // TODO(25342): re-enable + // cy.findByText('Layout') // wait for lazy loading + // cy.findByText('Chat').should('not.exist') } function expectNoHistoryAccess() { diff --git a/server-ce/test/sandboxed-compiles.spec.ts b/server-ce/test/sandboxed-compiles.spec.ts index f39a00161b..c84af1897b 100644 --- a/server-ce/test/sandboxed-compiles.spec.ts +++ b/server-ce/test/sandboxed-compiles.spec.ts @@ -59,7 +59,9 @@ describe('SandboxedCompiles', function () { }) function checkSyncTeX() { - describe('SyncTeX', function () { + // TODO(25342): re-enable + // eslint-disable-next-line mocha/no-skipped-tests + describe.skip('SyncTeX', function () { let projectName: string beforeEach(function () { projectName = `Project ${uuid()}` diff --git a/server-ce/test/templates.spec.ts b/server-ce/test/templates.spec.ts index e36e99315d..4959e149fc 100644 --- a/server-ce/test/templates.spec.ts +++ b/server-ce/test/templates.spec.ts @@ -47,7 +47,9 @@ describe('Templates', () => { cy.url().should('match', /\/templates$/) }) - it('should have templates feature', () => { + // TODO(25342): re-enable + // eslint-disable-next-line mocha/no-skipped-tests + it.skip('should have templates feature', () => { login(TEMPLATES_USER) const name = `Template ${Date.now()}` const description = `Template Description ${Date.now()}` diff --git a/services/web/test/frontend/features/project-list/components/table/project-list-table.test.tsx b/services/web/test/frontend/features/project-list/components/table/project-list-table.test.tsx index f8f9143c16..e870478076 100644 --- a/services/web/test/frontend/features/project-list/components/table/project-list-table.test.tsx +++ b/services/web/test/frontend/features/project-list/components/table/project-list-table.test.tsx @@ -7,7 +7,9 @@ import { renderWithProjectListContext } from '../../helpers/render-with-context' const userId = '624333f147cfd8002622a1d3' -describe('', function () { +// TODO(25331): re-enable +// eslint-disable-next-line mocha/no-skipped-tests +describe.skip('', function () { beforeEach(function () { window.metaAttributesCache.set('ol-tags', []) window.metaAttributesCache.set('ol-user_id', userId) From 9762cf95e3b89ceb19d012ecb17111405eebeb20 Mon Sep 17 00:00:00 2001 From: Brian Gough Date: Fri, 9 May 2025 10:45:18 +0100 Subject: [PATCH 065/194] Merge pull request #25463 from overleaf/bg-history-redis-fix-logging-in-expire-worker reduce expire_redis_chunks log verbosity in production GitOrigin-RevId: afcf6edc7154d49714bc60c276c129d70eaa49c7 --- services/history-v1/storage/lib/scan.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/history-v1/storage/lib/scan.js b/services/history-v1/storage/lib/scan.js index 925b0590c4..fe4b8d514e 100644 --- a/services/history-v1/storage/lib/scan.js +++ b/services/history-v1/storage/lib/scan.js @@ -142,7 +142,7 @@ async function scanAndProcessDueItems( } else { try { await actionFn(projectId) - logger.info( + logger.debug( { ...logContext, projectId }, `Successfully performed ${taskName} for project` ) From 3c3414a7d3a7a6563f50a87793d43855af8523f9 Mon Sep 17 00:00:00 2001 From: Mathias Jakobsen Date: Fri, 9 May 2025 13:14:53 +0100 Subject: [PATCH 066/194] Merge pull request #25435 from overleaf/mj-review-panel-tests [web] Add review panel cypress tests GitOrigin-RevId: e953519fc8fed089df59970ee3c745b06d78ddfb --- .../components/review-panel-current-file.tsx | 8 +- .../components/review-panel-header.tsx | 3 +- .../components/review-panel-overview.tsx | 6 +- .../review-panel-resolved-threads-button.tsx | 1 + .../components/review-panel-tabs.tsx | 8 + .../components/review-panel.tsx | 8 +- .../components/review-tooltip-menu.tsx | 2 + .../review-panel/review-panel.spec.tsx | 740 ++++++++++++++---- .../source-editor/helpers/mock-doc.ts | 8 +- .../source-editor/helpers/mock-scope.ts | 15 +- 10 files changed, 626 insertions(+), 173 deletions(-) diff --git a/services/web/frontend/js/features/review-panel-new/components/review-panel-current-file.tsx b/services/web/frontend/js/features/review-panel-new/components/review-panel-current-file.tsx index db9933d66b..5f65e9fbb0 100644 --- a/services/web/frontend/js/features/review-panel-new/components/review-panel-current-file.tsx +++ b/services/web/frontend/js/features/review-panel-new/components/review-panel-current-file.tsx @@ -295,7 +295,11 @@ const ReviewPanelCurrentFile: FC = () => { } return ( - <> +
{showEmptyState && } {onMoreCommentsAboveClick && ( { direction="downward" /> )} - +
) } diff --git a/services/web/frontend/js/features/review-panel-new/components/review-panel-header.tsx b/services/web/frontend/js/features/review-panel-new/components/review-panel-header.tsx index 258922479c..950af503c1 100644 --- a/services/web/frontend/js/features/review-panel-new/components/review-panel-header.tsx +++ b/services/web/frontend/js/features/review-panel-new/components/review-panel-header.tsx @@ -7,9 +7,8 @@ import getMeta from '@/utils/meta' import { PanelHeading } from '@/shared/components/panel-heading' import useReviewPanelLayout from '../hooks/use-review-panel-layout' -const isReviewerRoleEnabled = getMeta('ol-isReviewerRoleEnabled') - const ReviewPanelHeader: FC = () => { + const isReviewerRoleEnabled = getMeta('ol-isReviewerRoleEnabled') const [trackChangesMenuExpanded, setTrackChangesMenuExpanded] = useState(false) const { closeReviewPanel } = useReviewPanelLayout() diff --git a/services/web/frontend/js/features/review-panel-new/components/review-panel-overview.tsx b/services/web/frontend/js/features/review-panel-new/components/review-panel-overview.tsx index 575419677d..0f2a9ea49a 100644 --- a/services/web/frontend/js/features/review-panel-new/components/review-panel-overview.tsx +++ b/services/web/frontend/js/features/review-panel-new/components/review-panel-overview.tsx @@ -48,7 +48,11 @@ export const ReviewPanelOverview: FC = () => { }, [rangesForDocs]) return ( -
+
{error &&
{t('something_went_wrong')}
} {showEmptyState && } diff --git a/services/web/frontend/js/features/review-panel-new/components/review-panel-resolved-threads-button.tsx b/services/web/frontend/js/features/review-panel-new/components/review-panel-resolved-threads-button.tsx index b44f39c16e..6f05a555fa 100644 --- a/services/web/frontend/js/features/review-panel-new/components/review-panel-resolved-threads-button.tsx +++ b/services/web/frontend/js/features/review-panel-new/components/review-panel-resolved-threads-button.tsx @@ -27,6 +27,7 @@ export const ReviewPanelResolvedThreadsButton: FC = () => { } ref={buttonRef} onClick={() => setExpanded(true)} + aria-label={t('resolved_comments')} > diff --git a/services/web/frontend/js/features/review-panel-new/components/review-panel-tabs.tsx b/services/web/frontend/js/features/review-panel-new/components/review-panel-tabs.tsx index b7a1709b4d..dff291e573 100644 --- a/services/web/frontend/js/features/review-panel-new/components/review-panel-tabs.tsx +++ b/services/web/frontend/js/features/review-panel-new/components/review-panel-tabs.tsx @@ -16,6 +16,10 @@ const ReviewPanelTabs: FC = () => { return ( <> @@ -256,6 +257,7 @@ const ReviewTooltipMenuContent: FC<{ onAddComment: () => void }> = ({ 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 f9ed6e38b6..7971ff01d2 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 @@ -1,21 +1,188 @@ import CodeMirrorEditor from '../../../../frontend/js/features/source-editor/components/codemirror-editor' -import { EditorProviders } from '../../helpers/editor-providers' +import { + EditorProviders, + USER_EMAIL, + USER_ID, +} from '../../helpers/editor-providers' import { mockScope } from '../source-editor/helpers/mock-scope' import { TestContainer } from '../source-editor/helpers/test-container' +import { docId } from '../source-editor/helpers/mock-doc' -// TODO: update tests and re-enable once reviewer role is active -// eslint-disable-next-line mocha/no-skipped-tests -describe.skip('', function () { +describe('', function () { beforeEach(function () { + window.metaAttributesCache.set('ol-isReviewerRoleEnabled', true) window.metaAttributesCache.set('ol-preventCompileOnLoad', true) cy.interceptEvents() - const scope = mockScope('') - scope.editor.showVisual = true + cy.intercept('GET', '/project/*/changes/users', [ + { + id: USER_ID, + email: USER_EMAIL, + first_name: 'Test', + last_name: 'User', + }, + ]) - // The tests expect no documents, so remove them from the scope - scope.project.rootFolder = [] + const userData = { + avatar_text: 'User', + email: USER_EMAIL, + hue: 180, + id: USER_ID, + isSelf: true, + first_name: 'Test', + last_name: 'User', + } + + const resolvedThreadId = 'resolved-thread-id' + const unresolvedThreadId = 'unresolved-thread-id' + + cy.intercept('GET', '/project/*/threads', { + // Resolved comment thread + [resolvedThreadId]: { + messages: [ + { + content: 'comment text', + id: `${resolvedThreadId}-1`, + timestamp: new Date('2025-01-01T00:00:00.000Z'), + user: userData, + user_id: USER_ID, + }, + ], + resolved: true, + resolved_at: new Date('2025-01-02T00:00:00.000Z').toISOString(), + resolved_by_user_id: USER_ID, + resolved_by_user: userData, + }, + // Unresolved comment thread + [unresolvedThreadId]: { + messages: [ + { + content: 'unresolved comment text', + id: `${unresolvedThreadId}-1`, + timestamp: new Date('2025-01-01T00:00:00.000Z'), + user: userData, + user_id: USER_ID, + }, + { + content: 'reply to thread', + id: `${unresolvedThreadId}-2`, + timestamp: new Date('2025-01-01T01:00:00.000Z'), + user: userData, + user_id: USER_ID, + }, + ], + }, + }) + + const commentOps = [ + { + id: 'resolved-op-id', + op: { p: 161, c: 'Your introduction', t: resolvedThreadId }, + }, + { + id: 'unresolved-op-id', + op: { p: 210, c: 'Your results', t: unresolvedThreadId }, + }, + ] + + const changesOps = [ + { + metadata: { + user_id: USER_ID, + ts: new Date('2025-01-01T00:00:00.000Z'), + }, + id: 'inserted-op-id', + op: { p: 166, t: 'inserted-op-id', i: 'introduction' }, + }, + { + metadata: { + user_id: USER_ID, + ts: new Date('2025-01-01T01:00:00.000Z'), + }, + id: 'deleted-op-id', + op: { p: 110, t: 'deleted-op-id', d: 'beautiful ' }, + }, + ] + + cy.intercept('GET', '/project/*/ranges', [ + { + id: docId, + ranges: { + changes: changesOps, + comments: commentOps, + docId, + }, + }, + ]) + + cy.intercept( + 'POST', + `/project/*/doc/${docId}/thread/${resolvedThreadId}/reopen`, + {} + ).as('reopenThread') + + cy.intercept( + 'POST', + `/project/*/doc/${docId}/thread/${unresolvedThreadId}/resolve`, + {} + ).as('resolveThreadId') + + cy.intercept( + 'POST', + `/project/*/thread/${unresolvedThreadId}/messages/${unresolvedThreadId}-1/edit`, + {} + ).as('editComment') + + cy.intercept( + 'POST', + `/project/*/thread/${unresolvedThreadId}/messages`, + {} + ).as('addReply') + + cy.intercept( + 'POST', + /\/project\/.*\/thread\/[a-z0-9]{24}\/messages/, + {} + ).as('addNewComment') + + cy.intercept( + 'DELETE', + `/project/*/doc/${docId}/thread/${resolvedThreadId}`, + {} + ).as('deleteResolvedThread') + + cy.intercept( + 'DELETE', + `/project/*/thread/${unresolvedThreadId}/messages/${unresolvedThreadId}-2`, + {} + ).as('deleteComment') + + cy.intercept( + 'DELETE', + `/project/*/doc/${docId}/thread/${unresolvedThreadId}`, + {} + ).as('deleteThread') + + cy.intercept('POST', `/project/*/doc/${docId}/changes/accept`, {}).as( + 'acceptChange' + ) + + cy.intercept('POST', `/project/*/doc/${docId}/metadata`, {}) + + const getChanges = cy.stub().as('getChanges').returns([]) + const removeChangeIds = cy.stub().as('removeChangeIds') + + const scope = mockScope(undefined, { + docOptions: { + rangesOptions: { + comments: commentOps, + changes: changesOps, + getChanges, + removeChangeIds, + }, + }, + }) cy.wrap(scope).as('scope') @@ -27,107 +194,80 @@ describe.skip('', function () { ) + // Open the review panel with keyboard shortcut + cy.findByText('contentLine 0').type('{command}j', { scrollBehavior: false }) + cy.findByText('contentLine 1').type('{ctrl}j', { scrollBehavior: false }) + cy.findByTestId('review-panel').as('review-panel') }) describe('toolbar', function () { describe('resolved comments dropdown', function () { - it('renders dropdown button', function () { - cy.findByRole('button', { name: /resolved comments/i }) - }) - - // eslint-disable-next-line mocha/no-skipped-tests - it.skip('opens dropdown', function () { - cy.findByRole('button', { name: /resolved comments/i }).click() - // TODO dropdown opens/closes - }) - - // eslint-disable-next-line mocha/no-skipped-tests - it.skip('renders list of resolved comments', function () {}) - - // eslint-disable-next-line mocha/no-skipped-tests - it.skip('reopens resolved comment', function () {}) - - // eslint-disable-next-line mocha/no-skipped-tests - it.skip('deletes resolved comment', function () {}) - }) - - describe('track changes toggle menu', function () { - it('renders track changes toolbar', function () { - cy.get('@review-panel').within(() => { - cy.findByRole('button', { name: /track changes is (on|off)$/i }) - }) - }) - - it('opens/closes toggle menu', function () { - cy.get('@review-panel').within(() => { - cy.findByTestId('review-panel-track-changes-menu').should('not.exist') - cy.findByRole('button', { name: /track changes is/i }).click() - // verify the menu is expanded - cy.findByTestId('review-panel-track-changes-menu') - .as('menu') - .then($el => { - const height = window - .getComputedStyle($el[0]) - .getPropertyValue('height') - return parseFloat(height) - }) - .should('be.gt', 1) - cy.findByRole('button', { name: /track changes is/i }).click() - cy.get('@menu').should('not.exist') - }) - }) - - it('toggles the "everyone" track changes switch', function () { - cy.get('@review-panel').within(() => { - cy.findByRole('button', { name: /track changes is off/i }).click() - cy.findByLabelText(/track changes for everyone/i).click({ - force: true, + it('renders a dropdown of resolved comments', function () { + // The dropdown button should be visible + cy.findByLabelText('Resolved comments').click() + // It should open the dropdown + cy.findByRole('tooltip') + .should('exist') + .within(() => { + // TODO: Fix selector + cy.get( + '.review-panel-resolved-comments-header .badge-content' + ).should('contain.text', '1') + // Should name the document with the comment + cy.findByText('test.tex').should('exist') + // Should show the comment text + cy.findByText('comment text').should('exist') + // Should show the author name + // TODO: Fix selector + cy.get('.review-panel-entry-user').should( + 'contain.text', + 'Test User' + ) }) - cy.findByLabelText(/track changes for everyone/i).should('be.checked') - // TODO: assert that track changes is on for everyone + }) + + it('reopens resolved comment', function () { + cy.findByLabelText('Resolved comments').click() + cy.findByRole('tooltip').within(() => { + // Find the re-open icon button using the hidden label + cy.findByText('Re-open').click({ force: true }) + // verify the reopen thread API call + cy.wait('@reopenThread') + + // TODO: Figure out a way to plumb the websocket response back through + // to the test so we can verify the comment is no longer resolved + // cy.get( + // '.review-panel-resolved-comments-header .badge-content' + // ).should('contain.text', '0') }) }) - // eslint-disable-next-line mocha/no-skipped-tests - it.skip('renders track changes with "on" state', function () { - const scope = mockScope('') - scope.editor.showVisual = true - scope.editor.wantTrackChanges = true + it('deletes resolved comment', function () { + cy.findByLabelText('Resolved comments').click() + cy.findByRole('tooltip').within(() => { + // Find the Delete icon button using the hidden label + cy.findByText('Delete').click({ force: true }) + // verify the delete thread API call + cy.wait('@deleteResolvedThread') - cy.mount( - - - - - - ) - - cy.findByTestId('review-panel').within(() => { - cy.findByRole('button', { name: /track changes is on/i }).click() + // TODO: Figure out a way to plumb the websocket response back through + // to the test so we can verify the comment is no longer there + // cy.get( + // '.review-panel-resolved-comments-header .badge-content' + // ).should('contain.text', '0') }) }) - - it('renders a disabled guests switch', function () { - cy.findByRole('button', { name: /track changes is off/i }).click() - cy.findByLabelText(/track changes for guests/i).should('be.disabled') - }) }) }) describe('toggler', function () { - it('renders toggler button', function () { + it('should close panel when pressing close button', function () { cy.get('@review-panel').within(() => { - cy.findByRole('button', { name: /toggle review panel/i }) - }) - }) - - // eslint-disable-next-line mocha/no-skipped-tests - it.skip('calls the toggler function on click', function () { - cy.get('@review-panel').within(() => { - cy.findByRole('button', { name: /toggle review panel/i }).click() - cy.get('@scope').its('toggleReviewPanel').should('be.calledOnce') + cy.findByLabelText('Close').click({ scrollBehavior: false }) }) + // We should collapse to the mini state + cy.get('.review-panel-mini').should('exist') }) }) @@ -167,46 +307,177 @@ describe.skip('', function () { }) describe('comment entries', function () { - // eslint-disable-next-line mocha/no-skipped-tests - it.skip('shows threads and comments', function () {}) + it('shows threads and comments', function () { + cy.get('@review-panel').within(() => { + cy.findByText('unresolved comment text').should('exist') + cy.findByText('reply to thread').should('exist') + }) + }) - // eslint-disable-next-line mocha/no-skipped-tests - it.skip('edits comment', function () {}) + it('edits comment', function () { + cy.get('@review-panel').within(() => { + // TODO: Fix selector + cy.get('.review-panel-comment-wrapper') + .first() + .within(() => { + // Find the options icon button using the hidden label + cy.findByText('More options') + .first() + .click({ force: true, scrollBehavior: false }) + cy.findByRole('menu').within(() => { + cy.findByText('Edit').click({ scrollBehavior: false }) + }) + cy.findByRole('textbox').type( + '{selectAll}edited comment text{enter}', + { scrollBehavior: false } + ) + cy.wait('@editComment') + // TODO: Figure out a way to plumb the websocket response back through + // to the test so we can verify the comment is resolved + // cy.findByText('edited comment text').should('exist') + }) + }) + }) - // eslint-disable-next-line mocha/no-skipped-tests - it.skip('deletes comment', function () {}) + it('deletes thread', function () { + cy.get('@review-panel').within(() => { + // TODO: Fix selector + cy.get('.review-panel-comment-wrapper') + .first() + .within(() => { + // Find the options icon button using the hidden label + cy.findByText('More options') + .first() + .click({ force: true, scrollBehavior: false }) + cy.findByRole('menu').within(() => { + cy.findByText('Delete').click({ scrollBehavior: false }) + }) + }) + }) + cy.findByRole('dialog').within(() => { + cy.findByRole('button', { name: 'Delete' }).click() + }) + cy.wait('@deleteThread') + // TODO: Figure out a way to plumb the websocket response back through + // to the test so we can verify the thread is deleted + // cy.findByText('unresolved comment text').should('not.exist') + }) - // eslint-disable-next-line mocha/no-skipped-tests - it.skip('cancels comment editing', function () {}) + it('deletes reply', function () { + cy.get('@review-panel').within(() => { + // TODO: Fix selector + cy.get('.review-panel-comment-wrapper') + .eq(1) + .within(() => { + // Find the options icon button using the hidden label + cy.findByText('More options') + .first() + .click({ force: true, scrollBehavior: false }) + cy.findByRole('menu').within(() => { + cy.findByText('Delete').click({ scrollBehavior: false }) + }) + }) + }) + cy.findByRole('dialog').within(() => { + cy.findByRole('button', { name: 'Delete' }).click() + }) + cy.wait('@deleteComment') + // TODO: Figure out a way to plumb the websocket response back through + // to the test so we can verify the reply is deleted + // cy.findByText('reply to thread').should('not.exist') + }) - // eslint-disable-next-line mocha/no-skipped-tests - it.skip('cancels comment deletion', function () {}) + it('cancels comment deletion', function () { + cy.get('@review-panel').within(() => { + // TODO: Fix selector + cy.get('.review-panel-comment-wrapper') + .eq(1) + .within(() => { + // Find the options icon button using the hidden label + cy.findByText('More options') + .first() + .click({ force: true, scrollBehavior: false }) + cy.findByRole('menu').within(() => { + cy.findByText('Delete').click({ scrollBehavior: false }) + }) + }) + }) + cy.findByRole('dialog').within(() => { + cy.findByRole('button', { name: 'Cancel' }).click() + }) + cy.findByText('unresolved comment text').should('exist') + }) - // eslint-disable-next-line mocha/no-skipped-tests - it.skip('adds new comment (replies) to a thread', function () {}) + it('adds new comment (replies) to a thread', function () { + cy.get('@review-panel').within(() => { + cy.findByRole('textbox').type('a new reply{enter}', { + scrollBehavior: false, + }) + }) + cy.wait('@addReply') + }) - // eslint-disable-next-line mocha/no-skipped-tests - it.skip('resolves comment', function () {}) + it('resolves comment', function () { + cy.get('@review-panel').within(() => { + // Find the resolve icon button using the hidden label + cy.findByText('Resolve comment').click({ force: true }) + cy.wait('@resolveThreadId') + // TODO: Figure out a way to plumb the websocket response back through + // to the test so we can verify the comment is resolved + // cy.findByText('unresolved comment text').should('not.exist') + }) + }) }) describe('change entries', function () { - // eslint-disable-next-line mocha/no-skipped-tests - it.skip('renders inserted entries in current file mode', function () {}) + it('renders inserted entries in current file mode', function () { + cy.get('@review-panel').within(() => { + cy.findByText('Added:').should('exist') + cy.findByText('introduction').should('exist') + }) + }) - // eslint-disable-next-line mocha/no-skipped-tests - it.skip('renders deleted entries in current file mode', function () {}) + it('renders deleted entries in current file mode', function () { + cy.get('@review-panel').within(() => { + cy.findByText('Deleted:').should('exist') + cy.findByText('beautiful').should('exist') + }) + }) - // eslint-disable-next-line mocha/no-skipped-tests - it.skip('renders inserted entries in overview mode', function () {}) + it('accepts change', function () { + cy.get('@review-panel').within(() => { + // TODO: Fix selector + cy.get('.review-panel-entry-insert').within(() => { + // Find the accept icon button using the hidden label + cy.findByText('Accept change').click({ force: true }) + cy.wait('@acceptChange') + }) - // eslint-disable-next-line mocha/no-skipped-tests - it.skip('renders deleted entries in overview mode', function () {}) + // TODO: Fix selector + cy.get('.review-panel-entry-delete').within(() => { + // Find the accept icon button using the hidden label + cy.findByText('Accept change').click({ force: true }) + cy.wait('@acceptChange') + }) + }) + }) - // eslint-disable-next-line mocha/no-skipped-tests - it.skip('accepts change', function () {}) - - // eslint-disable-next-line mocha/no-skipped-tests - it.skip('rejects change', function () {}) + it('rejects change', function () { + cy.get('@review-panel').within(() => { + // TODO: Fix selector + cy.get('.review-panel-entry-insert').within(() => { + // Find the reject icon button using the hidden label + cy.findByText('Reject change').click({ force: true }) + cy.get('@getChanges').should('be.calledOnce') + }) + // TODO: Fix selector + cy.get('.review-panel-entry-delete').within(() => { + // Find the reject icon button using the hidden label + cy.findByText('Reject change').click({ force: true }) + cy.get('@getChanges').should('be.calledTwice') + }) + }) + }) }) describe('aggregate change entries', function () { @@ -218,63 +489,206 @@ describe.skip('', function () { }) describe('add comment entry', function () { - // eslint-disable-next-line mocha/no-skipped-tests - it.skip('renders `add comment button`', function () {}) + beforeEach(function () { + cy.findByText('contentLine 12').type( + '{home}{shift}' + '{rightArrow}'.repeat(6), + { scrollBehavior: false } + ) + // TODO: Fix selector + cy.get('.review-tooltip-add-comment-button').as('add-comment-button') + }) - // eslint-disable-next-line mocha/no-skipped-tests - it.skip('cancels adding comment', function () {}) + it('renders floating `add comment button`', function () { + cy.get('@add-comment-button').should('exist') + }) - // eslint-disable-next-line mocha/no-skipped-tests - it.skip('adds comment', function () {}) + it('can add comment', function () { + cy.get('@add-comment-button').click({ scrollBehavior: false }) + cy.get('@review-panel').within(() => { + // TODO: Fix selector + cy.get('.review-panel-add-comment-textarea').type( + 'a new comment{enter}', + { + scrollBehavior: false, + } + ) + }) + cy.wait('@addNewComment') + // TODO : Figure out a way to plumb the websocket response back through + // to the test so we can verify the comment is added + // cy.findByText('a new comment').should('exist') + }) + + it('cancels adding comment', function () { + cy.get('@add-comment-button').click({ scrollBehavior: false }) + cy.get('@review-panel').within(() => { + cy.findByRole('button', { name: 'Cancel' }).click({ + scrollBehavior: false, + }) + }) + }) }) describe('bulk actions entry', function () { - // eslint-disable-next-line mocha/no-skipped-tests - it.skip('renders the reject and accept all buttons`', function () {}) + beforeEach(function () { + // Select a deletion and an insertion + cy.findByText('\\maketitle').type( + '{home}{shift}' + '{downArrow}'.repeat(10), + { scrollBehavior: false } + ) + cy.findByLabelText('Accept selected changes').as( + 'accept-selected-changes' + ) + cy.findByLabelText('Reject selected changes').as( + 'reject-selected-changes' + ) + }) - // eslint-disable-next-line mocha/no-skipped-tests - it.skip('accepts all changes', function () {}) + it('renders the reject and accept all buttons`', function () { + cy.get('@accept-selected-changes').should('exist') + cy.get('@reject-selected-changes').should('exist') + }) - // eslint-disable-next-line mocha/no-skipped-tests - it.skip('rejects all changes', function () {}) + it('accepts all changes', function () { + cy.get('@accept-selected-changes').click({ scrollBehavior: false }) + cy.findByRole('dialog').within(() => { + cy.findByText( + 'Are you sure you want to accept the selected 2 changes?' + ).should('exist') + cy.findByRole('button', { name: 'OK' }).click({ + scrollBehavior: false, + }) + cy.wait('@acceptChange') + cy.get('@removeChangeIds').should('have.been.calledWith', [ + 'inserted-op-id', + 'deleted-op-id', + ]) + }) + }) + + it('rejects all changes', function () { + cy.get('@reject-selected-changes').click({ scrollBehavior: false }) + cy.findByRole('dialog').within(() => { + cy.findByText( + 'Are you sure you want to reject the selected 2 changes?' + ).should('exist') + cy.findByRole('button', { name: 'OK' }).click({ + scrollBehavior: false, + }) + cy.get('@getChanges').should('have.been.calledWith', [ + 'inserted-op-id', + 'deleted-op-id', + ]) + }) + }) }) describe('overview mode', function () { - // eslint-disable-next-line mocha/no-skipped-tests - it.skip('shows list of files changed', function () {}) + beforeEach(function () { + cy.findByRole('tab', { name: /overview/i }).click() + }) + it('shows list of files changed', function () { + // TODO: Fix selector + cy.get('.collapsible-file-header').should('contain.text', 'test.tex') + }) - // eslint-disable-next-line mocha/no-skipped-tests - it.skip('renders comments', function () {}) - }) + it('renders comments', function () { + cy.get('@review-panel').within(() => { + cy.findByText('unresolved comment text').should('exist') + cy.findByText('reply to thread').should('exist') + }) + }) - describe('in editor widgets', function () { - // eslint-disable-next-line mocha/no-skipped-tests - it.skip('toggle review panel', function () {}) + it('renders changes', function () { + cy.get('@review-panel').within(() => { + cy.findByText('Added:').should('exist') + cy.findByText('introduction').should('exist') + cy.findByText('Deleted:').should('exist') + cy.findByText('beautiful').should('exist') + }) + }) - // eslint-disable-next-line mocha/no-skipped-tests - it.skip('accepts all changes', function () {}) - - // eslint-disable-next-line mocha/no-skipped-tests - it.skip('rejects all changes', function () {}) - - // eslint-disable-next-line mocha/no-skipped-tests - it.skip('add comment', function () {}) - }) - - describe('upgrade track changes', function () { - // eslint-disable-next-line mocha/no-skipped-tests - it.skip('renders modal', function () {}) - - // eslint-disable-next-line mocha/no-skipped-tests - it.skip('closes modal', function () {}) - - // eslint-disable-next-line mocha/no-skipped-tests - it.skip('opens subscription page after clicking on `upgrade`', function () {}) - - // eslint-disable-next-line mocha/no-skipped-tests - it.skip('opens subscription page after clicking on `try it for free`', function () {}) - - // eslint-disable-next-line mocha/no-skipped-tests - it.skip('shows `ask project owner to upgrade` message', function () {}) + it('collapses the file entries when clicked', function () { + cy.findByText('test.tex').click() + cy.get('@review-panel').within(() => { + // TODO: Fix selector + cy.get('.review-panel-entry').should('not.exist') + }) + cy.findByText('test.tex').click() + cy.get('@review-panel').within(() => { + // TODO: Fix selector + cy.get('.review-panel-entry').should('exist') + }) + }) + }) +}) + +describe(' for free users', function () { + function mountEditor(ownerId = USER_ID) { + const scope = mockScope(undefined, { + permissions: { write: true, trackedWrite: false, comment: true }, + projectFeatures: { trackChanges: false }, + projectOwner: { + _id: ownerId, + }, + }) + + cy.wrap(scope).as('scope') + + cy.mount( + + + + + + ) + + cy.findByLabelText('Editing').click() + cy.findByRole('menu').within(() => { + cy.findByText(/Reviewing/).click() + }) + } + + beforeEach(function () { + window.metaAttributesCache.set('ol-isReviewerRoleEnabled', true) + window.metaAttributesCache.set('ol-preventCompileOnLoad', true) + cy.interceptEvents() + cy.intercept('GET', '/project/*/changes/users', []) + cy.intercept('GET', '/project/*/threads', {}) + }) + + it('renders modal', function () { + mountEditor() + cy.findByRole('dialog').within(() => { + cy.findByText('Upgrade to Review').should('exist') + }) + }) + + it('closes modal', function () { + mountEditor() + cy.findByRole('dialog').within(() => { + cy.findByText('Close').click() + }) + cy.findByRole('dialog').should('not.exist') + }) + + it('opens subscription page after clicking on `upgrade`', function () { + mountEditor() + cy.findByRole('dialog').within(() => { + // Verify the button exists. Clicking it will open a new window + cy.findByText('Upgrade').should('exist') + }) + }) + + // eslint-disable-next-line mocha/no-skipped-tests + it.skip('opens subscription page after clicking on `try it for free`', function () {}) + + it('shows `ask project owner to upgrade` message', function () { + mountEditor('other-user-id') + cy.findByRole('dialog').within(() => { + cy.findByText( + 'Please ask the project owner to upgrade to use track changes' + ).should('exist') + }) }) }) diff --git a/services/web/test/frontend/features/source-editor/helpers/mock-doc.ts b/services/web/test/frontend/features/source-editor/helpers/mock-doc.ts index 91f6cc6eff..4c239c1f60 100644 --- a/services/web/test/frontend/features/source-editor/helpers/mock-doc.ts +++ b/services/web/test/frontend/features/source-editor/helpers/mock-doc.ts @@ -53,7 +53,10 @@ class MockShareDoc extends EventEmitter { } } -export const mockDoc = (content = defaultContent) => { +export const mockDoc = ( + content = defaultContent, + { rangesOptions = {} } = {} +) => { const mockShareJSDoc: ShareDoc = new MockShareDoc(content) return { @@ -92,7 +95,10 @@ export const mockDoc = (content = defaultContent) => { }, }), resetDirtyState: () => {}, + removeCommentId: () => {}, + ...rangesOptions, }, + submitOp: (op: any) => {}, setTrackChangesIdSeeds: () => {}, getTrackingChanges: () => true, setTrackingChanges: () => {}, diff --git a/services/web/test/frontend/features/source-editor/helpers/mock-scope.ts b/services/web/test/frontend/features/source-editor/helpers/mock-scope.ts index 8eafae47c0..621bdecd3c 100644 --- a/services/web/test/frontend/features/source-editor/helpers/mock-scope.ts +++ b/services/web/test/frontend/features/source-editor/helpers/mock-scope.ts @@ -5,10 +5,18 @@ import { Folder } from '../../../../../types/folder' export const rootFolderId = '012345678901234567890123' export const figuresFolderId = '123456789012345678901234' export const figureId = '234567890123456789012345' -export const mockScope = (content?: string) => { +export const mockScope = ( + content?: string, + { + docOptions = {}, + projectFeatures = {}, + permissions = {}, + projectOwner = undefined, + }: any = {} +) => { return { editor: { - sharejs_doc: mockDoc(content), + sharejs_doc: mockDoc(content, docOptions), open_doc_name: 'test.tex', open_doc_id: docId, showVisual: false, @@ -61,14 +69,17 @@ export const mockScope = (content?: string) => { ] as Folder[], features: { trackChanges: true, + ...projectFeatures, }, trackChangesState: {}, members: [], + owner: projectOwner, }, permissions: { comment: true, trackedWrite: true, write: true, + ...permissions, }, ui: { reviewPanelOpen: false, From ddfadbc474fcb82e0b1dbb0ae975bb82dc5c533a Mon Sep 17 00:00:00 2001 From: Mathias Jakobsen Date: Fri, 9 May 2025 13:15:41 +0100 Subject: [PATCH 067/194] Merge pull request #25422 from overleaf/mj-ide-rail-badges-placement [web] Editor redesign: Improve badge locations for Rail buttons GitOrigin-RevId: 11eef60e6ab35003b21fa1ebf0bde4588c5f7228 --- .../ide-redesign/components/chat/chat.tsx | 4 ++-- .../features/ide-redesign/components/errors.tsx | 7 +++++-- .../ide-redesign/components/rail-indicator.tsx | 17 +++++++++++++++++ .../bootstrap-5/pages/editor/rail.scss | 10 +++++++--- 4 files changed, 31 insertions(+), 7 deletions(-) create mode 100644 services/web/frontend/js/features/ide-redesign/components/rail-indicator.tsx diff --git a/services/web/frontend/js/features/ide-redesign/components/chat/chat.tsx b/services/web/frontend/js/features/ide-redesign/components/chat/chat.tsx index d76d82cb22..9ebe33e065 100644 --- a/services/web/frontend/js/features/ide-redesign/components/chat/chat.tsx +++ b/services/web/frontend/js/features/ide-redesign/components/chat/chat.tsx @@ -2,7 +2,6 @@ import ChatFallbackError from '@/features/chat/components/chat-fallback-error' import InfiniteScroll from '@/features/chat/components/infinite-scroll' import MessageInput from '@/features/chat/components/message-input' import { useChatContext } from '@/features/chat/context/chat-context' -import OLBadge from '@/features/ui/components/ol/ol-badge' import { FetchError } from '@/infrastructure/fetch-json' import { FullSizeLoadingSpinner } from '@/shared/components/loading-spinner' import MaterialIcon from '@/shared/components/material-icon' @@ -11,6 +10,7 @@ import { lazy, Suspense, useEffect } from 'react' import { useTranslation } from 'react-i18next' import classNames from 'classnames' import { RailPanelHeader } from '../rail' +import { RailIndicator } from '../rail-indicator' const MessageList = lazy(() => import('../../../chat/components/message-list')) @@ -19,7 +19,7 @@ export const ChatIndicator = () => { if (unreadMessageCount === 0) { return null } - return {unreadMessageCount} + return } const Loading = () => diff --git a/services/web/frontend/js/features/ide-redesign/components/errors.tsx b/services/web/frontend/js/features/ide-redesign/components/errors.tsx index 73b3ae4bc1..2313022d3c 100644 --- a/services/web/frontend/js/features/ide-redesign/components/errors.tsx +++ b/services/web/frontend/js/features/ide-redesign/components/errors.tsx @@ -1,7 +1,7 @@ import PdfLogsViewer from '@/features/pdf-preview/components/pdf-logs-viewer' import { PdfPreviewProvider } from '@/features/pdf-preview/components/pdf-preview-provider' import { useDetachCompileContext as useCompileContext } from '@/shared/context/detach-compile-context' -import OLBadge from '@/features/ui/components/ol/ol-badge' +import { RailIndicator } from './rail-indicator' export const ErrorIndicator = () => { const { logEntries } = useCompileContext() @@ -19,7 +19,10 @@ export const ErrorIndicator = () => { } return ( - 0 ? 'danger' : 'warning'}>{totalCount} + 0 ? 'danger' : 'warning'} + /> ) } diff --git a/services/web/frontend/js/features/ide-redesign/components/rail-indicator.tsx b/services/web/frontend/js/features/ide-redesign/components/rail-indicator.tsx new file mode 100644 index 0000000000..d6cf15ebc4 --- /dev/null +++ b/services/web/frontend/js/features/ide-redesign/components/rail-indicator.tsx @@ -0,0 +1,17 @@ +import OLBadge from '@/features/ui/components/ol/ol-badge' + +type RailIndicatorProps = { + type: 'danger' | 'warning' | 'info' + count: number +} + +function formatNumber(num: number) { + if (num > 99) { + return '99+' + } + return Math.floor(num).toString() +} + +export const RailIndicator = ({ count, type }: RailIndicatorProps) => { + return {formatNumber(count)} +} diff --git a/services/web/frontend/stylesheets/bootstrap-5/pages/editor/rail.scss b/services/web/frontend/stylesheets/bootstrap-5/pages/editor/rail.scss index 96df113261..b285dc084e 100644 --- a/services/web/frontend/stylesheets/bootstrap-5/pages/editor/rail.scss +++ b/services/web/frontend/stylesheets/bootstrap-5/pages/editor/rail.scss @@ -65,7 +65,7 @@ color: var(--ide-rail-color); background-color: var(--ide-rail-link-background); position: relative; - overflow-y: hidden; + overflow: visible; &:visited, &:focus { @@ -108,8 +108,12 @@ .badge { position: absolute; - top: 0; - right: 0; + top: -4px; + right: -4px; + pointer-events: none; + z-index: 10; + padding: var(--spacing-00) var(--spacing-02); + border-radius: var(--border-radius-full); } } From 5506e0d58e22c9efdde0f81d2e802c1f7d1afb31 Mon Sep 17 00:00:00 2001 From: Eric Mc Sween <5454374+emcsween@users.noreply.github.com> Date: Fri, 9 May 2025 10:49:40 -0400 Subject: [PATCH 068/194] Merge pull request #25302 from overleaf/em-pkce-support-enforcement Enforce pkceEnabled flag in OAuth configuration GitOrigin-RevId: 8e941179017712050570f13522ec42814aa58c06 --- services/web/app/src/models/OauthApplication.js | 1 + 1 file changed, 1 insertion(+) diff --git a/services/web/app/src/models/OauthApplication.js b/services/web/app/src/models/OauthApplication.js index f02a9850db..d7bd181fd6 100644 --- a/services/web/app/src/models/OauthApplication.js +++ b/services/web/app/src/models/OauthApplication.js @@ -10,6 +10,7 @@ const OauthApplicationSchema = new Schema( name: String, redirectUris: [String], scopes: [String], + pkceEnabled: Boolean, }, { collection: 'oauthApplications', From caf8b5c3c59c97801d7c9a847abd0fbbb4cb5590 Mon Sep 17 00:00:00 2001 From: Liangjun Song <146005915+adai26@users.noreply.github.com> Date: Mon, 12 May 2025 09:47:13 +0100 Subject: [PATCH 069/194] Merge pull request #25329 from overleaf/ls-enable-stripe-checkout-for-group-plan Enable stripe checkout for group subscriptions GitOrigin-RevId: 10a579ebca789773bd2c94f8240b7b979b6e8eb0 --- .../Subscription/PaymentProviderEntities.js | 19 ++++++++++++++++ .../src/Features/Subscription/PlansLocator.js | 22 +++++++++++++------ .../src/Subscription/PlansLocatorTests.js | 12 +++++----- services/web/types/subscription/plan.ts | 12 ++++++++-- 4 files changed, 50 insertions(+), 15 deletions(-) diff --git a/services/web/app/src/Features/Subscription/PaymentProviderEntities.js b/services/web/app/src/Features/Subscription/PaymentProviderEntities.js index 298480a972..f6a8af4aa5 100644 --- a/services/web/app/src/Features/Subscription/PaymentProviderEntities.js +++ b/services/web/app/src/Features/Subscription/PaymentProviderEntities.js @@ -87,6 +87,15 @@ class PaymentProviderSubscription { return isStandaloneAiAddOnPlanCode(this.planCode) } + /** + * Returns whether this subscription is a group subscription + * + * @return {boolean} + */ + isGroupSubscription() { + return isGroupPlanCode(this.planCode) + } + /** * Returns whether this subcription will have the given add-on next billing * period. @@ -543,6 +552,15 @@ function isStandaloneAiAddOnPlanCode(planCode) { return STANDALONE_AI_ADD_ON_CODES.includes(planCode) } +/** + * Returns whether the given plan code is a group plan + * + * @param {string} planCode + */ +function isGroupPlanCode(planCode) { + return planCode.includes('group') +} + /** * Returns whether subscription change will have have the ai bundle once the change is processed * @@ -575,6 +593,7 @@ module.exports = { PaymentProviderPlan, PaymentProviderCoupon, PaymentProviderAccount, + isGroupPlanCode, isStandaloneAiAddOnPlanCode, subscriptionChangeIsAiAssistUpgrade, PaymentProviderImmediateCharge, diff --git a/services/web/app/src/Features/Subscription/PlansLocator.js b/services/web/app/src/Features/Subscription/PlansLocator.js index 937d2d3ccb..a418e23b78 100644 --- a/services/web/app/src/Features/Subscription/PlansLocator.js +++ b/services/web/app/src/Features/Subscription/PlansLocator.js @@ -34,6 +34,10 @@ const recurlyPlanCodeToStripeLookupKey = { 'student-annual': 'student_annual', student: 'student_monthly', student_free_trial_7_days: 'student_monthly', + group_professional: 'group_professional_enterprise', + group_professional_educational: 'group_professional_educational', + group_collaborator: 'group_standard_enterprise', + group_collaborator_educational: 'group_standard_educational', } /** @@ -46,24 +50,28 @@ function mapRecurlyPlanCodeToStripeLookupKey(recurlyPlanCode) { } const recurlyPlanCodeToPlanTypeAndPeriod = { - collaborator: { planType: 'standard', period: 'monthly' }, - collaborator_free_trial_7_days: { planType: 'standard', period: 'monthly' }, - 'collaborator-annual': { planType: 'standard', period: 'annual' }, - professional: { planType: 'professional', period: 'monthly' }, + collaborator: { planType: 'individual', period: 'monthly' }, + collaborator_free_trial_7_days: { planType: 'individual', period: 'monthly' }, + 'collaborator-annual': { planType: 'individual', period: 'annual' }, + professional: { planType: 'individual', period: 'monthly' }, professional_free_trial_7_days: { - planType: 'professional', + planType: 'individual', period: 'monthly', }, - 'professional-annual': { planType: 'professional', period: 'annual' }, + 'professional-annual': { planType: 'individual', period: 'annual' }, student: { planType: 'student', period: 'monthly' }, student_free_trial_7_days: { planType: 'student', period: 'monthly' }, 'student-annual': { planType: 'student', period: 'annual' }, + group_professional: { planType: 'group', period: 'annual' }, + group_professional_educational: { planType: 'group', period: 'annual' }, + group_collaborator: { planType: 'group', period: 'annual' }, + group_collaborator_educational: { planType: 'group', period: 'annual' }, } /** * * @param {RecurlyPlanCode} recurlyPlanCode - * @returns {{ planType: 'standard' | 'professional' | 'student', period: 'annual' | 'monthly'}} + * @returns {{ planType: 'individual' | 'group' | 'student', period: 'annual' | 'monthly'}} */ function getPlanTypeAndPeriodFromRecurlyPlanCode(recurlyPlanCode) { return recurlyPlanCodeToPlanTypeAndPeriod[recurlyPlanCode] diff --git a/services/web/test/unit/src/Subscription/PlansLocatorTests.js b/services/web/test/unit/src/Subscription/PlansLocatorTests.js index 9373c02b89..f705baa01c 100644 --- a/services/web/test/unit/src/Subscription/PlansLocatorTests.js +++ b/services/web/test/unit/src/Subscription/PlansLocatorTests.js @@ -120,7 +120,7 @@ describe('PlansLocator', function () { this.PlansLocator.getPlanTypeAndPeriodFromRecurlyPlanCode( 'collaborator' ) - expect(planType).to.equal('standard') + expect(planType).to.equal('individual') expect(period).to.equal('monthly') }) @@ -129,7 +129,7 @@ describe('PlansLocator', function () { this.PlansLocator.getPlanTypeAndPeriodFromRecurlyPlanCode( 'collaborator_free_trial_7_days' ) - expect(planType).to.equal('standard') + expect(planType).to.equal('individual') expect(period).to.equal('monthly') }) @@ -138,7 +138,7 @@ describe('PlansLocator', function () { this.PlansLocator.getPlanTypeAndPeriodFromRecurlyPlanCode( 'collaborator-annual' ) - expect(planType).to.equal('standard') + expect(planType).to.equal('individual') expect(period).to.equal('annual') }) @@ -147,7 +147,7 @@ describe('PlansLocator', function () { this.PlansLocator.getPlanTypeAndPeriodFromRecurlyPlanCode( 'professional' ) - expect(planType).to.equal('professional') + expect(planType).to.equal('individual') expect(period).to.equal('monthly') }) @@ -156,7 +156,7 @@ describe('PlansLocator', function () { this.PlansLocator.getPlanTypeAndPeriodFromRecurlyPlanCode( 'professional_free_trial_7_days' ) - expect(planType).to.equal('professional') + expect(planType).to.equal('individual') expect(period).to.equal('monthly') }) @@ -165,7 +165,7 @@ describe('PlansLocator', function () { this.PlansLocator.getPlanTypeAndPeriodFromRecurlyPlanCode( 'professional-annual' ) - expect(planType).to.equal('professional') + expect(planType).to.equal('individual') expect(period).to.equal('annual') }) diff --git a/services/web/types/subscription/plan.ts b/services/web/types/subscription/plan.ts index a1b0f7b5d6..c5e8f7e820 100644 --- a/services/web/types/subscription/plan.ts +++ b/services/web/types/subscription/plan.ts @@ -81,11 +81,19 @@ export type RecurlyPlanCode = | 'student' | 'student-annual' | 'student_free_trial_7_days' + | 'group_professional' + | 'group_professional_educational' + | 'group_collaborator' + | 'group_collaborator_educational' export type StripeLookupKey = - | 'collaborator_monthly' - | 'collaborator_annual' + | 'standard_monthly' + | 'standard_annual' | 'professional_monthly' | 'professional_annual' | 'student_monthly' | 'student_annual' + | 'group_standard_enterprise' + | 'group_professional_enterprise' + | 'group_standard_educational' + | 'group_professional_educational' From 0d70223a48692cce42ab3e1206109722a88c0eef Mon Sep 17 00:00:00 2001 From: Brian Gough Date: Mon, 12 May 2025 09:57:05 +0100 Subject: [PATCH 070/194] Merge pull request #25482 from overleaf/bg-fix-bull-exporter-errors retain completed and failed jobs for backup queue GitOrigin-RevId: 7831ce2565dc493e3ce7f55001207daea2140575 --- services/history-v1/storage/scripts/backup_scheduler.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/history-v1/storage/scripts/backup_scheduler.mjs b/services/history-v1/storage/scripts/backup_scheduler.mjs index 3fac053f12..38b6e6ef04 100644 --- a/services/history-v1/storage/scripts/backup_scheduler.mjs +++ b/services/history-v1/storage/scripts/backup_scheduler.mjs @@ -17,8 +17,8 @@ const redisOptions = config.get('redis.queue') const backupQueue = new Queue('backup', { redis: redisOptions, defaultJobOptions: { - removeOnComplete: true, - removeOnFail: true, + removeOnComplete: { age: 60 }, // keep completed jobs for 60 seconds + removeOnFail: { age: 7 * 24 * 3600, count: 1000 }, // keep failed jobs for 7 days, max 1000 }, }) From 70c26b6ed2c5ba28a23f04b022adbb75ee2b3f6a Mon Sep 17 00:00:00 2001 From: Kristina <7614497+khjrtbrg@users.noreply.github.com> Date: Mon, 12 May 2025 11:28:43 +0200 Subject: [PATCH 071/194] [web] prevent downgrade to personal upsell for stripe subscriptions (#25392) GitOrigin-RevId: a954f42e1159e4bcc8fd06f5f6df9a53c67f9f90 --- .../SubscriptionViewModelBuilder.js | 12 +++ .../cancel-plan/cancel-subscription.tsx | 10 +- .../util/is-monthly-collaborator-plan.ts | 10 -- .../util/show-downgrade-option.ts | 18 ---- .../dashboard/states/active/active.test.tsx | 8 +- .../subscription/fixtures/subscriptions.ts | 11 +++ .../util/is-monthly-collaborator-plan.test.ts | 17 ---- .../util/show-downgrade-option.test.ts | 68 ------------- .../SubscriptionViewModelBuilderTests.js | 99 +++++++++++++++++++ .../subscription/dashboard/subscription.ts | 1 + 10 files changed, 129 insertions(+), 125 deletions(-) delete mode 100644 services/web/frontend/js/features/subscription/util/is-monthly-collaborator-plan.ts delete mode 100644 services/web/frontend/js/features/subscription/util/show-downgrade-option.ts delete mode 100644 services/web/test/frontend/features/subscription/util/is-monthly-collaborator-plan.test.ts delete mode 100644 services/web/test/frontend/features/subscription/util/show-downgrade-option.test.ts diff --git a/services/web/app/src/Features/Subscription/SubscriptionViewModelBuilder.js b/services/web/app/src/Features/Subscription/SubscriptionViewModelBuilder.js index 129463dcf0..180a78f294 100644 --- a/services/web/app/src/Features/Subscription/SubscriptionViewModelBuilder.js +++ b/services/web/app/src/Features/Subscription/SubscriptionViewModelBuilder.js @@ -282,6 +282,18 @@ async function buildUsersSubscriptionViewModel(user, locale = 'en') { isEligibleForGroupPlan: paymentRecord.subscription.service === 'recurly' && !isInTrial, } + + const isMonthlyCollaboratorPlan = + personalSubscription.planCode.includes('collaborator') && + !personalSubscription.planCode.includes('ann') && + !personalSubscription.plan.groupPlan + personalSubscription.payment.isEligibleForDowngradeUpsell = + !personalSubscription.payment.pausedAt && + !personalSubscription.payment.remainingPauseCycles && + isMonthlyCollaboratorPlan && + !isInTrial && + paymentRecord.subscription.service === 'recurly' + if (paymentRecord.subscription.pendingChange) { const pendingPlanCode = paymentRecord.subscription.pendingChange.nextPlanCode diff --git a/services/web/frontend/js/features/subscription/components/dashboard/states/active/cancel-plan/cancel-subscription.tsx b/services/web/frontend/js/features/subscription/components/dashboard/states/active/cancel-plan/cancel-subscription.tsx index dc34d38693..1b04e6385f 100644 --- a/services/web/frontend/js/features/subscription/components/dashboard/states/active/cancel-plan/cancel-subscription.tsx +++ b/services/web/frontend/js/features/subscription/components/dashboard/states/active/cancel-plan/cancel-subscription.tsx @@ -8,7 +8,6 @@ import { cancelSubscriptionUrl, redirectAfterCancelSubscriptionUrl, } from '../../../../../data/subscription-url' -import showDowngradeOption from '../../../../../util/show-downgrade-option' import GenericErrorAlert from '../../../generic-error-alert' import DowngradePlanButton from './downgrade-plan-button' import ExtendTrialButton from './extend-trial-button' @@ -157,13 +156,8 @@ export function CancelSubscription() { if (!personalSubscription || !('payment' in personalSubscription)) return null - const showDowngrade = showDowngradeOption( - personalSubscription.plan.planCode, - personalSubscription.plan.groupPlan, - personalSubscription.payment.trialEndsAt, - personalSubscription.payment.pausedAt, - personalSubscription.payment.remainingPauseCycles - ) + const showDowngrade = + personalSubscription.payment.isEligibleForDowngradeUpsell const planToDowngradeTo = plans.find( plan => plan.planCode === planCodeToDowngradeTo ) diff --git a/services/web/frontend/js/features/subscription/util/is-monthly-collaborator-plan.ts b/services/web/frontend/js/features/subscription/util/is-monthly-collaborator-plan.ts deleted file mode 100644 index 550c5ec2a5..0000000000 --- a/services/web/frontend/js/features/subscription/util/is-monthly-collaborator-plan.ts +++ /dev/null @@ -1,10 +0,0 @@ -export default function isMonthlyCollaboratorPlan( - planCode: string, - isGroupPlan?: boolean -) { - return ( - planCode.indexOf('collaborator') !== -1 && - planCode.indexOf('ann') === -1 && - !isGroupPlan - ) -} diff --git a/services/web/frontend/js/features/subscription/util/show-downgrade-option.ts b/services/web/frontend/js/features/subscription/util/show-downgrade-option.ts deleted file mode 100644 index 283823f481..0000000000 --- a/services/web/frontend/js/features/subscription/util/show-downgrade-option.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Nullable } from '../../../../../types/utils' -import isInFreeTrial from './is-in-free-trial' -import isMonthlyCollaboratorPlan from './is-monthly-collaborator-plan' - -export default function showDowngradeOption( - planCode: string, - isGroupPlan?: boolean, - trialEndsAt?: string | null, - pausedAt?: Nullable, - remainingPauseCycles?: Nullable -) { - return ( - !pausedAt && - !remainingPauseCycles && - isMonthlyCollaboratorPlan(planCode, isGroupPlan) && - !isInFreeTrial(trialEndsAt) - ) -} diff --git a/services/web/test/frontend/features/subscription/components/dashboard/states/active/active.test.tsx b/services/web/test/frontend/features/subscription/components/dashboard/states/active/active.test.tsx index 15dd65b6ba..4102760cd1 100644 --- a/services/web/test/frontend/features/subscription/components/dashboard/states/active/active.test.tsx +++ b/services/web/test/frontend/features/subscription/components/dashboard/states/active/active.test.tsx @@ -470,10 +470,10 @@ describe('', function () { }) }) - it('does not show option to downgrade when not a collaborator plan', function () { - const trialPlan = cloneDeep(monthlyActiveCollaborator) - trialPlan.plan.planCode = 'anotherplan' - renderActiveSubscription(trialPlan) + it('does not show option to downgrade when plan is not eligible for downgrades', function () { + const ineligiblePlan = cloneDeep(monthlyActiveCollaborator) + ineligiblePlan.payment.isEligibleForDowngradeUpsell = false + renderActiveSubscription(ineligiblePlan) showConfirmCancelUI() expect( screen.queryByRole('button', { diff --git a/services/web/test/frontend/features/subscription/fixtures/subscriptions.ts b/services/web/test/frontend/features/subscription/fixtures/subscriptions.ts index ebd741b240..08690742d3 100644 --- a/services/web/test/frontend/features/subscription/fixtures/subscriptions.ts +++ b/services/web/test/frontend/features/subscription/fixtures/subscriptions.ts @@ -54,6 +54,7 @@ export const annualActiveSubscription: PaidSubscription = { addOnDisplayPricesWithoutAdditionalLicense: {}, isEligibleForGroupPlan: true, isEligibleForPause: false, + isEligibleForDowngradeUpsell: false, }, } @@ -96,6 +97,7 @@ export const annualActiveSubscriptionEuro: PaidSubscription = { addOnDisplayPricesWithoutAdditionalLicense: {}, isEligibleForGroupPlan: true, isEligibleForPause: true, + isEligibleForDowngradeUpsell: false, }, } @@ -137,6 +139,7 @@ export const annualActiveSubscriptionPro: PaidSubscription = { addOnDisplayPricesWithoutAdditionalLicense: {}, isEligibleForGroupPlan: true, isEligibleForPause: true, + isEligibleForDowngradeUpsell: false, }, } @@ -179,6 +182,7 @@ export const pastDueExpiredSubscription: PaidSubscription = { addOnDisplayPricesWithoutAdditionalLicense: {}, isEligibleForGroupPlan: true, isEligibleForPause: true, + isEligibleForDowngradeUpsell: false, }, } @@ -221,6 +225,7 @@ export const canceledSubscription: PaidSubscription = { addOnDisplayPricesWithoutAdditionalLicense: {}, isEligibleForGroupPlan: true, isEligibleForPause: true, + isEligibleForDowngradeUpsell: false, }, } @@ -263,6 +268,7 @@ export const pendingSubscriptionChange: PaidSubscription = { addOnDisplayPricesWithoutAdditionalLicense: {}, isEligibleForGroupPlan: true, isEligibleForPause: false, + isEligibleForDowngradeUpsell: false, }, pendingPlan: { planCode: 'professional-annual', @@ -316,6 +322,7 @@ export const groupActiveSubscription: GroupSubscription = { addOnDisplayPricesWithoutAdditionalLicense: {}, isEligibleForGroupPlan: true, isEligibleForPause: false, + isEligibleForDowngradeUpsell: false, }, } @@ -365,6 +372,7 @@ export const groupActiveSubscriptionWithPendingLicenseChange: GroupSubscription addOnDisplayPricesWithoutAdditionalLicense: {}, isEligibleForGroupPlan: true, isEligibleForPause: false, + isEligibleForDowngradeUpsell: false, }, pendingPlan: { planCode: 'group_collaborator_10_enterprise', @@ -417,6 +425,7 @@ export const trialSubscription: PaidSubscription = { addOnDisplayPricesWithoutAdditionalLicense: {}, isEligibleForGroupPlan: true, isEligibleForPause: false, + isEligibleForDowngradeUpsell: false, }, } @@ -480,6 +489,7 @@ export const trialCollaboratorSubscription: PaidSubscription = { addOnDisplayPricesWithoutAdditionalLicense: {}, isEligibleForGroupPlan: true, isEligibleForPause: true, + isEligibleForDowngradeUpsell: false, }, } @@ -521,5 +531,6 @@ export const monthlyActiveCollaborator: PaidSubscription = { addOnDisplayPricesWithoutAdditionalLicense: {}, isEligibleForGroupPlan: true, isEligibleForPause: true, + isEligibleForDowngradeUpsell: true, }, } diff --git a/services/web/test/frontend/features/subscription/util/is-monthly-collaborator-plan.test.ts b/services/web/test/frontend/features/subscription/util/is-monthly-collaborator-plan.test.ts deleted file mode 100644 index dd9c2a64b1..0000000000 --- a/services/web/test/frontend/features/subscription/util/is-monthly-collaborator-plan.test.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { expect } from 'chai' -import isMonthlyCollaboratorPlan from '../../../../../frontend/js/features/subscription/util/is-monthly-collaborator-plan' - -describe('isMonthlyCollaboratorPlan', function () { - it('returns false when a plan code without "collaborator" ', function () { - expect(isMonthlyCollaboratorPlan('test', false)).to.be.false - }) - it('returns false when on a plan with "collaborator" and "ann"', function () { - expect(isMonthlyCollaboratorPlan('collaborator-annual', false)).to.be.false - }) - it('returns false when on a plan with "collaborator" and without "ann" but is a group plan', function () { - expect(isMonthlyCollaboratorPlan('collaborator', true)).to.be.false - }) - it('returns true when on a plan with non-group "collaborator" monthly plan', function () { - expect(isMonthlyCollaboratorPlan('collaborator', false)).to.be.true - }) -}) diff --git a/services/web/test/frontend/features/subscription/util/show-downgrade-option.test.ts b/services/web/test/frontend/features/subscription/util/show-downgrade-option.test.ts deleted file mode 100644 index 8a464ac796..0000000000 --- a/services/web/test/frontend/features/subscription/util/show-downgrade-option.test.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { expect } from 'chai' -import showDowngradeOption from '../../../../../frontend/js/features/subscription/util/show-downgrade-option' -import dateformat from 'dateformat' - -describe('showDowngradeOption', function () { - const today = new Date() - const sevenDaysFromToday = new Date().setDate(today.getDate() + 7) - const sevenDaysFromTodayFormatted = dateformat( - sevenDaysFromToday, - 'dS mmmm yyyy' - ) - - it('returns false when no trial end date', function () { - expect(showDowngradeOption('collab')).to.be.false - }) - it('returns false when a plan code without "collaborator" ', function () { - expect(showDowngradeOption('test', false, sevenDaysFromTodayFormatted)).to - .be.false - }) - it('returns false when on a plan with trial date in future but has "collaborator" and "ann" in plan code', function () { - expect( - showDowngradeOption( - 'collaborator-annual', - false, - sevenDaysFromTodayFormatted - ) - ).to.be.false - }) - it('returns false when on a plan with trial date in future and plan code has "collaborator" and no "ann" but is a group plan', function () { - expect( - showDowngradeOption('collaborator', true, sevenDaysFromTodayFormatted) - ).to.be.false - }) - it('returns false when on a plan with "collaborator" and without "ann" and trial date in future', function () { - expect( - showDowngradeOption('collaborator', false, sevenDaysFromTodayFormatted) - ).to.be.false - }) - it('returns true when on a plan with "collaborator" and without "ann" and no trial date', function () { - expect(showDowngradeOption('collaborator', false)).to.be.true - }) - it('returns true when on a plan with "collaborator" and without "ann" and trial date is in the past', function () { - expect( - showDowngradeOption('collaborator', false, '2000-02-16T17:59:07.000Z') - ).to.be.true - }) - it('returns false when on a monthly collaborator plan with a pending pause', function () { - expect( - showDowngradeOption( - 'collaborator', - false, - null, - '2030-01-01T12:00:00.000Z' - ) - ).to.be.false - }) - it('returns false when on a monthly collaborator plan with an active pause', function () { - expect( - showDowngradeOption( - 'collaborator', - false, - null, - '2030-01-01T12:00:00.000Z', - 5 - ) - ).to.be.false - }) -}) diff --git a/services/web/test/unit/src/Subscription/SubscriptionViewModelBuilderTests.js b/services/web/test/unit/src/Subscription/SubscriptionViewModelBuilderTests.js index e969cf381c..0f666b888a 100644 --- a/services/web/test/unit/src/Subscription/SubscriptionViewModelBuilderTests.js +++ b/services/web/test/unit/src/Subscription/SubscriptionViewModelBuilderTests.js @@ -25,6 +25,11 @@ describe('SubscriptionViewModelBuilder', function () { planCode: this.planCode, features: this.planFeatures, } + this.annualPlanCode = 'collaborator_annual' + this.annualPlan = { + planCode: this.annualPlanCode, + features: this.planFeatures, + } this.individualSubscription = { planCode: this.planCode, plan: this.plan, @@ -74,6 +79,7 @@ describe('SubscriptionViewModelBuilder', function () { features: this.groupPlanFeatures, membersLimit: 4, membersLimitAddOn: 'additional-license', + groupPlan: true, } this.groupSubscription = { planCode: this.groupPlanCode, @@ -166,6 +172,8 @@ describe('SubscriptionViewModelBuilder', function () { this.PlansLocator.findLocalPlanInSettings .withArgs(this.planCode) .returns(this.plan) + .withArgs(this.annualPlanCode) + .returns(this.annualPlan) .withArgs(this.groupPlanCode) .returns(this.groupPlan) .withArgs(this.commonsPlanCode) @@ -575,6 +583,7 @@ describe('SubscriptionViewModelBuilder', function () { }, isEligibleForGroupPlan: true, isEligibleForPause: false, + isEligibleForDowngradeUpsell: true, }) }) @@ -689,6 +698,96 @@ describe('SubscriptionViewModelBuilder', function () { }) }) + describe('isEligibleForDowngradeUpsell', function () { + it('is true for eligible individual subscriptions', async function () { + this.paymentRecord.pausePeriodStart = null + this.paymentRecord.remainingPauseCycles = null + this.paymentRecord.trialPeriodEnd = null + this.paymentRecord.service = 'recurly' + const result = + await this.SubscriptionViewModelBuilder.promises.buildUsersSubscriptionViewModel( + this.user + ) + assert.isTrue( + result.personalSubscription.payment.isEligibleForDowngradeUpsell + ) + }) + + it('is false for group plans', async function () { + this.individualSubscription.planCode = this.groupPlanCode + this.paymentRecord.pausePeriodStart = null + this.paymentRecord.remainingPauseCycles = null + this.paymentRecord.trialPeriodEnd = null + this.paymentRecord.service = 'recurly' + const result = + await this.SubscriptionViewModelBuilder.promises.buildUsersSubscriptionViewModel( + this.user + ) + assert.isFalse( + result.personalSubscription.payment.isEligibleForDowngradeUpsell + ) + }) + + it('is false for annual individual plans', async function () { + this.individualSubscription.planCode = this.annualPlanCode + this.paymentRecord.pausePeriodStart = null + this.paymentRecord.remainingPauseCycles = null + this.paymentRecord.trialPeriodEnd = null + this.paymentRecord.service = 'recurly' + const result = + await this.SubscriptionViewModelBuilder.promises.buildUsersSubscriptionViewModel( + this.user + ) + assert.isFalse( + result.personalSubscription.payment.isEligibleForDowngradeUpsell + ) + }) + + it('is false for paused plans', async function () { + this.paymentRecord.pausePeriodStart = new Date() + this.paymentRecord.remainingPauseCycles = 1 + this.paymentRecord.trialPeriodEnd = null + this.paymentRecord.service = 'recurly' + const result = + await this.SubscriptionViewModelBuilder.promises.buildUsersSubscriptionViewModel( + this.user + ) + assert.isFalse( + result.personalSubscription.payment.isEligibleForDowngradeUpsell + ) + }) + + it('is false for plans in free trial period', async function () { + this.paymentRecord.pausePeriodStart = null + this.paymentRecord.remainingPauseCycles = null + this.paymentRecord.trialPeriodEnd = new Date( + Date.now() + 24 * 60 * 60 * 1000 // tomorrow + ) + this.paymentRecord.service = 'recurly' + const result = + await this.SubscriptionViewModelBuilder.promises.buildUsersSubscriptionViewModel( + this.user + ) + assert.isFalse( + result.personalSubscription.payment.isEligibleForDowngradeUpsell + ) + }) + + it('is false for Stripe subscriptions', async function () { + this.paymentRecord.pausePeriodStart = null + this.paymentRecord.remainingPauseCycles = null + this.paymentRecord.trialPeriodEnd = null + this.paymentRecord.service = 'stripe' + const result = + await this.SubscriptionViewModelBuilder.promises.buildUsersSubscriptionViewModel( + this.user + ) + assert.isFalse( + result.personalSubscription.payment.isEligibleForDowngradeUpsell + ) + }) + }) + it('includes pending changes', async function () { this.paymentRecord.pendingChange = new PaymentProviderSubscriptionChange({ diff --git a/services/web/types/subscription/dashboard/subscription.ts b/services/web/types/subscription/dashboard/subscription.ts index 4b9c9de57c..a1ee934423 100644 --- a/services/web/types/subscription/dashboard/subscription.ts +++ b/services/web/types/subscription/dashboard/subscription.ts @@ -46,6 +46,7 @@ type PaymentProviderRecord = { remainingPauseCycles?: Nullable isEligibleForPause: boolean isEligibleForGroupPlan: boolean + isEligibleForDowngradeUpsell: boolean } export type GroupPolicy = { From 67a436b6392eedc5fa228cd6172fccae58de4fbd Mon Sep 17 00:00:00 2001 From: Kristina <7614497+khjrtbrg@users.noreply.github.com> Date: Mon, 12 May 2025 11:29:17 +0200 Subject: [PATCH 072/194] Merge pull request #25469 from overleaf/mj-paste-tables-multicol [web] Improve borders and column definitions of pasted tables with multi-column cells GitOrigin-RevId: fe9c44bd8ac6a34e8a8057f1a07d97771a116e1a --- .../extensions/visual/paste-html.ts | 35 ++++++++++++----- ...demirror-editor-visual-paste-html.spec.tsx | 38 ++++++++++++++++++- 2 files changed, 62 insertions(+), 11 deletions(-) diff --git a/services/web/frontend/js/features/source-editor/extensions/visual/paste-html.ts b/services/web/frontend/js/features/source-editor/extensions/visual/paste-html.ts index ec9e3bd7bd..01db7b18ba 100644 --- a/services/web/frontend/js/features/source-editor/extensions/visual/paste-html.ts +++ b/services/web/frontend/js/features/source-editor/extensions/visual/paste-html.ts @@ -487,6 +487,7 @@ const tabular = (element: HTMLTableElement) => { alignment: string borderLeft: boolean borderRight: boolean + inferred?: boolean }> = [] const rows = element.querySelectorAll('tr') @@ -500,16 +501,29 @@ const tabular = (element: HTMLTableElement) => { for (const cell of cells) { // NOTE: reading the alignment and borders from the first cell definition in each column - if (definitions[index] === undefined) { - const { textAlign, borderLeftStyle, borderRightStyle } = cell.style + const colspan = Number(cell.getAttribute('colspan') ?? 1) + const { textAlign, borderLeftStyle, borderRightStyle } = cell.style - definitions[index] = { - alignment: textAlign, - borderLeft: visibleBorderStyle(borderLeftStyle), - borderRight: visibleBorderStyle(borderRightStyle), + for (let i = 0; i < colspan; i++) { + if ( + // There's no definition for this column + definitions[index + i] === undefined || + // There's an inferred definition of the column, and we're a cell that + // can accurately represent the whole column, since we're not a + // multicolumn cell ourselves. + (colspan === 1 && definitions[index + i].inferred) + ) { + definitions[index + i] = { + alignment: textAlign, + borderLeft: visibleBorderStyle(borderLeftStyle), + borderRight: visibleBorderStyle(borderRightStyle), + // We can't trust the details from a multicolumn cell to represent the + // whole column, so we mark it as inferred. + inferred: colspan > 1, + } } } - index += Number(cell.getAttribute('colspan') ?? 1) + index += colspan } } @@ -614,9 +628,12 @@ const nextRowHasBorderStyle = ( } const startMulticolumn = (element: HTMLTableCellElement): string => { + const { textAlign, borderLeftStyle, borderRightStyle } = element.style const colspan = Number(element.getAttribute('colspan') || 1) - const alignment = cellAlignment.get(element.style.textAlign) ?? 'l' - return `\\multicolumn{${colspan}}{${alignment}}{` + const alignment = cellAlignment.get(textAlign) ?? 'l' + const borderLeft = visibleBorderStyle(borderLeftStyle) + const borderRight = visibleBorderStyle(borderRightStyle) + return `\\multicolumn{${colspan}}{${borderLeft ? '|' : ''}${alignment}${borderRight ? '|' : ''}}{` } const startMultirow = (element: HTMLTableCellElement): string => { diff --git a/services/web/test/frontend/features/source-editor/components/codemirror-editor-visual-paste-html.spec.tsx b/services/web/test/frontend/features/source-editor/components/codemirror-editor-visual-paste-html.spec.tsx index 450af37a5a..9220d6ccdf 100644 --- a/services/web/test/frontend/features/source-editor/components/codemirror-editor-visual-paste-html.spec.tsx +++ b/services/web/test/frontend/features/source-editor/components/codemirror-editor-visual-paste-html.spec.tsx @@ -227,6 +227,40 @@ describe(' paste HTML in Visual mode', function () { cy.get('.table-generator-cell[colspan="2"]').should('have.length', 2) }) + it('handles a pasted 1-row table with merged columns', function () { + mountEditor() + + const data = [ + ``, + ``, + `
testtest
`, + ].join('') + + const clipboardData = new DataTransfer() + clipboardData.setData('text/html', data) + cy.get('@content').trigger('paste', { clipboardData }) + + cy.get('@content').should('have.text', 'testtest' + menuIconsText) + cy.get('.table-generator-cell').should('have.length', 2) + cy.get('.table-generator-cell[colspan="2"]').should('have.length', 1) + }) + + it('handles a pasted table with a bordered merged column', function () { + mountEditor() + + const data = [ + ``, + ``, + `
test
`, + ].join('') + + const clipboardData = new DataTransfer() + clipboardData.setData('text/html', data) + cy.get('@content').trigger('paste', { clipboardData }) + cy.get('.table-generator-cell-border-right').should('have.length', 1) + cy.get('.table-generator-cell-border-left').should('have.length', 1) + }) + it('handles a pasted table with merged rows', function () { mountEditor() @@ -312,8 +346,8 @@ describe(' paste HTML in Visual mode', function () { cy.get('@content').should('have.text', 'foofoobarfoobar' + menuIconsText) cy.get('.table-generator-cell').should('have.length', 5) cy.get('.table-generator-cell[colspan="2"]').should('have.length', 1) - cy.get('.table-generator-cell-border-left').should('have.length', 2) - cy.get('.table-generator-cell-border-right').should('have.length', 4) + cy.get('.table-generator-cell-border-left').should('have.length', 3) + cy.get('.table-generator-cell-border-right').should('have.length', 5) cy.get('.table-generator-row-border-top').should('have.length', 5) cy.get('.table-generator-row-border-bottom').should('have.length', 2) }) From c1f3758aa26f1de32e0831342243b1b309cb0c32 Mon Sep 17 00:00:00 2001 From: Domagoj Kriskovic Date: Mon, 12 May 2025 12:14:01 +0200 Subject: [PATCH 073/194] Add script to update null references in for readOnly_refs and pendingReviewer_refs (#25417) * Add script to update null references in for readOnly_refs and pendingReviewer_refs * update script to only update readOnly_refs * clean up * use projectAuditLogEntries to find relevant projects * use updateOne GitOrigin-RevId: bbeaa04b837ebb657c802598f0de72879f641bd0 --- .../scripts/fix_collaborator_refs_null.mjs | 175 ++++++++++++++++++ 1 file changed, 175 insertions(+) create mode 100644 services/web/scripts/fix_collaborator_refs_null.mjs diff --git a/services/web/scripts/fix_collaborator_refs_null.mjs b/services/web/scripts/fix_collaborator_refs_null.mjs new file mode 100644 index 0000000000..062662a5f8 --- /dev/null +++ b/services/web/scripts/fix_collaborator_refs_null.mjs @@ -0,0 +1,175 @@ +import minimist from 'minimist' +import { + db, + ObjectId, + READ_PREFERENCE_SECONDARY, +} from '../app/src/infrastructure/mongodb.js' +import lodash from 'lodash' + +const args = minimist(process.argv.slice(2), { + boolean: ['commit'], +}) + +const run = async () => { + try { + const projects = await db.projectAuditLogEntries + .find( + { + operation: 'collaborator-limit-exceeded', + timestamp: { + $gte: new Date('2025-03-26'), + $lt: new Date('2025-04-02'), + }, + }, + { + readPreference: READ_PREFERENCE_SECONDARY, + projection: { + _id: 1, + projectId: 1, + }, + } + ) + .toArray() + + const uniqueProjectIds = lodash.uniq( + projects.map(p => p.projectId.toString()) + ) + + console.log( + `Found ${uniqueProjectIds.length} projects where collaborator-limit-exceeded operation was logged in provided date range` + ) + + let readOnlyCount = 0 + let pendingReviewerCount = 0 + let reviewerCount = 0 + + for (const projectId of uniqueProjectIds) { + if (args.commit) { + const readOnlyUpdate = await db.projects.updateOne( + { + _id: new ObjectId(projectId), + readOnly_refs: null, + }, + { + $set: { + readOnly_refs: [], + }, + } + ) + if (readOnlyUpdate.modifiedCount > 0) { + console.log(`Updated readOnly_refs for project id ${projectId}`) + } + readOnlyCount += readOnlyUpdate.modifiedCount + } else { + const project = await db.projects.findOne( + { + _id: new ObjectId(projectId), + readOnly_refs: null, + }, + { + projection: { + _id: 1, + readOnly_refs: 1, + }, + } + ) + if (project) { + readOnlyCount++ + console.log( + `Dry run: Would update readOnly_refs for project id ${projectId}` + ) + } + } + + if (args.commit) { + const pendingReviewerUpdate = await db.projects.updateOne( + { + _id: new ObjectId(projectId), + pendingReviewer_refs: null, + }, + { + $set: { + pendingReviewer_refs: [], + }, + } + ) + if (pendingReviewerUpdate.modifiedCount > 0) { + console.log( + `Updated pendingReviewer_refs for project id ${projectId}` + ) + } + pendingReviewerCount += pendingReviewerUpdate.modifiedCount + } else { + const project = await db.projects.findOne( + { + _id: new ObjectId(projectId), + pendingReviewer_refs: null, + }, + { + projection: { + _id: 1, + pendingReviewer_refs: 1, + }, + } + ) + if (project) { + pendingReviewerCount++ + console.log( + `Dry run: Would update pendingReviewer_refs for project id ${projectId}` + ) + } + } + + if (args.commit) { + const reviewerUpdate = await db.projects.updateOne( + { + _id: new ObjectId(projectId), + reviewer_refs: null, + }, + { + $set: { + reviewer_refs: [], + }, + } + ) + reviewerCount += reviewerUpdate.modifiedCount + } else { + const project = await db.projects.findOne( + { + _id: new ObjectId(projectId), + reviewer_refs: null, + }, + { + projection: { + _id: 1, + reviewer_refs: 1, + }, + } + ) + if (project) { + reviewerCount++ + console.log( + `Dry run: Would update reviewer_refs for project id ${projectId}` + ) + } + } + } + + if (args.commit) { + console.log( + `Updated readOnly_refs for ${readOnlyCount} projects, pendingReviewer_refs for ${pendingReviewerCount} projects, and reviewer_refs for ${reviewerCount} projects.` + ) + } else { + console.log( + `Dry run: Would update readOnly_refs for ${readOnlyCount} projects, pendingReviewer_refs for ${pendingReviewerCount} projects, and reviewer_refs for ${reviewerCount} projects.` + ) + } + + process.exit(0) + } catch (err) { + console.error('Error while processing projects:', err) + process.exit(1) + } +} + +run() From b99a81cb25f0e31da49a63c69345fa0ef933cc53 Mon Sep 17 00:00:00 2001 From: Domagoj Kriskovic Date: Mon, 12 May 2025 12:14:37 +0200 Subject: [PATCH 074/194] Fix monthly price if billed annually for AI Assist (#25297) * Fix monthly price if billed annually for AI Assist * update script * show annual price * fix formatting GitOrigin-RevId: e50493fa2176e6c8acb476a01a393eb940a3f1a2 --- .../web/app/src/Features/Project/ProjectController.js | 8 ++++++++ services/web/frontend/extracted-translations.json | 2 +- services/web/frontend/js/utils/meta.ts | 5 ++++- services/web/locales/en.json | 1 + services/web/scripts/recurly/generate_addon_prices.mjs | 2 ++ 5 files changed, 16 insertions(+), 2 deletions(-) diff --git a/services/web/app/src/Features/Project/ProjectController.js b/services/web/app/src/Features/Project/ProjectController.js index 2d949a8753..f34e2522ec 100644 --- a/services/web/app/src/Features/Project/ProjectController.js +++ b/services/web/app/src/Features/Project/ProjectController.js @@ -949,9 +949,17 @@ const _ProjectController = { const annualPrice = Settings.localizedAddOnsPricing[currency][plan].annual const monthlyPrice = Settings.localizedAddOnsPricing[currency][plan].monthly + const annualDividedByTwelve = + Settings.localizedAddOnsPricing[currency][plan].annualDividedByTwelve plansData[plan] = { annual: formatCurrency(annualPrice, currency, locale, true), + annualDividedByTwelve: formatCurrency( + annualDividedByTwelve, + currency, + locale, + true + ), monthly: formatCurrency(monthlyPrice, currency, locale, true), } }) diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json index 608e758298..320db9714a 100644 --- a/services/web/frontend/extracted-translations.json +++ b/services/web/frontend/extracted-translations.json @@ -1203,7 +1203,7 @@ "pending_invite": "", "per_license": "", "per_month": "", - "per_month_billed_annually": "", + "per_month_x_annually": "", "percent_is_the_percentage_of_the_line_width": "", "permanently_disables_the_preview": "", "personal_library": "", diff --git a/services/web/frontend/js/utils/meta.ts b/services/web/frontend/js/utils/meta.ts index 6c7209a5bb..220cd0803f 100644 --- a/services/web/frontend/js/utils/meta.ts +++ b/services/web/frontend/js/utils/meta.ts @@ -55,7 +55,10 @@ import type { ScriptLogType } from '../../../modules/admin-panel/frontend/js/fea import { ActiveExperiment } from './labs-utils' export interface Meta { 'ol-ExposedSettings': ExposedSettings - 'ol-addonPrices': Record + 'ol-addonPrices': Record< + string, + { annual: string; monthly: string; annualDividedByTwelve: string } + > 'ol-allInReconfirmNotificationPeriods': UserEmailData[] 'ol-allowedExperiments': string[] 'ol-allowedImageNames': AllowedImageName[] diff --git a/services/web/locales/en.json b/services/web/locales/en.json index c2401427b7..779d61ecbf 100644 --- a/services/web/locales/en.json +++ b/services/web/locales/en.json @@ -1594,6 +1594,7 @@ "per_license": "per license", "per_month": "per month", "per_month_billed_annually": "per month, billed annually", + "per_month_x_annually": "per month, __price__ annually", "per_user_month": "per user / month", "per_user_year": "per user / year", "per_year": "per year", diff --git a/services/web/scripts/recurly/generate_addon_prices.mjs b/services/web/scripts/recurly/generate_addon_prices.mjs index 9885d46b06..37378e6baf 100644 --- a/services/web/scripts/recurly/generate_addon_prices.mjs +++ b/services/web/scripts/recurly/generate_addon_prices.mjs @@ -37,6 +37,8 @@ async function main() { localizedAddOnsPricing[currency] = { [ADD_ON_CODE]: {} } } localizedAddOnsPricing[currency][ADD_ON_CODE].annual = unitAmount + localizedAddOnsPricing[currency][ADD_ON_CODE].annualDividedByTwelve = + (unitAmount || 0) / 12 } console.log(JSON.stringify({ localizedAddOnsPricing }, null, 2)) From 50c2d8f32f6a1f19269fe4c29ac49d088a0e7130 Mon Sep 17 00:00:00 2001 From: Jimmy Domagala-Tang Date: Mon, 12 May 2025 06:18:36 -0400 Subject: [PATCH 075/194] Merge pull request #25405 from overleaf/jdt-wf-rebrand-popover Writefull Rebranded Features Editor Promotion GitOrigin-RevId: 49beddbfa44bacf1546543e172dc8edcdb3784bc --- .../Features/Tutorial/TutorialController.mjs | 1 + services/web/config/settings.defaults.js | 1 + .../web/frontend/extracted-translations.json | 2 ++ .../components/pdf-preview-pane.tsx | 10 +++++++- .../bootstrap-5/modules/writefull.scss | 23 +++++++++++++++++++ services/web/locales/en.json | 2 ++ 6 files changed, 38 insertions(+), 1 deletion(-) diff --git a/services/web/app/src/Features/Tutorial/TutorialController.mjs b/services/web/app/src/Features/Tutorial/TutorialController.mjs index a5cdf8d478..e66a54f71b 100644 --- a/services/web/app/src/Features/Tutorial/TutorialController.mjs +++ b/services/web/app/src/Features/Tutorial/TutorialController.mjs @@ -13,6 +13,7 @@ const VALID_KEYS = [ 'us-gov-banner-fedramp', 'full-project-search-promo', 'editor-popup-ux-survey', + 'wf-features-moved', ] async function completeTutorial(req, res, next) { diff --git a/services/web/config/settings.defaults.js b/services/web/config/settings.defaults.js index be567bf13e..07970fb852 100644 --- a/services/web/config/settings.defaults.js +++ b/services/web/config/settings.defaults.js @@ -968,6 +968,7 @@ module.exports = { sourceEditorComponents: [], pdfLogEntryComponents: [], pdfLogEntriesComponents: [], + pdfPreviewPromotions: [], diagnosticActions: [], sourceEditorCompletionSources: [], sourceEditorSymbolPalette: [], diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json index 320db9714a..207da71688 100644 --- a/services/web/frontend/extracted-translations.json +++ b/services/web/frontend/extracted-translations.json @@ -613,6 +613,7 @@ "full_width": "", "future_payments": "", "generate_from_text_or_image": "", + "generate_tables_and_equations": "", "generate_token": "", "generic_if_problem_continues_contact_us": "", "generic_linked_file_compile_error": "", @@ -1053,6 +1054,7 @@ "need_to_leave": "", "neither_agree_nor_disagree": "", "new_compile_domain_notice": "", + "new_create_tables_and_equations": "", "new_file": "", "new_folder": "", "new_font_open_dyslexic": "", diff --git a/services/web/frontend/js/features/pdf-preview/components/pdf-preview-pane.tsx b/services/web/frontend/js/features/pdf-preview/components/pdf-preview-pane.tsx index 8133ff31ee..7bbecbc327 100644 --- a/services/web/frontend/js/features/pdf-preview/components/pdf-preview-pane.tsx +++ b/services/web/frontend/js/features/pdf-preview/components/pdf-preview-pane.tsx @@ -1,4 +1,4 @@ -import { memo, Suspense } from 'react' +import { ElementType, memo, Suspense } from 'react' import classNames from 'classnames' import PdfLogsViewer from './pdf-logs-viewer' import PdfViewer from './pdf-viewer' @@ -11,6 +11,7 @@ import { PdfPreviewProvider } from './pdf-preview-provider' import PdfPreviewHybridToolbarNew from '@/features/ide-redesign/components/pdf-preview/pdf-preview-hybrid-toolbar' import PdfErrorState from '@/features/ide-redesign/components/pdf-preview/pdf-error-state' import { useIsNewEditorEnabled } from '@/features/ide-redesign/utils/new-editor-utils' +import importOverleafModules from '../../../../macros/import-overleaf-module.macro' function PdfPreviewPane() { const { pdfUrl, hasShortCompileTimeout } = useCompileContext() @@ -18,6 +19,10 @@ function PdfPreviewPane() { 'pdf-empty': !pdfUrl, }) const newEditor = useIsNewEditorEnabled() + const pdfPromotions = importOverleafModules('pdfPreviewPromotions') as { + import: { default: ElementType } + path: string + }[] return (
@@ -36,6 +41,9 @@ function PdfPreviewPane() {
{newEditor ? : } + {pdfPromotions.map(({ import: { default: Component }, path }) => ( + + ))}
) diff --git a/services/web/frontend/stylesheets/bootstrap-5/modules/writefull.scss b/services/web/frontend/stylesheets/bootstrap-5/modules/writefull.scss index bc4107e3ae..6ffb33254b 100644 --- a/services/web/frontend/stylesheets/bootstrap-5/modules/writefull.scss +++ b/services/web/frontend/stylesheets/bootstrap-5/modules/writefull.scss @@ -10,3 +10,26 @@ max-width: 520px; text-align: left; } + +.feature-rebrand-promo-container { + z-index: 1; + position: absolute; + bottom: 15px; + right: 15px; + background: $bg-dark-primary; + color: $content-primary-dark; + max-width: 450px; +} + +.feature-rebrand-promo-title-container { + display: flex; +} + +.feature-rebrand-promo-title-text { + color: white; + font-size: 14px; +} + +.feature-rebrand-promo-body { + color: white; +} diff --git a/services/web/locales/en.json b/services/web/locales/en.json index 779d61ecbf..065ae7ba38 100644 --- a/services/web/locales/en.json +++ b/services/web/locales/en.json @@ -813,6 +813,7 @@ "gallery_show_more_tags": "Show more", "general": "General", "generate_from_text_or_image": "From text or image", + "generate_tables_and_equations": "Generate tables and equations from text and images. Try it for free in the Overleaf toolbar!", "generate_token": "Generate token", "generic_if_problem_continues_contact_us": "If the problem continues please contact us", "generic_linked_file_compile_error": "This project’s output files are not available because it failed to compile. Please open the project to see the compilation error details.", @@ -1380,6 +1381,7 @@ "need_to_leave": "Need to leave?", "neither_agree_nor_disagree": "Neither agree nor disagree", "new_compile_domain_notice": "We’ve recently migrated PDF downloads to a new domain. Something might be blocking your browser from accessing that new domain, <0>__compilesUserContentDomain__. This could be caused by network blocking or a strict browser plugin rule. Please follow our <1>troubleshooting guide.", + "new_create_tables_and_equations": "NEW! Create tables and equations in seconds", "new_file": "New file", "new_folder": "New folder", "new_font_open_dyslexic": "New font: OpenDyslexic Mono is designed to improve readability for those with dyslexia.", From 1ec12e3d88b45ca93abf1d5b960b50a042c94ddc Mon Sep 17 00:00:00 2001 From: Jimmy Domagala-Tang Date: Mon, 12 May 2025 06:19:39 -0400 Subject: [PATCH 076/194] Merge pull request #25493 from overleaf/jdt-ai-assist-entitlement-fix Access addOnCode correctly when determining bundle entitlement GitOrigin-RevId: c3aee15b3ef6fc1d32f24283ec848e600f0777f1 --- .../web/app/src/Features/Subscription/SubscriptionLocator.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/services/web/app/src/Features/Subscription/SubscriptionLocator.js b/services/web/app/src/Features/Subscription/SubscriptionLocator.js index ac0fa5918a..8526ad0fb2 100644 --- a/services/web/app/src/Features/Subscription/SubscriptionLocator.js +++ b/services/web/app/src/Features/Subscription/SubscriptionLocator.js @@ -121,9 +121,11 @@ const SubscriptionLocator = { async hasAiAssist(userOrId) { const userId = SubscriptionLocator._getUserId(userOrId) const subscription = await Subscription.findOne({ admin_id: userId }).exec() + // todo: as opposed to recurlyEntities which use addon.code, subscription model uses addon.addOnCode + // which we hope to align via https://github.com/overleaf/internal/issues/25494 return Boolean( isStandaloneAiAddOnPlanCode(subscription?.planCode) || - subscription?.addOns?.some(addOn => addOn.code === AI_ADD_ON_CODE) + subscription?.addOns?.some(addOn => addOn.addOnCode === AI_ADD_ON_CODE) ) }, From bf22684e2d4e7b6980b92cbb425547349efe3e89 Mon Sep 17 00:00:00 2001 From: Jimmy Domagala-Tang Date: Mon, 12 May 2025 06:20:50 -0400 Subject: [PATCH 077/194] Merge pull request #25507 from overleaf/jdt-show-wf-src-on-admin Add premium source for Writefull entitlment to the Admin page GitOrigin-RevId: 937b6d588d0f9328eb450809a0cd2f0e4b0ea299 --- services/web/types/user.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/services/web/types/user.ts b/services/web/types/user.ts index 0c6c45facf..8d00ea803f 100644 --- a/services/web/types/user.ts +++ b/services/web/types/user.ts @@ -52,6 +52,7 @@ export type User = { enabled: boolean autoCreatedAccount: boolean firstAutoLoad: boolean + premiumSource: string } aiErrorAssistant?: { enabled: boolean From 732b1d146e7e842f7b9317ee8e7f55b4d2444925 Mon Sep 17 00:00:00 2001 From: Eric Mc Sween <5454374+emcsween@users.noreply.github.com> Date: Mon, 12 May 2025 07:58:39 -0400 Subject: [PATCH 078/194] Merge pull request #25456 from overleaf/em-concurrency-handling Add consistency constraints to the chunk store and Redis buffer GitOrigin-RevId: 6f983ff207a13d204645e343290c94443dc537b0 --- .../storage/lib/chunk_store/index.js | 63 ++++++-- .../storage/lib/chunk_store/mongo.js | 10 +- .../storage/lib/chunk_store/postgres.js | 8 +- .../storage/lib/chunk_store/redis.js | 6 + .../history-v1/storage/lib/persist_changes.js | 7 +- .../acceptance/js/api/backupVerifier.test.mjs | 2 +- .../acceptance/js/storage/chunk_store.test.js | 134 ++++++++++++++---- .../storage/chunk_store_redis_backend.test.js | 52 ++++--- 8 files changed, 216 insertions(+), 66 deletions(-) diff --git a/services/history-v1/storage/lib/chunk_store/index.js b/services/history-v1/storage/lib/chunk_store/index.js index 12c0924707..b026a073d4 100644 --- a/services/history-v1/storage/lib/chunk_store/index.js +++ b/services/history-v1/storage/lib/chunk_store/index.js @@ -213,16 +213,29 @@ async function create(projectId, chunk, earliestChangeTimestamp) { const backend = getBackend(projectId) const chunkStart = chunk.getStartVersion() - const chunkId = await uploadChunk(projectId, chunk) const opts = {} if (chunkStart > 0) { - opts.oldChunkId = await getChunkIdForVersion(projectId, chunkStart - 1) + const oldChunk = await backend.getChunkForVersion(projectId, chunkStart - 1) + + if (oldChunk.endVersion !== chunkStart) { + throw new ChunkVersionConflictError( + 'unexpected end version on chunk to be updated', + { + projectId, + expectedVersion: chunkStart, + actualVersion: oldChunk.endVersion, + } + ) + } + + opts.oldChunkId = oldChunk.id } if (earliestChangeTimestamp != null) { opts.earliestChangeTimestamp = earliestChangeTimestamp } + const chunkId = await uploadChunk(projectId, chunk) await backend.confirmCreate(projectId, chunk, chunkId, opts) } @@ -253,24 +266,44 @@ async function uploadChunk(projectId, chunk) { * chunk. * * @param {string} projectId - * @param {number} oldEndVersion * @param {Chunk} newChunk * @param {Date} [earliestChangeTimestamp] * @return {Promise} */ -async function update( - projectId, - oldEndVersion, - newChunk, - earliestChangeTimestamp -) { +async function update(projectId, newChunk, earliestChangeTimestamp) { assert.projectId(projectId, 'bad projectId') - assert.integer(oldEndVersion, 'bad oldEndVersion') assert.instance(newChunk, Chunk, 'bad newChunk') assert.maybe.date(earliestChangeTimestamp, 'bad timestamp') const backend = getBackend(projectId) - const oldChunkId = await getChunkIdForVersion(projectId, oldEndVersion) + const oldChunk = await backend.getChunkForVersion( + projectId, + newChunk.getStartVersion(), + { preferNewer: true } + ) + + if (oldChunk.startVersion !== newChunk.getStartVersion()) { + throw new ChunkVersionConflictError( + 'unexpected start version on chunk to be updated', + { + projectId, + expectedVersion: newChunk.getStartVersion(), + actualVersion: oldChunk.startVersion, + } + ) + } + + if (oldChunk.endVersion > newChunk.getEndVersion()) { + throw new ChunkVersionConflictError( + 'chunk update would decrease chunk version', + { + projectId, + currentVersion: oldChunk.endVersion, + newVersion: newChunk.getEndVersion(), + } + ) + } + const newChunkId = await uploadChunk(projectId, newChunk) const opts = {} @@ -278,7 +311,13 @@ async function update( opts.earliestChangeTimestamp = earliestChangeTimestamp } - await backend.confirmUpdate(projectId, oldChunkId, newChunk, newChunkId, opts) + await backend.confirmUpdate( + projectId, + oldChunk.id, + newChunk, + newChunkId, + opts + ) } /** diff --git a/services/history-v1/storage/lib/chunk_store/mongo.js b/services/history-v1/storage/lib/chunk_store/mongo.js index a34b7194af..d84e7a8327 100644 --- a/services/history-v1/storage/lib/chunk_store/mongo.js +++ b/services/history-v1/storage/lib/chunk_store/mongo.js @@ -43,8 +43,14 @@ async function getLatestChunk(projectId, opts = {}) { /** * Get the metadata for the chunk that contains the given version. + * + * @param {string} projectId + * @param {number} version + * @param {object} [opts] + * @param {boolean} [opts.preferNewer] - If the version is at the boundary of + * two chunks, return the newer chunk. */ -async function getChunkForVersion(projectId, version) { +async function getChunkForVersion(projectId, version, opts = {}) { assert.mongoId(projectId, 'bad projectId') assert.integer(version, 'bad version') @@ -55,7 +61,7 @@ async function getChunkForVersion(projectId, version) { startVersion: { $lte: version }, endVersion: { $gte: version }, }, - { sort: { startVersion: 1 } } + { sort: { startVersion: opts.preferNewer ? -1 : 1 } } ) if (record == null) { throw new Chunk.VersionNotFoundError(projectId, version) diff --git a/services/history-v1/storage/lib/chunk_store/postgres.js b/services/history-v1/storage/lib/chunk_store/postgres.js index 0c33c0fd82..bfb5c6954a 100644 --- a/services/history-v1/storage/lib/chunk_store/postgres.js +++ b/services/history-v1/storage/lib/chunk_store/postgres.js @@ -38,14 +38,18 @@ async function getLatestChunk(projectId, opts = {}) { * * @param {string} projectId * @param {number} version + * @param {object} [opts] + * @param {boolean} [opts.preferNewer] - If the version is at the boundary of + * two chunks, return the newer chunk. */ -async function getChunkForVersion(projectId, version) { +async function getChunkForVersion(projectId, version, opts = {}) { assert.postgresId(projectId, 'bad projectId') const record = await knex('chunks') .where('doc_id', parseInt(projectId, 10)) + .where('start_version', '<=', version) .where('end_version', '>=', version) - .orderBy('end_version') + .orderBy('end_version', opts.preferNewer ? 'desc' : 'asc') .first() if (!record) { throw new Chunk.VersionNotFoundError(projectId, version) diff --git a/services/history-v1/storage/lib/chunk_store/redis.js b/services/history-v1/storage/lib/chunk_store/redis.js index 6858127a0c..0ae7cee2e5 100644 --- a/services/history-v1/storage/lib/chunk_store/redis.js +++ b/services/history-v1/storage/lib/chunk_store/redis.js @@ -495,6 +495,12 @@ rclient.defineCommand('set_persisted_version', { return 'not_found' end + -- Get current persisted version + local persistedVersion = tonumber(redis.call('GET', persistedVersionKey)) + if persistedVersion and persistedVersion > newPersistedVersion then + return 'too_low' + end + -- Set the persisted version redis.call('SET', persistedVersionKey, newPersistedVersion) diff --git a/services/history-v1/storage/lib/persist_changes.js b/services/history-v1/storage/lib/persist_changes.js index 09d41382d4..5b80285eb0 100644 --- a/services/history-v1/storage/lib/persist_changes.js +++ b/services/history-v1/storage/lib/persist_changes.js @@ -250,12 +250,7 @@ async function persistChanges(projectId, allChanges, limits, clientEndVersion) { checkElapsedTime(timer) - await chunkStore.update( - projectId, - originalEndVersion, - currentChunk, - earliestChangeTimestamp - ) + await chunkStore.update(projectId, currentChunk, earliestChangeTimestamp) } async function createNewChunksAsNeeded() { diff --git a/services/history-v1/test/acceptance/js/api/backupVerifier.test.mjs b/services/history-v1/test/acceptance/js/api/backupVerifier.test.mjs index 44e1a2652b..0a1fa528ab 100644 --- a/services/history-v1/test/acceptance/js/api/backupVerifier.test.mjs +++ b/services/history-v1/test/acceptance/js/api/backupVerifier.test.mjs @@ -156,7 +156,7 @@ async function addFileInNewChunk( ) const changes = [new Change([operation], creationDate, [])] chunk.pushChanges(changes) - await chunkStore.update(historyId, 0, chunk) + await chunkStore.update(historyId, chunk) } /** diff --git a/services/history-v1/test/acceptance/js/storage/chunk_store.test.js b/services/history-v1/test/acceptance/js/storage/chunk_store.test.js index 48c142817d..da70467934 100644 --- a/services/history-v1/test/acceptance/js/storage/chunk_store.test.js +++ b/services/history-v1/test/acceptance/js/storage/chunk_store.test.js @@ -6,6 +6,9 @@ const { expect } = require('chai') const sinon = require('sinon') const { ObjectId } = require('mongodb') const { projects } = require('../../../../storage/lib/mongodb') +const { + ChunkVersionConflictError, +} = require('../../../../storage/lib/chunk_store/errors') const { Chunk, @@ -66,17 +69,33 @@ describe('chunkStore', function () { const lastChangeTimestamp = new Date('2015-01-01T00:00:00') beforeEach(async function () { const blob = await blobStore.putString('abc') - const chunk = makeChunk( + const firstChunk = makeChunk( [ makeChange( Operation.addFile('main.tex', File.createLazyFromBlobs(blob)), lastChangeTimestamp ), ], + 0 + ) + await chunkStore.update(projectId, firstChunk, pendingChangeTimestamp) + + const secondChunk = makeChunk( + [ + makeChange( + Operation.addFile('other.tex', File.createLazyFromBlobs(blob)), + lastChangeTimestamp + ), + ], 1 ) - await chunkStore.create(projectId, chunk, pendingChangeTimestamp) + await chunkStore.create( + projectId, + secondChunk, + pendingChangeTimestamp + ) }) + it('creates a chunk and inserts the pending change timestamp', async function () { const project = await projects.findOne({ _id: new ObjectId(projectRecord.insertedId), @@ -101,7 +120,6 @@ describe('chunkStore', function () { beforeEach(async function () { const chunk = await chunkStore.loadLatest(projectId) - const oldEndVersion = chunk.getEndVersion() const blob = await blobStore.putString('') const changes = [ makeChange( @@ -111,12 +129,7 @@ describe('chunkStore', function () { ] lastChangeTimestamp = changes[1].getTimestamp() chunk.pushChanges(changes) - await chunkStore.update( - projectId, - oldEndVersion, - chunk, - pendingChangeTimestamp - ) + await chunkStore.update(projectId, chunk, pendingChangeTimestamp) }) it('records the correct metadata in db readOnly=false', async function () { @@ -204,12 +217,7 @@ describe('chunkStore', function () { ], 0 ) - await chunkStore.update( - projectId, - 0, - firstChunk, - pendingChangeTimestamp - ) + await chunkStore.update(projectId, firstChunk, pendingChangeTimestamp) firstChunk = await chunkStore.loadLatest(projectId) secondChunk = makeChunk( @@ -234,6 +242,10 @@ describe('chunkStore', function () { Operation.addFile('quux.tex', File.createLazyFromBlobs(blob)), thirdChunkTimestamp ), + makeChange( + Operation.addFile('barbar.tex', File.createLazyFromBlobs(blob)), + thirdChunkTimestamp + ), ], 4 ) @@ -308,7 +320,7 @@ describe('chunkStore', function () { const project = await projects.findOne({ _id: new ObjectId(projectRecord.insertedId), }) - expect(project.overleaf.history.currentEndVersion).to.equal(5) + expect(project.overleaf.history.currentEndVersion).to.equal(6) expect(project.overleaf.history.currentEndTimestamp).to.deep.equal( thirdChunkTimestamp ) @@ -323,6 +335,80 @@ describe('chunkStore', function () { ) }) + describe('chunk update', function () { + it('rejects a chunk that removes changes', async function () { + const newChunk = makeChunk([thirdChunk.getChanges()[0]], 4) + await expect( + chunkStore.update(projectId, newChunk) + ).to.be.rejectedWith(ChunkVersionConflictError) + const latestChunk = await chunkStore.loadLatest(projectId) + expect(latestChunk.toRaw()).to.deep.equal(thirdChunk.toRaw()) + }) + + it('accepts the same chunk', async function () { + await chunkStore.update(projectId, thirdChunk) + const latestChunk = await chunkStore.loadLatest(projectId) + expect(latestChunk.toRaw()).to.deep.equal(thirdChunk.toRaw()) + }) + + it('accepts a larger chunk', async function () { + const blob = await blobStore.putString('foobar') + const newChunk = makeChunk( + [ + ...thirdChunk.getChanges(), + makeChange( + Operation.addFile( + 'onemore.tex', + File.createLazyFromBlobs(blob) + ), + thirdChunkTimestamp + ), + ], + 4 + ) + await chunkStore.update(projectId, newChunk) + const latestChunk = await chunkStore.loadLatest(projectId) + expect(latestChunk.toRaw()).to.deep.equal(newChunk.toRaw()) + }) + }) + + describe('chunk create', function () { + let change + + beforeEach(async function () { + const blob = await blobStore.putString('foobar') + change = makeChange( + Operation.addFile('onemore.tex', File.createLazyFromBlobs(blob)), + thirdChunkTimestamp + ) + }) + + it('rejects a base version that is too low', async function () { + const newChunk = makeChunk([change], 5) + await expect( + chunkStore.create(projectId, newChunk) + ).to.be.rejectedWith(ChunkVersionConflictError) + const latestChunk = await chunkStore.loadLatest(projectId) + expect(latestChunk.toRaw()).to.deep.equal(thirdChunk.toRaw()) + }) + + it('rejects a base version that is too high', async function () { + const newChunk = makeChunk([change], 7) + await expect( + chunkStore.create(projectId, newChunk) + ).to.be.rejectedWith(ChunkVersionConflictError) + const latestChunk = await chunkStore.loadLatest(projectId) + expect(latestChunk.toRaw()).to.deep.equal(thirdChunk.toRaw()) + }) + + it('accepts the right base version', async function () { + const newChunk = makeChunk([change], 6) + await chunkStore.create(projectId, newChunk) + const latestChunk = await chunkStore.loadLatest(projectId) + expect(latestChunk.toRaw()).to.deep.equal(newChunk.toRaw()) + }) + }) + describe('after updating the last chunk', function () { let newChunk @@ -341,12 +427,12 @@ describe('chunkStore', function () { ], 4 ) - await chunkStore.update(projectId, 5, newChunk) + await chunkStore.update(projectId, newChunk) newChunk = await chunkStore.loadLatest(projectId) }) it('replaces the latest chunk', function () { - expect(newChunk.getChanges()).to.have.length(2) + expect(newChunk.getChanges()).to.have.length(3) }) it('returns the right chunk when querying by version', async function () { @@ -366,7 +452,7 @@ describe('chunkStore', function () { const project = await projects.findOne({ _id: new ObjectId(projectRecord.insertedId), }) - expect(project.overleaf.history.currentEndVersion).to.equal(6) + expect(project.overleaf.history.currentEndVersion).to.equal(7) expect(project.overleaf.history.currentEndTimestamp).to.deep.equal( thirdChunkTimestamp ) @@ -529,7 +615,7 @@ describe('chunkStore', function () { const chunkRecords = [] for await (const chunk of chunkStore.getProjectChunksFromVersion( projectId, - 6 + 7 )) { chunkRecords.push(chunk) } @@ -566,9 +652,9 @@ describe('chunkStore', function () { ] chunk.pushChanges(changes) - await expect( - chunkStore.update(projectId, oldEndVersion, chunk) - ).to.be.rejectedWith('S3 Error') + await expect(chunkStore.update(projectId, chunk)).to.be.rejectedWith( + 'S3 Error' + ) chunk = await chunkStore.loadLatest(projectId) expect(chunk.getEndVersion()).to.equal(oldEndVersion) }) @@ -598,7 +684,7 @@ describe('chunkStore', function () { ], 0 ) - await chunkStore.update(projectId, 0, chunk) + await chunkStore.update(projectId, chunk) }) it('refuses to create a chunk with the same start version', async function () { diff --git a/services/history-v1/test/acceptance/js/storage/chunk_store_redis_backend.test.js b/services/history-v1/test/acceptance/js/storage/chunk_store_redis_backend.test.js index 5e226ff9e2..2b13343fc4 100644 --- a/services/history-v1/test/acceptance/js/storage/chunk_store_redis_backend.test.js +++ b/services/history-v1/test/acceptance/js/storage/chunk_store_redis_backend.test.js @@ -704,28 +704,42 @@ describe('chunk buffer Redis backend', function () { expect(result).to.equal('not_found') }) - it('should set the persisted version', async function () { - // Set head version - const headVersion = 5 - await rclient.set( - keySchema.headVersion({ projectId }), - headVersion.toString() - ) + describe('when the persisted version is not set', function () { + beforeEach(async function () { + await setupState(projectId, { + headVersion: 5, + persistedVersion: null, + changes: 5, + }) + }) - // Set persisted version - const persistedVersion = 3 - const result = await redisBackend.setPersistedVersion( - projectId, - persistedVersion - ) + it('should set the persisted version', async function () { + await redisBackend.setPersistedVersion(projectId, 3) + const state = await redisBackend.getState(projectId) + expect(state.persistedVersion).to.equal(3) + }) + }) - expect(result).to.equal('ok') + describe('when the persisted version is set', function () { + beforeEach(async function () { + await setupState(projectId, { + headVersion: 5, + persistedVersion: 3, + changes: 5, + }) + }) - // Verify the persisted version was set - const persistedVersionRedis = await rclient.get( - keySchema.persistedVersion({ projectId }) - ) - expect(parseInt(persistedVersionRedis, 10)).to.equal(persistedVersion) + it('should set the persisted version', async function () { + await redisBackend.setPersistedVersion(projectId, 5) + const state = await redisBackend.getState(projectId) + expect(state.persistedVersion).to.equal(5) + }) + + it('should not decrease the persisted version', async function () { + await redisBackend.setPersistedVersion(projectId, 2) + const state = await redisBackend.getState(projectId) + expect(state.persistedVersion).to.equal(3) + }) }) it('should trim the changes list to keep only MAX_PERSISTED_CHANGES beyond persisted version', async function () { From 3bfc3ee7ae5b53f43f3bf570a6b20dea355e23fd Mon Sep 17 00:00:00 2001 From: Jimmy Domagala-Tang Date: Mon, 12 May 2025 08:10:09 -0400 Subject: [PATCH 079/194] Merge pull request #25540 from overleaf/jdt-push-assist-interstitial-higher-z-index Fix assist promotion being hidden under error logs pane GitOrigin-RevId: 715d82ddd98ce5e15ce2e42935526387b4c6fa1b --- .../web/frontend/stylesheets/bootstrap-5/modules/writefull.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/web/frontend/stylesheets/bootstrap-5/modules/writefull.scss b/services/web/frontend/stylesheets/bootstrap-5/modules/writefull.scss index 6ffb33254b..c5dd1a32e7 100644 --- a/services/web/frontend/stylesheets/bootstrap-5/modules/writefull.scss +++ b/services/web/frontend/stylesheets/bootstrap-5/modules/writefull.scss @@ -12,7 +12,7 @@ } .feature-rebrand-promo-container { - z-index: 1; + z-index: 12; position: absolute; bottom: 15px; right: 15px; From 8234e809316c70c30623cda773516e13e44bcefe Mon Sep 17 00:00:00 2001 From: Jimmy Domagala-Tang Date: Mon, 12 May 2025 08:10:19 -0400 Subject: [PATCH 080/194] Merge pull request #25538 from overleaf/jdt-fix-assist-interstitial-on-redesign Add assist interstitial to editor redesign GitOrigin-RevId: 97c2447bb6f6b47864563fac45ea8da46ca83777 --- .../ide-redesign/components/main-layout.tsx | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/services/web/frontend/js/features/ide-redesign/components/main-layout.tsx b/services/web/frontend/js/features/ide-redesign/components/main-layout.tsx index ccdf7c8b53..2c422af279 100644 --- a/services/web/frontend/js/features/ide-redesign/components/main-layout.tsx +++ b/services/web/frontend/js/features/ide-redesign/components/main-layout.tsx @@ -8,11 +8,17 @@ import { HorizontalToggler } from '@/features/ide-react/components/resize/horizo import { useTranslation } from 'react-i18next' import { usePdfPane } from '@/features/ide-react/hooks/use-pdf-pane' import { useLayoutContext } from '@/shared/context/layout-context' -import { useState } from 'react' +import { ElementType, useState } from 'react' import EditorPanel from './editor-panel' import { useRailContext } from '../contexts/rail-context' import HistoryContainer from '@/features/ide-react/components/history-container' import { DefaultSynctexControl } from '@/features/pdf-preview/components/detach-synctex-control' +import importOverleafModules from '../../../../macros/import-overleaf-module.macro' + +const mainEditorLayoutModalsModules: Array<{ + import: { default: ElementType } + path: string +}> = importOverleafModules('mainEditorLayoutModals') export default function MainLayout() { const [resizing, setResizing] = useState(false) @@ -111,6 +117,11 @@ export default function MainLayout() {
+ {mainEditorLayoutModalsModules.map( + ({ import: { default: Component }, path }) => ( + + ) + )}
) } From 2f3166aa54289d37dbd7b6d6980448fa53d99800 Mon Sep 17 00:00:00 2001 From: Jimmy Domagala-Tang Date: Mon, 12 May 2025 08:10:26 -0400 Subject: [PATCH 081/194] Merge pull request #25517 from overleaf/dk-fix-get-error-assist-translation Fix translation for "get_error_assist" GitOrigin-RevId: ea9ef20b8d94e49e89cc77cc6517da25d002ba7f --- services/web/locales/en.json | 1 + 1 file changed, 1 insertion(+) diff --git a/services/web/locales/en.json b/services/web/locales/en.json index 065ae7ba38..d9daebdd6a 100644 --- a/services/web/locales/en.json +++ b/services/web/locales/en.json @@ -820,6 +820,7 @@ "generic_something_went_wrong": "Sorry, something went wrong", "get_collaborative_benefits": "Get the collaborative benefits from __appName__, even if you prefer to work offline", "get_discounted_plan": "Get discounted plan", + "get_error_assist": "Get Error Assist", "get_exclusive_access_to_labs": "Get exclusive access to early-stage experiments when you join Overleaf Labs. All we ask in return is your honest feedback to help us develop and improve.", "get_in_touch": "Get in touch", "get_in_touch_having_problems": "Get in touch with support if you’re having problems", From 82e5b2c5d715504475acb4873b4569cfba0e202e Mon Sep 17 00:00:00 2001 From: Jimmy Domagala-Tang Date: Mon, 12 May 2025 08:15:33 -0400 Subject: [PATCH 082/194] Merge pull request #25151 from overleaf/dk-use-user-features UserFeaturesContext with cross-tab syncing via BroadcastChannel GitOrigin-RevId: 4262719f5018f5717211851ce28b3255af65461a --- .../src/Features/Project/ProjectController.js | 12 +-- .../Subscription/SubscriptionController.js | 28 ++++++- .../web/app/src/Features/User/UserGetter.js | 28 +++++-- .../src/Features/User/UserInfoController.js | 11 +++ services/web/app/src/router.mjs | 5 ++ .../fixtures/build/mock-writefull-api.js | 5 +- .../ide-react/context/react-context-root.tsx | 62 ++++++++------- .../successful-subscription/root.tsx | 5 +- .../successful-subscription.tsx | 2 + .../context/types/writefull-instance.ts | 1 + .../shared/context/user-features-context.tsx | 69 +++++++++++++++++ .../successful-subscription.test.tsx | 29 ++++--- .../src/Project/ProjectControllerTests.js | 38 ---------- .../SubscriptionControllerTests.js | 11 ++- .../web/test/unit/src/User/UserGetterTests.js | 59 +++++++++++++- .../unit/src/User/UserInfoControllerTests.js | 76 ++++++++++++++++++- 16 files changed, 336 insertions(+), 105 deletions(-) create mode 100644 services/web/frontend/js/shared/context/user-features-context.tsx diff --git a/services/web/app/src/Features/Project/ProjectController.js b/services/web/app/src/Features/Project/ProjectController.js index f34e2522ec..1e92a55519 100644 --- a/services/web/app/src/Features/Project/ProjectController.js +++ b/services/web/app/src/Features/Project/ProjectController.js @@ -15,7 +15,6 @@ const metrics = require('@overleaf/metrics') const { User } = require('../../models/User') const SubscriptionLocator = require('../Subscription/SubscriptionLocator') const LimitationsManager = require('../Subscription/LimitationsManager') -const FeaturesHelper = require('../Subscription/FeaturesHelper') const Settings = require('@overleaf/settings') const AuthorizationManager = require('../Authorization/AuthorizationManager') const InactiveProjectManager = require('../InactiveData/InactiveProjectManager') @@ -753,16 +752,7 @@ const _ProjectController = { let fullFeatureSet = user?.features if (!anonymous) { - // generate users feature set including features added, or overriden via modules - const moduleFeatures = - (await Modules.promises.hooks.fire( - 'getModuleProvidedFeatures', - userId - )) || [] - fullFeatureSet = FeaturesHelper.computeFeatureSet([ - user.features, - ...moduleFeatures, - ]) + fullFeatureSet = await UserGetter.promises.getUserFeatures(userId) } const isPaywallChangeCompileTimeoutEnabled = diff --git a/services/web/app/src/Features/Subscription/SubscriptionController.js b/services/web/app/src/Features/Subscription/SubscriptionController.js index 72efe77980..fe23512901 100644 --- a/services/web/app/src/Features/Subscription/SubscriptionController.js +++ b/services/web/app/src/Features/Subscription/SubscriptionController.js @@ -25,6 +25,7 @@ const RecurlyClient = require('./RecurlyClient') const { AI_ADD_ON_CODE } = require('./PaymentProviderEntities') const PlansLocator = require('./PlansLocator') const PaymentProviderEntities = require('./PaymentProviderEntities') +const { User } = require('../../models/User') /** * @import { SubscriptionChangeDescription } from '../../../../types/subscription/subscription-change-preview' @@ -186,7 +187,9 @@ async function userSubscriptionPage(req, res) { async function successfulSubscription(req, res) { const user = SessionManager.getSessionUser(req.session) - + if (!user) { + throw new Error('User is not logged in') + } const { personalSubscription } = await SubscriptionViewModelBuilder.promises.buildUsersSubscriptionViewModel( user, @@ -198,11 +201,23 @@ async function successfulSubscription(req, res) { if (!personalSubscription) { res.redirect('/user/subscription/plans') } else { + const userInDb = await User.findById(user._id, { + _id: 1, + features: 1, + }) + + if (!userInDb) { + throw new Error('User not found') + } + res.render('subscriptions/successful-subscription-react', { title: 'thank_you', personalSubscription, postCheckoutRedirect, - user, + user: { + _id: user._id, + features: userInDb.features, + }, }) } } @@ -387,7 +402,6 @@ async function purchaseAddon(req, res, next) { addOnCode, quantity ) - return res.sendStatus(200) } catch (err) { if (err instanceof DuplicateAddOnError) { HttpErrorHandler.badRequest( @@ -406,6 +420,14 @@ async function purchaseAddon(req, res, next) { return next(err) } } + + try { + await FeaturesUpdater.promises.refreshFeatures(user._id, 'add-on-purchase') + } catch (err) { + logger.error({ err }, 'Failed to refresh features after add-on purchase') + } + + return res.sendStatus(200) } async function removeAddon(req, res, next) { diff --git a/services/web/app/src/Features/User/UserGetter.js b/services/web/app/src/Features/User/UserGetter.js index 34d758add6..9be0c64b9a 100644 --- a/services/web/app/src/Features/User/UserGetter.js +++ b/services/web/app/src/Features/User/UserGetter.js @@ -11,6 +11,8 @@ const Errors = require('../Errors/Errors') const Features = require('../../infrastructure/Features') const { User } = require('../../models/User') const { normalizeQuery, normalizeMultiQuery } = require('../Helpers/Mongo') +const Modules = require('../../infrastructure/Modules') +const FeaturesHelper = require('../Subscription/FeaturesHelper') function _lastDayToReconfirm(emailData, institutionData) { const globalReconfirmPeriod = settings.reconfirmNotificationDays @@ -95,6 +97,21 @@ async function getUserFullEmails(userId) { ) } +async function getUserFeatures(userId) { + const user = await UserGetter.promises.getUser(userId, { + features: 1, + }) + if (!user) { + throw new Error('User not Found') + } + + const moduleFeatures = + (await Modules.promises.hooks.fire('getModuleProvidedFeatures', userId)) || + [] + + return FeaturesHelper.computeFeatureSet([user.features, ...moduleFeatures]) +} + async function getUserConfirmedEmails(userId) { const user = await UserGetter.promises.getUser(userId, { emails: 1, @@ -136,13 +153,7 @@ const UserGetter = { } }, - getUserFeatures(userId, callback) { - this.getUser(userId, { features: 1 }, (error, user) => { - if (error) return callback(error) - if (!user) return callback(new Errors.NotFoundError('user not found')) - callback(null, user.features) - }) - }, + getUserFeatures: callbackify(getUserFeatures), getUserEmail(userId, callback) { this.getUser(userId, { email: 1 }, (error, user) => @@ -335,9 +346,10 @@ const decorateFullEmails = ( } UserGetter.promises = promisifyAll(UserGetter, { - without: ['getSsoUsersAtInstitution', 'getUserFullEmails'], + without: ['getSsoUsersAtInstitution', 'getUserFullEmails', 'getUserFeatures'], }) UserGetter.promises.getUserFullEmails = getUserFullEmails UserGetter.promises.getSsoUsersAtInstitution = getSsoUsersAtInstitution +UserGetter.promises.getUserFeatures = getUserFeatures module.exports = UserGetter diff --git a/services/web/app/src/Features/User/UserInfoController.js b/services/web/app/src/Features/User/UserInfoController.js index 4eeea4f5e9..c95bc45af5 100644 --- a/services/web/app/src/Features/User/UserInfoController.js +++ b/services/web/app/src/Features/User/UserInfoController.js @@ -1,6 +1,7 @@ const UserGetter = require('./UserGetter') const SessionManager = require('../Authentication/SessionManager') const { ObjectId } = require('mongodb-legacy') +const { expressify } = require('@overleaf/promise-utils') function getLoggedInUsersPersonalInfo(req, res, next) { const userId = SessionManager.getLoggedInUserId(req.session) @@ -78,9 +79,19 @@ function formatPersonalInfo(user) { return formattedUser } +async function getUserFeatures(req, res, next) { + const userId = SessionManager.getLoggedInUserId(req.session) + if (!userId) { + throw new Error('User is not logged in') + } + const features = await UserGetter.promises.getUserFeatures(userId) + return res.json(features) +} + module.exports = { getLoggedInUsersPersonalInfo, getPersonalInfo, sendFormattedPersonalInfo, formatPersonalInfo, + getUserFeatures: expressify(getUserFeatures), } diff --git a/services/web/app/src/router.mjs b/services/web/app/src/router.mjs index 9201ad4c55..f87297c35c 100644 --- a/services/web/app/src/router.mjs +++ b/services/web/app/src/router.mjs @@ -484,6 +484,11 @@ async function initialize(webRouter, privateApiRouter, publicApiRouter) { AuthenticationController.requirePrivateApiAuth(), UserInfoController.getPersonalInfo ) + webRouter.get( + '/user/features', + AuthenticationController.requireLogin(), + UserInfoController.getUserFeatures + ) webRouter.get( '/user/reconfirm', diff --git a/services/web/cypress/fixtures/build/mock-writefull-api.js b/services/web/cypress/fixtures/build/mock-writefull-api.js index 4ba52ba2c8..a70a28653e 100644 --- a/services/web/cypress/fixtures/build/mock-writefull-api.js +++ b/services/web/cypress/fixtures/build/mock-writefull-api.js @@ -1 +1,4 @@ -module.exports = {} +module.exports = { + addEventListener: () => {}, + removeEventListener: () => {}, +} diff --git a/services/web/frontend/js/features/ide-react/context/react-context-root.tsx b/services/web/frontend/js/features/ide-react/context/react-context-root.tsx index f8b094f821..0eeb870a2b 100644 --- a/services/web/frontend/js/features/ide-react/context/react-context-root.tsx +++ b/services/web/frontend/js/features/ide-react/context/react-context-root.tsx @@ -23,6 +23,7 @@ import { ReferencesProvider } from '@/features/ide-react/context/references-cont import { SnapshotProvider } from '@/features/ide-react/context/snapshot-context' import { SplitTestProvider } from '@/shared/context/split-test-context' import { UserProvider } from '@/shared/context/user-context' +import { UserFeaturesProvider } from '@/shared/context/user-features-context' import { UserSettingsProvider } from '@/shared/context/user-settings-context' import { IdeRedesignSwitcherProvider } from './ide-redesign-switcher-context' import { CommandRegistryProvider } from './command-registry-context' @@ -60,6 +61,7 @@ export const ReactContextRoot: FC< UserSettingsProvider, IdeRedesignSwitcherProvider, CommandRegistryProvider, + UserFeaturesProvider, ...providers, } @@ -77,35 +79,37 @@ export const ReactContextRoot: FC< - - - - - - - - - - - - - - - {children} - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + {children} + + + + + + + + + + + + + + + diff --git a/services/web/frontend/js/features/subscription/components/successful-subscription/root.tsx b/services/web/frontend/js/features/subscription/components/successful-subscription/root.tsx index f7ef400ded..437ac2928e 100644 --- a/services/web/frontend/js/features/subscription/components/successful-subscription/root.tsx +++ b/services/web/frontend/js/features/subscription/components/successful-subscription/root.tsx @@ -1,3 +1,4 @@ +import { UserProvider } from '@/shared/context/user-context' import useWaitForI18n from '../../../../shared/hooks/use-wait-for-i18n' import { SubscriptionDashboardProvider } from '../../context/subscription-dashboard-context' import SuccessfulSubscription from './successful-subscription' @@ -13,7 +14,9 @@ function Root() { return ( - + + + ) diff --git a/services/web/frontend/js/features/subscription/components/successful-subscription/successful-subscription.tsx b/services/web/frontend/js/features/subscription/components/successful-subscription/successful-subscription.tsx index f211c85f10..e48ce0053f 100644 --- a/services/web/frontend/js/features/subscription/components/successful-subscription/successful-subscription.tsx +++ b/services/web/frontend/js/features/subscription/components/successful-subscription/successful-subscription.tsx @@ -13,6 +13,7 @@ import { isStandaloneAiPlanCode, } from '../../data/add-on-codes' import { PaidSubscription } from '../../../../../../types/subscription/dashboard/subscription' +import { useBroadcastUser } from '@/shared/hooks/user-channel/use-broadcast-user' function SuccessfulSubscription() { const { t } = useTranslation() @@ -20,6 +21,7 @@ function SuccessfulSubscription() { useSubscriptionDashboardContext() const postCheckoutRedirect = getMeta('ol-postCheckoutRedirect') const { appName, adminEmail } = getMeta('ol-ExposedSettings') + useBroadcastUser() if (!subscription || !('payment' in subscription)) return null diff --git a/services/web/frontend/js/shared/context/types/writefull-instance.ts b/services/web/frontend/js/shared/context/types/writefull-instance.ts index 213ab67f18..120590e668 100644 --- a/services/web/frontend/js/shared/context/types/writefull-instance.ts +++ b/services/web/frontend/js/shared/context/types/writefull-instance.ts @@ -1,6 +1,7 @@ export interface WritefullEvents { 'writefull-login-complete': { method: 'email-password' | 'login-with-overleaf' + isPremium: boolean } 'writefull-received-suggestions': { numberOfSuggestions: number } 'writefull-register-as-auto-account': { email: string } diff --git a/services/web/frontend/js/shared/context/user-features-context.tsx b/services/web/frontend/js/shared/context/user-features-context.tsx new file mode 100644 index 0000000000..2cc9ba932d --- /dev/null +++ b/services/web/frontend/js/shared/context/user-features-context.tsx @@ -0,0 +1,69 @@ +import { + createContext, + FC, + useCallback, + useContext, + useEffect, + useState, +} from 'react' +import { User } from '../../../../types/user' +import { useUserContext } from './user-context' +import { useReceiveUser } from '../hooks/user-channel/use-receive-user' +import { getJSON } from '@/infrastructure/fetch-json' +import { useEditorContext } from './editor-context' + +export const UserFeaturesContext = createContext(undefined) + +export const UserFeaturesProvider: FC = ({ + children, +}) => { + const user = useUserContext() + const { writefullInstance } = useEditorContext() + const [features, setFeatures] = useState(user.features) + + useReceiveUser( + useCallback(data => { + if (data?.features) { + setFeatures(data.features) + } + }, []) + ) + + useEffect(() => { + const listener = async ({ isPremium }: { isPremium: boolean }) => { + if (features?.aiErrorAssistant === isPremium) { + // the user is premium on writefull and has the AI assist, no need to refresh the features + return + } + const newFeatures = await getJSON('/user/features') + setFeatures(newFeatures) + } + + writefullInstance?.addEventListener('writefull-login-complete', listener) + + return () => { + writefullInstance?.removeEventListener( + 'writefull-login-complete', + listener + ) + } + }, [features?.aiErrorAssistant, writefullInstance]) + + return ( + + {children} + + ) +} + +export function useUserFeaturesContext() { + const context = useContext(UserFeaturesContext) + + if (!context) { + throw new Error( + 'useUserFeaturesContext is only available inside UserFeaturesContext, or `ol-user` meta is not defined' + ) + } + + return context +} diff --git a/services/web/test/frontend/features/subscription/components/successful-subscription/successful-subscription.test.tsx b/services/web/test/frontend/features/subscription/components/successful-subscription/successful-subscription.test.tsx index 33979e6b9e..90453c9349 100644 --- a/services/web/test/frontend/features/subscription/components/successful-subscription/successful-subscription.test.tsx +++ b/services/web/test/frontend/features/subscription/components/successful-subscription/successful-subscription.test.tsx @@ -4,21 +4,28 @@ import SuccessfulSubscription from '../../../../../../frontend/js/features/subsc import { renderWithSubscriptionDashContext } from '../../helpers/render-with-subscription-dash-context' import { annualActiveSubscription } from '../../fixtures/subscriptions' import { ExposedSettings } from '../../../../../../types/exposed-settings' +import { UserProvider } from '@/shared/context/user-context' describe('successful subscription page', function () { it('renders the invoices link', function () { const adminEmail = 'foo@example.com' - renderWithSubscriptionDashContext(, { - metaTags: [ - { - name: 'ol-ExposedSettings', - value: { - adminEmail, - } as ExposedSettings, - }, - { name: 'ol-subscription', value: annualActiveSubscription }, - ], - }) + renderWithSubscriptionDashContext( + + + , + + { + metaTags: [ + { + name: 'ol-ExposedSettings', + value: { + adminEmail, + } as ExposedSettings, + }, + { name: 'ol-subscription', value: annualActiveSubscription }, + ], + } + ) screen.getByRole('heading', { name: /thanks for subscribing/i }) const alert = screen.getByRole('alert') diff --git a/services/web/test/unit/src/Project/ProjectControllerTests.js b/services/web/test/unit/src/Project/ProjectControllerTests.js index 71de5023b1..46427171da 100644 --- a/services/web/test/unit/src/Project/ProjectControllerTests.js +++ b/services/web/test/unit/src/Project/ProjectControllerTests.js @@ -1121,44 +1121,6 @@ describe('ProjectController', function () { this.ProjectController.loadEditor(this.req, this.res) }) }) - - describe('when fetching the users featureSet', function () { - beforeEach(function () { - this.Modules.promises.hooks.fire = sinon.stub().resolves() - this.user.features = {} - }) - - it('should take into account features overrides from modules', function (done) { - // this case occurs when the user has bought the ai bundle on WF, which should include our error assistant - const bundleFeatures = { aiErrorAssistant: true } - this.user.features = { aiErrorAssistant: false } - this.Modules.promises.hooks.fire = sinon - .stub() - .resolves([bundleFeatures]) - this.res.render = (pageName, opts) => { - expect(opts.user.features).to.deep.equal(bundleFeatures) - this.Modules.promises.hooks.fire.should.have.been.calledWith( - 'getModuleProvidedFeatures', - this.user._id - ) - done() - } - this.ProjectController.loadEditor(this.req, this.res) - }) - - it('should handle modules not returning any features', function (done) { - this.Modules.promises.hooks.fire = sinon.stub().resolves([]) - this.res.render = (pageName, opts) => { - expect(opts.user.features).to.deep.equal({}) - this.Modules.promises.hooks.fire.should.have.been.calledWith( - 'getModuleProvidedFeatures', - this.user._id - ) - done() - } - this.ProjectController.loadEditor(this.req, this.res) - }) - }) }) describe('userProjectsJson', function () { diff --git a/services/web/test/unit/src/Subscription/SubscriptionControllerTests.js b/services/web/test/unit/src/Subscription/SubscriptionControllerTests.js index 27ba5bf85b..5b6e86663e 100644 --- a/services/web/test/unit/src/Subscription/SubscriptionControllerTests.js +++ b/services/web/test/unit/src/Subscription/SubscriptionControllerTests.js @@ -158,6 +158,7 @@ describe('SubscriptionController', function () { './FeaturesUpdater': (this.FeaturesUpdater = { promises: { hasFeaturesViaWritefull: sinon.stub().resolves(false), + refreshFeatures: sinon.stub().resolves({ features: {} }), }, }), './GroupPlansData': (this.GroupPlansData = {}), @@ -186,6 +187,11 @@ describe('SubscriptionController', function () { '../../util/currency': (this.currency = { formatCurrency: sinon.stub(), }), + '../../models/User': { + User: { + findById: sinon.stub().resolves(this.user), + }, + }, }, }) @@ -221,7 +227,10 @@ describe('SubscriptionController', function () { title: 'thank_you', personalSubscription: 'foo', postCheckoutRedirect: undefined, - user: this.user, + user: { + _id: this.user._id, + features: this.user.features, + }, }) done() } diff --git a/services/web/test/unit/src/User/UserGetterTests.js b/services/web/test/unit/src/User/UserGetterTests.js index 91df5d8c6d..0e0c170fd6 100644 --- a/services/web/test/unit/src/User/UserGetterTests.js +++ b/services/web/test/unit/src/User/UserGetterTests.js @@ -19,7 +19,7 @@ describe('UserGetter', function () { beforeEach(function () { const confirmedAt = new Date() this.fakeUser = { - _id: '12390i', + _id: new ObjectId(), email: 'email2@foo.bar', emails: [ { @@ -45,6 +45,10 @@ describe('UserGetter', function () { } this.getUserAffiliations = sinon.stub().resolves([]) + this.Modules = { + promises: { hooks: { fire: sinon.stub().resolves() } }, + } + this.UserGetter = SandboxedModule.require(modulePath, { requires: { '../Helpers/Mongo': { normalizeQuery, normalizeMultiQuery }, @@ -63,6 +67,7 @@ describe('UserGetter', function () { '../../models/User': { User: (this.User = {}), }, + '../../infrastructure/Modules': this.Modules, }, }) }) @@ -1259,4 +1264,56 @@ describe('UserGetter', function () { }) }) }) + + describe('getUserFeatures', function () { + beforeEach(function () { + this.Modules.promises.hooks.fire = sinon.stub().resolves() + this.fakeUser.features = {} + }) + + it('should return user features', function (done) { + this.fakeUser.features = { feature1: true, feature2: false } + this.UserGetter.getUserFeatures(new ObjectId(), (error, features) => { + expect(error).to.not.exist + expect(features).to.deep.equal(this.fakeUser.features) + done() + }) + }) + + it('should return user features when using promises', async function () { + this.fakeUser.features = { feature1: true, feature2: false } + const features = await this.UserGetter.promises.getUserFeatures( + this.fakeUser._id + ) + expect(features).to.deep.equal(this.fakeUser.features) + }) + + it('should take into account features overrides from modules', async function () { + // this case occurs when the user has bought the ai bundle on WF, which should include our error assistant + const bundleFeatures = { aiErrorAssistant: true } + this.fakeUser.features = { aiErrorAssistant: false } + this.Modules.promises.hooks.fire = sinon.stub().resolves([bundleFeatures]) + const features = await this.UserGetter.promises.getUserFeatures( + this.fakeUser._id + ) + expect(features).to.deep.equal(bundleFeatures) + this.Modules.promises.hooks.fire.should.have.been.calledWith( + 'getModuleProvidedFeatures', + this.fakeUser._id + ) + }) + + it('should handle modules not returning any features', async function () { + this.Modules.promises.hooks.fire = sinon.stub().resolves([]) + this.fakeUser.features = { test: true } + const features = await this.UserGetter.promises.getUserFeatures( + this.fakeUser._id + ) + expect(features).to.deep.equal({ test: true }) + this.Modules.promises.hooks.fire.should.have.been.calledWith( + 'getModuleProvidedFeatures', + this.fakeUser._id + ) + }) + }) }) diff --git a/services/web/test/unit/src/User/UserInfoControllerTests.js b/services/web/test/unit/src/User/UserInfoControllerTests.js index 022dbd2d7f..dd90ac3b00 100644 --- a/services/web/test/unit/src/User/UserInfoControllerTests.js +++ b/services/web/test/unit/src/User/UserInfoControllerTests.js @@ -10,7 +10,11 @@ describe('UserInfoController', function () { beforeEach(function () { this.UserDeleter = { deleteUser: sinon.stub().callsArgWith(1) } this.UserUpdater = { updatePersonalInfo: sinon.stub() } - this.UserGetter = {} + this.UserGetter = { + promises: { + getUserFeatures: sinon.stub(), + }, + } this.UserInfoController = SandboxedModule.require(modulePath, { requires: { @@ -148,4 +152,74 @@ describe('UserInfoController', function () { }) }) }) + + describe('getUserFeatures', function () { + describe('when the user is logged in', function () { + beforeEach(async function () { + this.user_id = new ObjectId().toString() + this.features = { + collaborators: 10, + trackChanges: true, + references: true, + } + this.SessionManager.getLoggedInUserId.returns(this.user_id) + this.UserGetter.promises.getUserFeatures.resolves(this.features) + await this.UserInfoController.getUserFeatures( + this.req, + this.res, + this.next + ) + }) + + it('should fetch the user features', function () { + expect(this.UserGetter.promises.getUserFeatures.callCount).to.equal(1) + expect( + this.UserGetter.promises.getUserFeatures.calledWith(this.user_id) + ).to.equal(true) + }) + + it('should return the features as JSON', function () { + expect(this.res.json.callCount).to.equal(1) + expect(this.res.json.calledWith(this.features)).to.equal(true) + }) + }) + + describe('when the user is not logged in', function () { + beforeEach(async function () { + this.SessionManager.getLoggedInUserId.returns(null) + await this.UserInfoController.getUserFeatures( + this.req, + this.res, + this.next + ) + }) + + it('should call next with an error', function () { + expect(this.next.callCount).to.equal(1) + expect(this.next.firstCall.args[0]).to.be.an.instanceof(Error) + expect(this.next.firstCall.args[0].message).to.equal( + 'User is not logged in' + ) + }) + }) + + describe('when fetching features fails', function () { + beforeEach(async function () { + this.user_id = new ObjectId().toString() + this.error = new Error('something went wrong') + this.SessionManager.getLoggedInUserId.returns(this.user_id) + this.UserGetter.promises.getUserFeatures.rejects(this.error) + await this.UserInfoController.getUserFeatures( + this.req, + this.res, + this.next + ) + }) + + it('should call next with the error', function () { + expect(this.next.callCount).to.equal(1) + expect(this.next.firstCall.args[0]).to.equal(this.error) + }) + }) + }) }) From 8e31c30ec7b929fec164f25c169d00c314f596d6 Mon Sep 17 00:00:00 2001 From: David <33458145+davidmcpowell@users.noreply.github.com> Date: Mon, 12 May 2025 14:44:54 +0100 Subject: [PATCH 083/194] Merge pull request #25398 from overleaf/dp-file-tree-proptypes Remove PropTypes from file-tree components GitOrigin-RevId: 7ecbf9778da59b852be8678c5dff61e13d927b9c --- .../file-tree-create/danger-message.jsx | 9 ------ .../file-tree-create/danger-message.tsx | 9 ++++++ .../{error-message.jsx => error-message.tsx} | 11 ++++--- ...ut.jsx => file-tree-create-name-input.tsx} | 28 +++++++--------- ...=> file-tree-modal-create-file-footer.tsx} | 30 ++++++++--------- ...x => file-tree-modal-create-file-mode.tsx} | 17 +++++----- ... => file-tree-draggable-preview-layer.tsx} | 32 +++++++------------ ....jsx => file-tree-modal-create-folder.tsx} | 25 +++++++-------- 8 files changed, 73 insertions(+), 88 deletions(-) delete mode 100644 services/web/frontend/js/features/file-tree/components/file-tree-create/danger-message.jsx create mode 100644 services/web/frontend/js/features/file-tree/components/file-tree-create/danger-message.tsx rename services/web/frontend/js/features/file-tree/components/file-tree-create/{error-message.jsx => error-message.tsx} (94%) rename services/web/frontend/js/features/file-tree/components/file-tree-create/{file-tree-create-name-input.jsx => file-tree-create-name-input.tsx} (85%) rename services/web/frontend/js/features/file-tree/components/file-tree-create/{file-tree-modal-create-file-footer.jsx => file-tree-modal-create-file-footer.tsx} (79%) rename services/web/frontend/js/features/file-tree/components/file-tree-create/{file-tree-modal-create-file-mode.jsx => file-tree-modal-create-file-mode.tsx} (75%) rename services/web/frontend/js/features/file-tree/components/{file-tree-draggable-preview-layer.jsx => file-tree-draggable-preview-layer.tsx} (74%) rename services/web/frontend/js/features/file-tree/components/modals/{file-tree-modal-create-folder.jsx => file-tree-modal-create-folder.tsx} (87%) diff --git a/services/web/frontend/js/features/file-tree/components/file-tree-create/danger-message.jsx b/services/web/frontend/js/features/file-tree/components/file-tree-create/danger-message.jsx deleted file mode 100644 index ceaafc6fa8..0000000000 --- a/services/web/frontend/js/features/file-tree/components/file-tree-create/danger-message.jsx +++ /dev/null @@ -1,9 +0,0 @@ -import OLNotification from '@/features/ui/components/ol/ol-notification' -import PropTypes from 'prop-types' - -export default function DangerMessage({ children }) { - return -} -DangerMessage.propTypes = { - children: PropTypes.any.isRequired, -} diff --git a/services/web/frontend/js/features/file-tree/components/file-tree-create/danger-message.tsx b/services/web/frontend/js/features/file-tree/components/file-tree-create/danger-message.tsx new file mode 100644 index 0000000000..d6b7173cb0 --- /dev/null +++ b/services/web/frontend/js/features/file-tree/components/file-tree-create/danger-message.tsx @@ -0,0 +1,9 @@ +import OLNotification from '@/features/ui/components/ol/ol-notification' + +export default function DangerMessage({ + children, +}: { + children: React.ReactNode +}) { + return +} diff --git a/services/web/frontend/js/features/file-tree/components/file-tree-create/error-message.jsx b/services/web/frontend/js/features/file-tree/components/file-tree-create/error-message.tsx similarity index 94% rename from services/web/frontend/js/features/file-tree/components/file-tree-create/error-message.jsx rename to services/web/frontend/js/features/file-tree/components/file-tree-create/error-message.tsx index 0c9ab381c6..02cc083928 100644 --- a/services/web/frontend/js/features/file-tree/components/file-tree-create/error-message.jsx +++ b/services/web/frontend/js/features/file-tree/components/file-tree-create/error-message.tsx @@ -1,4 +1,3 @@ -import PropTypes from 'prop-types' import { useTranslation } from 'react-i18next' import { FetchError } from '../../../../infrastructure/fetch-json' import RedirectToLogin from './redirect-to-login' @@ -9,7 +8,12 @@ import { } from '../../errors' import DangerMessage from './danger-message' -export default function ErrorMessage({ error }) { +// TODO: Update the error type when we properly type FileTreeActionableContext +export default function ErrorMessage({ + error, +}: { + error: string | Record +}) { const { t } = useTranslation() const fileNameLimit = 150 @@ -119,6 +123,3 @@ export default function ErrorMessage({ error }) { return {t('generic_something_went_wrong')} } } -ErrorMessage.propTypes = { - error: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired, -} diff --git a/services/web/frontend/js/features/file-tree/components/file-tree-create/file-tree-create-name-input.jsx b/services/web/frontend/js/features/file-tree/components/file-tree-create/file-tree-create-name-input.tsx similarity index 85% rename from services/web/frontend/js/features/file-tree/components/file-tree-create/file-tree-create-name-input.jsx rename to services/web/frontend/js/features/file-tree/components/file-tree-create/file-tree-create-name-input.tsx index a76768c4df..80c53f1b63 100644 --- a/services/web/frontend/js/features/file-tree/components/file-tree-create/file-tree-create-name-input.jsx +++ b/services/web/frontend/js/features/file-tree/components/file-tree-create/file-tree-create-name-input.tsx @@ -1,7 +1,6 @@ import { useRef, useEffect } from 'react' import { useTranslation } from 'react-i18next' import { useFileTreeCreateName } from '../../contexts/file-tree-create-name' -import PropTypes from 'prop-types' import { BlockedFilenameError, DuplicateFilenameError, @@ -23,6 +22,15 @@ export default function FileTreeCreateNameInput({ placeholder, error, inFlight, +}: { + label?: string + focusName?: boolean + classes?: { + formGroup?: string + } + placeholder?: string + error?: string | Record + inFlight: boolean }) { const { t } = useTranslation() @@ -30,7 +38,7 @@ export default function FileTreeCreateNameInput({ const { name, setName, touchedName, validName } = useFileTreeCreateName() // focus the first part of the filename if needed - const inputRef = useRef(null) + const inputRef = useRef(null) useEffect(() => { if (inputRef.current && focusName) { @@ -73,18 +81,7 @@ export default function FileTreeCreateNameInput({ ) } -FileTreeCreateNameInput.propTypes = { - focusName: PropTypes.bool, - label: PropTypes.string, - classes: PropTypes.shape({ - formGroup: PropTypes.string, - }), - placeholder: PropTypes.string, - error: PropTypes.oneOfType([PropTypes.object, PropTypes.string]), - inFlight: PropTypes.bool.isRequired, -} - -function ErrorMessage({ error }) { +function ErrorMessage({ error }: { error: string | Record }) { const { t } = useTranslation() // if (typeof error === 'string') { @@ -124,6 +121,3 @@ function ErrorMessage({ error }) { return null // other errors are displayed elsewhere } } -ErrorMessage.propTypes = { - error: PropTypes.oneOfType([PropTypes.object, PropTypes.string]).isRequired, -} diff --git a/services/web/frontend/js/features/file-tree/components/file-tree-create/file-tree-modal-create-file-footer.jsx b/services/web/frontend/js/features/file-tree/components/file-tree-create/file-tree-modal-create-file-footer.tsx similarity index 79% rename from services/web/frontend/js/features/file-tree/components/file-tree-create/file-tree-modal-create-file-footer.jsx rename to services/web/frontend/js/features/file-tree/components/file-tree-create/file-tree-modal-create-file-footer.tsx index 3432d7b1b8..d1966f1b5d 100644 --- a/services/web/frontend/js/features/file-tree/components/file-tree-create/file-tree-modal-create-file-footer.jsx +++ b/services/web/frontend/js/features/file-tree/components/file-tree-create/file-tree-modal-create-file-footer.tsx @@ -2,7 +2,6 @@ import { useTranslation } from 'react-i18next' import { useFileTreeCreateForm } from '../../contexts/file-tree-create-form' import { useFileTreeActionable } from '../../contexts/file-tree-actionable' import { useFileTreeData } from '../../../../shared/context/file-tree-data-context' -import PropTypes from 'prop-types' import OLButton from '@/features/ui/components/ol/ol-button' import OLNotification from '@/features/ui/components/ol/ol-notification' @@ -26,21 +25,33 @@ export function FileTreeModalCreateFileFooterContent({ valid, fileCount, inFlight, - newFileCreateMode, cancel, + newFileCreateMode, +}: { + valid: boolean + fileCount: + | { + limit: number + status: string + value: number + } + | number + inFlight: boolean + cancel: () => void + newFileCreateMode?: string }) { const { t } = useTranslation() return ( <> - {fileCount.status === 'warning' && ( + {typeof fileCount !== 'number' && fileCount.status === 'warning' && (
{t('project_approaching_file_limit')} ({fileCount.value}/ {fileCount.limit})
)} - {fileCount.status === 'error' && ( + {typeof fileCount !== 'number' && fileCount.status === 'error' && ( ) } -FileTreeModalCreateFileFooterContent.propTypes = { - cancel: PropTypes.func.isRequired, - fileCount: PropTypes.shape({ - limit: PropTypes.number.isRequired, - status: PropTypes.string.isRequired, - value: PropTypes.number.isRequired, - }).isRequired, - inFlight: PropTypes.bool.isRequired, - newFileCreateMode: PropTypes.string, - valid: PropTypes.bool.isRequired, -} diff --git a/services/web/frontend/js/features/file-tree/components/file-tree-create/file-tree-modal-create-file-mode.jsx b/services/web/frontend/js/features/file-tree/components/file-tree-create/file-tree-modal-create-file-mode.tsx similarity index 75% rename from services/web/frontend/js/features/file-tree/components/file-tree-create/file-tree-modal-create-file-mode.jsx rename to services/web/frontend/js/features/file-tree/components/file-tree-create/file-tree-modal-create-file-mode.tsx index 259dac3144..40a3d7ba9e 100644 --- a/services/web/frontend/js/features/file-tree/components/file-tree-create/file-tree-modal-create-file-mode.jsx +++ b/services/web/frontend/js/features/file-tree/components/file-tree-create/file-tree-modal-create-file-mode.tsx @@ -1,11 +1,18 @@ import classnames from 'classnames' -import PropTypes from 'prop-types' import { useFileTreeActionable } from '../../contexts/file-tree-actionable' import * as eventTracking from '../../../../infrastructure/event-tracking' import OLButton from '@/features/ui/components/ol/ol-button' import MaterialIcon from '@/shared/components/material-icon' -export default function FileTreeModalCreateFileMode({ mode, icon, label }) { +export default function FileTreeModalCreateFileMode({ + mode, + icon, + label, +}: { + mode: string + icon: string + label: string +}) { const { newFileCreateMode, startCreatingFile } = useFileTreeActionable() const handleClick = () => { @@ -27,9 +34,3 @@ export default function FileTreeModalCreateFileMode({ mode, icon, label }) { ) } - -FileTreeModalCreateFileMode.propTypes = { - mode: PropTypes.string.isRequired, - icon: PropTypes.string.isRequired, - label: PropTypes.string.isRequired, -} diff --git a/services/web/frontend/js/features/file-tree/components/file-tree-draggable-preview-layer.jsx b/services/web/frontend/js/features/file-tree/components/file-tree-draggable-preview-layer.tsx similarity index 74% rename from services/web/frontend/js/features/file-tree/components/file-tree-draggable-preview-layer.jsx rename to services/web/frontend/js/features/file-tree/components/file-tree-draggable-preview-layer.tsx index fffc4f5ae1..e9076cc3c3 100644 --- a/services/web/frontend/js/features/file-tree/components/file-tree-draggable-preview-layer.jsx +++ b/services/web/frontend/js/features/file-tree/components/file-tree-draggable-preview-layer.tsx @@ -1,6 +1,6 @@ import { useRef } from 'react' -import PropTypes from 'prop-types' import classNames from 'classnames' +import { XYCoord } from 'react-dnd' // a custom component rendered on top of a draggable area that renders the // dragged item. See @@ -12,8 +12,13 @@ function FileTreeDraggablePreviewLayer({ isDragging, item, clientOffset, +}: { + isOver: boolean + isDragging: boolean + item: { title: string } + clientOffset: XYCoord | null }) { - const ref = useRef() + const ref = useRef(null) return (
{title}
} -DraggablePreviewItem.propTypes = { - title: PropTypes.string.isRequired, -} - // makes the preview item follow the cursor. // See https://react-dnd.github.io/react-dnd/docs/api/drag-layer-monitor -function getItemStyle(clientOffset, containerOffset) { +function getItemStyle( + clientOffset: XYCoord | null, + containerOffset: DOMRect | undefined +) { if (!containerOffset || !clientOffset) { return { display: 'none', diff --git a/services/web/frontend/js/features/file-tree/components/modals/file-tree-modal-create-folder.jsx b/services/web/frontend/js/features/file-tree/components/modals/file-tree-modal-create-folder.tsx similarity index 87% rename from services/web/frontend/js/features/file-tree/components/modals/file-tree-modal-create-folder.jsx rename to services/web/frontend/js/features/file-tree/components/modals/file-tree-modal-create-folder.tsx index 850b603229..7342c1f752 100644 --- a/services/web/frontend/js/features/file-tree/components/modals/file-tree-modal-create-folder.jsx +++ b/services/web/frontend/js/features/file-tree/components/modals/file-tree-modal-create-folder.tsx @@ -1,5 +1,4 @@ -import { useEffect, useState } from 'react' -import PropTypes from 'prop-types' +import React, { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import { useRefWithAutoFocus } from '../../../../shared/hooks/use-ref-with-auto-focus' import { useFileTreeActionable } from '../../contexts/file-tree-actionable' @@ -109,19 +108,25 @@ function InputName({ validName, setValidName, handleCreateFolder, +}: { + name: string + setName: (name: string) => void + validName: boolean + setValidName: (validName: boolean) => void + handleCreateFolder: () => void }) { - const { autoFocusedRef } = useRefWithAutoFocus() + const { autoFocusedRef } = useRefWithAutoFocus() - function handleFocus(ev) { + function handleFocus(ev: React.FocusEvent) { ev.target.setSelectionRange(0, -1) } - function handleChange(ev) { + function handleChange(ev: React.ChangeEvent) { setValidName(isCleanFilename(ev.target.value.trim())) setName(ev.target.value) } - function handleKeyDown(ev) { + function handleKeyDown(ev: React.KeyboardEvent) { if (ev.key === 'Enter' && validName) { handleCreateFolder() } @@ -140,12 +145,4 @@ function InputName({ ) } -InputName.propTypes = { - name: PropTypes.string.isRequired, - setName: PropTypes.func.isRequired, - validName: PropTypes.bool.isRequired, - setValidName: PropTypes.func.isRequired, - handleCreateFolder: PropTypes.func.isRequired, -} - export default FileTreeModalCreateFolder From 8d940ad84101953d7fb6e2c86cf8a0ed3450503a Mon Sep 17 00:00:00 2001 From: David <33458145+davidmcpowell@users.noreply.github.com> Date: Mon, 12 May 2025 14:45:08 +0100 Subject: [PATCH 084/194] Merge pull request #25387 from overleaf/dp-editor-toolbar-proptypes Remove proptypes from editor-navigation-toolbar components GitOrigin-RevId: 77a1c4e13e3da6c06bb515b0137da2f70bfdf4a8 --- ...ggle-button.jsx => chat-toggle-button.tsx} | 17 +++--- ...obranding-logo.jsx => cobranding-logo.tsx} | 12 ++-- .../editor-navigation-toolbar-root.tsx | 6 +- ...e-button.jsx => history-toggle-button.tsx} | 7 +-- .../{menu-button.jsx => menu-button.tsx} | 7 +-- ...ers-widget.jsx => online-users-widget.tsx} | 54 ++++++++--------- ...ct-button.jsx => share-project-button.tsx} | 7 +-- ...{toolbar-header.jsx => toolbar-header.tsx} | 58 ++++++++++--------- ...on.jsx => track-changes-toggle-button.tsx} | 11 ++-- ...{upgrade-prompt.jsx => upgrade-prompt.tsx} | 2 +- .../js/shared/context/editor-context.tsx | 13 +---- services/web/types/cobranding.ts | 11 ++++ 12 files changed, 94 insertions(+), 111 deletions(-) rename services/web/frontend/js/features/editor-navigation-toolbar/components/{chat-toggle-button.jsx => chat-toggle-button.tsx} (76%) rename services/web/frontend/js/features/editor-navigation-toolbar/components/{cobranding-logo.jsx => cobranding-logo.tsx} (67%) rename services/web/frontend/js/features/editor-navigation-toolbar/components/{history-toggle-button.jsx => history-toggle-button.tsx} (77%) rename services/web/frontend/js/features/editor-navigation-toolbar/components/{menu-button.jsx => menu-button.tsx} (77%) rename services/web/frontend/js/features/editor-navigation-toolbar/components/{online-users-widget.jsx => online-users-widget.tsx} (77%) rename services/web/frontend/js/features/editor-navigation-toolbar/components/{share-project-button.jsx => share-project-button.tsx} (77%) rename services/web/frontend/js/features/editor-navigation-toolbar/components/{toolbar-header.jsx => toolbar-header.tsx} (82%) rename services/web/frontend/js/features/editor-navigation-toolbar/components/{track-changes-toggle-button.jsx => track-changes-toggle-button.tsx} (81%) rename services/web/frontend/js/features/editor-navigation-toolbar/components/{upgrade-prompt.jsx => upgrade-prompt.tsx} (96%) create mode 100644 services/web/types/cobranding.ts diff --git a/services/web/frontend/js/features/editor-navigation-toolbar/components/chat-toggle-button.jsx b/services/web/frontend/js/features/editor-navigation-toolbar/components/chat-toggle-button.tsx similarity index 76% rename from services/web/frontend/js/features/editor-navigation-toolbar/components/chat-toggle-button.jsx rename to services/web/frontend/js/features/editor-navigation-toolbar/components/chat-toggle-button.tsx index 78afcdde42..3336d59aa6 100644 --- a/services/web/frontend/js/features/editor-navigation-toolbar/components/chat-toggle-button.jsx +++ b/services/web/frontend/js/features/editor-navigation-toolbar/components/chat-toggle-button.tsx @@ -1,10 +1,17 @@ -import PropTypes from 'prop-types' import classNames from 'classnames' import { useTranslation } from 'react-i18next' import MaterialIcon from '@/shared/components/material-icon' import OLBadge from '@/features/ui/components/ol/ol-badge' -function ChatToggleButton({ chatIsOpen, unreadMessageCount, onClick }) { +function ChatToggleButton({ + chatIsOpen, + unreadMessageCount, + onClick, +}: { + chatIsOpen: boolean + unreadMessageCount: number + onClick: () => void +}) { const { t } = useTranslation() const classes = classNames('btn', 'btn-full-height', { active: chatIsOpen }) @@ -26,10 +33,4 @@ function ChatToggleButton({ chatIsOpen, unreadMessageCount, onClick }) { ) } -ChatToggleButton.propTypes = { - chatIsOpen: PropTypes.bool, - unreadMessageCount: PropTypes.number.isRequired, - onClick: PropTypes.func.isRequired, -} - export default ChatToggleButton diff --git a/services/web/frontend/js/features/editor-navigation-toolbar/components/cobranding-logo.jsx b/services/web/frontend/js/features/editor-navigation-toolbar/components/cobranding-logo.tsx similarity index 67% rename from services/web/frontend/js/features/editor-navigation-toolbar/components/cobranding-logo.jsx rename to services/web/frontend/js/features/editor-navigation-toolbar/components/cobranding-logo.tsx index 80f3bc12b8..56d9f7b6ab 100644 --- a/services/web/frontend/js/features/editor-navigation-toolbar/components/cobranding-logo.jsx +++ b/services/web/frontend/js/features/editor-navigation-toolbar/components/cobranding-logo.tsx @@ -1,9 +1,11 @@ -import PropTypes from 'prop-types' - function CobrandingLogo({ brandVariationHomeUrl, brandVariationName, logoImgUrl, +}: { + brandVariationHomeUrl: string + brandVariationName: string + logoImgUrl: string }) { return ( void openShareProjectModal: () => void }) { @@ -93,7 +94,7 @@ const EditorNavigationToolbarRoot = React.memo( }, [setLeftMenuShown]) const goToUser = useCallback( - (user: any) => { + (user: OnlineUser) => { if (user.doc && typeof user.row === 'number') { openDoc(user.doc, { gotoLine: user.row + 1 }) } @@ -103,7 +104,6 @@ const EditorNavigationToolbarRoot = React.memo( return ( void }) { const { t } = useTranslation() return ( @@ -16,8 +15,4 @@ function HistoryToggleButton({ onClick }) { ) } -HistoryToggleButton.propTypes = { - onClick: PropTypes.func.isRequired, -} - export default HistoryToggleButton diff --git a/services/web/frontend/js/features/editor-navigation-toolbar/components/menu-button.jsx b/services/web/frontend/js/features/editor-navigation-toolbar/components/menu-button.tsx similarity index 77% rename from services/web/frontend/js/features/editor-navigation-toolbar/components/menu-button.jsx rename to services/web/frontend/js/features/editor-navigation-toolbar/components/menu-button.tsx index fa3d8fa46c..fb059737dd 100644 --- a/services/web/frontend/js/features/editor-navigation-toolbar/components/menu-button.jsx +++ b/services/web/frontend/js/features/editor-navigation-toolbar/components/menu-button.tsx @@ -1,8 +1,7 @@ -import PropTypes from 'prop-types' import { useTranslation } from 'react-i18next' import MaterialIcon from '@/shared/components/material-icon' -function MenuButton({ onClick }) { +function MenuButton({ onClick }: { onClick: () => void }) { const { t } = useTranslation() return ( @@ -15,8 +14,4 @@ function MenuButton({ onClick }) { ) } -MenuButton.propTypes = { - onClick: PropTypes.func.isRequired, -} - export default MenuButton diff --git a/services/web/frontend/js/features/editor-navigation-toolbar/components/online-users-widget.jsx b/services/web/frontend/js/features/editor-navigation-toolbar/components/online-users-widget.tsx similarity index 77% rename from services/web/frontend/js/features/editor-navigation-toolbar/components/online-users-widget.jsx rename to services/web/frontend/js/features/editor-navigation-toolbar/components/online-users-widget.tsx index 700bc5eb05..6188d5e8ea 100644 --- a/services/web/frontend/js/features/editor-navigation-toolbar/components/online-users-widget.jsx +++ b/services/web/frontend/js/features/editor-navigation-toolbar/components/online-users-widget.tsx @@ -1,5 +1,4 @@ import React from 'react' -import PropTypes from 'prop-types' import { useTranslation } from 'react-i18next' import { Dropdown, @@ -11,17 +10,27 @@ import { import { getBackgroundColorForUserId } from '@/shared/utils/colors' import OLTooltip from '@/features/ui/components/ol/ol-tooltip' import MaterialIcon from '@/shared/components/material-icon' +import { OnlineUser } from '@/features/ide-react/context/online-users-context' -function OnlineUsersWidget({ onlineUsers, goToUser }) { +function OnlineUsersWidget({ + onlineUsers, + goToUser, +}: { + onlineUsers: OnlineUser[] + goToUser: (user: OnlineUser) => void +}) { const { t } = useTranslation() const shouldDisplayDropdown = onlineUsers.length >= 4 if (shouldDisplayDropdown) { return ( - + @@ -63,17 +72,15 @@ function OnlineUsersWidget({ onlineUsers, goToUser }) { } } -OnlineUsersWidget.propTypes = { - onlineUsers: PropTypes.arrayOf( - PropTypes.shape({ - user_id: PropTypes.string.isRequired, - name: PropTypes.string.isRequired, - }) - ).isRequired, - goToUser: PropTypes.func.isRequired, -} - -function UserIcon({ user, showName, onClick }) { +function UserIcon({ + user, + showName, + onClick, +}: { + user: OnlineUser + showName?: boolean + onClick?: (user: OnlineUser) => void +}) { const backgroundColor = getBackgroundColorForUserId(user.user_id) function handleOnClick() { @@ -93,16 +100,10 @@ function UserIcon({ user, showName, onClick }) { ) } -UserIcon.propTypes = { - user: PropTypes.shape({ - user_id: PropTypes.string.isRequired, - name: PropTypes.string.isRequired, - }), - showName: PropTypes.bool, - onClick: PropTypes.func, -} - -const DropDownToggleButton = React.forwardRef((props, ref) => { +const DropDownToggleButton = React.forwardRef< + HTMLButtonElement, + { onlineUserCount: number; onClick: React.MouseEventHandler } +>((props, ref) => { const { t } = useTranslation() return ( { DropDownToggleButton.displayName = 'DropDownToggleButton' -DropDownToggleButton.propTypes = { - onlineUserCount: PropTypes.number.isRequired, - onClick: PropTypes.func, -} - export default OnlineUsersWidget diff --git a/services/web/frontend/js/features/editor-navigation-toolbar/components/share-project-button.jsx b/services/web/frontend/js/features/editor-navigation-toolbar/components/share-project-button.tsx similarity index 77% rename from services/web/frontend/js/features/editor-navigation-toolbar/components/share-project-button.jsx rename to services/web/frontend/js/features/editor-navigation-toolbar/components/share-project-button.tsx index 6c4123f03d..359ce92ffb 100644 --- a/services/web/frontend/js/features/editor-navigation-toolbar/components/share-project-button.jsx +++ b/services/web/frontend/js/features/editor-navigation-toolbar/components/share-project-button.tsx @@ -1,8 +1,7 @@ -import PropTypes from 'prop-types' import { useTranslation } from 'react-i18next' import MaterialIcon from '@/shared/components/material-icon' -function ShareProjectButton({ onClick }) { +function ShareProjectButton({ onClick }: { onClick: () => void }) { const { t } = useTranslation() return ( @@ -16,8 +15,4 @@ function ShareProjectButton({ onClick }) { ) } -ShareProjectButton.propTypes = { - onClick: PropTypes.func.isRequired, -} - export default ShareProjectButton diff --git a/services/web/frontend/js/features/editor-navigation-toolbar/components/toolbar-header.jsx b/services/web/frontend/js/features/editor-navigation-toolbar/components/toolbar-header.tsx similarity index 82% rename from services/web/frontend/js/features/editor-navigation-toolbar/components/toolbar-header.jsx rename to services/web/frontend/js/features/editor-navigation-toolbar/components/toolbar-header.tsx index 97bfa763cc..4304768c48 100644 --- a/services/web/frontend/js/features/editor-navigation-toolbar/components/toolbar-header.jsx +++ b/services/web/frontend/js/features/editor-navigation-toolbar/components/toolbar-header.tsx @@ -1,5 +1,4 @@ -import React from 'react' -import PropTypes from 'prop-types' +import React, { ElementType } from 'react' import { useTranslation } from 'react-i18next' import MenuButton from './menu-button' import CobrandingLogo from './cobranding-logo' @@ -18,13 +17,22 @@ import getMeta from '@/utils/meta' import { isSplitTestEnabled } from '@/utils/splitTestUtils' import { canUseNewEditor } from '@/features/ide-redesign/utils/new-editor-utils' import TryNewEditorButton from '../try-new-editor-button' +import { OnlineUser } from '@/features/ide-react/context/online-users-context' +import { Cobranding } from '../../../../../types/cobranding' -const [publishModalModules] = importOverleafModules('publishModal') +const [publishModalModules] = importOverleafModules('publishModal') as { + import: { default: ElementType } + path: string +}[] const PublishButton = publishModalModules?.import.default const offlineModeToolbarButtons = importOverleafModules( 'offlineModeToolbarButtons' -) +) as { + import: { default: ElementType } + path: string +}[] + // double opt-in const enableROMirrorOnClient = isSplitTestEnabled('ro-mirror-on-client') && @@ -51,6 +59,26 @@ const ToolbarHeader = React.memo(function ToolbarHeader({ hasRenamePermissions, openShareModal, trackChangesVisible, +}: { + cobranding: Cobranding | undefined + onShowLeftMenuClick: () => void + chatIsOpen: boolean + toggleChatOpen: () => void + reviewPanelOpen: boolean + toggleReviewPanelOpen: (e: React.MouseEvent) => void + historyIsOpen: boolean + toggleHistoryOpen: () => void + unreadMessageCount: number + onlineUsers: OnlineUser[] + goToUser: (user: OnlineUser) => void + isRestrictedTokenMember: boolean | undefined + hasPublishPermissions: boolean + chatVisible: boolean + projectName: string + renameProject: (name: string) => void + hasRenamePermissions: boolean + openShareModal: () => void + trackChangesVisible: boolean | undefined }) { const chatEnabled = getMeta('ol-chatEnabled') @@ -132,26 +160,4 @@ const ToolbarHeader = React.memo(function ToolbarHeader({ ) }) -ToolbarHeader.propTypes = { - onShowLeftMenuClick: PropTypes.func.isRequired, - cobranding: PropTypes.object, - chatIsOpen: PropTypes.bool, - toggleChatOpen: PropTypes.func.isRequired, - reviewPanelOpen: PropTypes.bool, - toggleReviewPanelOpen: PropTypes.func.isRequired, - historyIsOpen: PropTypes.bool, - toggleHistoryOpen: PropTypes.func.isRequired, - unreadMessageCount: PropTypes.number.isRequired, - onlineUsers: PropTypes.array.isRequired, - goToUser: PropTypes.func.isRequired, - isRestrictedTokenMember: PropTypes.bool, - hasPublishPermissions: PropTypes.bool, - chatVisible: PropTypes.bool, - projectName: PropTypes.string.isRequired, - renameProject: PropTypes.func.isRequired, - hasRenamePermissions: PropTypes.bool, - openShareModal: PropTypes.func.isRequired, - trackChangesVisible: PropTypes.bool, -} - export default ToolbarHeader diff --git a/services/web/frontend/js/features/editor-navigation-toolbar/components/track-changes-toggle-button.jsx b/services/web/frontend/js/features/editor-navigation-toolbar/components/track-changes-toggle-button.tsx similarity index 81% rename from services/web/frontend/js/features/editor-navigation-toolbar/components/track-changes-toggle-button.jsx rename to services/web/frontend/js/features/editor-navigation-toolbar/components/track-changes-toggle-button.tsx index 469dc7cd29..927e6ec2c5 100644 --- a/services/web/frontend/js/features/editor-navigation-toolbar/components/track-changes-toggle-button.jsx +++ b/services/web/frontend/js/features/editor-navigation-toolbar/components/track-changes-toggle-button.tsx @@ -1,4 +1,3 @@ -import PropTypes from 'prop-types' import classNames from 'classnames' import { useTranslation } from 'react-i18next' import MaterialIcon from '@/shared/components/material-icon' @@ -7,6 +6,10 @@ function TrackChangesToggleButton({ trackChangesIsOpen, disabled, onMouseDown, +}: { + trackChangesIsOpen: boolean + disabled?: boolean + onMouseDown: (e: React.MouseEvent) => void }) { const { t } = useTranslation() const classes = classNames('btn', 'btn-full-height', { @@ -30,10 +33,4 @@ function TrackChangesToggleButton({ ) } -TrackChangesToggleButton.propTypes = { - trackChangesIsOpen: PropTypes.bool, - disabled: PropTypes.bool, - onMouseDown: PropTypes.func.isRequired, -} - export default TrackChangesToggleButton diff --git a/services/web/frontend/js/features/editor-navigation-toolbar/components/upgrade-prompt.jsx b/services/web/frontend/js/features/editor-navigation-toolbar/components/upgrade-prompt.tsx similarity index 96% rename from services/web/frontend/js/features/editor-navigation-toolbar/components/upgrade-prompt.jsx rename to services/web/frontend/js/features/editor-navigation-toolbar/components/upgrade-prompt.tsx index 594dd9feec..b5bbc1a0ba 100644 --- a/services/web/frontend/js/features/editor-navigation-toolbar/components/upgrade-prompt.jsx +++ b/services/web/frontend/js/features/editor-navigation-toolbar/components/upgrade-prompt.tsx @@ -5,7 +5,7 @@ import OLButton from '@/features/ui/components/ol/ol-button' function UpgradePrompt() { const { t } = useTranslation() - function handleClick(e) { + function handleClick() { eventTracking.send('subscription-funnel', 'code-editor', 'upgrade') eventTracking.sendMB('upgrade-button-click', { source: 'code-editor' }) } diff --git a/services/web/frontend/js/shared/context/editor-context.tsx b/services/web/frontend/js/shared/context/editor-context.tsx index b08f05450a..a3c01d57dd 100644 --- a/services/web/frontend/js/shared/context/editor-context.tsx +++ b/services/web/frontend/js/shared/context/editor-context.tsx @@ -20,20 +20,11 @@ import { saveProjectSettings } from '@/features/editor-left-menu/utils/api' import { PermissionsLevel } from '@/features/ide-react/types/permissions' import { useModalsContext } from '@/features/ide-react/context/modals-context' import { WritefullAPI } from './types/writefull-instance' +import { Cobranding } from '../../../../types/cobranding' export const EditorContext = createContext< | { - cobranding?: { - logoImgUrl: string - brandVariationName: string - brandVariationId: number - brandId: number - brandVariationHomeUrl: string - publishGuideHtml?: string - partner?: string - brandedMenu?: boolean - submitBtnHtml?: string - } + cobranding?: Cobranding hasPremiumCompile?: boolean renameProject: (newName: string) => void setPermissionsLevel: (permissionsLevel: PermissionsLevel) => void diff --git a/services/web/types/cobranding.ts b/services/web/types/cobranding.ts new file mode 100644 index 0000000000..dc6822be84 --- /dev/null +++ b/services/web/types/cobranding.ts @@ -0,0 +1,11 @@ +export type Cobranding = { + logoImgUrl: string + brandVariationName: string + brandVariationId: number + brandId: number + brandVariationHomeUrl: string + publishGuideHtml?: string + partner?: string + brandedMenu?: boolean + submitBtnHtml?: string +} From 11d964649c6341362ce5f2c808b64874f5dd884a Mon Sep 17 00:00:00 2001 From: Tim Down <158919+timdown@users.noreply.github.com> Date: Mon, 12 May 2025 15:14:41 +0100 Subject: [PATCH 085/194] Merge pull request #25502 from overleaf/td-chat-open-bug Prevent chat opening by default for new user GitOrigin-RevId: 260050c26f6b9dee7ea52284dadb7ed00ce9eddb --- server-ce/test/project-sharing.spec.ts | 25 ++++++++---------- .../features/ide-react/hooks/use-chat-pane.ts | 26 +++++++++++++++---- 2 files changed, 32 insertions(+), 19 deletions(-) diff --git a/server-ce/test/project-sharing.spec.ts b/server-ce/test/project-sharing.spec.ts index ab71cb7dc9..e26439264b 100644 --- a/server-ce/test/project-sharing.spec.ts +++ b/server-ce/test/project-sharing.spec.ts @@ -36,14 +36,13 @@ describe('Project Sharing', function () { login('user@example.com') createProject(projectName) - // TODO(25342): re-enable - // // Add chat message - // cy.findByText('Chat').click() - // // wait for lazy loading of the chat pane - // cy.findByText('Send your first message to your collaborators') - // cy.get( - // 'textarea[placeholder="Send a message to your collaborators…"]' - // ).type('New Chat Message{enter}') + // Add chat message + cy.findByText('Chat').click() + // wait for lazy loading of the chat pane + cy.findByText('Send your first message to your collaborators') + cy.get( + 'textarea[placeholder="Send a message to your collaborators…"]' + ).type('New Chat Message{enter}') // Get link sharing links enableLinkSharing().then( @@ -95,9 +94,8 @@ describe('Project Sharing', function () { } function expectChatAccess() { - // TODO(25342): re-enable - // cy.findByText('Chat').click() - // cy.findByText('New Chat Message') + cy.findByText('Chat').click() + cy.findByText('New Chat Message') } function expectHistoryAccess() { @@ -111,9 +109,8 @@ describe('Project Sharing', function () { } function expectNoChatAccess() { - // TODO(25342): re-enable - // cy.findByText('Layout') // wait for lazy loading - // cy.findByText('Chat').should('not.exist') + cy.findByText('Layout') // wait for lazy loading + cy.findByText('Chat').should('not.exist') } function expectNoHistoryAccess() { diff --git a/services/web/frontend/js/features/ide-react/hooks/use-chat-pane.ts b/services/web/frontend/js/features/ide-react/hooks/use-chat-pane.ts index 0c6d816181..44b165fd57 100644 --- a/services/web/frontend/js/features/ide-react/hooks/use-chat-pane.ts +++ b/services/web/frontend/js/features/ide-react/hooks/use-chat-pane.ts @@ -1,6 +1,7 @@ import { useLayoutContext } from '@/shared/context/layout-context' import useCollapsiblePanel from '@/features/ide-react/hooks/use-collapsible-panel' -import { useCallback, useRef, useState } from 'react' +import useDebounce from '@/shared/hooks/use-debounce' +import { useCallback, useEffect, useRef, useState } from 'react' import { ImperativePanelHandle } from 'react-resizable-panels' export const useChatPane = () => { @@ -8,6 +9,17 @@ export const useChatPane = () => { const [resizing, setResizing] = useState(false) const panelRef = useRef(null) + // Keep track of a debounced local state variable for panel openness and + // only update the external openness state when the debounced value changes. + // This prevents successive calls to onCollapse and onExpand from + // react-resizable-panels updating the openness state multiple times in quick + // succession, which causes confusing behaviour that is different in React 17 + // and 18. Collapsing the chat pane on initialization is necessary because + // react-resizable-panels does not provide a way to specify both that a panel + // should be collapsed and a default size for the panel when expanded. + const [localIsOpen, setLocalIsOpen] = useState(isOpen) + const debouncedLocalIsOpen = useDebounce(localIsOpen, 100) + useCollapsiblePanel(isOpen, panelRef) const togglePane = useCallback(() => { @@ -15,12 +27,16 @@ export const useChatPane = () => { }, [setIsOpen]) const handlePaneExpand = useCallback(() => { - setIsOpen(true) - }, [setIsOpen]) + setLocalIsOpen(true) + }, []) const handlePaneCollapse = useCallback(() => { - setIsOpen(false) - }, [setIsOpen]) + setLocalIsOpen(false) + }, []) + + useEffect(() => { + setIsOpen(debouncedLocalIsOpen) + }, [debouncedLocalIsOpen, setIsOpen]) return { isOpen, From 7356c3b863b4df625f3696efd89eb094eb98d50f Mon Sep 17 00:00:00 2001 From: Christopher Hoskin Date: Mon, 12 May 2025 16:51:18 +0100 Subject: [PATCH 086/194] Merge pull request #25543 from overleaf/csh-issue-23942-more-migrate Remove more CR refs GitOrigin-RevId: 7ba91712943c309d40613b0f7247f20d45d7fbb1 --- services/clsi/Makefile | 8 +++--- services/clsi/buildscript.txt | 4 +-- services/clsi/docker-compose.ci.yml | 2 +- services/clsi/docker-compose.yml | 2 +- services/clsi/kube.yaml | 41 ----------------------------- 5 files changed, 7 insertions(+), 50 deletions(-) delete mode 100644 services/clsi/kube.yaml diff --git a/services/clsi/Makefile b/services/clsi/Makefile index 2f673dbd87..70415b80e9 100644 --- a/services/clsi/Makefile +++ b/services/clsi/Makefile @@ -24,7 +24,6 @@ DOCKER_COMPOSE_TEST_UNIT = \ clean: -docker rmi ci/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) - -docker rmi gcr.io/overleaf-ops/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) -docker rmi us-east1-docker.pkg.dev/overleaf-ops/ol-docker/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) -$(DOCKER_COMPOSE_TEST_UNIT) down --rmi local -$(DOCKER_COMPOSE_TEST_ACCEPTANCE) down --rmi local @@ -129,11 +128,10 @@ build: --pull \ --build-arg BUILDKIT_INLINE_CACHE=1 \ --tag ci/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) \ - --tag gcr.io/overleaf-ops/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) \ - --tag gcr.io/overleaf-ops/$(PROJECT_NAME):$(BRANCH_NAME) \ - --cache-from gcr.io/overleaf-ops/$(PROJECT_NAME):$(BRANCH_NAME) \ - --cache-from gcr.io/overleaf-ops/$(PROJECT_NAME):main \ --tag us-east1-docker.pkg.dev/overleaf-ops/ol-docker/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) \ + --tag us-east1-docker.pkg.dev/overleaf-ops/ol-docker/$(PROJECT_NAME):$(BRANCH_NAME) \ + --cache-from us-east1-docker.pkg.dev/overleaf-ops/ol-docker/$(PROJECT_NAME):$(BRANCH_NAME) \ + --cache-from us-east1-docker.pkg.dev/overleaf-ops/ol-docker/$(PROJECT_NAME):main \ --file Dockerfile \ ../.. diff --git a/services/clsi/buildscript.txt b/services/clsi/buildscript.txt index 1834ac9648..831ac3ecc9 100644 --- a/services/clsi/buildscript.txt +++ b/services/clsi/buildscript.txt @@ -1,8 +1,8 @@ clsi --data-dirs=cache,compiles,output --dependencies= ---docker-repos=gcr.io/overleaf-ops,us-east1-docker.pkg.dev/overleaf-ops/ol-docker ---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=gcr.io/overleaf-ops,TEXLIVE_IMAGE_USER="tex",DOCKER_RUNNER="true",COMPILES_HOST_DIR=$PWD/compiles,OUTPUT_HOST_DIR=$PWD/output +--docker-repos=us-east1-docker.pkg.dev/overleaf-ops/ol-docker +--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",DOCKER_RUNNER="true",COMPILES_HOST_DIR=$PWD/compiles,OUTPUT_HOST_DIR=$PWD/output --env-pass-through= --esmock-loader=False --node-version=20.18.2 diff --git a/services/clsi/docker-compose.ci.yml b/services/clsi/docker-compose.ci.yml index 1754a3a916..b6643008f7 100644 --- a/services/clsi/docker-compose.ci.yml +++ b/services/clsi/docker-compose.ci.yml @@ -27,7 +27,7 @@ services: 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: gcr.io/overleaf-ops + TEX_LIVE_IMAGE_NAME_OVERRIDE: us-east1-docker.pkg.dev/overleaf-ops/ol-docker TEXLIVE_IMAGE_USER: "tex" DOCKER_RUNNER: "true" COMPILES_HOST_DIR: $PWD/compiles diff --git a/services/clsi/docker-compose.yml b/services/clsi/docker-compose.yml index 3e70c256ea..e0f29ab09d 100644 --- a/services/clsi/docker-compose.yml +++ b/services/clsi/docker-compose.yml @@ -45,7 +45,7 @@ services: 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: gcr.io/overleaf-ops + TEX_LIVE_IMAGE_NAME_OVERRIDE: us-east1-docker.pkg.dev/overleaf-ops/ol-docker TEXLIVE_IMAGE_USER: "tex" DOCKER_RUNNER: "true" COMPILES_HOST_DIR: $PWD/compiles diff --git a/services/clsi/kube.yaml b/services/clsi/kube.yaml deleted file mode 100644 index d3fb04291e..0000000000 --- a/services/clsi/kube.yaml +++ /dev/null @@ -1,41 +0,0 @@ -apiVersion: v1 -kind: Service -metadata: - name: clsi - namespace: default -spec: - type: LoadBalancer - ports: - - port: 80 - protocol: TCP - targetPort: 80 - selector: - run: clsi ---- -apiVersion: extensions/v1beta1 -kind: Deployment -metadata: - name: clsi - namespace: default -spec: - replicas: 2 - template: - metadata: - labels: - run: clsi - spec: - containers: - - name: clsi - image: gcr.io/henry-terraform-admin/clsi - imagePullPolicy: Always - readinessProbe: - httpGet: - path: status - port: 80 - periodSeconds: 5 - initialDelaySeconds: 0 - failureThreshold: 3 - successThreshold: 1 - - - From 3cb7ef05d9cda9502898620f23723df686011947 Mon Sep 17 00:00:00 2001 From: Rebeka Dekany <50901361+rebekadekany@users.noreply.github.com> Date: Mon, 12 May 2025 18:09:17 +0200 Subject: [PATCH 087/194] Fix undefined alt text for images (#25447) GitOrigin-RevId: 59efe870275f3dcbabecf1c3d115e7a7a85be594 --- services/web/app/views/_mixins/quote.pug | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/services/web/app/views/_mixins/quote.pug b/services/web/app/views/_mixins/quote.pug index b8065dbdab..573e0b6b0c 100644 --- a/services/web/app/views/_mixins/quote.pug +++ b/services/web/app/views/_mixins/quote.pug @@ -1,9 +1,10 @@ -mixin quoteLargeTextCentered(quote, person, position, affiliation, link, pictureUrl, pictureAltAttr) +mixin quoteLargeTextCentered(quote, person, position, affiliation, link, pictureUrl) blockquote.quote-large-text-centered .quote !{quote} if pictureUrl .quote-img - img(src=pictureUrl alt=pictureAltAttr) + -var pictureAlt=`Photo of ${person}` + img(src=pictureUrl alt=pictureAlt) footer div.quote-person strong #{person} @@ -33,7 +34,7 @@ mixin collinsQuote1 -var quotePersonPosition = 'Associate Professor and Lab Director, Ontario Tech University' -var quotePersonImg = buildImgPath("advocates/collins.jpg") .card-body - +quoteLargeTextCentered(quote, quotePerson, quotePersonPosition, null, null, quotePersonImg, quotePerson) + +quoteLargeTextCentered(quote, quotePerson, quotePersonPosition, null, null, quotePersonImg) mixin collinsQuote2 .card.card-dark-green-bg @@ -42,7 +43,7 @@ mixin collinsQuote2 -var quotePersonPosition = 'Associate Professor and Lab Director, Ontario Tech University' -var quotePersonImg = buildImgPath("advocates/collins.jpg") .card-body - +quoteLargeTextCentered(quote, quotePerson, quotePersonPosition, null, null, quotePersonImg, quotePerson) + +quoteLargeTextCentered(quote, quotePerson, quotePersonPosition, null, null, quotePersonImg) mixin bennettQuote1 .card.card-dark-green-bg @@ -51,4 +52,4 @@ mixin bennettQuote1 -var quotePersonPosition = 'Software Architect, Symplectic' -var quotePersonImg = buildImgPath("advocates/bennett.jpg") .card-body - +quoteLargeTextCentered(quote, quotePerson, quotePersonPosition, null, null, quotePersonImg, quotePerson) \ No newline at end of file + +quoteLargeTextCentered(quote, quotePerson, quotePersonPosition, null, null, quotePersonImg) From ac51878186f55835beaf27dad99d082a6bb77cd4 Mon Sep 17 00:00:00 2001 From: Rebeka Dekany <50901361+rebekadekany@users.noreply.github.com> Date: Mon, 12 May 2025 18:10:00 +0200 Subject: [PATCH 088/194] Improve ARIA labels for buttons and links on the Account setting page (#25499) * Improve announced button and link labels for screen reader users * Improve labels for integration widgets and update test * Make integration widget IDs to be required GitOrigin-RevId: 1e0124ef63a91fb63dffd79881c60794bccb9d27 --- server-ce/test/git-bridge.spec.ts | 4 +-- .../web/frontend/extracted-translations.json | 3 ++ .../components/account-info-section.tsx | 3 +- .../settings/components/emails-section.tsx | 1 + .../components/linking/integration-widget.tsx | 21 ++++++++++++-- .../components/linking/sso-widget.tsx | 29 ++++++++++++++++--- services/web/locales/en.json | 3 ++ .../components/account-info-section.test.tsx | 20 ++++++------- .../emails-section-add-new-email.test.tsx | 29 ++++++++++++------- .../components/emails/emails-section.test.tsx | 1 + .../components/linking-section.test.tsx | 9 ++++-- .../linking/integration-widget.test.tsx | 3 +- .../components/linking/sso-widget.test.tsx | 20 ++++++------- 13 files changed, 102 insertions(+), 44 deletions(-) diff --git a/server-ce/test/git-bridge.spec.ts b/server-ce/test/git-bridge.spec.ts index 071091bdfd..67a09b3409 100644 --- a/server-ce/test/git-bridge.spec.ts +++ b/server-ce/test/git-bridge.spec.ts @@ -93,7 +93,7 @@ describe('git-bridge', function () { cy.get('code').contains(`git clone ${gitURL(id.toString())}`) }) cy.findByRole('button', { - name: 'Generate token', + name: /generate token/i, }).click() cy.get('code').contains(/olp_[a-zA-Z0-9]{16}/) }) @@ -196,7 +196,7 @@ describe('git-bridge', function () { cy.get('code').contains(`git clone ${gitURL(projectId.toString())}`) }) cy.findByRole('button', { - name: 'Generate token', + name: /generate token/i, }).click() cy.get('code') .contains(/olp_[a-zA-Z0-9]{16}/) diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json index 207da71688..df46529ffe 100644 --- a/services/web/frontend/extracted-translations.json +++ b/services/web/frontend/extracted-translations.json @@ -895,10 +895,12 @@ "layout_options": "", "layout_processing": "", "learn_more": "", + "learn_more_about": "", "learn_more_about_account": "", "learn_more_about_compile_timeouts": "", "learn_more_about_link_sharing": "", "learn_more_about_managed_users": "", + "learn_more_about_managing_email": "", "learn_more_about_other_causes_of_compile_timeouts": "", "leave": "", "leave_any_group_subscriptions": "", @@ -1144,6 +1146,7 @@ "other_causes_of_compile_timeouts": "", "other_logs_and_files": "", "other_output_files": "", + "our_help_page": "", "our_team_will_get_back_to_you_shortly": "", "our_values": "", "out_of_sync": "", diff --git a/services/web/frontend/js/features/settings/components/account-info-section.tsx b/services/web/frontend/js/features/settings/components/account-info-section.tsx index 4fd410d89f..1ff865e757 100644 --- a/services/web/frontend/js/features/settings/components/account-info-section.tsx +++ b/services/web/frontend/js/features/settings/components/account-info-section.tsx @@ -70,7 +70,7 @@ function AccountInfoSection() { return ( <> -

{t('update_account_info')}

+

{t('update_account_info')}

{hasAffiliationsFeature ? null : ( {t('update')} diff --git a/services/web/frontend/js/features/settings/components/emails-section.tsx b/services/web/frontend/js/features/settings/components/emails-section.tsx index a8bc54b017..30d52fbf9e 100644 --- a/services/web/frontend/js/features/settings/components/emails-section.tsx +++ b/services/web/frontend/js/features/settings/components/emails-section.tsx @@ -40,6 +40,7 @@ function EmailsSectionContent() { , ]} /> diff --git a/services/web/frontend/js/features/settings/components/linking/integration-widget.tsx b/services/web/frontend/js/features/settings/components/linking/integration-widget.tsx index def60625a2..0ef67446b6 100644 --- a/services/web/frontend/js/features/settings/components/linking/integration-widget.tsx +++ b/services/web/frontend/js/features/settings/components/linking/integration-widget.tsx @@ -20,6 +20,7 @@ function trackLinkingClick(integration: string) { } type IntegrationLinkingWidgetProps = { + id: string logo: ReactNode title: string description: string @@ -35,6 +36,7 @@ type IntegrationLinkingWidgetProps = { } export function IntegrationLinkingWidget({ + id, logo, title, description, @@ -65,12 +67,17 @@ export function IntegrationLinkingWidget({
{logo}
void linkPath: string disabled?: boolean + titleId: string } function ActionButton({ @@ -114,16 +123,22 @@ function ActionButton({ linkPath, disabled, integration, + titleId, }: ActionButtonProps) { const { t } = useTranslation() + const linkTextId = `${titleId}-link` + if (!hasFeature) { return ( trackUpgradeClick(integration)} + aria-labelledby={`${titleId} ${linkTextId}`} > - {t('upgrade')} + + {t('upgrade')} + ) } else if (linked) { diff --git a/services/web/frontend/js/features/settings/components/linking/sso-widget.tsx b/services/web/frontend/js/features/settings/components/linking/sso-widget.tsx index 8d5bc169d1..bea87e7979 100644 --- a/services/web/frontend/js/features/settings/components/linking/sso-widget.tsx +++ b/services/web/frontend/js/features/settings/components/linking/sso-widget.tsx @@ -69,12 +69,17 @@ export function SSOLinkingWidget({
{providerLogos[providerId]}
-

{title}

+

{title}

{description?.replace(/<[^>]+>/g, '')}{' '} {helpPath ? ( - + {t('learn_more')} ) : null} @@ -85,6 +90,7 @@ export function SSOLinkingWidget({

void + titleId: string } function ActionButton({ @@ -113,8 +120,11 @@ function ActionButton({ accountIsLinked, linkPath, onUnlinkClick, + titleId, }: ActionButtonProps) { const { t } = useTranslation() + const linkTextId = `${titleId}-link` + if (unlinkRequestInflight) { return ( @@ -123,13 +133,24 @@ function ActionButton({ ) } else if (accountIsLinked) { return ( - + {t('unlink')} ) } else { return ( - + {t('link')} ) diff --git a/services/web/locales/en.json b/services/web/locales/en.json index d9daebdd6a..2735e04205 100644 --- a/services/web/locales/en.json +++ b/services/web/locales/en.json @@ -1174,11 +1174,13 @@ "ldap_create_admin_instructions": "Choose an email address for the first __appName__ admin account. This should correspond to an account in the LDAP system. You will then be asked to log in with this account.", "learn": "Learn", "learn_more": "Learn more", + "learn_more_about": "Learn more about __integrationName__", "learn_more_about_account": "<0>Learn more about managing your __appName__ account.", "learn_more_about_compile_timeouts": "<0>Learn more about compile timeouts.", "learn_more_about_emails": "<0>Learn more about managing your __appName__ emails.", "learn_more_about_link_sharing": "Learn more about Link Sharing", "learn_more_about_managed_users": "Learn more about Managed Users.", + "learn_more_about_managing_email": "Learn more about managing your __appName__ emails.", "learn_more_about_other_causes_of_compile_timeouts": "<0>Learn more about other causes of compile timeouts and how to fix them.", "leave": "Leave", "leave_any_group_subscriptions": "Leave any group subscriptions other than the one that will be managing your account. <0>Leave them from the Subscription page.", @@ -1511,6 +1513,7 @@ "other_output_files": "Download other output files", "other_sessions": "Other Sessions", "other_ways_to_log_in": "Other ways to log in", + "our_help_page": "Our help page", "our_team_will_get_back_to_you_shortly": "Our team will get back to you shortly.", "our_values": "Our values", "out_of_sync": "Out of sync", diff --git a/services/web/test/frontend/features/settings/components/account-info-section.test.tsx b/services/web/test/frontend/features/settings/components/account-info-section.test.tsx index 112e7d90e7..e41cfe643f 100644 --- a/services/web/test/frontend/features/settings/components/account-info-section.test.tsx +++ b/services/web/test/frontend/features/settings/components/account-info-section.test.tsx @@ -47,7 +47,7 @@ describe('', function () { }) fireEvent.click( screen.getByRole('button', { - name: 'Update', + name: /update/i, }) ) expect(updateMock.callHistory.called()).to.be.true @@ -68,7 +68,7 @@ describe('', function () { target: { value: 'john' }, }) const button = screen.getByRole('button', { - name: 'Update', + name: /update/i, }) as HTMLButtonElement expect(button.disabled).to.be.true @@ -87,14 +87,14 @@ describe('', function () { fireEvent.click( screen.getByRole('button', { - name: 'Update', + name: /update/i, }) ) await screen.findByRole('button', { name: /saving/i }) finishUpdateCall(200) await screen.findByRole('button', { - name: 'Update', + name: /update/i, }) screen.getByText('Thanks, your settings have been updated.') }) @@ -105,7 +105,7 @@ describe('', function () { fireEvent.click( screen.getByRole('button', { - name: 'Update', + name: /update/i, }) ) await screen.findByText('Something went wrong. Please try again.') @@ -117,7 +117,7 @@ describe('', function () { fireEvent.click( screen.getByRole('button', { - name: 'Update', + name: /update/i, }) ) await screen.findByText( @@ -136,7 +136,7 @@ describe('', function () { fireEvent.click( screen.getByRole('button', { - name: 'Update', + name: /update/i, }) ) await screen.findByText('This email is already registered') @@ -153,7 +153,7 @@ describe('', function () { fireEvent.click( screen.getByRole('button', { - name: 'Update', + name: /update/i, }) ) expect( @@ -184,7 +184,7 @@ describe('', function () { fireEvent.click( screen.getByRole('button', { - name: 'Update', + name: /update account info/i, }) ) expect( @@ -212,7 +212,7 @@ describe('', function () { fireEvent.click( screen.getByRole('button', { - name: 'Update', + name: /update/i, }) ) expect( diff --git a/services/web/test/frontend/features/settings/components/emails/emails-section-add-new-email.test.tsx b/services/web/test/frontend/features/settings/components/emails/emails-section-add-new-email.test.tsx index c556ac83a7..105e52f0aa 100644 --- a/services/web/test/frontend/features/settings/components/emails/emails-section-add-new-email.test.tsx +++ b/services/web/test/frontend/features/settings/components/emails/emails-section-add-new-email.test.tsx @@ -106,7 +106,7 @@ describe('', function () { }) fireEvent.click(button) - await screen.findByLabelText(/email/i) + await screen.findByLabelText(/email/i, { selector: 'input' }) }) it('renders "Start adding your address" until a valid email is typed', async function () { @@ -121,7 +121,7 @@ describe('', function () { }) fireEvent.click(button) - const input = screen.getByLabelText(/email/i) + const input = screen.getByLabelText(/email/i, { selector: 'input' }) // initially the text is displayed and the "add email" button disabled screen.getByText('Start by adding your email address.') @@ -200,7 +200,7 @@ describe('', function () { .post('/user/emails/confirm-secondary', 200) fireEvent.click(addAnotherEmailBtn) - const input = screen.getByLabelText(/email/i) + const input = screen.getByLabelText(/email/i, { selector: 'input' }) fireEvent.change(input, { target: { value: userEmailData.email }, @@ -242,7 +242,7 @@ describe('', function () { .post('/user/emails/secondary', 400) fireEvent.click(addAnotherEmailBtn) - const input = screen.getByLabelText(/email/i) + const input = screen.getByLabelText(/email/i, { selector: 'input' }) fireEvent.change(input, { target: { value: userEmailData.email }, @@ -279,7 +279,7 @@ describe('', function () { await userEvent.click(button) - const input = screen.getByLabelText(/email/i) + const input = screen.getByLabelText(/email/i, { selector: 'input' }) fireEvent.change(input, { target: { value: 'user@autocomplete.edu' }, }) @@ -302,7 +302,10 @@ describe('', function () { await userEvent.click(button) - await userEvent.type(screen.getByLabelText(/email/i), userEmailData.email) + await userEvent.type( + screen.getByLabelText(/email/i, { selector: 'input' }), + userEmailData.email + ) await userEvent.click(screen.getByRole('button', { name: /let us know/i })) @@ -415,7 +418,10 @@ describe('', function () { // open "add new email" section and click "let us know" to open the Country/University form await userEvent.click(button) - await userEvent.type(screen.getByLabelText(/email/i), userEmailData.email) + await userEvent.type( + screen.getByLabelText(/email/i, { selector: 'input' }), + userEmailData.email + ) await userEvent.click(screen.getByRole('button', { name: /let us know/i })) // select a country @@ -457,7 +463,10 @@ describe('', function () { await userEvent.click(button) - await userEvent.type(screen.getByLabelText(/email/i), userEmailData.email) + await userEvent.type( + screen.getByLabelText(/email/i, { selector: 'input' }), + userEmailData.email + ) await userEvent.click(screen.getByRole('button', { name: /let us know/i })) @@ -574,7 +583,7 @@ describe('', function () { await userEvent.click(button) await userEvent.type( - screen.getByLabelText(/email/i), + screen.getByLabelText(/email/i, { selector: 'input' }), `user@${hostnameFirstChar}` ) @@ -647,7 +656,7 @@ describe('', function () { await userEvent.click(button) await userEvent.type( - screen.getByLabelText(/email/i), + screen.getByLabelText(/email/i, { selector: 'input' }), `user@${hostnameFirstChar}` ) diff --git a/services/web/test/frontend/features/settings/components/emails/emails-section.test.tsx b/services/web/test/frontend/features/settings/components/emails/emails-section.test.tsx index 451a510855..13d3a0f9ff 100644 --- a/services/web/test/frontend/features/settings/components/emails/emails-section.test.tsx +++ b/services/web/test/frontend/features/settings/components/emails/emails-section.test.tsx @@ -40,6 +40,7 @@ describe('', function () { screen.getByText(/add additional email addresses/i) screen.getByText(/to change your primary email/i) + screen.getByLabelText('Learn more about managing your Overleaf emails.') }) it('renders a loading message when loading', async function () { diff --git a/services/web/test/frontend/features/settings/components/linking-section.test.tsx b/services/web/test/frontend/features/settings/components/linking-section.test.tsx index d134ffeae6..887ada15de 100644 --- a/services/web/test/frontend/features/settings/components/linking-section.test.tsx +++ b/services/web/test/frontend/features/settings/components/linking-section.test.tsx @@ -69,16 +69,19 @@ describe('', function () { screen.getByText('linked accounts') screen.getByText('Google') + screen.getByRole('button', { name: /link google/i }) screen.getByText('Log in with Google.') - screen.getByRole('button', { name: 'Unlink' }) + screen.getByRole('button', { name: /unlink/i }) screen.getByText('ORCID') screen.getByText( /Securely establish your identity by linking your ORCID iD/ ) - const helpLink = screen.getByRole('link', { name: 'Learn more' }) + const helpLink = screen.getByRole('link', { + name: /learn more about orcid/i, + }) expect(helpLink.getAttribute('href')).to.equal('/blog/434') - const linkButton = screen.getByRole('button', { name: 'Link' }) + const linkButton = screen.getByRole('button', { name: /link orcid/i }) expect(linkButton.getAttribute('href')).to.equal('/auth/orcid?intent=link') }) diff --git a/services/web/test/frontend/features/settings/components/linking/integration-widget.test.tsx b/services/web/test/frontend/features/settings/components/linking/integration-widget.test.tsx index ed656056e3..435dbba0fa 100644 --- a/services/web/test/frontend/features/settings/components/linking/integration-widget.test.tsx +++ b/services/web/test/frontend/features/settings/components/linking/integration-widget.test.tsx @@ -6,6 +6,7 @@ import * as eventTracking from '@/infrastructure/event-tracking' describe('', function () { const defaultProps = { + id: 'integration-widget-id', logo:
, title: 'Integration', description: 'paragraph1', @@ -32,7 +33,7 @@ describe('', function () { }) it('should render an upgrade link and track clicks', function () { - const upgradeLink = screen.getByRole('button', { name: 'Upgrade' }) + const upgradeLink = screen.getByRole('button', { name: /upgrade/i }) expect(upgradeLink.getAttribute('href')).to.equal( '/user/subscription/plans' ) diff --git a/services/web/test/frontend/features/settings/components/linking/sso-widget.test.tsx b/services/web/test/frontend/features/settings/components/linking/sso-widget.test.tsx index d78de73ebc..5d29176c17 100644 --- a/services/web/test/frontend/features/settings/components/linking/sso-widget.test.tsx +++ b/services/web/test/frontend/features/settings/components/linking/sso-widget.test.tsx @@ -25,7 +25,7 @@ describe('', function () { screen.getByText('integration') screen.getByText('integration description') expect( - screen.getByRole('link', { name: 'Learn more' }).getAttribute('href') + screen.getByRole('link', { name: /learn more/i }).getAttribute('href') ).to.equal('/help/integration') }) @@ -33,7 +33,7 @@ describe('', function () { it('should render a link to `linkPath`', function () { render() expect( - screen.getByRole('button', { name: 'Link' }).getAttribute('href') + screen.getByRole('button', { name: /link/i }).getAttribute('href') ).to.equal('/integration/link?intent=link') }) }) @@ -49,11 +49,11 @@ describe('', function () { }) it('should display an `unlink` button', function () { - screen.getByRole('button', { name: 'Unlink' }) + screen.getByRole('button', { name: /unlink/i }) }) it('should open a modal to confirm integration unlinking', function () { - fireEvent.click(screen.getByRole('button', { name: 'Unlink' })) + fireEvent.click(screen.getByRole('button', { name: /unlink/i })) screen.getByText('Unlink integration Account') screen.getByText( 'Warning: When you unlink your account from integration you will not be able to sign in using integration anymore.' @@ -61,7 +61,7 @@ describe('', function () { }) it('should cancel unlinking when clicking cancel in the confirmation modal', async function () { - fireEvent.click(screen.getByRole('button', { name: 'Unlink' })) + fireEvent.click(screen.getByRole('button', { name: /unlink/i })) const cancelBtn = screen.getByRole('button', { name: 'Cancel', hidden: false, @@ -80,9 +80,9 @@ describe('', function () { render( ) - fireEvent.click(screen.getByRole('button', { name: 'Unlink' })) + fireEvent.click(screen.getByRole('button', { name: /unlink/i })) confirmBtn = within(screen.getByRole('dialog')).getByRole('button', { - name: 'Unlink', + name: /unlink/i, hidden: false, }) }) @@ -114,11 +114,11 @@ describe('', function () { render( ) - fireEvent.click(screen.getByRole('button', { name: 'Unlink' })) + fireEvent.click(screen.getByRole('button', { name: /unlink/i })) const confirmBtn = within(screen.getByRole('dialog')).getByRole( 'button', { - name: 'Unlink', + name: /unlink/i, hidden: false, } ) @@ -130,7 +130,7 @@ describe('', function () { }) it('should display the unlink button ', async function () { - await screen.findByRole('button', { name: 'Unlink' }) + await screen.findByRole('button', { name: /unlink/i }) }) }) }) From 7c79c3b4c3ed40cdbcaeefea01ddf3062c550fff Mon Sep 17 00:00:00 2001 From: Rebeka Dekany <50901361+rebekadekany@users.noreply.github.com> Date: Mon, 12 May 2025 18:10:11 +0200 Subject: [PATCH 089/194] Add ARIA attributes for menu and separator (#25501) GitOrigin-RevId: ef80d2811ecee78adc8bb359cf3b059d79fe9900 --- .../features/project-list/components/sidebar/sidebar-ds-nav.tsx | 2 ++ .../project-list/components/sidebar/sidebar-filters.tsx | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/services/web/frontend/js/features/project-list/components/sidebar/sidebar-ds-nav.tsx b/services/web/frontend/js/features/project-list/components/sidebar/sidebar-ds-nav.tsx index 74bdc5a8e2..3c2b3a09c0 100644 --- a/services/web/frontend/js/features/project-list/components/sidebar/sidebar-ds-nav.tsx +++ b/services/web/frontend/js/features/project-list/components/sidebar/sidebar-ds-nav.tsx @@ -73,6 +73,7 @@ function SidebarDsNav() { sendMB('menu-expand', { item: 'help', location: 'sidebar' }) } }} + role="menu" > -
  • +
  • From aa002369cbc517fd598dd652bb39741b871c2485 Mon Sep 17 00:00:00 2001 From: Antoine Clausse Date: Tue, 13 May 2025 10:23:31 +0200 Subject: [PATCH 090/194] Update defaultHighWaterMark to 64KiB (Node 22's default) (#25522) * Set defaultHighWaterMark to 16KiB This is already the default in Node 20 * Set defaultHighWaterMark to 64KiB Per https://github.com/overleaf/internal/pull/25522#issuecomment-2872035192 GitOrigin-RevId: 19d731abf683066654027de3a4f9ac0b8916f22c --- services/chat/config/settings.defaults.cjs | 3 +++ services/clsi/config/settings.defaults.js | 4 ++++ services/contacts/config/settings.defaults.cjs | 3 +++ services/docstore/config/settings.defaults.js | 4 ++++ services/document-updater/config/settings.defaults.js | 3 +++ services/filestore/config/settings.defaults.js | 4 ++++ services/notifications/config/settings.defaults.js | 5 +++++ services/project-history/config/settings.defaults.cjs | 3 +++ services/real-time/config/settings.defaults.js | 4 ++++ services/web/config/settings.defaults.js | 6 +++++- 10 files changed, 38 insertions(+), 1 deletion(-) diff --git a/services/chat/config/settings.defaults.cjs b/services/chat/config/settings.defaults.cjs index 4b7dc293a9..9cc27a6877 100644 --- a/services/chat/config/settings.defaults.cjs +++ b/services/chat/config/settings.defaults.cjs @@ -1,6 +1,9 @@ const http = require('node:http') const https = require('node:https') +const stream = require('node:stream') +// TODO(24011): remove this after node 22 update +stream.setDefaultHighWaterMark(false, 64 * 1024) http.globalAgent.keepAlive = false https.globalAgent.keepAlive = false diff --git a/services/clsi/config/settings.defaults.js b/services/clsi/config/settings.defaults.js index 5edaec4a8a..7472334d80 100644 --- a/services/clsi/config/settings.defaults.js +++ b/services/clsi/config/settings.defaults.js @@ -1,5 +1,9 @@ const Path = require('node:path') const os = require('node:os') +const stream = require('node:stream') + +// TODO(24011): remove this after node 22 update +stream.setDefaultHighWaterMark(false, 64 * 1024) const isPreEmptible = process.env.PREEMPTIBLE === 'TRUE' const CLSI_SERVER_ID = os.hostname().replace('-ctr', '') diff --git a/services/contacts/config/settings.defaults.cjs b/services/contacts/config/settings.defaults.cjs index 7ffdb83ce5..bcf014ee70 100644 --- a/services/contacts/config/settings.defaults.cjs +++ b/services/contacts/config/settings.defaults.cjs @@ -1,6 +1,9 @@ const http = require('node:http') const https = require('node:https') +const stream = require('node:stream') +// TODO(24011): remove this after node 22 update +stream.setDefaultHighWaterMark(false, 64 * 1024) http.globalAgent.maxSockets = 300 http.globalAgent.keepAlive = false https.globalAgent.keepAlive = false diff --git a/services/docstore/config/settings.defaults.js b/services/docstore/config/settings.defaults.js index 9ad506a9bd..14da21e132 100644 --- a/services/docstore/config/settings.defaults.js +++ b/services/docstore/config/settings.defaults.js @@ -1,5 +1,9 @@ const http = require('node:http') const https = require('node:https') +const stream = require('node:stream') + +// TODO(24011): remove this after node 22 update +stream.setDefaultHighWaterMark(false, 64 * 1024) http.globalAgent.maxSockets = 300 http.globalAgent.keepAlive = false diff --git a/services/document-updater/config/settings.defaults.js b/services/document-updater/config/settings.defaults.js index 0cd29d325b..a6a1c397fa 100755 --- a/services/document-updater/config/settings.defaults.js +++ b/services/document-updater/config/settings.defaults.js @@ -1,6 +1,9 @@ const http = require('node:http') const https = require('node:https') +const stream = require('node:stream') +// TODO(24011): remove this after node 22 update +stream.setDefaultHighWaterMark(false, 64 * 1024) http.globalAgent.keepAlive = false https.globalAgent.keepAlive = false diff --git a/services/filestore/config/settings.defaults.js b/services/filestore/config/settings.defaults.js index 9a08bb197e..28d7190376 100644 --- a/services/filestore/config/settings.defaults.js +++ b/services/filestore/config/settings.defaults.js @@ -1,4 +1,8 @@ const Path = require('node:path') +const stream = require('node:stream') + +// TODO(24011): remove this after node 22 update +stream.setDefaultHighWaterMark(false, 64 * 1024) // environment variables renamed for consistency // use AWS_ACCESS_KEY_ID-style going forward diff --git a/services/notifications/config/settings.defaults.js b/services/notifications/config/settings.defaults.js index 3453b88bd6..26a3b79c6b 100644 --- a/services/notifications/config/settings.defaults.js +++ b/services/notifications/config/settings.defaults.js @@ -1,3 +1,8 @@ +const stream = require('node:stream') + +// TODO(24011): remove this after node 22 update +stream.setDefaultHighWaterMark(false, 64 * 1024) + module.exports = { internal: { notifications: { diff --git a/services/project-history/config/settings.defaults.cjs b/services/project-history/config/settings.defaults.cjs index 9e5a39868a..ce92fc1cf2 100644 --- a/services/project-history/config/settings.defaults.cjs +++ b/services/project-history/config/settings.defaults.cjs @@ -1,6 +1,9 @@ const http = require('node:http') const https = require('node:https') +const stream = require('node:stream') +// TODO(24011): remove this after node 22 update +stream.setDefaultHighWaterMark(false, 64 * 1024) http.globalAgent.keepAlive = false https.globalAgent.keepAlive = false diff --git a/services/real-time/config/settings.defaults.js b/services/real-time/config/settings.defaults.js index 57b0a50a42..652fdae56a 100644 --- a/services/real-time/config/settings.defaults.js +++ b/services/real-time/config/settings.defaults.js @@ -1,6 +1,10 @@ /* eslint-disable camelcase */ const http = require('node:http') const https = require('node:https') +const stream = require('node:stream') + +// TODO(24011): remove this after node 22 update +stream.setDefaultHighWaterMark(false, 64 * 1024) http.globalAgent.keepAlive = false https.globalAgent.keepAlive = false diff --git a/services/web/config/settings.defaults.js b/services/web/config/settings.defaults.js index 07970fb852..9faeace655 100644 --- a/services/web/config/settings.defaults.js +++ b/services/web/config/settings.defaults.js @@ -1,6 +1,10 @@ -const Path = require('path') +const Path = require('node:path') +const stream = require('node:stream') const { merge } = require('@overleaf/settings/merge') +// TODO(24011): remove this after node 22 update +stream.setDefaultHighWaterMark(false, 64 * 1024) + let defaultFeatures, siteUrl // Make time interval config easier. From 271635491a8a81463d2a1042a1a640bbe2ad563c Mon Sep 17 00:00:00 2001 From: Rebeka Dekany <50901361+rebekadekany@users.noreply.github.com> Date: Tue, 13 May 2025 12:02:47 +0200 Subject: [PATCH 091/194] Remove incorrect "button" role from navigational links that are styled as buttons (#25504) GitOrigin-RevId: 717b20a6f2e893034eb12547fa663d358c0de419 --- .../ui/components/bootstrap-5/button.tsx | 1 + .../bootstrap-5/components/button.scss | 1 - .../components/add-seats.spec.tsx | 8 +++--- .../components/request-status.spec.tsx | 4 +-- .../components/upgrade-subscription.spec.tsx | 2 +- .../components/current-plan-widget.test.tsx | 2 +- .../components/notifications.test.tsx | 26 +++++++++---------- .../sidebar/add-affiliation.test.tsx | 2 +- .../components/survey-widget.test.tsx | 2 +- .../components/linking-section.test.tsx | 3 +-- .../linking/integration-widget.test.tsx | 4 +-- .../components/linking/sso-widget.test.tsx | 2 +- .../components/security-section.test.tsx | 2 +- .../dashboard/personal-subscription.test.tsx | 2 +- 14 files changed, 30 insertions(+), 31 deletions(-) diff --git a/services/web/frontend/js/features/ui/components/bootstrap-5/button.tsx b/services/web/frontend/js/features/ui/components/bootstrap-5/button.tsx index 4eec23475a..2bbc97c695 100644 --- a/services/web/frontend/js/features/ui/components/bootstrap-5/button.tsx +++ b/services/web/frontend/js/features/ui/components/bootstrap-5/button.tsx @@ -52,6 +52,7 @@ const Button = forwardRef( ref={ref} disabled={isLoading || props.disabled} data-ol-loading={isLoading} + role={undefined} > {isLoading && ( diff --git a/services/web/frontend/stylesheets/bootstrap-5/components/button.scss b/services/web/frontend/stylesheets/bootstrap-5/components/button.scss index 3b782ed1f8..80220a6911 100644 --- a/services/web/frontend/stylesheets/bootstrap-5/components/button.scss +++ b/services/web/frontend/stylesheets/bootstrap-5/components/button.scss @@ -252,7 +252,6 @@ // Set the visited colour for a link that is styled as a button. This is necessary because we have a generic rule that // sets the colour of visited links -a[role='button']:visited, a.btn:visited { color: var(--bs-btn-color); } diff --git a/services/web/test/frontend/features/group-management/components/add-seats.spec.tsx b/services/web/test/frontend/features/group-management/components/add-seats.spec.tsx index 211714db2b..ead0c74a1c 100644 --- a/services/web/test/frontend/features/group-management/components/add-seats.spec.tsx +++ b/services/web/test/frontend/features/group-management/components/add-seats.spec.tsx @@ -31,7 +31,7 @@ describe('', function () { it('renders the back button', function () { cy.findByTestId('group-heading').within(() => { - cy.findByRole('button', { name: /back to subscription/i }).should( + cy.findByRole('link', { name: /back to subscription/i }).should( 'have.attr', 'href', '/user/subscription' @@ -71,7 +71,7 @@ describe('', function () { }) it('renders the cancel button', function () { - cy.findByRole('button', { name: /cancel/i }).should( + cy.findByRole('link', { name: /cancel/i }).should( 'have.attr', 'href', '/user/subscription' @@ -213,7 +213,7 @@ describe('', function () { describe('request', function () { afterEach(function () { - cy.findByRole('button', { name: /go to subscriptions/i }).should( + cy.findByRole('link', { name: /go to subscriptions/i }).should( 'have.attr', 'href', '/user/subscription' @@ -414,7 +414,7 @@ describe('', function () { describe('request', function () { afterEach(function () { - cy.findByRole('button', { name: /go to subscriptions/i }).should( + cy.findByRole('link', { name: /go to subscriptions/i }).should( 'have.attr', 'href', '/user/subscription' diff --git a/services/web/test/frontend/features/group-management/components/request-status.spec.tsx b/services/web/test/frontend/features/group-management/components/request-status.spec.tsx index 79982a13d1..4a3061f464 100644 --- a/services/web/test/frontend/features/group-management/components/request-status.spec.tsx +++ b/services/web/test/frontend/features/group-management/components/request-status.spec.tsx @@ -12,7 +12,7 @@ describe('', function () { it('renders the back button', function () { cy.findByTestId('group-heading').within(() => { - cy.findByRole('button', { name: /back to subscription/i }).should( + cy.findByRole('link', { name: /back to subscription/i }).should( 'have.attr', 'href', '/user/subscription' @@ -35,7 +35,7 @@ describe('', function () { }) it('renders the link to subscriptions', function () { - cy.findByRole('button', { name: /go to subscriptions/i }).should( + cy.findByRole('link', { name: /go to subscriptions/i }).should( 'have.attr', 'href', '/user/subscription' diff --git a/services/web/test/frontend/features/group-management/components/upgrade-subscription.spec.tsx b/services/web/test/frontend/features/group-management/components/upgrade-subscription.spec.tsx index 5918c1bd68..f7f2c564d7 100644 --- a/services/web/test/frontend/features/group-management/components/upgrade-subscription.spec.tsx +++ b/services/web/test/frontend/features/group-management/components/upgrade-subscription.spec.tsx @@ -66,7 +66,7 @@ describe('', function () { it('shows the "Upgrade" and "Cancel" buttons', function () { cy.findByRole('button', { name: /upgrade/i }) - cy.findByRole('button', { name: /cancel/i }).should( + cy.findByRole('link', { name: /cancel/i }).should( 'have.attr', 'href', '/user/subscription' diff --git a/services/web/test/frontend/features/project-list/components/current-plan-widget.test.tsx b/services/web/test/frontend/features/project-list/components/current-plan-widget.test.tsx index 0ec6db3c9f..84836c204b 100644 --- a/services/web/test/frontend/features/project-list/components/current-plan-widget.test.tsx +++ b/services/web/test/frontend/features/project-list/components/current-plan-widget.test.tsx @@ -70,7 +70,7 @@ describe('', function () { }) it('clicks on upgrade button', function () { - const upgradeLink = screen.getByRole('button', { name: /upgrade/i }) + const upgradeLink = screen.getByRole('link', { name: /upgrade/i }) fireEvent.click(upgradeLink) expect(sendMBSpy).to.be.calledOnce expect(sendMBSpy).calledWith('upgrade-button-click', { diff --git a/services/web/test/frontend/features/project-list/components/notifications.test.tsx b/services/web/test/frontend/features/project-list/components/notifications.test.tsx index f91399ff0c..db844f0260 100644 --- a/services/web/test/frontend/features/project-list/components/notifications.test.tsx +++ b/services/web/test/frontend/features/project-list/components/notifications.test.tsx @@ -141,7 +141,7 @@ describe('', function () { expect(screen.queryByRole('button', { name: /join project/i })).to.be.null - const openProject = screen.getByRole('button', { name: /open project/i }) + const openProject = screen.getByRole('link', { name: /open project/i }) expect(openProject.getAttribute('href')).to.equal( `/project/${notificationProjectInvite.messageOpts.projectId}` ) @@ -203,7 +203,7 @@ describe('', function () { screen.getByRole('alert') screen.getByText(/your free WFH2020 upgrade came to an end on/i) - const viewLink = screen.getByRole('button', { name: /view/i }) + const viewLink = screen.getByRole('link', { name: /view/i }) expect(viewLink.getAttribute('href')).to.equal( 'https://www.overleaf.com/events/wfh2020' ) @@ -242,7 +242,7 @@ describe('', function () { expect(findOutMore.getAttribute('href')).to.equal( 'https://www.overleaf.com/learn/how-to/Institutional_Login' ) - const linkAccount = screen.getByRole('button', { name: /link account/i }) + const linkAccount = screen.getByRole('link', { name: /link account/i }) expect(linkAccount.getAttribute('href')).to.equal( `${exposedSettings.samlInitPath}?university_id=${notificationIPMatchedAffiliation.messageOpts.institutionId}&auto=/project` ) @@ -277,7 +277,7 @@ describe('', function () { /add an institutional email address to claim your features/i ) - const addAffiliation = screen.getByRole('button', { + const addAffiliation = screen.getByRole('link', { name: /add affiliation/i, }) expect(addAffiliation.getAttribute('href')).to.equal(`/user/settings`) @@ -303,7 +303,7 @@ describe('', function () { screen.getByText(/file limit/i) screen.getByText(/You can't add more files to the project or sync it/i) - const accountSettings = screen.getByRole('button', { + const accountSettings = screen.getByRole('link', { name: /Open project/i, }) expect(accountSettings.getAttribute('href')).to.equal('/project/123') @@ -493,7 +493,7 @@ describe('', function () { '/learn/how-to/Institutional_Login' ) - const action = screen.getByRole('button', { name: /link account/i }) + const action = screen.getByRole('link', { name: /link account/i }) expect(action.getAttribute('href')).to.equal( `${exposedSettings.samlInitPath}?university_id=${notificationsInstitution.institutionId}&auto=/project&email=${notificationsInstitution.email}` ) @@ -558,7 +558,7 @@ describe('', function () { screen.getByRole('alert') screen.getByText(/which is already registered with/i) - const action = screen.getByRole('button', { name: /find out more/i }) + const action = screen.getByRole('link', { name: /find out more/i }) expect(action.getAttribute('href')).to.equal( '/learn/how-to/Institutional_Login' ) @@ -931,7 +931,7 @@ describe('', function () { renderWithinProjectListProvider(GroupsAndEnterpriseBanner) await fetchMock.callHistory.flush(true) - expect(screen.queryByRole('button', { name: 'Contact Sales' })).to.be.null + expect(screen.queryByRole('link', { name: 'Contact Sales' })).to.be.null }) it('shows the banner for users that have dismissed the previous banners', async function () { @@ -941,7 +941,7 @@ describe('', function () { renderWithinProjectListProvider(GroupsAndEnterpriseBanner) await fetchMock.callHistory.flush(true) - await screen.findByRole('button', { name: 'Contact Sales' }) + await screen.findByRole('link', { name: 'Contact Sales' }) }) it('shows the banner for users that have dismissed the banner more than 30 days ago', async function () { @@ -956,7 +956,7 @@ describe('', function () { renderWithinProjectListProvider(GroupsAndEnterpriseBanner) await fetchMock.callHistory.flush(true) - await screen.findByRole('button', { name: 'Contact Sales' }) + await screen.findByRole('link', { name: 'Contact Sales' }) }) it('does not show the banner for users that have dismissed the banner within the last 30 days', async function () { @@ -971,7 +971,7 @@ describe('', function () { renderWithinProjectListProvider(GroupsAndEnterpriseBanner) await fetchMock.callHistory.flush(true) - expect(screen.queryByRole('button', { name: 'Contact Sales' })).to.be.null + expect(screen.queryByRole('link', { name: 'Contact Sales' })).to.be.null }) describe('users that are not in group and are not affiliated', function () { @@ -1012,7 +1012,7 @@ describe('', function () { await screen.findByText( 'Overleaf On-Premises: Does your company want to keep its data within its firewall? Overleaf offers Server Pro, an on-premises solution for companies. Get in touch to learn more.' ) - const link = screen.getByRole('button', { name: 'Contact Sales' }) + const link = screen.getByRole('link', { name: 'Contact Sales' }) expect(link.getAttribute('href')).to.equal(`/for/contact-sales-2`) }) @@ -1029,7 +1029,7 @@ describe('', function () { await screen.findByText( 'Why do Fortune 500 companies and top research institutions trust Overleaf to streamline their collaboration? Get in touch to learn more.' ) - const link = screen.getByRole('button', { name: 'Contact Sales' }) + const link = screen.getByRole('link', { name: 'Contact Sales' }) expect(link.getAttribute('href')).to.equal(`/for/contact-sales-4`) }) diff --git a/services/web/test/frontend/features/project-list/components/sidebar/add-affiliation.test.tsx b/services/web/test/frontend/features/project-list/components/sidebar/add-affiliation.test.tsx index 8d69c2ddda..c49cd38f2c 100644 --- a/services/web/test/frontend/features/project-list/components/sidebar/add-affiliation.test.tsx +++ b/services/web/test/frontend/features/project-list/components/sidebar/add-affiliation.test.tsx @@ -31,7 +31,7 @@ describe('Add affiliation widget', function () { await waitFor(() => expect(fetchMock.callHistory.called('/api/project'))) await screen.findByText(/are you affiliated with an institution/i) - const addAffiliationLink = screen.getByRole('button', { + const addAffiliationLink = screen.getByRole('link', { name: /add affiliation/i, }) expect(addAffiliationLink.getAttribute('href')).to.equal('/user/settings') diff --git a/services/web/test/frontend/features/project-list/components/survey-widget.test.tsx b/services/web/test/frontend/features/project-list/components/survey-widget.test.tsx index bd6aeb683c..59f6bb9680 100644 --- a/services/web/test/frontend/features/project-list/components/survey-widget.test.tsx +++ b/services/web/test/frontend/features/project-list/components/survey-widget.test.tsx @@ -36,7 +36,7 @@ describe('', function () { screen.getByText(this.preText) screen.getByText(this.linkText) - const link = screen.getByRole('button', { + const link = screen.getByRole('link', { name: 'Take survey', }) as HTMLAnchorElement expect(link.href).to.equal(this.url) diff --git a/services/web/test/frontend/features/settings/components/linking-section.test.tsx b/services/web/test/frontend/features/settings/components/linking-section.test.tsx index 887ada15de..056273ccc8 100644 --- a/services/web/test/frontend/features/settings/components/linking-section.test.tsx +++ b/services/web/test/frontend/features/settings/components/linking-section.test.tsx @@ -69,7 +69,6 @@ describe('', function () { screen.getByText('linked accounts') screen.getByText('Google') - screen.getByRole('button', { name: /link google/i }) screen.getByText('Log in with Google.') screen.getByRole('button', { name: /unlink/i }) @@ -81,7 +80,7 @@ describe('', function () { name: /learn more about orcid/i, }) expect(helpLink.getAttribute('href')).to.equal('/blog/434') - const linkButton = screen.getByRole('button', { name: /link orcid/i }) + const linkButton = screen.getByRole('link', { name: /link orcid/i }) expect(linkButton.getAttribute('href')).to.equal('/auth/orcid?intent=link') }) diff --git a/services/web/test/frontend/features/settings/components/linking/integration-widget.test.tsx b/services/web/test/frontend/features/settings/components/linking/integration-widget.test.tsx index 435dbba0fa..1a623ba7b7 100644 --- a/services/web/test/frontend/features/settings/components/linking/integration-widget.test.tsx +++ b/services/web/test/frontend/features/settings/components/linking/integration-widget.test.tsx @@ -33,7 +33,7 @@ describe('', function () { }) it('should render an upgrade link and track clicks', function () { - const upgradeLink = screen.getByRole('button', { name: /upgrade/i }) + const upgradeLink = screen.getByRole('link', { name: /upgrade/i }) expect(upgradeLink.getAttribute('href')).to.equal( '/user/subscription/plans' ) @@ -52,7 +52,7 @@ describe('', function () { it('should render a link to initiate integration linking', function () { expect( - screen.getByRole('button', { name: 'Link' }).getAttribute('href') + screen.getByRole('link', { name: 'Link' }).getAttribute('href') ).to.equal('/link') }) diff --git a/services/web/test/frontend/features/settings/components/linking/sso-widget.test.tsx b/services/web/test/frontend/features/settings/components/linking/sso-widget.test.tsx index 5d29176c17..2f02b7709b 100644 --- a/services/web/test/frontend/features/settings/components/linking/sso-widget.test.tsx +++ b/services/web/test/frontend/features/settings/components/linking/sso-widget.test.tsx @@ -33,7 +33,7 @@ describe('', function () { it('should render a link to `linkPath`', function () { render() expect( - screen.getByRole('button', { name: /link/i }).getAttribute('href') + screen.getByRole('link', { name: /link/i }).getAttribute('href') ).to.equal('/integration/link?intent=link') }) }) diff --git a/services/web/test/frontend/features/settings/components/security-section.test.tsx b/services/web/test/frontend/features/settings/components/security-section.test.tsx index 08aa89e9a4..e1cfd98c2f 100644 --- a/services/web/test/frontend/features/settings/components/security-section.test.tsx +++ b/services/web/test/frontend/features/settings/components/security-section.test.tsx @@ -22,7 +22,7 @@ describe('', function () { render() expect(screen.getAllByText('Single Sign-On (SSO)').length).to.equal(2) - const link = screen.getByRole('button', { + const link = screen.getByRole('link', { name: /Set up SSO/i, }) expect(link).to.exist diff --git a/services/web/test/frontend/features/subscription/components/dashboard/personal-subscription.test.tsx b/services/web/test/frontend/features/subscription/components/dashboard/personal-subscription.test.tsx index 065aeec79f..6f09d66ab8 100644 --- a/services/web/test/frontend/features/subscription/components/dashboard/personal-subscription.test.tsx +++ b/services/web/test/frontend/features/subscription/components/dashboard/personal-subscription.test.tsx @@ -82,7 +82,7 @@ describe('', function () { screen.getByText('No further payments will be taken.', { exact: false }) - screen.getByRole('button', { name: 'View your invoices' }) + screen.getByRole('link', { name: 'View your invoices' }) screen.getByRole('button', { name: 'Reactivate your subscription' }) }) From 6d35305d7da16d46767ec141990dea6620da1b13 Mon Sep 17 00:00:00 2001 From: Rebeka Dekany <50901361+rebekadekany@users.noreply.github.com> Date: Tue, 13 May 2025 12:02:55 +0200 Subject: [PATCH 092/194] Use the semantic
    HTML tag instead for disclosure (#25489) GitOrigin-RevId: 9ed634529a17abd0693441c7563262ed5d1c7f88 --- .../web/frontend/stylesheets/bootstrap-5/pages/onboarding.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/services/web/frontend/stylesheets/bootstrap-5/pages/onboarding.scss b/services/web/frontend/stylesheets/bootstrap-5/pages/onboarding.scss index 616f6e498b..da59fafdca 100644 --- a/services/web/frontend/stylesheets/bootstrap-5/pages/onboarding.scss +++ b/services/web/frontend/stylesheets/bootstrap-5/pages/onboarding.scss @@ -106,7 +106,6 @@ user-select: none; } -.onboarding-collapse-button, .onboarding-privacy-extended { @include media-breakpoint-down(md) { padding: 0 var(--spacing-08); From 21c035b8d56361eb170a3cba9deef9fc3985baff Mon Sep 17 00:00:00 2001 From: ilkin-overleaf <100852799+ilkin-overleaf@users.noreply.github.com> Date: Tue, 13 May 2025 13:05:26 +0300 Subject: [PATCH 093/194] Merge pull request #25468 from overleaf/ii-flexible-licensing-invoice-date [web] FL next invoice date formatting GitOrigin-RevId: 5f4f86d4f11c7ee217ff806d26fc3f8a79e5affc --- .../components/add-seats/cost-summary.tsx | 3 ++- .../upgrade-subscription-upgrade-summary.tsx | 6 +++++- services/web/frontend/js/features/utils/format-date.ts | 9 +++++++-- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/services/web/frontend/js/features/group-management/components/add-seats/cost-summary.tsx b/services/web/frontend/js/features/group-management/components/add-seats/cost-summary.tsx index 812faabbea..d13c543852 100644 --- a/services/web/frontend/js/features/group-management/components/add-seats/cost-summary.tsx +++ b/services/web/frontend/js/features/group-management/components/add-seats/cost-summary.tsx @@ -156,7 +156,8 @@ function CostSummary({ subscriptionChange, totalLicenses }: CostSummaryProps) { ), date: formatTime( subscriptionChange.nextInvoice.date, - 'MMMM D' + 'MMMM D', + true ), } )} diff --git a/services/web/frontend/js/features/group-management/components/upgrade-subscription/upgrade-subscription-upgrade-summary.tsx b/services/web/frontend/js/features/group-management/components/upgrade-subscription/upgrade-subscription-upgrade-summary.tsx index d7546ccfa7..23cb76a294 100644 --- a/services/web/frontend/js/features/group-management/components/upgrade-subscription/upgrade-subscription-upgrade-summary.tsx +++ b/services/web/frontend/js/features/group-management/components/upgrade-subscription/upgrade-subscription-upgrade-summary.tsx @@ -100,7 +100,11 @@ function UpgradeSummary({ subscriptionChange }: UpgradeSummaryProps) { subscriptionChange.nextInvoice.tax.amount, subscriptionChange.currency ), - date: formatTime(subscriptionChange.nextInvoice.date, 'MMMM D'), + date: formatTime( + subscriptionChange.nextInvoice.date, + 'MMMM D', + true + ), } )} {subscriptionChange.immediateCharge.discount !== 0 && diff --git a/services/web/frontend/js/features/utils/format-date.ts b/services/web/frontend/js/features/utils/format-date.ts index 5210bf6962..d347c832b0 100644 --- a/services/web/frontend/js/features/utils/format-date.ts +++ b/services/web/frontend/js/features/utils/format-date.ts @@ -11,8 +11,13 @@ moment.updateLocale('en', { }, }) -export function formatTime(date: moment.MomentInput, format = 'h:mm a') { - return moment(date).format(format) +export function formatTime( + date: moment.MomentInput, + format = 'h:mm a', + utc = false +) { + const momentDate = utc ? moment.utc(date) : moment(date) + return momentDate.format(format) } export function relativeDate(date: moment.MomentInput) { From 587390d0666b714c67ce79333c09a7a89a9878ea Mon Sep 17 00:00:00 2001 From: Mathias Jakobsen Date: Tue, 13 May 2025 11:06:07 +0100 Subject: [PATCH 094/194] Merge pull request #25514 from overleaf/mj-ide-pdf-controls-editor-only [web] Hide PDF controls from menu bar in editor only mode GitOrigin-RevId: 91513e40693e1214c3bdbc56d9dec19e08c74e36 --- .../components/pdf-viewer-controls-toolbar.tsx | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/services/web/frontend/js/features/pdf-preview/components/pdf-viewer-controls-toolbar.tsx b/services/web/frontend/js/features/pdf-preview/components/pdf-viewer-controls-toolbar.tsx index f11769f4e6..fed34bf3ee 100644 --- a/services/web/frontend/js/features/pdf-preview/components/pdf-viewer-controls-toolbar.tsx +++ b/services/web/frontend/js/features/pdf-preview/components/pdf-viewer-controls-toolbar.tsx @@ -8,6 +8,7 @@ import PdfViewerControlsMenuButton from './pdf-viewer-controls-menu-button' import { useDetachCompileContext as useCompileContext } from '../../../shared/context/detach-compile-context' import { useCommandProvider } from '@/features/ide-react/hooks/use-command-provider' import { useTranslation } from 'react-i18next' +import { useLayoutContext } from '@/shared/context/layout-context' type PdfViewerControlsToolbarProps = { requestPresentationMode: () => void @@ -44,8 +45,15 @@ function PdfViewerControlsToolbar({ const { elementRef: pdfControlsRef } = useResizeObserver(handleResize) - useCommandProvider( - () => [ + const { view: ideView, pdfLayout } = useLayoutContext() + const editorOnly = ideView !== 'pdf' && pdfLayout === 'flat' + + useCommandProvider(() => { + if (editorOnly) { + return + } + + return [ { id: 'view-pdf-presentation-mode', label: t('presentation_mode'), @@ -71,9 +79,8 @@ function PdfViewerControlsToolbar({ label: t('fit_to_height'), handler: () => setZoom('page-height'), }, - ], - [t, requestPresentationMode, setZoom] - ) + ] + }, [t, requestPresentationMode, setZoom, editorOnly]) if (!toolbarControlsElement) { return null From b52c0bf08ea6c0b74e64f2fe8678658e5536a548 Mon Sep 17 00:00:00 2001 From: Mathias Jakobsen Date: Tue, 13 May 2025 11:06:14 +0100 Subject: [PATCH 095/194] Merge pull request #25534 from overleaf/mj-table-gen-color [web] Fix table generator edited cell color GitOrigin-RevId: 7ad762954637b13022a361f87fdf08f7dc97e17f --- .../features/source-editor/extensions/visual/table-generator.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/services/web/frontend/js/features/source-editor/extensions/visual/table-generator.ts b/services/web/frontend/js/features/source-editor/extensions/visual/table-generator.ts index 42cc6cc5de..10772e3dc6 100644 --- a/services/web/frontend/js/features/source-editor/extensions/visual/table-generator.ts +++ b/services/web/frontend/js/features/source-editor/extensions/visual/table-generator.ts @@ -322,6 +322,7 @@ export const tableGeneratorTheme = EditorView.baseTheme({ }, '.table-generator-cell-input': { + color: 'inherit', 'background-color': 'transparent', width: '100%', 'text-align': 'inherit', From 5730cd3dde39ce64ca6c871424c5c4e05fa6fd43 Mon Sep 17 00:00:00 2001 From: Jimmy Domagala-Tang Date: Tue, 13 May 2025 08:11:44 -0400 Subject: [PATCH 096/194] Merge pull request #25577 from overleaf/dk-assist-promition-spacing-fix Fix alignment issue in equation and table generator promotion GitOrigin-RevId: 2f085e266b0385efcae546b89e2c73388764bae3 --- .../web/frontend/stylesheets/bootstrap-5/modules/writefull.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/services/web/frontend/stylesheets/bootstrap-5/modules/writefull.scss b/services/web/frontend/stylesheets/bootstrap-5/modules/writefull.scss index c5dd1a32e7..9d5afb20e6 100644 --- a/services/web/frontend/stylesheets/bootstrap-5/modules/writefull.scss +++ b/services/web/frontend/stylesheets/bootstrap-5/modules/writefull.scss @@ -23,6 +23,7 @@ .feature-rebrand-promo-title-container { display: flex; + justify-content: space-between; } .feature-rebrand-promo-title-text { From dd351f64fbf2d0e024c7242eb79e88a175126706 Mon Sep 17 00:00:00 2001 From: Jimmy Domagala-Tang Date: Tue, 13 May 2025 08:12:04 -0400 Subject: [PATCH 097/194] Merge pull request #25587 from overleaf/dk-select-table-size-fix Prevent "select size" popover in TableDropdown from closing when toolbar is collapsed GitOrigin-RevId: 82f8226e0ff071dfea965c8c991141d90ff72197 --- .../components/toolbar/button-menu.tsx | 20 ++++++++++++++++--- .../components/toolbar/table-dropdown.tsx | 19 ++++++++++-------- 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/services/web/frontend/js/features/source-editor/components/toolbar/button-menu.tsx b/services/web/frontend/js/features/source-editor/components/toolbar/button-menu.tsx index 12fa52032e..086ecc66e5 100644 --- a/services/web/frontend/js/features/source-editor/components/toolbar/button-menu.tsx +++ b/services/web/frontend/js/features/source-editor/components/toolbar/button-menu.tsx @@ -1,4 +1,4 @@ -import { FC, memo, useRef } from 'react' +import { FC, memo, useEffect, useRef } from 'react' import useDropdown from '../../../../shared/hooks/use-dropdown' import OLListGroup from '@/features/ui/components/ol/ol-list-group' import OLTooltip from '@/features/ui/components/ol/ol-tooltip' @@ -13,13 +13,27 @@ export const ToolbarButtonMenu: FC< id: string label: string icon: React.ReactNode + disablePopover?: boolean altCommand?: (view: EditorView) => void }> -> = memo(function ButtonMenu({ icon, id, label, altCommand, children }) { +> = memo(function ButtonMenu({ + icon, + id, + label, + altCommand, + disablePopover, + children, +}) { const target = useRef(null) const { open, onToggle, ref } = useDropdown() const view = useCodeMirrorViewContext() + useEffect(() => { + if (disablePopover && open) { + onToggle(false) + } + }, [open, disablePopover, onToggle]) + const button = ( + + + ) +}) diff --git a/services/web/frontend/js/features/review-panel-new/components/review-panel-change.tsx b/services/web/frontend/js/features/review-panel-new/components/review-panel-change.tsx index 06d10ece2b..a153b1e1d4 100644 --- a/services/web/frontend/js/features/review-panel-new/components/review-panel-change.tsx +++ b/services/web/frontend/js/features/review-panel-new/components/review-panel-change.tsx @@ -1,4 +1,4 @@ -import { memo, useCallback, useState } from 'react' +import { memo, useCallback, useMemo, useState } from 'react' import { useRangesActionsContext } from '../context/ranges-context' import { Change, @@ -8,8 +8,6 @@ import { import { useTranslation } from 'react-i18next' import classnames from 'classnames' import { usePermissionsContext } from '@/features/ide-react/context/permissions-context' -import OLTooltip from '@/features/ui/components/ol/ol-tooltip' -import MaterialIcon from '@/shared/components/material-icon' import { FormatTimeBasedOnYear } from '@/shared/components/format-time-based-on-year' import { useChangesUsersContext } from '../context/changes-users-context' import { ReviewPanelChangeUser } from './review-panel-change-user' @@ -17,7 +15,12 @@ import { ReviewPanelEntry } from './review-panel-entry' import { useModalsContext } from '@/features/ide-react/context/modals-context' import { ExpandableContent } from './review-panel-expandable-content' import { useUserContext } from '@/shared/context/user-context' -import { PreventSelectingEntry } from './review-panel-prevent-selecting' +import { ChangeAction } from '@/features/review-panel-new/components/review-panel-change-action' +import { + AddIcon, + DeleteIcon, + EditIcon, +} from '@/features/review-panel-new/components/review-panel-action-icons' export const ReviewPanelChange = memo<{ change: Change @@ -27,8 +30,8 @@ export const ReviewPanelChange = memo<{ docId: string hoverRanges?: boolean hovered?: boolean - onEnter?: (changeId: string) => void - onLeave?: (changeId: string) => void + handleEnter?: (changeId: string) => void + handleLeave?: () => void }>( ({ change, @@ -38,8 +41,8 @@ export const ReviewPanelChange = memo<{ hoverRanges, editable = true, hovered, - onEnter, - onLeave, + handleEnter, + handleLeave, }) => { const { t } = useTranslation() const { acceptChanges, rejectChanges } = useRangesActionsContext() @@ -68,6 +71,34 @@ export const ReviewPanelChange = memo<{ } }, [acceptChanges, aggregate, change.id, showGenericMessageModal, t]) + const rejectHandler = useCallback(async () => { + if (aggregate) { + await rejectChanges(change.id, aggregate.id) + } else { + await rejectChanges(change.id) + } + }, [aggregate, change, rejectChanges]) + + const translations = useMemo( + () => ({ + accept_change: t('accept_change'), + reject_change: t('reject_change'), + aggregate_changed: t('aggregate_changed'), + aggregate_to: t('aggregate_to'), + tracked_change_added: t('tracked_change_added'), + tracked_change_deleted: t('tracked_change_deleted'), + }), + [t] + ) + + const { handleMouseEnter, handleMouseLeave } = useMemo( + () => ({ + handleMouseEnter: handleEnter && (() => handleEnter(change.id)), + handleMouseLeave: handleLeave && (() => handleLeave()), + }), + [change.id, handleEnter, handleLeave] + ) + if (!changesUsers) { // if users are not loaded yet, do not show "Unknown" user return null @@ -90,14 +121,14 @@ export const ReviewPanelChange = memo<{ docId={docId} hoverRanges={hoverRanges} disabled={accepting} - onEnterEntryIndicator={onEnter && (() => onEnter(change.id))} - onLeaveEntryIndicator={onLeave && (() => onLeave(change.id))} + handleEnter={handleMouseEnter} + handleLeave={handleMouseLeave} entryIndicator="edit" >
    onEnter(change.id))} - onMouseLeave={onLeave && (() => onLeave(change.id))} + onMouseEnter={handleMouseEnter} + onMouseLeave={handleMouseLeave} >
    @@ -111,56 +142,22 @@ export const ReviewPanelChange = memo<{ {editable && (
    {permissions.write && ( - - - - - + )} {(permissions.write || (permissions.trackedWrite && isChangeAuthor)) && ( - - - - - + )}
    )} @@ -169,21 +166,11 @@ export const ReviewPanelChange = memo<{
    {'i' in change.op && ( <> - {aggregateChange ? ( - - ) : ( - - )} + {aggregateChange ? : } {aggregateChange ? ( - {t('aggregate_changed')}:{' '} + {translations.aggregate_changed}:{' '} {' '} - {t('aggregate_to')}{' '} + {translations.aggregate_to}{' '} ) : ( - {t('tracked_change_added')}:  + {translations.tracked_change_added}:  - - + - {t('tracked_change_deleted')}:  + {translations.tracked_change_deleted}:  Promise onDeleteThread?: (threadId: ThreadId) => Promise onResolve?: () => Promise - onLeave?: (changeId: string) => void - onEnter?: (changeId: string) => void + onLeave?: () => void + onEnter?: () => void }>( ({ comment, @@ -69,8 +69,8 @@ export const ReviewPanelCommentContent = memo<{ return (
    onEnter(comment.id))} - onMouseLeave={onLeave && (() => onLeave(comment.id))} + onMouseEnter={onEnter} + onMouseLeave={onLeave} > {thread.messages.map((message, i) => { const isReply = i !== 0 diff --git a/services/web/frontend/js/features/review-panel-new/components/review-panel-comment.tsx b/services/web/frontend/js/features/review-panel-new/components/review-panel-comment.tsx index dd6366f091..c7f60aac35 100644 --- a/services/web/frontend/js/features/review-panel-new/components/review-panel-comment.tsx +++ b/services/web/frontend/js/features/review-panel-new/components/review-panel-comment.tsx @@ -1,4 +1,4 @@ -import { memo, useCallback, useState } from 'react' +import { memo, useCallback, useMemo, useState } from 'react' import { Change, CommentOperation } from '../../../../../types/change' import { useThreadsActionsContext, @@ -21,160 +21,169 @@ export const ReviewPanelComment = memo<{ docId: string top?: number hoverRanges?: boolean - onEnter?: (changeId: string) => void - onLeave?: (changeId: string) => void + handleEnter?: (changeId: string) => void + handleLeave?: (changeId: string) => void hovered?: boolean -}>(({ comment, top, hovered, onEnter, onLeave, docId, hoverRanges }) => { - const threads = useThreadsContext() - const { - resolveThread, - editMessage, - deleteMessage, - deleteOwnMessage, - deleteThread, - addMessage, - } = useThreadsActionsContext() - const { showGenericMessageModal } = useModalsContext() - const { t } = useTranslation() - const permissions = usePermissionsContext() - - const [processing, setProcessing] = useState(false) - - const handleResolveComment = useCallback(async () => { - setProcessing(true) - try { - await resolveThread(comment.op.t) - } catch (err) { - debugConsole.error(err) - showGenericMessageModal( - t('resolve_comment_error_title'), - t('resolve_comment_error_message') - ) - } finally { - setProcessing(false) - } - }, [comment.op.t, resolveThread, showGenericMessageModal, t]) - - const handleEditMessage = useCallback( - async (commentId: CommentId, content: string) => { - setProcessing(true) - try { - await editMessage(comment.op.t, commentId, content) - } catch (err) { - debugConsole.error(err) - showGenericMessageModal( - t('edit_comment_error_title'), - t('edit_comment_error_message') - ) - } finally { - setProcessing(false) - } - }, - [comment.op.t, editMessage, showGenericMessageModal, t] - ) - - const handleDeleteMessage = useCallback( - async (commentId: CommentId) => { - setProcessing(true) - try { - if (permissions.resolveAllComments) { - // Owners and editors can delete any message - await deleteMessage(comment.op.t, commentId) - } else if (permissions.resolveOwnComments) { - // Reviewers can only delete their own messages - await deleteOwnMessage(comment.op.t, commentId) - } - } catch (err) { - debugConsole.error(err) - showGenericMessageModal( - t('delete_comment_error_title'), - t('delete_comment_error_message') - ) - } finally { - setProcessing(false) - } - }, - [ - comment.op.t, +}>( + ({ comment, top, hovered, handleEnter, handleLeave, docId, hoverRanges }) => { + const threads = useThreadsContext() + const { + resolveThread, + editMessage, deleteMessage, deleteOwnMessage, - showGenericMessageModal, - t, - permissions.resolveOwnComments, - permissions.resolveAllComments, - ] - ) + deleteThread, + addMessage, + } = useThreadsActionsContext() + const { showGenericMessageModal } = useModalsContext() + const { t } = useTranslation() + const permissions = usePermissionsContext() - const handleDeleteThread = useCallback( - async (commentId: ThreadId) => { + const [processing, setProcessing] = useState(false) + + const handleResolveComment = useCallback(async () => { setProcessing(true) try { - await deleteThread(commentId) + await resolveThread(comment.op.t) } catch (err) { debugConsole.error(err) showGenericMessageModal( - t('delete_comment_error_title'), - t('delete_comment_error_message') + t('resolve_comment_error_title'), + t('resolve_comment_error_message') ) } finally { setProcessing(false) } - }, - [deleteThread, showGenericMessageModal, t] - ) + }, [comment.op.t, resolveThread, showGenericMessageModal, t]) - const handleSubmitReply = useCallback( - async (content: string) => { - setProcessing(true) - try { - await addMessage(comment.op.t, content) - } catch (err) { - debugConsole.error(err) - showGenericMessageModal( - t('add_comment_error_title'), - t('add_comment_error_message') - ) - throw err - } finally { - setProcessing(false) + const handleEditMessage = useCallback( + async (commentId: CommentId, content: string) => { + setProcessing(true) + try { + await editMessage(comment.op.t, commentId, content) + } catch (err) { + debugConsole.error(err) + showGenericMessageModal( + t('edit_comment_error_title'), + t('edit_comment_error_message') + ) + } finally { + setProcessing(false) + } + }, + [comment.op.t, editMessage, showGenericMessageModal, t] + ) + + const handleDeleteMessage = useCallback( + async (commentId: CommentId) => { + setProcessing(true) + try { + if (permissions.resolveAllComments) { + // Owners and editors can delete any message + await deleteMessage(comment.op.t, commentId) + } else if (permissions.resolveOwnComments) { + // Reviewers can only delete their own messages + await deleteOwnMessage(comment.op.t, commentId) + } + } catch (err) { + debugConsole.error(err) + showGenericMessageModal( + t('delete_comment_error_title'), + t('delete_comment_error_message') + ) + } finally { + setProcessing(false) + } + }, + [ + comment.op.t, + deleteMessage, + deleteOwnMessage, + showGenericMessageModal, + t, + permissions.resolveOwnComments, + permissions.resolveAllComments, + ] + ) + + const handleDeleteThread = useCallback( + async (commentId: ThreadId) => { + setProcessing(true) + try { + await deleteThread(commentId) + } catch (err) { + debugConsole.error(err) + showGenericMessageModal( + t('delete_comment_error_title'), + t('delete_comment_error_message') + ) + } finally { + setProcessing(false) + } + }, + [deleteThread, showGenericMessageModal, t] + ) + + const handleSubmitReply = useCallback( + async (content: string) => { + setProcessing(true) + try { + await addMessage(comment.op.t, content) + } catch (err) { + debugConsole.error(err) + showGenericMessageModal( + t('add_comment_error_title'), + t('add_comment_error_message') + ) + throw err + } finally { + setProcessing(false) + } + }, + [addMessage, comment.op.t, showGenericMessageModal, t] + ) + + const { handleMouseEnter, handleMouseLeave } = useMemo(() => { + return { + handleMouseEnter: handleEnter && (() => handleEnter(comment.id)), + handleMouseLeave: handleLeave && (() => handleLeave(comment.id)), } - }, - [addMessage, comment.op.t, showGenericMessageModal, t] - ) + }, [comment.id, handleEnter, handleLeave]) - const thread = threads?.[comment.op.t] - if (!thread || thread.resolved || thread.messages.length === 0) { - return null + const thread = threads?.[comment.op.t] + if (!thread || thread.resolved || thread.messages.length === 0) { + return null + } + + return ( + + + + ) } - - return ( - onEnter(comment.id))} - onLeaveEntryIndicator={onLeave && (() => onLeave(comment.id))} - entryIndicator="comment" - > - - - ) -}) +) ReviewPanelComment.displayName = 'ReviewPanelComment' diff --git a/services/web/frontend/js/features/review-panel-new/components/review-panel-current-file.tsx b/services/web/frontend/js/features/review-panel-new/components/review-panel-current-file.tsx index 5f65e9fbb0..b79fc85f1c 100644 --- a/services/web/frontend/js/features/review-panel-new/components/review-panel-current-file.tsx +++ b/services/web/frontend/js/features/review-panel-new/components/review-panel-current-file.tsx @@ -53,7 +53,7 @@ const ReviewPanelCurrentFile: FC = () => { setHoveredEntry(id) }, []) - const handleEntryLeave = useCallback((id: string) => { + const handleEntryLeave = useCallback(() => { clearTimeout(hoverTimeout.current) hoverTimeout.current = window.setTimeout(() => { setHoveredEntry(null) @@ -333,8 +333,8 @@ const ReviewPanelCurrentFile: FC = () => { top={positions.get(change.id)} aggregate={aggregatedRanges.aggregates.get(change.id)} hovered={hoveredEntry === change.id} - onEnter={handleEntryEnter} - onLeave={handleEntryLeave} + handleEnter={handleEntryEnter} + handleLeave={handleEntryLeave} /> ) )} @@ -348,8 +348,8 @@ const ReviewPanelCurrentFile: FC = () => { comment={comment} top={positions.get(comment.id)} hovered={hoveredEntry === comment.id} - onEnter={handleEntryEnter} - onLeave={handleEntryLeave} + handleEnter={handleEntryEnter} + handleLeave={handleEntryLeave} /> ) )} diff --git a/services/web/frontend/js/features/review-panel-new/components/review-panel-entry-indicator.tsx b/services/web/frontend/js/features/review-panel-new/components/review-panel-entry-indicator.tsx new file mode 100644 index 0000000000..ee14dc24f5 --- /dev/null +++ b/services/web/frontend/js/features/review-panel-new/components/review-panel-entry-indicator.tsx @@ -0,0 +1,27 @@ +import { memo, MouseEventHandler } from 'react' +import MaterialIcon from '@/shared/components/material-icon' + +export const EntryIndicator = memo<{ + handleMouseEnter?: MouseEventHandler + handleMouseLeave?: MouseEventHandler + handleMouseDown?: MouseEventHandler + type: string +}>(function EntryIndicator({ + handleMouseEnter, + handleMouseLeave, + handleMouseDown, + type, +}) { + return ( +
    + +
    + ) +}) diff --git a/services/web/frontend/js/features/review-panel-new/components/review-panel-entry.tsx b/services/web/frontend/js/features/review-panel-new/components/review-panel-entry.tsx index 4192dd518e..3576b7b542 100644 --- a/services/web/frontend/js/features/review-panel-new/components/review-panel-entry.tsx +++ b/services/web/frontend/js/features/review-panel-new/components/review-panel-entry.tsx @@ -12,9 +12,9 @@ import { } from '@/features/source-editor/extensions/ranges' import { useEditorManagerContext } from '@/features/ide-react/context/editor-manager-context' import { EditorSelection } from '@codemirror/state' -import MaterialIcon from '@/shared/components/material-icon' import { OFFSET_FOR_ENTRIES_ABOVE } from '../utils/position-items' import useReviewPanelLayout from '../hooks/use-review-panel-layout' +import { EntryIndicator } from './review-panel-entry-indicator' export const ReviewPanelEntry: FC< React.PropsWithChildren<{ @@ -26,8 +26,8 @@ export const ReviewPanelEntry: FC< selectLineOnFocus?: boolean hoverRanges?: boolean disabled?: boolean - onEnterEntryIndicator?: () => void - onLeaveEntryIndicator?: () => void + handleEnter?: () => void + handleLeave?: () => void entryIndicator?: 'comment' | 'edit' }> > = ({ @@ -40,8 +40,8 @@ export const ReviewPanelEntry: FC< docId, hoverRanges = true, disabled, - onEnterEntryIndicator, - onLeaveEntryIndicator, + handleEnter, + handleLeave, entryIndicator, }) => { const state = useCodeMirrorStateContext() @@ -194,19 +194,12 @@ export const ReviewPanelEntry: FC< }} > {entryIndicator && ( -
    - -
    + )} {children}
    diff --git a/services/web/frontend/js/features/review-panel-new/components/review-panel-expandable-content.tsx b/services/web/frontend/js/features/review-panel-new/components/review-panel-expandable-content.tsx index 3850614281..7a8d29f032 100644 --- a/services/web/frontend/js/features/review-panel-new/components/review-panel-expandable-content.tsx +++ b/services/web/frontend/js/features/review-panel-new/components/review-panel-expandable-content.tsx @@ -1,24 +1,24 @@ -import { FC, useCallback, useRef, useState } from 'react' +import { memo, useCallback, useRef, useState } from 'react' import OLButton from '@/features/ui/components/ol/ol-button' import { useTranslation } from 'react-i18next' import classNames from 'classnames' import { PreventSelectingEntry } from './review-panel-prevent-selecting' -export const ExpandableContent: FC<{ +export const ExpandableContent = memo<{ className?: string content: string contentLimit?: number newLineCharsLimit?: number checkNewLines?: boolean inline?: boolean -}> = ({ +}>(function ExpandableContent({ content, className, contentLimit = 50, newLineCharsLimit = 3, checkNewLines = true, inline = false, -}) => { +}) { const { t } = useTranslation() const contentRef = useRef(null) const [isExpanded, setIsExpanded] = useState(false) @@ -83,7 +83,7 @@ export const ExpandableContent: FC<{
    ) -} +}) function indexOfNthLine(content: string, n: number) { if (n < 1) return null diff --git a/services/web/frontend/js/features/review-panel-new/components/review-tooltip-menu.tsx b/services/web/frontend/js/features/review-panel-new/components/review-tooltip-menu.tsx index 16d3228aa6..ebb176c0b5 100644 --- a/services/web/frontend/js/features/review-panel-new/components/review-tooltip-menu.tsx +++ b/services/web/frontend/js/features/review-panel-new/components/review-tooltip-menu.tsx @@ -131,8 +131,8 @@ const ReviewTooltipMenuContent: FC<{ onAddComment: () => void }> = ({ showGenericConfirmModal({ message: t('confirm_accept_selected_changes', { count: nChanges }), title: t('accept_selected_changes'), - onConfirm: () => { - acceptChanges(...changeIdsInSelection) + onConfirm: async () => { + await acceptChanges(...changeIdsInSelection) }, primaryVariant: 'danger', }) @@ -153,8 +153,8 @@ const ReviewTooltipMenuContent: FC<{ onAddComment: () => void }> = ({ showGenericConfirmModal({ message: t('confirm_reject_selected_changes', { count: nChanges }), title: t('reject_selected_changes'), - onConfirm: () => { - rejectChanges(...changeIdsInSelection) + onConfirm: async () => { + await rejectChanges(...changeIdsInSelection) }, primaryVariant: 'danger', }) diff --git a/services/web/frontend/js/features/review-panel-new/context/ranges-context.tsx b/services/web/frontend/js/features/review-panel-new/context/ranges-context.tsx index 2e816ccdb2..7066a78bb3 100644 --- a/services/web/frontend/js/features/review-panel-new/context/ranges-context.tsx +++ b/services/web/frontend/js/features/review-panel-new/context/ranges-context.tsx @@ -32,8 +32,8 @@ export type Ranges = { export const RangesContext = createContext(undefined) type RangesActions = { - acceptChanges: (...ids: string[]) => void - rejectChanges: (...ids: string[]) => void + acceptChanges: (...ids: string[]) => Promise + rejectChanges: (...ids: string[]) => Promise } const buildRanges = (currentDocument: DocumentContainer | null) => { @@ -166,7 +166,7 @@ export const RangesProvider: FC = ({ children }) => { setRanges(buildRanges(currentDocument)) } }, - rejectChanges(...ids: string[]) { + async rejectChanges(...ids: string[]) { if (currentDocument?.ranges) { view.dispatch(rejectChanges(view.state, currentDocument.ranges, ids)) } diff --git a/services/web/frontend/js/features/review-panel-new/utils/position-items.ts b/services/web/frontend/js/features/review-panel-new/utils/position-items.ts index 26fecc19de..87fa92cd80 100644 --- a/services/web/frontend/js/features/review-panel-new/utils/position-items.ts +++ b/services/web/frontend/js/features/review-panel-new/utils/position-items.ts @@ -44,37 +44,39 @@ export const positionItems = debounce( const activeItemTop = getTopPosition(activeItem, activeItemIndex === 0) - activeItem.style.top = `${activeItemTop}px` - activeItem.style.visibility = 'visible' - const focusedItemRect = activeItem.getBoundingClientRect() + const positions: [HTMLElement, number][] = [] + positions.push([activeItem, activeItemTop]) // above the active item let topLimit = activeItemTop for (let i = activeItemIndex - 1; i >= 0; i--) { const item = items[i] - const rect = item.getBoundingClientRect() + const height = item.offsetHeight let top = getTopPosition(item, i === 0) - const bottom = top + rect.height + const bottom = top + height if (bottom > topLimit) { - top = topLimit - rect.height - GAP_BETWEEN_ENTRIES + top = topLimit - height - GAP_BETWEEN_ENTRIES } - item.style.top = `${top}px` - item.style.visibility = 'visible' + positions.push([item, top]) topLimit = top } // below the active item - let bottomLimit = activeItemTop + focusedItemRect.height + let bottomLimit = activeItemTop + activeItem.offsetHeight for (let i = activeItemIndex + 1; i < items.length; i++) { const item = items[i] - const rect = item.getBoundingClientRect() + const height = item.offsetHeight let top = getTopPosition(item, false) if (top < bottomLimit) { top = bottomLimit + GAP_BETWEEN_ENTRIES } + positions.push([item, top]) + bottomLimit = top + height + } + + for (const [item, top] of positions) { item.style.top = `${top}px` item.style.visibility = 'visible' - bottomLimit = top + rect.height } return { From fb0cfbe0bbe98f10aa73ff1b587df2bb8e6c14cd Mon Sep 17 00:00:00 2001 From: Alf Eaton Date: Tue, 13 May 2025 13:36:04 +0100 Subject: [PATCH 104/194] Add more detail to word count UI (#25400) GitOrigin-RevId: 3521f2ea03332e46ef1bac634ce0650cdce01249 --- .../web/frontend/extracted-translations.json | 5 + .../components/word-count-client.tsx | 7 +- .../components/word-count-data.ts | 4 +- .../components/word-count-server.tsx | 2 +- .../components/word-counts-client.tsx | 138 ++++++++++++++++++ .../components/word-counts.tsx | 105 ++++--------- .../utils/count-words-in-file.ts | 135 ++++++++++++----- .../word-count-modal/utils/segmenters.ts | 2 +- services/web/locales/en.json | 3 + .../utils/count-words-in-file.test.ts | 72 +++++++++ .../word-count-modal/utils/word-count.tex | 118 +++++++++++++++ 11 files changed, 474 insertions(+), 117 deletions(-) create mode 100644 services/web/frontend/js/features/word-count-modal/components/word-counts-client.tsx create mode 100644 services/web/test/frontend/features/word-count-modal/utils/count-words-in-file.test.ts create mode 100644 services/web/test/frontend/features/word-count-modal/utils/word-count.tex diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json index df46529ffe..bf0b0f3768 100644 --- a/services/web/frontend/extracted-translations.json +++ b/services/web/frontend/extracted-translations.json @@ -32,6 +32,7 @@ "about_to_leave_projects": "", "about_to_trash_projects": "", "about_writefull": "", + "abstract": "", "accept_and_continue": "", "accept_change": "", "accept_change_error_description": "", @@ -209,6 +210,7 @@ "cant_see_what_youre_looking_for_question": "", "caption_above": "", "caption_below": "", + "captions": "", "card_details": "", "card_details_are_not_valid": "", "card_must_be_authenticated_by_3dsecure": "", @@ -588,6 +590,7 @@ "footer_about_us": "", "footer_contact_us": "", "footer_navigation": "", + "footnotes": "", "for_enterprise": "", "for_government": "", "for_individuals_and_groups": "", @@ -1723,6 +1726,7 @@ "test_configuration_successful": "", "tex_live_version": "", "texgpt": "", + "text": "", "thank_you": "", "thank_you_exclamation": "", "thank_you_for_your_feedback": "", @@ -1860,6 +1864,7 @@ "tooltip_show_filetree": "", "tooltip_show_panel": "", "tooltip_show_pdf": "", + "total": "", "total_due_in_x_days": "", "total_due_today": "", "total_per_month": "", diff --git a/services/web/frontend/js/features/word-count-modal/components/word-count-client.tsx b/services/web/frontend/js/features/word-count-modal/components/word-count-client.tsx index 5f2660d4a8..899d8de2f5 100644 --- a/services/web/frontend/js/features/word-count-modal/components/word-count-client.tsx +++ b/services/web/frontend/js/features/word-count-modal/components/word-count-client.tsx @@ -11,8 +11,8 @@ import { debugConsole } from '@/utils/debugging' import { signalWithTimeout } from '@/utils/abort-signal' import { isMainFile } from '@/features/pdf-preview/util/editor-files' import { countWordsInFile } from '@/features/word-count-modal/utils/count-words-in-file' -import { WordCounts } from '@/features/word-count-modal/components/word-counts' import { createSegmenters } from '@/features/word-count-modal/utils/segmenters' +import { WordCountsClient } from './word-counts-client' export const WordCountClient: FC = () => { const [loading, setLoading] = useState(true) @@ -59,7 +59,8 @@ export const WordCountClient: FC = () => { footnoteWords: 0, footnoteCharacters: 0, outside: 0, - outsideCharacters: 0, + otherWords: 0, + otherCharacters: 0, headers: 0, elements: 0, mathInline: 0, @@ -99,7 +100,7 @@ export const WordCountClient: FC = () => { <> {loading && !error && } {error && } - {data && } + {data && } ) } diff --git a/services/web/frontend/js/features/word-count-modal/components/word-count-data.ts b/services/web/frontend/js/features/word-count-modal/components/word-count-data.ts index a69dc55d45..e322ac9636 100644 --- a/services/web/frontend/js/features/word-count-modal/components/word-count-data.ts +++ b/services/web/frontend/js/features/word-count-modal/components/word-count-data.ts @@ -20,6 +20,6 @@ export type WordCountData = ServerWordCountData & { footnoteCharacters: number abstractWords: number abstractCharacters: number - // outsideWords: number - outsideCharacters: number + otherWords: number + otherCharacters: number } diff --git a/services/web/frontend/js/features/word-count-modal/components/word-count-server.tsx b/services/web/frontend/js/features/word-count-modal/components/word-count-server.tsx index fccd49f8c3..a34807864e 100644 --- a/services/web/frontend/js/features/word-count-modal/components/word-count-server.tsx +++ b/services/web/frontend/js/features/word-count-modal/components/word-count-server.tsx @@ -42,7 +42,7 @@ export const WordCountServer: FC = () => { <> {loading && !error && } {error && } - {data && } + {data && } ) } diff --git a/services/web/frontend/js/features/word-count-modal/components/word-counts-client.tsx b/services/web/frontend/js/features/word-count-modal/components/word-counts-client.tsx new file mode 100644 index 0000000000..4c4aae65f7 --- /dev/null +++ b/services/web/frontend/js/features/word-count-modal/components/word-counts-client.tsx @@ -0,0 +1,138 @@ +import { FC, useMemo } from 'react' +import { WordCountData } from '@/features/word-count-modal/components/word-count-data' +import { useTranslation } from 'react-i18next' +import { Container, Row, Col, Form } from 'react-bootstrap-5' +import OLNotification from '@/features/ui/components/ol/ol-notification' +import usePersistedState from '@/shared/hooks/use-persisted-state' + +export const WordCountsClient: FC<{ data: WordCountData }> = ({ data }) => { + const { t } = useTranslation() + + const [included, setIncluded] = usePersistedState( + 'word-count-total', + ['text'] + ) + + const items = useMemo(() => { + return [ + { + key: 'text', + label: t('text'), + words: data.textWords, + chars: data.textCharacters, + }, + { + key: 'headers', + label: t('headers'), + words: data.headWords, + chars: data.headCharacters, + }, + { + key: 'abstract', + label: t('abstract'), + words: data.abstractWords, + chars: data.abstractCharacters, + }, + { + key: 'captions', + label: t('captions'), + words: data.captionWords, + chars: data.captionCharacters, + }, + { + key: 'footnotes', + label: t('footnotes'), + words: data.footnoteWords, + chars: data.footnoteCharacters, + }, + { + key: 'other', + label: t('other'), + words: data.otherWords, + chars: data.otherCharacters, + }, + ] + }, [data, t]) + + const totals = useMemo(() => { + const totals = { + words: 0, + chars: 0, + } + + for (const item of items) { + if (included.includes(item.key)) { + totals.words += item.words + totals.chars += item.chars + } + } + + return totals + }, [included, items]) + + return ( + + {data.messages && ( + + + {data.messages}

    + } + /> + +
    + )} + + {items.map(item => ( + + + + setIncluded(prevValue => { + return event.target.checked + ? prevValue.concat(item.key) + : prevValue.filter(key => key !== item.key) + }) + } + aria-label={`Include ${item.label} in total`} + /> + + + {item.words} words +
    + {item.chars} chars + +
    + ))} + + + + + {t('total')}: {totals.words} words +
    + {totals.chars} chars +
    + +
    +
    + ) +} diff --git a/services/web/frontend/js/features/word-count-modal/components/word-counts.tsx b/services/web/frontend/js/features/word-count-modal/components/word-counts.tsx index dec4d2e6d8..16d96525ac 100644 --- a/services/web/frontend/js/features/word-count-modal/components/word-counts.tsx +++ b/services/web/frontend/js/features/word-count-modal/components/word-counts.tsx @@ -1,22 +1,12 @@ -import { - ServerWordCountData, - WordCountData, -} from '@/features/word-count-modal/components/word-count-data' -import { useTranslation } from 'react-i18next' import { FC } from 'react' +import { ServerWordCountData } from '@/features/word-count-modal/components/word-count-data' +import { useTranslation } from 'react-i18next' import { Container, Row, Col } from 'react-bootstrap-5' import OLNotification from '@/features/ui/components/ol/ol-notification' -export const WordCounts: FC< - | { - data: ServerWordCountData - source: 'server' - } - | { - data: WordCountData - source: 'client' - } -> = ({ data, source }) => { +export const WordCounts: FC<{ + data: ServerWordCountData +}> = ({ data }) => { const { t } = useTranslation() return ( @@ -34,69 +24,32 @@ export const WordCounts: FC< )} - {source === 'client' ? ( - <> - - -
    Text:
    - - {data.textWords} -
    + + +
    {t('total_words')}:
    + + {data.textWords} +
    + + +
    {t('headers')}:
    + + {data.headers} +
    - - -
    Headers:
    - - {data.headWords} -
    + + +
    {t('math_inline')}:
    + + {data.mathInline} +
    - - -
    Captions:
    - - {data.captionWords} -
    - - - -
    Footnotes:
    - - {data.footnoteWords} -
    - - ) : ( - - -
    {t('total_words')}:
    - - {data.textWords} -
    - )} - - {source === 'server' && ( - <> - - -
    {t('headers')}:
    - - {data.headers} -
    - - - -
    {t('math_inline')}:
    - - {data.mathInline} -
    - - - -
    {t('math_display')}:
    - - {data.mathDisplay} -
    - - )} + + +
    {t('math_display')}:
    + + {data.mathDisplay} +
    ) } diff --git a/services/web/frontend/js/features/word-count-modal/utils/count-words-in-file.ts b/services/web/frontend/js/features/word-count-modal/utils/count-words-in-file.ts index d9a9154620..a14120d8d5 100644 --- a/services/web/frontend/js/features/word-count-modal/utils/count-words-in-file.ts +++ b/services/web/frontend/js/features/word-count-modal/utils/count-words-in-file.ts @@ -1,4 +1,3 @@ -import { ProjectSnapshot } from '@/infrastructure/project-snapshot' import { LaTeXLanguage } from '@/features/source-editor/languages/latex/latex-language' import { WordCountData } from '@/features/word-count-modal/components/word-count-data' import { NodeType, SyntaxNodeRef } from '@lezer/common' @@ -8,7 +7,7 @@ import { Segmenters } from './segmenters' const whiteSpaceRe = /^\s$/ -type Context = 'text' | 'header' | 'abstract' | 'caption' | 'footnote' +type Context = 'text' | 'header' | 'abstract' | 'caption' | 'footnote' | 'other' const counters: Record< Context, @@ -37,10 +36,15 @@ const counters: Record< word: 'footnoteWords', character: 'footnoteCharacters', }, + other: { + word: 'otherWords', + character: 'otherCharacters', + }, } +// https://en.wikibooks.org/wiki/LaTeX/Special_Characters#Escaped_codes const replacementsMap: Map = new Map([ - // LaTeX commands that create part of a word + // LaTeX commands that create characters ['aa', 'å'], ['AA', 'Å'], ['ae', 'æ'], @@ -63,19 +67,36 @@ const replacementsMap: Map = new Map([ ['NG', 'Ŋ'], ['i', 'ı'], ['j', 'ȷ'], + // reserved characters + ['&', '&'], + ['$', '$'], + ['%', '%'], + ['#', '#'], ['_', '_'], - // modifier commands for the character in the arguments - ['H', 'a'], - ['c', 'a'], - ['d', 'a'], - ['k', 'a'], - ['v', 'a'], + ['{', '{'], + ['}', '}'], + // modifier commands for the subsequent character(s) (in braces) + ['H', 'ő'], // long Hungarian umlaut (double acute) + ['b', 'o'], // bar under the letter + ['c', 'ç'], // cedilla + ['d', 'o'], // dot under the letter + ['k', 'ą'], // ogonek + ['r', 'å'], // ring over the letter + ['t', 'o͡o'], // "tie" over the two letters + ['u', 'ŏ'], // breve over the letter + ['v', 'š'], // caron/háček over the letter // modifier symbols for the subsequent character - ["'", ''], - ['^', ''], - ['"', ''], - ['=', ''], - ['.', ''], + ["'", ''], // acute + ['^', ''], // circumflex + ['"', ''], // umlaut, trema or dieresis + ['=', ''], // macron accent (a bar over the letter) + ['.', ''], // dot over the letter + ['`', ''], // grave + ['~', ''], // tilde + // commands that create text + ['TeX', 'TeX'], + ['LaTeX', 'LaTeX'], + ['textbackslash', '\\'], ]) type TextNode = { @@ -87,7 +108,7 @@ type TextNode = { export const countWordsInFile = ( data: WordCountData, - projectSnapshot: ProjectSnapshot, + projectSnapshot: { getDocContents(path: string): string | null }, docPath: string, segmenters: Segmenters ) => { @@ -106,10 +127,9 @@ export const countWordsInFile = ( const iterateNode = (nodeRef: SyntaxNodeRef, context: Context = 'text') => { const previousContext = currentContext currentContext = context - const { node } = nodeRef - node.cursor().iterate(childNodeRef => { + nodeRef.node.cursor().iterate(childNodeRef => { // TODO: a better way to iterate only descendants? - if (childNodeRef.node !== node) { + if (childNodeRef.node !== nodeRef.node) { return bodyMatcher(childNodeRef.type)?.(childNodeRef) } }) @@ -141,34 +161,72 @@ export const countWordsInFile = ( const child = nodeRef.node.getChild('UnknownCommand') if (!child) return - const grandchild = child.getChild('CtrlSeq') ?? child.getChild('CtrlSym') + const grandchild = + child.getChild('$CtrlSeq') ?? child.getChild('$CtrlSym') if (!grandchild) return const commandName = content.substring(grandchild.from + 1, grandchild.to) if (!commandName) return + switch (commandName) { + case 'thanks': + iterateNode(nodeRef, 'other') + return false + } + if (!replacementsMap.has(commandName)) return + // TODO: handle accented character in braces after a CtrlSym, e.g. \'{a} + // TODO: handle markup within words, e.g. inter\textbf{nal}formatting + // TODO: handle commands like \egrave and \eacute + const text = replacementsMap.get(commandName)! + textNodes.push({ from: nodeRef.from, to: nodeRef.to, text, context: currentContext, }) + return false }, - BeginEnv(nodeRef) { - const envName = content - ?.substring(nodeRef.from + '\\begin{'.length, nodeRef.to - 1) - .replace(/\*$/, '') + $Environment(nodeRef) { + const envNameNode = nodeRef.node + .getChild('BeginEnv') + ?.getChild('EnvNameGroup') + ?.getChild('EnvName') - if (envName === 'abstract') { - data.headers++ - iterateNode(nodeRef, 'abstract') - return false + if (envNameNode) { + const envName = content + ?.substring(envNameNode.from, envNameNode.to) + .replace(/\*$/, '') + + if (envName === 'abstract') { + data.headers++ + + const contentNode = nodeRef.node.getChild('Content') + if (contentNode) { + iterateNode(contentNode, 'abstract') + } + + return false + } } }, + BeginEnv() { + return false // ignore text in \begin arguments + }, + Math(nodeRef) { + const parent = nodeRef.node.parent + if (parent?.type.is('InlineMath') || parent?.type.is('ParenMath')) { + data.mathInline++ + } else { + data.mathDisplay++ + } + + return false // TODO: count \text in math nodes? + }, 'ShortTextArgument ShortOptionalArg'() { return false }, @@ -177,12 +235,6 @@ export const countWordsInFile = ( iterateNode(nodeRef, 'header') return false }, - 'DisplayMath BracketMath'() { - data.mathDisplay++ - }, - 'InlineMath ParenMath'() { - data.mathInline++ - }, Caption(nodeRef) { iterateNode(nodeRef, 'caption') return false @@ -201,6 +253,14 @@ export const countWordsInFile = ( countWordsInFile(data, projectSnapshot, path, segmenters) } }, + 'BlankLine LineBreak'(nodeRef) { + textNodes.push({ + from: nodeRef.from, + to: nodeRef.to, + text: '\n', + context: currentContext, + }) + }, }) const preambleExtent = findPreambleExtent(tree) @@ -226,6 +286,7 @@ export const countWordsInFile = ( caption: '', text: '', footnote: '', + other: '', } let pos = 0 @@ -240,12 +301,18 @@ export const countWordsInFile = ( for (const [context, text] of Object.entries(texts)) { const counter = counters[context as Context] - for (const value of segmenters.word.segment(text)) { + // TODO: replace - and _ with a word character if hyphenated words should be counted as one word? + + for (const value of segmenters.word.segment( + text.replace(/\w[-_]\w/g, 'aaa') + )) { if (value.isWordLike) { data[counter.word]++ } } + // TODO: count hyphens as characters? + for (const value of segmenters.character.segment(text)) { // TODO: option for whether to include whitespace? if (!whiteSpaceRe.test(value.segment)) { diff --git a/services/web/frontend/js/features/word-count-modal/utils/segmenters.ts b/services/web/frontend/js/features/word-count-modal/utils/segmenters.ts index e14c3365fd..7e52e1e6fa 100644 --- a/services/web/frontend/js/features/word-count-modal/utils/segmenters.ts +++ b/services/web/frontend/js/features/word-count-modal/utils/segmenters.ts @@ -1,5 +1,5 @@ const wordRe = /['\-.\p{L}]+/gu -const wordLikeRe = /\p{L}/gu // must contain at least one "letter" to be a word +const wordLikeRe = /\p{L}/u // must contain at least one "letter" to be a word const characterRe = /\S/gu type SegmentDataLike = { diff --git a/services/web/locales/en.json b/services/web/locales/en.json index 2735e04205..30ac3c472e 100644 --- a/services/web/locales/en.json +++ b/services/web/locales/en.json @@ -274,6 +274,7 @@ "cant_see_what_youre_looking_for_question": "Can’t see what you’re looking for?", "caption_above": "Caption above", "caption_below": "Caption below", + "captions": "Captions", "card_details": "Card details", "card_details_are_not_valid": "Card details are not valid", "card_must_be_authenticated_by_3dsecure": "Your card must be authenticated with 3D Secure before continuing", @@ -769,6 +770,7 @@ "footer_contact_us": "Contact us", "footer_navigation": "Footer navigation", "footer_plans_and_pricing": "Plans & pricing", + "footnotes": "Footnotes", "for_business": "For business", "for_enterprise": "For enterprise", "for_government": "For government", @@ -2234,6 +2236,7 @@ "test_configuration_successful": "Test configuration successful", "tex_live_version": "TeX Live version", "texgpt": "TexGPT", + "text": "Text", "thank_you": "Thank you!", "thank_you_email_confirmed": "Thank you, your email is now confirmed", "thank_you_exclamation": "Thank you!", diff --git a/services/web/test/frontend/features/word-count-modal/utils/count-words-in-file.test.ts b/services/web/test/frontend/features/word-count-modal/utils/count-words-in-file.test.ts new file mode 100644 index 0000000000..32c7b72c7d --- /dev/null +++ b/services/web/test/frontend/features/word-count-modal/utils/count-words-in-file.test.ts @@ -0,0 +1,72 @@ +import { readFile } from 'node:fs/promises' +import path from 'node:path' +import { countWordsInFile } from '@/features/word-count-modal/utils/count-words-in-file' +import { WordCountData } from '@/features/word-count-modal/components/word-count-data' +import { createSegmenters } from '@/features/word-count-modal/utils/segmenters' +import { expect } from 'chai' + +describe('word count', function () { + beforeEach(async function () { + this.data = { + encode: '', + textWords: 0, + headWords: 0, + outside: 0, + headers: 0, + elements: 0, + mathInline: 0, + mathDisplay: 0, + errors: 0, + messages: '', + textCharacters: 0, + headCharacters: 0, + captionWords: 0, + captionCharacters: 0, + footnoteWords: 0, + footnoteCharacters: 0, + abstractWords: 0, + abstractCharacters: 0, + otherWords: 0, + otherCharacters: 0, + } satisfies WordCountData + + const content = { + 'word-count.tex': await readFile( + path.join(__dirname, 'word-count.tex'), + 'utf-8' + ), + } + + this.projectSnapshot = { + getDocContents(path: keyof typeof content) { + return content[path] + }, + } + + this.segmenters = createSegmenters('en_US') + }) + + it('produces correct counts', function () { + countWordsInFile( + this.data, + this.projectSnapshot, + 'word-count.tex', + this.segmenters + ) + + expect(this.data).to.deep.include({ + abstractCharacters: 8, + abstractWords: 2, + captionCharacters: 16, + captionWords: 4, + footnoteCharacters: 8, + footnoteWords: 2, + headCharacters: 296, + headWords: 52, + otherCharacters: 10, + otherWords: 2, + textCharacters: 193, + textWords: 42, + }) + }) +}) diff --git a/services/web/test/frontend/features/word-count-modal/utils/word-count.tex b/services/web/test/frontend/features/word-count-modal/utils/word-count.tex new file mode 100644 index 0000000000..4522d6a3bc --- /dev/null +++ b/services/web/test/frontend/features/word-count-modal/utils/word-count.tex @@ -0,0 +1,118 @@ +\documentclass{article} +\usepackage{graphicx} +\usepackage{amsmath} + +\title{The Title} % 2 in headers +\author{An Author} % 0 +\date{May 2025} % 0 + +\begin{document} + +\maketitle % 0 + +\thanks{bleep bloop} + +\begin{abstract} +Word word % 2 in abstract +\end{abstract} + +\section{plain text} +Word word % 2 + +\section{accents} +w\'ard w\`erd w\"ird w\~ord w\.urd w\^ord % 6 + +\section{accents with groups} +% w\'{a}rd w\`{e}rd w\"{i}rd w\~{o}rd w\.{u}rd w\^{o}rd w\c{o}rd w\u{o}rd % 8 % TODO + +\section{grouped single character command} +% w{\o}rd w\~{\o}rd % 2 % TODO + +\section{commands that create characters} +\o\oe\aa\AE % 1 + +\subsection{with braces} +w\o{}rd w\oe{}rd w\aa{}rd w\AE{}rd % 4 + +\subsection{with spaces} +w\o rd w\oe rd w\ae rd w\AE rd % 4 + +\section{symbols} +\S{} \P{} % 0 + +\section{formatting} +\textit{italic} \textbf{bold} \textit{\textbf{bold italic}} % 4 +\texttt{teletype} \textsf{sans-serif} \textsc{small caps} % 4 +\textsf{-} % 0 + +\section{formatting inside word} +% wo\textit{italic}rd %1 % TODO + +\section{commands that create text} +\textbackslash{}word \LaTeX{} % 2 + +\section{special characters} +word\&word \$word word\% \#word wo\_rd \{word\} % 7 + +\section{footnote} +\footnote{word word} % 2 in footnote + +\section{headers} +\part{word} % 1 +\chapter{word} % 1 +\section{word} % 1 +\subsection{word} % 1 +\subsubsection{word} % 1 +\paragraph{word} % 1 + +\section{verbatim} +\verb|word word word| % 0 + +\section{list} +\begin{itemize} + \item word % 1 + \item word % 1 +\end{itemize} + +\section{figure} +\begin{figure} + \includegraphics[width=0.5\linewidth]{example.png} %0 + \caption{Word word} %2 in captions +\end{figure} + +\section{table} +\begin{table} + \begin{tabular}{c|c} + word & word \\ % 2 + word & word % 2 + \end{tabular} + \caption{Word word} % 2 in captions +\end{table} + +\section{line break} +word\\word % 2 + +\section{inline math} +$2+3=5$ % 0 + +\section{display math} +\[2+3=5\] + +\begin{equation} + 2+3=5 +\end{equation} + +\begin{equation*} + 2+3=5 +\end{equation*} + +\begin{align*} +2x - 5y &= 8 \\ +3x + 9y &= -12 +\end{align*} + +\section{text in math} + +$ 2+3 \text{ is equal to } 5 $ + +\end{document} From 443fb3f152dbdf73a589538313b9a986a8b9db48 Mon Sep 17 00:00:00 2001 From: Jimmy Domagala-Tang Date: Tue, 13 May 2025 08:53:26 -0400 Subject: [PATCH 105/194] Merge pull request #24739 from overleaf/dk-thankyou-page Update "thanks for subscribing page" with AI Assist links GitOrigin-RevId: 41a23d6fd5edfc8f9ad0f97e513e1ea66aed5bdc --- .../web/frontend/extracted-translations.json | 4 +- .../dashboard/premium-features-link.tsx | 57 ++++++++++++++++++- .../dashboard/subscription-dashboard.tsx | 5 +- .../successful-subscription.tsx | 2 +- .../subscription/data/add-on-codes.ts | 4 +- services/web/locales/en.json | 4 +- .../dashboard/subscription-dashboard.test.tsx | 4 +- .../successful-subscription.test.tsx | 6 +- 8 files changed, 73 insertions(+), 13 deletions(-) diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json index bf0b0f3768..53fc6adae9 100644 --- a/services/web/frontend/extracted-translations.json +++ b/services/web/frontend/extracted-translations.json @@ -626,7 +626,9 @@ "get_error_assist": "", "get_exclusive_access_to_labs": "", "get_in_touch": "", - "get_most_subscription_discover_premium_features": "", + "get_most_subscription_by_checking_ai_writefull": "", + "get_most_subscription_by_checking_overleaf": "", + "get_most_subscription_by_checking_overleaf_ai_writefull": "", "get_real_time_track_changes": "", "git": "", "git_authentication_token": "", diff --git a/services/web/frontend/js/features/subscription/components/dashboard/premium-features-link.tsx b/services/web/frontend/js/features/subscription/components/dashboard/premium-features-link.tsx index 068857f6c8..14d9c8682a 100644 --- a/services/web/frontend/js/features/subscription/components/dashboard/premium-features-link.tsx +++ b/services/web/frontend/js/features/subscription/components/dashboard/premium-features-link.tsx @@ -1,10 +1,63 @@ import { Trans } from 'react-i18next' +import { Subscription } from '../../../../../../types/subscription/dashboard/subscription' +import { AI_ADD_ON_CODE, isStandaloneAiPlanCode } from '../../data/add-on-codes' + +function PremiumFeaturesLink({ + subscription, +}: { + subscription?: Subscription +}) { + const hasAiAddon = subscription?.addOns?.some( + addOn => addOn.addOnCode === AI_ADD_ON_CODE + ) + const onAiStandalonePlan = isStandaloneAiPlanCode(subscription?.planCode) + + if (onAiStandalonePlan) { + return ( +

    + , + // eslint-disable-next-line react/jsx-key, jsx-a11y/anchor-has-content + , + ]} + /> +

    + ) + } + + if (hasAiAddon) { + return ( +

    + , + // eslint-disable-next-line react/jsx-key, jsx-a11y/anchor-has-content + , + // eslint-disable-next-line react/jsx-key, jsx-a11y/anchor-has-content + , + ]} + /> +

    + ) + } -function PremiumFeaturesLink() { return (

    , diff --git a/services/web/frontend/js/features/subscription/components/dashboard/subscription-dashboard.tsx b/services/web/frontend/js/features/subscription/components/dashboard/subscription-dashboard.tsx index 6bb25065ea..972e268597 100644 --- a/services/web/frontend/js/features/subscription/components/dashboard/subscription-dashboard.tsx +++ b/services/web/frontend/js/features/subscription/components/dashboard/subscription-dashboard.tsx @@ -21,6 +21,7 @@ function SubscriptionDashboard() { hasDisplayedSubscription, hasSubscription, hasValidActiveSubscription, + personalSubscription, } = useSubscriptionDashboardContext() const fromPlansPage = getMeta('ol-fromPlansPage') @@ -49,7 +50,9 @@ function SubscriptionDashboard() { - {hasValidActiveSubscription && } + {hasValidActiveSubscription && ( + + )} {!hasDisplayedSubscription && (hasSubscription ? : )}

    diff --git a/services/web/frontend/js/features/subscription/components/successful-subscription/successful-subscription.tsx b/services/web/frontend/js/features/subscription/components/successful-subscription/successful-subscription.tsx index e48ce0053f..ed80fd33b8 100644 --- a/services/web/frontend/js/features/subscription/components/successful-subscription/successful-subscription.tsx +++ b/services/web/frontend/js/features/subscription/components/successful-subscription/successful-subscription.tsx @@ -80,7 +80,7 @@ function SuccessfulSubscription() { subscription={subscription} onAiStandalonePlan={onAiStandalonePlan} /> - {!onAiStandalonePlan && } +

    {t('need_anything_contact_us_at')}  diff --git a/services/web/frontend/js/features/subscription/data/add-on-codes.ts b/services/web/frontend/js/features/subscription/data/add-on-codes.ts index 80942ff215..4ee7a65b22 100644 --- a/services/web/frontend/js/features/subscription/data/add-on-codes.ts +++ b/services/web/frontend/js/features/subscription/data/add-on-codes.ts @@ -4,10 +4,10 @@ import { PendingPaymentProviderPlan } from "../../../../../types/subscription/pl export const AI_ASSIST_STANDALONE_MONTHLY_PLAN_CODE = 'assistant' export const AI_ADD_ON_CODE = 'assistant' // we dont want translations on plan or add-on names -export const ADD_ON_NAME = "Error Assist" +export const ADD_ON_NAME = "AI Assist" export const AI_ASSIST_STANDALONE_ANNUAL_PLAN_CODE = 'assistant-annual' -export function isStandaloneAiPlanCode(planCode: string) { +export function isStandaloneAiPlanCode(planCode?: string) { return planCode === AI_ASSIST_STANDALONE_MONTHLY_PLAN_CODE || planCode === AI_ASSIST_STANDALONE_ANNUAL_PLAN_CODE } diff --git a/services/web/locales/en.json b/services/web/locales/en.json index 30ac3c472e..97818e2116 100644 --- a/services/web/locales/en.json +++ b/services/web/locales/en.json @@ -827,7 +827,9 @@ "get_in_touch": "Get in touch", "get_in_touch_having_problems": "Get in touch with support if you’re having problems", "get_involved": "Get involved", - "get_most_subscription_discover_premium_features": "Get the most from your __appName__ subscription. <0>Discover premium features.", + "get_most_subscription_by_checking_ai_writefull": "Get the most out of your subscription by checking out <0>Overleaf’s AI features and <1>Writefull’s features.", + "get_most_subscription_by_checking_overleaf": "Get the most out of your subscription by checking out <0>Overleaf’s features.", + "get_most_subscription_by_checking_overleaf_ai_writefull": "Get the most out of your subscription by checking out <0>Overleaf’s features, <1>Overleaf’s AI features and <2>Writefull’s features.", "get_real_time_track_changes": "Get real-time track changes", "get_the_best_overleaf_experience": "Get the best Overleaf experience", "get_the_most_out_headline": "Get the most out of __appName__ with features such as:", diff --git a/services/web/test/frontend/features/subscription/components/dashboard/subscription-dashboard.test.tsx b/services/web/test/frontend/features/subscription/components/dashboard/subscription-dashboard.test.tsx index 77d377cd60..364b09820e 100644 --- a/services/web/test/frontend/features/subscription/components/dashboard/subscription-dashboard.test.tsx +++ b/services/web/test/frontend/features/subscription/components/dashboard/subscription-dashboard.test.tsx @@ -32,7 +32,7 @@ describe('', function () { }) it('renders the "Get the most from your subscription" text', function () { - screen.getByText(/get the most from your Overleaf subscription/i) + screen.getByText(/Get the most out of your subscription/i) }) }) @@ -42,7 +42,7 @@ describe('', function () { }) it('does not render the "Get the most out of your" subscription text', function () { - const text = screen.queryByText('Get the most out of your', { + const text = screen.queryByText('Get the most out of your subscription', { exact: false, }) expect(text).to.be.null diff --git a/services/web/test/frontend/features/subscription/components/successful-subscription/successful-subscription.test.tsx b/services/web/test/frontend/features/subscription/components/successful-subscription/successful-subscription.test.tsx index 90453c9349..9259eaf259 100644 --- a/services/web/test/frontend/features/subscription/components/successful-subscription/successful-subscription.test.tsx +++ b/services/web/test/frontend/features/subscription/components/successful-subscription/successful-subscription.test.tsx @@ -43,8 +43,8 @@ describe('successful subscription page', function () { screen.getByText( /it’s support from people like yourself that allows .* to continue to grow and improve/i ) - expect(screen.getByText(/get the most from your/i).textContent).to.match( - /get the most from your .* subscription\. discover premium features/i + expect(screen.getByText(/get the most out of your/i).textContent).to.match( + /get the most out of your subscription by checking out Overleaf’s features/i ) expect( screen @@ -73,7 +73,7 @@ describe('successful subscription page', function () { ) const helpLink = screen.getByRole('link', { - name: /discover premium features/i, + name: /Overleaf’s features/i, }) expect(helpLink.getAttribute('href')).to.equal( '/learn/how-to/Overleaf_premium_features' From e25a69936ef1db2b206ba7cb4131d979bf95f910 Mon Sep 17 00:00:00 2001 From: Jakob Ackermann Date: Tue, 13 May 2025 13:54:07 +0100 Subject: [PATCH 106/194] [clsi-cache] base64 encode X-All-Files header if needed (#25579) * [clsi-cache] base64 encode X-All-Files header if needed * [clsi-cache] add explicit error check Co-authored-by: Brian Gough --------- Co-authored-by: Brian Gough GitOrigin-RevId: bd3b6381b68398aac4a07e48cd69e6aa97e94f18 --- services/web/app/src/Features/Compile/ClsiCacheHandler.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/services/web/app/src/Features/Compile/ClsiCacheHandler.js b/services/web/app/src/Features/Compile/ClsiCacheHandler.js index 76b5d50f12..c04afd483b 100644 --- a/services/web/app/src/Features/Compile/ClsiCacheHandler.js +++ b/services/web/app/src/Features/Compile/ClsiCacheHandler.js @@ -145,6 +145,10 @@ async function getRedirectWithFallback( } = await fetchRedirectWithResponse(u, { signal, }) + let allFilesRaw = headers.get('X-All-Files') + if (!allFilesRaw.startsWith('[')) { + allFilesRaw = Buffer.from(allFilesRaw, 'base64url').toString() + } // Success, return the cache entry. return { location, @@ -152,7 +156,7 @@ async function getRedirectWithFallback( shard: headers.get('X-Shard') || 'cache', lastModified: new Date(headers.get('X-Last-Modified')), size: parseInt(headers.get('X-Content-Length'), 10), - allFiles: JSON.parse(headers.get('X-All-Files')), + allFiles: JSON.parse(allFilesRaw), } } catch (err) { if (err instanceof RequestFailedError && err.response.status === 404) { From eebda2427ef9feeded5daef6fe75075fee8adee4 Mon Sep 17 00:00:00 2001 From: Jakob Ackermann Date: Tue, 13 May 2025 13:55:31 +0100 Subject: [PATCH 107/194] [clsi-cache] fix path traversal (#25585) * [clsi-cache] fix path traversal * [clsi-cache] double down on path traversal validation Co-authored-by: Brian Gough --------- Co-authored-by: Brian Gough GitOrigin-RevId: 28a6a2024aae81e9b361db7918dc0c5381cd8246 --- services/web/app/src/Features/Compile/ClsiCacheHandler.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/services/web/app/src/Features/Compile/ClsiCacheHandler.js b/services/web/app/src/Features/Compile/ClsiCacheHandler.js index c04afd483b..bb0414bf03 100644 --- a/services/web/app/src/Features/Compile/ClsiCacheHandler.js +++ b/services/web/app/src/Features/Compile/ClsiCacheHandler.js @@ -9,7 +9,15 @@ const Settings = require('@overleaf/settings') const OError = require('@overleaf/o-error') const { NotFoundError, InvalidNameError } = require('../Errors/Errors') +/** + * Keep in sync with validateFilename in services/clsi-cache/app/js/utils.js + * + * @param {string} filename + */ function validateFilename(filename) { + if (filename.split('/').includes('..')) { + throw new InvalidNameError('path traversal') + } if ( !( [ From 40193752c4f7294234052972f39687239172045d Mon Sep 17 00:00:00 2001 From: CloudBuild Date: Wed, 14 May 2025 01:08:59 +0000 Subject: [PATCH 108/194] auto update translation GitOrigin-RevId: c67507e30704ca92838cf1b72ae911624fc66200 --- services/web/locales/zh-CN.json | 1 + 1 file changed, 1 insertion(+) diff --git a/services/web/locales/zh-CN.json b/services/web/locales/zh-CN.json index 8b088d7cce..2efb3819e0 100644 --- a/services/web/locales/zh-CN.json +++ b/services/web/locales/zh-CN.json @@ -806,6 +806,7 @@ "generic_something_went_wrong": "抱歉,出错了", "get_collaborative_benefits": "从 __appName__ 获得协作优势,即使你喜欢离线工作", "get_discounted_plan": "获得折扣计划", + "get_error_assist": "获取错误帮助", "get_exclusive_access_to_labs": "加入 Overleaf Labs 后,即可获得早期实验的独家访问权。我们唯一的要求就是您提供真实的反馈,以帮助我们发展和改进。", "get_in_touch": "联系", "get_in_touch_having_problems": "如果遇到问题,请与支持部门联系", From ec1bd69605f21883d171a9fec9ca246457665f58 Mon Sep 17 00:00:00 2001 From: Jakob Ackermann Date: Thu, 15 May 2025 09:05:12 +0100 Subject: [PATCH 109/194] [clsi-cache] remove non sharded instances (#25645) * Revert "[clsi-cache] only use sharding from updated project editor tabs (#25326)" This reverts commit 1754276bed3186c0536055c983e32476cc90d416. * [clsi-cache] remove non sharded instances GitOrigin-RevId: aa3ac46140dfc1722a3350cf7071e5b11af61199 --- services/clsi/app/js/CLSICacheHandler.js | 9 +-------- services/clsi/app/js/CompileController.js | 1 - services/clsi/app/js/RequestParser.js | 8 -------- services/clsi/config/settings.defaults.js | 12 ++---------- services/web/app/src/Features/Compile/ClsiManager.js | 1 - .../app/src/Features/Compile/CompileController.js | 2 -- .../js/features/pdf-preview/util/compiler.js | 1 - .../test/unit/src/Compile/CompileControllerTests.js | 4 ---- 8 files changed, 3 insertions(+), 35 deletions(-) diff --git a/services/clsi/app/js/CLSICacheHandler.js b/services/clsi/app/js/CLSICacheHandler.js index 73137d23c3..b9415ae3ec 100644 --- a/services/clsi/app/js/CLSICacheHandler.js +++ b/services/clsi/app/js/CLSICacheHandler.js @@ -41,7 +41,6 @@ function getShard(projectId) { * @param {string} editorId * @param {[{path: string}]} outputFiles * @param {string} compileGroup - * @param {boolean} clsiCacheSharded * @param {Record} options * @return {string | undefined} */ @@ -52,17 +51,11 @@ function notifyCLSICacheAboutBuild({ editorId, outputFiles, compileGroup, - clsiCacheSharded, options, }) { if (!Settings.apis.clsiCache.enabled) return undefined if (!OBJECT_ID_REGEX.test(projectId)) return undefined - let { url, shard } = getShard(projectId) - if (!clsiCacheSharded) { - // Client is not aware of sharding yet. - url = Settings.apis.clsiCache.url - shard = 'cache' - } + const { url, shard } = getShard(projectId) /** * @param {[{path: string}]} files diff --git a/services/clsi/app/js/CompileController.js b/services/clsi/app/js/CompileController.js index 4c411a54cf..7329c14342 100644 --- a/services/clsi/app/js/CompileController.js +++ b/services/clsi/app/js/CompileController.js @@ -125,7 +125,6 @@ function compile(req, res, next) { editorId: request.editorId, outputFiles, compileGroup: request.compileGroup, - clsiCacheSharded: request.clsiCacheSharded, options: { compiler: request.compiler, draft: request.draft, diff --git a/services/clsi/app/js/RequestParser.js b/services/clsi/app/js/RequestParser.js index f65d6940c8..4e9d722921 100644 --- a/services/clsi/app/js/RequestParser.js +++ b/services/clsi/app/js/RequestParser.js @@ -90,14 +90,6 @@ function parse(body, callback) { type: 'boolean', } ) - response.clsiCacheSharded = _parseAttribute( - 'clsiCacheSharded', - compile.options.clsiCacheSharded, - { - default: false, - type: 'boolean', - } - ) response.check = _parseAttribute('check', compile.options.check, { type: 'string', }) diff --git a/services/clsi/config/settings.defaults.js b/services/clsi/config/settings.defaults.js index 7472334d80..35783eb5b0 100644 --- a/services/clsi/config/settings.defaults.js +++ b/services/clsi/config/settings.defaults.js @@ -60,16 +60,8 @@ module.exports = { }`, }, clsiCache: { - enabled: !!(process.env.CLSI_CACHE_SHARDS || process.env.CLSI_CACHE_HOST), - url: `http://${process.env.CLSI_CACHE_HOST}:3044`, - shards: process.env.CLSI_CACHE_SHARDS - ? JSON.parse(process.env.CLSI_CACHE_SHARDS) - : [ - { - url: `http://${process.env.CLSI_CACHE_HOST}:3044`, - shard: 'cache', - }, - ], + enabled: !!process.env.CLSI_CACHE_SHARDS, + shards: JSON.parse(process.env.CLSI_CACHE_SHARDS || '[]'), }, }, diff --git a/services/web/app/src/Features/Compile/ClsiManager.js b/services/web/app/src/Features/Compile/ClsiManager.js index 011ba60759..6f11297248 100644 --- a/services/web/app/src/Features/Compile/ClsiManager.js +++ b/services/web/app/src/Features/Compile/ClsiManager.js @@ -781,7 +781,6 @@ function _finaliseRequest(projectId, options, project, docs, files) { imageName: project.imageName, draft: Boolean(options.draft), stopOnFirstError: Boolean(options.stopOnFirstError), - clsiCacheSharded: Boolean(options.clsiCacheSharded), check: options.check, syncType: options.syncType, syncState: options.syncState, diff --git a/services/web/app/src/Features/Compile/CompileController.js b/services/web/app/src/Features/Compile/CompileController.js index 25f8190374..8b190402ff 100644 --- a/services/web/app/src/Features/Compile/CompileController.js +++ b/services/web/app/src/Features/Compile/CompileController.js @@ -128,14 +128,12 @@ const _CompileController = { const isAutoCompile = !!req.query.auto_compile const fileLineErrors = !!req.query.file_line_errors const stopOnFirstError = !!req.body.stopOnFirstError - const clsiCacheSharded = !!req.body.clsiCacheSharded const userId = SessionManager.getLoggedInUserId(req.session) const options = { isAutoCompile, fileLineErrors, stopOnFirstError, editorId: req.body.editorId, - clsiCacheSharded, } if (req.body.rootDoc_id) { diff --git a/services/web/frontend/js/features/pdf-preview/util/compiler.js b/services/web/frontend/js/features/pdf-preview/util/compiler.js index cd052cb5a9..d938cb3893 100644 --- a/services/web/frontend/js/features/pdf-preview/util/compiler.js +++ b/services/web/frontend/js/features/pdf-preview/util/compiler.js @@ -110,7 +110,6 @@ export default class DocumentCompiler { incrementalCompilesEnabled: !this.error, stopOnFirstError: options.stopOnFirstError, editorId: EDITOR_SESSION_ID, - clsiCacheSharded: true, } const data = await postJSON( diff --git a/services/web/test/unit/src/Compile/CompileControllerTests.js b/services/web/test/unit/src/Compile/CompileControllerTests.js index df7276131c..25af7926b1 100644 --- a/services/web/test/unit/src/Compile/CompileControllerTests.js +++ b/services/web/test/unit/src/Compile/CompileControllerTests.js @@ -251,7 +251,6 @@ describe('CompileController', function () { fileLineErrors: false, stopOnFirstError: false, editorId: undefined, - clsiCacheSharded: false, } ) }) @@ -294,7 +293,6 @@ describe('CompileController', function () { fileLineErrors: false, stopOnFirstError: false, editorId: undefined, - clsiCacheSharded: false, } ) }) @@ -319,7 +317,6 @@ describe('CompileController', function () { fileLineErrors: false, stopOnFirstError: false, editorId: undefined, - clsiCacheSharded: false, } ) }) @@ -343,7 +340,6 @@ describe('CompileController', function () { fileLineErrors: false, stopOnFirstError: false, editorId: 'the-editor-id', - clsiCacheSharded: false, } ) }) From 59f614a41b2da34ce44480222a7eac82c8ccce7a Mon Sep 17 00:00:00 2001 From: Jakob Ackermann Date: Thu, 15 May 2025 09:05:19 +0100 Subject: [PATCH 110/194] [web] use the common split-test for clsi-cache when cloning (#25644) GitOrigin-RevId: 30377d69a9e1be11261eb6076f8996e71090fb9e --- services/web/app/src/Features/Compile/ClsiCacheManager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/web/app/src/Features/Compile/ClsiCacheManager.js b/services/web/app/src/Features/Compile/ClsiCacheManager.js index 45970f4619..b1ef2a46ac 100644 --- a/services/web/app/src/Features/Compile/ClsiCacheManager.js +++ b/services/web/app/src/Features/Compile/ClsiCacheManager.js @@ -160,7 +160,7 @@ async function prepareClsiCache( ) { const { variant } = await SplitTestHandler.promises.getAssignmentForUser( userId, - 'copy-clsi-cache' + 'populate-clsi-cache' ) if (variant !== 'enabled') return From 2ebc3a982a7928ec8c6a8c393a88d5ec79e8f630 Mon Sep 17 00:00:00 2001 From: Tim Down <158919+timdown@users.noreply.github.com> Date: Thu, 15 May 2025 10:01:14 +0100 Subject: [PATCH 111/194] Merge pull request #25588 from overleaf/td-bs5-restricted-page Migrate restricted page to BS5 GitOrigin-RevId: 7df26700b5e3b8fb08d061fd9e211bf09ca4e956 --- services/web/app/views/user/restricted.pug | 11 ++++------- .../frontend/stylesheets/bootstrap-5/base/layout.scss | 9 +++++++++ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/services/web/app/views/user/restricted.pug b/services/web/app/views/user/restricted.pug index 949bd9b4b6..eba1d2ab05 100644 --- a/services/web/app/views/user/restricted.pug +++ b/services/web/app/views/user/restricted.pug @@ -1,16 +1,13 @@ extends ../layout-marketing -block vars - - bootstrap5PageStatus = 'disabled' - block content main.content#main-content .container .row - .col-md-8.col-md-offset-2.text-center + .col-md-8.offset-md-2.text-center .page-header h2 #{translate("restricted_no_permission")} p - a(href="/") - i.fa.fa-arrow-circle-o-left(aria-hidden="true") - | #{translate("take_me_home")} + span.inline-material-symbols + a(href="/").material-symbols(aria-hidden="true") arrow_left_alt + a(href="/") #{translate("take_me_home")} diff --git a/services/web/frontend/stylesheets/bootstrap-5/base/layout.scss b/services/web/frontend/stylesheets/bootstrap-5/base/layout.scss index a4bf73f87c..650bdc727f 100644 --- a/services/web/frontend/stylesheets/bootstrap-5/base/layout.scss +++ b/services/web/frontend/stylesheets/bootstrap-5/base/layout.scss @@ -78,3 +78,12 @@ hr { .row-spaced-extra-large { margin-top: calc(var(--line-height-03) * 4); } + +.inline-material-symbols { + display: inline-flex; + align-items: center; + + a.material-symbols { + text-decoration: none; + } +} From 5c4cb506285b050da3ee1d6d3c2a53b100f56a16 Mon Sep 17 00:00:00 2001 From: Tim Down <158919+timdown@users.noreply.github.com> Date: Thu, 15 May 2025 10:01:29 +0100 Subject: [PATCH 112/194] Merge pull request #24988 from overleaf/td-bs5-upgrade-and-rename Apply minor upgrades to Bootstrap 5 and react-bootstrap GitOrigin-RevId: eb013f38515ebd4b9572d139f00841aca344e3c6 --- package-lock.json | 23 +++++++------- .../components/editor-left-menu.tsx | 2 +- .../settings/settings-menu-select.tsx | 2 +- .../components/layout-dropdown-button.tsx | 2 +- .../modes/file-tree-import-from-project.tsx | 2 +- .../components/add-seats/add-seats.tsx | 2 +- .../components/add-seats/cost-summary.tsx | 2 +- .../components/add-seats/po-number.tsx | 2 +- .../group-management/components/card.tsx | 2 +- .../members-table/dropdown-button.tsx | 2 +- .../components/request-status.tsx | 2 +- .../upgrade-subscription-plan-details.tsx | 2 +- .../upgrade-subscription-upgrade-summary.tsx | 2 +- .../upgrade-subscription.tsx | 2 +- .../features/ide-redesign/components/rail.tsx | 2 +- .../components/settings/dropdown-setting.tsx | 2 +- .../settings/settings-modal-body.tsx | 2 +- .../toolbar/change-layout-options.tsx | 2 +- .../components/pdf-preview-hybrid-toolbar.tsx | 2 +- .../components/pdf-synctex-controls.tsx | 4 +-- .../components/dropdown/actions-dropdown.tsx | 2 +- .../components/sidebar/sidebar-ds-nav.tsx | 2 +- .../components/select-collaborators.jsx | 2 +- .../share-project-modal-content.tsx | 2 +- .../components/transfer-ownership-modal.jsx | 2 +- .../components/diagnostic-component.tsx | 2 +- .../components/socket-diagnostics.tsx | 2 +- .../components/full-project-search-button.tsx | 2 +- .../spelling-suggestions-language.tsx | 2 +- .../spelling/spelling-suggestions.tsx | 2 +- .../components/dashboard/pause-modal.tsx | 2 +- .../dashboard/states/active/add-ons.tsx | 2 +- .../writefull-bundle-management-modal.tsx | 2 +- .../bootstrap-5/badge-link-with-tooltip.tsx | 2 +- .../ui/components/bootstrap-5/badge.tsx | 2 +- .../ui/components/bootstrap-5/button.tsx | 2 +- .../components/bootstrap-5/dropdown-menu.tsx | 30 +++++++++---------- .../dropdown-toggle-with-tooltip.tsx | 4 +-- .../bootstrap-5/form/form-control.tsx | 5 +--- .../bootstrap-5/form/form-feedback.tsx | 2 +- .../bootstrap-5/form/form-group.tsx | 2 +- .../components/bootstrap-5/form/form-text.tsx | 2 +- .../bootstrap-5/navbar/account-menu-items.tsx | 2 +- .../bootstrap-5/navbar/contact-us-item.tsx | 2 +- .../bootstrap-5/navbar/default-navbar.tsx | 2 +- .../navbar/nav-dropdown-link-item.tsx | 4 +-- .../bootstrap-5/navbar/nav-dropdown-menu.tsx | 2 +- .../bootstrap-5/navbar/nav-item.tsx | 2 +- .../bootstrap-5/navbar/nav-link-item.tsx | 2 +- .../ui/components/bootstrap-5/table.tsx | 2 +- .../ui/components/bootstrap-5/tag.tsx | 2 +- .../ui/components/bootstrap-5/tooltip.tsx | 2 +- .../ui/components/ol/ol-button-group.tsx | 2 +- .../ui/components/ol/ol-button-toolbar.tsx | 2 +- .../js/features/ui/components/ol/ol-card.tsx | 2 +- .../ui/components/ol/ol-close-button.tsx | 2 +- .../js/features/ui/components/ol/ol-col.tsx | 2 +- .../ui/components/ol/ol-form-checkbox.tsx | 2 +- .../ui/components/ol/ol-form-feedback.tsx | 2 +- .../ui/components/ol/ol-form-group.tsx | 2 +- .../ui/components/ol/ol-form-label.tsx | 2 +- .../ui/components/ol/ol-form-select.tsx | 2 +- .../ui/components/ol/ol-form-switch.tsx | 2 +- .../js/features/ui/components/ol/ol-form.tsx | 2 +- .../ui/components/ol/ol-list-group-item.tsx | 2 +- .../ui/components/ol/ol-list-group.tsx | 2 +- .../js/features/ui/components/ol/ol-modal.tsx | 4 +-- .../features/ui/components/ol/ol-overlay.tsx | 2 +- .../ui/components/ol/ol-page-content-card.tsx | 2 +- .../features/ui/components/ol/ol-popover.tsx | 2 +- .../js/features/ui/components/ol/ol-row.tsx | 2 +- .../features/ui/components/ol/ol-spinner.tsx | 2 +- .../ui/components/ol/ol-toast-container.tsx | 2 +- .../js/features/ui/components/ol/ol-toast.tsx | 2 +- .../components/ol/ol-toggle-button-group.tsx | 2 +- .../ui/components/ol/ol-toggle-button.tsx | 2 +- .../components/word-count-loading.tsx | 2 +- .../components/word-counts-client.tsx | 2 +- .../components/word-counts.tsx | 2 +- .../components/menu-bar/menu-bar-dropdown.tsx | 2 +- .../frontend/js/shared/components/select.tsx | 2 +- .../js/shared/components/upgrade-prompt.tsx | 2 +- .../ui/form/form-check-bs5.stories.tsx | 2 +- .../ui/form/form-input-bs5.stories.tsx | 2 +- .../ui/form/form-radio-bs5.stories.tsx | 2 +- .../ui/form/form-select-bs5.stories.tsx | 2 +- .../ui/form/form-textarea-bs5.stories.tsx | 2 +- .../web/frontend/stories/ui/row.stories.tsx | 2 +- .../stories/ui/split-button.stories.tsx | 2 +- services/web/package.json | 4 +-- 90 files changed, 120 insertions(+), 122 deletions(-) diff --git a/package-lock.json b/package-lock.json index 087d59fce9..c94500986d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16487,9 +16487,9 @@ }, "node_modules/bootstrap-5": { "name": "bootstrap", - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.3.tgz", - "integrity": "sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==", + "version": "5.3.6", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.6.tgz", + "integrity": "sha512-jX0GAcRzvdwISuvArXn3m7KZscWWFAf1MKBcnzaN02qWMb3jpMoUX4/qgeiGzqyIb4ojulRzs89UCUmGcFSzTA==", "dev": true, "funding": [ { @@ -16501,6 +16501,7 @@ "url": "https://opencollective.com/bootstrap" } ], + "license": "MIT", "peerDependencies": { "@popperjs/core": "^2.11.8" } @@ -34373,17 +34374,17 @@ "react": ">=16.4.1" } }, - "node_modules/react-bootstrap-5": { - "name": "react-bootstrap", - "version": "2.10.5", - "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-2.10.5.tgz", - "integrity": "sha512-XueAOEn64RRkZ0s6yzUTdpFtdUXs5L5491QU//8ZcODKJNDLt/r01tNyriZccjgRImH1REynUc9pqjiRMpDLWQ==", + "node_modules/react-bootstrap": { + "version": "2.10.10", + "resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-2.10.10.tgz", + "integrity": "sha512-gMckKUqn8aK/vCnfwoBpBVFUGT9SVQxwsYrp9yDHt0arXMamxALerliKBxr1TPbntirK/HGrUAHYbAeQTa9GHQ==", "dev": true, "license": "MIT", "dependencies": { "@babel/runtime": "^7.24.7", "@restart/hooks": "^0.4.9", - "@restart/ui": "^1.6.9", + "@restart/ui": "^1.9.4", + "@types/prop-types": "^15.7.12", "@types/react-transition-group": "^4.4.6", "classnames": "^2.3.2", "dom-helpers": "^5.2.1", @@ -44897,7 +44898,7 @@ "babel-plugin-module-resolver": "^5.0.2", "backbone": "^1.6.0", "bootstrap": "^3.4.1", - "bootstrap-5": "npm:bootstrap@^5.3.3", + "bootstrap-5": "npm:bootstrap@^5.3.6", "c8": "^7.2.0", "chai": "^4.3.6", "chai-as-promised": "^7.1.1", @@ -44962,7 +44963,7 @@ "prop-types": "^15.7.2", "qrcode": "^1.4.4", "react": "^18.3.1", - "react-bootstrap-5": "npm:react-bootstrap@^2.10.5", + "react-bootstrap": "^2.10.10", "react-chartjs-2": "^5.0.1", "react-color": "^2.19.3", "react-dnd": "^16.0.1", diff --git a/services/web/frontend/js/features/editor-left-menu/components/editor-left-menu.tsx b/services/web/frontend/js/features/editor-left-menu/components/editor-left-menu.tsx index 392793ec9c..40eed9e77e 100644 --- a/services/web/frontend/js/features/editor-left-menu/components/editor-left-menu.tsx +++ b/services/web/frontend/js/features/editor-left-menu/components/editor-left-menu.tsx @@ -3,7 +3,7 @@ import LeftMenuMask from './left-menu-mask' import classNames from 'classnames' import { lazy, memo, Suspense } from 'react' import { FullSizeLoadingSpinner } from '@/shared/components/loading-spinner' -import { Offcanvas } from 'react-bootstrap-5' +import { Offcanvas } from 'react-bootstrap' import { EditorLeftMenuProvider } from './editor-left-menu-context' import withErrorBoundary from '@/infrastructure/error-boundary' import OLNotification from '@/features/ui/components/ol/ol-notification' diff --git a/services/web/frontend/js/features/editor-left-menu/components/settings/settings-menu-select.tsx b/services/web/frontend/js/features/editor-left-menu/components/settings/settings-menu-select.tsx index a6a68cd5f9..6b1f06ec36 100644 --- a/services/web/frontend/js/features/editor-left-menu/components/settings/settings-menu-select.tsx +++ b/services/web/frontend/js/features/editor-left-menu/components/settings/settings-menu-select.tsx @@ -2,7 +2,7 @@ import OLFormGroup from '@/features/ui/components/ol/ol-form-group' import OLFormLabel from '@/features/ui/components/ol/ol-form-label' import OLFormSelect from '@/features/ui/components/ol/ol-form-select' import { ChangeEventHandler, useCallback, useEffect, useRef } from 'react' -import { Spinner } from 'react-bootstrap-5' +import { Spinner } from 'react-bootstrap' import { useEditorLeftMenuContext } from '@/features/editor-left-menu/components/editor-left-menu-context' type PossibleValue = string | number | boolean diff --git a/services/web/frontend/js/features/editor-navigation-toolbar/components/layout-dropdown-button.tsx b/services/web/frontend/js/features/editor-navigation-toolbar/components/layout-dropdown-button.tsx index 748dd565c8..ff0fbda5f1 100644 --- a/services/web/frontend/js/features/editor-navigation-toolbar/components/layout-dropdown-button.tsx +++ b/services/web/frontend/js/features/editor-navigation-toolbar/components/layout-dropdown-button.tsx @@ -1,5 +1,5 @@ import { memo, useCallback, forwardRef } from 'react' -import { Spinner } from 'react-bootstrap-5' +import { Spinner } from 'react-bootstrap' import { Dropdown, DropdownItem, diff --git a/services/web/frontend/js/features/file-tree/components/file-tree-create/modes/file-tree-import-from-project.tsx b/services/web/frontend/js/features/file-tree/components/file-tree-create/modes/file-tree-import-from-project.tsx index 7afdbbdd55..7e02183641 100644 --- a/services/web/frontend/js/features/file-tree/components/file-tree-create/modes/file-tree-import-from-project.tsx +++ b/services/web/frontend/js/features/file-tree/components/file-tree-create/modes/file-tree-import-from-project.tsx @@ -24,7 +24,7 @@ import OLFormGroup from '@/features/ui/components/ol/ol-form-group' import OLFormLabel from '@/features/ui/components/ol/ol-form-label' import OLForm from '@/features/ui/components/ol/ol-form' import OLFormSelect from '@/features/ui/components/ol/ol-form-select' -import { Spinner } from 'react-bootstrap-5' +import { Spinner } from 'react-bootstrap' export default function FileTreeImportFromProject() { const { t } = useTranslation() diff --git a/services/web/frontend/js/features/group-management/components/add-seats/add-seats.tsx b/services/web/frontend/js/features/group-management/components/add-seats/add-seats.tsx index 79f20ff60f..ab9f96a975 100644 --- a/services/web/frontend/js/features/group-management/components/add-seats/add-seats.tsx +++ b/services/web/frontend/js/features/group-management/components/add-seats/add-seats.tsx @@ -13,7 +13,7 @@ import { FormGroup, FormLabel, FormControl, -} from 'react-bootstrap-5' +} from 'react-bootstrap' import FormText from '@/features/ui/components/bootstrap-5/form/form-text' import Button from '@/features/ui/components/bootstrap-5/button' import PoNumber from '@/features/group-management/components/add-seats/po-number' diff --git a/services/web/frontend/js/features/group-management/components/add-seats/cost-summary.tsx b/services/web/frontend/js/features/group-management/components/add-seats/cost-summary.tsx index d13c543852..21e19ce257 100644 --- a/services/web/frontend/js/features/group-management/components/add-seats/cost-summary.tsx +++ b/services/web/frontend/js/features/group-management/components/add-seats/cost-summary.tsx @@ -1,5 +1,5 @@ import { Trans, useTranslation } from 'react-i18next' -import { Card, ListGroup } from 'react-bootstrap-5' +import { Card, ListGroup } from 'react-bootstrap' import { formatCurrency } from '@/shared/utils/currency' import { formatTime } from '@/features/utils/format-date' import { diff --git a/services/web/frontend/js/features/group-management/components/add-seats/po-number.tsx b/services/web/frontend/js/features/group-management/components/add-seats/po-number.tsx index c66f5cd3fd..c6257553d6 100644 --- a/services/web/frontend/js/features/group-management/components/add-seats/po-number.tsx +++ b/services/web/frontend/js/features/group-management/components/add-seats/po-number.tsx @@ -1,6 +1,6 @@ import { useState } from 'react' import { useTranslation } from 'react-i18next' -import { FormControl, FormGroup, FormLabel } from 'react-bootstrap-5' +import { FormControl, FormGroup, FormLabel } from 'react-bootstrap' import FormText from '@/features/ui/components/bootstrap-5/form/form-text' import OLFormCheckbox from '@/features/ui/components/ol/ol-form-checkbox' diff --git a/services/web/frontend/js/features/group-management/components/card.tsx b/services/web/frontend/js/features/group-management/components/card.tsx index 04b858ba7a..ff51231f5a 100644 --- a/services/web/frontend/js/features/group-management/components/card.tsx +++ b/services/web/frontend/js/features/group-management/components/card.tsx @@ -1,7 +1,7 @@ import { useTranslation } from 'react-i18next' import getMeta from '@/utils/meta' import useWaitForI18n from '@/shared/hooks/use-wait-for-i18n' -import { Card as BSCard, CardBody, Col, Row } from 'react-bootstrap-5' +import { Card as BSCard, CardBody, Col, Row } from 'react-bootstrap' import IconButton from '@/features/ui/components/bootstrap-5/icon-button' type CardProps = { diff --git a/services/web/frontend/js/features/group-management/components/members-table/dropdown-button.tsx b/services/web/frontend/js/features/group-management/components/members-table/dropdown-button.tsx index a78f08d40c..bd3b5ee10e 100644 --- a/services/web/frontend/js/features/group-management/components/members-table/dropdown-button.tsx +++ b/services/web/frontend/js/features/group-management/components/members-table/dropdown-button.tsx @@ -19,7 +19,7 @@ import { useGroupMembersContext } from '../../context/group-members-context' import getMeta from '@/utils/meta' import MaterialIcon from '@/shared/components/material-icon' import DropdownListItem from '@/features/ui/components/bootstrap-5/dropdown-list-item' -import { Spinner } from 'react-bootstrap-5' +import { Spinner } from 'react-bootstrap' type resendInviteResponse = { success: boolean diff --git a/services/web/frontend/js/features/group-management/components/request-status.tsx b/services/web/frontend/js/features/group-management/components/request-status.tsx index e7bf3d9cff..637380ac13 100644 --- a/services/web/frontend/js/features/group-management/components/request-status.tsx +++ b/services/web/frontend/js/features/group-management/components/request-status.tsx @@ -1,5 +1,5 @@ import { useTranslation } from 'react-i18next' -import { Card, CardBody, Row, Col } from 'react-bootstrap-5' +import { Card, CardBody, Row, Col } from 'react-bootstrap' import Button from '@/features/ui/components/bootstrap-5/button' import MaterialIcon from '@/shared/components/material-icon' import getMeta from '@/utils/meta' diff --git a/services/web/frontend/js/features/group-management/components/upgrade-subscription/upgrade-subscription-plan-details.tsx b/services/web/frontend/js/features/group-management/components/upgrade-subscription/upgrade-subscription-plan-details.tsx index a0a95fc692..85088d74af 100644 --- a/services/web/frontend/js/features/group-management/components/upgrade-subscription/upgrade-subscription-plan-details.tsx +++ b/services/web/frontend/js/features/group-management/components/upgrade-subscription/upgrade-subscription-plan-details.tsx @@ -1,7 +1,7 @@ import getMeta from '@/utils/meta' import { useMemo } from 'react' import { useTranslation } from 'react-i18next' -import { Card, Row, Col } from 'react-bootstrap-5' +import { Card, Row, Col } from 'react-bootstrap' import MaterialIcon from '@/shared/components/material-icon' import { formatCurrency } from '@/shared/utils/currency' diff --git a/services/web/frontend/js/features/group-management/components/upgrade-subscription/upgrade-subscription-upgrade-summary.tsx b/services/web/frontend/js/features/group-management/components/upgrade-subscription/upgrade-subscription-upgrade-summary.tsx index 23cb76a294..6d3f76e5a2 100644 --- a/services/web/frontend/js/features/group-management/components/upgrade-subscription/upgrade-subscription-upgrade-summary.tsx +++ b/services/web/frontend/js/features/group-management/components/upgrade-subscription/upgrade-subscription-upgrade-summary.tsx @@ -1,5 +1,5 @@ import { useTranslation } from 'react-i18next' -import { Card, ListGroup } from 'react-bootstrap-5' +import { Card, ListGroup } from 'react-bootstrap' import { formatCurrency } from '@/shared/utils/currency' import { formatTime } from '@/features/utils/format-date' import { diff --git a/services/web/frontend/js/features/group-management/components/upgrade-subscription/upgrade-subscription.tsx b/services/web/frontend/js/features/group-management/components/upgrade-subscription/upgrade-subscription.tsx index f5750ae349..467ce88dea 100644 --- a/services/web/frontend/js/features/group-management/components/upgrade-subscription/upgrade-subscription.tsx +++ b/services/web/frontend/js/features/group-management/components/upgrade-subscription/upgrade-subscription.tsx @@ -1,7 +1,7 @@ import getMeta from '@/utils/meta' import { postJSON } from '@/infrastructure/fetch-json' import { useTranslation, Trans } from 'react-i18next' -import { Card, Row, Col } from 'react-bootstrap-5' +import { Card, Row, Col } from 'react-bootstrap' import IconButton from '@/features/ui/components/bootstrap-5/icon-button' import Button from '@/features/ui/components/bootstrap-5/button' import UpgradeSubscriptionPlanDetails from './upgrade-subscription-plan-details' diff --git a/services/web/frontend/js/features/ide-redesign/components/rail.tsx b/services/web/frontend/js/features/ide-redesign/components/rail.tsx index c3e14edfcb..8f54331630 100644 --- a/services/web/frontend/js/features/ide-redesign/components/rail.tsx +++ b/services/web/frontend/js/features/ide-redesign/components/rail.tsx @@ -1,6 +1,6 @@ import { FC, ReactElement, useCallback, useMemo } from 'react' import { useTranslation } from 'react-i18next' -import { Nav, NavLink, Tab, TabContainer } from 'react-bootstrap-5' +import { Nav, NavLink, Tab, TabContainer } from 'react-bootstrap' import MaterialIcon, { AvailableUnfilledIcon, } from '@/shared/components/material-icon' diff --git a/services/web/frontend/js/features/ide-redesign/components/settings/dropdown-setting.tsx b/services/web/frontend/js/features/ide-redesign/components/settings/dropdown-setting.tsx index 957acdce6e..47c5c54ef0 100644 --- a/services/web/frontend/js/features/ide-redesign/components/settings/dropdown-setting.tsx +++ b/services/web/frontend/js/features/ide-redesign/components/settings/dropdown-setting.tsx @@ -2,7 +2,7 @@ import OLFormSelect from '@/features/ui/components/ol/ol-form-select' import { ChangeEventHandler, useCallback } from 'react' import Setting from './setting' import classNames from 'classnames' -import { Spinner } from 'react-bootstrap-5' +import { Spinner } from 'react-bootstrap' type PossibleValue = string | number | boolean diff --git a/services/web/frontend/js/features/ide-redesign/components/settings/settings-modal-body.tsx b/services/web/frontend/js/features/ide-redesign/components/settings/settings-modal-body.tsx index f1a6099c6d..b9a1fddbfe 100644 --- a/services/web/frontend/js/features/ide-redesign/components/settings/settings-modal-body.tsx +++ b/services/web/frontend/js/features/ide-redesign/components/settings/settings-modal-body.tsx @@ -8,7 +8,7 @@ import { TabContainer, TabContent, TabPane, -} from 'react-bootstrap-5' +} from 'react-bootstrap' import { useTranslation } from 'react-i18next' import EditorSettings from './editor-settings/editor-settings' import AppearanceSettings from './appearance-settings/appearance-settings' diff --git a/services/web/frontend/js/features/ide-redesign/components/toolbar/change-layout-options.tsx b/services/web/frontend/js/features/ide-redesign/components/toolbar/change-layout-options.tsx index 3c3126e8da..19d4905d22 100644 --- a/services/web/frontend/js/features/ide-redesign/components/toolbar/change-layout-options.tsx +++ b/services/web/frontend/js/features/ide-redesign/components/toolbar/change-layout-options.tsx @@ -12,7 +12,7 @@ import { useTranslation } from 'react-i18next' import * as eventTracking from '../../../../infrastructure/event-tracking' import useEventListener from '@/shared/hooks/use-event-listener' import { DetachRole } from '@/shared/context/detach-context' -import { Spinner } from 'react-bootstrap-5' +import { Spinner } from 'react-bootstrap' type LayoutOption = 'sideBySide' | 'editorOnly' | 'pdfOnly' | 'detachedPdf' diff --git a/services/web/frontend/js/features/pdf-preview/components/pdf-preview-hybrid-toolbar.tsx b/services/web/frontend/js/features/pdf-preview/components/pdf-preview-hybrid-toolbar.tsx index 4a000e9a41..b5b9c8609c 100644 --- a/services/web/frontend/js/features/pdf-preview/components/pdf-preview-hybrid-toolbar.tsx +++ b/services/web/frontend/js/features/pdf-preview/components/pdf-preview-hybrid-toolbar.tsx @@ -9,7 +9,7 @@ import PdfHybridDownloadButton from './pdf-hybrid-download-button' import PdfHybridCodeCheckButton from './pdf-hybrid-code-check-button' import PdfOrphanRefreshButton from './pdf-orphan-refresh-button' import { DetachedSynctexControl } from './detach-synctex-control' -import { Spinner } from 'react-bootstrap-5' +import { Spinner } from 'react-bootstrap' const ORPHAN_UI_TIMEOUT_MS = 5000 diff --git a/services/web/frontend/js/features/pdf-preview/components/pdf-synctex-controls.tsx b/services/web/frontend/js/features/pdf-preview/components/pdf-synctex-controls.tsx index e20b08617f..0167a98db1 100644 --- a/services/web/frontend/js/features/pdf-preview/components/pdf-synctex-controls.tsx +++ b/services/web/frontend/js/features/pdf-preview/components/pdf-synctex-controls.tsx @@ -7,8 +7,8 @@ import * as eventTracking from '../../../infrastructure/event-tracking' import OLTooltip from '@/features/ui/components/ol/ol-tooltip' import OLButton from '@/features/ui/components/ol/ol-button' import MaterialIcon from '@/shared/components/material-icon' -import { Spinner } from 'react-bootstrap-5' -import { Placement } from 'react-bootstrap-5/types' +import { Spinner } from 'react-bootstrap' +import { Placement } from 'react-bootstrap/types' import useSynctex from '../hooks/use-synctex' const GoToCodeButton = memo(function GoToCodeButton({ diff --git a/services/web/frontend/js/features/project-list/components/dropdown/actions-dropdown.tsx b/services/web/frontend/js/features/project-list/components/dropdown/actions-dropdown.tsx index 058f0319ce..7b2bd909f8 100644 --- a/services/web/frontend/js/features/project-list/components/dropdown/actions-dropdown.tsx +++ b/services/web/frontend/js/features/project-list/components/dropdown/actions-dropdown.tsx @@ -1,5 +1,5 @@ import { useTranslation } from 'react-i18next' -import { Spinner } from 'react-bootstrap-5' +import { Spinner } from 'react-bootstrap' import { Dropdown, DropdownItem, diff --git a/services/web/frontend/js/features/project-list/components/sidebar/sidebar-ds-nav.tsx b/services/web/frontend/js/features/project-list/components/sidebar/sidebar-ds-nav.tsx index 3c2b3a09c0..2aaed364a7 100644 --- a/services/web/frontend/js/features/project-list/components/sidebar/sidebar-ds-nav.tsx +++ b/services/web/frontend/js/features/project-list/components/sidebar/sidebar-ds-nav.tsx @@ -5,7 +5,7 @@ import NewProjectButton from '../new-project-button' import SidebarFilters from './sidebar-filters' import AddAffiliation, { useAddAffiliation } from '../add-affiliation' import { usePersistedResize } from '@/shared/hooks/use-resize' -import { Dropdown } from 'react-bootstrap-5' +import { Dropdown } from 'react-bootstrap' import getMeta from '@/utils/meta' import OLTooltip from '@/features/ui/components/ol/ol-tooltip' import { useTranslation } from 'react-i18next' diff --git a/services/web/frontend/js/features/share-project-modal/components/select-collaborators.jsx b/services/web/frontend/js/features/share-project-modal/components/select-collaborators.jsx index a683201d0b..b65d4b3f0a 100644 --- a/services/web/frontend/js/features/share-project-modal/components/select-collaborators.jsx +++ b/services/web/frontend/js/features/share-project-modal/components/select-collaborators.jsx @@ -8,7 +8,7 @@ import classnames from 'classnames' import MaterialIcon from '@/shared/components/material-icon' import Tag from '@/features/ui/components/bootstrap-5/tag' import { DropdownItem } from '@/features/ui/components/bootstrap-5/dropdown-menu' -import { Spinner } from 'react-bootstrap-5' +import { Spinner } from 'react-bootstrap' // Unicode characters in these Unicode groups: // "General Punctuation — Spaces" diff --git a/services/web/frontend/js/features/share-project-modal/components/share-project-modal-content.tsx b/services/web/frontend/js/features/share-project-modal/components/share-project-modal-content.tsx index cabf2ddd70..3f08fdf3a1 100644 --- a/services/web/frontend/js/features/share-project-modal/components/share-project-modal-content.tsx +++ b/services/web/frontend/js/features/share-project-modal/components/share-project-modal-content.tsx @@ -11,7 +11,7 @@ import OLModal, { } from '@/features/ui/components/ol/ol-modal' import OLNotification from '@/features/ui/components/ol/ol-notification' import OLButton from '@/features/ui/components/ol/ol-button' -import { Spinner } from 'react-bootstrap-5' +import { Spinner } from 'react-bootstrap' const ReadOnlyTokenLink = lazy(() => import('./link-sharing').then(({ ReadOnlyTokenLink }) => ({ diff --git a/services/web/frontend/js/features/share-project-modal/components/transfer-ownership-modal.jsx b/services/web/frontend/js/features/share-project-modal/components/transfer-ownership-modal.jsx index 81961d7ede..fec3e941ea 100644 --- a/services/web/frontend/js/features/share-project-modal/components/transfer-ownership-modal.jsx +++ b/services/web/frontend/js/features/share-project-modal/components/transfer-ownership-modal.jsx @@ -12,7 +12,7 @@ import OLModal, { } from '@/features/ui/components/ol/ol-modal' import OLNotification from '@/features/ui/components/ol/ol-notification' import OLButton from '@/features/ui/components/ol/ol-button' -import { Spinner } from 'react-bootstrap-5' +import { Spinner } from 'react-bootstrap' export default function TransferOwnershipModal({ member, cancel }) { const { t } = useTranslation() diff --git a/services/web/frontend/js/features/socket-diagnostics/components/diagnostic-component.tsx b/services/web/frontend/js/features/socket-diagnostics/components/diagnostic-component.tsx index 7adddc5329..91829a09e2 100644 --- a/services/web/frontend/js/features/socket-diagnostics/components/diagnostic-component.tsx +++ b/services/web/frontend/js/features/socket-diagnostics/components/diagnostic-component.tsx @@ -1,7 +1,7 @@ import React from 'react' import classnames from 'classnames' import type { ConnectionStatus } from './types' -import { Badge, Button } from 'react-bootstrap-5' +import { Badge, Button } from 'react-bootstrap' import OLNotification from '@/features/ui/components/ol/ol-notification' import MaterialIcon from '@/shared/components/material-icon' diff --git a/services/web/frontend/js/features/socket-diagnostics/components/socket-diagnostics.tsx b/services/web/frontend/js/features/socket-diagnostics/components/socket-diagnostics.tsx index 498a9ecc23..39876d250e 100644 --- a/services/web/frontend/js/features/socket-diagnostics/components/socket-diagnostics.tsx +++ b/services/web/frontend/js/features/socket-diagnostics/components/socket-diagnostics.tsx @@ -7,7 +7,7 @@ import { DiagnosticItem, ErrorAlert, } from './diagnostic-component' -import { Col, Container, Row } from 'react-bootstrap-5' +import { Col, Container, Row } from 'react-bootstrap' import MaterialIcon from '@/shared/components/material-icon' import OLFormCheckbox from '@/features/ui/components/ol/ol-form-checkbox' import { CopyToClipboard } from '@/shared/components/copy-to-clipboard' diff --git a/services/web/frontend/js/features/source-editor/components/full-project-search-button.tsx b/services/web/frontend/js/features/source-editor/components/full-project-search-button.tsx index 235a4428f3..698204d89c 100644 --- a/services/web/frontend/js/features/source-editor/components/full-project-search-button.tsx +++ b/services/web/frontend/js/features/source-editor/components/full-project-search-button.tsx @@ -7,7 +7,7 @@ import { forwardRef, memo, Ref, useCallback, useEffect, useRef } from 'react' import { useCodeMirrorViewContext } from './codemirror-context' import MaterialIcon from '@/shared/components/material-icon' import { useTranslation } from 'react-i18next' -import { Overlay, Popover } from 'react-bootstrap-5' +import { Overlay, Popover } from 'react-bootstrap' import Close from '@/shared/components/close' import useTutorial from '@/shared/hooks/promotions/use-tutorial' import { useEditorContext } from '@/shared/context/editor-context' diff --git a/services/web/frontend/js/features/source-editor/extensions/spelling/spelling-suggestions-language.tsx b/services/web/frontend/js/features/source-editor/extensions/spelling/spelling-suggestions-language.tsx index 59c995f760..be65d00bfa 100644 --- a/services/web/frontend/js/features/source-editor/extensions/spelling/spelling-suggestions-language.tsx +++ b/services/web/frontend/js/features/source-editor/extensions/spelling/spelling-suggestions-language.tsx @@ -1,7 +1,7 @@ import { memo, useCallback } from 'react' import { useTranslation } from 'react-i18next' import OLTooltip from '@/features/ui/components/ol/ol-tooltip' -import { Dropdown } from 'react-bootstrap-5' +import { Dropdown } from 'react-bootstrap' import MaterialIcon from '@/shared/components/material-icon' export const SpellingSuggestionsLanguage = memo<{ diff --git a/services/web/frontend/js/features/source-editor/extensions/spelling/spelling-suggestions.tsx b/services/web/frontend/js/features/source-editor/extensions/spelling/spelling-suggestions.tsx index 4fc78bccc2..724f79c652 100644 --- a/services/web/frontend/js/features/source-editor/extensions/spelling/spelling-suggestions.tsx +++ b/services/web/frontend/js/features/source-editor/extensions/spelling/spelling-suggestions.tsx @@ -15,7 +15,7 @@ import { SpellingSuggestionsLanguage } from './spelling-suggestions-language' import { captureException } from '@/infrastructure/error-reporter' import { debugConsole } from '@/utils/debugging' import { SpellCheckLanguage } from '../../../../../../types/project-settings' -import { Dropdown } from 'react-bootstrap-5' +import { Dropdown } from 'react-bootstrap' const ITEMS_TO_SHOW = 8 diff --git a/services/web/frontend/js/features/subscription/components/dashboard/pause-modal.tsx b/services/web/frontend/js/features/subscription/components/dashboard/pause-modal.tsx index fa47be6a5c..b9b79a28f9 100644 --- a/services/web/frontend/js/features/subscription/components/dashboard/pause-modal.tsx +++ b/services/web/frontend/js/features/subscription/components/dashboard/pause-modal.tsx @@ -10,7 +10,7 @@ import OLModal, { import { Select } from '@/shared/components/select' import OLFormGroup from '@/features/ui/components/ol/ol-form-group' import Button from '@/features/ui/components/bootstrap-5/button' -import { Stack } from 'react-bootstrap-5' +import { Stack } from 'react-bootstrap' import { debugConsole } from '@/utils/debugging' import * as eventTracking from '../../../../infrastructure/event-tracking' import PauseDuck from '../../images/pause-duck.svg' diff --git a/services/web/frontend/js/features/subscription/components/dashboard/states/active/add-ons.tsx b/services/web/frontend/js/features/subscription/components/dashboard/states/active/add-ons.tsx index 521f670e5f..82952445e6 100644 --- a/services/web/frontend/js/features/subscription/components/dashboard/states/active/add-ons.tsx +++ b/services/web/frontend/js/features/subscription/components/dashboard/states/active/add-ons.tsx @@ -1,6 +1,6 @@ import { useTranslation } from 'react-i18next' import getMeta from '@/utils/meta' -import { Dropdown, DropdownMenu, DropdownToggle } from 'react-bootstrap-5' +import { Dropdown, DropdownMenu, DropdownToggle } from 'react-bootstrap' import OLDropdownMenuItem from '@/features/ui/components/ol/ol-dropdown-menu-item' import MaterialIcon from '@/shared/components/material-icon' import { diff --git a/services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/modals/writefull-bundle-management-modal.tsx b/services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/modals/writefull-bundle-management-modal.tsx index d5933d137a..7d7c971197 100644 --- a/services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/modals/writefull-bundle-management-modal.tsx +++ b/services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/modals/writefull-bundle-management-modal.tsx @@ -9,7 +9,7 @@ import OLModal, { } from '@/features/ui/components/ol/ol-modal' import OLButton from '@/features/ui/components/ol/ol-button' import sparkle from '@/shared/svgs/sparkle.svg' -import { Dropdown, DropdownMenu, DropdownToggle } from 'react-bootstrap-5' +import { Dropdown, DropdownMenu, DropdownToggle } from 'react-bootstrap' import OLDropdownMenuItem from '@/features/ui/components/ol/ol-dropdown-menu-item' import MaterialIcon from '@/shared/components/material-icon' import { ADD_ON_NAME } from '@/features/subscription/data/add-on-codes' diff --git a/services/web/frontend/js/features/ui/components/bootstrap-5/badge-link-with-tooltip.tsx b/services/web/frontend/js/features/ui/components/bootstrap-5/badge-link-with-tooltip.tsx index aea97ad6f2..43910f1a4c 100644 --- a/services/web/frontend/js/features/ui/components/bootstrap-5/badge-link-with-tooltip.tsx +++ b/services/web/frontend/js/features/ui/components/bootstrap-5/badge-link-with-tooltip.tsx @@ -1,4 +1,4 @@ -import { OverlayTrigger, Tooltip } from 'react-bootstrap-5' +import { OverlayTrigger, Tooltip } from 'react-bootstrap' import type { MergeAndOverride } from '../../../../../../types/utils' import BadgeLink, { type BadgeLinkProps } from './badge-link' import { useEffect, useRef, useState } from 'react' diff --git a/services/web/frontend/js/features/ui/components/bootstrap-5/badge.tsx b/services/web/frontend/js/features/ui/components/bootstrap-5/badge.tsx index 0e66500bc0..642be2e6b2 100644 --- a/services/web/frontend/js/features/ui/components/bootstrap-5/badge.tsx +++ b/services/web/frontend/js/features/ui/components/bootstrap-5/badge.tsx @@ -1,4 +1,4 @@ -import { Badge as BSBadge, BadgeProps as BSBadgeProps } from 'react-bootstrap-5' +import { Badge as BSBadge, BadgeProps as BSBadgeProps } from 'react-bootstrap' import { MergeAndOverride } from '../../../../../../types/utils' export type BadgeProps = MergeAndOverride< diff --git a/services/web/frontend/js/features/ui/components/bootstrap-5/button.tsx b/services/web/frontend/js/features/ui/components/bootstrap-5/button.tsx index 2bbc97c695..cede608249 100644 --- a/services/web/frontend/js/features/ui/components/bootstrap-5/button.tsx +++ b/services/web/frontend/js/features/ui/components/bootstrap-5/button.tsx @@ -1,5 +1,5 @@ import { forwardRef } from 'react' -import { Button as BS5Button, Spinner } from 'react-bootstrap-5' +import { Button as BS5Button, Spinner } from 'react-bootstrap' import type { ButtonProps } from '@/features/ui/components/types/button-props' import classNames from 'classnames' import { useTranslation } from 'react-i18next' diff --git a/services/web/frontend/js/features/ui/components/bootstrap-5/dropdown-menu.tsx b/services/web/frontend/js/features/ui/components/bootstrap-5/dropdown-menu.tsx index aa35c158d9..402d465923 100644 --- a/services/web/frontend/js/features/ui/components/bootstrap-5/dropdown-menu.tsx +++ b/services/web/frontend/js/features/ui/components/bootstrap-5/dropdown-menu.tsx @@ -7,8 +7,7 @@ import { DropdownDivider as BS5DropdownDivider, DropdownHeader as BS5DropdownHeader, Button as BS5Button, - type ButtonProps, -} from 'react-bootstrap-5' +} from 'react-bootstrap' import type { DropdownProps, DropdownItemProps, @@ -109,19 +108,20 @@ const ForwardReferredDropdownItem = fixedForwardRef(DropdownItem, { export { ForwardReferredDropdownItem as DropdownItem } -export const DropdownToggleCustom = forwardRef( - ({ children, className, ...props }, ref) => ( - - {children} - - - ) -) -DropdownToggleCustom.displayName = 'CustomCaret' +export const DropdownToggleCustom = forwardRef< + HTMLButtonElement, + React.ComponentProps +>(({ children, className, ...props }, ref) => ( + + {children} + + +)) +DropdownToggleCustom.displayName = 'DropdownToggleCustom' export const DropdownToggle = forwardRef< typeof BS5DropdownToggle, diff --git a/services/web/frontend/js/features/ui/components/bootstrap-5/dropdown-toggle-with-tooltip.tsx b/services/web/frontend/js/features/ui/components/bootstrap-5/dropdown-toggle-with-tooltip.tsx index f2ba81fbad..cdf20e3dd3 100644 --- a/services/web/frontend/js/features/ui/components/bootstrap-5/dropdown-toggle-with-tooltip.tsx +++ b/services/web/frontend/js/features/ui/components/bootstrap-5/dropdown-toggle-with-tooltip.tsx @@ -1,12 +1,12 @@ import { ReactNode, forwardRef } from 'react' -import { BsPrefixRefForwardingComponent } from 'react-bootstrap-5/helpers' +import { BsPrefixRefForwardingComponent } from 'react-bootstrap/helpers' import type { DropdownToggleProps } from '@/features/ui/components/types/dropdown-menu-props' import { DropdownToggle as BS5DropdownToggle, OverlayTrigger, OverlayTriggerProps, Tooltip, -} from 'react-bootstrap-5' +} from 'react-bootstrap' import type { MergeAndOverride } from '../../../../../../types/utils' type DropdownToggleWithTooltipProps = MergeAndOverride< diff --git a/services/web/frontend/js/features/ui/components/bootstrap-5/form/form-control.tsx b/services/web/frontend/js/features/ui/components/bootstrap-5/form/form-control.tsx index 178bab52c1..5a4ab4f00f 100644 --- a/services/web/frontend/js/features/ui/components/bootstrap-5/form/form-control.tsx +++ b/services/web/frontend/js/features/ui/components/bootstrap-5/form/form-control.tsx @@ -1,8 +1,5 @@ import React, { forwardRef } from 'react' -import { - Form, - FormControlProps as BS5FormControlProps, -} from 'react-bootstrap-5' +import { Form, FormControlProps as BS5FormControlProps } from 'react-bootstrap' import classnames from 'classnames' export type OLBS5FormControlProps = BS5FormControlProps & { diff --git a/services/web/frontend/js/features/ui/components/bootstrap-5/form/form-feedback.tsx b/services/web/frontend/js/features/ui/components/bootstrap-5/form/form-feedback.tsx index 85c91031ba..67c31aa96c 100644 --- a/services/web/frontend/js/features/ui/components/bootstrap-5/form/form-feedback.tsx +++ b/services/web/frontend/js/features/ui/components/bootstrap-5/form/form-feedback.tsx @@ -1,4 +1,4 @@ -import { Form } from 'react-bootstrap-5' +import { Form } from 'react-bootstrap' import FormText from '@/features/ui/components/bootstrap-5/form/form-text' import { ComponentProps } from 'react' diff --git a/services/web/frontend/js/features/ui/components/bootstrap-5/form/form-group.tsx b/services/web/frontend/js/features/ui/components/bootstrap-5/form/form-group.tsx index 19b88cdc65..5807ef9839 100644 --- a/services/web/frontend/js/features/ui/components/bootstrap-5/form/form-group.tsx +++ b/services/web/frontend/js/features/ui/components/bootstrap-5/form/form-group.tsx @@ -1,5 +1,5 @@ import { forwardRef } from 'react' -import { FormGroup as BS5FormGroup, FormGroupProps } from 'react-bootstrap-5' +import { FormGroup as BS5FormGroup, FormGroupProps } from 'react-bootstrap' import classnames from 'classnames' const FormGroup = forwardRef( diff --git a/services/web/frontend/js/features/ui/components/bootstrap-5/form/form-text.tsx b/services/web/frontend/js/features/ui/components/bootstrap-5/form/form-text.tsx index fe9b939680..78a962ad49 100644 --- a/services/web/frontend/js/features/ui/components/bootstrap-5/form/form-text.tsx +++ b/services/web/frontend/js/features/ui/components/bootstrap-5/form/form-text.tsx @@ -1,4 +1,4 @@ -import { Form, FormTextProps as BS5FormTextProps } from 'react-bootstrap-5' +import { Form, FormTextProps as BS5FormTextProps } from 'react-bootstrap' import MaterialIcon from '@/shared/components/material-icon' import classnames from 'classnames' import { MergeAndOverride } from '../../../../../../../types/utils' diff --git a/services/web/frontend/js/features/ui/components/bootstrap-5/navbar/account-menu-items.tsx b/services/web/frontend/js/features/ui/components/bootstrap-5/navbar/account-menu-items.tsx index 825d56c100..2b6ed6a64b 100644 --- a/services/web/frontend/js/features/ui/components/bootstrap-5/navbar/account-menu-items.tsx +++ b/services/web/frontend/js/features/ui/components/bootstrap-5/navbar/account-menu-items.tsx @@ -1,4 +1,4 @@ -import { Dropdown } from 'react-bootstrap-5' +import { Dropdown } from 'react-bootstrap' import { useTranslation } from 'react-i18next' import getMeta from '@/utils/meta' import type { NavbarSessionUser } from '@/features/ui/components/types/navbar' diff --git a/services/web/frontend/js/features/ui/components/bootstrap-5/navbar/contact-us-item.tsx b/services/web/frontend/js/features/ui/components/bootstrap-5/navbar/contact-us-item.tsx index 80d731cc46..d14be5ab55 100644 --- a/services/web/frontend/js/features/ui/components/bootstrap-5/navbar/contact-us-item.tsx +++ b/services/web/frontend/js/features/ui/components/bootstrap-5/navbar/contact-us-item.tsx @@ -1,5 +1,5 @@ import { useTranslation } from 'react-i18next' -import { DropdownItem } from 'react-bootstrap-5' +import { DropdownItem } from 'react-bootstrap' import DropdownListItem from '@/features/ui/components/bootstrap-5/dropdown-list-item' import { type ExtraSegmentations, 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 9066f5bbe7..a5fb5afd10 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,7 +1,7 @@ import { useState } from 'react' import { sendMB } from '@/infrastructure/event-tracking' import { useTranslation } from 'react-i18next' -import { Button, Container, Nav, Navbar } from 'react-bootstrap-5' +import { Button, Container, Nav, Navbar } from 'react-bootstrap' import useWaitForI18n from '@/shared/hooks/use-wait-for-i18n' import AdminMenu from '@/features/ui/components/bootstrap-5/navbar/admin-menu' import type { DefaultNavbarMetadata } from '@/features/ui/components/types/default-navbar-metadata' diff --git a/services/web/frontend/js/features/ui/components/bootstrap-5/navbar/nav-dropdown-link-item.tsx b/services/web/frontend/js/features/ui/components/bootstrap-5/navbar/nav-dropdown-link-item.tsx index 5acc808c21..1d418a344b 100644 --- a/services/web/frontend/js/features/ui/components/bootstrap-5/navbar/nav-dropdown-link-item.tsx +++ b/services/web/frontend/js/features/ui/components/bootstrap-5/navbar/nav-dropdown-link-item.tsx @@ -1,7 +1,7 @@ import { ReactNode } from 'react' import DropdownListItem from '@/features/ui/components/bootstrap-5/dropdown-list-item' -import { DropdownItem } from 'react-bootstrap-5' -import { DropdownItemProps } from 'react-bootstrap-5/DropdownItem' +import { DropdownItem } from 'react-bootstrap' +import { DropdownItemProps } from 'react-bootstrap/DropdownItem' export default function NavDropdownLinkItem({ href, diff --git a/services/web/frontend/js/features/ui/components/bootstrap-5/navbar/nav-dropdown-menu.tsx b/services/web/frontend/js/features/ui/components/bootstrap-5/navbar/nav-dropdown-menu.tsx index 6c588eef46..9b962238c1 100644 --- a/services/web/frontend/js/features/ui/components/bootstrap-5/navbar/nav-dropdown-menu.tsx +++ b/services/web/frontend/js/features/ui/components/bootstrap-5/navbar/nav-dropdown-menu.tsx @@ -1,5 +1,5 @@ import { type ReactNode, useState } from 'react' -import { Dropdown } from 'react-bootstrap-5' +import { Dropdown } from 'react-bootstrap' import { CaretUp, CaretDown } from '@phosphor-icons/react' import { useDsNavStyle } from '@/features/project-list/components/use-is-ds-nav' diff --git a/services/web/frontend/js/features/ui/components/bootstrap-5/navbar/nav-item.tsx b/services/web/frontend/js/features/ui/components/bootstrap-5/navbar/nav-item.tsx index 58295a47eb..3a55f2791b 100644 --- a/services/web/frontend/js/features/ui/components/bootstrap-5/navbar/nav-item.tsx +++ b/services/web/frontend/js/features/ui/components/bootstrap-5/navbar/nav-item.tsx @@ -1,4 +1,4 @@ -import { Nav, NavItemProps } from 'react-bootstrap-5' +import { Nav, NavItemProps } from 'react-bootstrap' export default function NavItem(props: Omit) { const { children, ...rest } = props diff --git a/services/web/frontend/js/features/ui/components/bootstrap-5/navbar/nav-link-item.tsx b/services/web/frontend/js/features/ui/components/bootstrap-5/navbar/nav-link-item.tsx index 4e3875f5bb..519b84762e 100644 --- a/services/web/frontend/js/features/ui/components/bootstrap-5/navbar/nav-link-item.tsx +++ b/services/web/frontend/js/features/ui/components/bootstrap-5/navbar/nav-link-item.tsx @@ -1,5 +1,5 @@ import { ReactNode } from 'react' -import { Nav } from 'react-bootstrap-5' +import { Nav } from 'react-bootstrap' import NavItem from '@/features/ui/components/bootstrap-5/navbar/nav-item' export default function NavLinkItem({ diff --git a/services/web/frontend/js/features/ui/components/bootstrap-5/table.tsx b/services/web/frontend/js/features/ui/components/bootstrap-5/table.tsx index 8f6fe3ee60..176d6f737c 100644 --- a/services/web/frontend/js/features/ui/components/bootstrap-5/table.tsx +++ b/services/web/frontend/js/features/ui/components/bootstrap-5/table.tsx @@ -1,4 +1,4 @@ -import { Table as BS5Table } from 'react-bootstrap-5' +import { Table as BS5Table } from 'react-bootstrap' import classnames from 'classnames' export function TableContainer({ diff --git a/services/web/frontend/js/features/ui/components/bootstrap-5/tag.tsx b/services/web/frontend/js/features/ui/components/bootstrap-5/tag.tsx index dda1c9861e..3a3fec7879 100644 --- a/services/web/frontend/js/features/ui/components/bootstrap-5/tag.tsx +++ b/services/web/frontend/js/features/ui/components/bootstrap-5/tag.tsx @@ -1,5 +1,5 @@ import { useTranslation } from 'react-i18next' -import { Badge, BadgeProps } from 'react-bootstrap-5' +import { Badge, BadgeProps } from 'react-bootstrap' import MaterialIcon from '@/shared/components/material-icon' import { MergeAndOverride } from '../../../../../../types/utils' import classnames from 'classnames' diff --git a/services/web/frontend/js/features/ui/components/bootstrap-5/tooltip.tsx b/services/web/frontend/js/features/ui/components/bootstrap-5/tooltip.tsx index fa479b58c6..f16bcb5425 100644 --- a/services/web/frontend/js/features/ui/components/bootstrap-5/tooltip.tsx +++ b/services/web/frontend/js/features/ui/components/bootstrap-5/tooltip.tsx @@ -4,7 +4,7 @@ import { OverlayTriggerProps, Tooltip as BSTooltip, TooltipProps as BSTooltipProps, -} from 'react-bootstrap-5' +} from 'react-bootstrap' import { callFnsInSequence } from '@/utils/functions' type OverlayProps = Omit diff --git a/services/web/frontend/js/features/ui/components/ol/ol-button-group.tsx b/services/web/frontend/js/features/ui/components/ol/ol-button-group.tsx index dc26595aac..4b6c4dece0 100644 --- a/services/web/frontend/js/features/ui/components/ol/ol-button-group.tsx +++ b/services/web/frontend/js/features/ui/components/ol/ol-button-group.tsx @@ -1,4 +1,4 @@ -import { ButtonGroup, ButtonGroupProps } from 'react-bootstrap-5' +import { ButtonGroup, ButtonGroupProps } from 'react-bootstrap' function OLButtonGroup({ as, ...rest }: ButtonGroupProps) { return diff --git a/services/web/frontend/js/features/ui/components/ol/ol-button-toolbar.tsx b/services/web/frontend/js/features/ui/components/ol/ol-button-toolbar.tsx index 377228f72d..a60b854732 100644 --- a/services/web/frontend/js/features/ui/components/ol/ol-button-toolbar.tsx +++ b/services/web/frontend/js/features/ui/components/ol/ol-button-toolbar.tsx @@ -1,4 +1,4 @@ -import { ButtonToolbar, ButtonToolbarProps } from 'react-bootstrap-5' +import { ButtonToolbar, ButtonToolbarProps } from 'react-bootstrap' function OLButtonToolbar(props: ButtonToolbarProps) { return diff --git a/services/web/frontend/js/features/ui/components/ol/ol-card.tsx b/services/web/frontend/js/features/ui/components/ol/ol-card.tsx index 1f0eb70ace..f5ff025d10 100644 --- a/services/web/frontend/js/features/ui/components/ol/ol-card.tsx +++ b/services/web/frontend/js/features/ui/components/ol/ol-card.tsx @@ -1,4 +1,4 @@ -import { Card } from 'react-bootstrap-5' +import { Card } from 'react-bootstrap' import { FC } from 'react' const OLCard: FC> = ({ diff --git a/services/web/frontend/js/features/ui/components/ol/ol-close-button.tsx b/services/web/frontend/js/features/ui/components/ol/ol-close-button.tsx index 414a329eec..c07d1cc390 100644 --- a/services/web/frontend/js/features/ui/components/ol/ol-close-button.tsx +++ b/services/web/frontend/js/features/ui/components/ol/ol-close-button.tsx @@ -1,4 +1,4 @@ -import { CloseButton, CloseButtonProps } from 'react-bootstrap-5' +import { CloseButton, CloseButtonProps } from 'react-bootstrap' import { useTranslation } from 'react-i18next' import { forwardRef } from 'react' diff --git a/services/web/frontend/js/features/ui/components/ol/ol-col.tsx b/services/web/frontend/js/features/ui/components/ol/ol-col.tsx index dc70ca23c8..a482ccf97d 100644 --- a/services/web/frontend/js/features/ui/components/ol/ol-col.tsx +++ b/services/web/frontend/js/features/ui/components/ol/ol-col.tsx @@ -1,4 +1,4 @@ -import { Col } from 'react-bootstrap-5' +import { Col } from 'react-bootstrap' function OLCol(props: React.ComponentProps) { return diff --git a/services/web/frontend/js/features/ui/components/ol/ol-form-checkbox.tsx b/services/web/frontend/js/features/ui/components/ol/ol-form-checkbox.tsx index d82e49ea2d..191f850159 100644 --- a/services/web/frontend/js/features/ui/components/ol/ol-form-checkbox.tsx +++ b/services/web/frontend/js/features/ui/components/ol/ol-form-checkbox.tsx @@ -1,4 +1,4 @@ -import { Form, FormCheckProps } from 'react-bootstrap-5' +import { Form, FormCheckProps } from 'react-bootstrap' import { MergeAndOverride } from '../../../../../../types/utils' import FormText from '../bootstrap-5/form/form-text' diff --git a/services/web/frontend/js/features/ui/components/ol/ol-form-feedback.tsx b/services/web/frontend/js/features/ui/components/ol/ol-form-feedback.tsx index 1c850c1ffc..59b81bb6a4 100644 --- a/services/web/frontend/js/features/ui/components/ol/ol-form-feedback.tsx +++ b/services/web/frontend/js/features/ui/components/ol/ol-form-feedback.tsx @@ -1,4 +1,4 @@ -import { Form } from 'react-bootstrap-5' +import { Form } from 'react-bootstrap' import { ComponentProps } from 'react' import FormFeedback from '@/features/ui/components/bootstrap-5/form/form-feedback' diff --git a/services/web/frontend/js/features/ui/components/ol/ol-form-group.tsx b/services/web/frontend/js/features/ui/components/ol/ol-form-group.tsx index 8ccc974d4e..9c6db07e93 100644 --- a/services/web/frontend/js/features/ui/components/ol/ol-form-group.tsx +++ b/services/web/frontend/js/features/ui/components/ol/ol-form-group.tsx @@ -1,4 +1,4 @@ -import { FormGroupProps } from 'react-bootstrap-5' +import { FormGroupProps } from 'react-bootstrap' import FormGroup from '@/features/ui/components/bootstrap-5/form/form-group' function OLFormGroup(props: FormGroupProps) { diff --git a/services/web/frontend/js/features/ui/components/ol/ol-form-label.tsx b/services/web/frontend/js/features/ui/components/ol/ol-form-label.tsx index 1e1038f9e3..ed46dd8117 100644 --- a/services/web/frontend/js/features/ui/components/ol/ol-form-label.tsx +++ b/services/web/frontend/js/features/ui/components/ol/ol-form-label.tsx @@ -1,4 +1,4 @@ -import { Form } from 'react-bootstrap-5' +import { Form } from 'react-bootstrap' function OLFormLabel(props: React.ComponentProps<(typeof Form)['Label']>) { return diff --git a/services/web/frontend/js/features/ui/components/ol/ol-form-select.tsx b/services/web/frontend/js/features/ui/components/ol/ol-form-select.tsx index dfcd147dfd..8c111932ce 100644 --- a/services/web/frontend/js/features/ui/components/ol/ol-form-select.tsx +++ b/services/web/frontend/js/features/ui/components/ol/ol-form-select.tsx @@ -1,5 +1,5 @@ import { forwardRef } from 'react' -import { Form, FormSelectProps } from 'react-bootstrap-5' +import { Form, FormSelectProps } from 'react-bootstrap' const OLFormSelect = forwardRef( (props, ref) => { diff --git a/services/web/frontend/js/features/ui/components/ol/ol-form-switch.tsx b/services/web/frontend/js/features/ui/components/ol/ol-form-switch.tsx index a9a6ffe041..3e349a6f76 100644 --- a/services/web/frontend/js/features/ui/components/ol/ol-form-switch.tsx +++ b/services/web/frontend/js/features/ui/components/ol/ol-form-switch.tsx @@ -1,4 +1,4 @@ -import { FormCheck, FormCheckProps, FormLabel } from 'react-bootstrap-5' +import { FormCheck, FormCheckProps, FormLabel } from 'react-bootstrap' type OLFormSwitchProps = FormCheckProps & { inputRef?: React.MutableRefObject diff --git a/services/web/frontend/js/features/ui/components/ol/ol-form.tsx b/services/web/frontend/js/features/ui/components/ol/ol-form.tsx index 724578769e..d53dec0ad3 100644 --- a/services/web/frontend/js/features/ui/components/ol/ol-form.tsx +++ b/services/web/frontend/js/features/ui/components/ol/ol-form.tsx @@ -1,4 +1,4 @@ -import { Form } from 'react-bootstrap-5' +import { Form } from 'react-bootstrap' import { ComponentProps } from 'react' function OLForm(props: ComponentProps) { diff --git a/services/web/frontend/js/features/ui/components/ol/ol-list-group-item.tsx b/services/web/frontend/js/features/ui/components/ol/ol-list-group-item.tsx index a27457fa7a..c99b8c1cb4 100644 --- a/services/web/frontend/js/features/ui/components/ol/ol-list-group-item.tsx +++ b/services/web/frontend/js/features/ui/components/ol/ol-list-group-item.tsx @@ -1,4 +1,4 @@ -import { ListGroupItem, ListGroupItemProps } from 'react-bootstrap-5' +import { ListGroupItem, ListGroupItemProps } from 'react-bootstrap' function OLListGroupItem(props: ListGroupItemProps) { const as = props.as ?? 'button' diff --git a/services/web/frontend/js/features/ui/components/ol/ol-list-group.tsx b/services/web/frontend/js/features/ui/components/ol/ol-list-group.tsx index a28c7e977d..4eb473217a 100644 --- a/services/web/frontend/js/features/ui/components/ol/ol-list-group.tsx +++ b/services/web/frontend/js/features/ui/components/ol/ol-list-group.tsx @@ -1,4 +1,4 @@ -import { ListGroup, ListGroupProps } from 'react-bootstrap-5' +import { ListGroup, ListGroupProps } from 'react-bootstrap' function OLListGroup(props: ListGroupProps) { const as = props.as ?? 'div' diff --git a/services/web/frontend/js/features/ui/components/ol/ol-modal.tsx b/services/web/frontend/js/features/ui/components/ol/ol-modal.tsx index bf20d18eef..057d8dc3ce 100644 --- a/services/web/frontend/js/features/ui/components/ol/ol-modal.tsx +++ b/services/web/frontend/js/features/ui/components/ol/ol-modal.tsx @@ -4,8 +4,8 @@ import { ModalHeaderProps, ModalTitleProps, ModalFooterProps, -} from 'react-bootstrap-5' -import { ModalBodyProps } from 'react-bootstrap-5/ModalBody' +} from 'react-bootstrap' +import { ModalBodyProps } from 'react-bootstrap/ModalBody' type OLModalProps = ModalProps & { size?: 'sm' | 'lg' diff --git a/services/web/frontend/js/features/ui/components/ol/ol-overlay.tsx b/services/web/frontend/js/features/ui/components/ol/ol-overlay.tsx index bcf2a024c2..eb68066dd9 100644 --- a/services/web/frontend/js/features/ui/components/ol/ol-overlay.tsx +++ b/services/web/frontend/js/features/ui/components/ol/ol-overlay.tsx @@ -1,4 +1,4 @@ -import { Overlay, OverlayProps } from 'react-bootstrap-5' +import { Overlay, OverlayProps } from 'react-bootstrap' function OLOverlay(props: OverlayProps) { return diff --git a/services/web/frontend/js/features/ui/components/ol/ol-page-content-card.tsx b/services/web/frontend/js/features/ui/components/ol/ol-page-content-card.tsx index c10de1c0c6..c47be6d9de 100644 --- a/services/web/frontend/js/features/ui/components/ol/ol-page-content-card.tsx +++ b/services/web/frontend/js/features/ui/components/ol/ol-page-content-card.tsx @@ -1,4 +1,4 @@ -import { Card, CardBody } from 'react-bootstrap-5' +import { Card, CardBody } from 'react-bootstrap' import { FC } from 'react' import classNames from 'classnames' diff --git a/services/web/frontend/js/features/ui/components/ol/ol-popover.tsx b/services/web/frontend/js/features/ui/components/ol/ol-popover.tsx index 772084bc22..a20a2af13f 100644 --- a/services/web/frontend/js/features/ui/components/ol/ol-popover.tsx +++ b/services/web/frontend/js/features/ui/components/ol/ol-popover.tsx @@ -1,5 +1,5 @@ import { forwardRef } from 'react' -import { Popover, PopoverProps } from 'react-bootstrap-5' +import { Popover, PopoverProps } from 'react-bootstrap' type OLPopoverProps = Omit & { title?: React.ReactNode diff --git a/services/web/frontend/js/features/ui/components/ol/ol-row.tsx b/services/web/frontend/js/features/ui/components/ol/ol-row.tsx index 88c05ce102..88f2bc82d6 100644 --- a/services/web/frontend/js/features/ui/components/ol/ol-row.tsx +++ b/services/web/frontend/js/features/ui/components/ol/ol-row.tsx @@ -1,4 +1,4 @@ -import { Row } from 'react-bootstrap-5' +import { Row } from 'react-bootstrap' function OLRow(props: React.ComponentProps) { return diff --git a/services/web/frontend/js/features/ui/components/ol/ol-spinner.tsx b/services/web/frontend/js/features/ui/components/ol/ol-spinner.tsx index 4c1be6b125..ebd73ffab1 100644 --- a/services/web/frontend/js/features/ui/components/ol/ol-spinner.tsx +++ b/services/web/frontend/js/features/ui/components/ol/ol-spinner.tsx @@ -1,4 +1,4 @@ -import { Spinner } from 'react-bootstrap-5' +import { Spinner } from 'react-bootstrap' export type OLSpinnerSize = 'sm' | 'lg' diff --git a/services/web/frontend/js/features/ui/components/ol/ol-toast-container.tsx b/services/web/frontend/js/features/ui/components/ol/ol-toast-container.tsx index ca8250bf49..26c0ff3545 100644 --- a/services/web/frontend/js/features/ui/components/ol/ol-toast-container.tsx +++ b/services/web/frontend/js/features/ui/components/ol/ol-toast-container.tsx @@ -1,5 +1,5 @@ import { CSSProperties, FC } from 'react' -import { ToastContainer as BS5ToastContainer } from 'react-bootstrap-5' +import { ToastContainer as BS5ToastContainer } from 'react-bootstrap' type OLToastContainerProps = { style?: CSSProperties diff --git a/services/web/frontend/js/features/ui/components/ol/ol-toast.tsx b/services/web/frontend/js/features/ui/components/ol/ol-toast.tsx index 7460a6b269..e4256f4fca 100644 --- a/services/web/frontend/js/features/ui/components/ol/ol-toast.tsx +++ b/services/web/frontend/js/features/ui/components/ol/ol-toast.tsx @@ -1,5 +1,5 @@ import classNames from 'classnames' -import { Toast as BS5Toast } from 'react-bootstrap-5' +import { Toast as BS5Toast } from 'react-bootstrap' import { NotificationIcon, NotificationType, diff --git a/services/web/frontend/js/features/ui/components/ol/ol-toggle-button-group.tsx b/services/web/frontend/js/features/ui/components/ol/ol-toggle-button-group.tsx index 35777d29a6..3344fdade1 100644 --- a/services/web/frontend/js/features/ui/components/ol/ol-toggle-button-group.tsx +++ b/services/web/frontend/js/features/ui/components/ol/ol-toggle-button-group.tsx @@ -1,4 +1,4 @@ -import { ToggleButtonGroup, ToggleButtonGroupProps } from 'react-bootstrap-5' +import { ToggleButtonGroup, ToggleButtonGroupProps } from 'react-bootstrap' function OLToggleButtonGroup(props: ToggleButtonGroupProps) { return diff --git a/services/web/frontend/js/features/ui/components/ol/ol-toggle-button.tsx b/services/web/frontend/js/features/ui/components/ol/ol-toggle-button.tsx index d56d0b216a..a58772b78a 100644 --- a/services/web/frontend/js/features/ui/components/ol/ol-toggle-button.tsx +++ b/services/web/frontend/js/features/ui/components/ol/ol-toggle-button.tsx @@ -1,4 +1,4 @@ -import { ToggleButton, ToggleButtonProps } from 'react-bootstrap-5' +import { ToggleButton, ToggleButtonProps } from 'react-bootstrap' function OLToggleButton(props: ToggleButtonProps) { return diff --git a/services/web/frontend/js/features/word-count-modal/components/word-count-loading.tsx b/services/web/frontend/js/features/word-count-modal/components/word-count-loading.tsx index 61a4263b04..e1f277d714 100644 --- a/services/web/frontend/js/features/word-count-modal/components/word-count-loading.tsx +++ b/services/web/frontend/js/features/word-count-modal/components/word-count-loading.tsx @@ -1,4 +1,4 @@ -import { Spinner } from 'react-bootstrap-5' +import { Spinner } from 'react-bootstrap' import { useTranslation } from 'react-i18next' export const WordCountLoading = () => { diff --git a/services/web/frontend/js/features/word-count-modal/components/word-counts-client.tsx b/services/web/frontend/js/features/word-count-modal/components/word-counts-client.tsx index 4c4aae65f7..dac4934a50 100644 --- a/services/web/frontend/js/features/word-count-modal/components/word-counts-client.tsx +++ b/services/web/frontend/js/features/word-count-modal/components/word-counts-client.tsx @@ -1,7 +1,7 @@ import { FC, useMemo } from 'react' import { WordCountData } from '@/features/word-count-modal/components/word-count-data' import { useTranslation } from 'react-i18next' -import { Container, Row, Col, Form } from 'react-bootstrap-5' +import { Container, Row, Col, Form } from 'react-bootstrap' import OLNotification from '@/features/ui/components/ol/ol-notification' import usePersistedState from '@/shared/hooks/use-persisted-state' diff --git a/services/web/frontend/js/features/word-count-modal/components/word-counts.tsx b/services/web/frontend/js/features/word-count-modal/components/word-counts.tsx index 16d96525ac..e8a9731b7b 100644 --- a/services/web/frontend/js/features/word-count-modal/components/word-counts.tsx +++ b/services/web/frontend/js/features/word-count-modal/components/word-counts.tsx @@ -1,7 +1,7 @@ import { FC } from 'react' import { ServerWordCountData } from '@/features/word-count-modal/components/word-count-data' import { useTranslation } from 'react-i18next' -import { Container, Row, Col } from 'react-bootstrap-5' +import { Container, Row, Col } from 'react-bootstrap' import OLNotification from '@/features/ui/components/ol/ol-notification' export const WordCounts: FC<{ diff --git a/services/web/frontend/js/shared/components/menu-bar/menu-bar-dropdown.tsx b/services/web/frontend/js/shared/components/menu-bar/menu-bar-dropdown.tsx index 3cb495f0dd..bbdd117b23 100644 --- a/services/web/frontend/js/shared/components/menu-bar/menu-bar-dropdown.tsx +++ b/services/web/frontend/js/shared/components/menu-bar/menu-bar-dropdown.tsx @@ -7,7 +7,7 @@ import { FC, forwardRef, useCallback } from 'react' import classNames from 'classnames' import { useNestableDropdown } from '@/shared/hooks/use-nestable-dropdown' import { NestableDropdownContextProvider } from '@/shared/context/nestable-dropdown-context' -import { AnchorProps } from 'react-bootstrap-5' +import { AnchorProps } from 'react-bootstrap' import MaterialIcon from '../material-icon' import { DropdownMenuProps } from '@/features/ui/components/types/dropdown-menu-props' diff --git a/services/web/frontend/js/shared/components/select.tsx b/services/web/frontend/js/shared/components/select.tsx index 23f7d03521..667983c4bd 100644 --- a/services/web/frontend/js/shared/components/select.tsx +++ b/services/web/frontend/js/shared/components/select.tsx @@ -9,7 +9,7 @@ import { import classNames from 'classnames' import { useSelect } from 'downshift' import { useTranslation } from 'react-i18next' -import { Form, Spinner } from 'react-bootstrap-5' +import { Form, Spinner } from 'react-bootstrap' import FormControl from '@/features/ui/components/bootstrap-5/form/form-control' import MaterialIcon from '@/shared/components/material-icon' import { DropdownItem } from '@/features/ui/components/bootstrap-5/dropdown-menu' diff --git a/services/web/frontend/js/shared/components/upgrade-prompt.tsx b/services/web/frontend/js/shared/components/upgrade-prompt.tsx index 336d9c4344..1469d0d42b 100644 --- a/services/web/frontend/js/shared/components/upgrade-prompt.tsx +++ b/services/web/frontend/js/shared/components/upgrade-prompt.tsx @@ -5,7 +5,7 @@ import OLCol from '@/features/ui/components/ol/ol-col' import OLRow from '@/features/ui/components/ol/ol-row' import MaterialIcon from '@/shared/components/material-icon' import { PropsWithChildren } from 'react' -import { Container } from 'react-bootstrap-5' +import { Container } from 'react-bootstrap' import { Trans, useTranslation } from 'react-i18next' type IconListItemProps = PropsWithChildren<{ diff --git a/services/web/frontend/stories/ui/form/form-check-bs5.stories.tsx b/services/web/frontend/stories/ui/form/form-check-bs5.stories.tsx index 82ba8eba13..cfe1de227c 100644 --- a/services/web/frontend/stories/ui/form/form-check-bs5.stories.tsx +++ b/services/web/frontend/stories/ui/form/form-check-bs5.stories.tsx @@ -1,5 +1,5 @@ import { useRef, useLayoutEffect } from 'react' -import { Form } from 'react-bootstrap-5' +import { Form } from 'react-bootstrap' import type { Meta, StoryObj } from '@storybook/react' const meta: Meta<(typeof Form)['Check']> = { diff --git a/services/web/frontend/stories/ui/form/form-input-bs5.stories.tsx b/services/web/frontend/stories/ui/form/form-input-bs5.stories.tsx index 65c1121ea3..6d24f21901 100644 --- a/services/web/frontend/stories/ui/form/form-input-bs5.stories.tsx +++ b/services/web/frontend/stories/ui/form/form-input-bs5.stories.tsx @@ -1,4 +1,4 @@ -import { Form } from 'react-bootstrap-5' +import { Form } from 'react-bootstrap' import type { Meta, StoryObj } from '@storybook/react' import FormGroup from '@/features/ui/components/bootstrap-5/form/form-group' import FormText from '@/features/ui/components/bootstrap-5/form/form-text' diff --git a/services/web/frontend/stories/ui/form/form-radio-bs5.stories.tsx b/services/web/frontend/stories/ui/form/form-radio-bs5.stories.tsx index 95c15dc41e..99ce0cb40d 100644 --- a/services/web/frontend/stories/ui/form/form-radio-bs5.stories.tsx +++ b/services/web/frontend/stories/ui/form/form-radio-bs5.stories.tsx @@ -1,4 +1,4 @@ -import { Form } from 'react-bootstrap-5' +import { Form } from 'react-bootstrap' import type { Meta, StoryObj } from '@storybook/react' const meta: Meta<(typeof Form)['Check']> = { diff --git a/services/web/frontend/stories/ui/form/form-select-bs5.stories.tsx b/services/web/frontend/stories/ui/form/form-select-bs5.stories.tsx index 61e96c7aa2..a8cd9cec82 100644 --- a/services/web/frontend/stories/ui/form/form-select-bs5.stories.tsx +++ b/services/web/frontend/stories/ui/form/form-select-bs5.stories.tsx @@ -1,4 +1,4 @@ -import { Form, FormSelectProps } from 'react-bootstrap-5' +import { Form, FormSelectProps } from 'react-bootstrap' import type { Meta, StoryObj } from '@storybook/react' import FormGroup from '@/features/ui/components/bootstrap-5/form/form-group' import FormText from '@/features/ui/components/bootstrap-5/form/form-text' diff --git a/services/web/frontend/stories/ui/form/form-textarea-bs5.stories.tsx b/services/web/frontend/stories/ui/form/form-textarea-bs5.stories.tsx index 898ec97960..6bca986d0d 100644 --- a/services/web/frontend/stories/ui/form/form-textarea-bs5.stories.tsx +++ b/services/web/frontend/stories/ui/form/form-textarea-bs5.stories.tsx @@ -1,4 +1,4 @@ -import { Form } from 'react-bootstrap-5' +import { Form } from 'react-bootstrap' import type { Meta, StoryObj } from '@storybook/react' import FormGroup from '@/features/ui/components/bootstrap-5/form/form-group' import FormText from '@/features/ui/components/bootstrap-5/form/form-text' diff --git a/services/web/frontend/stories/ui/row.stories.tsx b/services/web/frontend/stories/ui/row.stories.tsx index 4b6411619c..a5a7409bb6 100644 --- a/services/web/frontend/stories/ui/row.stories.tsx +++ b/services/web/frontend/stories/ui/row.stories.tsx @@ -1,4 +1,4 @@ -import { Container, Row, Col } from 'react-bootstrap-5' +import { Container, Row, Col } from 'react-bootstrap' import { Meta } from '@storybook/react' type Args = React.ComponentProps diff --git a/services/web/frontend/stories/ui/split-button.stories.tsx b/services/web/frontend/stories/ui/split-button.stories.tsx index d78643b4cd..674d2e796b 100644 --- a/services/web/frontend/stories/ui/split-button.stories.tsx +++ b/services/web/frontend/stories/ui/split-button.stories.tsx @@ -9,7 +9,7 @@ import { DropdownToggle, } from '@/features/ui/components/bootstrap-5/dropdown-menu' import Button from '@/features/ui/components/bootstrap-5/button' -import { ButtonGroup } from 'react-bootstrap-5' +import { ButtonGroup } from 'react-bootstrap' type Args = React.ComponentProps diff --git a/services/web/package.json b/services/web/package.json index f849507460..5a0e1c3959 100644 --- a/services/web/package.json +++ b/services/web/package.json @@ -261,7 +261,7 @@ "babel-plugin-module-resolver": "^5.0.2", "backbone": "^1.6.0", "bootstrap": "^3.4.1", - "bootstrap-5": "npm:bootstrap@^5.3.3", + "bootstrap-5": "npm:bootstrap@^5.3.6", "c8": "^7.2.0", "chai": "^4.3.6", "chai-as-promised": "^7.1.1", @@ -326,7 +326,7 @@ "prop-types": "^15.7.2", "qrcode": "^1.4.4", "react": "^18.3.1", - "react-bootstrap-5": "npm:react-bootstrap@^2.10.5", + "react-bootstrap": "^2.10.10", "react-chartjs-2": "^5.0.1", "react-color": "^2.19.3", "react-dnd": "^16.0.1", From fee5ea8411121d3d03e7c858291bdd8d1f1fc385 Mon Sep 17 00:00:00 2001 From: Antoine Clausse Date: Thu, 15 May 2025 11:01:38 +0200 Subject: [PATCH 113/194] Remove returns of promises within functions with callbacks (address DeprecationWarning) (#25603) * [document-updater] Don't return promises within functions with callbacks Remove the errors: DeprecationWarning: Calling promisify on a function that returns a Promise is likely a mistake https://cloudlogging.app.goo.gl/YHDhoarvLEw2w9rXA * Remove some more unnecessary returns in functions with callbacks, for consistency * Add `sendCanaryAppliedOp` to excluded methods for promisification GitOrigin-RevId: fa6d3e47c4e6561dc29d4c15e57c3289fc1f3dfa --- libraries/redis-wrapper/RedisLocker.js | 55 +++++++++---------- .../app/js/RealTimeRedisManager.js | 6 +- 2 files changed, 30 insertions(+), 31 deletions(-) diff --git a/libraries/redis-wrapper/RedisLocker.js b/libraries/redis-wrapper/RedisLocker.js index b819ad2188..17ad514246 100644 --- a/libraries/redis-wrapper/RedisLocker.js +++ b/libraries/redis-wrapper/RedisLocker.js @@ -97,7 +97,8 @@ module.exports = class RedisLocker { } /** - * @param {Callback} callback + * @param {string} id + * @param {function(Error, boolean, string): void} callback */ tryLock(id, callback) { if (callback == null) { @@ -106,7 +107,7 @@ module.exports = class RedisLocker { const lockValue = this.randomLock() const key = this.getKey(id) const startTime = Date.now() - return this.rclient.set( + this.rclient.set( key, lockValue, 'EX', @@ -121,7 +122,7 @@ module.exports = class RedisLocker { const timeTaken = Date.now() - startTime if (timeTaken > MAX_REDIS_REQUEST_LENGTH) { // took too long, so try to free the lock - return this.releaseLock(id, lockValue, function (err, result) { + this.releaseLock(id, lockValue, function (err, result) { if (err != null) { return callback(err) } // error freeing lock @@ -139,7 +140,8 @@ module.exports = class RedisLocker { } /** - * @param {Callback} callback + * @param {string} id + * @param {function(Error, string): void} callback */ getLock(id, callback) { if (callback == null) { @@ -153,7 +155,7 @@ module.exports = class RedisLocker { return callback(e) } - return this.tryLock(id, (error, gotLock, lockValue) => { + this.tryLock(id, (error, gotLock, lockValue) => { if (error != null) { return callback(error) } @@ -173,14 +175,15 @@ module.exports = class RedisLocker { } /** - * @param {Callback} callback + * @param {string} id + * @param {function(Error, boolean): void} callback */ checkLock(id, callback) { if (callback == null) { callback = function () {} } const key = this.getKey(id) - return this.rclient.exists(key, (err, exists) => { + this.rclient.exists(key, (err, exists) => { if (err != null) { return callback(err) } @@ -196,30 +199,26 @@ module.exports = class RedisLocker { } /** - * @param {Callback} callback + * @param {string} id + * @param {string} lockValue + * @param {function(Error, boolean): void} callback */ releaseLock(id, lockValue, callback) { const key = this.getKey(id) - return this.rclient.eval( - UNLOCK_SCRIPT, - 1, - key, - lockValue, - (err, result) => { - if (err != null) { - return callback(err) - } else if (result != null && result !== 1) { - // successful unlock should release exactly one key - logger.error( - { id, key, lockValue, redis_err: err, redis_result: result }, - 'unlocking error' - ) - metrics.inc(this.metricsPrefix + '-unlock-error') - return callback(new Error('tried to release timed out lock')) - } else { - return callback(null, result) - } + this.rclient.eval(UNLOCK_SCRIPT, 1, key, lockValue, (err, result) => { + if (err != null) { + return callback(err) + } else if (result != null && result !== 1) { + // successful unlock should release exactly one key + logger.error( + { id, key, lockValue, redis_err: err, redis_result: result }, + 'unlocking error' + ) + metrics.inc(this.metricsPrefix + '-unlock-error') + return callback(new Error('tried to release timed out lock')) + } else { + return callback(null, result) } - ) + }) } } diff --git a/services/document-updater/app/js/RealTimeRedisManager.js b/services/document-updater/app/js/RealTimeRedisManager.js index 08bf132dec..2b67971c5c 100644 --- a/services/document-updater/app/js/RealTimeRedisManager.js +++ b/services/document-updater/app/js/RealTimeRedisManager.js @@ -49,7 +49,7 @@ const RealTimeRedisManager = { MAX_OPS_PER_ITERATION, -1 ) - return multi.exec(function (error, replys) { + multi.exec(function (error, replys) { if (error != null) { return callback(error) } @@ -80,7 +80,7 @@ const RealTimeRedisManager = { }, getUpdatesLength(docId, callback) { - return rclient.llen(Keys.pendingUpdates({ doc_id: docId }), callback) + rclient.llen(Keys.pendingUpdates({ doc_id: docId }), callback) }, sendCanaryAppliedOp({ projectId, docId, op }) { @@ -132,5 +132,5 @@ const RealTimeRedisManager = { module.exports = RealTimeRedisManager module.exports.promises = promisifyAll(RealTimeRedisManager, { - without: ['sendData'], + without: ['sendCanaryAppliedOp', 'sendData'], }) From 69d99079b1d7f787cf60d4479392aef695d926b3 Mon Sep 17 00:00:00 2001 From: David <33458145+davidmcpowell@users.noreply.github.com> Date: Thu, 15 May 2025 11:04:28 +0100 Subject: [PATCH 114/194] Merge pull request #25553 from overleaf/dp-clone-project-modal-proptypes Remove Proptypes from CloneProjectModal GitOrigin-RevId: 400f4c9de72eb1910a0ca067882a6358663303d3 --- ...nt.jsx => clone-project-modal-content.tsx} | 39 +++++++++---------- ...ject-modal.jsx => clone-project-modal.tsx} | 25 +++++------- ...=> editor-clone-project-modal-wrapper.tsx} | 17 ++++---- .../shared/context/types/project-context.tsx | 7 +--- 4 files changed, 38 insertions(+), 50 deletions(-) rename services/web/frontend/js/features/clone-project-modal/components/{clone-project-modal-content.jsx => clone-project-modal-content.tsx} (84%) rename services/web/frontend/js/features/clone-project-modal/components/{clone-project-modal.jsx => clone-project-modal.tsx} (71%) rename services/web/frontend/js/features/clone-project-modal/components/{editor-clone-project-modal-wrapper.jsx => editor-clone-project-modal-wrapper.tsx} (74%) diff --git a/services/web/frontend/js/features/clone-project-modal/components/clone-project-modal-content.jsx b/services/web/frontend/js/features/clone-project-modal/components/clone-project-modal-content.tsx similarity index 84% rename from services/web/frontend/js/features/clone-project-modal/components/clone-project-modal-content.jsx rename to services/web/frontend/js/features/clone-project-modal/components/clone-project-modal-content.tsx index 404af82ca4..5754522360 100644 --- a/services/web/frontend/js/features/clone-project-modal/components/clone-project-modal-content.jsx +++ b/services/web/frontend/js/features/clone-project-modal/components/clone-project-modal-content.tsx @@ -1,6 +1,5 @@ /* eslint-disable jsx-a11y/no-autofocus */ -import PropTypes from 'prop-types' -import { useCallback, useMemo, useState } from 'react' +import { FormEvent, useCallback, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' import { postJSON } from '../../../infrastructure/fetch-json' import { CloneProjectTag } from './clone-project-tag' @@ -16,6 +15,7 @@ import OLFormGroup from '@/features/ui/components/ol/ol-form-group' import OLFormControl from '@/features/ui/components/ol/ol-form-control' import OLFormLabel from '@/features/ui/components/ol/ol-form-label' import OLButton from '@/features/ui/components/ol/ol-button' +import { Tag } from '../../../../../app/src/Features/Tags/types' export default function CloneProjectModalContent({ handleHide, @@ -25,10 +25,18 @@ export default function CloneProjectModalContent({ projectId, projectName, projectTags, +}: { + handleHide: () => void + inFlight: boolean + setInFlight: (inFlight: boolean) => void + handleAfterCloned: (clonedProject: any, tags: Tag[]) => void + projectId: string + projectName: string + projectTags: Tag[] }) { const { t } = useTranslation() - const [error, setError] = useState() + const [error, setError] = useState() const [clonedProjectName, setClonedProjectName] = useState( `${projectName} (Copy)` ) @@ -42,7 +50,7 @@ export default function CloneProjectModalContent({ ) // form submission: clone the project if the name is valid - const handleSubmit = event => { + const handleSubmit = (event: FormEvent) => { event.preventDefault() if (!valid) { @@ -75,7 +83,7 @@ export default function CloneProjectModalContent({ }) } - const removeTag = useCallback(tag => { + const removeTag = useCallback((tag: Tag) => { setClonedProjectTags(value => value.filter(item => item._id !== tag._id)) }, []) @@ -120,7 +128,11 @@ export default function CloneProjectModalContent({ {error && ( )} @@ -142,18 +154,3 @@ export default function CloneProjectModalContent({ ) } -CloneProjectModalContent.propTypes = { - handleHide: PropTypes.func.isRequired, - inFlight: PropTypes.bool, - setInFlight: PropTypes.func.isRequired, - handleAfterCloned: PropTypes.func.isRequired, - projectId: PropTypes.string, - projectName: PropTypes.string, - projectTags: PropTypes.arrayOf( - PropTypes.shape({ - _id: PropTypes.string.isRequired, - name: PropTypes.string.isRequired, - color: PropTypes.string, - }) - ), -} diff --git a/services/web/frontend/js/features/clone-project-modal/components/clone-project-modal.jsx b/services/web/frontend/js/features/clone-project-modal/components/clone-project-modal.tsx similarity index 71% rename from services/web/frontend/js/features/clone-project-modal/components/clone-project-modal.jsx rename to services/web/frontend/js/features/clone-project-modal/components/clone-project-modal.tsx index 4ebc22545d..8c5793b87e 100644 --- a/services/web/frontend/js/features/clone-project-modal/components/clone-project-modal.jsx +++ b/services/web/frontend/js/features/clone-project-modal/components/clone-project-modal.tsx @@ -1,7 +1,8 @@ import React, { memo, useCallback, useState } from 'react' -import PropTypes from 'prop-types' import CloneProjectModalContent from './clone-project-modal-content' import OLModal from '@/features/ui/components/ol/ol-modal' +import { ClonedProject } from '../../../../../types/project/dashboard/api' +import { Tag } from '../../../../../app/src/Features/Tags/types' function CloneProjectModal({ show, @@ -10,6 +11,13 @@ function CloneProjectModal({ projectId, projectName, projectTags, +}: { + show: boolean + handleHide: () => void + handleAfterCloned: (clonedProject: ClonedProject, tags: Tag[]) => void + projectId: string + projectName: string + projectTags: Tag[] }) { const [inFlight, setInFlight] = useState(false) @@ -42,19 +50,4 @@ function CloneProjectModal({ ) } -CloneProjectModal.propTypes = { - handleHide: PropTypes.func.isRequired, - show: PropTypes.bool.isRequired, - handleAfterCloned: PropTypes.func.isRequired, - projectId: PropTypes.string, - projectName: PropTypes.string, - projectTags: PropTypes.arrayOf( - PropTypes.shape({ - _id: PropTypes.string.isRequired, - name: PropTypes.string.isRequired, - color: PropTypes.string, - }) - ), -} - export default memo(CloneProjectModal) diff --git a/services/web/frontend/js/features/clone-project-modal/components/editor-clone-project-modal-wrapper.jsx b/services/web/frontend/js/features/clone-project-modal/components/editor-clone-project-modal-wrapper.tsx similarity index 74% rename from services/web/frontend/js/features/clone-project-modal/components/editor-clone-project-modal-wrapper.jsx rename to services/web/frontend/js/features/clone-project-modal/components/editor-clone-project-modal-wrapper.tsx index 2ebc6edece..aba9a87fbc 100644 --- a/services/web/frontend/js/features/clone-project-modal/components/editor-clone-project-modal-wrapper.jsx +++ b/services/web/frontend/js/features/clone-project-modal/components/editor-clone-project-modal-wrapper.tsx @@ -1,11 +1,18 @@ import React from 'react' -import PropTypes from 'prop-types' import { useProjectContext } from '../../../shared/context/project-context' import withErrorBoundary from '../../../infrastructure/error-boundary' import CloneProjectModal from './clone-project-modal' const EditorCloneProjectModalWrapper = React.memo( - function EditorCloneProjectModalWrapper({ show, handleHide, openProject }) { + function EditorCloneProjectModalWrapper({ + show, + handleHide, + openProject, + }: { + show: boolean + handleHide: () => void + openProject: ({ project_id }: { project_id: string }) => void + }) { const { _id: projectId, name: projectName, @@ -30,10 +37,4 @@ const EditorCloneProjectModalWrapper = React.memo( } ) -EditorCloneProjectModalWrapper.propTypes = { - handleHide: PropTypes.func.isRequired, - show: PropTypes.bool.isRequired, - openProject: PropTypes.func.isRequired, -} - export default withErrorBoundary(EditorCloneProjectModalWrapper) diff --git a/services/web/frontend/js/shared/context/types/project-context.tsx b/services/web/frontend/js/shared/context/types/project-context.tsx index 18eb42010c..4e1abdc420 100644 --- a/services/web/frontend/js/shared/context/types/project-context.tsx +++ b/services/web/frontend/js/shared/context/types/project-context.tsx @@ -1,6 +1,7 @@ import { UserId } from '../../../../../types/user' import { PublicAccessLevel } from '../../../../../types/public-access-level' import { ProjectSnapshot } from '@/infrastructure/project-snapshot' +import { Tag } from '../../../../../app/src/Features/Tags/types' export type ProjectContextMember = { _id: UserId @@ -43,11 +44,7 @@ export type ProjectContextValue = { privileges: string signUpDate: string } - tags: { - _id: string - name: string - color?: string - }[] + tags: Tag[] trackChangesState: boolean | Record projectSnapshot: ProjectSnapshot joinedOnce: boolean From 4847c83cb856182e6f73607d748adfcd9dfb0a84 Mon Sep 17 00:00:00 2001 From: David <33458145+davidmcpowell@users.noreply.github.com> Date: Thu, 15 May 2025 11:04:53 +0100 Subject: [PATCH 115/194] Merge pull request #25618 from overleaf/dp-symbol-palette-proptypes Remove Proptypes from SymbolPalette components GitOrigin-RevId: 58b74652a5b47612c4622a7cac9b1ff3aadadfc5 --- services/web/frontend/js/shared/context/editor-context.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/services/web/frontend/js/shared/context/editor-context.tsx b/services/web/frontend/js/shared/context/editor-context.tsx index 7d751ac6ab..2ebab1be7b 100644 --- a/services/web/frontend/js/shared/context/editor-context.tsx +++ b/services/web/frontend/js/shared/context/editor-context.tsx @@ -21,6 +21,7 @@ import { PermissionsLevel } from '@/features/ide-react/types/permissions' import { useModalsContext } from '@/features/ide-react/context/modals-context' import { WritefullAPI } from './types/writefull-instance' import { Cobranding } from '../../../../types/cobranding' +import { SymbolWithCharacter } from '../../../../modules/symbol-palette/frontend/js/data/symbols' export const EditorContext = createContext< | { @@ -30,7 +31,7 @@ export const EditorContext = createContext< setPermissionsLevel: (permissionsLevel: PermissionsLevel) => void showSymbolPalette?: boolean toggleSymbolPalette?: () => void - insertSymbol?: (symbol: string) => void + insertSymbol?: (symbol: SymbolWithCharacter) => void isProjectOwner: boolean isRestrictedTokenMember?: boolean isPendingEditor: boolean @@ -169,7 +170,7 @@ export const EditorProvider: FC = ({ children }) => { setTitle(title) }, [projectName, setTitle, role]) - const insertSymbol = useCallback((symbol: string) => { + const insertSymbol = useCallback((symbol: SymbolWithCharacter) => { window.dispatchEvent( new CustomEvent('editor:insert-symbol', { detail: symbol, From 449a5d63390562dbd1ed7fa4eed7dbc01a7e1d2b Mon Sep 17 00:00:00 2001 From: David <33458145+davidmcpowell@users.noreply.github.com> Date: Thu, 15 May 2025 11:05:17 +0100 Subject: [PATCH 116/194] Merge pull request #25605 from overleaf/dp-track-changes-redirect Redirect old track changes marketing page to track changes learn wiki page GitOrigin-RevId: 890e7f746b71b8e5108e8209d55903f68adde1ea --- .../StaticPages/StaticPagesRouter.mjs | 5 - .../stylesheets/app/review-features-page.less | 481 ------------------ .../web/frontend/stylesheets/main-style.less | 1 - .../img/feature-page/feat-accept-poster.jpg | Bin 86569 -> 0 bytes .../public/img/feature-page/feat-accept.mp4 | Bin 394826 -> 0 bytes .../img/feature-page/feat-changes-poster.jpg | Bin 83046 -> 0 bytes .../public/img/feature-page/feat-changes.mp4 | Bin 421813 -> 0 bytes .../img/feature-page/feat-discuss-poster.jpg | Bin 107571 -> 0 bytes .../public/img/feature-page/feat-discuss.mp4 | Bin 214985 -> 0 bytes .../img/feature-page/feat-todos-poster.jpg | Bin 94730 -> 0 bytes .../public/img/feature-page/feat-todos.mp4 | Bin 225988 -> 0 bytes .../public/img/feature-page/intro-poster.jpg | Bin 72008 -> 0 bytes .../web/public/img/feature-page/intro.mp4 | Bin 240870 -> 0 bytes .../public/img/feature-page/pamela-marcum.jpg | Bin 53721 -> 0 bytes 14 files changed, 487 deletions(-) delete mode 100644 services/web/frontend/stylesheets/app/review-features-page.less delete mode 100644 services/web/public/img/feature-page/feat-accept-poster.jpg delete mode 100644 services/web/public/img/feature-page/feat-accept.mp4 delete mode 100644 services/web/public/img/feature-page/feat-changes-poster.jpg delete mode 100644 services/web/public/img/feature-page/feat-changes.mp4 delete mode 100644 services/web/public/img/feature-page/feat-discuss-poster.jpg delete mode 100644 services/web/public/img/feature-page/feat-discuss.mp4 delete mode 100644 services/web/public/img/feature-page/feat-todos-poster.jpg delete mode 100644 services/web/public/img/feature-page/feat-todos.mp4 delete mode 100644 services/web/public/img/feature-page/intro-poster.jpg delete mode 100644 services/web/public/img/feature-page/intro.mp4 delete mode 100644 services/web/public/img/feature-page/pamela-marcum.jpg diff --git a/services/web/app/src/Features/StaticPages/StaticPagesRouter.mjs b/services/web/app/src/Features/StaticPages/StaticPagesRouter.mjs index b81e25c7cd..60350505a6 100644 --- a/services/web/app/src/Features/StaticPages/StaticPagesRouter.mjs +++ b/services/web/app/src/Features/StaticPages/StaticPagesRouter.mjs @@ -22,11 +22,6 @@ export default { HomeController.externalPage('planned_maintenance', 'Planned Maintenance') ) - webRouter.get( - '/track-changes-and-comments-in-latex', - HomeController.externalPage('review-features-page', 'Review features') - ) - webRouter.get('/university', UniversityController.getIndexPage) return webRouter.get('/university/*', UniversityController.getPage) }, diff --git a/services/web/frontend/stylesheets/app/review-features-page.less b/services/web/frontend/stylesheets/app/review-features-page.less deleted file mode 100644 index b03256af50..0000000000 --- a/services/web/frontend/stylesheets/app/review-features-page.less +++ /dev/null @@ -1,481 +0,0 @@ -@rfp-h1-size: 2.442em; -@rfp-h2-size: 1.953em; -@rfp-h3-size: 1.563em; -@rfp-lead-size: 1.25em; - -@rfp-sl-red: @red; -@rfp-rp-blue: @rp-type-blue; - -@rfp-rp-blue-light: #f8f9fd; -@rfp-rp-blue-dark: shade(@rfp-rp-blue, 50%); -@rfp-rp-blue-darker: shade(@rfp-rp-blue, 65%); -@rfp-rp-blue-darkest: shade(@rfp-rp-blue, 75%); - -@rfp-card-shadow: 0 0 30px 5px rgba(0, 0, 0, 0.3); -@rfp-border-radius: 5px; - -@rfp-header-height: 80px; -@rfp-header-height-collapsed: 50px; - -.rfp-main { - background-color: @content-alt-bg-color; - font-size: 18px; - min-width: 240px; -} - -// Typographical scale and basics. -.rfp-h1 { - font-size: @rfp-h2-size; - margin-bottom: 1.6em; - color: inherit; - @media (min-width: @screen-xs-min) { - font-size: @rfp-h1-size; - } -} -.rfp-h1-masthead { - color: #fff; - margin-bottom: 1em; -} -.rfp-h2 { - font-size: @rfp-h2-size; - margin-bottom: 1.6em; - color: inherit; -} -.rfp-h3 { - font-size: @rfp-h3-size; - margin-bottom: 1.6em; - color: inherit; -} -.rfp-h3-cta { - margin-top: 0; - margin-bottom: 40px; -} -.rfp-lead { - margin-bottom: 1.6em; - max-width: 30em; - margin-left: auto; - margin-right: auto; - @media (min-width: @screen-xs-min) { - font-size: @rfp-lead-size; - } -} -.rfp-lead-cta { - margin-top: 0; - margin-bottom: 40px; -} -.rfp-lead-strong { - font-weight: 700; - .rfp-section-masthead & { - margin-bottom: 0; - } -} -.rfp-p { - margin-bottom: 1.6em; - max-width: 30em; - margin-left: auto; - margin-right: auto; - .rfp-section-feature & { - margin-left: initial; - } - .rfp-section-feature-alt & { - margin-left: auto; - margin-right: initial; - } -} -.rfp-highlight { - font-weight: 700; -} -// Sections -.rfp-header { - position: fixed; - top: 0; - width: 100%; - z-index: 2; - height: @rfp-header-height; - transition: height 0.2s; - background-color: fade(@rfp-rp-blue-darkest, 90%); - padding: 15px 20px; - min-width: 320px; - @media (min-width: @screen-xs-min) { - padding-left: 30px; - padding-right: 30px; - } - @media (min-width: @screen-sm-min) { - padding-left: 60px; - padding-right: 60px; - } - .rfp-main-header-collapsed & { - height: @rfp-header-height-collapsed; - padding-top: 10px; - padding-bottom: 10px; - } -} -.rfp-header-wrapper { - display: flex; - align-items: center; - justify-content: space-between; - max-width: @container-large-desktop; - height: 100%; - margin: auto; -} -.rfp-header-logo-container, -.rfp-header-logo { - height: 100%; -} -.rfp-section { - padding: 30px; - text-align: center; - overflow: hidden; - @media (min-width: @screen-xs-min) { - padding: 30px; - } - @media (min-width: @screen-sm-min) { - padding: 60px; - } -} -.rfp-section-masthead { - color: #fff; - background-size: cover; - background-position: center; - background-color: @rfp-rp-blue-darker; - padding-top: @rfp-header-height; - .rfp-lead { - opacity: 0; - transition: opacity 0.8s ease; - } - &.rfp-section-masthead-in { - .rfp-lead { - opacity: 1; - } - } -} -.rfp-section-blockquote { - position: relative; - padding-top: 30px; - padding-bottom: 30px; - background-color: @brand-secondary; - box-shadow: @rfp-card-shadow; -} -.rfp-section-feature { - display: block; - text-align: left; - @media (min-width: @screen-sm-min) { - .rfp-section-wrapper { - display: flex; - align-items: center; - } - } -} -.rfp-feature-description-container, -.rfp-feature-video-container { - flex: 0 0 50%; -} -.rfp-feature-description-container { - @media (min-width: @screen-sm-min) { - padding-right: 1em; - .rfp-section-feature-alt & { - padding-right: 0; - padding-left: 1em; - } - } -} -.rfp-feature-video-container { - @media (min-width: @screen-sm-min) { - padding-left: 1em; - .rfp-section-feature-alt & { - padding-left: 0; - padding-right: 1em; - order: -1; - } - } -} -.rfp-section-feature-alt { - color: #fff; - background-color: @ol-blue-gray-5; - @media (min-width: @screen-sm-min) { - text-align: right; - } -} -.rfp-section-feature-white { - background: #ffffff; -} -.rfp-section-testimonials { - background-color: @rfp-rp-blue-darkest; -} -.rfp-section-final { - background-color: @rfp-rp-blue-darker; -} -.rfp-section-wrapper { - max-width: @container-large-desktop; - margin: 0 auto; -} -// Elements -.rfp-h1-masthead-portion { - display: inline-block; - transform: translate(150px, 0); - opacity: 0; - transition: - transform 0.8s ease 0s, - opacity 0.8s ease 0s; - &:nth-child(2) { - transition-delay: 0.5s, 0.5s; - } - &:nth-child(3) { - transition-delay: 0.5s, 0.5s; - } - &:nth-child(4) { - transition-delay: 1s, 1s; - } - &:nth-child(5) { - transition-delay: 1s, 1s; - } - - .rfp-section-masthead-in & { - transform: translate(0, 0); - opacity: 1; - } -} -.rfp-video { - max-width: 100%; - box-shadow: @rfp-card-shadow; - border-radius: @rfp-border-radius; -} -.rfp-video-masthead { - width: 270px; - height: 163px; - margin-bottom: 2em; - transform: translate(0, 100px); - opacity: 0; - transition: - transform 0.8s ease 1s, - opacity 0.8s ease 1s; - box-shadow: none; - max-width: none; - - @media (min-width: @screen-xs-min) { - width: 400px; - height: 241px; - } - @media (min-width: 600px) { - width: 525px; - height: 316px; - } - @media (min-width: @screen-sm-min) { - width: 633px; - height: 381px; - } - @media (min-width: @screen-sm-min) { - width: 697px; - height: 420px; - } - .rfp-section-masthead-in & { - transform: translate(0, 0); - opacity: 1; - box-shadow: @rfp-card-shadow; - } -} -.rfp-video-anim { - transition: - transform 0.8s ease, - opacity 0.8s ease; - transform: translate(100%, 0); - opacity: 0; -} -.rfp-video-anim-alt { - transform: translate(-100%, 0); -} -.rfp-video-anim-in { - transform: translate(0, 0); - opacity: 1; -} -.rfp-quote-section { - @media (min-width: @screen-md-min) { - display: flex; - } -} -.rfp-quote { - display: block; - width: 100%; - padding: 20px 40px; - border-left: 0; - max-width: 30em; - font-size: @rfp-lead-size; - quotes: '\201C' '\201D'; - box-shadow: @rfp-card-shadow; - border-radius: @rfp-border-radius; - background-color: #fff; - color: @rfp-rp-blue-dark; - font-size: 1em; - margin: 0 auto 20px; - - @media (min-width: @screen-xs-min) { - font-size: @rfp-lead-size; - } - - @media (min-width: @screen-md-min) { - display: flex; - flex-direction: column; - justify-content: space-between; - flex: 0 1 50%; - margin-right: 20px; - } - // Override weird Boostrap default. - p { - display: block; - } - &:last-of-type { - @media (min-width: @screen-md-min) { - margin-right: 0; - } - } - &::before { - content: none; - } -} -.rfp-quote-main { - color: #ffffff; - display: block; - max-width: none; - border-left: 0; - margin: 0 auto; - padding: 0; - quotes: '\201C' '\201D'; - font-size: @rfp-lead-size; - @media (min-width: @screen-md-min) { - display: flex; - } - // Override weird Boostrap default. - p { - display: block; - } - &::before { - content: none; - } -} -.rfp-quoted-text { - position: relative; - display: inline-block; - font-family: @font-family-serif; - font-style: italic; - text-align: left; - margin: 0 0 40px 0; - &::before { - content: open-quote; - display: block; - position: absolute; - font-family: @font-family-serif; - font-size: @rfp-lead-size; - line-height: inherit; - color: inherit; - left: -0.75em; - } - .rfp-quote-main & { - @media (min-width: @screen-md-min) { - flex: 1 1 70%; - margin: auto 40px auto auto; - } - } -} -.rfp-quoted-person { - display: inline-block; - font-size: 0.8em; - .rfp-quote-main & { - display: flex; - align-items: center; - justify-content: center; - flex: 0 0 30%; - } -} -.rfp-quoted-person-name { - margin: 0; -} -.rfp-quoted-person-affil { - margin: 0; - font-size: 0.8em; - &:hover, - &:focus { - text-decoration: none; - cursor: pointer; - } - .rfp-quote-main & { - color: #fff; - &:hover, - &:focus { - color: #fff; - } - } -} -.rfp-quoted-person-photo { - border-radius: 3em; - width: 6em; - margin-bottom: 20px; - .rfp-quote-main & { - margin-bottom: 0; - margin-right: 20px; - } -} -.rfp-users { - display: flex; - flex-wrap: wrap; - margin: 0 1em 2em; - @media (min-width: @screen-md-min) { - flex-wrap: nowrap; - align-items: center; - } -} -.rfp-user-container { - flex: 0 0 100%; - padding: 10px; - @media (min-width: @screen-xs-min) { - flex-basis: 50%; - } - @media (min-width: @screen-md-min) { - flex-basis: 25%; - padding: 20px; - } -} -.rfp-user-logo { - max-width: 100%; -} -.rfp-cta-container { - max-width: 40em; - margin: 0 auto; - padding: 40px; - background-color: #fff; - color: @rfp-rp-blue-dark; - box-shadow: @rfp-card-shadow; - border-radius: @rfp-border-radius; -} -.rfp-cta-header { - font-size: 1em; - padding: 0.2em 1em; -} -.rfp-cta-main { - display: block; - transition: transform 0.25s; - transform: translate(0, 0); -} -.rfp-cta-extra { - display: block; - position: absolute; - left: 50%; - text-transform: uppercase; - transition: - opacity 0.25s, - transform 0.25s; - transform: translate(-50%, 100%); - opacity: 0; - font-size: 0.5em; -} -.rfp-universities { - text-align: center; - img { - display: inline-block; - padding: 0 @padding-md; - width: 20%; - @media only screen and (max-width: @screen-sm-max) { - padding: @padding-md; - width: 50%; - } - } -} diff --git a/services/web/frontend/stylesheets/main-style.less b/services/web/frontend/stylesheets/main-style.less index 696da82176..d42a2ab502 100644 --- a/services/web/frontend/stylesheets/main-style.less +++ b/services/web/frontend/stylesheets/main-style.less @@ -126,7 +126,6 @@ @import 'app/ol-chat.less'; @import 'app/templates-v2.less'; @import 'app/login-register.less'; -@import 'app/review-features-page.less'; @import 'app/institution-hub.less'; @import 'app/publisher-hub.less'; @import 'app/admin-hub.less'; diff --git a/services/web/public/img/feature-page/feat-accept-poster.jpg b/services/web/public/img/feature-page/feat-accept-poster.jpg deleted file mode 100644 index bad07902f348b02761d1b3f96b295a34797ee87a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 86569 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

    ~D#wz9X znRuvLNs-E5L$#jM!<5=i(F;noPHQJ&Feesp-gzL?XXx?h%ExWM#U^Oq?JwUe-XGHGzlaU}S^osQcV@Cf&a?!&p0QD&RFFLtjQV;cLsQRB<3qwdr;0qL5J0v+}sywu~j!Vxo_ zckh{g8%*l&?|2YJ6w`;lRUKJ5_(dCvPj@-@efh;5)kr|b>uCN`H8*znazuK2WPo+0 zfvQMDZdufg?;-Y&@4-;q!q!~fLZ6bzst7NkNo)|-S|8Vjd3~91JGp?bJNpP&3E+y0{vvQjCDrhaB$G|_7IDR`U?ftC=icpL ztGl3w2!hR2`bHpf0-wBoC=ooG@TsdasLr#{Qs9cYW&%)7*8aIpWfH%WLYD8^^~;12 z|E?WfW98AMS(EZ-7M2g+zYGL(2(#7QB0WkL1aUaMB8dc@L1|ZQP-nAmN#Cy5bC%+# z>bbw{OQ4mWNTn-1LS5XpRz;jzc4Ce`p%IiB$GLmW6~zS6{Zq0P^IV&pw1!bj&9*YL z+On!Ts(O0@iXQEayo>1b5OOKaI+fj)B$5ipvIh-n1N29O@k#G}bq(cbY>+BP$!0;S z%8Hz;5yb26KHQM2@^H?q-Xvl3xfL4C{e`&wg6Shsr6O#zP604EHzI)o?=oSN={T95|X z^@^I|LG;zXu+TvwOQ{i8p%~LtM8tS6y@Non9CT$&$P|6I2}G(~(rXiv@cF2bS(}zc z1VEnN;tI#)5=`6|QoxbN(d_T+X8fZ=kDr*bj?3hIqPEePFf? z<83JlslG(DjM~(W#xBv=z!mK&QNYmicul;Wk3AlV0p$V`!%0|DTp*D8s5Q{633a(u zHvTkD3cHs^To|aK=))%XV;E9Z8AJFO%#bu4kB+Doxw^-g`SFHyCq7&>XY zVZ&DfNh#R{HE|@(*#XRFsP{RnNJfW-#(pn%yY@l$MNd=R=YIY(#NvGZ2KX()nHIe# zq;W*NzAbDZX9($86!*4xhS1~5g(+9*R*Rsvw}Y!`-*>@XNYOT>pq@b>P%I5^pJOdT z8jV_QO#0e{N(DJeybKq(+zb?atCbN1`f?bhmO^73tZj;@e|^AQc82g$b?oI%e7g}T zoAfG*orlu#AuXXYWc|y6MS;f`{F2~I3HuwDMd}TRoY$?1-zFs~)3E%*m4uAwHD(i% z)Qss5^e?K5p#nu$tf4^MzOS5{Kx8prfklyQ#V|Hpjlo2^P`Z51drx2Xzr_n1A38Jk zL8mJAI`)O=`AwwKA~7FPz2basht!87HUqectrWyK&!-fZf(5ifKF5iU92XhrK7yJUQG$EAW{HWq!KFOfAqAbsP)kT4z3`l?diQZeewZJxHH z&l5_Gk29LPWYb>0tU4nR$pm>&Mu932`}?RG)Ffu@vU+&Q_{hY0@iPn8hwtJeK~bN} zjyYa9JX*}OSUjM8rsAs`MGhXhOQidRid{tZXSFCZwZGP#Q}Bjk=H~jUlb`cGsyg*~ z?2Yas7?D$F_q*4LiA3`VoyaSGn~#u~Q2JhK)1fIf&B++g!X$AvXA;L+@H(8?8GqD* z$=jT+5#soLS9?MA))1m>8~vU>B4~T17IgG3*!IqySAlHATpKEF1qD}=wjwt=K{4kS z7S@uC>nRuJ=(rN+VeX5OpUziRaInw(_cuGguqyOwKboI)W&bSK8T_lQSY7C!Gh^TW zFA@BQ!RHsVAj8jZzL}5TxV`rO)bz@&-sNw<5dWc5953|?EA{*8g<6`-!Owpq|4r2O z|BBay5+@8Ld{DqZ%b5S~xPs{!aY^n8X-l%uN^haTnE$FW#X}llWxURY1 zLllcPAI#=df5E;dxtwUDsYpA94}ha#ey6rlu;=_`d<~)f^)phj@JRBaZ)@M>J;F_> z9odCRj3^7Z4HG6jNg=iIsW&?{Xx`Uy+jsVXlUOI&OK1>|P4WP%%x zSXqS+tnrphnby$)&*m(mc5e11w`r}nOWlVa+jhGZ1)}1HQJ2R}K-GKqi%^1!`*h4Y zP{=r0SdpU5G!vg-`y22utMkwUX4cr7)K3HL3-%A7z+TmzA7T;Dxn zpKBo*Mu*QU&Sjmr)X7cS%WFlSWYmIZ99sm^;Bxs#JSuUN+|hT9CiRx)L|i=Z-+pn3 zG9VppWG-yb2yw0zU@%t_%Io*$%1pcQXa-N=%G31hTqYb8^a%yY0beUhg|?5&Bm%rj z53B9@X!iq}8kMZ`?fts(HX^c3z(s-YnOeMH7FE)Atqz9COihVP3k_I`HkY&XirjQX zmJ)YBm8~Ip&-Cj7 zyN`L+n*EOXHdYnHRMqT@Zj_o=4`T70dC{Iut-|YfHeNSTLKm#H_39P0EF%w4Z)RJa z_@y;#STQqIfMc@ZHn}*U+NMfIIggtzCBH`R5&-p4kdjm4;=_T%%ddv=A01Lz?u*J- z^IQHrzve#-366@d-4jB^RxGxD_;3~|C{_m>$S&RNZ=y%b>yVLOJib%i7uuE+-hTuO zz?ps}I%7N5oPcG`gcZzY;Gg4Ml*;`0vrPDanI4m8Qr%Kjmf-p``?CcwZyVGW8N`yfOaU-`@8zygL~wJ6SJP0KR#64 z%3LrcRjP>VFX7dU1nYj};gshpRxl9ZKyvbwAZ(^sAHRQD#`7BOVD@fqOx>L>?rQsR zOWGB@yd>?Yq3cFZ5)rh71}>l%J+>jozpz*|qCG3Z*$25i3w$>=Uk~dOg|J2k)sC0K zX?*EV8kTmHAzuE|X?GrL6I>u08Eb5onys%9QkS1`RFX&veRwi)&Rs8U`5SdL6z7)o zFDW%JEKAER8Z{*w95F-~{qsu_QaIl9S~#r!s41X*KAw~;}nX7{y9MG~Jz zR$CbKaX$f5tT6cHgsF-v<#&GmVhE zBGp2&`?5jSy>ZBhLs?(lVLhj!Yd{jmG3@|{X65p5qf#zcvWE-fL&}ZE{2f^&Z5YqR zTML2!M2YWHU}>5^EKZJRy{X z8d|d9Mrcpd;~k9k1Hmr)psbHi;t`FQrS#iS?|6b7`Z#bLGRNOqBJNnBB^hIt-x!LY z`bpE2vu~EVVY_8FEmOhv38I^nX}gg?r%eK5r%gAl^Z>^_vMY>bV#03jcKUI7#nxW) zEg{>)(l*j6^sw>~b>unMSAhzWol(iayax-nY9UiAqLnArJMZ$MYRQit3i6B~z#rrj z_+*r2oj}{Q`EY!${8~|=$!=a;_xPu&x1oS0ersW_!RHg7gnVX%&K0{JE1$xQmKy3k zHz$fIK4`W^?5DvObHnVg}EREEdP63H0RT9Lt zY$Y2Jps4V!u;AU4yKJfU$vfGp<*ZV^{nsrIDSSmuP$?nE7Un`n#RMC0?}PlLf#6qO z${3)JzVABwN}uiPqfY{`S|ehfbi}0Rdpz2u;0~?*Tlo%Vk~W%lPrFPJm0VOpf->_M z#Gsxj(9Teoas|0S21B31{9;QGUt~Q%j@d}&D*lAUu<4@;v6;>5K(K%6Krah4Q#^kP zwG%e(?F3HqUE}Jn;3p=Nz|_pSG_mHM+i!A?$&y%7farQ5=LmBAGKR-AN{@XmjcJu~ zl9uXrBtZpy4rAJ~`GpX_8#BYe>=%~8m#(G8D7DVky1$_l_s#h)|D^S)^XmU{W!5c>-sj5n1Q-DG?mL&IX>*?Q&JrUJ6$18gmT= zV%kLBg398BdVz`;B@oDi;5%_fh}o5{H}+%`fAF5*(Kzq|bsQBIiIb9AL`38P+?Z(0 z>`0h{sqC|%C!a%oeDF)ns8WHHM5G$AZCQD%o8qxCIW>1r$dt6Of_4^sk$jVq`3EMc zm|*R`3to4^%S0L#19v@hV-Uf#07pzBWy=30)a0QD|gu}1+Nf>nOm&0+w{0Y zQ;6jf5{Z0xsy{TKzTB*dLc`mpx8aLxh{gUvQfq#+uo%b3_Zav#L^u%gdP|wm#CjRq zu+8rXDvXESv+h%o zCq$9cEK#!JO>2;hbZn<>rhL*&A_9pt5<=p2j7C+DLf8W#>(=+<3rW^aXY?jtCyUYO zDf0VWuK4ZB{%tR=LXO ze%n$Z*(KGb8M7rtkM1TD{CLUEf!RQauwUA8iC29>yp?!VOMfKa^hi{wSJz&srFRnv zC)Vi0&xr_TzxOoAELqPiJNtQfz*;-nGQJ*t$pU9f_~5@Zyt({5mXL2d%5DA!FG8Af z$Z3<}^KGb5K)6+T2NSb5MB**!o`knkJ}v&^>=|)#__m=KXNf{Um{}B5U04QBlWibr zVb~=^g;+PHXvy+%KjEZIzNX2$=x~s=VHHiNw|P8`P+Y~W45|pK^k}AZU-IW#g@dF5 zJuht)Lt5VB*#RhpHj@xht{7)Hu0lYly}P;%yVCIHQ50!b=)cH=e(8 z#kl!05#u-&7{#VzQmTRuDZrmYVjo21D!!%2qSy%jI3M6#KERlsZ7Wa8XFz}wy;T_Z z;EH;aX;~hJ(&2SFwzeC;Z!OCF;$5*l{{AO>drITiNofp4DK0Asn_8sKR)JUzL5elb zc`uZL+&i3Qqn|gVcH2g4WC9`ki+y)czp#ovp0ccn0-ic(stRz6KXqcHFcvt}YVHzZ zx^G}D{DxE@*qRhgJhM*ss24)AzAd#MO#oC{c2Z0SC?lfI!>`IfS?yhA=kOnbCm7_AW+(ZB+7-Lf!H}X` z;F-h?!sj36VPDJm&1N_)Cp;rHYLcEl=;Nue;(fx+c7o-o(y}4_S@1CfNiKi-tFx*X z>TQX+$ve@$1%0?!kj;lvW$W~yPhAFD=H!F{Jk}{*RJ>R!+K&NJAi2 zUI#?_OzITrE2H$$B)^$7n35RHn{&CEsfzIWo+u5a{lTB_V*bOy`jw$rpi&qnW3!U6>7+!en}xO$o7P6>9^_IQ*z zn^a@-)k{>}b$GI9LpEco(@Q7b@5w;aWTyU_X{O0fEKP>>={z9P=7Blor0S&D+DSGm zCwuQ;8r%7IQA(!|uutTR=NGeK8BG|$NokGI*J=4FeO@XMNYZT@BD^!P&E{r`yK2F6 z4K4f143nRhuE{2n9|%eEm?KMah3)#!ZE=nd??z|R;?Iu!D9du4w|^?5^r;ExKCU{* z95d<|x8)yN%Cw4&WY&dp3p8Q5`LrihfB2Zk4fa6C=z zGO>_c!WRgm5HCLK;&9%b6<=xVg~F$C;A|ONGw)>k7rw!$cKZNT+qwZ>7<=*Dy0C2P z^7NzT0~u9Pv{NE@5%u!b?ct5DWxV$~VHZmIPcJA;CFV){Yunt*!kLNFtm|FU7xnIo zaN11(?@?#mmN%y*gjja%%g^fYd!a2!QYf4ICG0=SRTfRw9X}VQh3L88r`|uef=rDf zcM4<<$dRj#L|Hbpgu+lbV8yTQu^tmw&RL+?t^sd)q!9MCwK(T^y*LZh?zKy(Mo8kB zAP+m2jDByJ;yNnXY1TNkeD0xaBe{h=6d1E?jbw`R7rajzVlRJ^zy(whxVRRj5)Cn& zZ1k)lI>Hrr9pMmc`BM7z{f{*TFz;u^kK9x-Z)6A(ztsoPH_iqT59`zF=3Q@OPZ%tg zItZU7mKhtmw6QihkMcTO3((QM#tnLvRT|-S{c{$7TAUQ+Bgf}Iu--zyAo!3#0dZ#- zi%?Fr-X$U=B&BW-fU0P95YX9*)%cMlDj!%pskpPXQ$6N`rc$Uv%d7CS2C@=ov?X5O zgj>k{Dylzo;|BqiPy^_`R=mZsb=Q=Uj&6Mn8>blLo92o0_xI$44yA%0yr(ZQO}9cZ zn_}FdonKEOq==u0U4C1hdtchJ4OH|^ac1(yw z#W!N!W>)ggtn$LB3sl&SRH0$<_#8{-Qo&Q?%WP+-p*sb{iD`QW`>4H}0O_mi)gzPs zOoMrA5ZfPv>Oa|EWH7{Ut@|f$?BBI+;pK!w=OWvOs#52Sk{J4ijY@||>(QXy&x-*l z&0RKoat?wEVc>MCAlZVg+;WECi7D4I1{~`t(2agne3FXi$DRB`)`q9jnNH5)p`eE6 zaKDg3?)b_VjTEnw3z+`?3hPoIa@3!*lBQ=g=`dd9l z|2#V%bhtAeu_Kd#(|hp8XFlgiKgYqbr+3|bRX-Y()$kMcLMF`|YmRO3ig*h!+jt9K z1wW+LvT3xqF~>Biu}k~vzneW1SWjyfwixi`AO{DLbGS0mneIYQdBd(JAB(%j8}!jG)Au z_V^l8*5(sfT-ek8sHGNc?9(^9NIBDERT#xXAF-a1#-3kPz@a=T^md!dy{+gvQclE` zT6o#9r(Q@KzHCBxtJqUxtgOx5U3vJa&%;(zZl?Q<p^m=gIh@)J$+}MuP4K-KaNyau{p7q8jyVu*TEj z`e@EUMc{o6$0}+iETX6nE0V-8$LUt8Ny|3Pc4c3U?A-p6%zTh;yRKJIaUdOVp%uN` zCv*}@s0ev4Hwj}C@`zb(aZ+eY-uZ68goCI{s;H(m!J+)r)u0R!>JWEwJmLcF=s%s! z2v8k^H#odWH&BBaPiV&_TU2hQ8`c;1A5oCjtu67i0<+?4O?jTMfGD(KjaevLyC^(`(0m3T!owCKWny4Ci94p$F zqN4$$rvmhVx7z5PA;ztoB~=NCx$DNaD-3wrAq#`ZVg*}jYh7g%7x;LW3J)iwah&#< z*+Y_{;V$}YByNIr!_NDL1HZ!!02@itmXWo@g|{uoqY0sB_#K1CO*GAz)PDQj>F(tu zx=!snZHl{Ood8GVr7hUlKY#Dwk&TMV37(|ZJBvCupmIJwp*UP4l9c^{%~`G<)$){Q zx`o+#Yn(*?QLpEda>p_)Ec8vDS_)W>>3J%ivZ$yk*bCvs7<2Fv&~WH;PlD*X*Bw3m z*kngsYJ82lzPig@(KpG_d}CVlbv9a=uCBae%`z5)Y(e|7%thVUHMUHO{PP*0{Z(rPwMyYF#x|B&H`%jKKjr`B4fwhA%2TTeMQFu&B(bP=Ijv*aMk8~#7PDS|% zj!7S^gUwd9Vz#)FkYIT-*(Mq=0$sOZnaP(qAs|Jy;*?3HHkK}mD1bP`^Y$RihE$_Y zCh=6FZz+YX(~|~>Qf=;epI*>ZZi;Z?*1H(NCVK1X!AoG^t1us;(gPmQi@Ix^A)7(q zxTlP&Dj1K$&yzzsE2|O`_d~6ClcP_)kb#ux~3Oy`tc@JvtClb2tG)#T!c z1CQcvoc2H~NZn=$$9U)44q%J6&0EJz$dhgpWPzwKA@t=Lt@b05Sp0yG!?SsN!fRI} zndFtdmgDu&3(T!cjSe3)A^HowP2w40q<#k1nG2cX=8K5(0g* zsgT2|1dgKjiSOsGHET+u5e$bWftLB|>mc&m@!VIw6Eg%)qO0l=N#C2R%C{o6QuN$B z^oEkl4YIaV2?Tv{-pi(k$Ad(UwLWLNZZ^OCEKK79w@opy(B9Jqf{3LYl$YEO(g~kr zAIrMCTWfkMMD1l45Yd;@$qrLj1evX#KlD3Mo8tvt9rpY`RUahkFYlg!=Qkwsm!FTo z%lJE*|GD*dqUvv5`F8+5citGi`HW|xDppvxm_4HfIAbD*i>*yVqa#lG@K*1v>F4)a zGVQ6x%v0Os2j0t!w)81E+_44hx3})yhw4CA>UX4Xfcm3@&9@8hFSs6c7*}0;?je+h z#aVb+KfQN0f7j@kfwW04y1#NYws~uaI3^hoYm%_fU`dd>p}zmvz3x2$Qfj_}X57N+ z>3gYs{+^4OaIj-De%eEErSh=zZP+VJ?6^QHqIO zY&IT0d0@u{_d~9)?ZEe(!tpD1Bwy@u9+62RR16R5zY8n&KDX;Ik@(n?_n>zFln}Pa z-R}4{Yb@MEBZ3jg*)IwD`93dAczyKF=kScHph>YWY~6Xi^)R@0t?4BuZ9k&CmHo@- z;yM2Z-u-p){e_yQ)vn*y`unnwUv*SNJYI73h2p8wQE@E#c7m+9!4dL>QV zj3nM$u!4EM5l^*^Rb(qyQL;nY=A*jADP1k}B3LfOo`|r8fzI=r``m^;_nB^9O_%E2 z1KXxrS~ew@rIa_#DyKXzvl1er)}-l_eqr&R+nc}FGHWr>T57o971MuRZ+GBu#;s|| zH#8bIfY|KU$fniX(<)}Z-j4+|7N=&DS8sdgvH+G12?NO{wDm%XYFE7aygs3FU1k|P zmr1q`QoPp$qA|^DrGs-G0Um5GS!+3ms%^FeF9ux-R*o~|ODxaJjjRq*jXkyNMUc>zLi(iqpqrcT$dk-fBvi`oV=ZC7RDOV0Y0ZTRdM-WH8O9(r_J0hQb0ivWVf zFGS$4qg-z53=#=ZtsAH)KoU{O!6!~AD%No(jkIzq6TQqa>Pc*IpWa2&1h@hQY8~$r zorv0R`djZ zB%dO!QVFZ>RUzoSPYPS4#Pf=j=~jX;ky%c;CKT6aU;Xu%neolHwQ@c;Wyx&beP^<{ z=pC77;v7GZSvZ9`G&?X0IJsx#dc@LqBkAuWd#IFYU<$o;P&j#MGt5bo@7vy;gc1O%fFF z9%dxqS3KY*L}Ox*$}D$k~!#OGV6ce^&QXksGH2;=0f5KV+6#(P=%Tf>0uT?defBK(H^xqQckK|(X znNW@W)@BZSf%itb(B|)m8snXB+Sj&{Z+H=8+3bDN(CCOvZ1lM`K3J29#j-;FiJ!Ly zU;MrAOg@F9(+;F!CF|U3;?A7_rcx7amsD7cZ>k9#xK$lwHIp~%#4$V_>}{tmHSN~I z9y|AgC~BP~DKj!@KfY>; zN^Ds6m%@irrZyo3($|YQUqlS@TzvkX9X06Q3Y}#WT;^vUuRX-t4~%+)i#@FSb}c=A zy_kDEr=@lvH5ByIsAZ-OUxZ@?Ez?)2f**ICD;95vGjEMB`7tMLdY?mX+!D3?a z#i`S-GG;V)mkk^vCj4$j<-Jo7$*#o4$u|#fRrY(6p!y)BcdS+1PvLt1K_}hwtx;=r z(+CkNdhk^9OSUYo<@-XMr`(}-EprXf&3Mi#TMWFovhd4+UQt5}&dP1Z+8=^AYt0o* zDfkAIX05zN7}LykjV?Z$l-K;&!m>-J%wL_s%aNdK;Z(v9c@*z3EY)k}0Yh!G&$m+g z0h!4|0X$7JpO`n)TXMbaMtC7#bm$6cx7!pBzl%MaT_5~bT&TQz8V@Kv4mwqRsu8{z z^Ep#=sdqq4g^a$*FI%D^kI)^DbYLt2;1na!1d&>_prOh)5Y?8^;Zbl=1Ro{BYddqqmZ z;2#=Ru8v!yKH8&Gi+k$@3g&u41f<8MPQky>%QD|0prugU(t@zLzPn#8FmY zItg_d?1w~iB_}$ecf&X^*AKJJki*?PyuAi@MA^kwvl7#%P*=2sBcqSZ)3u(f3)Fs+ zD4yl~w(X)s+c}ryC*K^JXFFfe%hb7z|07+g-}lFBYqP4io8plgg-%it?Y2h)j-HB& z!}v@JH+XS}rsHhLTnk#MR`P+(c_QKqt^V~x>%+Ara1k`T&$fTe2+|ihR=>@nG- zJ!3GxbAzQ%zd-lK2w-S~^pxnia%1+TvYT4-o~#KnE2hIH++9R7(wd`P67k-VsguGi zyEvg^OgHa7;VeEk2;CGY1KMBn_BR?b)SJZa&iTbC>oDmt1&I);lo46WgwjgdK49VY4FojnpX4KaNtQt~OC?n!c8(x%vCAWi67PAcd+TCD)+U;EV!b+pm$tYouk zn;|Zzktth`4_twwSASMh_{8R z;GV1Ru55S5k{|^m?5POnz$9&t?L;ad;7#TEO|FHX)wc2xD*az(5O>i^I%Se{==n~; zoa-!#H~vuYDoYax*YloR0GJ{dCbMh?4ZJjK06OMHWjXtHqKUd{`a%DwB0PssH2wgn zKc`zY4oXlZksYU*bm!iRkUT2+UwFWH_^PiwN4?jpMp#w#z1Dd@HGf(~kK~?Hp}IqH z_>gSEfKAbC@U&e-!Sg=U>c92;XH^VV{vd;RRhCKB)nosh{T~+ers;3{91K(YH*E*m zUb9`g?+(k>(-Q*9(>{8<+rU(kALx)P@Iwum5szo$D%MDe-(Yxs_3XX%qg3s^)pM)! zGVsEB?9%rSCAX>g2`qEe4Ovl);}W*k-;U7T&GA@tiwBpxQn_WawP!c+F7^LWQ~&45 z{;h@o+BW|$yXF6Gl0UUzn%^kqKg0gnFTdk&v>)9aZ_eL;A_zugIT3(>`NC($M0Pgl z;_R`45zeN-MR@FdwwgrQr_LWUfGXq1(+=)T5%KJ3ru(=7^Voz4bYG-yDt_lV%ffGq zfb(y7?ti=;|Hb`3RQ?8Yf8Vk<|8mRz+r|8|mHw=QWw6J8^QRa}$m$}_6z<#WL=_53 zfSz#v1}ugjAa*7F+*hoP0}retPi$)XTqSP_-C0_f@pHlVaz9(m%<#W!9J~^B?e3>H z+>kRMSYsR}Y?{%{>SE4K14zd2Z8h=fn`1WZoqI;JiQPqN2eCNdIWhNn3`9)2wo zOg7w`Z4mdQH&XEaD&R=LS9E;Mr!pt=m>uLsCXC9qZO2rfOxbiCi;zG`+9(qes{4Sz zAbI(Y4OK|Rx3a@IE@R(4P7d(5lT5fgr?`+#H%uF@3Nrv)mhoBmK&N!Gk`8$*f$YM| z>CFOC4!!Ov_)w^qY=oXBZqLyK0&Q=i>#HPt1s}69Nqyyh9JSN{tx)S&6Lc6S)?7+M>tj7<1=$!cN4zmCeyp{=^rY@!Sru>ieiiA{ zvZ@3sHs5glQJ9&`Bdl~SJ!VCG;E`R) z+OqyC*JCQYz$Q4>FnWA7na&KLkKwq4jReV#j@(W}ckxEL)%X$@x?!2P%%-x*%e!Io zGxjA<(G_0lQ3+m6YZ{vp?2JL!5qBToVI1PGIE>3ebHz6OwAi2_t!|jsFU2mehq8F& zP4&tF*PG`%YVkPTRww1$jQ47q@miu0ZPRC8pQ$(djP%Phe`SV^5pUeeew&ve4Z z>u%8e%mzbE2?dsH@9Bv~A7t6{W3LYCl+@f#b^1;?l}qb+ zD9jFaE372@>H(LGs9XB*b}k_VqH0@mPv6?oe(x!X!p*j}iDidTxL zLUR+XvSHall4&ESi7e5d@UlrO1T-b_z5;JZ#a9$?n9{P`1Hk?7*g!1o+Im}+c>&se zj+!QVKBe?Bi2#FFlQj5pQCFcN;_=%1g_p|JtDTAsb=WL z_O#N$T0o-5#R-XtqA%5!P-$rLo);~&E#*mvvBYKoQwRpPDQ3ZO5T@%0nVg6&%r&;yzLYDfHf z>Xbm|=y7kK0+-40V6AM+Q^BFdR`#mEPd6|7&r9Dt zzrjMgI@}15NDqU$944%)d4nX(o7j>wLr~Zv}^Z@bxl3c$KLBa0o zN_pEQy81j(mN!zX8jD4jej%OI5jY3U=*^&MXd6d7B{3;mSHm$4-HiYZ0fhjw3ra=x z%sucGKj|C3t_~K-2m1Gzt&3vf8NhsXnWUC#*yp1q!5ZUU#g^1KhbJ8AcN_Z{!UVK=p|Rk|5R)yx3cd?^h{=mdPuu6JZ7Mh=74^v;<2u76{^~>NG7i6w zY|gT!g&zv{j=Z&whRy@_aB z=v4PLE7y3Z#ZW>~SrqT$vZJ21-@FI|i6(7P^<4O?oHP>~v|}v9MJ@!LAk5@`joz*; z*oK(jQrQ>xPr99$iN%;Ricp4fXM;cB`J0NZNQaYOX$6<6)6~oZ1Y;wLTV-FbyXq+_ z)2^{W?|R}wH1FP|whecvTYFMQjVB6y)uEk$xm&lEuopxpxWCcYV;og^uli1ATHs~Q z!tqvAMEmW6O1_notTP6Z2wyj28Z&8x=Z6_@w1Nk)^G#qSrU4NM=3S@g^tt#!g6oTO zIa|fi#EkMrr0puyMsXpFE8oS>%9%|C@1@)oPDbsByLJeFC-yR=v#XRKF8`66s@t!3 z*AD(bRT&lX2t>*&CAhJr=^Hev%BM|?M<>n{<9DZy$mT;toXri;|4@cOW>vPaksl1pQjuW{+I)&^C_u6e(yu?B~s04|%6dpV*#e#9i}Qz`jgS z;zx1)3jW7@|LL&6PHW7X`(4dx{d_SaD+SKsby`Wn1MVEtW%&Q0?5)GvTHb$AplH#c zMT@%>3*Hjk-62pYPJ%myLU4BtP>K~P6b(*srxce!DekmToYH;My}$dM-|wDt?{f}+ z&GW3uVltVv-g)1TjFpw%7yDm7Px@u?O~EGKb?J1fO_nspr>*V99H+zFL;!ETbFH^h z?n3U29RbNgtmwhw^-Qqhw(yFoP>YPPdcFRBki%0gUofrr%w3pCvXiX_6Igbzn?<4O zTkE%v&r(LcgdUjg)E>jJ!gZ}W^DR%QSRc&=7E(8Hbu%U)mW3(ESXXs>FLo{piuS*o zX*ccPWR3h-Hia}3MiPeV(%21+4t>d#rpKQJ)nezg4tEQS!7yel_9pxz(tTXzRgemm zM^;8(CviH!dFO1EH8Ul={@KcJQE;;b#@bUy^IMTaQu>xXkUp`Nv#I7mwRZ(NR*htp z)&!MoYJZC(qR$U&2X!T70jUGrnwE2PGV zFn+$8vwS15fGc3`AjMUdF4(5|fr}Sb3{Xw7xXBk8ct-FS+C)*j_e@?2^Wl+*3Uu7T zpnGe=LbV6oj#Y%9H5pcGt5LMQ@Mu9iSxba+F$il{^jb};McF)_Qsulvb@U5#X0rkL zGWd0Tj;l9KEn?D?<{>!U2{xC~EuS>22lkg`k?av)_K+Xg^m#`_P7|%M&g67LS2IZM zb+O-+&cqa2`4^h6k;O)?w=K6R@!*+`ne&KP^-w0{Rqw_swuUJWW&z<@GAljProT`R zr|4J4uVc4%a|yD>`B4K=Z07zoC85bhwVHV!2@*V(+S{X7imDKf)qUCs3wC8k^(j)< z8Jl-_1hxS%r+SK04zYoRa3o7nE{~>~N%l~g*{|*SzQeSwCgf&+DDJGI0D4t9KLUEe zE)TMMx9O}>&(`AaG5ad9tI1AMV2n~wE4zRwowsFpWq3#tl*}3EY5#8Kv?oF?Z6%fR z71|#As<@|lN+Qi&xq;)Y0{2HD) zqv6bBSj%3J>7+Gyb<3%3LF;C1-j6Q>7s%#ZJezK(l5bw2Q*irN%HTVwUk z#Y$X`^zSMX+=yP!!M_HDEsc}eF>o5~>abtG40*n!=Wf{BX8U1IO} zG6@TGVM0LQFIW=e9+?$Z@AA4HM6Tg^g+Q^WTJXVFYt?(}H9wU0C&wP?QWP?ZK&9vs z!hO6_MfRww8H$Gc0(3w#G(;PA?WR^4iIPC1dOeYxfMD<~-76Bs!(N>C+_-k{1QLeo zM|FG_PqZ|tYzESo3yjJN<)H0xT&|ON4NEdTpnM+iLr8OV!7B)d=LX8Elp8hIyA#Ft(Ht z$XNFM&fm_2bN{6HaceX9*&H=RyFXbF#F7i+BXou!@O|566NCLSWOfzDK{lCd;TFB~ zi@4ldp)~F?oU&%K1%15*T70gE!Q9779%#NuYrY2ZhYW_j&&k*<4coQ}4Y>>$O6G1Y zOyqi2^_*gOOpi(ZxLx7r--{-(jLLmS8CDuT0;Ft}@Q5)J^yXA$njlzx2RHQxMcK}+ zy=~?;tBg-c$;%^`c4JC9Y~gXo9ndkDAqq911kVtt3bLqp;k*~GQKHZE6r>Q<00lz4dF4+LM+BN~PG)kH)NQMd=`Y76Y?aRwX8Hjm_T*v&NN< z$Bf4jR+lngNwcw{vkmiB@S$$;2btIzMn{Ye<6)wc$7H85GrLwbG1!bH^9$>hND3&u z##*q%k|TSh5fQ21g-7N6Z;cI?%cg;Kr!ebgRKl1oBO#hfASB4{6A~o(@1nr+`QJjh z|01~mt?2R}B-eo(Pxt?0)!;}}yr0Oe%$=QBBbs``lz*FMx>2&r`C5>2a{3uMgYChq zY*={4tT9ygb!_R@z^9I7hemgaYb@+B`N?N@;R?%~Kc*)?srLW4Cnr6*3)fMxUoT%c zhN`7{wf0Dj#=EeVAhYLZ60V|k6@@n*#_+A!;>LdKu?VlVHBdR2Q%{lPwiY36%u{e5 zZ>p5ch>&$w4i>PCUjDM4FtI0Lqm4)mZwsw+);xBskocuKCqlP2wpZbu6@#$c`8wvU zL=W38HKMk|W)do@2^2Y0renVnpuiDl&}kk-AU(q+ZKPh;3n_~BLp+pD(`6Y)M6}eG zO8{BV8Q!Xc)=VusRz$}GgU1`MV}wr1#FMND3Y-?!D6dH&v|40s{`kBqft-?1&6e$* z#gNlsmf()xDiz`~&v#238q3(UI`R>L)axE&w081!8|0tqnDotE=L#s!xG=rp+|pz}bt_*k zr8E{vi>VxCB_fR5+WZV_U^^P4U--$r<>ss>Ms(4Z zzF&0Lg)?zEx2EbmC&WLyYmqyBw(O*MFEL~oXH)f&Zh;=dXK37GU`4-2TJzqUMwFso zPqY2-GO^O4Y5BOWSS?By>6mXk3G?$5!yGh`iDn=05)ydY2r3r|Ru7mH7~z2Wg+0&C zKbu%UGLF3KEwbox~zq?o z`Q3KqqF-U!d<8{m95Oh6(YFUgXG!DjLD+X#bT^4Ja@wAEcv4oN)*iXUKhi0Ny>9OS zca@s2n&;@!F<@(ND@8|vU?pdAZZ7dZzT7*b$v+A8WRaSP@GOqaqGmYPt>062Pg~N~`_HXgM<5I>;7j9QXwD$`qb_PzdgUlX_l ztPxs(d`|tYAks`;H$|9#!)OC<^%riU0ri6RItIiMa7?aSLqv$+esDXp&MW|94T{asJ zRmkkrV)1O^5p9j1$=hJg94qFmucoUNV05BP6J97*0bw9|$6Ck8u~eAlW0T)n&n~;d ztg|{KBUqV0jG9vvOM$;>@bW?iYwee})>sdtlkA4R#B9kTt?6@u0S`nj5VxPmtm#Gxm)Vbj?rq@c3Rz$2=C^@n7oZs4c_e`%n8J z*WWTf=oupaXVU&Z3)Fw_8~$6dos*oFlTGQ@@*e{%29awxv>7n^e-e@tcR_gZ{VcjvMiT&t6#5i`JcmmJMaF}G9czBDi%Q}cABx&aQ zi5-zot#nKoV>y`;ZsiK0q3U|AT9@L9j#kS`4PwKcQOT84(zKQDF$NbTyuJAKNCv3} zdJp@APS3PLM7p;D_Xj#)`BA7=5+ZujTQoeLa`WV7o+L6U_ zoE+iEYh|5FAl~1TB(z^4zU(DOsu@eQYfzND3wIU7X%tH!o|q-SFejpaO02DkE;^=Tme|}AGR^S_j^+X8|=`m?|?7L#(h~W1@g)~0|90oDGbn(yA%wK zopJTNeRI;i%=6=2h_v+{am)pzihX0&#!Ikn~p=;7+bCUQ=wlw~717$9Z=G%n=NFbx2F>4hWJ>XcEu?%rjwuw;jX3EbU^(pj`3 zsrP=Ynw2)A4fZQ#%AV4ZI41rE^p1lnDyl#O1B2aobxM8N+2`6PXG79F7E09UKJ{o0 zuF=C(%E2uLf*R`kGlN+s2kWPWu^&$vYx?)C6u8AXqxn^%wdc#?c~08BWLMTzDerAn zKE{_Zp(qrp^dbh#iVBz8P4#wr1uuWM+)!PV#dCgQZz6`KN=>Fp88lMac78ZjvQd#! z>+o*?aGNxk*LlP1%H%wGa7bOMejyB-b$w=7+1zivq*L*^O&Y0qLeknsW7%ID*^Y!( z)vb9n=)15fT2Q%~Z;I602Hd@ovv95UD{zfx->z|gteyuw`kxaz%3N<9593B5r$ zKQNfuGm@12Mt55wti{G|XkPJyd{mC-!4lO|)SQ{Mc(wpBmS}J>7-NblM`B0G+Ov3T zJI$Licg%V6eRpV8DrO_tqd39)5mJ=nM|@nyEmf}LV7ZpCqil6N|6@EvekUt)Or<)dKW?B2n{QoG&*&gd zLTH6ayz`gx`#`wGbJSvjMV3)SvwUzo?4RzVt>FoZRd#z#V}NvhvTIJrZ@M`NnE_e~ z$CxTof|ET2?voNOk64=>(9*D4u%66x&Gn_1B)P!ZHnL-0ooX<8-j z#mr#(dfqJM=P5_=g*TK@10sklPN7y zofV2@t;^O_GO}$?6?Z<>mj2vg(4zKyMOHQp-}&`)kjKw&diVAX&e_u=t$vN$ZmYw; z&``x$GhZ6%b%Dz_18-`i2nBa<-&X%-d2m{C{P}un9%V)LyXjbD_+{?hr_C1&*&@Dp z|6KOcxcaxH{J&mQewqDu|C(>pOKiVC-Xvd^ZJ<8-rusM6gOmS1E!6+;2Mhjt`I|$X z`hyi}Wa_;o|F-&2L-_R15IXR1#!KJ?rmWCd4j z&D6O;HJZ{)(zEvQE4(ap3o*hbMICZ&mjXZz)ePor6{`CXT(Zm!t}GqeGAc;z^Efj>k2@?zaQW^ z4xm1Rjqr@&RVj=$YBbG~MFsEB4I0MD!C!*lG$(A}&^xVMYIW+~e3zH{nwe#@C=4HL zT0Uh@t2s(pmfQF^ps9rZns%MZ@p>&N+fH|&E00jp3L#kOc6IHb0%wpM_DGs@w57A3 zi&50Yc^`6!x1L4mCfwzt=Rs|K8F$1jC7oOnvq((%%ULah%CV!EAL({zMr335oo(*U z?q*RUV5jGBXP)Fc!swk*wq%A<@LL z3d8|H{LHW8(r45!0bo(|k!w=jVZ!zXGl*+uCG%i7jrv(B{`pii4GwVt`mb9qy>A+7>NmI2#-Lc5+9@TDtN~RYwMI3I9~J6;fQWi|x{xv6mPOfVQ1kv6 zrA~1s%wty{XEKAV_>>r%yLye`LsgQl@mK{{%#EgZ$?G?p+iOAH>xVRu70Y)i7cPWD z>TKa3lze(#c;IpuPaBx|S*>sgyCr?6V@M~k73SNWdy?7@#hZ5;JhMJJcv_-aJ`25D znHIg7jMt-f_EmRF`{^&rf~jXodRkQ-$Az1QM>6XiTi!gNUiNtdcYbX-tA6vNy*~~n zCV!*uveZKrl;q-fcS6Dw3nTe4;kB6KX{ce8Mxl;V3vm`}N1W#voW8GowW1bVEJHjC z8THmSJC6RC(D6INdwpTta31+SO{QyF zdDU}o=t*i=nQ`XGUuc{&QTHX`paY-Zg|t#)#;UtHzS=hInhnbE)U;#*6v z4G=gTOM9rB$6`~V-{x+=Ys!r|nItaBZV*{Ae_ecV@Kf(eLe44Uh_RMB_ZOfp&=@Xc zh9n($GG ztwLq=peEeB>Xh~banL;uS`a>Gy43$s`&~h1TJ0Z7*dGFo;=TG0wXX!@8+qSR=`qyl zH{`{yX_wx~Q|0yO;l1lu-}^6^{fz6cUjDHP#Kw$>#5KehkRA&yvn?Cjx>NWntOYQCaZO~G&caae!&;!zfV)$WtIF(GqKv2iW(hHX zeGqUlFe`R1dBUi#g2HZ9H7M$-y*%SRHY#y=hxk^zFV{J(>nQWZIh$WZWshCIc$~0& z_n?+JwtIEn5mzj@u}J?eC;sjGkc`xkYc;h@ujCU0F)F>p40Bh1ooNXG4PSdzabG4St2ZIR zV^RbvAuF}-ZC@uAxicupEg^S_h>&qxmsNyXPRiNZVi0w{rV`qrix@g%9bNStxa(Fn z?HjD+?sEw(a^QqV=*a$wI2dID!WmztU9`|&iPPxH zpI6;>N=!%nonZsH(w#I|aUS$t)wFgYvu!Acmrg?8b|T?ONli_NPJf^$5~Zd@;MCL)4)OBBxW)TP@x^rJ^&#g61aFi<_!WjJ)T{`9G{bJ~vQ84hen zHkOru47_Z1kQmez`kpY8>~eEaKeTW`M6<&$=^{I3bL1Jq$8kZwRB>3jMT-Wl*gAT! zecq7wW&NU-`18eFWH6Yth$=Q zM1@Ec(f9I5Y^%z~`{zNE+3BMik-5JygCeSZK24s#YIg;)aymP1*~_e}GaVM!*d0X2 z4HK;ZV>swP#B7$-iX_t1?$@u9>rvdqm&3>tiG9+Yn|z1OTeX|Ik3x~V{FmROr*p=N zyef!QTh#Lcs`%sO4uHTd-COp@GH$g{sJ@(fzXDJD$m|@t(;( z)=}U@!C{&Wa{KwzA58Ysh{}{I@2!ia>^>ZPwwaiB>RAz%zY)4DshtqU}wUZUc+4pQ+8HxNGG}F&41LlD% z7Q6VlNj=r=J9AGb4VxBuThv9bH3bO~?Aj~a7w_o}otix^$b>Z%vLO|G)YG#VbRuGK z2`8_g`HMgU3g$Hf^ykScL>CGV>ekt!U9TDGZDfY-w)ol_DqCozpzenH-dZfJl~m93_u2Pr_*m4Mb_xaK+k?xL`Q)6d znM$oP9UqjaeZng3<|DD#sui6ny+vficuSaiv#EDWMU>>AjiPpxD#hZ3GB3=+=le%5 zXY{q3gv@O%eb4tOgjV&oi)%cQg-`AjigF=!hy8O#K{az5jSNUq1;to;jTYn7PbyJz zZ>xXYi#P!Xox9EHLrCL^P}KX2h?H`d4DM4*?)q%otLs+P=+2RQXGVs*>*`t*(G^Iy-EUvxYTdHRm$>pdF^S2BN-zpd_3 zEvL-1qhx}nIjV8%dC%Qb66xuSbS)svvPi0*Z-^3lWGCyNVt`PzOp!U#1FHMlH5~A5 z2HUkxHfuHulC$#jubwRn77KFj_ILCJFZ1vzk-tyHRiqLGSmx_W8ifOq1rKJ?NLs;e<3!HmcBf`V3^n`5FIm z{CI;~jQfT0bj;&H9|R~%Rhj)>0@x`>e@7Jy@(3>Q&#Y!T*e5euTJH1pnh8Z8CIt0D z%rQN1gFnP)V(~Z68{>QVkt0^wB!Gi}7W){XVTaA9I@PmjO%aBnH>R&boSMat~f}Xg^;3w_FHEnKaDlF ztkyh5x&ovPxVVBB0KuX`>Bq6mnKTw59@29aK%@_4iAt8#YV0q2H(@;<1j@A-dthR~ zu#sGuJe+)rxu_y%a;b^3G59_{ZLw*@4v|mNW7wid(xM=DFI02{)pz@HnPbLbLHXJ# z<*~{3l31TRA(p~3t(O&+3kD0*iVvI}Vu`md?!!Kv0sehX4EljOY{+qsN+ zMKnc~jFnNW@}J^hn}1fja%+hF-ttYE%Trx#aIR+JZ6ivx_4sGgzZ!#oa`5nxV2n6_FQooWb zE%boj%|G-%aG!_?;P~WVC~5OE#wm0Vd$J!LmQRiw5XyON3Jb9o^7;+ZISnW*MFwTJ z!@*<9`S!n?-?;yUrUn(`^zz}U+wM;~D$C!aC^y8S5CZ6dZ-dg{z}!9RPc>GnFJ|Ck z+yQ1Q@_vShA$yHwLPqjT>pERKx*xj=C5L-0ad0sn>(+71CGDqX#klY9^wJSk{|b0&nkp*1k6EeiD2Bw!>5@NXirs(uVu3Kz-w(@GrE&m_#P(m&FYU z();N-vA|N5b-r`a^?neWb)6i_a9nqOq&@!A?%w!u5vx(k{J^*n9aROw-~oQ3sGN7w z{@~*^)@&|zEKp#>9I>i{ z-RD&e1;;~*Z?h_TB%px?aQD_SwkDiyL_|=Wcs1<%C%!XFRb8`_*>4v1t4IgYz(=D* zmWL|2?t`BTQ8K^4v=XjZz&oQWz2$u+Bm5}l}r`Jn3dU+pWIjogH0Qe7=X zHRh?jPsiIUej*w9PVOx^RuaR~Hcw5eolV%syZg-FSHY}a*%8o@8?XLs|UIqz64csESqT{#E`dg$tNZvMVXda)+nB|E7_g`pw=l7gWCoIha#uOAP(Wt`+ zh>zEAz62a69MAYbKL2`Nc6opO{oc76pD>ex=z~h5WyAU#WEXYd@#Go7m6b{i+Z=fG z^w09>Q*_j9pi4E=012_}{5bM&k-f9JX(Iny&nUy<=A;^N3 z+qLumZeI7)&Z+8cvrW9jk&oM*%F=?^#($bd*$AJwIA5+T3Tz?DVf%nP#x6zqnbK|{L?G91?S+kQRsYJMoo2rQ zZ$2Gy1p>LNd8OS@wZNc6hEc=Ko1&u}iLQ_+?@gEv3Ynv_rR5OYqg* za~g_BpVKvp8B!5ZV+uoGVTJ9&Qbo;blSc7wrVqK$WNzOf_&T{SO}RqZ$@>qPE_+@< z_17Ec0S`;}KORUjzX!)~mSKeJ*XQ^w6u6TlX6iwW=c?dtpj`!APkfFQDm+=ZA%8H| z*9;nyMssQw_9cycB0ws4TJ@X9mE0HeO9MglRZ@LFb*Jgkbsib&8DM-TA2qhVt*p;k zFo!t!CkQ2`p-TQ-HZkD+@NTXypme=Wv$9W%SrTkZt#szJ8kPoWEw1gq(zgi)QNRtm z(y}z4G3$>@IKM4rLsTCDsHNNTxWY!mpS1C70Yz^WW;t06V5STX=79u80-K`V+ z7WrQ)!E$d9gg-QY*I*+*!y7F9Ej{Ce@Dgv7s^ng!K~y5R+3GpoNa8klD`ln~wm-C$ zFgv~~yoEq9Y%rWYS#oWR!MrG?)Jfy?bwgQc(MeG3CnMS)Eth)Xt1g=DqIFQnGbS`z zqbNzYIE?!jLokXK6{l+3`fQ9x5EZly#H(ys0hA4&A>h6elqHgtpX{L?|BMzdM$Pg2 zbA_&L=IWU2&*7kG@2CQwN94Z7EkIdLuUqIaCwNX zeB`*BQb|KHH{KUUg)l&C>Z54-njeI7h*5*=T<(v7UsV3!u7K00xouY=TxP>_`iu7Z zul_Ae&)WTmj)T%28B~Z9a~eMxtp2uAD?GLiHPKQR!tsB67FNXQ0nlj&c=i}Ire}rJ z5)tlx=N~M8)pT2c2$}_4ucvtwcttoT0JW>Qk_9e}be+eI_X=jST28AlwthzLp?2H) zNlV4y?1#_}SbSXDW)+Ktt6vN-H-&k`!$!F)f-K{HjD6J@olxhk0UP~DYrx5DCg^bA z?}kQLr^J{E5_wbY#=gTlT*C4Z$jZKXtPtP5m5KSF&ET5RsMV&_q2|>>$60;>GnV6b zEs|mN1BZJ&|BgmMssnYNNVKZtUFc|P*@(;&F4}YC_+p!$%n~;(9w8Hi+ZL^q=YWr)~4zJ~+*#J?~+7>4n+&yT z*f-Xjetn%g4iWkMX#XRLkZeubF{mBqO?S+qS2FWnE$P=5JOXpdrDQ1vskn8L%z0T> z!Cfhi{>`FFC*=IBs>vFDlq+yMA{DT#PB98a*e-FCxbh6bO3GoBTNB^4JF$V^hH@&U41nDv)3ZP z#ERXKCi+wQxcy%_mM;TcYT*NS^{ilYyIv1N1MwmS6SsMJm-5_P$QW~pZq{{m=Y zw&9mbVc%#Eb!!PRApdDI+CL_`TzUS)b4@A{4?$>7?#=Yfv~Zp0lP}IR=<-QYdcCQ4 zr_xet$h?9X|EgAPsLRI>q*_PJj|7x+mMX|>Fl-oG<8nRAzexJkI^H(qi+jya_HZhRnk-Bt zH+JRXvHiDjXDc;W?ZdLQuwxj;=AoH`g>HOlQ<@f;O$`9eBhw&H*+Qv*a5fVQyx=n< zlWZd2q8(t<8$#>5e5iJSaC4vkrcB}Ifws7LRrjvx0qj`tzA2=(bFf3h6SSfUqW4gx zO&O!+IdSby)Mni$O6?1W&MXGjIUUvfCLA*C+s{htlNzf9>ZN*Z0T*syMdwOA!6@Jf z3@>_qU*he^IEqNM)$ed(IauS>Nfmzy>rTOXR($e!Spum~h5!0Jz3&g-HlZi2jnB%I zyiMunS1J6GHd7zRy(z6oHge#=JON(t^tY%s6t|x2lz9RDUlYI$?8LB*6JwH_zy=Jw z*Ie}J8er;H$%pg3XVmR-9y5X<>DH5Ky1}NG(St3GpcqvzH!ayYqBYhoky@L6n_A9> z)U})%9@}+6ud>&6^Poq1YfKhUKQ6CRqME_@8Tn`k4z2>NjZTdH$W2Q0hs56Ds_>5- zv|&5cSqtW{wZ2ES84i}rZgB!=#i68|(%G(g{HqVYTs>KD<`;Ax|s`5ZK5Hx+gaYv0RKd#vDo!NM(jw2OMf3d> zplDcB4h|?ETD`a~1X3s5D5eoLz1(tbt$?wwl&fZ4P;2_V?;yW*6+c3UfFx=^(!EKqZBqOCoOrMJszpGLf=C-d>?Ocf9NnTU7G#zPPySUgW{tz=tbQ!*+sI!pYdX}p4s(7^JhIcaCLRi zL%8vbx~%QmG<;cYe4aWr1!FDby8(^chRzi>ab{=;S!#K=JpDprlNV*#jHxRfcV+tk(jvzta8l~eUD*j#u;W;l#U9-DPqxFB*n0FS(@!|ntI@ zCjiW%;MN&K;t9C%JJXTN+`*oiyO(yHP{~`{DM6X~Kqg7h$>zepTYiLvSNw;x29xRk z`5*8jF7S8^;x@T*7kH;X*_&%T(!2<-ho@BR{>sM? z)xhJCkg$)0%pz`pMm5-Y+`>WI$#D#}3Z)7a9p#5=UGtNZ5-~a@;Wh)ld6X?1jA%(x z*INzV-+vkaFLaAj#FvF0BgZ;l>MF{wZz^hiL{v2kLlJeO&jyUlUm_;!AtZ*nervj9 z1)%0#>fmC#tHA|L3c^MOCIc(I;^`;JM0Br%qt5){gsm0A-cXVEby{>3~tLMaDH6z#_Y| zjU(`jn${f;Mvn(>C>EWME4(Wn4epUa(cP+BC*s*d|sh1dLlZLcG+F+o;af^>p zhsP=?l&OiT&Z6u9)A|KqUa~$Uvu{EMGAflzXKUY;#2{5RA7ju!;^Ts(PS(^ioZhI^ z-P?@UZHbRmRRC>RIjh$jUX5jPE=f8@Nh(pAsqE+ckg#s)phuwY%AXX!9KszQApNR6@48ahMU;Xw$L>mf zDrJeJ^LqFLSQ(-ob z5-Y1rDJE@ugQwHTbrip^Vd>ji6=rg-d@c+KRa%0U!?jeZTpT=p1fCh~G+Lzt3B zBO0#`l8+8TSpfKf(=s_%7t*1X%6zT$L;8hc1B!%ioS0ER8|@no;;2M-Go?sR%3GVr z?)zfs`X@<)5>+Sm`9+4~`qtCqr1m0i``EQqZr2XIGFtjs)}J+6c+CK^uHj^AAuo*i3 z9X$V$$Qp+IDDe3Utr}ImU$VL_d;Qz(Pxbz3%ykJ0%oZ~4y4om*EY%gaaqGv-60$4V zt;VWEzHZb%l%R?I-A{PUIAQJoW%J$Ekiu?EkUztXT$jm~iH#L##`SAKy`t2}D=CJq zU*>@s-Pzrpw=OP(l4HoAR&#UvnQMaSZ(M{WabxXc{%~bQa`^bB+PD_cUufkYDXT>X zuW22@ZI*mf3U*+z6hU`+GWWZ2Xr}>bb7IyN0%{!G_DC>48!sodIIOw?hKy;;!wzJm9>2hKl?)zf zKujX<3;fNO<-G!(a&yiEJA}e#u(ho z^PNdbn8KQH5Ko;})K5t3TG6+rtNRKSaE3axlMAHpI4v3KM({Zgwp7-9qeoCC!PXzJ z*2TXY1LLmTcuerA`1^TNSK-&68l}~bCdobUupcoYA{G7IF*VazukApr-mm*&335YM z@zDKjHlC@ej<8@W>S|hutZC0DV)Dq8wT0;JmKuZZ>AR*RxnbKZo@tM#vG-dqU>?3V z6>PVPVyj&TH$QOA+eF+(lnuAzDu#)1FfEw`5A`(r89tVP42iXCP8aR7og#ZRUoaau zFlLmX2gPoAR6@PLlb%zq-S3Jz!b({MS+qL`fZtF?SFdX1o_UqcUA5)(WEsE}$2p`;3LCd=7C&92IW8UnvB2UNT39vmGUv>ZR<^)iNrx z8ZrxOo@>?vit6UGWE7ttj;G==AW9u*Ijh-5Mg-C>I@MOV#5twwifR!~g!0bOWfr#7 znL;D8(`5~{JT`KFp|L%aR_`F+t^_TXTo(onx%`YEyor0ehR|F+J-fcpA5n?%B2HY% z!>)HvO~B{P)2wIS&Pn?vLc9MbivQOzAAHovRsXLj$?0FVIWgpQg8^Mq*F2X)dr!ow zT(*$^Tl8)g4eZIEe3LVP@wX}*x<;O!Q}Yx8zoZh|F-K|@eK(iWQP$ebN3Z8Ibq$Y( zD?+&Y^Rl*fm|5jLs9RKXFw$@dKL7}rN%4nrxgji{1d^AFetgeB(2|okO-S%l6Zntz z%L^){ZiucTAR1H`Ia`W>4r6w8MXLF~Bmg-O7CHqFEFx_e3p1tRkW6Ee@EPlkseV~M z%W6c>GA16UTTh$F=~c7%r|^O4BTz&z<9z(pnb| zCj7(a4YQ80>pX86@7Y>s(BO$5`T{Zy*yj~J?eM^a^D^&>4pzVu&+C4RT37gRfx%;3 z|F>>9#%uS>C|iL4^3FpcmjAnV-gyBuuygfE2*eg6j0mLo4ZRr7U%Z*rPV1k1VMQ1y zzR8rE8J)h)QT-(9(x0xpZLXi~EA74F#lh(48KJ`SL`$E5fZbfj0`F-=ub42~GC=0A ziNV5NI+WZU>x&{8K`NdY#j2{M3Q-ui>~~z-nUVywjr21yMpFF#PW_1zn=el<){@9+< zlnlf_p=g~5=c}sZ1afktXiMTgdJ zGvdNfA(>O=pWW*}An#+`YvKvip^)S?uMX7;JA~l>dW-wQIfX%Sf75`jP)ECfMg!y< zCOaf&(bl>Zp3{^}*Yd(E-hWa>$0&|^fkLNHRYLcT?+dlVWlT;$dzJJ`+MbiN6{kV= zLb+vneZC%AM??J?iEQFc?xFmPF7T5TMSCeyKzVLU8#+Kacyv?FIDk7yrK1eYp93bj zTZ`LR=@r6oi;Vl;rmQ5k{szvS2WGK&oi8Sdu9(i%z+{8(F$bS)3PDdr&mk>`76zkE z3KkV{vUZsjXe9D>`hZ+=_TnBZLrB(jkW{3ll4gM)SXwkiP^Q|YC|*|@srSV4892dR zdYwpLqm`dSEgCSVXTYBy_z1NhEuF21>KlrukF>~)LaDbdzj4CteG^CG7 z?iC7OlxXtDtVkZ_rLpaTdhCX9M{h}TJ)-UKnB#!IcV&xZeox!|m{onRNda#uOdNjd z1T7^j0Yp&HQ|V+2afmHN>O+)i>ha%03g+im|1S@!eT;<_jQrbcx9&Mv?Waro<-gF{ z@c%+%{rL88X4}9S!$t5-NR--j_Z(O5^a?b0T(ng#4F!$Fs>~SgvOaJOu@rke=(CdNsGtvVB{ptX@p=#HX^FS%AXHHL)7V z(U~Tj6*oOpJt|2)iqR%s?2R@Fvn{Y$h?82^K4^;djh;~$oNf}W2d0w5Lpgl2{W7^d z#K9@FcA+lITaURrt(x)6%XQ|sMd=^TGMN={N}KA+MCk`PdQA28$eAdC%pMd}pURsP zgoVz0*UaXm{zAKp(C*=?v>Pj<46iWzvHX!$% zv}qI|Sg|#A1uhdeZJm(U*`b4K8g`Mj9|Ioy>8K%`tHERUUX2R+Vkex7&OvSprKeyt z^GlMK6?^?CVb0$N4#Gfn_U4WiQI8lwbYR z{f!#U|LFH6mHE9F>b^z)9MO)7od0=){2w$>RFbPd!mpRFIlylYCs9Wa`%?XOvU&ku zP5wk2y(zPg!n6obm{uza(}MARUY+b?9`-SijOgt>LytRUeIcn6fG_jGuJr53*yQEd z228yA$O3`N@w;HeSfe@XP$&WG1WSK1ML8A*QOO9eK-1G4oSAD*iHUqf6h90KUoLbZ#xR zt(jQz5yUn#9GCs;$lM>{_Wv?J{{@)Id?ES&t}IBmZUUEEElDVtA7Jn=p1_L5%9TL7 zD<;I(cU_Gc$_%&S{|{yF9Tvs%t&Jj*^9(sk&J2=45FN=GB!@wXl7^fS)FC+JoIyt7 z00NRTk~2z9L(VxTMf4l@-oJg$x!<|ZeeQofU0q$(-8Hppt$Nq{9+dyYnXq1dNx3En zDk4Nvchb5v&0l8iJPr-B5_5V>k|whr+P?t`NVMv9>w3;5f%w7kH+})%^Z)M5|F1KY z*KbDu#N*cfokg0_vxsN~?q2~p(*FeH7H(b90Xb}2M8bjZr&4FhWx$Ekz{h7YYJpb@ z3T0M!@XMbs5yH$DsgzZ(lQ)<>C?J*cPaz|L@w=t^7NrY?*%{|i@tx4E9_2{#d#*|i zLRWfCO(ptN)G%Mx(lYv;k4*?A0IU9`OM)PK@qK9V_Jg`!zX@sv@gDWA&eHnF#jI`e z={iLX(aJoS6QEX}DsZ>B#ymw2Iv^NmdP$6%CJbV*HRd7M9g3XCiqg?$*lg>Uyzufh zK+aM2Emumgs@K!I)d+j7Jf+mbWIvkIozjK|^Yo|aCA<1YYwLH4pxA+sZbNJNWhjzu z9^}FPgkPn1nZ=coj+{af#lGAsF`$@G;oP6h`aX%swJ4K zeq#)L?y|^u^Lp|6KKfhqG?)oR;tz0+S7?*%AoWTI#Wv z3eAK7RZ^RdFB_#iYLv28nCbgV5Yh*kz}Si_Lx$5&yL;<{W3{0+|5;K0w?zlf|GnfE zdJ_7^eUzI0SV3z(i2$AJiM*=;{sE z{81+l2xfSGV|@Q(691RMOvyL<*I>peS{MC|K?=`2iDn=PaFSc7p|dfiNjQLT9Wb)SOS{FHt1Ni>%2;X@PG z1)DbfhG))vfwbH{tcM3bC+XIWEtOn{iPzS6$lp4r-{U!MTPv+E4_=T~SB@zy+fs@U zr%gz~$U0%X4GVtl8>O^%@}5Z~Vn0VJ1yyl^h@g^wDcP0#^{e9AiJI4DpFY>)*Vp=& zqiWH#$T5^f(p+IE7Qiq_<-82H$OP9!JT^^|mpywSS=@wR`{J;2M`x8gO#%R5qqlvz z;*@rgRt{ObZ1`&N4J4UkkSdxSx87$#C*I2o6oxjuw2Se*W)~QG;^^PnDD+d7kN!1Y z-3?SOf=0-8tU%y@aA$$12YMSY@0{bkLSq zgjEd0SNUNmcep%v>z)mcI4!uRM z9@h}^<3Kum(04UxsopE5s0nIg@*K1`Y~JzCpNIF6!GA?A`JrSF*n)>Gq#@0jaN{ykJ{c4)(0ArGZUfj+N8ztvU@@mPw`&+zWjEh-lWU zb#1L1jXf0ZsQenX(UanQw9Y;I@nGNgj9g)dzwHpma^JMMT7dl$NmFaC+{?E2ON%+u zVI{rhT?9*n`$ZDjL7Uz>1f)$5EtJL{nXu)lXCYBWx>UAwlIGn`pEr9JK~^h>7AP+6 zho4FYT(WR$n=N{6?zq$k2qg5^(;6V1zUp!{JY8uCL5@4&v{-f_9#_Ea*vwmy`S`J2 z&TMR;Mf~(@aSr7bn$tcOi;qub`B1EPbi^}nc*czU;G^``%ib7f-|C*sh0=MX)jh_^ zKXq!QVAPK}=vf;h*=dgx5&GzhF<~jszHIY-9L90<^QTliwC-j=cvpxBW%n&?R(xqr za`iwh_HmP9@Q2t<;MN{0FFG^QH5=qaZc|i+vk;OsVy2@d-&WOE2s8CR(y}yIs?U8v z?AXyIM!WN%}%YE4!eX++= z&0@5z8rIn#Hw2=GhU0;Dc$<&RngVuY-{hCq-AN#fr^pYnCbwA>xNJ(_uAje1$qjB~ zEtgS>dL6`o1UUz3A5rNvRQuIoXugL^e{zjEYnUh#wIm61z|38@wi*Hs!p;ox4Fn%T z`+HS{*~S|hp|RN5ZYUQ=&<}!Ep-&Yv-^7Q`X%%Z0z&(fTM@QlsCAmD}!Byg0j|o#= zCQ^yxKydowrYOIVvT$hZU|vq{&7*2W zbB&pKfHA~E?d=3Guh$xtHj%&9%rHDk7Ao(`qN9%}elsXGY}_>Bj21)h;;%M)FRK=7 znNOS+(SQ~tB6q51F_WbuYQeKP24Z?W4Xw?owi}Cgx-22Uxb8K!aI~dTWZc} z3EY}0ZZX8mFg&imLh_~#MxEP=3>&LlnMGsj!Kl5p)gq7>m{z+)q&=%Udx(9{sV#;1 z1+9T7Ar9g5%8iFyJrr(<+Gbi7{e~`KFp`RCej|o=06;>vhX~DL!P0&1XcA{YVhSO%yV|vq~o;-}?A(rYkrH;hQ&)?O9j`Wx|{FICgQkNRKoyEpV z6HJ~7e`#j_j+d50t3XNDUM0MGG3xtkLSA(9XvZ~v*FeJ5{))A+*Zv4ZfHl+*6A*sm zwksQfFX$LqRvYdY1puj1-&y{5z7FG9Q*+Kp88xd?maT_;ia}>a(OK~Pby6! zzs42Pd#b5s2$(!29(qJu|Ak05f;9vp^=cK-8eWp8+1wH8f|bzSVD9GroT?MWnHzOB z=gyU^mh!6yduVO-;Yyv6WG)i>NT5*w_x{F-z-OD#s-2-zuSfxECwG+w6(NeIW$P(l zXIRS6sn}|7@VCT~=g(hO+Sb2^rZRq4^YD#*8AZr4j5SlN-*shr!wm{HW0~(+8mL9p z7koM#eBG-UAZV~eLR~cEf{kV9)q1&KHceUH{XDaJ*p|O&UPbE9XdRxkUJu#tLey+~QPEDbeS3njd81_zZP=&ayM*t43p+<-p zZhdKw8NeCnlF(*Q?@!HpZ6{4b((g|W>c@bdV{zT9Uc==5$yH^YBRlyg5K3@c{}(q$ z3vP!@UYpy`f}S}1M&Wof3iC}J$UZs&PHy!k)?4-`+EG3fzWfpP#O=*sTa9aPj=8Ya ztww9m$Jm7zXQgAf7I%e9U$py<1i$O%l63uBQY6p#{wg!zPVPa&mrYH$=AV*Zf7SVa zlt_~vieLYI`)o4r2lLS5OQN8)-x$e{m=B)RpqBS&?s|S>@Opx0zg?yO=YDDCz38%; z|0;W9UtcjPbQ}3gZzuTkuL+kINULk=pT&Q5&-~x+_1?7UxR8|23HOdvtrzz}w@FH#53?*1BlPWVqy zBh-)a&+>^B1ZxuxY)*j* zsIrn$!7yQ% zW3ra;k>2mm`o-ZwiMV9#M_upugnGvJJ2my!z|#^QA)8Bn&W=8B-1s4g>s?5r;k?ee zO{XJ{+a)DmvTj7JQip6fM|`OPr;|2MS-hLqix`1_E$EoQH3WmI<&)nZ;N1RCVi;qY2_?jOvDRgUt1ve2h z#WFeIFKUeg@dDE+n>aF*RHK0a`MBpMzWM>f5YXoCE@E;yaY8yPGXJ(ayt=^jLv5Qp z2Jy+F!05Zm4Xg*>4z(c?*0I#897*SijA@XlN_iy)EXS~BR;P5Jw(I;GO~`|m-JjDN z8`3n5;S02Q?I_h5JUTM$i*mu5y<1U6{Z!>AcH+k2uJbbt{pn#k^SS^I{ngVJ--rr9 zmtAeo8VW)CMynwIL(DZDX`>nut5VCA#*Rg71vMMY9yeZMpr`z+i@UYqss*ZJpn1~z zY>YMM++Pnoa2{#blB zJIuQGU3#PN!ds8fCeeTgAszU-sY`T3nqx<;?hduftje$t5b@sf>gok@}75jh)PbC&qy5MsGtiiN9IO zE}e26ZkKDuN*p(}3haFk*rHGrX_VE_j55!<8`y|vnYkJgrJzK)yh>%cvJ===pmohx zVAY`|&Qw)df?MW&IYs2hqc^;3tV|44GSMWz!>)0eY;c`ZgGJ2?Ir4jt@Pu1FNO%|@ znID0^C5kT@eDr)utC%MCObozy?bb!c%5mfGMo0X)MkIR4s715+$s1rU>0j#0wh&boc(0b7x`S~Fn&n)R*{l3OM>v0jUYGfUU@kd9ndrrJHq zeG|MYQ5nOUyS2fpEX89WQ^KF`oVXE&UamBu z_6U@XJt!|!hV59Y5f=3x#=8PNVofT|+)mQ3cpq4CULSWWl~JSl7#L@Bn+IESbfU0wo9D==A&XLe@NX@W{7MU&D#H#c)zx z7wevY99r(G)gDEl*;bkmy=DTx$m6T^ZzrLu|t4R`V3Os_>56RAt9UUkws}c)YBt2D?!aWwV%0 zA&X3)_e<@V&c+akYtY1yimi>t6)8eM*7llXiW|gS)8+^J-rS1C~u1FK!YA*aIWpQt$WYDV1@PnAmC=P4 zzcC`6VBHP3{p6c_2HCvE1mP2h#O#=c3BkyGH0 z*^D5u6pmbh5g@y_Mk;-fh!$aELW(>`-C*V+7+S<1ZFJyz$lv55MqJ|r@o1(kr<0!UBiBtQ`UVxq3VjVvK014j~6x!2mB4u7{c80`6}O>lEncgMvngN zPX#&;;G&bXFBssb9lB!J4{Q%n&-Tb;^K_%NukV=&JT#A)~r^ z45^7L*D1VF_nvV))GUC^Sc7v(O_`NKP~TbtbD#1>iDRT8KJs!pH2cRP^TWv*P2 z*=yLgkLpx*)6<11BO?$TZN>zYYIMEgHdvd4rU{C5d#=6Njc0YdEoUbh- zT>s&OOWyexjc4_M{|OXJ%H&-`)4OKVPJt>n5@S^nFt~kG2=d)yxhEMrUNwTB9p;N2 zD$nkvLMCp$ITVO|*gfmA-X2)*uAtN2T`zyEH)r=aDGZxfrGvsHGa^4cwxVX&=cFvmjht4Cuz7}DwJ`cfrhjYonu&$l~Zik^}~{%(>HP7Xy8`)a*SAHGBcLC^2rPcYsevrs}yuUD6JZmo0#dp9+C;jcjcUm3(3V@Qzr)d z+$zGfzri$v5x)C|Tt%b6uK7sXSTY;-}^8Spw3BJzg}oT~N+@JXdN)&BeWz zJbS>U&E^Ks(5cp5Bu}XDzMG_S1o6toPpzmBWajazWE8dH2JY~B1ri+peMdI-b-^Jzni4hz(5nMhCBKj;xjSa8=j;eNhYNrAfTEeBZm zRT<9b9K3nzXL9mmxk#EcWC>}wj*j={KH>_PN6+Bui633Bq#ZBk5~SR$t#4FIFX4A> zG>jlOm$yOZ7ox51>$pzADb6e;Kp(ualI zj$g)>d5Lpx*|FAWO0C)yBQiM`Z!1Q?`Q-+y3eS@UnIkev8hAeE9+b4))*}YYperTs zIX>zruqwFf=wJg>40Ea?zXpT8hV+X&_uJPkqC%`KxlOMvOIofGk?yU1?;fFuM_Hva z^M~Z6op^~$BMQ!)M9H(`x|AdKVf48r)<~In3G?3=?%KY#P79en^rsRv!+=pfk`EF6^AK-M$FnaD~~a)dA)56@C3 zO>xoUGB4hA^JGBe#JnAz0TwJ`FJ+-$hf-hGxrEg1RaRt3*xNLBK31Ac=Z}ru`exl7 zGI-gN=ZJ&^8iR)Z6+UT~?q}-WnwQH>!MMWAkBBlyBENb-3s{~BmP9m{VN*mact(C& zF}(X_m>qxDxkk{VI2qTWD)U{)xs%Pj*oarkjQ^UcT6%!N_`EQjNig zea4SPZ)Z_K4->eZvhV&HzL$K|jQs>IvL6JL}|{ zLZ%F*6gMoCm35}QU90oEif)=&Na~8IuC~)yFmonTyw_v;riKt++NPYz;UJ&?g`klu zl0qe19`tA&7@JRl5@J8u+%K>YN1^B}XDdVz6XiqDo72&AW~xwK_vIY1n-V>gd}Np6 zN?W(JrE*hDDSS1-IkG92;7ne>K%=TYMbXiN1}=c6w>`6!=Spx=sjpsEjo2Q@^9)C< zikCLjm8J|FEhpR4?S*1y0uS%T|212+I{u|jD*B>&b4AF9)@Gcayp4TFdabcou`V2i zK2j@G2^OcS&M;I@xJ4H7wV^diKJ>UoMad_qPPk)@-pH4<@+=g0U-1cPXr2-xWupPwNXS*Ro|6xf zWK;eDJkp4{C5ZJAxZPI{uC*7{7ws$0{Es<9hf)5Rv+w+}AI)tO|C5{7e^K`j&fp(S z|0oLP0fG(-F`jVMFJ%mch+TrL-Dgu(>>(qWZ(rPAT zj+UKXOpGwM>3e*&(1e$w^a8FR@k5dCa&*H#jq4m+c3JG8HA*q$N64n3msyd_IuHAb zSP+SUl`qW30fi0hf?pfOJib&3@WYh%+xSbRBbxYQ3B`ZC)AnT25;)2A8(meCB2w{% z81&LUi@}ZQX~*20syCON!k#Xzs`i_xCk*(Mw{cDqDNs_v1u$hHu}AL&eLcA9-`;4` zFVc_;T1JLVPUvD}qUUxjuv-n=tZxZyOOm=ql6+X=RT7)Mm+`A8N+E7w;&sTaVa#4I&c4ZaIB>HC5L;rP%I!ZeOPuubGWm^<`71rQE$+bYLocnd9&m++Ym#PDGY>ruh*Pi z8$flJ5-I$|HW=|r9#!FgtH-#7{{%;)3d=x$3tiyS$Hy0C5Z!Ul$7@scKLrrleNfXGfn2$;bT{ zDnohmh-{xKlcVcED!^KkfugEomJ9>~yuuA{3avUBG3?xq30^{0c8z=63f6Hq_(gBq zNCCPe!lJWdv(Ls_TQ#)@9dsBqH?_&Jiakux%ug|zXmeSUR zEP0A@*4D8bFxD&9*L+%9?c(M=JId9dH=h{B%iWsMGW-k?rPlc*2Rd}k&S#UxpsRZ9 zq7sJF*`KzyG5$jy=sAZ9k_u2-L(F{SkFJ*`A6DT6#g~0kN~D2qlKbG__ewe|IW@!8 zJr1U`)Y8l?`@Nx?bsua+2o+@s#H89ruVOe5|~mSp7LG51|Q~ zchd6r|Kj9F@7tc?Rx=y&BiKTZoxeqtimEWzP{3DTD<^u%VHbM&-$pJhV{tsdsG;{aPJ5 z=zQGO?>ZQqT)ErFB{xxNJO3dZP`WsvI%Q^-6%HbWSnPtdWO%zL=&{JO+UU;3_ z-YHtr9D-`BX#F+S0yMiE?67IfeB}xU5+^TKv+L>I>N@Nz=ZJIB`2I}$?Ef+LSa9Fa zUik?pzidPB?a+^@IprrD7r!xXgzh%)?5pB})X)t2Csyc^2^;30sT033R{E|}FAF#F zA0+&E^ONS+*{>RO6;3p6eyC^uJQ0_W81B_C?u%4isFj0Mw@W|sQUJ$ysElCRQ^L6-y;v*DU z5kJ^`XoxC|Nkx^?2fWwozbw;j%R(KMwe!?l7br8M>(m|92x22W@YJ(ieW+$!&9mf_ zm1X%I`Vs`tfPB&88)o!dax_Sqc#`1n^Aqol#AUI)Xy3+%Y&4VcgONM?YV}_#yUQmm zKbNOd;$sIiq|x2U{4wEfpY!J!4}CmUlVNqHh1NQNk#7B}smXZ4eu2L0FPD8amAt>s zzx(^Vh3AK?-xyebUaIMU9#Y&?52zbSVn@VIY5#$fy5z68XP@ zMNBHw-Q9R&MAz;5I+Wg9&0G}@q3GPR>V!#~z(V zLSuzR^Z;7(rQkzqPlfAI^q$H;@Ic%%*?^-RaSWk{pQS! z{sNZZKGYM9)p*tKF&3Z1pg8Mljx%LQ`S7He)wj|-|JfB1t@MX}L)d}KfB$I#5Qy2} zAHCr}$aFN=i%8q{-5-q^%V1HO+`|ld%a2u`h|ce7wUGsX(5ZjR01|(09lGU?)g!kG zazaxxceWgrB@v+Opf4D~t&=qhn#cEKFidJ}ijbU>8KaYKH{=9#`K$DqHC19gg^qN$ z7Azl)DonaxD(?|<%DF_>DOShV;*WXn*=2nOQEH? zCJuy3e4u4%2;MRMJ*5}zbbHJUL%hU1o1DLL%e)>eLHP+^1DC6TFCp{S>!h1LI zwZyreZ(l1!xKrGf4hCn|E{i!f!LXZdChvuMh8?_%W@MD0*Y5k+$6&^&uJ&Oh6z7hh z3$V3kz*a+4wKyC{=cIk_F=1dQH#~{5$;E8()t(g9Z9e=VqrLeHVR~V@tZ1<|(ES)* zF=l;oVb08MI~j!;|71(lB^Gk*zNXnGmO)-vU>U-5lKyqHVTd1)Q-jU&83$VF6fyd` z>2T9_W9lqGeCMzkXNKQG8epaFu9u)d;hW+0+33)7!$MBcr^wNm?>aa>P)?*j1ew4nlxB^NMW zi z_oU~u0etf88s{;J5?ejnzSLq?Q1O6@(;NykwH{=1$>beEvR9noO5t__&E~O((YjI- ze0ocuB~3wUdZ!_qwWn1lr#7pb*GvGM?y264RX{p{Pa~M%xLm9Zeq`Zkxv<(>)#?+opcmlgR;18|AD^1f^d$-G zxHoES>Lp&K)?(+wt&L`*Ur2V2LB+5|KN`m@ea<+GV(0$}#Z~Iuj({}}+#Tx)S>}iz zlRQM_DL2ZTkEpp_*W0g~VG+kIklO%2TwP;eUXto5Vkw(+pI9P+jJHXab)Aj9QHt|T z-)IK(n1B@B$)8<-=;h$!A~T0w;<@s~m9A*}CUxce49Z_M6>Two0D0>KQl4-^d~d-k zXq+?dJrT1d+8K~h{uj(KMssL$fJa6=8jlJTv3y3R^o>80>QUA%;?!%#B>%n+q|D7y z%6bBnC`=P*rXrji)-#7S0YjoBSuF8h9+L%Mc+xw|Iwfx6DnN1{&L-W|;|4s#h%B7z zvwJVBS+LR8MA%zPQ7`Bpu~Y^Vv$K>Q2D-%ajnJmNzEfqWLu`ODVhtA8p|)bI=zjGf-8^-(meRx;V#gD5a_nvW!4S_-hzn0qqd}c+*3VL-NmCM)+xCzZ@%8hh zrV(@32jsjw&sg3!uf;KXi}TgQ@cK{6u;7%jC7p^N>6m2Q(U>Riga*`GyIoDl3`hXH z1Q7@4z_%m9r}$Q2pWGuKo+h0+V!5!ze)b`IBP(&%%;ElrY)7traJh7MR>Zca=b^zb z*oRcf_-E9Z>oqBxP3LnT8{je`*5o9=UcYDtvnlUA)}<}uquU1Z2S8UtEJZNn-800~ z46zm-Pvuf$P$Og>0JHh=AFa&CE1wesZ4H0#}*xI|5Vxe*Es$$lYPZ+YykV&ShLHSwU@Xjs^?ArGYSQRV0-=Oqq=l4a%b?$ zj$*Wx!wxQMnBTy`LaHsn_455`9xn3AtZZ+s8Z3jSCjCY=>vwGt=C2m! z8q{?fawvDOH2B@z6z)HihcZ5!)xBsKRj`t)lTbFqPYnp|f%ZCgSn5 zrxV|&Tf47rOxaX?(RrE;@bC;bde?bYc}>3-u*i?3&$(MqAo4zg^(SkXUkr&ng0R zgdcwNfklXgm(bBu%V)9&3!-#l*a-q>BcKWH1!tMwsd@$* zZ8M7H!1iQ(vUuH9L_~&(P(iVcTB6XtT**&AQZidbMA;8@46H<;`6?YL=`Qz1^+Keq zH9j>pD&Yd&i)o4G_G3$mUkFL6#kMgStbK%wLM>1hWSWJ0NK`2^KN0}o;?ba6#R+K zzd{fd^*jBaZI#8jR82h-{rb@RElExrE&MqLrI<6&%VZa#;Bbr~{>Vy&d5M#mlNccX-RJ&cA{)4crqQ?V z;R=x-h=946X+;yW0R!V0zxU?tV?A=0rbis}kn)!r$QL>h?#W@53E@LCp)hpFrg3o|A5+dcl0D^O%rYm%18 z$vOx4B0gf*7c*bOAgVvCQr#~_ljxg_vtO8GyIZ6<1X_Jq$+>X;Hq)LDK7`z1G81$0 z6w(^A0&nf2)tDqOrdI_bv+LO-z3ehnr0Ja2m^2bOL9CpXW!ir4a`{i1?}@u)j#^8L z#H@alJUl3o_c|1s|c6U_g%$Y1z}Md4s8vi-VCTUP{tftAGHQkz`r| z9e2u5)rRaIDb?Kr+jqsBdo=BGt~qov5)+=QCu^Hs-QF9fRGiJ1c(`5QY^j2@&q>{} z;(B46G8-`?%$b~X66#s9RLod#`JlN*Fpw%NnrN$TWYEEslRf&-0m|<^Xj0Mu-nnVr zE5URo!!f7BFD@t>*fOtwb3=6(TP$@nUcD>j<%w2(Eg_5`psq=@36yRzP3q@h&g2HSR=U_tN&UG`{PwFdY$7L``y7Li^lR zVoNjJX*6e%dPc)ow zgBeFmE&14+yH!4^MUpW*=UimaLs5d5ApL5!nBPhcsrl|pB*zUAWxY&g)*qF{OS73) zS&YW%y|m5nmWPiltTVu)-8O1K1c^{C{sF*inVrYGw{d2Oq~6WmtbwLA5mxm0V{u|F zDQUILirv+=rON-TnRS5YgLlUHIikD|+QCMlFy}+DJ>aM98(AWH(YWan>lSk{_8j<< z;h8{RtL!%jb~+HR73BoUE2O z=4*7>xChxy^GQSR+k*O_{hX3U0Z%uvY{noJasaz zYS%$qzsx`+8X2e2z#GBNo0*$ifRh>hbr~8}RW#tt@3v9iqz+ zIJrJ=wziH9JX<;GQEH{ApA$!P2P#;`JM$%y&w67}aj~0GLa9A9l3F1bc#jggWaW{g z<$#SZgy(fAR~3`3;+gvQjdr44Q}dP{-$V#@spKj<%wC&%8e%A*SoI)rK9{2o-Wg_sd?N76F70o=5H+y~mjcNJp{TNT)GEvn?yr9U;c?_gz2wOJ%zsG$;N zC`_89i~0G=wUb`lGw!EnOvo$l0O^qFt>T?g`1>HHMf84@N|i%hzEZ~8vS;1gqP_#u zYhf8n=bj#rbwX3aRr1=3C>^j90O;s?9wD~9 zQVRJ*$Y!GsY>y;p1$twuqOs`Z7F8w~yQA*mcJiWW?}t)nSiqr7fm=(d_A#XQ7WC^r z5RFlo5{<#9Y4iaQ&p0KoBh8>%1Y*i0Y-Kvb^y)(@@y&4V zBZrm5_WgidCmb9a>hZlFHH_qmG_i!WYM!*#_lRdSw4drir}Pcc`VzBVn{~TSv=dZg zw*rLvsJ8Jhcpr62I@q>8W^WiZpLeMXXm*xzN}FuA*M=B`6H@9e0=$pVK{CDm%BLi1 zxudkfPZgV4GecmQIa0ubBuc{(Eds`fRayFjwx3TEqH&B_ztE^?)b@RNnl~-VRQ9O6 zPsW!;R_Lq!+aNRV1t8~WC`Ga^I)@c8S9DASF-CK=%~t0|1w&I=c}oZgxa1<6e(0c; zvb6N<$C^0u%Z0`_6krN#T;v=Yr>Hg59HMN(WNFUrJ)Z4gEYrSZwxWeCe3i{Pj+3=C z)pgW!lF~;*w@5%MBp`ZFRE30tg0w8c;_)i=RFxyHn`42C(P=6nnk8V zuArl@aJ{BS7=!+rqfs75Zn+`ouc_I@8p8suB-tPKPb`5AD=TY<`cP_&={D-vu|fpJ zOZ7bC-BTvT^(x7IM*)Tlnb&1F{nU68G||Io7-Ynm^TZ*L4Cvz=kpx6fwSQ~M-5ONd z>R-+!{IApVzw`88(KaG&f5g8W5Mm_x377{o*Q-%z2$^qc7Bd+yz zs2$q15LB6DGfiWC*L4^y_t#ZkbnfANp}OMS6qSq0?3gz8s++HMpK1v~3okazCNf(T zu(@Ne2Kekv>Xc;ASa^#fIDTZ$Lg%5kr?*Q-1PV{yKGm;7N9J!1)0Jj*DcqRzEL(pL z$-FO}M+Cc$`V>@t;&W1B4Hk0RS|@fL{csSInLx{Mjp<>daFH!aUAM^JebbaE&eh0y z$Y9Ef&QyUWA_m0AFfiRYue)O(l!8f?os!zr%c&&i{dOb@4~8f=R>koyLTu#1NLYWE zmw#$u)MJc(uWo#Jiq^hjw$(GTAX6bnnnK`R+Y1x!h=Os~qiEBLt5nM;IC9lotyISF zJXK+$5enxF3$z3KygK#Ht=$#d!Fm$ysFTb z^3wMQpZSs2uOZrJNzBU|mdxw9Bd5Lddb*vi-8c}6U4_gSj-N@aFNu1uEg~t`r97zRGFldxa_~+P# zdf>dUd-=jya4;MJlWCo|RiuhDFsvWM3#+CH3D?P*p zE1vf`oAVO;rz)FoiTEo=NutbsL*1z6I!NS*@Je&%slimcs~-84h;y&;@&(WG5@&wL zr1aF}sMx3#`vRXZWxh^~K8mLcS{-1oiVNQl2~EE=pJFvXp@q+ijk+kr?9nPwE|M45 zx+?8!%h!T4J!gnV0<^M-W$WZZ234p=s|-;f`Z9t90!!Iw2~bX0uzBp~EJn~(7NJUe za~?U(d4e$JcwP)$4f3R1K7?I55G@Kxul0mVKe1lIlh!+}h!$`)i-+DBOD|Kb8P5o%q)V>*CU)AZY1cj>!4LuX8^&${ny+TiWFwa9n1FvnnTtDU(jkB z&eu$TtaC6tPOxZ{1uWW}9PYsiXdWH%6K{IcwAEEL|(1}CaQS;q46HRG{z z0d!k_2^N6CWuE&!_TIP#80yOPR3m2(F*}m(4;Tu*;zvtk4(S#Z(p#diUl>)b$^h8# zKC(FtIDHG_&3(+w+b{~A7QZd)wSk>7aEbx(TikO$6DK8c1><}R+j`m}b{g=87$@}; zDQ;pw3!$fdc|TfZ1qBSP4D5327Vg%O$Hw=AUyQm;ak%H$SE)OX@v19Vr+aTYuuvpu z)J4FQX;O59c;>$`GHmX>G$yh2eS#M6EW6(j4iaBhAq-?CS)+aZSQPj=7ghLmoIGrq)@H#9 zy~c`)fR`&r?6l3YrVLDKtb@;cMVXKx2R9#S7D9ywK#nC5k=c6z&xTVP&le^&mRrHI zsEdK8ExSwuxbFxSp@~eYZ*vK4U-mKw)jrpLgzqoU+10#9Tz)T%{OjHmrLL~2`X+?_Din?Oy9f~ActTq>+&$PH0SSI^fCO`P=n zrpcwni<#*>;+mX`&B9_FkgL^z)GqKv32FQupFy$rOzFYFEK{<3lZMiKL&^JB?sVi1 zcp>>k-FElnjrw|im5^k&axl3h(Qkg;bXKw$u2q0de9h&KD{1btC-*|-_mIFo+!oj> zPED}0c|;6}PxdrDL}l7cTGI?`O5I16u-f^(D_TB@#tnQK_VAPU&D&V!zL<$;3HtaO zPqDO=iwCq)^>Iazz5CRrosGmFZ(Ar}8@>xz*oxMPnI$=7@fhkt7tNmQV2h1C;z5$c zaNst5m4;omQkudu3Vqkq0^JtBGZT^&w6QC1{`LMi2;Zb9-EhWM^&eCApB+2&p3@&s z1nsf>*AX{by!wB)eqC3iYKV%|mo@4iy1(m$?&_p^6HV!3>o#ip^m$&RJM)j{gOggQ zU@>j%)q%?njKpZ&QqC;lngcieV|j^%Z51Lr4@n|lzrrr8y2=v7EBaak05y4!c#D5s zrFbD8uT!PZqWA1rqer(5X2z|<+~drz7z#h+6sW=foU08L!JuSg2WXz@&vDDNoU2#4 zJW?hW|37@4byQpHw)TPIR)Q8SgkZsnYuN-6+}%sDVhL`gv=BVFYugl;0>whGmf~LA z-HI0}(z3hHmwnDY=iYnnx!-?ljEsztthL7bzH>g${5@b7j}>HmTMdE2@u2-f`^kP= zdfpX=ReDnynAUxNT+AI_*Q5y<86eOU_NQS|LK)rt0Koqan0lMWl>c&xyq%{-zu(s; z!5Cg#+on6}Z>o{iKkLh2WO`P0Yg~q$!~OVau%||$$i#m0fVbr{QMaBm)Qh8L;zKG4 z-o9A25-opAdubK#R-jWm-?UNwYHfh#P-Wruv2Jps_^v>u`WwB(^e+3Xn;g-yy?4x-%!YaZ3}l{VUZWs8DqBC zz*t1uDr35mi1wN=GV(BM{7x~HhkjmS8RP%8RrTXt?wm61^Y~gMq$PxY3QGtG;z2R6ZrQM^YY8^ zs@nddD4AG{@O)^aw^fUGR9ta>?ry&kY)gdMmm{CFy7~&cg#4-3uaUGmwZ8Y=gYThz zDd@#*9v|hvi&R3bNV?|0HpKkPPRy}EtV3HTL$21Gx>(E_a_zyEH+ zPW>+DRZC~N&NInvBbR|#xl()cxZ0wf4URuj^8GKsr1{$h?s0T)+$VGr#O$xX8gAf% z@OI#=uxmfqIW9$NVP>i1YfE`D^p*Tz?va~9R0TeJ8SJ93=I5wySCB{@1Cdf``(gaj zJwxPJJlDvJZq!b?8-(hEH-og=$kWI3uSwhjey{S-)eSC#IfPObjJ1srZoA`=YY{F- zpTO5U2`T!kTU96LH0%4CTJ3Bisahz*==5|J8M^7ORp06o@fOXqly%9cuNKI#M%Pkm zM}IUzO2(wWHDf4;Fc<`Kt?2(D$#k87DRs0+N8+;0u6 zq}gu6(X0+_wT9e==E*3B6Dr17Es~@o7H9is`)Al-ra|<&DPZc0NaS>JHIwxr$b93y zQy{n_Zna6>q3M%7w%2gJtx-?43CMjPNCp%mEDDi zU)BbA%qM*JpftC+q&QJ?=ZBf6TbE2Ixq3PAcWrHvGAe=Zmo&Mnm#v2mvlE_XITp#ixuTjGJQG`V?#tl>%B(1st6S5T zSwtqQs+ANl_66q#@tOvklt-pf(mTca#nwf%KZSp{X+Rg$iT=!9``zOD8M7NX)4uB$RZBS@9#+RYV)O4MIz45w2+Eyy2 z#P^rdv|FI;8R85+*5MG7)B*%3T~qL~KNOn%M+SUPCsb>4*N^za$C~Si7M(+4bXZ|rlWb@E^!7%3h@2M5@+s zj-A?_WY5;HB`T}m%nnE1kJ#h^R()3kJPThi0vlFz2c$owmUQ@tlEfYucun63zY0+k z;s4!?lc;ZxBcW$-C2l%~e9r?z?VsF*Gy(7&v8!CwioA=LRt)uQ&+snl;x!oS&sN8a zdu;?`Vsa2Nkay+Pnq!Ll)z;tf+c2bX#`^j82RRO{z2)1`0w$X615KdAj>@>;3A6Mz46{lMPFTf-_4Mdi!E)gbus8N;%iOHvtT8EDs`=@)M}s3d zF_GhCh)dwwj-ZpvLrZ(lj-3oc;@)Yt4-gT)SCM6ri2M(b>FWp5uAC7p6e<%y-*uz`shYF=iX~l}2O5$gkWhW1uXuB)-^FY)VZhvnaD0`eK(^Nt>O{$-~cd@l(MP zy6=;k#Zy_m1a;1#Xp|sNPblf`+JsAWoM_z^deY+}thA=CFO5a4`b^V|HCcWeHIeQZ z?xdX%8cNU3P9pv#<2CPSYU+y7SRQ*Ve|0U*qwB*e9b|e!2>8k&6VQY~#(UwXd8?#J zwfgdigD+!7oQ_Z`%1gBb!F4^5z0W&;fI{d>Rt=qGf6c-A6%@_MBDNS$(wnMdX!36ajll*4A@NyE2} zkK#7y?1eh2G0@>J=PyHzTp>tdO*WMc$$F*9qmK+NT)F7vh8J;WtJFj;w>%Tg_U}s3Dor4PN!0x2Tmna zAbjw_NoDNoVmesVXH|v_8cUUC8sdLf!JVA^p)}r8Ph8*nxsTgdsy<3-gRUWy0V3TV z+5`0L4~lYfq5W?SzAyG_u5aFWkIfrqt=97~`1Uf$L!Y|}4NrOrVr^$@PN@d?!>^Bq zGQ_-N5vMK$O?3PMn+MuZf$@>VWK2p%vK#}O(s=FTi`edU%9JJaEPT?8;spjY;5zHz zlA7<+BVBy1XhXweNOgl7^RLJ%tuYc=3;6zi6oem~>%XiHD;)&%GC+mQBB$Hz22UCS z>IZ+Qw90NzvkbP4O{qw&hfVwHn-W+4RGAd_Xf%D20#uIE;{SsELa*A>t86l)zIAS3 z6Hba3ehUN8%@6Slj_6xyS&o>6$qkXkgfPi0dm(u+f0X@n<<^{H6kse9cAAW`_yWrP zU51j9oz_?tXIonb@}P5Pv+wyWya_KAR}6@m?uEiW18Gfc=kqJ^u7nAnHI=3vDOM{a zg1d4Na$F_iFeCp<9u?S-eCq{zq|I*pQdsiN<);MuK2lHCNozC;>aS@UB$R6WnlR)! zx4tG5&Eg{&YK20x)kofXoZgFEAlp8u{L7qX<%oApqa>ya^|A>1?7h4_r&6x6*>~QV zSwTW%1|M+=4`X7^7^RVkh0Rv>NYwC-cB7s`Ki6LM+E-JesEYdC{y@p>zWs0Hm?O1< zcrxB$6pn|EE=wvpey{dY@asNRR^Cfh_Gy8g3}`ln?9`G0g7|1|mivk3U> zg5+bQB+kx+7yVB?W}63tB05f|$8_b(RK*Ss6Gv1oH#} zM5Fj+M%!)e?Y>{Nsg7YY(#;Y6s`w%l8V)L`(%5h4xA#pJ%32w)Q)zwqwI$*Z5J~Vu zezaC@IyEkfOLsQ^IgO^ApS3gAONvD^d!O@i*`ClE-nNs;u)bs$ueR!as<9w`sIIsa z%wWSiI|)!BG#;hn>=F8vPnD+lZvV&o;XwA!AVO(t9FUW&Mc1%A3rYsK=X(gFRURSW z=x_xs)}>Ntiv>U5Tfu_6KL)`#C|uU1zA&8e&af1FbP56`Tk++Hu)3gl{v$+_!|;tl zL|mTq$6`Q#B)rbjt-547n%KLOlNEPeaCWetM~2$E64e}p-)mTzVI`;KJWB2avpF-$2G78uRys1 z*pxTnSLWc8l!)=*b}6M#zS=^yZJ+d(3K_rZFeXN#Du4)wxjUcxgd(i3bbfl|JdFAB zn}#*#k@ajhd)dA0mcDeXUIM^*EBS0G)mkuIa7ydVxM{b8y^f3j{(PKS&Kpg+J{j9| zKnD-ZuzvNR}TJq8t*_g(UR!BNO z)9=S$z}(Qds*>vCxFV<0?}62pdV?Xhs;v&sBE6CyI zYrB_&K*=|1S&@D{GJ>LSn1yt%SL8A<((YAH2Bh^Z*?#0W z#H`rj%N*ES;7OmE3_JsvTC11{nFoM&3Q8q07Z=9;GeI=d8K-pCZq7Xcd){zURwK_d z4(GO$PtWJ7_jcJb%<@?BH{G(oTSqj5<^Z~mQQ)Q1rDNraD(jTr*xes37hdWKpL@xu ziJYNr*qzs98xRdO)DIp2X;Z|72At3RXWqMoieuhvuYPR~CDk8Ek;-x#012u_JbH&& z^i&@TZM`z7%Lsd$Zf(_~FMlS!(j`L~g=a{SpA)#!{xK(}QPZoP@oNB4Wu*tro7GJd zI%PUZ2D=>~y&=P|ruWSOP><*>rA|SG=};#D1KUC>I^{*N$gU;QTzGEn%XL$=*Dx5r zEZoC?9y3zf1UDA$A^s{=p&J!fC%gSZ8E6b-3fKO8%0iXmA#?mZLilI02OFp*^00o< zhiqVWi(#_~eA#aQdi4nd(aLMba_(i|%D4lYW@U$pE7f#R_E97}Ue=;0!2kQ538VCK zCTk(%v##JL@tl5clcy%QWKi2W#$_n5z~_ZUGB`5*w5mk=LmUd2#w;2I69-efU)9++ ztrj_HDn1Zhp%wZHYT5=zbW51@Z4$r^*mK+|;;edfM_aV8&5~+t3?{kTgF4k(anW-k zXqrha^3V}ve>=mWl7?8l5umZpYu1o%i_vPRs}Ve4IZ6>7s~U=zIxpIfr~Di7s1 zj-KCYT(UEzW4z_pBHzHO2|U5yG@2{^8Hb>*ViY2-2+X+lYhTjs6@PY~!8-~6{DoC#`6GTj5+sYfJ)T-z zO+;!emjo@CLSW56GLae4li>}>?Ac_#j$#wXtqUdwXS7|~Vn-q>nh{~^PFO~&rXP!U(4v;aX6LVz~Qhvip%cz{wtye)s zv;_tOi%t{982)(@|4+yD|K5uGw=UA3^3;DmuuJ@@O_>~iCj%I*3+l_gjNmknqpGHe zDEfYyT;Q(s-Jhu_GBs!Co8g+!bN6Nc%ndfK>EIQq<>r(3&j|uoY8h~NLt8c7C352XR|?A*;5;11^87-r8k zPyId=d-^iDDz!8@C%+nAIKi7O!{z-6lLKp|cP;M3Mj*MsPNkaGW%$KTBd@G%yI@!v zUQ*ZLh$zJ?RWS8WWb!$IwTwni0&cLOtCGWm6c@?vXZL-5*|jZpf7kZ*2hgX`InUg3 z`^m9!EkJF-cuOyTr~jGsC8R;AqC_MT#n&DhDz|GJ!biEq7m$mZx%7HXaJtqWAK<_; z82*8pb(|szg>kxI0moMXsE3Z{yho@j`=41_4UCxX5lxA_Pk9-Ou+E?$-00|WnMUAO zS8Tj3HKb)Y%kE2^-5O!uE*X0z2&xi&uD5ks{MOIHcekg#_i0XXPp(*egOdhBIY5J` z|BL8@+m|fg`nJSM9I+_xxc}AE?P#M0R{s?K?heRv24YH@Px63O>rHHqDL2W zc=q3=pQx>J@UE)3RuHY$^O`#Nw{Ch#T3Q9F^tQCK2#&qyEA-z2rRZoJdX^-AOLp5D z0M9NQ;>KSh8t>=Fmtr(b1O5;r7-D##?Ultuk5T<8H&Z{myPtTKW1v%iRtm_d-y@8x z!EO}qjCi!+W7kz4c0W0+W>%Z71OKRb|0o`r{X;b@_KBg9fG9<+T=;RpmRe+rZ-AL^ zj?JU_Zcj@IvxSs)BCD*1s%|<0+ld?})dOm`Sxf(0V~gSyBzw2eouPM$0@wLHpA06z zw2|5@VM2bml~4E5caMmt4A{g?%zKUV1?ht1gZQ=s*|;cFD~E7g84zsh8Swi) zW?_EZXSdnY0xwi6lB zd-M3XT7|I-Mch@WijDkth~LzOs`N^yNpp=>j8;P~-g+f15lvvM*I&d4=C-XQD?Clu zTYASG{B2*mBxUh^TU)Ci0&dHt=0M_#{3*B0=L1!cM*=a0QaXh=F85n1m_E)!5%6Zy zC&S*ClPi&;SiIyE=T5|P&?>mO1nnwc((TsG{q)3Fr*Mb5sBD#q2oAdd-+)xug3#L! zCW3B#glNa|s5=n|588KK*Nan4)%Gt^yF;x8y56 z99TpjT=nO{f06h`;&DUs%})E+2k0%~_+pDlzicX&vWL$jbJngZoBif)wi`WZr_+lP z4T3?k-Gh(tWi+$QdiA~37Ow2&CT#X!ONi6o`Q=bJ&6VJI!-jdl;NfI`4T56!j<|Eb zn1s=Lls@Qh`*MP~_H?VVR$naQ^Bt;S zEM|@HUg`Ek(-Oj<1^qJg8BW}0KYll1l7FQe7+O}ACe3Z5(6RA?0SD+@_LL+jdkc*S z9FEMyx(W=1%;57+ad zTuO)=T|k>Cg-9~Cg;ZjA5vJ&|2zA)w2NPjnI*n0j7Uj)icsRFvEB_XMf%6F*hmHA1 z;qwn0#zxkA%dD_Kr~m3${r6_n|Jd#z`DFE9RyM=f!kRuw(suECF>C3n*PPTnjnDW>U_L_7p z3olC%tmqPUsC=vF9nwwJb2o5OQN|7hfZEkzvBFQ94PNfS= z9Lj6J!COT1XvoRa?}iNJYKYrk53@I{12?6{={3-`AGeBXIR)BkhajEh_&8IAZPz5J z`5LOAl+g9Yf>uQQWVAhVkbhFk!+BplP4i>es)is$1XtWv&Bzx`mTwalylYztqPmA# zzOM+ZK9X|PkNHjGoP`1c;!@Fbt!xuL!I>qIl$vHEClz*#cTh47m6}1t

    B85gH(iFom-k&#E1V-<;-pWhNpV-X{jYa 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%0AdR>F%u1>VVG5c*a; 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�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 G&i)sI__e?Q diff --git a/services/web/public/img/feature-page/feat-changes-poster.jpg b/services/web/public/img/feature-page/feat-changes-poster.jpg deleted file mode 100644 index 1303abbad64666f4b3890f503acbceabda4da9ab..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 83046 zcmdSAbzB@v(oRq8-3@j`R4D8b%%p(*=90myi0Ra&K z2@w$q73t~!843~-$}@B{v}b5&=ve5#e}6WAoc#Oomk?NFWMm9<3?eKnB0Nk?OuSzQ zOg!x0tk{1v=zli<-u;jB$FDHhD6p6Cl<=_FFmTwg@Yt}A-7sWNO2Waz{?hla9|9sQ zJQ5r-$`e%?3kDV*9u6KJ6&?){4ju u5{B0R|BV2^*IhfXB&&j8DTYDyFLDX#BZ~ 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 diff --git a/services/web/public/img/feature-page/feat-changes.mp4 b/services/web/public/img/feature-page/feat-changes.mp4 deleted file mode 100644 index 8cced26501e86e92997dfc969978afde7088f9b6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 421813 zcmX`RV|-=J6D=HLVoc15ZQI7gnj|MVCz#lFCbl!l#I|kQwrx8%&;P#n{jhs?uU@OV zs=7YxU+)G310ykUbhEXxWoHEgg8(yku(A4jSOLz)EMFK47#J9wi9HAe2E#LN1q3*L zaUNixKR<8QB#zq;mZck$=$A;A$u2HkSvlEB7)Xq5>_H?f?5rgAtn6GoBtRe+4}g>9 zYeJgwi@>CyEFs0fP9mZq_QeE(jK3ztY;4^uK_-qQtjx@846Mv7|Cup!bhPDVVsdeD zVRSYJf@~}S){HjxrcD2>!f57bW%_H|1ESw}jkddX0 zv4sE&FEcMQ3BVd)>E-|uV0Qf)yRxvckXV5PoUF`99Gr~4dMwN&whnGz;;+`g9w@-V z$o!@HYDlciT|q#D|JnSK7}x`>O+f;z>?Fo!_BK`kga5Q7j`ko+OLGSSRuXPkZlJNF z05gfPoz+(h1Ob5VHr8JP7J3#I5)*)fqk*l1g}LpQ=>IcdXKP?%V&VXD6kuQ_aWu34 zQaA{3l33c#GvJ zJO8pkQ_fUjU zjjPACBVGKXC(vcm7R(SXqpM*#;c1NwY75<6SnWIGy*#zu>Ld3GHfQRD8+0?hugfZ4 zTno5O#4}?Vl+H*a{ey>6Ck%RO52;d*0x2Ax#36AEWEVrCzwWjCPlkK-yYP+|-2~qx zTD`x*QX2M0GpKjy6;8F$hY%7jo!!W-kT}Br7($ySsd66W@$OM@m%hN#wR4MExvm=* z)CtJE-98MDtq9hd{gF@Q`sK~FHVizhKZQJbgUPBl#GGf?eOHt{v*Qstr*D5hsSF>W z6^#a4&&Q|oHo+$st==b3%1XfsWT3oBvSwk+Uvy0oO}U9-UlXSOxp#4}9VSbzF}cd^ zqG_cqr6AjJ2~*2KO0jzd`xUfwaUsR4H^9;fY$qR)xL!Ek{uNX6!X6H;VV;wOB;%D= z@=m8$8)!v-TO669GWFCigcPK_0J`H>Un^b3(fYjAEG-9iwC=oyRMUw}2mlu0Hx0Wr zsf3Brz?SP6%FV!rVNML3Zg83kng>{Xhu8aadGu*z*}bgDd~N)=E?BXG7{_2np?2KX z7P{ZVqDi)k*NZ++G^JZdPf_KdR)5cinn>tRff#w&@@;_BY8|U_*YX@E=yf ziGx=!J>+zpM(|^mC#A8JloYb!f?`C@>h_|S{UNn*YSRuf;JJiqU<0f^=n8jNt5Wmu z$`dai(3q>N)aeq^3ZgCM?=(Ww>A#>1MsRD@hK2@IHA8WGaainRAr>csK(L>rfJfbA zPE-@G)g8D}Ph$TS^&-?aLJahFcMC(ot=GzHS9?EAL%(hK#(f8L3MfK z-{U%WVpnKh9=_tGk6Ak-nm`?>Or*-()d5u{Z_-eedPOs=UC#V5FTPy!)pYh;7`Iv0 z9zWidg6o%BVE+NRj%f>2rDo%dw`65n!}h>!)73=;rbY|1BL={4d#9Fo~7 zg7F-NRl&GF7z=T-goE(kRWS= zd|VWZ6}Dq{J+&dXxL9OD9mpZ5*n4B})R8P4e?d*h%;WhhB?@%w&z4;*7UTN8H(+4N z{q9XMu$&FQn?w%jlXZmyRQy*+Rj3*(Ki7)nxvIKE&2;ZFjt{SrCd$7bC&}&0-$(TE zP~ln18hjwL0ga@WBK^vqFYj}mPnJ_WT=a z#SV1oX(U)1N5c+IShR_3E$6HJtf*Aq4;zAP-ZX%-nJ|p~tKelvx#mRNT5}m9!PSy5 zN)WulRG4A~YS9d6*b;p_Dru6})a9hMQR5qg?3yktYTs%BVNk(=LcTg2=biS(6W|9_ zxA7XRgnWU9&QS830)r>PallPfZtb0E_~wz&$(hiUOq^(g6Lmr zRoZj~H)UAz{FqD*;aU1}mGhYT1LZTkz3|QDLg$F)riKM9`jn4_`5!{rHaT=+E6*0J zPkR#6qXwFadJtwlFw~1^#VZ!q4ov_6_J=$99Ntqep9I#e$HsksW{|JHdPCYZtY&9P z$A&G+>IJS*gY~CYQ=w6Aewsr7fdq3RbBu=bZ-N0mPR3sJ2L&%`7Uo02~8EksVC=^b#vMu^0p4I}DTSZM7*0Vs`I zGxK`{JT=^f0EYW&FGr$ItX{k6Qu*Lh-n!m_t<99959yg~!jDMI2VzJl{tv|e# zTU_zStur@Gr0vU`yu zeql8MA0uPrK6vfMg3(!apH*|}1!fOHx2BRDGI0*8L z7z`;HTgOe5Ja6>1>*Q>?mAB*$*p)=Q1rZeA-cyPA2^Jr{0*ULkJJev~>Eim7<&XNg zq|~<6mCWE_|CJVuI@M$#BPkIY*-N+uOGZ8b=^YcHLgQc<_ss6VH3BN<7h_1#GHS}h zbE%jaznxW%Mv9u2c_&AQ8ieVz&(CqV8x~E|t~85yW@UBPlsM0Ni;$o!UQ&veaLyQN z3%=ZXPhdw4kaXu(c+|~CK905FA4psC{CWii0!usjsnMULh>XyJ4ON8ty1CsC-Tr-s zxs6QsA+qcE+ua<%v0fYHZO7XW&K+apUi+mDL~BaJ_Iz8hvKrZr4Kq2mksuH0My(&pyN4ht+9hPG!*qc!`Q3%qIYz_)EPL7I`{9eD>4eERE9;EdRm98 zA|bZsf9HZHd>WKL{XNF6e3EL(v(th_;Eq1DznzN*dH7*H&^(vThv5-A zQw)_UQ+se$`O)H}Nfh_Yr1P{%6_GnDqFf%M=0(6G2GP@CjM@)Dytxx|T*kj%w0_C; z@{ccz2Jw%NffjfTrEahlMMg7^o&Jzke88I@bu->?OB4*6SBln$L=>+-&FYv=OvdEW zxx^GCQl4uo&wThBNdX^*fq&obV2X*LJx!iLUE)0~GJ0>k;7ar)n7fVUn zuF^m^{c+NJt?y3+tye?t!2TQ%N#MCmzebZlEXD+|dg82iyM+%A-a}9i7oEAALA)Dx z|5V6zXAqTDBfntVyj7C|YL%dpZOZPzc~2=9x)%lb85au#{i{8iK%KHVQcUgYC7rC| zn-mHHI7aLD4xd(5yt(=d92_=et+QGC73Ay-=zD+}00;EF{gbI$RN==4-hD)S6pa&q z6a0LW|F|T$nwVk0KGkMHlf-}K?#^P~kYCcKKV&XgkmZ*sa&Gb(h1>!n_kw{pc*(39 zaj1A+a$WGyb|5%+b@9L`GloRdVe_#H=;xV-({92v75~kEudlU2HUYJjzA|Arm%V1%EFVAbirW#kPG3 zc>`5QjHmjiC?A-YMFQ5Y!!{%-eRQhJygzn?f~R(fjTl;;RX0Wo?*jbZk>>m1HlDl= z*5}@%7>T_JW?q|qmJQBI7uJp}Cq~OhkTFVt!o@cBUT8~yxIbXsbmQbrWp`zac2{U; zY6b$81_g*+JLaf7FB6&?;$%hkgAX(``Hx%!Ulq*)LlJ%g(2%v_V|zp9RO-o7=YS3z zGLG31PO=WMsE2StsnYLA{6fu}>`a0JxX^<0f{h>(4e`&De;MDbt^}roayRt(j1OBr zi*>ww4~`b)m4AU+v$Jz`F|9q$>rh!cd*RMlR7HfHB=w@LwM0|(`UjIOMl}k&kUbZ` zX3BC!auq~to%1-fC# zAW8pfHDI+IfHz5UR)6~GU8y1!9?qImjVIms5k}U;L%hU7UPJGjmes!=T~+vtHp%&$ z8iljyPWmK=?H2InGj({7Qq(4|B{eENf_jIs;7*%y@ok%NM@2>HBze5@N;1Vs8hC8& zt_!t!e}$?2ZD+C~_`Ud;iLvo-4KOP-9(~y{Vo~a{ci_OVvGpy25)S@BWGD+NCC$Xa z$M!qbPMUfbuWTunwNCsB-DV>jVd`Bk4$8U!+GU?*=(@0B&F?yqu=x}g(kD+nM<`cw~x5ewy zu+UV)h+B6m8q72Z>}+&(8(Tv{L_Ip(;6U$)GfXR@#nSk{heYDaO>lDtGeAFze6 znx_sMlgBBaJuQR+Mvy{?rJ{cSt@1T;p@YAp$%hBCXaCcH<2dXa-!)~*m880UU`=1k zKQhqOrqtr1UcWLwAyo{nAz^>-upNw+({y?A(W+#?Gca_{hCE#%$Q;IEk+QGFW8%Dq zi@Cor<3-ovh~MN6o`k~2To>&^CC=8Cy@*S0_u=QED&PmNrDdBp90c;r(77YxTdW_j zK(xgM&~XU)Zhm`*SP-aF7#D=Ss;LN8Fcmr3n1cB8V_ri;j*KaovZ)v!N-Qalni>jA z80l(8b)f_WrZ!QgOj;?!xGXG(MChs9TDja7vj^|JSi79RxVbgFhXYPyh2PA|v8D<& z>Fn~~neuCgj&E4wzv6&MH(n~Pd~>$u%EeGHlqE|1d?E~oePk&99tmQy$Cr~k(d1LB zXnG$}AUpaT}G;@Ban`YZKaL$dveG1Ae|mc$Khg8<9S=PKW&?& zy-XH=(d}>og7sZrA`TsJrZ~Imfr#bOCCao~&m5RrQA!f}~8U{ZPU>5pA192ANP-d zEYyY^oP8*>Pp6;4CRLPQv*Y4WP+84*;I*Dqqy~bLP=6MTVl`gb!@j9CfQiu~!`!rXgFOpUCZ+Yr+3^|kq*Kb|@AerJ=^GT!(B zZ^lu$5ARyC6C*04r<(I5lM$9y#h8BGm`Ff>zlF4+U2T?F@kh5YeIYqjjp*{4g52Gl zXmlY7U?42}%wJ}RrplrOH#mR>n+ia z(kE=zJcXyL7F5Mo2r7l9qp()jWx!5c;vRAt^K$kIY{LINr@nianIlm}4HgxTD z{t_1Gw^am@Curc_71>k5A{;?q@?-EJhlLp&^Blllce_?SVa2%KUt|6FiwGMAg44AB zVB2+|tYsh67Raz0wG%JRNQZamJ2`h8=44?e&np5%(e=PGzW*WHFBVK`suZpeq@A3) z_MD2=-9APTwWR*;BK@sUBRKt2+3DXf36@iTl92g0ztb9SId?p8GUA7#qsZXjnD`wUn4j^+F%*9d_$_@z}rL7W_MiYh6l&hc0Xq7 zHq%g##y&y?Oy+OTmJc`63PVg{O!=@;#aHlVbo*$t3%FIg)%QPn?|GI2_Y9+4QOCIx6PX}V$pBK>RFyI-# zy^CrK;NK^GW1~3iyn>5+{k$$oP?XA1!P58`7*qK^vrU1Haa>9J5(#PBivcP}TieUl z>Lx95<0|nB8967YA0BZeJh=xpwGR-1fG%RK0TiJPdEN`v>^8N@ep&zVNOzFNYz?<2= zT)=O#1XaOj{jEorUFb2FQHNXw<~JLVtwdTMzI+fS;s2`)Y{Kt@*tb(Khb!Bzz- zNx>ajEfcoTT9;jF?tI%N+h1~P8w6pzoc$-n#i2Rpv|2k7No%lAZQbhosoWTP4~(Rl z%LPSKtcZLI;kY7nK@x35b({k%F8*;|FcTkx`ih;XjddqU$lr}p%|4^K>Fh`dj`5bJ1VKRrbVLRQ8~J#Nw^E8ssk zTT(@?RwZ;HcU&h~k@FsMy-Y(fY0D8<8IgaU$#`zoi5@u~rPX*zU++^Vx6(aEvr!QI zTgsd;d6FD{qUuIMiZoBAYG_Hs4rYkFzxy>_dW&liFa}F$!86DelF1N(>>S)v<#-G^ zzT(ARX$8|j0JxncWG@0`!bBY`EQbP-T48*1-LV4k?#j?TE1@J>3b1}HNd+3Y&bFdYkR-1lOCRDnYSok zvNMgwlfkWh7|IF!_tr?u0L%<)64CAu>M;|Ll$$E#Y%i6Am2s#q1xD(XoVzRVRw5qb zP6hyjTcFWlkWX_(HhC}+Bh6Ep;?cnm@%btaB}zUolJZFha{+TyHtK>y&&|!gEwGY_ zlDo>gXfuP-Mc-~CrfYVF+SItVEvtts&%ga+jv(K2XMdk_FLvwql7x1qR@?k^f$={8 zKO`yXV9>+R<3AGoQ8+hQ3@S)&pB!jyv`-b&0yN}H6uis4zKDFg(`fzpxb z9B&Uz6PWy)CEN$HbXJE{X16?GQa-Kr`tVbLh!*1rMDT^&%b4XS@SSzst$tD7Oe?o~ zRXU^*Y8<}TTwNdg;mPK6|b%rH8CK~nJ+1VU4sXv7PJUU zL(rn#D5b+%T&?CxHGH_3Z}cucwAXc5lhj zX<+q+yQ^VLs926-a1v0iEUV(7=?%OT>WF-eRa0vT3Ov5PY~LrN=dr^S$jhf2VlJ+H zO{s_L)NIm;u2JugKS ziy_BnrN!XIy0DTOC2d0>U2|yVOd4LvP|e4c5-O@L%c16B@k`~Ek(w`;V}!=rLL4UX z_4Q?ZB9Aw?i(cNUin6@Dx`a>}wryiGhvpk5ws06?xW85O@!MCDKS6jr)^q8y3ULqp2B^GpWgB!6 zh(z|`3&^M4oB4M9N*1b>YzX(r7m5fYND( zEmHa$i`xi&>%$g11T!N86WcsH0?6nY6`N8g))>(s3mfeFsB1&(uee2HE?SNxkqxG} z8H~8EP@Ts(qw}|^yrWVrB0&9b|%WiB1WB& z3@}KFJ+K|@3e7&O?Dniy1p!mAs#dUB%B)y;i@<=^Q+Wx6^G#y=!;hJKi`4JWGY4Wc z-!fTf9E;Nh(9TM^5P!~T?>-QV&#ghR)C&-b73?ydoBKZW{laJAC?D@PRxR|mw57s9 z7j~xMpDS4n5HAos@8+?+KmkDylC5R`?eeZOFO#J;^MrSk;s8%bTVveMSB=+0?V*(- zSvWm%;%6R!sq_6ybL}qfa)r(${z}&}^EP_LkdT{R^>4_tli^jri{|Wrx#Jhgt#YvRYc9Q)G2gY$9Y%i#XWiLAT;n%>Ln$vIjxZA~Ds-cj zJoQD0>{38t;6#V|{YYUOR74##SZl*v#Y0`_>>+YyV-hT|8q#^-SY<7+Qze4q&=I_9b5sTlFS!{q`b3p5xuMjT*G|Z_ z7z}=Bem6W#BXmd+m3MF_NGk6>#Q?kn#*oqcDOTprC7Y6jf7)&y^~O+}zehQF=CX;h zGD2lI&bz1P;HDjNWi1FkSy}zuf7J>&-p8>m z!=MsxaWeaSD}KG}^Qak|7H{Bo{FKSgGl*Z+nXe4-AVqH?6N-O>>EV$G2&V1U=c%&v z=;+eO#utI(mgU{9dQv`B@I4ablQlL>DKw*B&{03kit+Q?(;au&pAkF@CptvGA(TVv zHKf0SCvS58`sataPM~)ckmsoooEDIAY|dHZ-}b0z4OOWMeuU@)l`%&kw2(!<1>yMs zDT@n}HS*TnVvuUHoN=jx%D$T5wAogUy@^9mkxf!DrH!^PfBOL5C%G8<#x3CB+Wn7B zyAY)|OD<#KAe*!h4N2MhD&Lf%-o-#v_+tR=I0upTecA$<8$K{nI~cFlEzlgg3PWV?oPZt;;p>UfBTa3N3g&xtZ8 zQ)vxJ=nmpFm&!M0jMOnxzKUGjwb$zcZ^o}f!iY#uLHJtiqlJ$7T2fuxO2xAZD=Cea zL>MLay_#oD{V8!{sL2X#vh!sT_jimF{6lN3Jw!i&)b-WKXL{~tslg+wvh@Pq3Vm$E z`nKWp1h-K?ULDgJ{JZ{S5$EeqNruoY^K8u26x+W`noI~{A^-XvKqlAL@gyb8Su#Sm z!hsZUzyYiZjOGGyjOaY(=$lpqE zk+c_9>Bz5&ao$A24w?(h7KV$7QsEs;p&SSCQ|3LKaE6z8u@H0(6ka9ggiFeXAPBoh zwyQaVyMU@m6!|2;_7Qa<*184Yn62ggOkz$oxdM4IO&o5_?%8!mE15hH&RCp;f=Jn% zwF|+uS|}A+5CPf7$h1*Gt;@4uj|`tJqT@HeH%^T%{#89Kb|4( z{!1J`@a%UTdd@Qc$d%IIN+WhLXcU@qWR3&FJMmS^FoCg0B1aGW-n$D)lJ+g_teCcs zP)wZWd|HNA4}YIF>OF<>s#M5-Si!-YzmNU^s#V++e#$u{VB83o|Rp<0}myuR}}r>t5#QEh`W$*T@Q<+eC#^9F!(p6 zSssYK`AMQCxUcM4a13f?#+-dB3gHeSYQ9W~YW=t@lAmzj%|0-80&uEOdW4+)gTt(J z!~3mu&(mzO`g*qU*FJ%JAbER_LkSpN5j;g^|4Yh5DiP^z&k^<<9Vj1ul(rif6Ox}# zt&06X25)uY5b!fTv**Tu9e%$#xv>53lvlkz&ks@O$tK9EePc08Mf5`wXRKHv65NH4 zSA1P}P{l zZa_4AbO)37uO1wZtTuju6H7vNSNFS*hqZrxvuR@CfBAkA$gZ&2g=+owP?6@UI2&Jx z9$e3x(B&1f&H3RQU+*!7b*S2EmrfOvL_gBBoUDKzgU?wkG(_ zx%HN-gj?2W7!2>nKWzisn6aoI$0!`uP%0nBy$;Hm{V#+|8C#fW(VqH1o2s2ypw3#xa|i4#0Hfcgt5Ms<1Iual?h@53TJRQkTrF^;Yge?KWr zw@~rhQ37>bgY=char#en?%6U-0G!C_&4`Yj)epxA@#x9t+cm=))*-KpRj^gO^Cwis z=xYt+Gv>@2o$m>;u8ftx95ax%iwNXhO=cy7)Tw{(Y1B^0Z9JUkkc^&E(`CVz-6SSY5oFv z^W@a~2H(z4RV}3id~YGMQ-A@Lo!w20R{bA}VXBK`$c1DgP4!n@pt0z!gitke7L{$r zHKU#NtIp|_G$H7p0aYd=5TXz2UWuI_(ug>&I01sOX}`qt!mr32H6pq`0Gz**%rGWh z_Wb3#^Ew!nJW!(`?kVnre7*c3TEP<6MdH`DTmQuqc%y^Ne1BN5Fjsyj?zKhNL4{>9 z{qA?_0$Xuzv0Vfw0kd8UY@vm7X)B-Zb|ZN>CuwbHr>9f~V~ z2$-?mwYK?6q=5;f{zpe$?4G60PHVhY|AEBA<~*wgtDjFnaEyB+p(lUb0b#jmBB{#@ zs20Ff`MLG>Q<gU4(34eJ+R|f7b?KJeRqvCdf=ejXw4j7u%pX>#<^Bq1PDc?lS(Xn-1b2bYFXr~T(!2> z>;gIM8a;JTra`UrMzT)EtCJEz*~`9Bt{(P}1EHW`fF!QqmDbAAN?;|MdaaoL_UDJO zkxyqE0k4sEzL>WhAWmLzyGwv!s1QwT8P2My*G*>kXtZIGPhtUstXfd zMt!lY(TRk}@I;kn`t!06jn}qGt)1-Nl7gUp^;=G)q~5k}R0BHom76D@)Eae;vn2Enk7y`=v8aEThEvw~H&M(HCAgqfi+21;)jG%dXoobVz zZ&;@gdy99&ddgLc;yw;T6agxaB8l=kRzppu@Omn{$nm@z;o=kT-Bkc=3U)~JHQp1! z{h)SAM_7u1-fBW)BFvAo`1LVOwfUz?V_(p80Vb{&0|P!xm6g#8zb-I zw@w~^GLqL)aY(y%NLhyTmpkOv(L!P||Nb~7@|r_ZI;iUI;xsk6r~DkbkGgN4t#Ry>zm?a9_0$gJs19bct_$MD0_)wcsyUQ$0jWx%rva^YD6&c&Dy0Q^!~1< z)QW9i4w-rWb+u8r8B^v}65U}&Rf6m{2kt+FX;SKPZ{|0a1QD%&iv|mP2{iFB`9!Sr zWkgKLjb|Z!&9L$Cd)kXvT87zcwt}Q}_Kkn06wr^#1}rEA9B_uWkZzI+V_${Z5zOa0 zgKtvCT@yMIRiu8a_cJ^`$3E1(GH2FvVkH|V@0~<;@us>#5<{={+K)ah>upfee=^aP zL}iRPHx$81UNII2IL*D)L@1yz3FX-pxJe?rP7DiZLhlqdTL9haAj)*c+I8&7l}rx< z(w-6@m%1QjPkkPf)QBET2r=$l9jyI+S%9__vG`Ng{rz5ejo14FmV7 z*vuub6mDN4YdiUli5=z_>N3{9XN%GN{v-g}*XYt;fs0o6evG0Mz)9s${iTSXV{Y8; zjzugw==ph*@7l|al{>6YAU&Y!mW2@N6OC_Cvr<<{&B7Q^{WHgl_-uc?HhIX#9)Bh; zElWsMuf-ngn1_(8cXrXpbmw$*y+tMe{WH#bzo6(5iEGFSK|`I;EP&3Hu3vK1gOy9P zbik#vbPh*kOOy@QO5X0W%&mU^^U-_-xjX|uq0vw&1R?ApIhP0<#uN=?`~deQiv z*$FM=Nx+M<|0$5?rn%74oeYH4|6zFLN4C8RoX^!SC)yNC;0mm|tmq@~oEzhlccYP* zft_*V3H0WcQx!(lx9wi=;Knfbs`e`!merhyQ$Up8>4*t!A=Fn2tvSbr~Y zZ{Z9mV=Q|}e4Nnvm#$xWd||J4FryXZaAKGw0?)vBO|53{2sKt`C&Eu^KGM+41Cua5 z@Rr?{`eU2*-@oam)v>-89dfTK`P8$ermKpVi&)U7`WlxsM|FtfD&KS0R)!Gtj&$#c zCkLYs*xOcRPb9YS&sT^rE#UWIT(a-~U`&IA%VMDlogwZmXp1&f@z0%h zb)Z{--hT{y8ups*c`C&jY;EZ`{n%4-#j=V`@Q+KP`4soxF+J?JLv!Kyyg1vNdbZI% z`bm)$frCD2Bj?{|Gn<%b{kWZMSjEuqs9^|4$r0#53_d(^4H&YOo#b3?Iko@%iSy$>qBt_{%~i4o9OkG1vg`XCOhh z^hu03g`l=KzXx%Cx}Uj`S7VP{*C?^_HsrlA_r}-+y2JmJsI%&oK9|0Q;z2(vI0_S{ zl5sNz#;}v2Y{9!;$tC9Bw>F*!5|qDXq*bAI&q2FX!6&(CDbYiIfkE9Cu0;;W1p8*LzoG5Y z9twY1hG@+fxZ76+mb+I6B{3(x;rOj}h@(&a5{aYh|SwRa4eHTOt@i(5NXrZE3bu07DgJ1SFn^Ud`tU~>e3mXZER<~K_39~M08*vv{bk06c|Rw2k*-RMsuZ|L zxxXJp&dkGpZUtCSA5_igMHzb+fddFaTOL7StuQHw{svTi|kQe_@FcWQ8ni%2@^p-|jt_tP290}N`E^lZl^>vKg4iYC{(-3M0- ziEC6~%$nuxnDTA^kW70+A@92jGhOklUpe+`*YY|&-p63Y1lCi>&ta))SAet=NjtjC zm^~Tk)sEq>NGmjD5e}3iRr{{(?LWL7N7Tsz(SrGP-<$}<6y1fDr-~G67)(N(_7#RC zd4Ai%vkPV7Is-Gs%yQj-^Pj8HNGM>9WLD^{`(IqM2z`Q%Jefg|z@qO2QqH~!8c`YW zCyeHa43R#+WQpWyC+E2|Gc*KaUbyeKK3~lzgi?vBD~veVniA--ZAlM{uA*MK;pgMU zJ9fz~HtMtbN@kcJEXwqX>8NHE_5~$ADT!pD*4bykH|8=^4iPoehYMy5IK$+<$K8>9 z!>UrP1upMD?i2+M`@Ws~i%AyDq&yU>xfMBhRZz`08T{nc;CpqSQ`_7@mtD%WdHbf* z-=YqEORx91+M-2TTB!%gi(M7!H43mm5yflo8jo$;N*Wh6^?KIBQ1xu$TAd`GvTC*B$}B_GmBR zN)tyJ*mEJs5+)D}*s~UQO$MEdC{%x1QCPymkv5!{ld@_p#z(j#Kb`hS`6>XV6t;;M zWpYU)f0U;{csh=bDRQ}s;ap)yCMqn+-O?r|+`q>P>!)oIcwj}ltKSj@BF9z`Kid1u zASt6%1x=nSNgVXRrI8;e|DijcA7C%+uupW7&gNm9*k^BAo4)zrmwoW7nxzEaIboU6 zW+UKqu}OhheGj7N&L4ZJc5>S_l*x$bHl61b&PPboSETCPBvB-MbM|D^Y7e1MGNPQ=LU?pkMIkFcalIBXC@gcWe8 zK@-PJg$FxR%Me7R%5LpoT9~SAwzvs0?E}icP zm#NDIeZ43TAYOryQdTXo8XVzqTs#S7R;EA)W`ylL*92J&1q}^7GG3TQ{({S$3Z$9) zu}03Nyjm)n)eKwr95)gtfhbxSf-kSmc4)AF;3-y3Bl_8-cLUYnZ)E{and}FRPS8@> zQhG0OH-KEWZCsG=aBjecScKa7z1gyT2Rk)6XshOanh~c#g#(`#;iAFzhLBzPSpAcoVTf_j5O`V;8l?3TAJ?x7Wh^kATgA zHrM%8gikpi6GB}M`op2f#bp*_*Iu4_rkg|V+Rz^bJutq2&+UmgijqM#rTzOM zTIQ+Sv{IjNv^wNSXBggp@;QofrpP9g9EH8rOH==}8o}Kp7wYTo{6#wr_FpZe zJ5bzhnVPuR03$3M)`@8-Tk)9RQ(jXjKS>I;#>h+_HM|}sX6WDOYM=v%-f+QXEkRPVC^+QLL18J0a-jIOS}<5(9t)zC%H_uGg~s5ft2%R(#%n z7N}zY;_IZRJ8TcG{05}*5xZD)H{fDsuG$OIWaj}*3!Vvw1ylCT%6FtO z7We4{GpdwR5eGq3b!xTZbReOhcRmO`ORNpQg0`ne`F+hsvIP5F7S8S7#rb<*Xfihm z=~Aa9vSGBnXZs3!RdMO9sX0Yk-J`VC(1|2qD~b?(4{Jon)5VIyI6Cdx^~-oGE}?@( zU2-VOgu2MXmU54C4&K!ruER0u>7=f|yC#CK+b+#z9KAU;^+DZD1LfE<0xXjt-DMS! zIT~ToLiFm)GIxxAd-^kJgb*xPzh}3Yc;uvG4YKWbt&zU4zCcz zX%5rokM~78A=4S@{V?)xwzq?D?ETS^cjV`M6`1;LwbyQE@zQ~M4$C_$r)u^AYKwB# zZoi@SO>C@>18LCjO*-#Px^j7N_y01&%s$0nWGN_dMI2g?<8fe^VaYDU!=Jo<3jCqf zTWIHRvnN*ul5p~)|2B$)8S57ZE1o{RiR)ZFHjpsG;5pxPj|Wa84rfRV`VadI>@(>%Ackf{*z9|S|u zjz<~CKo%Oz=Q9`IEJ9h#o8+tG3WR>1Za(MB>(8zGrznue)&98T%-@&aA-@>QfS@X> z*&kc*gb8{q=UU7-TN(ziL*)gT6+GON&~T7;&M!14@2;;WaA#u}!4IjOrluZ5HVp9& zuc9@P${j(eS`*I3x;qq6$`wosdCNjd%(ofq>puPw_`d9=iX%xqSAl0t$x{R;QAEGC zlID{Sdx40Ah&=jzslcm&OV5c$N(+(7Z$!lt6ggmUw4lL>V|(TH?X_`Wr3h5nmaMXJdX_s)1#RMn?x@;(9y5@`-nJvVye-Hg^Pt z2lw>$t{dR3Co_LOcb|sq0(u$mu)r+T;>h?RVd6Z$f%Q5;gvL;rjZpP_0hvVJ!j3(K zTnT+gN%337-Y#6!tlt)2l+TUQb1T#L?p5PiJ?5QL!<%Ru&57|?StPgw`mLi|lSrc5 z(LsNWEE#L5F&Hgx%v|A01XRi_T;7HshAAywV_g?l`k}3pQ_>MjNmAVMygDHU=*LjJ zL0#}-A1E|;6yM?~@+;VXWG(8CH)&Ff%~mn3uyLVxjNeGY9pC>&qhAfDvGt%o{OjBK z>f|l93R#BMK6(raaEij38|w`5sIsboz7FuUW}HWo7~5z~B3Bn=LVtRGE9y`#bypoKMn0#I0 zfv`0`pwL?tYwi{OM#r!k@{@eBueUq3?DH1~Gpg4iJ^23tBtYB0AFbaE?%+4VE9im_ z##L5uHXTwTR_x>HGjkruDjJGTz2X$YI^SBlcrlxP*Uq@6YGX(2OJ}$VEg?!TAt|Sg z#Ly;oAwDAxcB-0fPgaw*qnzPC-8rek?#>d#)0Kc6c~1Ppcii@g752r3QX z^qG${`_Ifczy3CoX&0@X>#7~8_)395YhR}d0##PxFAa;#IE?w-63xT;*nCCCx=-qx z9QiG6)^A~Jvry2m(yYEdM$sK2R_Fe6ryC>1Hu9{hmf8yrY)B2U6pW(|vlHqQusj+3 zD<9uMUwh}mA*{;SEq z=Lri@-Q+z8ze!AWyRv5IUDbM0TX7p@t;{JC0JDrPE-WLF71{-zV_uBNTB!rth-5#- z-C;$vig(6E?pL=35_p1X0q-V`XW}gPP#0%GBfWoz+gC?o+_?f;#W9`! z#UiHrHMm148;G6V)4-QCrftiSUQ}R*pu`R2vGw}TZetk=c+>W5tq0HJMw7N zl*>*z+xHhqK-8V|I{AanbbjDf+9(X9#|GrU5~L&wc30?SnBESRnfJuxJQb{n{rjq5 zzd}fl)a#c%FmQI-I0r{zX1Iatj{Z|{gA@iz)5aheL=NUv6O9MLj(li_OE)n*KI7tK zjV$${5IC~4QI>rRVcBjTr2rhT@SS@3o$Kn;EZoy7!BKR}=z%=^Y|5YIqdClu%w^Hl zt;_D=WfH%`62t!qG8_FY6U*d(EWu!s(>w!$%`n?IKtZQ@={U0^Lmrj3paG((s0oys+b{3g@15x6 z`pA&DJZYSM8a<>n3t(y&N?GvXu2CAaYi^oL&}wDY3J|pIIVU2(TWa1Q%8*i;PT~Lo zU{HL91DpBfBK?B(0I#?E-0^6*piXC{pt<*zoizVKc4Q;RZbED7Z?Btnu$#km@Rcr7 zdR=*QHJQhr@@TQv_gHQ`fBBxyG$My?w*u+YN^6}p4PrTQ@2VUUHBiS_oz`;M=T-|T zFQ$H-5oO+w^GlLaF6=Pk$!Hu5JyC zH9J5*H11J{jVt7%kb8((ecK?-Dyov~|ZJ^H=5 z!l-(Hb~|R{^*dLh?7XM5(8b_Oe3FJ_&Of`gpDxHd?0E%w<_oBx?HS7@U1^ym?DQ%} zk2z3R4JE`I(GCFl!Ku#=mEuqP8QSOa)H^WR%v4f55DOg+S2pfvHEY?_sp=gg$FdR| z#AiMT$@uSxu^G)d)0gJdH`#A8Wojg_rV}j+q&Aec6oA;)ow(`)KA?P*%Y9a3D)f=_ z{I?lfv;JL(o?ztuO$FSj$_NLK&G)g@EBzR4p#}5XtdWbJ!_i6$KQYSP)c*8Ba)bPl zUdx44nK4#{2A)x}Y&D269Mvqp1lVtPQ##LThn zCg!>Ko4F!AR%nmIb4qm40=c`w@C2J+TcTXG>TWr!!(uG$Rz=(jbPh_Oz?r3h}Qa72ag02xtehEpr>gzVnfZ`#bSg+n|Uj zJaZdT_(Xw_0e{(427*4URrrQLY0dh(n{*ur z#M)aY2{7Z&a_yOwXGLcbR-}ekLHu{jnnAaa5smL*$O2J$--yfIB*;}1k6CHd4l{<# zImGZj@0B9Cl-BWbu_G%q*AG@@amB`&n{?k=y!C7HT=<8WjpS!feiY4nvZ8@x6Yr%Y z(nBx5Hd%)Te%|QPgD4axh$jD7%W+N8p43rf!8ux6y3ius)e01Dj9T0_ZvqzZhqo^` z9CguFESC8~we#Uz8ysvh%ZC=$=*U!Ly}zem3mYVy%HW7H77^rFYuR%{{yOuq!G-bb zRGE1(m)1glTm4KyX1T_{;Ia5S8&Uuk2{Nse()tt;t{NHe3~PmQn2-flS&1r&gH%7Z z14sOy-w#yDVu+$9E~0Y$w`v=JxR{;ZSTILUbr~M4Y5~Tadoz^&Kz=0EbE27`7~i8C z)-7|pFqq@2MCXyJGREJ|PGCI2`7r?OWXMUvbzYH()EhE6o`V1ro@l)r%K084nIjY$ z8R|ZQ7Bjsnfcd7RMg?^|vmucpC=`MeNl{b6odF{eYlYTWk#BdIP+w2b-W4Dh_({2X znwg-EN^$cq#R6NmD*fbA+gQN6es+VwS`-J?q?^;2oD}fEzbr}c;nM+eJbU?Kd%2?c zlOl2Tk)p$|pZWT)AmIrXP4;C1DN*z7h^D(HVVv-F|3c~WwoLWGwM>W{Y0JoP30*4_ zy%biC$<{%a{(`T%5TjhcxNlLVr+^<0Vnt9P+zS5U!d7_FwCRTxcQ$s~Y_keJZ8|05 zuoxoMM{Wax7a5p)v@7V+Zh?RIp5TB!JaTO)B!mp|;sVWqB+dcTN1_ zvHN{6Cb9Cp^nx3TAaZ3qMiSdHGOs$ED#R-mC!7Ili;!I+G&T-H*hccYsNNFAsCy8O zVT50RiXV5|+X;wD^#|MxJ}OV{l2X~R@M{MlJ$xeYf3hujnDzl-o`pSRA*O5n`6Y#L zU%i(T?f2^SkTy+0j^hi$i*Of(>`ZvEyfv&YxMbWVzX-i!!FdC28BZp~y-hk_TEaE( zTOO~QdHG+>8EhmJ`&8^~5kxm%kW>#N>>QshajTOg`E?U2ja%LpDcF;+(^SV1*B)aEDQ>6mK!E z+^uXMROst>={A6x`**v~EOQ3Or_r58yb;%S*~(At@Ubb1-p5A{a?-SXYL>evVYM_IEZC^%_)xVIMXd3G3D%AFGfv!7#P+ z#r(fqucJ1;oG4uY&&#$0Rbnetj+E{oDe)TthDFKiH=fsC7gfQhx3* zJq5!zZJkqo;-sHv+=tpsI1Qc^wg^uVhy{lR*?jERNe^1FVQ-qQUTr^ra6Q$=zO8rX z<+R2`4bk0DnOzV_Iu=z`)UQ&ywM!|v0#1S69JZ1-Lgr%N2A%$D%M%pdP?MYQnP@hp zn*SLEAW~yE!8SgaV~y|%FSTMnJA-5B1f zy(`QyI^WX{2S1n>-h}Zcs2OP56>3w-@tzyh^Hoq3Ko|0kbYu{&{cR-MH0_K_0HzRja%;pNPY zl~k=`$w6+-V)Eu9&zGidIt`OVNl9QJxZ61}CH>PFBp$qOrfX}Q1usW?za>l)l7X!$ z6Bxdmd@hxl;5-u%R?0(;?Dzp;fipxpTdt{oABLXj~#o`VDKo-V{7GrZ1~k{+XQc!iA=O zd`&@`3kA*3x_LOkn;%|M?V$TGJFBT3!jU0;%VBu@U(-tk$ZhSR;jI3xQ83!@#i3Ul z6tew01i)9-EONAzjohCULBK}i+M1hKYYgZ%oS*idbP&~jjh*n|>OAi57djfdf<5Z* z8F5$f7NyE8{_E;#Rmynv3oFLRYG#*I3ua_uZh#mLqMNfeJ6)2Wr2e|I4SnYSP|5FZ zKUfz6a#C??OR!MP^tt`~`v>(`4zg;w z5(xRBW62(cGC1@e&XB5BIy!&5zcWw!vX=%ehT&j)YBZJ?c=SZR0uZJFv=&Tm?>-}w z&LbT7@1k0=`?)U^mymeFZGqQ}f3H>cV&4iQE4rD8KmMpEvH7XjZf|bEsP#T` z_6nf=qA4BtBY1Q)iBU1sPB%;|N$H6JocS0nlN;Tf)+%qN9&1qS)d}vj zrO>B^B5W|IClB2nd@Xxodx9H3T5^Jp&ib0k%%LZYSn2&Wg8%7uTIn~pE~cJRvPI$4A6dx zEE|GPhANimmLTs8^`n@rJ?@Pd6A`P!pgPII zLpI`osXYc(qD)VmCnAQM33_!+bAv0Wbfl-Z9-8U>>z<=I9>a(*#IPNU13T?#`sDRN zqu51bjoi|0j+iI95Ve84s~hB5nFO^|K!7_lUqP{jVifnVb!pt72{f%+gx>ywDBTFAiQbG&hK)M?0G#qUYNpqM|@Q)c^~j zUAu~a9-UM1QTiQTqf5{A+WR|L&XlHW z=zv|`XMWA12BMV@t)x7scX{kU(}d$kUO2#i!0B`LpBY9>H_YfPoOcwF0;uFI{O|jH z_t16-5Z1pKNSI?&=Ti3^RMAP|sXk~3u(3)5P#5ojKw>O3Xb;?mj5I}$m<3YB(o93u zk>3WB`OEDgb;6f-`5Pf=2Hh%sYo=fvhTap-4ElE$>0!|>D}nXm{y_?!YxsQgr9VI7y<%>ssnF~}o9GutJV z@^tm5+)$LsBf$wJNn1|^yYQ7vVw*y@OeW8MHzanu=SM+D2qMQM1BurPUY6@hWrRqd z9hwmpy0|+S3JJ%cN{(Fs6DpNaAp_PsnI`i6vFvah7L=F2cjMAXm&-who&x7(?HQrEF^uOf^Ap;9MhcE4`|GeT! zz9_MpK_B*-TG*sPCmoK?Z1PMsd{%TSl)HLlX6LHV&1-wPo;@PuR0D#x(?n%RYjC+2 zY@eNYQTh??8)0L`HFb$IwjSVd^JN`2X;ZcI6INxqDvBxq(Z^qV1d>iT+gc9;sWeuLVD%^tR<4vM_ie9^oJi}$?x-nXlbv+9T#a<`5QH6ry1TMrhoD&*RDPXbgW}LI}A;urAtU9Z-W79jn8H9KY zL*@pm(?Fw^(D8Hh@kh+ffW2?nN2xZH+a8H8%n}K@>koT+rRht(mAErVwMc2tKY9E- z_c1fCR8}8#k2L1>nfi^Yoi{ zcI>iEPWD$+F2HB)Bm_)#5s#;m`)P13f@3+?$O1lJpk$c)h{33>N3%AAaQHxQr8g&_ z%AbOK^QY$lETV0@ZNoN_Xk6)Nmg`WF1N(%(&AsoY$G5*$F{D zTI7<3s|me|Q-c!g(Q|jp#hejOlTiuj#fX@Wd~N<(9D^!7$*P`e1M#m(C?Y}q(gYJP zBQmA~q4_`d4if&@sXH(}Mwkep+Kp9YT3tLvEgsL}m)M`KxsWh6JVa<{RuSJ-;U8NV zZO_o1kQeaFpoN~=*-RXGJ{AR=DF%WEz*)lcBcvzY@&Cp)cje@Lw6*{ljn`hEyR1l7 zYXtAvLkyRCWg6DY~A2Y41&8Q*2%h3#-1H>iD!+1~HxpC4Ir}x2CRoav=Gk8EZDQ%OSq@Bwn$q8l^XaNr zPM?2{Xhrfawn`?Mp4Hky^M11JN2NU1i+RTrFmsE&<%u48JoKOBNFLKT!L84|j8tKe zCl1pt=jFL8jfAR?;J*+zw*!_j0@o&Z)C@Ezb|b7()&{!RIMT5RZFbcR6Ri(O@#tRu z^h@2t4v^o-X*OktoO4nQN;4jc`5Oghek8hN@RGEDWZm235o%5X_iFTb#;eA<+~&@| z2BrAdI&iLvA%(4mFHtv5l>%SA=fDP9lp=ZKOx{0prWk#tah1E>|36;QOSenC#c12j zdoU3O-RNOCa(}fV@PbtAsJm}L+b4`CG4RAM*q95`HyT2Wa<=Cu&Nlb>Rs0=~w8R5m zem)l{+gxohub)S@q!@@=+X&6sgSkR~qka$t#at|uDoih6-fcqwO>@$vCN-v91RwlP zH7a_jdL7iPDOlN&+6uD{ECxZ_ii%dDrZ_e>k>Xl0^*jjtK2cf4rW6F=$@(-*2}ESc zPyz8;A?d2`!W;^7ce9=>G_0gr3%R8n?TQq#0xk`*JG7-E2g`juH=qL>v-GiBNAB(m zSd@bRhw~47rgRc$kO;O#8h%==5*n zY~Th(2ZR;EJ!obv{6E z8dg6k9-_<7;Lb;pd5@@NrZo7Uls&To(!I|-bqgx!3M>XEKUyEFf;pW^0YS&8r;uIJ z5D^xu&)WocxHA`I#Ga5phprq%3B99)7jffRYuZv@txWC!gCbHjmE2gk7$T85*%zmL3v+ zK-Vf>W^b!UsQe_wf-UL-^E;2cz}{Q9HW`6^tn8g8P@WNP ze3IhCTYcGlLKi1TKQ|`+|2?9%@q|jE$2>c1@|zxXA|GEK>Y?{*%8t6T;wh27wY1fmf#ce;+NPC>1^e&*)R}r7# zbhnpW6A8{or*QVPW6h*X3>hJ{!+??J~3BZSLrl=*RVrek6ChHipf43J!)K>gs(6?I4%Lyp{T)4C7V zP?<)C)@S!H6S-`*{Yw?rUJFwyXu{ouHe zbB#sA!>)FPBe!>wh2?OII>zzOT3@P_fOo*U`5jY18wn_=gO;m5xofylxLMFMbg0&E zRwU=?a15+p-)KfmNSlpbjypkkR)O_p!cau-|E()`I1qlD8xtxvJJ#=-lx_qQEV~N3y1_bn$;J6qGRIPUe9!bi-MA z1|Q_3MY%}Ju^troXF0}F(z^T;fB8pZIt24w{g8)qOf03w?26#cJc2Np2%A*wJ|xrq za&ycXNm3sgPC6@Ih+_+8KaZ^oq9SV7cDT}B>+iLl7a1FSgbn}G0k7qkkNNs1l z5I<$hGQ1duY{Rw#ITRN})-vFqK>Du>RkOYtrhWlp49f%7Y794*AzN>&``=C335@Cq z8Xq`_>G@-@^;1K@zHJ0nzK}+aX%I_r(N}T`ij|QM7*z^U>_BCz&gpZ$oi)+Kec)D9NK~XDLfZAu!*u?mVYDXD` zY<=ah@mn_jQhIoSY(^=^4&yzOG3pR9Is;yCpFeyXXR7lRO(yoMG z`|M-hL^NqiN169($bZnsaIvYL8JFP+ubhoGLD-z%_$!NGi{6EzTvHDWcG}rwVc4V) zNzWK}RZDNzxzVFeo%*02*s1i<9M@rfUbJm?NF% z+iWjn-RCYD?*Tc9dAV;rxudJ(#l1|#U|rWCIKgwb)&1#McB;-bY+CP4vewz3XBTT| z(CA*U5BteE)f&e6T2~xNpe>}NGgprCkYDnn)_j@+1-LSBxqa#-M+9Qbm2avcHyt@> zyg!zh2QUc{G`fqxSf2MaK0=19@xlOl*|E#mT+rcP9=`sfrHb01WrT}87=X|xA4>>p zR;y=Bb`QG>Uyfq9uJSe@Y;Tv1Sakqb-M0W9#Cd3H%8zvb^a;=3hep?p!E6!WRIx3J zrYVrC8){THF2gk9_;>inQ15>FZIQ8{2xsx+K*DxY&?sr0e;h{e27f#>;q+MY7wtg zwmXc27Z%E6bIe1b-eC}Jum$?#U#AoBdHuQ>`&l2t{rlN!*vFQFGDZrw3iIAcZ##5+ z0Y*{2#7D5pFH0w9IU6}1v6aWQW4CB^a0-e^#KD~kE3Etfd$~(=gWW$n(Lu?LvZ4TA z^8_y8euGT{mR62b%}9`M$Y1G?e+D+%`vIl!$y(U(EN_b-Ic)y#=oLyqpRVJ{%!7*F zHjq8PN=0bAhITakrzaT^2INxh((C+Fv7k0IBT6BvWd}}q6 zq{OGm(RmsjZx|v&6}g5UU|2Bm8jeSr!yS^Jp{3MEjXgv$=%@W&tY zV__hwEljIC^B!X%TpEXOAZDI|;#N{jO+D5oKbl}pGFTmM57U|#zQ6aYdK44AMxW=i z{NG$~2bu>mB0K1CW=|Tsl%jMmlOP}Z_h)xvTLqO*p9i(KYBUH6|9*SsHV*@KBevK0 z>!W_w5w$T!1R4vr)x+-ZZj*1KIXoy+Vzv&4Dnt>@Mp54$b6oMq(en~{`(!o;s>e1z zv#zVd{BRal^*EZj;}RkEUf;mmadCtC%v>*AFOf3_udW;-bRV ztgl%+y%54SPB7AGRx|GS*|g2&8ta^_pZ<%4e!dzshK?L;U^`31ir$$ZnbHEZuSZDG z(Sz?f&h)MU%@d--ow*@drkY;%Q(iX8-reU*9K&t9I2@v(ycV#vAUgUXFkuFtFQWmf zCN%e$P|Z$9u^phfw0}e`>m8#lXScTu8skl;k@#6eL}A z*B+-Qr>*&K7hVa}qP4u)NNEYi5s|8T?s~UZ;$Hq$Qf?US2 z>HV^~bS0CWB641yZTVJ;7&RyIDoejF1A<;e(RGqfzXA`6e9f!%)jdg>yD%1&!Kau& z0nz{fM%>-_`lh`*K&*1aAW7jOr8H;A%hmU!U2hcyx^icv;@r{2hnEAE~$PoWdzg_cL(ciw_pXz5*H=bjK0RxudGcc;OwldwC7 zkYCeQpV=@&*Fu_@^>z~(U9kHeo6^Uuf@KjpN2V$u9lwtjK%;TakdP}JBCea?lmcdy zUcjPLpBmMM0lQ)UWU6V0v z{vzJL9;H!fe)f}o8&7EJ@_E^{%g(r`L9%30e1kX ziqI12{|95;ui1{N|2Znp(bZggSpF17kIW2(kmSPtJHz>gY7t}lx4(!$^z?r~I9mAy zuMhaCIvN!^CFs#F;E5D=2{u5bXmcP{Pxz4tUIjrzww8^jiBcDAo7vbWzNGO2$~>S- z8EC0wLSS^(3snScY?)-kTJ|}sb_z#Iw5kh9mNUrb5lCm@(f_p z3@k`n*K|$;2~nC}ji40$iT>wbxd9zOG&{BHDo@89(3*cYPUoF%ttk5}ZBgx)A@%f( zBYs4S5J&M4KB=3@3-FWhYW%4AqabgdQDvj=IuKH%bnlT%L{!r1NiZM5X$0iQuh$#9 z<7NVD0bXXc&;+VZ$(Ix-Y6kGWv0Uk2Cp_G`!7jQ6CpZHU($@gX*^S(qL9xxzPR*mo ztH+mlRK&-N)B@uPrum*K6}^@3mjbi4LD3nR>LLskfJj?^!iQnc-om>qn5(ZXRJboo zm(E%M-~+lS#LY7-vr^g6yTI=s(nQjg^8Gk!5Udf&J`#)?GCh8@U2x~Fk-i1u#5Ib?V%Z2k9%n^99j}Y-@2~;4fP&aSqW_$;4KuRic>+9RSBn;uS~sdqcu|} zp4rQD>$>LO_?~OyEn4>~+EnDWNB3Gz zNW>NpIKbqX{jOUV(~`oDEcT9slJQ~E47i8(~nP{^}E za(ZWy+1BA`A6PUl4EL2#4aRE>sl8rwBJ_KZ15XjzsbBAy=&G)F0{+V%N|QguAQM+Y zzu5d~5@W*rhNTWn-DGUNRTWqE;E`no(Db~5E|wTK&ZuGX#kpYAzyZ@uh$8C)VlYE_lsu%)l?(=2{j0P30y9>i;|*gdlP~A)cyaJsdFo^jlJ+G0e^-D_*@BV#Ft^DC@cnjpCu8zYEt> zK7-aLM%XO!Q3M$9DC&wb{Z>e&0#s{wmtrnk@G?NjX|F2KD zk<8ZM?c;A~$c<)E3N}-tTeN_QQGG(9h#aISsv_@*2a#n( zczz_zC}o!aC39|IRfSos&IcySYnBc0wb$2JT#6^4`*DMN_%p<2c~=1~*pOUx&6Xl` z_zzd^z*yp1(CZ8Io9Z-M6&f zUUJQ7iD9GF;A#xCIPnO%d%di?c0w}-a72R!x;50X>DExUJv}bw4^;LYu43rN;`7^e z3-M{jYnSZMbmz|&@|m%(7I&A98pG&=yWfGmSot5<<^+*ZhoMlr3&Mb%@sz80(<1MK%ds!%@ZzoGK)-)Z)kr+ zsM+q&6tETjs3O62!IGC84}3^RqD_x^IRzHkOX>=^gQcOnG-4gjFWN%(Kyqn7*WA*U zI>9=cG21#OW-7g`YQ$%X7$wSuiIqK`%33c_d7k7W>pzKhN^87N&XRj-olu_Gm}5>n!(D&E3P??89vx=ds(2y zbG;0Kf~Ai+$%#UYvcaI-;o0}k8v$p0d3oHJOgr|i5V-9FTJ$zWzm204cRSnVG00Tt zBE&joRc957_GCjnd$c6Wm6laUi9?TUWf7D?-IxP5X+tJQ)J#9_{r9n{k!BAr@nV>= z$yxeACs*SC+9vVDC+XA^Rdd*E7~bldgF@qE+fNnRG+()V?SWjz!83NfEFO_~CoyY7 z9E~8LD3f+5paDCipGd<2=bZ0YbLu)~`KLR^fs%lq0|U~7tw7WA>?%Msn!zG7Bo-ua z-KSbbncuLw7qVogEpmQu=0kU&;pG-HkKaG?1z;BMCu0kC_3iK&<&;mbXIbmPI5T3o z5;v%8O%^>h^@cd>L%yK%H&DEJyWX6+9?kiFkQu-ZurUj-Q^l9SoOUi#VcPxVQ-_j+ zLvY2+_s>t5N;j2uALFMY^d|t`)Y1?-n!)ZrCXoqEQ=6BB`CY>sRUQ)5C z{yl+?CZzq32t;7J)x`728ZyBJlRT9qjMUQVuPh!4W(coRmT!FZKclxX73#zHxmLBD=0!#F{ z4*_0|2O5Cd?jyKAKvey)nBe~b3}b9-ITopx`(Ao=gbO(shx~`Bt9BGuzo_9jlHtCd zPm&45VdcXWUlhZ>)4(2ZDN2|+kcZ@=its^7R#Hu4-(tJJ>k(07(lm! zaBpvCs^8S^i_Ha=4ha0TSf=|1=4~><84?W}bPb)p%p}G--aETUaNT?8Za7jJCVfL0 zp{S1jBwLAK-_!~%BW>LA=E!j?E%?B*$m9wA=bN8a>|>&oz-sa=?M%$oVe-rbBc=}hQ?t1{*#THRhLk9?^PB;%fZqU?jNW-^g1%#7Mi;4Lt(=(;KP$; z_HSH8q^oGGL4y)xz=0`(M2p1FL5xMqe4|R>hZFYJ##zzAA*Mx}EO2XX@xhni%>49p zj>WE1*o@Ud_ch0WBMnyR@$@lz^u=~KYLjHeK`K~Id>T(b>bldN#bJql1yQu%4fQh@ z1+HYISWS(beKh}*wOLtkd4ta`D;JiE65{@!=ac3_FuB?WGsSYo$6i+&r^hG$FxkF4 zq2!V_Tmk)k{|4u~GaE*kl9~bm;YohVG0v=cQaG!&D@nuf_wswO_iS6}lY{7CM^xqE zXM!EL?||#Qu$B<6%pT)lZ#mh10a7sKlUfJE|J+TmQRZHo16u;K@JOq_UKGYsp6%xg z9`iP{48+gcqqjC~l4>J#rYtxwc3v`}Pg&%oOOobSB7m6f1XHWLvw z$wnj#zn+cy0c_((pa$8j#Y(XluKzHGTG7A8%B5xHHOp7yPNQ^_Y7hw#uHelOG2R7x z%ymL(_RS?A18dl66#av@FZ1m|Pl_yOv@LfgZ|jBtW8aCn$0=T@1aTUop%_`pqnx^W z_4MPI7APNO3(9T~eg^yz_w_x-h1ezmA&M0bIOz`ScG_c&|?7I-6I*Q&l`4W3+ZWEgDMfYgF zSj0=P=yRtHl>aqV(_}L*PiwEC$A3h*yCIMlU$n ze2DLaC3tI|zU$%pL#fupywurwV*}n>yFR49!Jvr7i1Deug+s7^7Q4hMC0$|)fch=& zzgyB|+XBn*;!X-21&NtEk-r7VRByMEJpGY7)M^Zo^U?pAjd|#5?f!1s97FP6Q(%@r zGn6OwN3yE!auz#W-d!YvI!JgC27YSRU#jIks^lY8iNZQ7kXh5yZUU~Z8hEb2Y**Gz zWV-aBc0nx4m?Q>QN+R5S;=wDOY!N!lsVD5}w86y-CvW@j5y&!TbFLg^AEDtG2$vMUGdRx|ZjOn=Nz{2x3Vw)cPU(YU$&Ci=@v` zACila?sfjxY<~o?o3pK@eX1n^wfYfoUx`KMEqlkkS9SE(37;+V+s~heh3Yc>-Rr#UMG3HTA)jsRS^EW z<=Qfc18Wd#l3zb{MQqw3H>szaxD^wkN?uL)V-v~; zx-40>ou7%VSOFwrX;@wsie;k=7-CJ68?WKhp z<(<-vYDe7Q>n%1sc?4>+0E(#+9xuz{c9|n2b+=p59ghor`f4M^lWkAqi{hZnuh@!T znZIh#+p3sAwQcs7CA<>b?AErV6ctN-Fy)3=^Slz4J$;Q0Y=E~z#7SWH!oy;!gscJd z^;;A+HnTQjCG96p5IsSA2Dj5HG5j+S9cp1uQ6ZEcJJ#Rt2N%OCcBQz4scavDksNYe zZlhh#d^+h8Cl&le#x;A#z)v}{(x={I6Gr6Tl~Clec|Q< z))mC9bR|@)EqwcfK*O~a3@tak$sHX^ zdY+%drB}C7B6c=FJi3`4mnpRrO65CU7J?B>{V8$OftzSjK85Kq3gg;<1V5>oaRxk6 zyaJuD9QFh?!9Y~$m6#?%lzzar%1*1>SX&W^)48^4L+~aaCRj%M*==Ag<_GS6C{N|`$rVy~=1TxI$OR*2XRYUh& zDZw@bgT8|=GFUFSBwyHc-VZGRurSLvh55sMF$F*|voD|Y9a5XQBRD0S`iiM#T7Q86 z?%;=YCX81h0)xBV132h%AXwDt#^?ztX*=3QGCGhF{-Zu_l|*j}u)&!T{Fwj~5J0f8 zoEd7^CDsKi<5WCFp|~7E#xBp$p6;A5;>KOrX~bdJKiN z*-LyxytB9pn!(EkAN;e5Z>}f04*LWzbsx=`05?F$zuE)YK|*yFXn6i+wK4IYr+)(m zDHA58CI%-*a$&L5k8Uf8Oe3{{(<=DY(R?YY@m5mRd0UNqgMie{L6dlFv$$e3nBA~| zZEH$jlm${ev2!yy^sASAm_&>X68%;oWD$L)Xg|!c9bLG8s<$<+HW_psN)`Wa>HrtI zQ9r%p4`l)|Kwp?X$^?q!(syf5O@Lr}Sqg3{d{|}FjWRt`*7^}xL=9*?G%L%a z&ITqLEi*-ZEh|Re+I%i7d27C`E^uMe8MC$Z*)tpU&2FIeT`3=ne{6Y0}iuSmE1Mj;C!KOsQ3Q9GAOQ+9Eb*W<(_rD zut|iAEnTHsWIQ-smlQU3&04Ozm1eQwrG6W|`>@3W11IH#P()4Pc(4DY_z%iaBLkQ; z(>+_z&AP^pdBljsdeP7&D|jJ0MOLD#x@pL}mq`a9jPBN68U?_DCska+1vw@#TtQ== zMjKecWHq6!{(_IP3V$uk%C(&)rWKiLN7ue?--pP|655-o_tH@=ej0lQ`xU%l_341~c8c zfI>-I-kN((;87FujN)*!?AOCmmv8CDHONIe7@EV1j=&a08y$?3R6|2riG^7NdQ_#@h`Q?p5-cB^4uLy!~=K ziYrSk6zf8OeO(_iD}zhwVbi7;?{^gk`lQcqgmvb-xJIRROwq|xzLa6H-QZB+D4G=l zWoMggo|rM!daBVEK=Mts-1zF^c&x0`f(OJa7&GK!4MOvr^Jt6M>mQxz#CV4Xh|Iq5 znh7NilrKEz;C39ld9n&K+PJ6k#2+IGAM^QzdBmx8ueLE#G^Mmf=X>c0>QZoyDGWaj z8x~L3#9+@Z9=|gKPXAL8>Jye9AoGsGnMV{|ty+m%Qt6*Xr&vO$nFxD0Aw*+06Y`@6 zapR1$cKvgm!nmRAvsd4n{dm>W+TrL3O)rg!2EO@7o++>f|1Ax?fi(+UydDO{`dH(7 zGbpGn^)G6!1bH;A#75DBICS@y587YAEM(cPAk38d^zCr83(v>iBU4Xiyk$XWC-94= zo}Q@1^sQkk`#lYk(#(bf!_bX_l9?j&6v4Mw?g+9cqA%zTR=3ZVx?}Urb+>Z>&P40* zAGb@(sG#$+bZThU=ks$zGgR=lNe1YW>TxP}3^0)Qx!Z=^B(;+Wt()LmqcNqXsg-K9EQ5WMlR;snWWbAOD)TkkVk6FL>{lKfXHFK--=d zX{WbSf&7~dGOrnlb8{L)jcrZ}?|C4qO5-H40@RXiU*g!voS`>5p?V|AGJko>|7_FW z181qmJw9=-D{IOs_u%A_Vc`5Xa_yFnX`YWbR_PsIap4|>;{Z9}p)2i6(o!C1IxP_s z%bP(h&u$_|%G87hC?hqjf>#XbdRuvRHw#;Ee5)3XT}Ap?fH*<}!h7aH!I04-nSap3 z=1!Vsqevn3q$z~#U?lv*B~InyoTq*yenz=*yDTpZAU-{Da0G*4a}C5>Lqm+9qX&*T}hu2|C1S>iRe zZsa~;fqe!ua1JI~H$Xy0OvJ4Glc^!ItCa-T!GLjDK#$2kJ~<{}C2DQ6A646Ii{rmN zaC(&+Bo6iRfVpc(G&&#iCd5(s`w@cys^O>k0Awe05hCVDqfL^sQyAET4t?CdDJ{=R zN1~>(a4D_EvDi$#MI=~%ihU?u*PL^L1HI@qL;Syzf(w!;!@jb>k6o{_fYN>D#mx&F zllVH~su{5&q55we?$q2_WR1*gz9X^@cHY}3rkvG371{q2;jiCRZe{(cyHwhuONA6N zKm(FTl&<_`w7WN3a9&e-Pe~J;2OT7grY6~o!~^7Tj^BiNN8Q0)_=R%uG5r1`@A98M zr=P2sv{&T$ZexA~X{@lvOyg2!E_+5;1q4o(b51}%q z2YxDqfqXe(RVKbXI{MIGQjlbZkm|bL=wA-bi&K~A9vby7k8rR?MK<~}4N0$)!g>9DBX~SaalEB>>`XNr3kJt*D z!a~tqpAk!EL0xe7_&=Tf4sI8TJJKXh9L!rwtB&lY%KaWZ8`bf^ZDUA+K0?ZwZ?6SA zf-s9PUVgvogYA`G{aE_|ARgAmL zndYyjhJk+ZC+N_mBoU9#v=T2T64vd{E$^Z-y-uiS=!BXBd#{hsYAcH%vQ97#iyb$W zStEM2W&t}rnhz)1hGX>_^7bh0SAPv7l*Up%d(?nlF;YXay|^UI`^qDO4?UkD{6#lP zm@Y-;FM5D|1cMY$6gy&Ox|kC_kExyk^z;r$%c38x_c}LvWGCJ6iJ6H!Vd@QU>ovKkUlS}j*DU}FU=I68T1wR{p>aKTQhzczAiNu*xMehF1N!i z@ymo_-I+In-9)|OHlFEzATjU<`(eM&Z=8Pv@Tduny4__EPr>BExiV&1KcnNu^T_?5 zjPSs=)PrF#o3S^HisaoFd4^tTgF~Z{hqF8+(ye`5M%vxV8D1dJY489&j|u{1G=&XE z{fdrn%0i7X4rkVUQN1@0{$6vWI~Tldo|0P4T>b6q>*pwG&ja0ce}6y$ z;Duw|8X#G$MOpm+&`8)atfJT7Y($2U^4ShAf6Mn#Tx6`<2P4wjfsO#^T(uAzqFFC@ zwm22{s>X(mv`RPQtX?)*F@?MOy9JiJyF1%j9(SrBi|gMm_jJZV9jJx5>?@b=g0*Wh zdys;p0<1ztFPYdg6Ko4sv?%BDF4Z8~Hx+aX%$I-9H%;$PUQ%*k!n-_!>~CO_?uNX5 zuNp$5=9>rsQaoCUwr&RWh6|)gw`?8Wi;&8G)BgX}HNB_wbpWF6tG$A>N?|cIieNBI zRs;*_IRKULLkbad=q!0!ervYOsqWG@O8^kZ_u@KA0x0jzOYj02j8%N|JlHp@f&}dwUeL<~G9b-7)7mo?2$+FjHHu zzVJ0Yb+I2W!_0*JzAVWrrW$YYG-~-t0U%A&@Yltko3CngT9J0*%1%|4mI4S50DdXB z^heP2+&kRE;k55u*SSCF(LSZUtyVc9z_NG}s0u>Sh9#ECyk>EV$PhuVb&-|NE)09c zCP}7d`AQ-ppdhEv@cB>I#RgX7u&344xCO+Bq0})q^reOg^$M6j{+Onxv7hd;(XCfW zXgx&Mb5KnVf`YLh&i@ z6I00TMQ|IKa&)hwxd@Y+=k9{?{E|_$zye4eJ2i2=3%G^zwaaew;aF-TaQF4ymgWFd z7|WKB^|dP#T@~dpUdz6Bg+x3tF;E%ezZy+$b?892?eyC_wWP}~ry>Sfbo|TQ7Cmsn zVpRR=)N%4b9D?3|i}bs|7BvocA9xpGtD7&{{a#ka>e>iuc7@u1TkePvaEE$c5VMb< z#8d?B4HSOZu4UPo>*UgOZnhv%%9${soe_BxolcBR0w7c2L!Ge6nc*-j>{*d_t)kDA z7fstE-3As+JVauSc-L8g>%s&35InyC=sq;9#^W|Z``qJ#MAn_Orf#?U%No8!rkI{5 z>%(Qk4n1ak?!B(2^Ljdn|9vhT2-7&bY6Gr_Z7H!b&DRk8BWc#ej+uXI!@HtXZDQt~VK)k9s?Mn>-NKoh%ffk{sa0UfiX!;xIDCT&> zVI@>GDokj-r3ajj?4m}XnBI5tWI^wKy9>l$acxo7$aWo`lsFn5ThN8376?FcUDmNa z&fYoQj%`Kb!(sl5JYr0^J212|$pkYVs3Xv~xMcM-s zK5+}&ohsqa_+(H01_75A&Z$j$7N|r0h1H|(j}yH+nUHcNe4=#47?%jI&!dtf?lOGy zwN%?ee_udLqbo>KG~+_qHi4!6v_QL*DxETwP21J2^t1q-YFt69Xs3iYy{0kxLo0UQ zAWwj97D|D57MHP%0GMql5PC{eQI=gZYZLYJNT7lf#-xB%!L!|=b4u({s*D%?C=|^s zD8K?xHP{_7vV4eBa$la}aG^c{DA7-awz4CxcV0WZ!Zf|MF$(=lTyw!+!37ih^X3-_ z@iLiPM`Ct}vv`T|dhx?8c#k`|;gLuG(}by`QAb%w$<+30;K(R$6va&CqE6NIC9MW9 zbGe?wrvH}|G*%9U;r^=_QfUZu^qLoMW+QU}v9j-%NT!NNVl-sf>YZG6RY5n!)%RUfz)0k}fE zb^1#MW8Z&SKY1EjK3f8(XTU6QvUPES+dE)J3%6J{C=JoW>IAp*ahBg38sB8SwIy7N zjQu#joZlfn8c$bLd3h<;0lW+JYM04^779-Pj5lUCH?*>iPJ?C|;!j5Rd%M%GZcsp; z2Fp_KMvxtrY^!$z0|u}wt+N~B&<@Z?YN2i9%@ZizGU^VcuP~QGM%k}129C>R^I}I3QXf+Fi2LJaD z&PACB{?+f5BMJ*TA9EEdSb>Yd>@VR6>Vs?1JQGt3M?&-$-TCC`imhwfS8Ea2W6hmc zDo=8;Dd@ETVzcRvHwV7HYLEgmZ%rHT ztM|Em@)Ph8ZsFA`0kf8%WB5zH1D0niXcqEbu=W5Jbzdx&{&5Ksv8HEJ24NJD-&9kH z?3Byzt-mpFuHqQ^>6*N7x3erWAgLPgc3_gt=v#j+@83M#Nxhl+@TbiQt6l9o*-7Kf$L z`o=nW(cb{vvc%O+?aWVCKAoV%J^|A;GG&nlT#qvZ^(n-Ko4Pj*BRPcE7iOgIjsR^{rk;?%WnW>89x|6 zsE%1?2Je7-Ws3NGcVz}hGkQ0wGD?~}f8EPjnrGRMT05Je^w;HfNQ4AOXG9iYe~h0- z35m=6=*HI;Mv67+b+pOm6RQHa?yayMum#B#Jw`-?9uu`gdWWx-RQ(in5w906ZteOZ z0A<2Do?pD??1%jFMr7hU$+dQc&G~=9)*Y3^at8uRIPgqE6l1r~gZ&Yl=BYN=d=6X& za^^PoE2>7B4Y^_!ATH9D-GjiLM{j4K&+;>u73kQP>M)?z|6O=j?j>i|^XC`oaKEl> z2H8<_hx&;UHD+!OoiPUmlWZ}N$=M<*+E%AHfPdacUWqDY@zfjO0zy~u@Md4QUk+H? zVI|$3BCA&{@C%&)&j(4eS>EIg-1sdK6hQY*#HoX&qS(L#b<@NDVKhPLKqO(;+~0qU z2DiTlVGpS945-Hs@&2uz8mWIk)0`$9bOES2zYYZ9PKWRGQOYIm_Al^p886O7E8w2B z>1&!7&g3eqeCIR)LD2%})5fXPGv530(cJ*@;-~Vfw_V`B%pt<*>K-7&6n6J*A&-kX zXVxQ{k)`?sz`y>%$&eH@Y$+_1v4U$owDEn~MwUMeZ{j>$m8TN(PJwPs_9!2fq69{F zgLaln_oxLXRjk9^#zHj;-{VZSqcouIveASgcg+x0ZZXts+h@NBhm%MowfcgJAFy@T zi84}?H?lwdX*8w{0NY8!fcb{}I=D87UboDo%fihb5D&d7wg~h(pbdOo7!s^Dqu;8L zaJR01-)_TZY#-_L@HaHgUgl>aowo@kZQ|3{_jaq}nAf^28`QFjxD-)hLnZCaRpyBq z+4N)(J_LmJzDpd(U?u#y@ZwMV|0i)^X*#2LCSP}KFW>D&?S zX{3Pu%+_|H{f|@9IE)!WGbgMAq3d48MZT{oh@DK86I*gBtU2nM#slO(=a*YU^>lI) zVb`OB9g9HiRw;}{*Y+7~W7tUkMf^731GL1Y@&8LB3I&eqq!pt)a%olY=NYhETjQXz z%&X2k*TdQ=M~NevAvu#e@1xH=9GPmtVohhg1kit_XB@UPo_4=V7(U6yzNvIG9!zl? z_ppOKzD?44s6743CQ)N;i@k^~jdbXprdf%ESpEDR6nB0BCzG1$AKAhl{S5BBS5PKz z5!j+BfQ-ngUdo^Tg=}2<5C!>LahU9j=RT#`dgnXVN$bjb6edZ2$Z)D+?^maA{U>Lw6@$s z+``2dn4DMX+QCye)#8x9*B@PO*_L7k43eK$sSFAM+&ZQW#h3L$w$;Z-H3wgOCM>Ng zZ_^2D_rAGukPUNItGL_368q0Uf`1&>iiqx=c9Q`J02#%bb0u?vu$Yt}3EF@N>JG4= z&g%om%S3c0A|l!9N-{fQc$jL&Do{-&acVmnO;z0HGnIh=saGK=Tr%`I<0k#Yn=iIb z$sc^EBJ#lu#LXb*V?Prlx@)8plv04UU!v_b981omOzz4WPakiM#`oIEpuX+KZ!s{> zs97*?z?GxhG&PRLs8#}Lh$~m2omfj@p4!+-mLF3h@Gv=-U3kVCF;>`JpNIj5741of0&qU!d$^VO0juL16!wnphiA1>6zH3aVJWSc_Q*O#%XbvpjK4boO|lzLGvBO z5WhY65CLgC03cZYh$5zn_!5;;)O^>?q*;fVM+)@@bWHuWF zk~})7x~_T5RWE~FFGgslHApK~Y@7uoPmmQI(<{8fb&_<~E6({EzH9EP*IV9jbr1N6 z-)j)^$ciVwBU1e{5~-Zspi7@@ldygREk&I*hyB*%xV}8F93&?43KN5_Q*99t!aL#b z=Y2%3d4ATR%h6_rEk<*m?}v_w+iD~d`pAN;PM;V(fwfrw9y7RQNC`9&{5+)#t*&fH#v+nty%22 z=zxhG$8*Im*AjYI$Y>+8o5hpKG@w`}>8+c=(Q4r;p;Z_2AFHob6wTJXeiXx^Igi?x zu3M2UBYP(b@TnfzP2_owy6LESP#kxb(ZOhIno3hQXt*>m#eqzp_U>mF1SZSxjtjP? z#c?DTV$I-t>W-ehTYBIkpq>Eo4`U`kf=6kI26+2Wnm_PJO<&`|7r9(Gt3JF^#(X8S z7Qpp$<-H7@?|jo^?Sf5~d;j`k6Z;p$up^QqlFpU*f(VT4JWjSDZ4#>(7Ve33EnVIE+>?bxy{Bzi>Obr%h zy?^UKaIK$P;_jFi6d36Ho`@fl0;QxMe|H~~PwN?q34PcWg7sFnb#hG2sp$hi(t)rA zMFJHw^wWRaTmpq%W~i_Snqu=krKGq&J$zTZth)L^g;-~t75>(01$y*g62+;i-m1*r zr7*bQnfxJRx-qOQz1EWv6-A&dumg6zgN$Gv} zrRyb-HysB7M7hrOx0`t?EjliBVBrGw9=N)hxClE_r1liE)y%dqlES(^kE~b_IYj+! z&pP#O&agwNg*d4%GM^Nps8k4{tZL`=9<{pTBxczL=?QwC*)Q&R9P1-JJFz)-0{y%y zMwK;LFB#FY_TzAf7UfUoC+dlruvV^kxu{jaxmiXa9v4xU#Qz{`vfOD|NaFqNrWsp` z+xrRmwQXVRA)sU8O%mnY$ygpMN(xVRj}kSBvY)bgL))T~%s6q-Stqm`Sl$#(<%Y&K zqV-rg?;AiOPb%uNs%t+4%sJFPTW@xUmM{AG+9vRcuL--y;Ovhy2F_(QZ`y{&e-Vxx zVppllY3eM6t>Sn zrc*W_Quil2Lz%3|i19aGS*uh=v|Vamj^>@F5+2QRABcS^qW5JV%_{>W7QbwHN8&6t z`-YQ3Hvk(A-g+&OYW@rn$FS2@feFAtCd8S&NYBwNRug(k0>5?6jUp#1k|r!(%!qe;IOsC%o&t;MD-idhz+uf~Chwmr6P&eV@N8nG6k9dDn** zZBXfFn0#IMVqhzu#xg8^J}aXF^bXN*yc0%SQ``pVN1~HjVGx3klx0ps#KO4m>c%|? zn)SD|RUVaSpn3uYdhung$F@XWsR@D6JfA`Eu{kLhILd}M5Ek)p|57gCTb|eS35zjq z5()|E zKE%~TvVKzcQ3?nW#HXfPOV1_H&Tsmqwk3{Il%3#{6^cDEJDn`Ls+~KYnrpclLg`|ejr#A_ z*6)ObFlAn7h{?*S{0`oItm5;t>+jI&u_aU@;zOgohUwM^Okt=R(WG2-#>7l_pUbJV zU+*}9&mQ#kfh}xUeGjTEu#q2sKAmO$tUl(S~NWw0y&*F^6hhY=~MB_7n zPCPqXg$681-wgswdtn=lz=tIioA;3#DF-lq7T7YE05hF`X`8@4qG}<7p?BW?d{B15?F) zA~B$RECzYMi)vqc{kOjj^ow(-eJCQbfFyJH&_gfw$%WU|3TXMDldvNzf*=IX7v*YA zL)hp*aT4P1r_@eOZKJzFQS7QYT_dj6zngqQYNMH^gXQye^tILt@T*_Azqm9*OeHwG zlJZRZ{4-A5N*R~^oPr&d(h#?skpS_C);YBR)RpQu!V>4;j$XxC@)LW9Q9iux9|zj5 z%9-PLFg{lXnKU3%QpGLbu#IchxGgC!W#G|zy;g1Ufxc`3v7PvBG$!ed-`3=AsA-Gn z#J43!$xinvINBMb8avK#y=zlXe#-7nX0W`@;P4uyazKN5{IB!$Q-0fl2rqc5g>R2_ z&&afymqhDO`<6lPjlas28HDoV3ZQyPU>4*Q-aUR=Eu6$?5@E?k-udAT-55RB^PDvn z5JA+&?4P&}GlF@Lj6Y$~LG@#$giD;^O7FI;0H_wz*k|C_ z5I~BL>A=@Vyh~e!6V(p;L*RxM{tFc2!o?|MZ_pL(g92$uJ7beR0n|V(0)xcvSz8}( zMWr7B#I*zB@f!SFSd+}K^stVP4BT;OMYfXw7$oJZ|G7$aojuZwlqjE14084^T7(uc z736v$PK=j?Ux)gQL8Ng1D~~#=sgrPeda36{-+#6L@wsenEWXUn=4EppPH_;|OB>Uk zvb3oYe_2NPIc;aJS4A+uhA+xVU~0@BQOpp*T9v)enXCuwrwkdqTkuhWiyP_Re1>py z!NpJjyQVGw$kyV|#T&X4d4SoCns#2V*`5&f3dlEpT8qn;%&`B+n@P3wMtP*0@`lNM z>&~G%%!LYea5Mk^|9U`QeumTeBYjF7E+P}$46hF`~ zSY5f;EUS_qPRBPsUQ5TSqezI|sQcG(MgFY8*}YGI2{cU;XAxeHP)_C~yx#9F zbOW_7Xog_QRA)u%ti7k>kU`6pG#ar>A69ilM$DJEp)OXQ$bP)UNO9g=F_#uM31TsK zPK~va8)rXLrF(hMAV*_pjJ3W;_OMB#EDFN6>zp(QKb`X{>yRjCAarDy%~PohlOFZ( z1+PNUwf@0aPzh_&WyU}_yAe#a21Uthmy98FlBc1{*0C*qLkVq4Ut5eL|y7{Tf)-`dtS7!j&$-%PVIjM%~Apqej8$kyNIPLDFjl3A?F|cZh;Lk21o9hmSV!fQ#Nx5P+*eNJb78n_pE& zu;+G7C#-D|b2Jm(FqCOjy8dcYbU!l{N(v5)J~pz|OaTHg>TS>WzM@6{wE*zjc9}~8 ze3l-kK_po)&&vGbkfj|*li&e}H+2JvFvd(}l&V@VgW~F9Mcdx+jly5L!+(MGFhkkL z*7;TjJ+Y?e_R>9s>{3LDWy^YC5UaJlNBn@A0)%3DeL_vehbE8`mYRLbMO51rE3k9` zpzhs~)n)ftY_j;4l`YKufkSOWadd2j@iubob34@Nm<=iAq5@i}8p$}H%UJ{!zFASt zW)9TL{B&vLb*m+;<~$b?5b2OdlyY%GvzeKv%ay~uTNflQAs5qoErsSdY~gzMtmL* zI(dlpP@#sy5uzRvXaJJ;Yx)uq4_9Dj%%HOC&M!xjc1~v@t4u5hd>uEQ_T?_AmtT9{ zue?^HV>r@mOo~-vX=1+|rI1seJ;}4Bo|C-d1$W6TLb4DM_XbHdmfiNbCaM13% za@TWy@jzht$P0Y+|LGx7`};B>Vm3C?d%JjyI&BAW;q%A~yMj*xrSvmeF;=eENvOw` zuLKyMUxl0=e4dL_f}W8VGcvKM*^Q@jIaOw>^!g2V^@El#YfSDoGu9fbJ|85zk{%-Y zf?n=eM*ee%CWKmnx-{eJ2IN2gkoE9=?sFPmQRBQM4YUD}*LbM+Lb=|She=~9h-o8Qb7Fej>vwDAlyvPjir%4nV{RsqmV zb^m=r!C_!9atQaxssM&8}Dm74)=|cf}&4qPMSwt57b(Sblk3@IgDg1kzncAw! zFGYy|ulM^i`H@<8*J8KfM-oU-_Vv#GM1i7pWUyt1Y0;=wMlPGhmD=oib-V2DVTEB= z@~&Mx87nizlOE5{)-iveJ6GE=*JO4;v&QPl>b_^B$erq^voU;uQbj*G#s% z1wR53p}a(AB~m*cj-*M#`F%!k`s+$b?YBj*)FLX5Y~=gc$zb;|o8H@!&p2ITMuP8# z{Ta?RDIpCR_6JY$1DGq@4aoL9z@Gb6C`!K-4?p~dIz_Rx6cySBspfH(`ourjCl|yh ze*x!*1t0@*{t;U}Ai3`efmZXV4)#vYr?-UNZoks^@Fds0He&R?!pTXpKVu-7G)EqX z#hGv&BCN=P-~KK_@Yv!gmGC=VJaAXac|a2c_-9afY890yV+pH=;Lg!^;olNgp5soD zmy5BbYWk~|!ef?l+DFIm;7g4E1-l;9;@WrppK?!E!`2-`o08CzS(#N~suU=AV}6MT zZICOfGs~S(u+{d$2%2vCPQ}->IW4-C;hbG~q8M6n5)74)O*YjySoV`EiAqh}gI!fN z!#TBxjtpjVv#TxJ{Iu}fhH(TgBsCdLwEFv0D`6n2!2HDxYqrj{KKDzEk5o&nxgx8W z$yeo?ibvP(FKX8E5#4ps*zRr1{Y98qWl35`qsxi2HP)Y4BI;F##(nYy)4o`+mm;b2ji>#==ll1?`LQYg5 zVPK9Foky#DfWBbci!C5kDZbr}=VBt@3sdmpjaeozcjzUMmsA$~aZWU20FYzNEJ+`8 zPLBPv8{FA`v2D@K+&1qHMBWY1Wpht6E0kdJJz^odjpn4hBKl1yN+;Q?M z4g(NTO`33h7{<`{lY){|V-fj`9|aXo2%sO^D`;~JU$j{MJc<3D*3f}@a=Zb zFE{k0Bh??Sz$8TB#CE2s_qm*NM~5?qoksbK@G+tM77H#~sA+c0XZx=)0?Rq0a`@Hz zlj%pLI&v1@ZweFmA{l(E9}*gbgD}3Fa_Ic33%1~V>1tvYz)*;s^9hdxI~phTQS(N&6?Izr8f<}&9<&vfK4DKQ1f!Q=;XP3U9JVWd-;xJQOY z>DCX5IWC4M@mIxiYbHZTleUdj)FrzIU$GStGa+e`Uuj)82u}dcmG1nQ`e z7RbA8X;+Vdo}d z2WWlQUpcroHGwuH-^DOHUsJ471aCD&4#n^b10e91us62)^G09jVp2_t(a zfUTP)U7B9rj`TKB`ewv!^X{d(mb{qUl9VMS5d}|Ps1;6jx)H~@7Bd<6o66q;b?M=` zYCICeef2kPg4w!9IoQjwRBst3QT_E~GYyjdw2*C+A6~0DYd$Hl(8DO{?VFTK%sw*C z;RQEH)mbg4a3ejp+XR?9pcJL}ex`J}M_7?o2G647gJDCE#D0;*wiyo(XPrQf|H+V5*w;)<~fEMIK z+YYU0tV>>rhD-NA+Wz#;-tVTtErT}cQE ztkrVy5Tu;6*ykl5#UkwSg=BVxw-XVI3-@n$g}VQ7l?>;zrJ~#=5smUn9^^u~cQ^rK zT7XL1p@#7y2X8(5qp4FCdh9GV6=DbpZxl z1Q}m9hfZOg+YD-&b&xGrKoNo`Vmu)dgI?54m>e(54+UkhV1Pm>gpd+e{ThVl2G_pc zQNY7DAMe_&&dQL;v+vweB2L4@3u1B&K^$C{p$D570TuVEZ27beCOdT1NZ|0#ok(=8 zC0jqqX}vfFJwMi_&o49TyL0CYgZnxoUW@_`MfR3h|4M@WS7Sqz$x(0k5*75wEYdLh zsXPo9>>0r)okAF5b(;8JXYVwb=m>JbsK*JMFv9|aF3qo&)PvAv3V*St+WZXi9`A?@ zj42rK|3tf^$y7nxiMtB#(3l$r+RPuLexvd+Uur_y*bC}?&7633YBeDQh_G?B0N|ZWq3qCb zKSt)H_fNF@unLf&sDPUm1hnoEhel_@<}~1B*DC zOTZhtyn~7Z@iXr9j2`UQ2}17REY#s-VG{~Y*aUDSOBcy&mFl0{#2uVcx*cna_q*N^ z00Td~56T}u!OhaMK%6yFPmYb9Ut7w!i0l@`2w{j>@Fr-!`4qEVCQAsFp&fZ?y0IAU z-l9#2`SVVJf}`l;fAM;PK#BQTym4nd4u+9oIJT@}Tu+>0nI#qe>ddz8lER~P7!4*2SD!7sNt>FGGUUW)+C z+prucve8w%Sm1<&Q+isK3g$E{8@|jhD(51&638E;hav2=$e;NCpy+}Fq7K!~I(SnFz&B}=X98^-^sVS8O& zS>KvE9e)TY#l*--tpZH8J&8OQ0(qCj?(||Zy`+#J4=-lC0G!Prjo*szO~Hpi`EL{jXar`|P+?!q zW>gg0KfMuQzCmX)Fvw(fYp2f$(wC+31MJrsm$j@%ST0tUC$0K9Km4DRGiIGFVk4dL z;S^1<1#aIP!P09|%mOosxFqK- zIPB`kqZd1D)>B;pw*KCFg%cB<_C(#@Eohwzx7d)h0f2C=yY)!Yt}0!bSLGKz6!nJb zHSI7NCSV%^Rj#@*bYB8t{yrMCMGSKg=NIq@qdhEzA6CS4srT{@j%D7Pq^tN~u-gC0LEqKQb>( zQaqRM9`7qCw_J%$e|9)VGLrB~*#!ugj2E?X-+Hfj4b*dfHS`MK}A7gl#yy` z2U{eu#pLsBec6vP=TFOv_l>>j?3D^Gy$ zUXj8Mp!4y4dUzS(sxbvXr&msUQJ6ZD4>Gh*am}3%oQXLQU5|AcCvcxqLsmUe8DMp0drX2N|kG{r{g_;SxEejeNIM)-WG zuZSN-dfFCx$@9av`&)rE;sf~F!@WYb?(iLyw^t5 zhY{WzBYFQJwdAy%$*~mxH+Ys^)=dCZC^NxEBvI)UW^>>2(l2UH`fd*_AW z{z_ePYm&j?{;eu$p{36Q--0grGp4B~xstk8+c|cs4wi=K;8an?Hcp;1qC!6(_NDHP zdjbAJ(}2F*_pmp`DHJS0r~8#D)1hOOPQV^6EHJccPB^|e2FR}toEjD(+Ab%;lH&h~ z_qL3=0fMX>d+DPuubnYSiu~Irbd#6R31bq`PlV$H>b5CniI;jx7g3BvaI$b2h4VW6 zMf-Ju1=_`gzM2dep|MkNeMVu#YoHqv1<^46B8)0u0>=4cy%#2cn*72RV{GD;mo}Bz>^Av%&ro$Y zL+DW`Izr5@t)}@g+f7VqT0B2NjLmr?C2g`g$Tw)*>8efeK8lTmR@g|JaksH_HK{G6 z1ka8Fx`*6*=}+U9E;k@j2Dv&%k#?R2B^#-Z0dRkIo;W(;lqJ05)Q77FpIEgyl~-*? zk=?ZCsbv_MF5N@pi)Rlwn4j>xUPU%1S?D!sBSJqFTLFyP1kSJ%iSRD;=Gzp&|BauQ zoSmJqr|UAuyo5Zh(g}Bf0b9Dv)PE0@VbYiVayT-eHq!r! zVHE5W`slWFhdFLDqz_@itkOK1EZx0nxmKK=BbK>iKH#~hl746K{8w9B9s`hn$NB=^ zQ6-&l{SDJZjC_PSa)v5Q+p|PaN2x+qFJPAaGE5{>;S+YrWz3-gx`bq{6^^*OG0-sz zC>bHhfcDEiydUp1$$we^Rej&kXYrYE#$F3Zxd$Folcv3xG!ZNTS~5}GK<{U&wf!`n z2?mxef-&HAcO{F$g4<6K0nih{aoET1INCA+d#Zv=^$MO(w4m|g-dZU+fqIAnI_-Z` ztrLQ=>~348yv($vvo!!G9gQo9(ymUz4lM10$1wZXBDQ(0`^pC-~YZpjI zdXAM3U)XdxtQ~ZQ9wcUsl3HP2hi++yktEE}2i%?9Bgm$!ejG#0B&QRPF%FjE^=NfI zj>AH8RC-qc!Bhf#6++Kn;nZpxrX{7Qnm7MhomKz@bmQ}jc!Ji0tk1fn-Y@N%fE=;e z;!1^Pgp$vJ;rVX%uYYXkflc!tT>X?B|%HLzR#y9bf+bn_KVo93m{9o|e$r zXU!~bImf>cdc!>MLC(FPa}y|~S@}X$0}?GL2jxO5sREqI$MTTmj~7rW%$O4|7Rf6$ zoRak#(VdY~#Vi#z{KEVH{umB|Kyx^?GarzR5qhOxgXpzz8ZLxq(8H{{1!^j2YAwYS z?Dwj%H#9%v-<;06UPJO z#~3fm2L-J5nw4bMq$7IEdG&*jy?Hwux-p??4~zvII;WWnb8Dw^c*7*S(*LFp;pBJ4 z-WE`gZ}^TuypNtZCB+lGZMCbJ$yArN`(&_ZL_{g&B;x(zKkKNV4yJ0WvcBBMzxt^z zT7hY7c|m3ELyKko^^9%ejqxw-Lys8ai2JB56TEB<=%eU13N*sQ!V?7+X$G1?-Qe*T z&}zlfGni*AVoaVMG<0%`t^_J3;Xg(lukcbS9=a`is6t}DjKR?w*)&cHyQcU@VL8eM zgGUp&hQ9s0=juM%Bs+%;LFpt8^l!?mLtj4LW~vhZ9=?c8&;}WMXQkq#h(t$O|J!ZtBZTpPT+Dz ziejC`nIO>$??1h7{(1a6<}!4FuKeb?3*%pB_TJd&UKO%qXCe{Oxl=59*@tCQRCL% z0^KDvPj6nRj<>5L8#YG78XL-0&^F%#zS(3Y7$riuajOJYmyi@bjD2@J)$jZNImh06 z?=5>o$U1fiMfT2KQAj8vdqo);2W5}!RS6*!*?W_{C8>R-c;q~Z2hes^=&mHp6HxkRtq+kWVr8k0;FI-T6O3do=+LSa zxP||!-QkA!5>wNWLR;K2nruAb=&!e#oNMUU!*qfL+LkI?MLsjXuIzGo8_DWqL>Apl zPLEaXhq3&IUD+X?S4uy+@Og2S#lrSFbzRYe+ybZ&&&a0q`wbs0S0-!Ts}b4*8ZR|t z6N{IFNyzinm3C*M=S5-?ybEA~maz z=I`%LAQjZ9%ugbExOEzF>5E&uWxV3qH#aH&a*0iEoX^OKH!0{?&4x{6B3*%K%+9_)}>0L6Zh`rpS408Q6 ztxkONhkhw_Qb)QvWqYwV5x+v(^QkJ2bh57WO3M_u2820=aCY|@{U&9Iew=|pTb#6M zarW&fuuykZF8u{Bw}tzlqWXLJ^jg7LZJ&Xc%NwtcrY)OSRRy|VTw!Q>b+8xTynOrH3#*ceseJTX=kdDwwAaK{R zeJr$Q%c5Nc>9pj(tQh2}aiwdNfJP|czNRBftbqGoG*^dh%vubo$x8~CQj3GBN%X}$ zCt>Bfu%bXOqHHu5^{$N;eBLI>bxy)K*+&VZrVl=%$8seT6tUbpGADqCa(v(UeYHx{ zH^3@9!LDIObDsS6)yOx;(Mc?4%+#00Z$3?wu|6VQQ%C>2o9X|7{C<^dv5}8E#;9qu z_+aUz=Hxv0>(Irk~qccQL};1Z3@nESOXaQeqjU`YA*RWJ$m6g5l$4LBgk=-h&%nX60@w+V6gCleXVHfIN7=7rW>i^VG?6^F$jv=q2r~aQdXy zm#QBmoO`FQXQO43&%`-MP|n=uV*9$M{XN7mnF{;n)!5%u-@1tw+-{XT+z!M^6N^UZ zj+;6$e4Ne9#SEEi7{2*k_1W^B2l1&!>09xN;pB53DGJ(Qgz2PoArM%lA;!riSBb7N z-X{$V8v;k3=0O@v(TlPlo*lc&sX!nQ890JCj4Xg2ulU9*u0ROXxjXp)eeu#1`V))- zi7VgNc=2>Fx0&|ZtQE*EH&M8n(mBGkr_y<^vOgQ+xe36A$U_m-oZ&k{Idc#Q8f28z z-r1TV0n&_#b2lLOwd=6SI%}#B0)NQra$IwW`4X*QutEttc|aaiLmGaJ8>_!?3UtT; z9V&n87^$prju5X$bC1Thk8;@Ad8C|9IhJ1F_g>@30p@Kp7p3hN=_7q1=Q)U;A~KFGS3MnTC}`XP?_m% z(Z<<*82F^QGNuKnAO*cBRog+zpZ~*=1g&JCwMT77V7vJ*0SN-c)>G|i-;G7w9ud65>q!raMU1I zK1!Q9!2u5?6mg0F<<&F6)jkMSh)x?dXtKo61Qc>Wxeh1-_R)b*Gr2L)Nmw3e2ku;Q z1?_)1g9iD_%S$B*|En3Pr5iN=Pw$a-H$nUVcAp2H%8OG2gG~Vcp=4*rPmqT22%GX2 ztt}w~^<7^wnoPz^gdh;i9MBtDI6@@4;tJA7vz$ZG(V2zPly zG6y-t@sg)-9{pDhMYg+6JoBB+F_h232(~)&l|_7M3Kk7^9AOBjhK(M^^DN4oy=`rRi024i3YK zShR5oMm)!PkAVtmC_;m86@Ud&;XHj+e#hU_oecyu2|&{Xw7)>XgXGOBsTFWKn~XI> znM=C7V6>kNoxW5Z`Y&Csu@@_`q*=VJ|WvNZIa>C;Ls4Bah?C$%K z?^hA&dmKGn%<9z8BCBa9xbcFTO)ooTVY2Ja2dYoVeu1&AQ3B-uP8E&%3bX|q9AEhX zek|Ioqv@0p@rHGk-yh$te9&1LZ+XP+rGhT;6yqRaUrE9W%?YS5gQsdblp+yFK@L&6 zNGqKCQjaAFno08Ka6eTYAPh)C);|=H+oQGdr!o+c&$*yQ58_7~6&o-wD9v?7h;%Tv zm>YgNG;B}b!BqJT!FucIQ}mQb4H$sK5qf-@ssTbI(G5J>KG4$9vRegTesXe$N8TNi z!oiK^s_*)NmdX_y7NmCrn`lOc$>ZnP`}i2f%@)x&fue3ojpvSuDlagbVZDTm*W^|{A7pZx81#NOg77*GMQHpK@j zP;nxnmW>ec;G%MH%d8r=yB8T{n*C9r(X@>$bKxvZpyN<%8y)b4%Q3g zZvlBtJuoUjv4UM8_Cj-!E0N@l)*6ATR4zUB9GHonzvrzAC_AKq`N?_!?jtE#KKTP`(=-Tnc> zQsRC3e~sK8_sjj96X!pi++}U33pcg8FYJ@k^10>jhLkwU7LVF7IHfNj$T3=Ak*_R9JqC;E-Nt@fSn_8_YM*l!N*NIoIv zb9JuxfDbtU@Cv{5C5O+GR~8<9aESQI37F7QCE5c4n;SgUwP5QU;&Y7AE)xB8Pnbgd z=SR6t-BVjWmLCtZAd_@u<;fzhjnrlbMVuss*SD6%8yI<2yY7FoTS~7V7eg5Ov^}F} zqUH^PPmoooN!#DnduWA)z^PBK(t$*r7mo09aZwK7qRIYx-M;P$;sx`;bxV^& zQQIr=a*Y~_N5@hHNbV$e9AvFzfg&DiI zhF+aMDC4QW7T)73zk5>@u!kNki!-@5$m0|n-b5@^Gm zHPcHt29A#>Xa>UYVK5xI0`?M(fU>` zW%LBZEf5+pA5q;W($qOXq5?dK=|;I3Q?=#bxlrqF6(U=LVjow8Gb=@Z#ee%m` z;bNo$QVU0zg#hcrV_L^>8lcSyo|@G07?w1G{26)yrgbz3b|%1zPnfL5B4SIXB_ZP7 z-qM191!K&;Nd<>~=IDv4pVx!xbk0u5a%~5_sW&8zFqfI{K^H4EH=^)UJD*DXCPo*8 zosQi!9__UpBj-kS9Syn$tjWR=h-m-pRTd;_m2CWW-fu;)u?uIpip#`WNaC5DPVvDSb_QpU9ea;1C|@`)O@=vdE?v~_Qq$|sIlD5@LK$%iQz1*Uq{%gb;PLDY}xu^?uK0S#y9}W3y6XN zU%Lt16I`8%+P};YO0rm|%SI8oWlTMB&Io%0g#$j|S{**%gUMVj`E#SBLlk14 zS#Un6bOd5eokHP7hGmi$Sp&gF)>jueqG=NeXxbTa85gO(0JCeN3{cTyFw0GTjax;T zg|D5X4wI9LX%ymF35}ZH9>wFeuI)AxOB&H8ycZHkjX7a+IE+T_a?ocs3|_i8b|DcA zD8pT@`D791rMyz#^-Lax$qj!TBVdmois*<&Mqv%wOr@Y??B2PvYgcB@xciz;eoe4J zl4aK70_pqBmzaToAP^`)Wo2`_5Whm{8N>;He-3-WyVzx-Wh#^A?IZ567Wb}^pMgj@ zG+!VBW`K@vAcB=a$vft`OfaBDgJE+ih{9Yub`iw|A$$SYq@ajqWGTQ(bNxz@_d9Uk zb1A;if$4<%C%U1w9nQ)ECYQB)ii9Ix^v?G0;|+KzNI+10!IB$UPgQ|5A1VhH@9qg5 z9*HpQ(EFq=%ET!|&N-(_eamNl)PnAamy+Ci6lQViVjs51kfTP8Cm~nCU!TKwk#dk; zWX+NVs*vdfrkM4byUD)_-d2AgxgwLF=bV$LMO$v>l;yHZT=&_PQmfThrxkaqXWOT& zsu|Up+p84Fs`eb7+Ja2A^IZE!szLeKMT)92n=Kn}J#e0QB9infvbB-gLXW(G=@t2*0E(IGTgAhVm(psF{WOjr0XZEzR!`CnKi35l@V>+X zw24CzedoCY*>EDu_Jk!}BOfBU^RfHq7J09oLv%2O_ma(_eQ&lSJ#jy328XmjWUYXt z5Rg1iqW5765!ZC+t$%eM5SeR_65ra@Jxx5kJfvh8=;^t4DG+6mDjvvyOzlHuksDib z?%7-f)Qf+u)9G4k33GmB+l?=`^gK9azg;+s!43pCp@@D|KDmk65Zr|2vj^H%^F`#A zt2|vC7oGZx6I2LfDe6DZorR#t=ZaY>kN55RZN+g}2oV_47d|rqK03hnS7I>Vwr`Xg zI5YDzl@r$kR|3HQHLbFlETJNb%OGSZzhC~ac_I{xM~>3U_T-iA-gd~ zQeTI6MsOVNFCh>@-t`tD{oba84=we&uY%d7VuLSqTh>$7y46{SDHbu9&Iod>UD{is zAWcE)JIe#BLU6=*$zKyq_R@o`W7P_buRW?{vl$~EF1IK(j>8lO`r^e@O;10l6XE$48c()jpkgTB4wHGtfjp0$a{~ zK0vy#{MYyfThx6QRL7c0Bo8k5krnegJoVc}ufTBoXRi`o+8V^%$-dt=Q=){36tvmT z%X=C7G1|t%z+O5m*q;W0W{@hVLv_j|(|lnJ_8BPTfFpk6|6PKA2x6rZL3!M1S1&yo zx-fflm&Xz?(n2#~B}gaMApX_@M#U~*(*UAEq=RuCP$>bm8-?+E%I`D@b0`}APe7pn zlubavGKR06H-n`L_yxG0iH6L>i{2nzmptc3L#7?rhy2@h8o-al1i2RGoTfQDUwU1t z$Grxm_~2>if8k>C&$S__K>}GRrBam#=Q(j z_dnc2R&LNt{s*rCe&pv~Zy-bnCg)I6s5K)!pj^1Ui#{pY+rf@@P8EbuR6roGukbXA zzdirwvJs2HXtM9%5!cZ0L^l2yoAlDxUgcGuCI|K=zST!}rlSk)6AQdL%-5}2pcm@l z_cBa_H7X9T(lu!(x#TUBkVL$N745(J)}VEwv9BFf|LECtpy=bhrzJ9eFP4{7zY}Xq znl(g>a($ee7K;^3hRv}EijF<$izLu9s?OsHRCSi`_tH|#k}DHum%Ogu$$XOe^!~Be z@C{sx$BuK-M2VqIvG)f*Xqm42X5y)#n>d^WJWbL*wftO`s*9;1)G6Us__)RyLeA+O z199y1<@`Pxi^x_X)YGudALj6gr?-gj{4jcZu^vx5mCr7^ax-G(`rJMydE0WG<_$U) zQp$WXDtE|ARPD(T!Co!SVd0^=#4N9lE0&XunPGn0NV!gv*2gypBOjfUq#Kn!JU6h_ zg};6M<{30)5JzfNZ+zoWJ>2ByLcw%kjRvnB19O%Hnycnhiz$ZEs7dq_raX+Z_z)dbl46ev+f#cDjvE{c*Lj z_>qluC-~E;OA+aj%%p^tCU}xdM_Q^3PsQNm1kx4z4UtJk!Q*K0<>sx6^fh<;KPoqF zgc@pOx9>f7ywr@b=J4DBBAfWt6ho;*R&0*I&~+7qDYSBN=bFkRWu21C^ZsY~PIB2) zv&;4Z700uMHjr0a-~&Od3m^Ja7*({UFZFzU^ek7n@Z+-uzEb8v6>GVkvQ(qy+h-5S zU&JfaIm?P)Mx1r0G`;*fb&z>`V`F=SOjHu`8@nJgUX*~tuj`&oo;K!^(&)kdvL3s) z%i_H*FmN8gGa0~$jXWQ*`#dxi#3-tnG@THE;)+k26bT zF^2NjniNlBNN})FBaot1$`=ldgF3*Dssrnb?+uixo7WTqQS=88Ar?Reb$}gJ2OMMG zb17r#T#R{WCZK`3$&Rc8%v~3S`~nK$ZjAG}cVau?rHN2cXyk z3aTRPq&-cW;vy__!B5>u5JO{J^7KmVsw3FdWa=UeYI3WokZcbk5WjAI=QDQ&n!dfa zsH<)b$-P45;uihShnj)K@-iJ(lT>(P!}C)ajbe9v{YV&V`%<2s+d`8CrmTRe^AaO4 z6-CSZATd8PQ2PBLYxs6Ue9h_Bv$Dzg`P9(*H1YE8m$k1w^C<6H$G875_54cnv(z+f zc;M3n`NX}$S%;T+138@pA)V1H4LX&n>^GB8HVB=v!7(@VDm;xd?|eQ58~A_BT$oF; zKH~&)WFbm4<5f!wP&5jx-Va2N=Pc= ze#%U5OeWOnjqbNVH*6+pQ)PY5y>FUxI20$yBpVq&g2Py0^LskAF0oOg{Be7c@9Rf5 zZz|C){)TELuaSQU*KZl1iqi_}mi3VMv50ZzS;TH;EoIUmD1dFu{+(76@*152W1*68 zyP82YFE=x$MhCl!Y%apytN)8sGYz6;{qo=?Rk`x#{UAny@Jsu0vukG( z6!Nf~n%BCc#W@c&Wzdxv10P`u)it_yi{S{OjH5aQMSL4NI35+puWP1N&Me%Ak5iDlG zc}=Ex^!buv?s6{ES|$_|B}=G*3#|fl5qR2_Kjti~w7;U^LDoB_GS2vUu-5P2IEm3N8 z$K?qf0U7jL1qmTIG)rlhN0Ok*M&-Vv9j=Ys)nY)4L_->IVc+)q41x2N4wLT4jM zI~_k5-|H$4in(N6fcAVRXr6NZsN8VRLEv-#k>#gR29`_gce8J@Z&TmuNvjH|S%S!@ zyReF*ZG9{dI7qv9+D8yIaF)h=?kp6!Q}BUjI{gRXnMmC#*w&Ah#S{}r+#ar#-Ln^@ zs!`|OKU-pxyc;IPMO(=*B{rQI{B zOI_q6p={d($;TZxeNHmC{bI{;Zm+AfpWB6%`x!IM1Ka%2Ob=Ah)}ZeaTwqTj5r(mQ z>&1j&3LzG)<9frP{qI4+0J*>nN1HTs6$%rPh@q{E`IY4KXYo+tRU zk@feP!b(aTx7bN)j;BwHggm7yotI~_#CMwVy%Yi;Ps(~Uh_7X4JbaaX=9XCt9a#z} zjpa9%n(CoAcMbv(&IUFdfQ|D+Sw0%U&_%rAe*U(l|5}9_%iiU$XEy`q@2R-1#KD4d z0tQY>%}ImZlV!li@2NiFJ2$63CKX4}v98qUtM;pJ#@1*x)ihBsMhy@L#?P`sr_!b* zJ0??5#(XMJdpaFp>mv2m%Mpj#Y_3DJmu56=o_RUS)>f`t^@`kCHn}oVy3}H?A~}8E zg~2IdLze$_1}#o@tBaADO7A39g?*eqN+og19CkhEzYRR?-k;Mo^|_L0cFbDg5NEaB z*f^H?EO}zUuBJtE9NYTf(V6@^nfGG%vm#yHLuVvSeI`9ppDnxIj?1;dY_@JAIQ$WP zJfVI@C6BZP12YiB6rSn#pY;17h(Sk5+y+Mvru2lCK7Cc+%#u_xB~!(pm(6ZnJz*vh zU8R?4NOtwA#HZJ1%#2_4h!?JEhVvsTgr>93)`T7tN~X=BbVF&!NXdzT{_D`pAXJL2 zZm4XV5JLo(GGs)9G2a-N7JK@^Zj8R@NUXoANN2qW2koVV;&}71_ zOYRmqkW8FyR8C8Y;+9;Fn6kf)AgU*PH>5D~*U``{#06P(Ie<=Ns=~VOhxo84yj_D0 z%moH|DK55oXePChqwJ%*07fuif{~dEQGA*c>iTx8eW<>DkeJSdFqRm$mEM^wMO8_`xt-zCc;mDA(}9mdR4 zJdH-ruUokNM!g4XO(L=PpJ7s?RG~qtwB!|$uIj~znsnV^-Lj6?y06k?AFcBp z!D$b*@AMOolma5vH?K%Z38{l4N)mmBP%0RZt4RT9=5zdu`9{j_30-5+@srBhdFHs@ zli$~~dkfINV$V|AH3I>tG&j#2kN^ND{t;lb_z|zxAQGYt53lc?!6oz<7Lb?u7Pgh2 zmx72EQq6`pH$PYOgEuq-?{IA1{t_I185aN1$sO4hObnn`8lIV6^7l>dzkGKKdT zKj9pm*y{3JeNFlu!q<4b?ggucz05wmF|cYMh?K=BOy#C72lBGe%p6UC#NW#<7d?&p zpEh;sGx63=VMg|3vxZYnD?Dgc4Z?$GwpF87^2e=xsvR1;OB1mqiF5m@2Gp8Fl;vmn9 zYQ=mw6Q)$(>}*d|FhO)VYXk=Zk&I|aMz8?M+59U~cy*7RuZ`;D#wtha>qQxL#yI$bF#>JT32!0R&uV#vHT{TZKJgaom@3vK=@m zeN5)Q^Sy-L8c(+|#(^3e1+Ly-C zbziv^`cgr)lvG*qIDaDEu^A~(fR_Zhk_EcO49%?hSNv{2eWcvihq8eaHHT&gcmM`9 ze@E#0NUhDh2ucYXGtPqx73a9~;Y{c`mmjp@Juo{k>IrU*6;Sx z;UsA7)kNyfN2d@1+L@r4wf~L+CPLCN3(0{UM2b2&Fo&nr{a5%clkP@C)l1)-ikexq z(~P$+Do@y*?i0R=K^I)>wBC^TR`onT?fdpSf}hxGAJtV~(Y({!=Buen@TWMhgV&HH zPy?R%{*S>J@TSQd0-Bz zd#WQ3)PXtSK(26$3VqU^12E_(wp12MGKpN^4Cu+cJ37 zgv1&!c{h(8)pZlfYESB+@q04^1~Fc(9A3diz~5P;?Hkn84GT z|4TgZCBN=6b?V&|a6M(|rfNI=!Ha2Zj>&<2_D!wjhlq!{&5eP<#}SB55yQ7+X8~;q zSrYl~UOP%(eRxn7^pU}kglG2tF&Ja3eezujxrkIK212j?jf5^?VJCtOC5RF1S@PE@ z@#w#ykF!K_V9Xyw?*rw)^U?nx{RTD836Ic%0SRC=dJZdZ0W=UPB-ad@!MXkDIfMw< z;i$kdk^lpMAP=-g{{m48|Dda(8AY4Eh1&gq!P)n39I>Uxbpq#*;ynDs7+}2t?qsF) z|2H6|WzjM3KxDjI_wpT9=pb^wh`?n(~e$<(Q$avS(WTC{eFK2OD0U>ZQIQKX19_zEH6um2tx}pexpF0OWJ>zf$DF)>Q zBIKa?9QcgA(oRccI~H=%L8|&2cLKH-sC5FjJT^#gWJ#R=AE=8{-#d2zwkHX_0(ihq zcmCs_*E;~W^M9zFE_gPw7LA-=!_QC`s&>`mM14;`6`nRD*|1sHAv~5qAIO8fb3DLP zEjk`oVk8xG)roFODd{R%ox}Vs5&4um#Ryz_uCf5j<`GzjTjC*4?Er z>b=fO5rHQqSBO0`?&9DYQRs|M==Z!sE33NiKK<@@BB{wOoHr+#n#E5^4L17w7I>|& z6DYdNbre~!Wo#n%qSFrqyuY{Q=stZbjX?i`SHCcrIueyB`AE;}jL?VJrf@x0V>Fvx zo(TO=J3yG!{EaQ?WZ5OB-aV2_)TQqk$}OODc3K z0`lPsY7R0DANKP$LPaz1#cE`Xe;72e7-IX~rqdqVesh~9&1zg<@Htx>!c}|vnf+kW zz4ZPv$X&X3rhGW^JDJ42Y-s#)a;4b?fiR2LKbBJ;$I!a{?^MJWRQ+Mj|Z10{r)9@@X^iR4LL5 z4jj^p@xeR0kd-eoS9QlV)a+%KI%{-Xzud*rgfd)7dT#xD1(HbqMnfh=wlRI$Heck4 zgx!i^I5AGHAIzJnvtrD=Got3`yKzQ+1UirW=PJ{gh1#mDZ+~x-> z-MFFIYj@mE1zo`&JFSf}ciLGhu2LZgPJm}P0x^#rGHthi6H-c& zO<0o5&W!Q=-*?D{XxCc87UI`OEJ85^E?xbRIxK=3&jY7}{>}J>U_k4_Gkz1_n?Oi8|8oAh*+;-8o>6?4?PZwXK#ylVR z^U2qM1>_wX=;l9{+jj=zK0`DmVb81O0VfP@m#ZVF>Kq7xV30X=-v@1Arboc=|5{%^ z%=$+Sd9eP%^4)kfss(i|1KehS@uBJrYBs;9W0(y3g-L|?s&hz2)U_P&pdIFdT0LVH zb}=KHjhZ3IqZtG&`cL%;)dysy`o}Vva&}K1DT&*SKovVkqABY62?snC?Z1?+e^*u! zI0EmF2#-j{~H$e{|XEHpJ8GDGc4@?6&CjY4_M@Yw;)+K{-3ZsK8J<> zKd}635%Z+v(+#7GbWtTgzmtH=hmQp|FJPO9xco<0aJ()6?4U&p0*oM8#QrNPVt+xsp89jUYy`76VftXyS|Tev@Mz`eANy& zTS!{)q3{N?%)l!sOk|h+!?2@)VIJ4lCZYj^CcaO0kRo=%WoRh*josl$D-+wLb|(qT3gu>jOH$7*IYB_W2qtyWD(I z9?2mb#y0AYV%=>(Pe12WK}H*O2MMg!{>+&HI6=C4asvZhSPs0SH`4rD-jsc?<@Or=G2c_)_?1gI z59{P<9Ud8b%P~_(HRY$w_D6rd>!5Pi)gcIGYDKPh5S7VYQMWVeb>>x?yh|{xAr&zN zKNj0BfrU`wm7*&zQNLV~8Vc&^r=Vi0jnbF{E$mb2Y0+hkLI7IPh~@>jBNNzJ(b0&amL}ox6k9 zw9gz{7>#}WrN6(@B6?nT_u6-gp|F(zlcra}G0_57ukU_)!L+RUJ32j~B`J6^E|q6B z^ZI-#>(IjdCRGIHWD#L#3aLN|vymVJfyEx?$9+fT7$Z2giGs72UOR0*u%U1eR z$-Oxpz1Zv2xK6|hYbmxdGY@x!hsuiHMc6j>wpx-0q-{f-1eru?f43h#CwmTBZrj&wWFg6)e5Pk zFf|<0Cb@o&>te5VKmPFHMmJAlQ*;RO*W#K+12GN*YwI)<;#en$a{cA>( zXVMrd+O(&>RhaDWh79>&OvEkok=JF4Uj8v3o_xCaK|ZK+xt{ECGq1*Rg{nWsp^N4{77=mfgP;A%SCZ2xCZfX&l5}Ex2T}qe zT7roW2P;eaYb-?4PjP0FqKWIb7CDlimkFF~c~W-HgTEa0S`Lj9m+A1waq?Q)I0@Gn zsrLjoKlI*o)XJ_xUlvDX=dOfNo_Rf=c4MYngpEYe9cN*AwHNj9x-B2;dy1;h2eUV( zy&D|o%7*c~4cK_GIkWxk@3WebZRh@27b)dWbeZocyV76o`$@6TSU=i2YilTLt1={v z<&$lUl)HoxTjw}#_Jc&Bk%OK#HuXv8PYpQJJW9lF^kPL)r{7w;hb=Kfv!tnum&T~v z$z9AB+C%E5LLo6Z&aa-YzH^9sX=U}X?QB@w_$b~|#HibEFlnW*mjW%|_brO92gyI) zJfV3VoUWgfz8#|komm<#?%-Xs;un*||73;}sTS|v&11pb{Bi!%i-4`#Wvv?88yOF+ zzH_p@Hh3gaZG6V~bT^-d**cLqK3d*aCzc<-TQv?wHC5NVe-Q7zxF`}+Gv2Qrn)pNm zR-XCz3qj5YSIbAilyz@P4iW0X3es^)+d2CWX$SW@teBl6fBUkepGKuj?6-6TCg>o> zTlXY46NeGSD#tgzb_%zvVm69kcI~|*f#caR&GAzfs&}(Nea71^X#>>~} z%+71?cDU;DovafaV}HMr6()0q?*mKWli=rcnZYoA8Z|JpF?~O(?@N zxjqWB!s~dn0_Jh!Ob$J{%4$uG1R1Lo&|%wN+~id??EL|roh~72qADRi1s>m}f2*XS?&Q3oWY!nDh&As!>MMkm6lP#1+ss!f#TyN>mA`*>`Wi+$jy({w(OPz;QA-=2`6S>lUWMB2 z!CDH0PRZe}{e8YS1?{lA-mF;LO-cA^Zzg(Vq$+Ms^6Er&uQq-%$$dmK$MDMIxua3Y z-XQLa@hgiisJ&V|AjQ2Nmh4e+H-WDGS*ShH z7^C--;p5Wmt+xzNkci# zZ-)22A-&+Z$?)RmLmosTo({>UE({+tTI@WHoJdNydk(+%6ta@%)t}*dY%62v1xC*; zxi>EJ9uMy`OHRrpe42no62EP+ii`R{P{J5)REz1_KX{x#wGf<`72EFqB|ro7;284B z_VGRKbxvVwJuGgA!N#pMymwt!jA4F0<@1-`7qGKA`c~cPYiiOI@b~%D`|(XhV_3U$ zXMtOw@W_n$&)EqPa+*cFi5EC62EQUG2&(WnhKiqdV?{uJrR@%8#8 zkJ4bC=@sIubQ7^IS{b)`V(jskgJnf(0#YCF528))mxN2XGFO`9OMcRHiCw+%;f+?K zmYkret8SeDk!HM19;cbsYZgiYCe`Fh>HXISW5hW&1}Dm&#OXED+q|^K6W>dA=Y^Ty ziPNqC#)qwV)mZpP%xiG}?=LCha=;0*Qmh`)aw?(0Jf z^>*kthc1+_l;#eb6RXWX~K?~(T+*)u4np*-SwR_3%?w0^uD#Kzl6qo zCbff&vBVjYc#6eBs@lv-LTiY8~r5jH?KRxRa-j01Z z**Sd6^emevG({-4;W;aBtaTr|d}_Ic}Qi zgX|>#C!Q?YPPFD`SOElWdfCBF9^8>Nf~RV){OP4l2tH@C|ND5i$(%;|UTQWKQh%&e z`z4Wu_gMC$u3v_$4@hW?~EYf>;M|pAlGGo_fAz-5y@Sfp(<5FrgovIt~ zDxg!qmoS!34_(NZ+=BCl`5R2TyU%qlU*QyDcc8t4u*kw5y?gU$a&hbKtj>4gpp|@z_p+J<#q**S;7oq66=2+Lk*LUnbzE;tnFKZqtzRbTSRUgMsMSuNqdvBy>ptb(PcY(~vIH305rf`HoH1bUs z%TaOl~CDxg5Us;trO9t`(YlNmyOmVe4#az0z5Ku@!}IU>0R-xi*t~%p)&HP%3rEqzN?Jmi7b$3{(GSxUoNl743 z0lf-X!G_ubxuJ$YQu1mV`Q{&Mfd}2lV>>iTKt)>r=Z4xIy+O#iYZb|X-bfUD`-5R0 zlY-)9=awU6)59;1!Sw?u9G+?j&Z5rW5&hRYP7u6B?W9HZ*K$M|qE@RcO=rj1oYV<7 zu6+LeSu+n~N0?-k7?TEjuWmm##9qArm0V15+Ed9@9hrGYihRE+@7{@U2DVuHVsh@0 zTBB0~*B@=Ja%_HEoUd={#?*~GTR-*H$9)aW)#`qCBbT7Mda;R-i{{2bS=To!g`|@4 z#%bLKiZM}N|7(_oI88%uDY%1#m~M`%SCKg7&j%L9&qXdgNvnoEqWEG_)yI&sWgWXE z4t-=Cg1_g(B)a*Lagu+grslHrZ}07ZIu9{Xq4}$E&;7=Y;dyn}gf6wkV-7w!$!Aq) ziymS{PoIqVxza=_)X%3BOYpnFw~VFOqfV`pe4w{!wyJM|9QMuaMkJ0K5&!L+Ek?P= zLK;LQ!UsRtZjmFy0wLK0$m_uomM;H#{j2cS9;H%Zmk`EHNY>N2E)*-}^ZKTT{YT;^ z`qGyDL@x{1^MdnenZ6im!9V&rdl0rsWlfr&Fe8;RnT!!Q0KFNDyMHSqG9Lo_PwUi5AA z{O+7VbrSW~H~2J&12QAQ5&8Kcj!`9fIA+8^Q0u6fSd!@&U8O02nOmlVm6E;}l@!X#`?iq-Yujpf3x< zp-johREzREy206AUnS7o-c=j*NOq_yg;*&_&<035EXjT9YIvcKCS2v)|wMafZy z5lXBa{tOD3UzdImfN_~{5o_;b0WbA;*5*fLR#~f7D|wAO?X89gW)x^K$ma{-RoWm_ z`2V`6h+MnauyV0ABYDW+mg4sW6%vEWk^f8WY<6e?GN2|!l>8&0&mWxPaE_EKCkZBr zZ9Aw26d;)eMy~5?+3C4?mGZ#v|CB89c`(c$!%c8^7DC-$SQW8Ie#b4n&=w?jhd@GkYNM&d2i*m zx{y<$?r>2;NYwKUKeWDE@`8h-Qa`h|es9gVL_ToX5$bz3!ofz=8YWjaSU$fMANRcJ zlpupJZaW8th7?-pJ>pT{Ff-tBL7FKGhg{7DhQK#;QqKc@{*sFj4ceEl`ovif z(o{8LFW=nay7;0f&?9hH>DDnZazgz-jC})irQMQtY}*~%9ou$yY#SYPY`bIIHak|w zwylos{3re0@6Me&cV_)rS!ZQs*WTxSo~rGtdI7pl{m}Ja-jyVLfj|KF7r<1v4*UsF z3b?jP`=e{1h}3Qu-M_qa{$v3HBpgfoqiLYX_4wa^pceW7Y5+_v?L*c7b(2e^5U+&4 z^#cfGa#7Ny94Bn&JPn~|G>VnPm*^IRrs-T+?N-%n( zi?t(&=@x6R@nwoL`kW)-Bynspo50@>KDRC+NjtA}9Wvy<@T~NBUy-pbvbYItZR+3$ zueuw9Vfg%D$8Oo&m@=F`4G|oYws_0ymO3zBU4i$-enRK#cfo$#nc&?8_Zj~m3!AbE zx9BQHmjQ)D>D9(yUtU4MqJ1^z?)%+Y&e&Lui@s9r7xSyo8#s<2yx=Dzf*h5|>`x__ zZ{5JT9CXklnYD~UT>8E7b0Ow;EWDl4qCSo6;BDn6Q4(H-wG+Zpr3{ZqNGXbO3?f1- z&GW(JLBk1Vi=>aFq#_4oyu$Y+Vhse|pTpWdf_AV4wNo$MFB_#EE+y;8TxWCom|r|h zu#b9{<8S5C_~#q|*ZZ7%5%Pp)T{Cu};0Ii9^Xvx@_-o@0yeYJ-PlR;uPz>NoiTSa! zR{?Kbp9R;tC>&?Xj)ZiCGrBJUi>IS4_;f!WnGKC}sX#K#`#qj0woS5B`{8ig1<`~6 z8;g}DJHvkk&RjBtE#Ey2zm0m!`qL9#1OXbh5zNeo`b7N4jqG z+xgDsjZ=@ObvwAkj^me3Rn8EBen%GgcosPUR3<3*z!QGvyE0Y3a8ZZ9ghZD~ZhlnnRS_`g22{ThOs)7c^Zpq^ z|66B+ac1q?Vlqa=RyN+tFXDjs93!$YtGhOICRsI5hd=BMFBJYfYSFO4f(&IlS7!y@ zKb3;pc15z}9$T>d)0h1$0nUblh!yn{w+$Vgk^6xCGA?e7@th-twGoArS319_SjBG< zk|7dGH1PoJkoBLAiSc*l)aUvSQs~&eYM&k?B4!?Xr(_02Az{y)Eou@35&m?jvlqK&!`(!Itt-Lx25l-n;DX&Ujt(daR%nWRPp&NxmzjObP`f zNN|I)y*$wd{tloFya9llTrjiY-zVQo_&gBLVd3Mh4}#F_(S8276a!Lcwf$vjf3A3) zjK|EjrtF_T{AB<`CdNtk$Dl|E5c)8qzxSTuQWL+AMPxrjfCyM5AOYKfj~TD+kDUMo zF7!jMLEB|*{`n$+L4K^R+TlKI2!tw~1^8>zYQbNtfIoXwzzo{{VFZBff&Aw`P5x;Q zFc7zYX#T@C{>zn*5zrc7ztJ86DEWI|V3L{rry3D87TCw!Ybco70rB^R?!P8$0F}25 zhH>NPC7NIo>V)|j$SAA0=_bQ0AK(8bWYrYoSFYy$eVD@&WMn53!SdG@%{CGIwSi+` zvK5YPd=d3$E5GN2T7i$RO8GEeBEii56OTVR%C={38pcdd6cGl{}&uU=8CA!#+aq@t5jki&Hl-A1;`$H z@b8`q=Rtp{0b;6e`{ShwKydb9P5-dCe@6f?sPhkl`nO`xzmuMvf1v*t92|e4KL0n= zAMgNVoc{;Z0P{QlK>a_>4=`w*kN)A~@8#R6!=JAL*T(-#QBH!XhyM#BFitiFthmmm zKY-k4%9)yDiMJ@#g#{udQJI+C_5>QdB(z&mzPo(QD7%Gnag_}>;^{Y2SZ#&8WPYRM z%E=+oap%p6i9|`&5nkH7C-kc0Lr7csZSnE#uq^;ARDziof0+H3Ro7pR$(N}inpl5% z^5hXg1$Y320XQE1;_x5g!FLXZ@(Leq;iZ`U8ASkH`r(7fKPx3md1{`ILIxmMz!vKx ziR;5(YzF?l8mju}6#zOP{xh8q|C7#-`2}EJ4}a15cf1J1A2$CEk_IHodHf$_dOxXs z2ytbm*d>4bL6r??iBvEP_`lHqOW+Q`<^lbKv#F;`y9*!(2;PAHZ@~5bl>bXyH1t1F z3w#v)OI$ScUzq=NH$chX5wy_%5povv{{}e=`u~QU2J`<78Rh>IGS2@E8Rvh6jPsu% z5$)qdV2Oxh^2I-^nzs42F{u^EnKqUYTvR40nO8Ey3vVZXY^^kziKZz0mG{}Cy{SP$Ao_x$4 zsNpH_pvSv40M|sogF}BR1swXfm{qR?l)qd<3>gO%p|!8B5x|}FxN)PhE*2#FGhQ5m~pHIpt(76lBjhY zjnv?bUHpf!lk-DAT3eBLd~y^(v|r zA4|s3afRv_eHYkC$CMidL;Khg`1xLm+Bla*qK$c>aY| zN?X?7e-OAbWiC^`KS_gqZ>O zOGZ%%lUzoS=k7AYw&Iavm^KQtRr50|1>LGBxHRb<J9yk@&PuasR za7%)?&UkIxmvg$w1c+IDJ@@ucXulrBWDBsV4-C!}Fl|60FLbbaoJ8+{m-K|Ddi0)S zoMb&}#R$nV;!Gl85fbn?D1XWpyo`_Oa7+*0^kB3ySMN`P-UP*^C**r3amvr`D>*`U z_!Rr#6!L^l^<&BUK^k7LWeLce+V~lE!+Mv*jiDtqn(p`JeSZ_-ZS@)L7d8mN#Pv*l z&1TB(0sJ1I{T?kDX*k|-5zM9NfK`<}MoLuQN`p87hJM3YwPU-O6rD3nZ!ykh zpMI)7kRBB(a_(gYSg3^hJPD$zV^d%loq5fEi`)Ucn}G2$|dE ze!55XC``8Iz=Q2fh`H~6nrl^y8{yYn*p}S=yvJZ0`YIOk`^WOPgZlC|uP81*lyNm> z@m@pAxCXS!E8?3+&SCZg5J>{B+FkD~Y;FGC*(N$59&u@H2JHr8K*pxb4U@Capbp3q zl~*cmcCR4y{FAZuee9+?I(h^!qz3mquDL7C>RLp#=U`(G@?N~3fg`+sC&$72bctVexoWc|4y*s?ce+amUp!C$B zZmA{_KD$DZ2gJXXO)*nKrbj>t^Du#x)oqbtSz9bMQMajYHpeG7p9!vJUw&pbDsEpx zPor2t)b~L@;X$c+aCNhvNePfMjzk*Rr3J?;sd0SJkURN}$PriUa7d2yVz;>1&!Dba z_`Z08u~w`co{|DCgt_|A$#+0tcgGUPB?hxz4+LJpS%{gk9bxg<`x(->N;{i*eK*FS zbo*(qlJhE%+tNo_mQpW!7F9Y?x2*o*`@(y3uV&JEOfjeJo^ml%1fR3Q@EPngiwwJ) zv(Z#P+xA`xSyS}pzDsKuSA=waxwLHgQ*k@j4DxVkia%opbW*V^rLy4{6A&!mvk0pA z!rz`NkSV`-c3ZFE17Ui55v23&N|uC+#L*|*y8SJ8br)0lh6KGC`zcA~hajAYsQoZo zzmZM|_G58$XU%&}v#I+(cw7I_^8K>yDa;aHG(Yzn$j(Y=Uo$VLNq zu4XpXCP<70Os0fI4r5`f?KNy%e#A<-Rgvb^p?XSOx^Pl;H(X{~4=+jO*BV6mSIWrf zIXr{oLZDM;LTZw1Xt=tGMx$0tJAp>TkDD>AVEh*K@EG|C+~gTfj3j}__nM-h8AFLT zE<65IDi%}ErM5`yn`|3KP)m;D<+0=~IL`!0;F(TM-y6XKymE@&mn@)*dK{1P@mn0t zS)!r6nqX-&jJ*ZUeOK{LYduhyrBma~9N(;$mL*4 zxc3cu4us7F%|M}VOF5h2q$ShsUnLu(s|Mq0I3hRJ4Sj*#jHRT6&0uP4`T0wxUsjUU z<7sNNVQl5CKeDu@IXoJd>1kNN!#F8b@lzS&yf8BVSc`>NHNGNlzcRKOV@@xL!LDP> zE&q`^3GWE1MXQ1(d~tI9TKBZh9=@wYmPV>IWiWlYl^{DKk9&&IUYO|1k}!%%VLXSi z@8o1kNkIACGK2<3E!^SD+UNz-g?9;jS5;pC$8aGK!}I5Sk8aL@5%yaRXS&ei7!)P~ zB~_sx$0kH*>sg1d*A|Nc%b@`ysTKx?%14*gVpMWh5~Oa@x*6cJwN8B0l$0|%z&qV| zdjv`FME!7e)tWWh0U5-f2OFn@)2x70f{XlgLaKV4Y#_JD$&}_%EqC|baeb_l%EOjYgS92KThiDPq&qLiya>k0widLQdVVCj zMccZ{Nn+ZCMV>8Zl%~pAJKBh7gonTN5Pu7YWUF$YrF%@%q=JeGWQRN37`cmoO~4 zsxv8#x^54QH~E&$K`IE%<5&8=Y<~RlhD}^&6u*$K%+_`qAOtBa^e#lT`Z(EHk4BbA zg*w;vHjEx+PErKwF#|a@hljx{hkZhtt-7Ci0=o(l<{A3X4UD&+o$DOmj--$$(IL&q zL0<9s^G?7`yn%FFO$HG`Ia1-%yQS7j?!toO>04B7S%U4R`CmWpd~5Zd(oCn0dZJl8 z4#OrPW!Qh`Tv};Km2z|CSR#ZPJ)d?q6iNBkMLmt24WzeKup-6??6&cMWZ$nN7GT;a z^vhe7B%I9)%wfM{+N3gh&6n~Zc3t)f%Wh?;X!~^nOfoWnwaA8}I`Lw8R?g&nNrQ0j zwy%4A;Ogkt9qC5PMT5vWY80vxp%- ze6cxQcjkW3k48-k;DTs67DA+pq!;=I6m)b2ElNbNh2??#|bz3&iMYsT(rQX zrWIhgSDx{SyzdjT!Lxislvk$`MYmffoF6%*oL;I(E)!yA#Sc#r8ul~Gf0hASMt*im zxj*ennJPNr>c4#7%Ta}c#?wFe%^T@i+DCd6{CXKaF z)YG)VqNJMMqXMTC*Xybj#eFImluk1Zv1Y6m9<{oErCTfiv*gZqMuwA6GqBYi%}MxG zHdm#hck6^XO7!qj_80Qi#Kk?3IhAwL`?^oKX4F!akA)`J9PDV`5tIZPyMhm4I`4D zm({E0?GGLF@(x5(A6bEb^aQh1WIj$EBHlYoXB?zW4Y${MCc1SUt1t;@q9OLZW;gdd z+vb&J35ISr-|G{c{&pQPf#)*j|YD6U{VSU0Ii%p30rU zI1M2aWF-?m?rQ)?7CIk?5`cqvWJqDac>uB~y1<`vk19Z^72w>Un?NjZI8mA4 zknMd7$R$OB7vDFTgMS{ilskFR_bx^EVu$mV#HY(76)Gl9_`olIT)4IC%klDrGuS zP5aVk8s5Uu_&{^<(d<5?BvKQhTb~7saB%Zvgz8z6zId1E8!UUQFc70qp~cX4bSZK7 zMuqw)z-3UjV46ONd>B&oZRg`} zhP{S3i&CS|_8dRK%cub(#D~Wd+K!t+D1eFquX3Iv+jC*c7Yuub<^Qj$)tBSnqLrfA(%`Per2T>${$%TM) z{|$*7J0i<9q#Ae9bIob`9Q%;#2r^mVD^Ik-3InbcNgw3CcSUi=*V{CuGYIN-gDHfW zL((&3Z^6HqR5r$^~`h{3c0uQpLTlE&nJjp{RTEEk0JbjPAkrdkBE z2C` z!eKe={wCV9`d2dMs?c*s=*v|$7E5gmwk(g^HNsTFY=RO$hM(Up^h@}v4R#hqDUtb# zfHk%F3BveDaaEU`&@u#$RR?mU7e}mVl{eGcR0gcUe%5saur#8z{VKElIDY*y`D|G0^TKcg;A_uopN#)ME zDY}4+-hNadP0T`y6mnGNk)J*xekFXXV+zp0{yNR2o{K&Ro^DpMbtnT1Xto6->_s6g zQ#<-+y!`UEqidk_W>?+vp2jbtEd5ktG8P1dOJxS2d6uN(ueww-tD-Vj;kL9Dw% z1C@lZk9EV4PeW-m1peGsZ|pPIzv-&&VeHA~aq8N!cYI_6K+8f|Fwr4KVh3qW=5yJ# zl`abw2S8BRHNHgowZf}A8}^**hd9BrHR^i1`?L2AkZ_LdeHn^OSvUiR=Lo1~5&hK| z8|=GBE*t-)OvVVTujer++>`N!QI4%e|4F=u2mVfi`#Rp1d%l&!)FV@~^!0YPgHl9` zHaI`4bSMAKFe46B`VK`;sZmSG^rfyk5okHdZELSpt%-bI z9G6t|WYf~47*v6F6Gs`k>mJjV{>gqD`nmRlI0ukFk4pa_W?>!`dqZz{IqK+iJ7$d z<4s{>F&ao4bRu84JQ7d05!H2e5g)@Kjb7SGc|i|h!fJk5eI;k?f*CWoK)XB_UtLmf z&GKlGrK6p=ir_}K(hA1$O_>MvJ7dcV&vi9F4UEZ%5SHLoq~MW8A~T62-_6sUF?<`R zj)&ji&)-2R$5}Pxgb2OgzwL$K&D4@cxGu^CK2}~&PN6h+X3O(K{q9g`z*0@>aQxWh zBPrGqIvI_Xc`I_Y(pUA`<%HSPNgYF%)2Lv@dWR^PUtAl~&czogB+6lAb2d!<<@7@F za{rvSt6-`np*}1Uq=+yJyfpFQLYuZs_vJM+CY`2#iYbJE2#MlVie$CeoTi>tLiH|% zxYK+!oR0td zhD1@>jPpVm$P5(G{E|11XXxTfVC--FBN+Fwbl<7HxnI@Q>DzZrTv&{-YTJY3{=rrLoLV0vI*@}_O0wUy0b=)-O zh=;EB{T04=<>oH}&D5g`yZII{59ezdgoK>r1<6!3u{GhbLQeA5Y-ZNSkCf*kOOBpp^lI_kn)U@F_6MGEfW6@aB{V!H%DPw zNqI@J1PayImzMCkm#`g2BAZ;zQlG3X4p6~#yCZg?5{x2?BAj;qs4hX6b2|I!y(<1~h$a?%uvXu|uXY`~&b=7S{K$S!*q z((L;7S@RWoZ<-Rz0Ow6s^)4m|c*MwCqJdjyH?42qTr(dqg>!Bq3mdCqMmStapBa3z zUmi!h3YSMe8bw;_Tx-mJbv#iM>k|oXD4;J3(m%Y$hG|ck2yi5HZX}1Kmvh!qtC7u0 z+<=d=L~BOI5Cuh-jX~lM%rG%r#{s-Op&&$XIrXp+_)d*cV4m1M)uJ%%3f>PKy9aHX zNwMqqFGxm`;ak$WgM$66=y7H)5O=&CFSe=*iRXP7VBVKdgfvlQOl1zl25$Us>m_B$ zEZJ4iBb=5{2d=X+YZlT}tN{w=-LG0IVIjv>WZuyAbW6P@!8lUyG~N1?p|Y_3&3kK6 z^otBRlUhVViAFsIe!cwZd`fycQ^Q8&7Qe1gii>*JX;@<}Ms%$6k8H!64*l!P%U~{N zOy@5eHs^OO%>sNdH^}(|%0$@K2#WA$FRRaar~GX$7q?Uk`8W}F=vPj=n2xy8(IDiT z7zNS?gZEvhswlmsYw&(l4b8iXu1B;F5)$U9T#IM2ZDs9z+mm31>JsDc^McRxtH6<_ zW@f;eG4zBuf18#sb2Jk|$1tcSDA3u;B84`(zeZxM5csMvYbzOt$t(UiBcBjpB#w@2 z#3f#OHgQ=kWWU)ZHSqbt->5GJX z_yHDA9$nIwX}uy%2Vw-K#*bZa(tLF-DkWZoQoEguXO_Fbk&}927n9^+UyYr=FW-&6|1r>V zIrArGHSK&vRG03{{VIx>vm}eA^oM%0Q+d9lT~h8HCv z;gAee%G8>K8bWpLYusiqySuAhWnP-UZu|@$zn)}AenYUQA5m@w3yZxF(|xh8RF{y$ zlpIex%Aeb!>F}X?Pw%Y#EVAdN97EB62OO!;u185*PZ(biGmKiYpL3So0^#t{#zLv(z7dB#7j>tAoYuj~buXCi-hX3Z@ zd)G5k&W|xy10VNd0L}o`&ooOPyv0Jir<2_Q1aqF71g>>pW3dPx@kD3$Q+{bSC{UkS zXl5?Yb2`O9t%ilf^{%;@dtTZ>Of{sYd3{ci@TDK~7wV`yFn<}^osCVO$h>ex1y*8G zp2k^rKtbe17!z*g4b;r8?qQur*af{m{7`zj`|FV1Vq8LfGGjJZUL@HV}$n)rfNf*yrjoFpEud=+X1Kz!J*QuIsQ7a5|VNzk;O+Y%pbx( za&Hu%3m<2U=sU^L^a_*4y|T889e@4uNX2Pki|XbsQ?b9?+1CbZUA0D!eMcOsA1g;d zy00@0-stP{vZF2&zQe2pnQ;_TpB>wVe|d6>H`&nqigaM~lp6hV=L1u^c#siJ&8K7G zs4+*oDkf2HFSXe%Wz2D}titRZL!H7`Dvra{Nmg&ej8B|NbQz(An6E{AU@TqMGjsIe zY7UZy_e10^k^Op*JpvSk()@h}M_86Hr^4EP)|37=4%t{|B8hcv-L|7vmL(pJfTB~V zA=vyFc8osD51b-_Ddy023#2) z2uV8#A;?zuKVKpxVmgOfOkVRK5XY?}<@$ipJ-aWDj=tT0!={$^Jd!RFG8l8;-%C%u zT6)KXCvdJFoKJJuH`+c)7TnpvUJh0mt!*&@F0^~UaM=}h@M6ThZYqIgQP81(Y<@&M zb5?icq#6PJ>T`I)5k(oCQ{Exg`J4XjQlVkc9FW!K5OVN@c1L%RXB*#nwRfR(XbANi zGPS(|`Z4fEF-6{Yb$u%LoRP3(L#)9)*n1#D2KtK;wlyNM@Hjy>#IGZ%v7wKE!(tvJgu%+>X@0MGOlYt+XMF(9(%h* zK2flQl0M|Z?w(I<3Y5Axt}ixehYE|uIVpT*Ud%dF8`RF?=ZzXYKbA%sw68WFi*Y)gYb-+}<+q&~DztlX^a3PmuV<6+B3V!2Q z;~-F<*?TymdgJj!Mw-Z5)Jev;-L{X8gB=hLTMLUvD1o*X~NDezX14m&zaQf`h{IfN2z|bJ86f7}}Hp4u?ANtIgvgsrz%`g5j@4V$aFC z{Lhbw(7vL|(986?>lZxX&P`3Pt;E*?JlAntg%_}gh+)6kpTgI?ws4OQgyFMSA$=6? ztO)zeOwnx5zR8+@p6id&+69?v#F^C8qXO9@5xtyI}&&`^CAc z%;=qnQb3rVdV>zE@!oL;@4eg`jBUDn%(uUFcj}BXA37n*;jkdLq4L$=!B;i(9p1-R z^s~dh(oEwZTXBk`DNqQo^XGh3ji>%xRMI=p)^UFA2T6yX>6F-adxitTG{UpNl1$CA zK>hC7l4EPRq#-?x^!fsNENFNb1!DcEdC)fES^v|QH8+r8#Ug#yJw+BtkqaZpjd$@y zEDw+e0eit&<#lLQ3=QCY=iNw;>tR{I5rI?Uxq0jR`hh zw+<`t8Jo{tx5iwtg&9fvnc-!7$EHbVNM*i3E;O2Pzt6u@pMhR>4T7-S;(X6@0Ja|>x&U>&9j<0MmA-=>9e`QU&s-}5>>I8@%&vE_DFc!3hD zJUImK7#unAJ$tTxl>GG55Okr3Z!A11O^2oxs=b&cdK@e>mxDXiGvw^9JO)8bD5@Jc zjoeR&rR%5O9-}9ocUUud>{+hJ2h@s7V|sC;yx(TguM$Hjzvpd&ut}}uI`vtiO+9xc zpWoH>K}RXzF7x~R$r2Qqw3$a`^W@wHxb4Q{`qZs!{X%jc%WTsV%3)Bjqo#loZKAf@ zxZoo!Yp1Jn>aWdKp0_T~hORd+BZKwyMl`h#EP)-p-c#919MEUq#S|wV6))iklnY1N z$s%SB^N~SHN&(}QQgY-q@b0GDI8_15%cI1?m^*8Kp&Xo%qwA&$7vI$QUF4hN#hPKt zZ9p3tYQiv9$SLsa8Na(?4{Q>bGMFk5HBZ!8O1rnLeU|A_A^~R$3?UGja) z)6c@XWyvX15d?7(80YtH&P}sI0$VO@KC%IBBU|ZQa}fWVlWpE8F$JSWA>BL~*~q#s zsS>A9&Mno50`;V;YqMV|fUBzJH9RE#ZcAb~zMfv+(C(V>~|GtD925 zHRiUxY1i@J^R^MPMV0aGf%>Nlv`U(INAr1#ns*^)sUhg4bI zM81}4?^}@d05HFyXABX)Iji%!2P_PXj?QHgfef?#@OZRN7-?71uKFwxh74k$Jec@0 zGN;-#ht!v-t8ROzekNk`S*94nxs}MhOcNf8w0QxrJwv#oT_mk3rtlFY2j-aNw61v~ z72%p;D%JbQCNu;eWWv$``F^%9;W*%WxvvK_4q;_Lbg-ubn(i_y21KwSx)i?@X1cgm z{ONe%gV0Jnw4vnfYfw~Pg*CiL!4GwI95~BtUyl-}b+Xd(X~%$6d5$fhk&SaP>}qwy zgY1l4JW2dbGzIVvQu~2Lc!S~cMgvO-ETXx0j?I7KHSs%|u8vN##ltka@zc8*mQYI8 zWjoYiTwm?v?l4IxFEG(E{mif@;e0HR^!IU-^mEBYawZHLPa!p?ULy&WxM4ywD$kvK zvThr#A-Mro!?u_zlRa$8kl5A6F{pCM+F_ z&C+Czvob~t}9%Svyx@WJJOt0KcS0%l=VQ6 z1d27}&^1txrQ(H=ei<@~gm5D(iKQ?q>&nGl-YHP~22#-rB4nQ{JNAptCprxkBQG5E zDWB*Ga%__OfMAN zrR_3C1Z~Dvz6Rr|&ohSY+fDm1eMg%KGk7r}$t?2Cmg`UN`Ii>nMP_BxfMUom0zx^T zL59b>*Uz^ETNBDAPx-!(FXz%^L!@1YJa4HEZa~fzDC>l9Ul4mQbu(Nz#@d*Ygf&R1WaZx6)tzW^#p`mwp_cuy)PFN=yJ`V3&Fu7g3h4@EC|SIUL=xs z3=FZnSc7(0!pSm?Iv&V3@vmKmWw%EQTRBc9InPOjT6l`Z9ju|I_Z_`H^)Y3Xq<&v4 zFY%bQhOY=s&yeBf>@S7rU7NC#>oU7cit#{W$&e)s5biFnz$RK7suk1y}_%|)^I7tn4?cmVaC&-gc!NMwj#;QL8Rwvuya zmI|bqZ3g7Ozxy*4MA20#l>dbCdVy7o8J3|17hv{)-z{0GsCQE?GMKWHQ~&j%_inU~ z`t89sv_6yfE62>Q3X}*LTPy*(xe`d?D_;q!&y{z@3YWV44Zw9p+##f8O}6NfZg$ zoBbg0Uw0Ho#nj=MLGD)BN2ftD4Z~{(^AZ&v=(Cn6ezAevVr#qo%fcD~g)&y4ie!!a zyMxjvoYzdW?ZiP{68Xc(Z3mhL^}GIMit2$C`?fEmF_9y`*%*-8aehtazX(}_CqSIO zdBvPY5iH*nb8#Av$%gH3;5RE`Q$otNyP8zVB)=gjP`KkQBX&Ax%ReS?nkaN$Y&JMM$ z;~u$HabpmFL`=6FNp8YKWuJMSynL$FEW3+a$O?YXr@qW0OzMbETIQf&b^s~s)P&@u zc89jP_YhmNp@Ugi!2P@Ak}0hZF#Il&NnLuju`g@xGl|S4cq+49tH*%W^MyeRith4R z^-HCeVbeFStJK`Ii5BtA`d0Gr`nk*U(iu!0je{S7O?Sp_Z#mCoZVg-$RJ1P}NAjj+ z_1r*gM`Qbht{|ic5hSnu51ur}y50oF>;ej9x~6I7hoO3&CrU7J{%;O1h3zHH4MVTT zpTCryLgjUnwlQ*PgCT6x$*8|BunLbEW=mr^L8Pg$pWC#%klCnS=|y)zU_tZ_KD0N% zc~OckJHNY{lj4{kAAllGI=;EA`mr!5&&XBt})JpjUnAd)i9@Y7`@>7O5BQksi;a56UkL+zU z7j(oMn(iLFm@28pi!;fF!~4b08*aMnv?`WE-)d)8%oB-|Qcpi^4A+_JrOhNyU=0*q zpNZd^4qOCa=*7A*RNc+DeM2d5?!byHKlX{1QQP8cJk!O%{;j_mb zvNSb58;V)L;X?D4d=QJ0tBXkd^|Q-a`{?q`3?42jp66X4xWMq~-K$i?5+x3dFd)uI_shSgqHMoP=PB2Y+3ksiM+xp_-wP81B`CXP$Koj#D}k{@*WeeC+wPfBOQQLp-mpyFg&b%y)?Yd^M6a0*`|(?~G2N$7$q%LvSn z^*bIL50f>h;qvs2PBg|Db-jd{U)W(M*{hW-F4R?1-u7hv`g0}@UA4mMML)c_cbCn@ z`f!u+9r7bm+$&8Q*Vms;gsxWa(ll>}=Oo0k7}FU{Wsi4^c+G1 zUL)LxTiekRNxXL_!M%np*4U(FXdl(uwdrd#uiUTth+^H`NAokS9ft~p-BMCe0e_qvdY@TOIUC}rPN zKFf#G8d#ns~#^xyuRZW#i@1#tF9Lr1<2`E#MPBRw=9xFX!Mo}Lu#L= zz)KQpqE)iHleYrXs0oU%N!v6iW^;I@hi7WA0H^cI+L*9!E$Obnl-UD=XX&_BL!Ono z8JA$E4F;19Gee&yWGOtlU4MVTrm-Yi^-Gt-NamKFetr>uhX61+a{lZKlSx)EvBMk=TIYL=8g^w_S$T3^zdNx@5>i(X$yh6j@w#P|I-)G9oKWO=_x z{|@TOu}jJsh)9He_Im9iF|`(-%7R~cDi0uyZmu=f2U{n^A$tBHrJ{}BiKrBKq(Ng7 zM&>aE%zw2V3OO~7_{{&!LqdnzJRkZwKG$hR8|b-oR`-o%Pk8y+z8_sGL+PO!S4X-_ z&7o2^H$P>jaJ=|AqsCPKo=UFEXU>>5x`Dx`r*_s8M{Q` zXKRJkp0M47_&}!jikajSKx@`HlWfluJ#94)d>sxgGrS-vUt6D{_ZzW{UOI?DsYa&s ziOfmoCT0dzVAvp&pmhqOu241eTv@9{#pY#W?clSGI=l7#%0Y$i@xXHG@{2rCo1IKIi zFK*ihP*S{-$O-Vt9fOo4)TrVBVX%V|9kgnJiebN9)Nw`>=YQHc8y`^<#3#7tOmwjb zPxtKbYh_%K_dA%hk1|Q}lf1Exihwib{}whruE7I6HG|sgs68JdTA4%ChFj@F?`Z8s z0@8=SIsgosuvVfJ%mQ+Xqb{o(5_f}${N@l-)eWL471r*qn=eN4*xy>U<7(EmU1-HfS9C%o!UlvR^_}PMf^-4_zJ`@TAa0|Z>c=}o#!BmDFHhWw98iC<`si5ER^Sw^ZCBOi8Hat%{#V`G0VsjQOnF%tD zJCR=@V*iOFKRT70RZZvtVPt>sx^p;^#v$0q>M6DIEvl%>wS&>{X3+KXbhuzueu$== zc^N|mG=AhHZ?}80<`neWTn36koP@r1we}&C98su&8_1bhg2%&40k1>f>Z*7n7rPc& z@ae?P(WlGa{7x`(aQy8p{H6zEP3}w3K!WPoRmrS|K=qiMPY+qH@lXUuRARkloG1p%CZTw#W@^eiW6oD zW{9o|xH~y@aHx<%6sl*Eag)*zgfK8CNtjQqvHYA#l?{mG0uGd>;KfClAzu$X#-sN+ z58C>^xX~cOsd5lq%7ve>G)oHiv@4RkBluXHic_0QatW;$X*V+4^%qxR;P20IpxNw5 z-^~81j|Ug`#0OTf)PC7q5H{Zp^nDE>Btwp{wBxyQx{aV#8*)mNmNWE)SxqksH9L8_!FTO zhu$ds(f9BSt7Ev|z?tiQ1b8Z7|nH9cG9W@nk7GuafcG z!v1A}%h$kwNDXiJjLH^``)&xpyY4foGY%7pyFsa(2nw%>GvFNi#C0d2esg+9$YIS1aZ<%L-{FgMGuF zFveLh93i@3l?wfXHf$$~3bFZ+$ontdiyW9doH2>lMn@HRD{=;EX0dV-jELM3MrdJ2 z35Js?KkeV7AxP@YpORz>+d^A;Q1FC|?N-tl5nM`Nd>UhiUzk9PB znFrcs8_%5XxlvhQKG~k=-oJ7?*WBp#kD*6eCRHnLT>*1iKya_;1YfGi7lTML66Ct- zzA_`I(d_9%h7atV^@Ise1bPepWW8d`s;RGy^z%5RrPo{4oZkep0(zUO_K-ATE4Lf-cj$wf>cLH765vd{5p zyD0KCr1)+WPw^QIII`2u##IFr4sJVQpk^ot(1h3s8tY!Nhie+wPW^I%#t5fa*)*@y zbcM%K|57&-pK_M8^mwJuK6$Zx!7jlT1ll8S{-OJmjH|1z^$W8}P)=yP=%{X0gZ|`1 z$)U#>m~s)}NiGVC#?x*70_IsOEYV#TFZS^m_bgrxd)L@!h35u_o>^e=*j!uN zdD+p5dWq`>@>b1Mf!AWhpB*XV=&`pu2#>T>70)~iYZ=Ak5ic8&nINOJw|?m0iL)4mbW!DH(Y?k(1QNeX>T^RO@Nc0z8?48e4BUF@1<+EZ z3#RpYE33Ew4f-GeI(3GZ$GJ_o5qXG9` z?jrCfL0N*mZ0%EjM%|VpvXw~jLn?bux*?@ACF89iO8d^1h{PA*w0FG)p}jnxtHD(_ zlg$V+P~CQ*aZM9df!Da6)*w#8C)0arpsv-j=<_Uxng0;+v{91F|F6m@XO3qlFh&+h ziqCKCn1Rn-p7OlyWmBmhD=UmA1c`I0{V;uAf<|Qs1LHO<1U{4#oGR~`Zd_dSvz^jH z_wy{L;kHMug!3|$M4_x$Lx+&XLNH-MG$g9?3LAM~fLd2+vlnl45xRf>pa{&PynE#QKq{`lcGJoAR9#5ocGFfLpc{Jl zbXJ?m?fD_NEG}XU8dG#K{WU;UE`LHhojHy;ujhu#5=gjM5YJ393}M)y^vvfjm!N5L zpiK6}e4_jf%=)A_%v~N*RZZ6OYKw#U&XY`t#9#Q$_Hv>O(gQryd|$F|@J?n0)={1y z2eGx=YzbJ7&k>m&)Js%bO49C*^>2boql~?|(m4EY9?mjUHs$z~1Om$ahYsbwNpv;j zbDBxe@!X)&?7>busk1A^l9iiv&;efB8LEZh@yOa#_4JpQ3s=AuTUjwN``hu_%>ej4 zuxE+*Nma1zrSJ9(TgNKl)FV*Kp8th`Th#PoE#~72PxXjKxvyM6U|dCm>0Rww;)(=S zNU#r{R!Un)$XRDV9BwYe z9V|ToKhhb^^}+G7n#w)*ciamWb^<)uzn#$ny6F&YzdKZepk?@RLw;%EHdgcC3r#pl zLlqyiM|BxB2F>V2SKRd*(v1HMurDN>ME1jg(h%4WG1$(c>#@u5m=_Yg>p(${GI4qf zFU}d3)qm+peE&Yy6psULu#xZ>K>rLM9dbJq8&ys63&}{XLT|glF~^`C43QyH?Qb53 z7cwR4x(mwZm8w|2mn79BQ(S{c8a~%1bqOILfeBP;#;5N){6Z7)C(W&N_AwucjaE9X zi!;ISyxhV7ig}uey5y#`1*qa#?)^kwF_Ji)=2_xK!{|~({t$v->t6Z&kv{j&j*^xx zj}PVVhTr-eql>&V9&Y?6$J>AQ3a-k^*)6AM`X%yW3MQL{_|i9#Wx=K6e}=M+!DjeC z$nVN(RHV6Q{~x6pR)=2u>VOqT0OWD~EyP4@Kn#M5&UYTtb7Jl4p{ zht_=#BRlVCTylEmG;P{5|E6iY1Kx-Bq?5Yl;$+kn#j79#OMEx0U0XtdVrvo}#EI1v z{$_l=VDKOi_lRfNJ+OA1$_DOJ#vdtO+mxb>#O0 zK}wUg3cHjbENt)f8X89jX39c^cD~V|?|G#ZGD!F}gk*OTHyNd`2hGRd@`3`(K zXO)F^N_Pg=t=((ysYxfzARK(0s6xDv)(g`-e&y*}m7)pQMEEd_&pMz!`$gr@5h4Du zMZn{NqP(6qQ@lzlvdNHIe&Fmj)YM^bJ0y?~e0?yMlx_EqsYKrBulcMJZnC$99JK}A zZ++2Gt7`MK2MMtddK92u);8JI1No|b*yMzedg!r;Hs7GOzQnOrdv1zrQ|#Y{{$KD$ zD7uAklV}vPpOc6o))P$Gth&Uq|B10Q93U!WO0AOvkG5d5mpZgH>!8Is7i{@~x^2rJ zZ&TNvq!RfG&L9Ed2kbhuvJ3D>T?Tl#DJ12)g68;yu}9=Nspn;%3*fI`b@&rhn%zfG zNKLEqpTUn)x>sJvp41^Ev+wd-AnGLuIOV=p%;?Q~eHt=xenm|138|zXf`n`(u{w`6 zQFLuOG;+58#MakJJ&^CAe|Czr`HbjKcTUY=GzHx?&0#EDaL`CmxT%gy$O&__Z>|Ny z1JW6E^OHExD9yX^&hHt0J4DX+mivt^vvj+c$>)8Ty0NtrOuWMd2G^Is6WF8H!>%U~DI*mVQiiA&f*i@Q0nIzvOB2X;clK)W58E zQA_N}Etih%)c=m>gz;SH1C>r;>6x2ul{&xxrI>8hL-N4v!@^;tM3q!N@3ftH|E*-c zU>ww9dq9q`uiK@7000`yL7Q&pfdT{$me$tR*3^bH9}a`z06@X;zyJUP06*@Pu#8!k z!Z#~PHWR2-vLd}98lK{|Ej2VAq^I24S+XHltM6gA^sDK0`jBO>0d1cL13XNx)}y>< zr;c1PX1!f65W~LyLPE|P*6NOtvKY<8Qzonil?FX4&z5fx(vky+4<3!Ht4j)k^Y`<|NSur#@Vj1>FqT}p&uwD#OS>hm z|JC!&`K8^iX8RTpWU{NX$xPf|OU2|6$)dWXUIW(j72btP)e}`xNoLqIiTL7GM{Yfj z_>?7z9Xo>~08o%}j5(yBiLNVrzt9IJiPU_jkaGxVZA;0?${0am#e|&HB6Xu-wUz?0 zj=UGxn$pUji_U{ft&wHr_IL2rU%M4SKmgbPy$VLj!XlO?p@@!OTu=23AmSI*9uOeK z+Hv63aR~jJN-#k}J68i*W^N`kx11nFg8l+;H8 z=*cFF*O)QM2r%UJR#O=NuRSo%6;buX*_$Q^G=&o{yyKWAeu?J(CEag4MciHUbag zNe%*&{r2%&`fe33Nt1S|e`jTgC-J)PhAGYnc=(2X`aAYaK$%b_Fm)N!g+LiWO*6JJ zC?Ogd*nl|h8wbFhC*XIVbBU1k!udAXa2JjS0_x2bpQIfj$ohw0A@H%Hu^v7jmSaDuDt$SxIun^OnN59B3&^a}ns zTCL4HDysz9Afb>oaV7pk51EEZNCzbCm)LIV({smH@3>kShR0R`@+J>j^}KCI?EQwl zA049}EBH*I?QNOn8v5&}YM2DnLE#4k*ZvxaCN0q6aJ`??jQP~?E_Ij=Uf3BD$}pdS zn`mLnkez(R-;|)UORWR|l}>t=6Z`6CW>PTQqfOBsd9N?lwRWB{Ffb+eM63t}0w`NHt{l~kKB?L*2^Y1b)3I}`SnZ$;uTO)WxP1^R1M}Zc zh}OOye$*I?f?R^NlVAH;T+!I#XU_3?ZDk3YB$a*l`cX~#*5B{&525JSZ9GAekePH} z@k4NdmPrg0$6FX5F^)vkj)W(m%x*uW@`b9SEwzv|bA8_epG|v;cXbkgoQN4Xqilc! z{h`Pz)RS>Aqk_#j3vfMYD|KVk&UBf?y_K};>kn-BNX199R+4IroFE22gVi<1e#u9c>e@i#(R_nT4Ge)BG-OTn)q+ zx_x@*7t2b290cq7mF0O3}6c$CXMrYBD#hAN_E zLgc&Rs1v#k@3`j)k0iZIcy6o@^k(c;SeBC1-2PpZ{tXS$dN%g;+ zx4ZCw)jCy4vM|;SngFx7ioS~JcQf=e_=RZiNC&$njOacO7 zfA#(_*;{M2X07w$-XK~ZhUs62Xh36{p`GuDR(c)9j_#HZ1$y}2_p)g@M+-`MurI)& zayIq4{rQpGX|KCpE7J#jb>ibyazVlc*7ixvccRuA`@(PT)=XAGODVj-4t@$7t_~ZC zIk#5j)-aSn?Bj)VALr@_n9FHYQxZ}#Ja6$P{d49JwU4pD;cMXAc8XNdVY zlkUw8s-A)>iHU!T+{`&OILolz0>?{m>pzjIh6Cf=IHDyUBx8ZkKGQ)GddVP(f-=Oy z(|y^TUuK=(T7VQHmK_dAPk~$!BNL{Pf!)?LU1id2IVo}`A*_RT5=tuLd?;L5sD6!5*(z8xD#_XWnj4LrC9W44bH=vsj=S4z@8TC}udVih3;yHD$Vi$3th0vbEJ zWKQ>)dV{GE=6w{G&?oqV{+QS;e)#m$)7K3}SH0rRWwKIIZ66H=Uh`{p?_x!jlww+) zjAs0j#At*|C28`cB^B@_NV4o>zxybd4T3<2`pai|{b|S!<`Hkzm;iqN`ZYtrJU^%J z2qqGs3T)XLbrsL6I4%9gOMqo;&9*ZaKC73^X}=|)I%im_q?M4CPEK(?2tg%=;1$p- zFK{q{HzwhFYEhXZ%Y67>k#R}0q9+f+5QK&!P}_ z5>$6!=nkvEX6ekRvJl6&s%EZI;|NN*WMCo*wNkD0g{@=qf7ZQ8C!xiqE5x%<&AhM{ zRd{fq!FunUBHrSO{k%k*gkQV=t{bd8jyweJAjl^SUFM(oO1$H$)%$)H^)``QWdy+g zUSHdo-;1=wHg%wO9ZV8|e?)dTz$TH^KJC6%L!;SEaajfI=_vHvq&}x*+c7O09?9* zPEvcvL%OQ`Z;KuGncbl^IC>!<9}VzDF_o1H9!sNsLQQ_xse@TyMd`am_jl~EIG6wc z=aGohDmCU5M%{xTw*4SnIDM!F?d!Q!!|SFT2Z=HTzX%phL_v>wO9n_p5~$v$-YFK( zWSm&lm)ZDEfy%U%Ysn{-B+43MOCL+Yk+`yfyx&p$t=1nz_@RP);5 zK%d>mNh)cC{m`jU3l)DxKrO9eBB1o7+zxsp2#GJPiKfB6(2J>5nL}T|wG+F>S-bk6 zgy|NmEoD}Er5jBMpo?q4yo^0ln+~8mPk-+3a&qCZVZ#^(D~1B}`gFs{>p(0k)~|D@>;zZb!s{~scs4xKW)F4M)%!N8+{^! zeOI1Y2NaVVDYwGT>|Nw0(is25c^yL2dpgcguizm$-+o)MJ5xzhElGZyk9*Z8IR}O$ zp?HTGT#1qi$|g34hHBa0=c&0d<}}M4=j8RO9l5Jy^~a;P9qwLPV_3VhcOq=v`iQzn zJZH`dk`PkgK;wn2??slVg-<7&-u|1rpvr_fz2m@>WPN0liS-%4mxc|t?7|mT^^d4t zLKvDb<(oe=`5}j(LHBc3pP}{J#sOj|Y7aEJ z3c-zXQK2h5Cc7O-3=JyU4l~pyT$XU0K=vq_5LrcpKjGm(Nf`#lAWe%6+2q@;kHW(r;n-C!ISSf{)D*I0v=?&)E9 z!TWT;X_CXgQ;r&xaMkkc6~hLM6BUV^cTD?;pW8)~E~y4zU82*$z_dol5$>cL$d1%+ zacH!4ncFebnk&mK(Me;aRuozotb}5j!Y@sDziK4@-l%urP50woS;abn9!}d!*Cxb5 zYoa(i_FJZ$x`Aq`*@~5bZbUmj<6-?O^uWW3EERF3wDgzrg~0;Tt+#xN9Dx2&Zbu4I~{|tzktR4$%3rc~CAPw8; z1hT~GqGHrZ2YKpu-$5K=Q*fS@AWrG)VvqB}i#-y&{D;k9oaoe-YqHR(#S$F1X#!9H- z!a$WZf_BBTltlPR7D9mfND!t~ysMmV+G1@Vn`=;cEqeb;snL4|#;=>PIiLH&E8S@K zQye!DNR;c|=yoQuGmmrwLd#VyY5c$t;(ElbRXYp*^6xBP$Cz}ihG~SvO79YroQ3U8 zsyR&9-Uc0-lRU{1v1Gcpc}*`iiz!jY(I)9@_C_8NrLYuVcxBm%k%=>tr9BxcX2qKC z435!UJQFoX61bK1IA@%dP>kxBcT=ryOW(rMSk^0E4ft?aFP#rQsh1)Zd}}dbK%t zs@BcL1$S6dQ=|G6^$V8E(Xm&&nku$&i+|$d9|`5$x;gIbUAt)10TBBiUeZ4|3$t}TcOtzQJg!h zLmA{5AH2jX7g!2?tw;HFJ0PLn=ic8i`aQMCO{exXiURL(w{eJ=VTPa8nRn4~*FKZo ziG;GD4q2q$S_U#4CI%>(cA;fPrS3~+Y>(Zv8C?LLcHtU?Ha_ODpBy=T!u%aqofqN? z32Z~TGA0-T4G3O%jmjB3G9UF-!*>l22ZbWGuD0%ms4^pPwiUV_Jvn}l5|rey3s%*g ztBO}4(w~;&)hbT5B1C}_`@B&#gGKNgP1svtYpvdmuuXuMTn)QtF50|dp!X@nZ+ULg zFq;J);ej}4MyPlA3D^TEhMCg)+DA%W*WOfjW+ur#B1};)Ixusw+_ftpD{t%Sh6V89 zXFZPWpLec`d(7evkwR}E+7W>)i_2nSzT`nm_m`Qo8$?kpNyj$~Qx;-J1^miubKp$C2aE3lMU zj^e^tPEe~7EVy>j8tipXjj3&Y=yc6u$hfb3SOYl9zFsKsJ~~duz}qWE zoRRf`V+~?`kM9a5l*xr=gQS81N)z$g{$b?f@^4Fp!*wCK-S7U z+tM?DQ62i!e^49g+5UYaA3T*#gWuH|g{xV+SdvSwH3Hy;Wo1wk%OT&JOsuw~bhnI1 zNvK3jhPIrp(xhxo>grIxl^+|PoM%+m4|wt-i8Ric1(7E!nA!RL{ja!t*kXJb%dYMO z_WE}1E#2<$gHD~FQbQdk??75S>##1=#UE}E@*3!y7BezsooTBzwJG8(&zB$$u}Fgz zyTTzZszHp*kx}%A$*D8y3B5!M?njT$mI#!Me-bocfMaImBqL-*P4tHUC7YdF6W#6= zDJT75@n_$V-0=ZRkU!Q&_M-bbC|mVv$rD`Wdt``(UlJezS__h&G zt;ByBO2T{%le!`Di!a&fC8#GlO^?9=_WEdG>k&&lNzr@r?1~9?F>fb}2!IS$t~gtg z!FAe`tZFN!Gs{SnwveOAHc^NrtX6Cy+qmnJvS`A#`oomg1D7+~f2_y>1u1*|8)kW6IB(p> z1QnIycf*{#678gs-O$$PrxV3p{nnd)8l^G_<8xiJo&8J>l)_7%4kyoLF57M8s#*p! zJ0)gXWL$FRbk(zSuiG76w52PbQk+F4_f17#4dG1zAuHba}BgI`Ng1=Cji~I3s|anV7Fx>jLbA_x zMI79|zDn&lrpmH9E_Vj=<#bz~kmRMGqJ1Oh-MTZ__6&eJ`@ItKt10FKE4}yS}ThC0U*1*z1~2)`Ue~BtLJbVCq?3 zMd5hDZS0JPQ{^k^EVD3$eK?v-WJ}ILr$QEa3Yq&I7Y67>L5`u+|IPP|bz2djxpJ^N zP)Ge>#N)?s2;p!t^ALr+(JKW`uK+RfgEA^nrDGm-jrOG#Rf31I_c1{Sbw>~z4>0;) zh8VB<0-8aP&_l8&T|Cq_L8PO3FduK$V=t-iJjF@70x{HrJ&5%#08L#aw(xEMmJS)Q zt5sIWcgnJ&=UP6%+xykj!boY3m_MtqmvrG-DNE5vHMlbW?!yGRBtz=yQp)9LfWah4 zn|QOphWr`L+g}L_926@gv5yp+cWf>@Kh}b*^)hp0?}G%pgH6(Jo}oEfay#?RRrpkX9hSv$raj6T-RkC*T}GKKP;jPxxu*oKW{i&93!`fdKuW& zw*uf-H*4mEjj4Lg;DMP!h)hPicq&OXA6h5TD<^>;rdY-g!#qBh*p=bc+U8bsid!we zUY3dyRd3bX_XRPVV3hsyQ7fq$mz2xQ#9-Azb(k@d5Slz`i;1U6yL7bBe8V!^GaEY}Ap<%t{W3levwLFC~YoOx?p?3G$M-b-~c>CqD7jG=8gHSj=gPvS( zZ`A)ba^G^2(6I=o$6kv<+6w*|f6MMhEvwq;S8sB6M zOl=ktL(PxV3CD+fUJ$ONG;LUD;MJlyfB4{}4bFyDTQ(e59RzR4-+O)p{-HDr_V6mtYzkZwdz2{H0gZiOgZzx_f9)%)GA{F_4!}X)up4; zf~o(v3uyX=t#!NVvD6pO#mWq#WdluEj%_L6@pmiobGUcf;V(cb|0BzHA;2)5cgum; z!9Y&`l?RA~OD3U#0013GL7R66fdT{%rM0!SwXzt{d^!(=0Rsoa000930KahE>+=`6 zTk;Rt6-}77qrF&mn?>yqE=IzU@LXq?K|LHspi%a~cW>FZmN%_y3={f@>;yd~^d4A_ zC06Wv`+w>-EZg_C)8TYy;Vm{zQG5zy|ZZ7xwhdXWj?3~V_xx-+K;6ufvhe5-|C5? zebyBTjF~DenNWZN4P7yvm9Pi}lTJd8jl2hTPOB#SM{yDse^YxoN~L0uGg(M?5AUy1 zv4y;^d9rc>D7?iNfRisp>(Mg;r!7XGIss?XSd<&bffHJT+zdQXh%m1#*jkhx$=Uho z`A2kW3=nk5M;3tv5`D*{@&`E2641rqAS*%A)WCNNer=!k)_LNHcZAc5?WNjl2AMhZ z7g1_>i+Z+HL39uIBN1-!gJCCyNx32GNq9$ zbObBRq$YVsX=ruEu;2uJ^I4P%_s?_UdGTbHpEef~k-g9gTTRtm|LL#o`N= zSV-OgT#iCD)!}{+uC9dq00B%6Y@yw*3bDY?%NMT!X`FW1hK44>67j)cywEiF;$_56 zgc9M>8jlCdtIUdp>Wu1r+?d3@1W~_Vtn3FGnC-?^p*<$7Z;o!sA8A)$V%P(Tpg+-gwRE z6%Sq2Av!WhGmFi=Vb^%B84S61r16BsVmPl9xq#E;5ZTk?9AJB>X`+G}LTF6o&2KzF zSS?}!ePbB!M=sLj9Pl1@6_IHTq)2?9Xe^n`iG06MCPC1bcY->{W~Nsf`n_6T%mRJM zcp~4q%#?&)^?R7d0ru1sykEb=x{nX@-MU6Pa) zUieSPV`4dq{fj@kdg2(g5F-^*%x`lCAqlw$2T)Us9&DOeOcWC}h{hdtS04US5Gu41 zz;yn!VW`HNPkjn#8a-1r+Zi52Sn88_eFj_dN7{a@Qt1APW>ITA$s48vYMX9@eyX+I|_{v>lR!g4S0OBC%7HVQ1yC*Y6ZcFm!0R z_;Q%-_4vEG1L7qrsXyOw5Whn-f@QceuPSS9a&)!)Q^3P6DTN{neVy2 zBp3sml5h@kjIQ0l>UcS-zE4(o9E2d(rWp1rEnCtI97?(@>+an0{s{#o+E3xWV}YEeR?27$kcM;jyHb zX|2-cS(1`5x&xWegth?E^ylNRfE_B1vSowp&71dONll4@Y|I{o%gUFt1G5TxHAf@= z=1E=F@tk2%X^H{Mh5)F#oZ+K3k8=HV%E`@lYP57{6owrr9bbD~77hshwl;FWl`(M> z#@!ejJ^VrTlNjz5oX-|vN1UY(`WtE9qPq?eS-wt)4A6#o_`(u#y@_O42v1HjVg?gf z5s=uzE;8=)ZFMeFm;}9hYdMp5Dz=*nzKQTZGjhB>G}}Z@$w!ZmBnoY~K&vg-Wd?vr zo@S|-#X^iLop4zZ@$8x`lf902Pat6HAj&AP7%kg-&*w`iR_t;55R51Uji588{L8w7 z1{_R+|0r3XY&KCem0yvSK&0`C@Glc{v{;*A)GN`CjwQH`3H*unS7!4&`{P9E97$N8 zZ&Efu5KncI4FR^k&Yu2&w?LiTt$8F~;9dHJL3#B9W*Jxr31wuEX`+;h;J!t+%~J+0y?6nW@FMGB=S2dy!`#gmUhJ516W`fwu>fSuzyLq4`&RSxV1s zz)x4!idkW81H?xulUS@YOcgBz-V}D4_TrS%U~=s=7rJshxio`tog@Z)i*{D z=L(;Sb|eG$_;khlr<~1f2Do8p7NVe`Fo>8rcp&K*Rrx_(WNb@{D@Oc5QFei{S&v9U zI(R@&K3)7{G@C_1NE?Cexo6ekZBeO&(}rzU8qw>yOepQxHAawoa-h2aq~0|uBL8GG zqJI`Lz?;E7y_-ae=nXanF3p9KSG33r6#I{_lFBRrc-IY{RZaM$On}@0M}Lg#J7c#c zf}R+s_JAe?$Zvigz$5M|Fl@5^m)Mk+ncpfPNnUQg_TqI$UoyVPx6^L?$=4UnGV>o7 zajxlrK2wzTm*DBQVLM-UV-UKet1YNyoX+_c7ZQgdMI#$!SH?Z}N=!t=sT2mt_ zviMK`(SZ{=3$YIzQTyIQGl zUXe2HAmxU$stWbu`~Ub^Ij_lXArrSgd+E`b8rk)lkPlLlT@-AgUvqP;JPTnF=Ds4d z@!cQ)JJ0O02pO~55v+4y(nYmN5Y2c(tC;uptE786r?lfRtoL%X*iMp>q4ne^!i zvbI|;7V{=sm;0-m#1joyks$6uR-M~b*Cjm68^%rjn05rNLocpuB^wb4`BQ@3Zcw$n z-T4V8NaEgEsrOna<)1qmahr$}09E?b;j`hZp za#CyVJ_r%jXpX7`M!Pmr(uF6GWT4u1*j&~cUg|Z%Woc44T5I9>d6En)c<`ImX-Ky~ zPZ$n$WIJ^53?^(RWE+lM*B-Ev$e5UGbZaKO3EP?I6SmJ$!c2`uXZ&Oo$#hIMnIsan zl_vW899vc?DMtl_V{ZcMb;)x8q(Zi zD9zMtSTHjIOAZiO&U6C#$0d06I*yJ*#6z+oexMtrt(dm&OC;4Kt-V}*=h)9&3MVVN zDi&>YY4rtOIv9l0hYYtak{O_X5n^A`GFsXt^$u}c z6IuO#5+YFH^U1#1|L!l2^WwuIuW)xyw0PR$Z~?IiveIN!`Ejv7-0(Mn{S057o8jH@ zBch6})hsWO+D^>aBHfj%KcCvK8ya``_G_7-oI!`;Mq3e&P`Z^-xZu{*>`IUDn3WHS^3G>WWktfR#cj20J+(+BW zDSbImIsNf3#d8QJ_9woABY4=3E{u}m1;VuUh;qwq^fUO@XAZe5Y_#3}fZTR*U4Vo( z{Iz$wLMLD_=n8F>cgDGui>@oNtht$d)Q(SWovq-5F-?QJ`;pX(cBJ9n4!AHws6U+5 z1UQpr8fMZ>Q~X$CT>F*(B__mQ8ZjJ*#C$YSKkY5z17>7zyh*HigIB3=k7!mV*s$J& zWX03VXQEu?jLafEXg+~%XMOkJ>6k2q8<#* zin(ipS0u0?$IqJ`&-YKH_-R-|Y6x63hgZiB$-f3>58ldhm9JwT?2$p6?!Ou~n+NjP zMp1db=Qk8}kEm-FWFR)G;08fTT^rQ#Ac^t z4Z@xyaCiFTt2PUbobGHapr}l6&AOTf1!J0!^cMDl6HQIA#;uzA(OPaGQ(;V*M71!{ zpBjw4HfSzo$+zM@ooyrpi&zBfTnTe`yS=C~1d+V{RBtzzxLo2>6%+7hl)sK*^wXjE zi(+xaZ)n6B$eH?rkm4Lu&A2mZdroAq(M$}qW-ZHj61{#}+8|Er$tjryx0PoBc#x5I zr&4{=-lz%sqP{5ruUw>b(%cr zkK>cwMA>~cbBjJ_v?E{JRpYue6O44ecvwdjoDQ%56Ah1m(&r*q5;-WKEu9U{+DfDX zV7|Ca=9R|bn9s4uZnGWk6iy7gk?lkqpHAS{2p^DysYe$!qTnjzU#L%3o!U}Om^81< zo&&K479Au9zS@tfw>A!}JX`QO!<&?(s)m_4NvA$^EPr=7|7=Ao6GveFz40_N)Kg>; zN4VDpv<)}~tn<&+zdTYF*nZ5|iiNwD^?hsBmh#xp5uW(j2P;lXnXxH@CNA1FI61TX zNRq4aF>=c~h-yZJ>rY$NqyzrPg`mp*&|sV$myS*)Eb85e zj_0a@m{H53z6UPvTPHOZ!8?de{7F`8q>TJdLVqJ>e(Kg7Z~v_HpcA8wOFm48O}+YN zg0sE55WVq1t%^snpV>ay?+^XXh;ta|!J`sFp_S#UBTQ+`su1%>v3hK&#)2Fg3#w*TE?sAjztkM6-2B7KoN~J>DX+!)VULu-3xl-b4hcHW*8*R#xFs$%Ulk zQe4Q9u2;0Xo&3td+(NWJd=HIQuHX37V!W3- zLN2LyGVEe7G=ZCuI&kfs5+zrE1L&KYJgcuMILORT<4xiy_vuwZ39Uy`;)U0?pWR&i zk?&MPTB;2t!`EmHKJ|A}J#Yz=+(Ok9A|}yp{TwLQhA;+0xbjiQA#c0}rB)lMmzp)V zE}N%%F*@59uO3LNsGvZ_=ohP1JIc>SLuueQ0@-%QV| zhZSrf+5AnWX<-Q8*~Xzs87s4n_Y0n*>h5GwoPfZ+%uix7pW-2aNw@=~X5W&Usk*D6 zY{LE5d}vz*1ThV~b8^NtSsT8#`yS!%P1xU!t~tEi6U?x6Yg6dxag*evRd0;fqZ?%i zp8dudQ1brAT6S%srZ!C5#kGb*l(5%hS(%Pk9*!u3H%MF_&cpad0x8e{(iV*3sO93x z{mfwymEH>dqkEmau-Xp$JRsK70w-F{Ry7ExoH=mjp+%DDp>?4+U_^}?gCA^1miPx^ z`tiSO71K6dyKzANmW#oh)jcr~XxRM53#f|&26V8Jv>SrtPx)rXJvh)G)#kkFbDJ$O z2nVYq>Neo~;UHv}*{Uv0^QxC)al&BU0`Z#2OQgthT6IRZZX;FB-DyPG|9(cKItd3j zW3~wS4;Pw=$MwkQmlwT$F5a~fgW&JJ@!wSiU-ktMiE#138v$b3Ij^*4?PIiXvjzrA zyGb>lrJ9K9MJxLK`UOUlyM3J@oDzDh^7*#I);60V2S7iZAtB2O#V?@Wc1H=Lb{|l; z-RJm?cmp|gO9eW1<)>j?dReCL-3HfPVb!pg=-oku9E-dLAd4Ss`^R0r$B?<@krkGKzp5bw0YA9z3pv_au+gB*EIBe7lhEe{B8dKa^)Rjg z1P#p159q$;7IKXg1CDioQGp7a4!7{{!fxoi;YShw!7ZbYDn3PQG}zKGTm);4)Y`}8 z0L~*PK#Vm-yo70~CTty670Zh(1hSIy=*UFsL9GzjVu!i%xh?3@YLOYlGd8k!f@m4Y z1#Cc!PpsK$`dHiRNeg?xH_sbp;r&l>ji!I1uIm=5m9^cI8Am}1^QYCf-vy<$c)%mu zre~Mzqq5-hdlE<~iAdYHf~6On@(ZkNFZ*WHXV}638yPC(Ph#US&Np?@9Mh|8Z}h~f z>gEQh6;%i(k!BNrzJb1e3#}ub-RXA-NjXTy^$UaBg1Gd7*$IxH6k=SH+3+9;I@8QB$*fjGpUIb>0aQg zEr9(+TBqb23uuq1@gc6LZtZ!Q= z=M0UNj@cyQmk~gB5}>3tztSSxQF9)v9OV>_#R{h7?3o3$%y=RC^qfq7b-8xd-a5#>ZEP7R#*SMb#(lkSP%^gF|XtRoJyTfm-9^|^>-rs1m0IUys za0D8cmtpRTuFXfJK`w6=CJ*w$$I5FbVQOO8Q6vOf5io*x`W76whg;|)QR|8F%_vz6 zEa&AjZI6q65RlsEbhYgfiv^74xvXSiZr8<`D37MLb_!A4P%1FV4aSa8Qj^%)EkFl@ ztR6uDPFzofP>v8>Km40Ma@|cZ+yXK3jPhASF+W7Rcy+x2A{yQ?2}{4Eke0y5rKiuP zLVQMcnemg8+e`$Iik(x98ri;CHC{6b|2C^l@hV0Hq|!fjv)8-ea~f#;_-PW(Zpf}x zOvydO0POWiA4qgjqH=3T^Vw6?Sk3|@6dE1yOzt{kP}cN{7!g?cfkvnDKO7X+1k^hdJgeDgE!G!dkIXc6dcv2KHw^0TX7DX>Ats z&rM}Q-+w+S+3rau{^C$%;P9v#tYLRfoi1`{%20AELm4Pjx^&4x4qeRj724Y^5+iK_ z-yInDe1Hv7?hkL>wsP5`+yS>DNN6I7`_(h37y8OEdBoZlQ(#Ew!oSk0Y?emWPKi9p z_U2D~Io9GZj#uOS17@X}Tv#uN6M>~;1x}XSP}lCI3a`Ei0X^=3jfdBv>J3*U&CxHS459w`fZEZ+w9}YqA zzyJUP05AXh)^oIY&cxA7t6aQ-2D;ld9CVz;!6u|ZDe!y7wN!G<_gYqD9dHv5wpEq+ zQWs&RX0|Xr^A_rzR%2z5M?euSs_^T7c-bPB6wxfneP%3#nwUGD$`%Vo{1#6i(#F#c z_B@;Cab51gv}Z?;n{gJvk4c&bt|H?qt#Uwf9UBMtk7xCfQoQwWk4Ce>Kuj2y@dOl* zrytn(^3xD_)M>p-~o({z_)mYaT zAlgHeDFfP+`;J61z^4b&HPiMGO&8oM5r-A#9BBu`5MgNLoh9mpw;k$f=!9YDB1(2m zG9QX#lTZ)Hh~oh}PT?_j_8Lkf61sw-dWlgiw!$G#R-_#<6$R8|fzLZF(tWAB%D^xv zkAoaL-M+_fpBId?tF*himXZV+l5CoN&5`QPFPx`bnI=iPbP{J=k<39O#?&6yhoS@d zuCWZRez&@y^u)Dg3yK(_TAO8wueY|7$gk4MlWQPuxUNW<0wp+-Cb#GO>xj%o%Ztsv zd|W-FYSr~1)H=e0_|vjnfty@ceAj_F&8JlL)KCmJ;~@{4dGSDI`;c-er2ZkS%}Rw! zThh9@JN;*XDmL#>^k-7D-i>^fl`arIE5^hnaEB(EMk`WoeP1Lkx^)EF5^hPU_32}5 z1YR*9DPC`(={O9KCp#rj#nzmBaR>s2U6*zuLW$>{BhUYC{8Pf|(r)e`Qfn->kGJnn zMUjt?Bfd{I^l8VPK|Pxs&sq5$k71GG2qb0iTlWLK&-#aDFk(lLPZX!l#=~ea2=bv^ z63l`MVKaj@VZ%IVw^qk|stZ7q<7NrDeQ`)t#2=yohY|LkR5$V(m@e$y!O;CkltiJKW{eQzI|PBuzFJJbe)jD?Dd77|_yY#)udZWu}65>aVI_8EnB~(1X^{8ik9+ zHhE(BVrNE7i4xHi@p^@ZJ;To2YqoX^RAN!LM*KT*7wlyfbl}!(s=w|6i{KHinbIHK zkTx)NOE?n;%PQQtO4oB`o&YNmO;E4ra<=5(Kwd`c9KyoU1YO`HOZ!=*$jY<0t^PFI zv3LIB7op#j=r@R1^RZiYm|x0#MU3z9`cnU}O9UwnbGVVmNPk_p-&H(Vewd@oWV@tV zCHd*bdLAUTuT&D3H|V)J1s>lUrQ0HHda+WYek$>XxCqkdUFmOqhC$PzThShzyDK>` z08!2U`yH7a|HNL{^<~S9rr9$;(z?^)fZSeiV=+GIZ;=yy5NC$QzBh(^&jQZ(FBbM{ z$U^evIuUAD5!Nj1L-P+mC;LvKog?EBi?GJi6xV_@Y2v8(&l&%dKlbNw+T17 zo=-p6Y8XITX@9sKa*W)TGD!OEYc2CZLSbk}8 z85>ikxE=8Fz&|u|&(I|+1(O9742!0R%emwTKc~&2qC~Q@+Yj21598Bn$}0hr-?DK#P*SVMT2O zf2R9{8X>2*c-aXZaW}>Momyg}%zN7d=-VW`ID*WY%MfeEx;RM)r@~tM?Zcs%+~o_M z*@x70u>F)W$qG(^$c>-4$OGN!+D4^FmcRKd%1&+r z-6W5InR$p8-#q?$kBjoBH83e`-oF)epDHd*&x**wa zcxYtsx-}*nx@y*WP27OrUcRvh5@qcVr~=h;B7Dt?2-X>V>KmqsOE?8DLSoI9Of+MS zOs6X!c)nBpg4=h`H88k=92dcROOMlg_n($rm0r;P2P4kUOCqbOq( zRqDabi+jP{qL@x@zvnSL19Y<(UkC!4?EWOENx_!;6oFoxP3bKnM0X6}e$ZeS8^7w> zFoes0SOZ7_d5iT~LOl2OSh&PveBP+=nOT~H-luvAf0CjS$oc;7Q28sFKo8OWOZslm z(ZeHrZ0jdr*aCIsLtxnB*-|cOGo>?nn98eQ??GlTZV)|T({={eq7MR@h@bHO196C1 zZ3Q!}w%_3J&xJ4ZHUps7e#jI>!M;8joy>*B{(0^`b^WCSMh8cB&~~ZQ-*Q&dr#*jC z7?I49mar`q?_u?+C|}I` z#jfCt9pdRC-vVnuhoXf51KP!XUb;cskjUxgos96uv47t*!iOIfC|HmVrCgK-9aWBn z*rAb6d{rhJ`P*6Nj(y4nuBFw*vp2N7>xj0c0)j8Qwg0@Ru_LI%B6VPDQK{ubqBP;x zR<{}e$87_^mB?>EybqcHYw0<|K?OB|oq4=+C!3s*8aigp=$LM7){kCcGM`G4+xc)g zS56E3zOeOBm?MQO{`Q=!%KDi}d#U~r^f7A~Z$=~QX$zo(01te(eh#{EH|dZ+^U%Xw z^GCL_@-c1&|H3QJvwBWS6rB&rl6f_LaTvet|BZ_I@x)-v6>ac>)%CH|m=Bd$Tt!Nm zD{_RH*|^89wY4FiZ5N1FlZ2HmE9_*MGQk4&SjgSPD~(Pyk%5o5pU+tlPsL^jhFAr- z#C|r&#Nk=}+PzEl`guX;DA<@n_d)yz=9uBpXtioUC!gZ&bw!Lfno*xF35a8Lr!|Y- zn}UBTL9(FM1!zVV^c@YjFWTUsgp&nPmPKj>dj9A~LaJP_nuKl1_B=$LzRsy~!%%PG zCP(TOt;bo>5QJ`pR>)}P7UDriCFXroVPnRoe2h7P+L{)nCnNAKGeS}d%ZHALECF}x zK7G6(gG(#pwF`I$MZU-t2%~f^Iggk~4*yX7a|(Q`ys#8dC3g0IS&$+4D&hJNtfxe>ZrW_J9A3@L=2Rrfsf`dZFB88Q&FPR# zB%5%3_F-51_(tNuFhPZlxKJvX>lk73L7yI=2HdclqFxer7Z358$f}uu&NRo71$#9) zu(1~~VeBwP(a<>Pvt)~sJZm^D@4V6krKo|HF7FVCM@AeJ-^oX{x80Bh8`DxAw_Yt= zH~JgjCrPlxx`QNg!w~Myn!S#^KZUy)Nxg1u9Ex}TK?YTchJ=u^y2ty*bM%6sZ3v!< z7f)|{m!MwgPQ47x=hd&`M9+a7^{hgWyT1f6qmVyRhvRx{NI>5b29%2DCbqc1t%?zrj_9ScP5mPfeECHteZek8!L^#%D_w_UO$u zq8pMb*_Ty6Vwl`|Iqg6jbGY7Ui4`tV!4_Pt^g!89bfLo#pP*@uuv`IF`?auypZ$A& zxymc}j^+9npW}G^2AsRP*(kt$1ud9R{}cB@jt=h{HX8mx$;wA}opi(P<45-PSv=0W zooUKRvcnbz44I;`#KQj0d9H(1(72}$JN{hsH~m zzQH;5V#RL5!2vEd|Hm30yX6C0nbikI@}5VG7QqKA6=T0FGERK0@kEl{++PkU4$?GyZb;2 zAqF9A>8m{>1GY5=Nr7DNj;!E6=+nosawO%Wb5FRYY1j)T80ii6w4$BdC$PmSK#-VY z0b)DM?hY%*htfBWb51BI=nubBJ3dsb4b(JCT;HwKYGg}$0#m+C=+d|lnTTD^ZEjPG z?a4{+F6hE-`l59m`$T;(Ym;BRwe=p5ydRwC!ct3YW8(0{*C+KIa!K-4F9r&cw$<^Bd;C%x;y|i zcRnM3pb+OclXEI5O`FwVMd07qlWEyWnb>J`4gm+tMg7Hoem5@CItZFB+wbRG_W^_G z0zx6z$y6;+B6dP5Za&hHJ7VUb(%%e7EkOL9O;%~}1CX?Q&X#-OXYTS%!j*4sNLoc= z;o7ZZ;&*pv8QyZOcgRX;y?+O6wtw)vKCsr9zo@?G02hj2q#^mG%6UEhdri;lPnoq3 zn}Lf>rsgrz8nzR>?n^%U9`N~1kW1QeTLJz-qdI-BdIN;3V~_mQlvbMKZF0f?I^34_ zlO=2GH+SjV5`tFs!4wJ8F5*BNUtvM_?=^^ntMa7xJuFq zD~W#X(qAC(SCaB+E!J(67lr=d0VIXc;FU;0#3;!rYEP}a1>-`UUZjDUEM@qwHjq{K zFCD_mxBGE7S0_XacM3{4q^O}pwqWbLt?^do_?nIVo=-dUb)}I++eLO%EeJ7ktjkph z9}OB|-GJKJ)B0qE*;omkH=el9)|{t68(A{E;B7&fSz1c286us&ep%pGwuRehNPoBV zV*+n6)q+)wbR?C=kC3$r6tD_LKmXwzVOG!Y0?d;7U|5JyhVWDt&7gAi;592D*aI& z+Yj{^#E8 z6BU|zAwTX}Vu{q(gGxC<7(XP(*@e-ydZ&STRp?9IV*7E7ww6CydOV>y=fU~DqI^#I zFBA3o@;tVn-34{B?~ZpF^Yl_%fvSeA4q_X~s$g({>Q9h_$HV`$bU z>(?0tjM5d1H(9I)Y8f?eZ3`j;#_i{-YXkT|$5d&iM6ue!_6i!HqZZrG}+NeLxGoCDhaOaFKpIX1?xUwv(X3sl7mnRkWcx0h<+jQMHWEm!(i9^Zmh zESfP$vksdT+wJ@;zyMvgr6IUi<{REYU#SJT!@N%3NycAoc5o)5w_y{)=-P67dq~?c-ijd%|MZNn zL)-9tm7T|eCgips=mpvXdzeMQstb2JK9=6Z8R!JXDK!K%!X-7{jG=Dp~Ub?u0?@Q&PhiN z%z$C-e?fVCbxs;}&w`x%DNiE~V3L%G1?q-Hm2p`>zwk-Zr9gc>UWS=Oxpzz}OTt2a zB_J0u9-C*?e|&hVEp?D89kDi~d`9YFZBvO@dY;ws3D{V0+Jtv5s*8;SL66TIQAGz`V1GKAIY zpa-_oKFbCFmu6+!)@~nv$$r)qz7KeX^V=)y7Z)6zZ;>KzTvFEjg{5=Z=FoZnufni zd`JJ5pA|3GaK4;9WGSQ|w7JIB_LZ_GR4iP~q7dq{*KXv(#)zrrePW@>J|H})W`=Pu zM5Fp|4_QMucsKWEEnUJI%e37{HHJ}ffnSk7{yD_}^z;?B68SBC9XKc>coC<^i%{IK z#ZuwCI4r%3nGJgeZn=E_sQZ4C#F2Yn!yvSnV)ta|0VM*c!mv7)=lJ+z8}OH=_{IdE zog${d!M3uUr$?%4SL#r7q_KjKA2UU$ze>ViB8k8Pdt+w1qUUHp#Z+))lr_+80k8-r zVK%@0$n^euN_I@q>BAnUmroV$<(I})?LPeApe}*%IO$-}n5aOUOwsx$viqCyn60yx zY~o{8tAQBn_9^h(5=*NJqQunHbtEyBH1M#t7o>x(KD}yks1;O^Lr!~gnA|EqY}S-X z2JS#6+aNz{k=W@yQ|g|2VG46M&iQ#u>Iq*>xMw|Xr`!& zUWhv|lHUfKfQ;ZK$Y#XOTiSyKh2MeKf;`TKry_EdVURiTd#UbK));)7-RN4<*_bgz zg>5uUF#$RdJ4k9&Vs0kZIpk@^RA9o%jyX|>mn*>0_aMZlxM8k}?r&^I67cWg_O!S# z`JF?Uuaho)cR9D?17wfFTI3ijXpW84O0c9N z>7bgyY{R4cwFM%^<|YgLf7ZeK!MwbK2Tt}pll>w=0mtRIX?t1fT2KuU6Y^(&4$V%& zCR?*|z|FV=2YHt-XOH+%PEuv#2IZBA@{$gr{PH3 zJ$41&C8KYzw2)m0tu$d>Neb|VxV>7!BULM7 z6U}ASHD~7JvOCvzyBw`|FJ*!{zQR3WjsV!UnQosSC)ID}^P0EL@?K3pV{jC|RP*^D zQRdrP%Y-%e>PdZTX}5bjQG#`X)}8RN4!MO`wSxg?U2tsgUm#tnLZAv#*XFnd_%Z0e zN}0D`(v4#oIC4(4lZOL5DxHJqq-hTeSViU&OB|F0I5WN>P_eb)IitwHjktY=KUf8| zOUy40A)bt1g8t>36&yXbUgitZP{T=~<1Pg%wylS&s01}%OnEp$)|6$Eaga-Y*F;5{ zS=~Y!TDF^4v_);Ny8sgs()l3?kB{Vgsxy7!#Dqa(bAOloEP4?kBL|Q)=(f_|3U;RT zZ$qonQXZZIku~KceiC>{dsJXa2xC1R3m81~u0y^rzrDWU*(uQPrh^XX?VnEZPDOuQxbSm}$g^=uT z8v@PkG!0{1i!CxGr|yLZ#9^naarU9FjaUv#h^0BG!~KC37-w^O z%I+0Rms9noSJit$MvUKy`zFgJHNh_p#L|Cl%-!l=8FpZ4Lt!MJ6M>dQnOfD71$9qa zfXj$9AlmIIepm?uD+JyV@?f0He!5~ov=hqiD?^Bcpex75=oQSLiQz|$6g9)0axa^0 zz4&`&zRe$aw84>M^`~!jd6i7jfN=t%=^$eD;NZYYyeL=e83`>qJHL&-_C2$!K8H4) zR%4QYHM=TiI_gbi)7JdX5Cvtjs(ZnM_EooC>k+`d60jiR*|o{lxG1=rVWvoC$Zkl1 z+4DJF8u-MpKtgtjpR0JVA6428@2x(fvQr>xe4h>*z3tu?AUbkc+!)O?#5HW3gg#3d zkj7f8QWFY1ZGgKh6N@M&Xm=?cB!?Am8mzxpI0Vy)$tW%hc1eoD?NE0)|43X^`W#PY z##d~3`E;^;Y(Z}*560vw{C>_xGq<(!i0&zWT>+R0v1@4K8G;L%V3r0{RkP_mOm)yJ)CC9*q{ zjQeO-(dYKYk6H@PUpH75q${Zd9D{^~D7MQI#Yrav{daxoC=KgsSda2kH@WVLUvl)u z!4A4^*ob#5Df!0Qw6v$NYa%BmU5j5xrN5^0NCG4$#M%IH+(+V~^t`x*O<|Gxgu*q~ zizfv!9;mAr*X+B(K-sRy>Rut`dZ+Tn)#2TicXM+k)73u$A>Exfoo5sb9R4s0RW@Mw z;Zr5&w`U|=+u_m#D zXHULs-Sy&9Ma{i24}a}S15uS>3Eq}35Gaga)S}4t@sMH^Scd2s`9;a`C z@QpNR4M!5T%e5)7;g`+@Gw1hL*$n=H1V9%7AL+QZiVQ@}(RV0Kt8a-hctE~t&CeP* za0h)WLHY72Ur`cm&U_|_?qu>9Qs!ktO^-SczrJ;FGtZX~#!PR7RY3E8Rm&)v?%PBv z@wSzzT-nz6CungKWWTI?;psPx86Nzbs)Goo)~t6T8ksm z(R_t#r}$S{;=0G`ovOy>#nk#+Q&;jn9v~r<{DWa#1S&~zQTQ|F>_u+9zAPULm zyS}ZR`jLREx516X?YG`Vug8C@-!3WdzpMk~MOvN4nXLL{W4N3QZ3$akOdeLMS-T4} z0rfQ?0TjNO*FZ8VdXP6-5dCr{QYl3N$IvAf?2$^4!xWzGJ@%xxiTLXM1omH?%}4-9 z$6ZcMTE--Y9U2umzS0HoIiHHu{0e5Db4+7K*-)KUcDWo@-Zpykzu=$a3a8ePJ_JJ0 z_WrDU1`D098YHkG;7~)SYDVr-`2lPK``mEP(g*>nM0gpvQn%9 zo*he9gWy?3j+-3~W)L7RtXfdT{%`deFDTOfl#c?KZ(KoBqcIDU^Ye!W(_UR-4vCrAi)-k`rX3 zV5Xo|96->U+o-vtfuGxN^JD@ubby-anJ@^lE4brp5NQzggYZf$P$560T|<2RAuGf? zHpIZ0Tz!;H8M+I>KFN)2dB&k6R2+({W6evWt4-OG|B-7CYDO?w)&%UHoc?vCEfSyf zsj0jA(Od|-%MA6Y*q{Or81Gg(9#eCV*9VUGrxY$97{MT^R)j_J%)@g1?H!!`a$uG0 zI%Xh(&jNn%tYQZMo0T79zbWaw4)hVT;P+qN8NO^phvFc#AOkmmnrb67naC7pjtcDD z^X{m+dg)l+=ao5O5{h6qd&oq%X56iiE=&Om6_z>;hSy)975dLiYbiZHQh0ZpuPPd` zuybQ@1+@wqthJ!v2X4(*z#&AF^5N|N1phDgss$|XfhGrcq$1uT)R_P-DD2EVqh)&b zsvsW(f-XJ@pG+LC)XRHlH}iB?0Rl$}!xgWvSlfzKYLRxL`zqk78J5;-0n%d>s;8)? zfGTSYw$;3RJJ)hu2`~!hzEU8ohWiUu!X(!+gmJzJ-B1GV2skwKsVr6HIG}-V3&D1^ z;OV?2U|Kzb2;4?>F}~MV+w^+B8J$tqK?;T2iTnlk`^rQ*?TN?q5E=G?1rA~8J z2bzurlbW3YY{KcCpG#)eI!@>BWtJuYMga=kJ&Z(8vMv8sM^m)qp_LKaOPgD_4x6$q zr@aK{EFW2vKfPio8FL?UlzAMf$B*v~OtYQh)RQV3!{S!gv+(d9e+V3IF!pNsR*>(> zhIn0j4UPYnTN{cmeJ!*Be69(A8c#xF?mdk6h_^-;5SyU92!qN%gn!?FkrycjgwC44 zOn_D$qxUYrWLyeyKj`dM?XvbFM(3kxi50QI4%m!yie85*ZM;d=2rU`f@x8UDYKa&g z8-yomZ>E$Rd_|y2aGz`d0M;K1&wJ*^(E32fp|&>V+)A-mAoGyjV9oA5OTZbK=iOl4 zaLDXGjR=2#Cff&2c0YWWX}2dfe!+nqsM&BQvqph95xSm7)!URq-#54d?{c>+EQbHi z1IZ34AOL4e128ZSfNtX7uI|N}l-m2D>l)PN6yXf%4e?(oCYKWAdZ@&@W8Cv~Yx@1U6<(p=1HLTYji3t4n29HAy?WJPC*Rd!~QK!T_ADtIL}BH=%x>ynwt zfF5>VwqS>p|0qFC;q+3*pC(@S&@w98u7Cn6Yys1-zZ5#0)yj7gIZQWj9psF;t*}GP z!JMfYC*T>Y7iVWuTYwh`?=7Hi*ZF^sQ5G9n*2tgevQe1C8}>gYUdd}+t1)A?tyn)c z>@xG4nw76}YG8{$57s__$)T&vg2-_RhYh|JZs|I-k?u^pd$j=Ln{}GnB7VR6{}`>m zRtec`jNvl>vBahQASrv6$`oE$Hp#C`zarHOY%zk0D6z@9<2+^3>a>37_X6*wYFDPQNL&&rVrH?l;1Hd%G;d~)N6$820|x) zJVCEuSl=K73D#an&1)2@S64~~CYa_2j!HA3{*Gx?*;}F2N?auO zgI^#9E$BRQ=A2h|Y+zHXHgO$nzd^xy1aY?$Jl9bkxJ1``mKjm0)_Iso!%Vob{C@>T6uYYAdyF`Yo6`puM(^Iavo=3fE4oqfQTwkrlMUnvNRt_UCwa6pOn+ z?8Y8@XvqF(Idi4OOpd{&eH%s1XiTeBXM?slARhxAN&V5I53&b~1r?@jmmo$g%(cBj zGmpFE*Q6%HR-4(tSnC7+=nFalGfk4;0U~Dj$jENXS&Zgf*#bdvinIVu(ysrM!|g-?pg?#qFtrz1 zMn0{`o_VS)yRiS7M#l!33D$rWtCU{5tHt6{|v6(u-+&FOihxE<}W6?K+kExcCisAx?^Ed&raTFFZlw zzxi3}ZvwY5ZvuGM2ca?kNXM^@g(1#SMV4mg@UJ7T7i#|r`XBd=`nAL{A zN7?5EpXa;Uen2jmJ^YMV?{%8SZsUpQCtc~zS_fUYH&V4ip-s`;p9K61wYTu_b>^P4 zUcLneVJo?_i!Fw>DFY6*`@G09l;b>Tc(K%|m>2i&YUZ*IVdf9xTUq+XWNvS$EPOpq zG`PRk@3h~z;AE=SOWq*6pq-IG<@t8l^r`SmOFGHiA1vk2q15$wV}JI?fjMEBuQIcm zE#ij-ZDBTDqF3+HJ2N!jE((-TP{w9DDlW5zL}w@=cnC;3n`wV%yanYR|u*mP-X$D)j`7f>UqCvK0u+L(P8FxQ#!tx*kKZta2 zwce2mR{oEhY1kC_oZ!gNr6v3t?@b$`rV<$ytzq}$@}F`>kmz`yN!+6Z;hAwc(<&Y8 z4IetV&(5mqoHDE-gQF8O0(%n7G@q!g>D7WuflF@r@y*CynG~!+~2BYd%SdKsj2WpsN zY)bm>Iw;nxWnBNd_Egu!u@Uit#{7Z0S=ADqpFacyI1u5*9ni<5w-b8DRiWXKETlY| z*;nOzhNW~}Xqlr_r9$QIzeQL7?t^!vH>j;8%6diHgJQCf6DKD-{GzpsAK=8*zNi=G zRsPN#AglxhKnE|j!2Y&UU8Dj9-gqn7B+_C`1B{LjyHxs4*hnihG4lk@E35pooOfDG~Vra z$w)Z!=D37Uw{MC|$;gwe*GKgDgd>&QOl6h0xjz1tBxQK-tAr`@-_qjUt`ck#!PAIO z7wk$6$~tT7$_YQh8N13DKf<{ZmZ2qwQks|3PZRl9g+bc%B^aCQ&#*VZ?&8bNB(;nn z3DHZ})}#^%ELnE(+eLAc?`|_%>j#y61hf|+A`6EOr40NVKB@wjDw6Elwq-i*caT}7 zFg#+Z(u_8Y7ig9kos*Nf?M!W!G+*x2MsF1Cn&2VSr@0;Be!zz5d<#4DdN^%a%O<#EvgIQ`EQB zgG7bklSoh(;$YQLX9F=1m350Q{_pIidDKCIZ^cd^&!%D46eqYpb^B?wH&1Eo*I2)Y_$&yAXjYa+%nTccc*u(5@#eKeyC!4$K#0KIs=7y>On#7e^hwQhcz79RZUm3 zl?n8uVBDsk*t-Z@#V(yKo;rEKYCUoA6+R~BWTvcYyCX6}UdG8&Gl@G&NiKUDhe=~k zhvP642tKsY4A?_d&esBo2v-1s?cT+XO>ABY8qW57ltO?q=tL~!* z9|Wo_Y$8HokhVilETT|UMhJ5CCq8!J7^9=C z<5VRZZ5v~)Im#E&z8ox%{NkusvM3x3{zGSaEhx+J{4q`ey1-l__Ajb*|L?j#8N?IzLIuj#Lk77;bpPNI-qd{C z+i<0=aUqkO^ZhNdDpyLO0Tq#N|N4s0%H!Hfyvy5f8%-p)7o*1W zlNXLyV)rz)mjS_2-a8VY^yn6|MfWKskuyNKey^u4M5qUyG;=i0MTmkj`pJ#YrK&oJ z*@)A>Go@Y_S?YY;Xm%~TfD|jvG4}#r@FV^QDp6JKxpdc@vjLsaIW>M1`{nChdBztV(~N#ncaa66EGNG-9oM^vIO zfj#wTV>7$D-MJi;id2PC*f-P|p#rsbd0-yigx7$?kYX9au*b|`x-M0;>6%y9W1!ti zy1N_YCvW32qJilNUR8_u;_U+bc)b8NfXsPN#6 zrPo#VU&*F}rMy!y$y)ejwmL_tflU1J8O0iwB;FP`2nQYG85|g61)A*4$Kwh>eXcQ8 z-eMAeD>YAM$3MXHs~H!XWKNh_$YqWt(Dkb&6h_M{bPxyNhCD6ay%CitC}iK$aX)%DGXVb%!j6~H)(;61KDXssk) zzS#vx-_}CHi&shu)(xHNF%;mh6ZoAo>U(XTYf2V3S%p;8baAV(%?P)rLHX0t$3N4tci|vCY?y;JTvc(p62*Hbk*^dc$n!doa3Q zd)5RHu&m&bbt>^=a_YjzUIg6TFi%x01G>W+#^Vx$ApZh5En~06Q^9?bfMOAnqk^sL z(!G6VsNn1mO*ZZ-)}^*z-|q{OmQP@=zIj8i#a=0Tx(PojH zc-oK-R_#N9WmkklHKyY{9bC-RR8Zu}>_9V(Ogg1>=*Es)_lJT>>H@o*l?@}(b*{10 z#TzH9$R3QajBA>nWipv)h$qg+XB!{tnsmJahxG5zX?FNc>SIfd>;Y`PO%QazMh9D} zx9A_$DenxhMYzXwVe%J7r~5kyG1H!?ihL8Xb%`FX)RSTOBSkpbo3~Ys8>8=I^u(o& zsnmG4n1RF1s3&3XL7HEZ1Yxv?5;?n<`tlNq4fE)r(trPtoBufPLU$SLs@F;)u&PAq zmoBA$TZJ%%#t?T~SOfR*czRkp80mxtd)3Zmp2|=%=Wa05ByADBmJJ>sfFcKMHZr?* z3Risk)2&w(bC|fD;{BuFJ-S49&$5wz&7-bq``|Onz&X%ZxT_qcjZfVWesr@uAk$*> zPPN@0$vArstqOmnj@^ZTu7R+4kygC=7AM?oOLq+Ktk80^eP+td9|H&nXrv~4RxMvs zTmp6>xBKZc!^NAO%I;Y@;T}%Ja;sv7lqRCfa1iP8kIo{?%6S~SH0SPA?^u8*-QXn5 zq|P=5<{uRgfqNTKWw#ux;J>l3c?3Mg2mJI-1sH( zkOJ4Pr@EzdDkF55M71a&im(5Y%i(Hq}#G@!HY-HsA4cCjiVKsKK{vT5y18YJ@waLG$D95Y*+|}U) zoEo%PYxU7k0fE6;$}W&(o~izvK;@myfG&-F2c!p8ygO^PL?tYe{eW-=pp?8B1jHf4 zLM$eZ29zYqThixVwLjF=3qYe|j35Zx4)%gXPsoUsA?qqjAJqKh`3xzPAN&@DXg3Mm zz=nnTF5#zcqhriOt7e~PZPzACfvBc79l$EZyq=`RaPJ%cZ2pJYpmM^qHX0k)VIkTB z?I!Oy3@xgB64Be%N8gT$Hmw+Gy4e3Mla}AM#LmNx9c}tDX=_3ZyeMcjDk9CuU}okdVOp3$3=KSb*h#B+Jc2dDqGmkbr3Is|Kc?-7gavh$MAoS;V){= zR-P>IP~{TV9Id~BsV)d3OJMA{!Z#+INt)(F!Lc`v{OYBI_aNj`0tB%?%g9ze(Q+Fo zDme=h%oXhpZ$!sLpx!njsm;23O_5X@BeR#MrnFc|N8Hrf;C_ZE16+ukz!1GcD8_a} z%=1dC1PJWGiIVO3tb_21Rn03bjo0fij-M}MI5+sB1o(byE|6HhiLpWUP>*A@Evrxw z*}|t#Q|x~81}kWebx|Zu@)5-8SSF_l=h~j`2G{qt1b<)wI9#o`*%6NYD;G#&}&x{CCd%ri_f^n8`Y`r|!-k=2>VuOXyez`SJ#&wNpU52Dk1aoq^7uKivK+zsG#S_i zP5=M~wE+v@-uaKNzyJVQFaQ0OWceXb77E3+gtefbmtb8Q4qj7wKE!Md(fHhEyeO*qbqOcYpg=*S>Q`(P^Jk5vCb-v`-7BVpRc|+UFLXBxp~Ni zerSMmD!YrZ{lix_3`IZ@rdld)QXl{fyHGPk@^TphGCxI8pM9;57y0`6>(UBLAO&z{ ziI6^&TVLM-(pvJx61Se6b(1gCh!DzkE+Ej0$!stYIlLM=c+zR0ED3$m1 z*DUtoR|*{`7xbt++*Z$S0A}HvVyUkR)%`NB^U(k_j;V*=*cQ+2`~Gl(0d43WcS>Z& zn=!Z4Y8k{C&g|cRe?E8W<-&v4woh*?N5#L^d9?Ho7AQ%`R=skpY!SdLEB%Vu@dAM!fy? zDR|=YxJHY(eBcgyL8EnlLf?k-2=`Fw!o}lAh9m%Qlijiz`lSZ_lt92F_-w&4#ey5hK!(^>ilJf{-mweIiF?kUKCp!<*t}R3n+mAPAn+e{lJc4$&#wmEcIJ0L_?55^oTTp;H*g@^mCsG;#GoSzfEw|Ji`nMR* zImE^JAf^wgG0K^LVjxHWHx9(FV?11h%o3hgyKbCMpP7eQR5nvqg3%mB&SkH1_RZfT=ff>DBMEZ|P&1a*vGL?xQ*GXo{v@MHog3G4 zn2^s&qG>tEK0{VPF0nCd_TU10p>#|#By*pXR&6nG&(ipFvRX9-8s|KFatt?NqYmI^ zk!v+sF9R8M6>nipKbcw3D>)b0W6=iuY1nVZA`Tl0W^uq2ZgF9oSoG6QA=38tMf}li z#RA+KJ%t`db~qDnOCjFikIb?X?5KB#cYL>u=oNf<8?XwYuqPc%o?(%s;WTXB^X<`M z_WVd*C!e18C&4fM_1C=H4gf_!y1&AW+I5!tQZCNes|l~{r(yeRag5ed@TKA#@~8Hm zYQ2PFxgORrd0`b#1;TO$q2BN-WcMQ-IoO~0Q&o>`<-qjOa)$0D zbTp4-NP1yz`yp6mkI%Xxr3bU@U&f6$mkgk$5tOSY|w&BpgFxym?+Hx9NrplmBDP%1-QNjP4KF$vraVnr(8p;?eYcF~#T z6B(+Tzxy)=ZzB78oNadyGt6JDTCiZB(;AHF`Z}Jdt7z^~e zcpU}k-Q>qkGd!6!LwfeX3_bvld`{}Lfkx{@6~Mb`AIaPlu3DLMQJJYi?~PJUm0ajD zR%Wr%t9j(nP(O(}{6J_n=IQlx;{wp}cS>2yW+IKN>833VWP{=LWOB@LaYF6_hML^~ zkidX{qTYiUtgeZo03cJJ&*{!_jJ4>kG^W_mc+UU}8lft~9l?qY$0zGm0JcLgg~g6AeTMa zleV(Pq0@rb3SDLkJ=C|Q&8j{A#wULKU4U!c4Y^B<5BuGP{%224d(DiE0kqRH$$CCx zywv0yMu+fS%7?k>S|rW#j07=29npd$15L;yckH#>dV~y#&DoF~vM|yNVYk8raK10B71O&hVDwHlHXH%8X$$rMnV) zkgHegdpfar9H$rAivGIJ&Z?{}w;R^;(bUh1ri6^Jua*}*$2I2Q?A;Ic+#VbKz=VDL zbv}~Ffk+R(xjDtAR@KS1S{w(OzExO-Q_BApL1m}0w9FY2+WhXf!8ry9>9g;m<K|vl7z-gn$ait2lbsd zlM(AM1V?zy`QS~f(GyG(8z2CZgY8o1Smtec>cApc!>xc4xgAzI8QdyjN&_rG?s@*R zEFwq*tJV4x+D^4MkO3}QS6gid5VD&YyeBoPvjMPVSzI1?ciT2*YYAVhu+GeY=yz#^iwA@`Y+v!I z-=t+Zu`dLRYeX`h%Ust7(7rqA4=)Ge2>@=xfs4>CTw9%phJhnxN;ZEU)x(c)U(cN^ zgVZ{-YH4Fs<+_46idI+1Dwgx><-iXC+G;E4{XpV#SOZw&QxpKV_aoIKlP^K3?*Rt~ zkpWgu^Z?gFC|E=`Tmf>il(`pV6yNVpUSVUT0e2YONwk_!&g2WO`HCg-lBBo7(Ew)| zZ~3SL z?b&%EKH^6!5W#4)g_H$yeEo2dJndk&Z%zDIP6SVA53v}?`ylqK2>(dRQhe}LsUzir zyQ)CyBfc+pzHc(G?!KfX;Chwcry&7wPf{{ysKn3!X>sW-_$-wp1k1%1hY$l_8gN*L3@ZLb=(3TJ(57 zCM4HXPqUmlD7X&zy|`LP!b9A%1ZgfPbCvFPo3w`$I%q(sc;OS++5OtAV2Oe_ktm2!Dj;{LX>g@?-#9^wL zK&xv-B)@ZAwhKo=zne-o7~xlk)Se@Dt4R=AL?YUd#z>yXP^QwN%r0-bK(KG2F3g^@ zxxg~Syjw5@ivP~R4+vI`>F@vs%`CFHEw@dor8ivx%r*1@>L-b%tkI5q017rpvq3fF zm&ijs3px-J>??+kJ+@Lr>w1ED`hTyVnRKS6Fn=4m5cMy26rYu9-zApvbk~9))~q~% zyg?wb<>-mS-gd@qRZ1pArVbFf>ssc+*@KSlL#c{P-iHz6`>PGYzvLg zSVwTSJbab{OLCW2O-|FSKy_IRN}{rX$$)M-Ypyu!d4cL5Rbn#w**#z8+C+IAwA!&~ z#{gBlT-gOt{0qLyplXpNxt=;%J(xkVE~Yzqx4>QZ#uEGJ-0++Vsh=seQ9AC%G2H9G z5KH@6>M9*c*v)9QH>!&G$Mb)(zwe#2QcbTwxwZa{6#mHbyo^?Yb9?a3)Y!t)%ggHa z?yRlS%a)}_TU9{;v!ybU-Pa z8LQ-GSCJ;ANIf78^xkB|ecEvUyg%y?!o}o+qLNTtxCi{oQC*-)Hg-P@Ryh#r_GUl%Wpd*!`sGg_gvIWmC@yR4 z9B-U$4cH1FX2s)S!`8kzKb#l00mleaiR@#_+*}lO*|cbfuKg~xQaUzmQTXx|{bM40 z8~LSDAf_AuN?!{t1P`t{^$If5D?_^znM0PL^jh3C4lYfeVJwqrNYhFjtw}Vf05wsrD7SnP~_jeNPZ#-!yd?} zc*H|d5os)P(vea97*gK7214|JO8doF_239{Y$_QdqeiIt2FmVU`LWdmf&h<9!!S_AC8eEVW!-WPaKzu6!R1a zV$cKUV1Sy9{lkbQP@zk!9+4*esDuEp9y71j%L@GvThSWljw zUKvVVpf#QAd;i)&RkJw8q5A7a$|5D;?QB zH}uj19uBgbh&(<|So=y~*0c(|_D+7-v{(=Yf|vp+F7P$<%oc(C$=4-}n4;OiJYCei z6tsiXES>V`gwlPG3m?Tpq!Fjm01ahGoorp!@TMS!N6QXQ3bn3)H9W$x9!g3Z%q695 ze0M}e5un3HK*UsL0RM7e@@`Z^zLV4VIu=UYERaNPGWos{u)&4|I6G z4k;GR%tdvoj#r`6 z=&XlIY=*u|&tnY1;f`hw4febDUmg4`Op%9++AcMR76hEUV~v6QMHm}y(nwB3c!|i& zy1}gG3@h&!jm2BZ&mSLxG(R9?0+P!rs&;LH0*3KLfL1K+(q=!P`KmfnmMz2wY~m=8 z%Gab_<`*cq1>R$yjhY3>SWm}lZy_mm76gNYwX_y>!yw7I*iRIr1Dq_7C4tXI@-@3D7RVwjIXAXKc6Qi0tSIP__;qGJPE#6&H3na?c^79BU8S`X%aT zU?f5X_|4(XS31nm7Zb{{lGTP)Hm*kwu7^`tIprrns8d#)B+1cF&rNe5=0B|{6S72M zXmU9=0AYkf`HRV#U4dr|qIGenUphbl6fb@k=lh`y5WE0QgzIzw1KfigaoPX`gn$6v z5*E>Bv8o`j00@ZG*Yg3EL1Y-9lh^<<6=VP;gs94ZO@PD|YO@9xlNIj+V+Mtkx$alw z8W9{~O#BV>!~4v(SSFlDNpDs^>^J@B$+HT2xWuh&r*bUe9g@0g&WaUfMLA=@0njJ_ z02d1|X`UR7)-%Yd=vn8pdE_#X0x4|^1P6&z;b}kuz zW3-c{6$}n+M10hiro~GBQ7qQXevhOVhtNhg$|C%YYNIAy{iP**l0ADW&*l~AetfiaGZ_+&usoSVY_2>uRF)h z57;t3q3vzVhz8;>fEOPM;tJxly6?d*WQ zTr0@?KI0|Ih2${h#?Zv2QE4kow)y=$+(RCrCm028k}K3<)pa)`WXuAY!<>&}&REla zn-`_>S4#O?A5GO?Ssat_+=KIACsB9@pi;od?QcWvS2lo1UdBNclrla%jjbP&Rj0s; zCW07y80UpnbTG-=C9Lq51{#-*OPS;d)6(wpl0LEw^1tPSLw3>Ja`e-B9etZEH4r#5 zfB2qht?N58{|0Njp4J{7uf_!J7<}+cb6-m(?W{GkBogz~4&@arovcRPI7?IdSVhv* z`-qm#dl!WRtrQ)KJ#+A`cmlgkl&1b~LA#@wj1!M#e{5_73Yk|E@OfnV{{L8_8qtUZ zohp&^bjrWy!zwNfx_`$Z9k=)8&|j>IEwye(gh|I6&C5C7<( z=WenU1%Q$SlZ~5kfombE6$HRkU+S8eL>~;{Jz&_ zMOS+*vVPkc8Dq?d{bNFtS;(ev2yh}z^`O{;37{T(Wk6U!33zFRZTBA`W>%5(ORrz20@NKm>hS1qB_tdt@~q)5z;>mmJMkm# zqP>os8TSAjd)DxMpl8Z=oW{k*ukodChpjE9>s*)-%5Ij0#wrnK;}Q{~>Jt9iN9KZ^ zIYs_l@2Ze92}D})d!!{RtZ4Oj3w8QMgey%e+P;tHrYEim%Brju*nW}M?hhAlV+N)f z7IJMiSu9o5QFGl2#cE!m_|)%aLJK`(G6Jto!@7leXl9f_J@)FkWC0|DAGxDcxZrG ziyc;F&_c|~82Gsm$1@pJo3(E#mA7fAT+bx3*amcncP;MGBoM>?x6$l;;pgHF9{$7NR?zbEw@Y$hwUd8iqNUw7j_6Nj zqvAVoRgLO3e7QU0eZ{t>uKtb8*CM%qCg0;ir%@u4(sXeF&S%citO!4ZY$h*YORpxu(jtkc(Pw!yzFRdRE}dze(XVjS_l| zTO<^Qv>s_~3gN`HcUhSq7W8zR!<#C1jnQ~pkt`I)|;heu!( zIG-b2XQ$}%WrD~|6+Mi@`>z39!(*YFdvt>;TP)JTb6x37{qUzl?Wzy4;m0B*je2$T zF-^6nBA_9vG_akYx665g#HiBIAnvVDfM=BiL$79yzirRxKG+W|d*~03-ccd`s#hPS zxYkb3id#Bcqktb`1R^w3vb%_l9AEGO)Fl#n8J2ywVH;Fqf)o=Jy7AbB2)!VNo%bEE z?jDZ_#V+i3Xc;r3K2JF)&^8(e4uD8a0BR4>+ycwH=8OOU2v`6JaDWIx46Wk;9(DBz zIi2|C=Hdn_1ES3--B8Fn+8r!X?LvTkAhdwb!YLAfDRO9Y01bP{?u`RAfB*s?&VcSF zpIe~0Q9@ro|B?4BFTtKg1Jq1F8IzHP+&u73m?+1}WIw_}3-fCF`u~Ez(}(~%X0C;S z1|Y>dYybf;5MPn&Jz_Iup$h`g#af3qE%}Rdkot_uNcmKl-)Z(y%o}oOQaIVhc<&J& z=$@C6a{vCU;(gp20|)>CG7#hIyO3E>0IPwIEtl`L4%HnqioHu)N$7MTtgQnrJ#)5G z09llk004nuAO~SV0^D~10ci?iZP0qy*8o+Ef9T)N*ZJ}x)#Qvn{Qv*}%mJT|OmvR_ z_Gksh5_D{ATw>5b|7C1g8Bbrn&M%-_l4+*4;h;s)(57cz8r<;j47wf$A7YGZ2mN4W z__|1}*EK_ve~3bI)toH}BQ>Mq6=(}?H!DovBoxG9f0{_rpD3NVpD+FdgBiRj5NYQ> zT%7RMjBbJ1lAkS!LAU_&HNI&#`76Uc=!{|hSbB|6I!FhQg2gW6Y;B^jz16!{oco{h zU+3NWXa&k`sy*RgL9)J$msW#-1n zUIQCDHCBH~Xw}%V%2Cp4p6Y8op9Fslc^CW+m^q~9i-UD{GLNTLH!>)it-lYAx7bso z@XqBwRPnBaOS^!I#%TcNC|@$JuA}YZmuMXV!hZQ9s7J0`Wz$o@5-14+B}e@alQ?s` z9rq78jfmZEuchTlktdK@oZ^P0Q|_DaWsU6oZ55~`U2~H)Ugx@9_D#7_&P-~*8*uR8 zE8}_d-%X?njgH~vd2E`*)E{7DzB%^GF1f@)(*S3$)Ki9NNfAXwazuR*g7Uhr4W(lS z7)5eA203IBhh#U`VTikpQ#=lMA8sY2E=C(rs6J*f zQ3Rh8qCYC;F4L{dl^QL#R0_Nm&ai?jSv~H9O7P-`{N)%*`l0`GfuSQL?Didj+=RM@ z2MLZ1`f{Moq^6I)BMguhTMJW@! zOX*k_&{XxPclu|68pxEcZ>Kjb4cQA;kUlC4;-BsnnF+T+7M{c$`JVO$QUq#~!Jsz` z_1r)u`3Btq9st$LbSMA|jluLd-YuHyVQ0p9I~DfV&{QyE^4LEd8e@@R((`Yz1Qn6M z?xo#CBc2)mKtLU69~N`k|GAG-5GuPh2)kCckRh>gNI7!9Qc*3mloJy;m&A{*37WY; zeFakUj9Y^t@-8Yhsm;oR*4^fkl59i@oz*02Qx0Yaf2!x$F!)v~Aq+*7uHU|G+5sCW zc1$E4l&KI>Yil?E9YZ5op-a&@8I$EM#^#)EzV4lFoj%DM=V?jFhnWe)SxS*x3 zq4xH4fUvql9aUY(8Mr`z1Elq9&KUgdJf+S@Jk7Nn@Ct#0gULdd8m(u~rflW|(GTcp zrJS@jw>nBYHPCtfh}Lt+e$F}KF9BhNkolLqm=H(KtU76mpFPc1m9RHJ?d+6kmG8>v zvCsp+AKyxD;vG{`2KRL~F|+^_9xiN~!&h|U0Y!diyqEwbIB2ddois1Yj5E9^H3~>) zA*Geh0_uM!p!u{#m@rALQoIeiKbyKH(O+fmu-1rrC3@6WJFGSh6(l~LX@QK4-AEXJ zcG;FEwrHzOvGN+`4D7>;58zD|dss14@&KO{#J_`shzpS}0J6c&y2ER-@^gE>Py}Y= zd!qSuC!NW1p_NI6D{*)Ln!o|s1Xtt@Fnna1t%B`YQ`rmyN2s!7C_Lp>XH1 z-MzZZ`C*qP6SY$sy*eLhqyn|{m%$*b*W2hO>iWuu6uoo?!jN(_3_x=Y>)jOv>=l(4 zXN-$u;-MA|D?YRNPS{ojH*Zn?%G}Wx0zOd zDwn;F38Czgt({x%H}K8KVkB9-3f+L~BrlG_fPMulRKcE~DI^`r&@8$2=f;ctPS*Uv z;}XBMpsNIQFJ_niNIwLs-wRGdl_Ex|UC{wKAf84S+g~JSc~}a+n?&KrA_J@o?6Ai3 zNd3Etl&D0!{VD*W+rj@tWmvC?&1xR8ssP>t z&GoXE5`OANF9ZDL0nDUZJqsf|#Dl;|#L`=9fi?#a~LU-6f4SAu#{&vRSL~ZVyOX`YNV(a3p(EUhg1 zN}eMv`{S!hs)Pr)1cDO7L{BWIc_Xx-qZdSfq9^4@=tGVelkn>!5V&G1Kx*%?o;L-P zsJ!_`KJuz-F8alR`vQJdG|$wrM7SI?pWZ>RiF~bq2?Lagf}`7aUd`6uEoKyBo|M2O zTz|VH=s?UEb17A!s_Qmdxn#m1Dz+3?8|J|g9u{^w-SgS6BmppiD{fH#{NPC z^J`xfqjOhgrFF6~c+8!SFe7I&PiXhCa6mL0PLPGGLgp^opb}W+U*Yy4-$8oBc|B1w zL+K9&y|3>&@cY7w(BD5Vfteg+R?gort4(EZ2>F1&4BSq(a<|!il`#jc(atRH7-$UXmj0k-1JZ(V| zEN6t!xY>C1OsI^d4|#J*JT@S*3YLxsXYrBJ#ePxNW^-Y*jNBi%BoytL-sxquM+6YC zriUHEp~!0Sc>#6!Dx359%ZqiA1l*HD`?UBaPLrRp@)O_8C=;yFkJNuAOe|fyb%~$5 z79#=bRH^6o>0t{c{EeHOPaKzWOk%R7SsUc*>5kz}8}Pg)eyn z&Vh3y8+ZLpc}zgd1tREeqWi2+09@p$Wj|}Aw6n@k8pata&w(rc3N!#sw&})KMz#v_ zm$h-Mgvl&fG=)j;M$iH4)PIBK9Dzd6{ z{S4@4nH^X$9-jpUH;WRrEGMX^Ma@ z;Lb#ol)vz3}ch3LhQB=NB=`Y(d2Q1}KALlH9X)%VM{kd5Dn+$Je;5{@s z+y=7c2&Do5tMssEnF$XTD{=0&{Ko?-RXIaRsfpcN3Y-&x*b{PE^1LR<>JhQ5ME_)( z68x=IvVUv@6T)-7d@HrE>;h;yd13^8h`w*RVE* z1ZhoaXS+hw$quPdz&3yvU6I(Z?hDA6@?M#NgZ#)fU>C%h=&vTCK->rmq4|d2E`q_k z8O}WkDm7c?gCjL8mN6^O7|$QgJ62){;5E&azrX}0%Mrw$k7 ziry6~gq^wLU~J|W*fPrJ8jwdzA`HXD8u93~#KmW<_N9!UJt>PTIGk3G3%G-j&%9Lh z*|RHJFM7q9eb0Lq?z5ybf5Vlidfvrt#q?W1EE1?4(&%rVR3VqMHq7x4kY!l7gfG5k zzQtw)fN6{ko0>i&$=pUv%sE|0Fa1Q^P1oRw`4vNV&f9;mL`@_Dit77Q{AFMY|{yXt=~;tIDC1CW+&65GzuH=)(T=G z$iu0aE?{imf8RhcpEVT+T=GG0Y$*j3aI-kEj*>VB{oa)}Ep7z*0I^oU5`bngE)TW& zJTC`h-#Iv?A@2Cn`DC6PcDZe94YS*{4W9-!t#rdev&d6AF?Ekc$jGm_!SvA;pC11c z)i4OubK#|&MWzYC*4_C7`GR;}>CKTo`LEQ@#S*~IAV9wC#@?4SO@atCfMWmBZxE#o z*ICKVI9&j=Vf1$tD4EexNzm%0lpK>5Pgd*;Sa@MY79tpms?GcPLE4y`8W;e@ex2;E z&NR1+j=sR_ov%-wGQL<0(SdA=gYn4Ax2V&F9Cx&8v(*jP@vbJ4Fc9fqJBXL*2XJ-f zt&)mjEElDwEzq3{ioixoG*sqz@jKP8yE{xK)o$%HpmZ9W5cjtvbj$o+XTRem#* zlq&}zq_nxY2WB?KOY(}8`)u}p?WvZevJAM4Ll5Y?94j+QPU#9VGql2SV3ue^srA(M}{Ay>X8bH9|+M z6H}VT>jEw136~esqt7g((A@oZ$j6Hi7(05O&mU4Jlb&%&yW26u}Q z^nQ7aID-%(p@VPv9em~Qw%B_=VwqV0h>(+42>x6GiW3KWK-}MOy1?{b9lINCE42WQFB)F}xxFC`Cxq*^VsL7~D~} zaz34+AJ6gl)2Eo|E#mR8Gs)_A^fz)kh5OstpS5o}Ot5G5Tu0pc@(^dJu}DXn8ELyT zUnHd-aO#hY^i{!Oq4XJv&zWr)_clG-M=*ARI|S!o=1^FZ=1?rtSyQeBeSRo zC!`)^>@6iiN0~1NF~s|r&bb!ni#avA%1qpLR70h9;YwyU5Q*=wsKHBYR1j=uEw(_-HHdXlHdb(-bHVKTT&%67d>}`BJ<4{biTez5S`qiC^DhK zXDOud=DL`}g%z~@AgDYV(+VI|ZGbK2z!Hcx^5h)v+RGC;$Npyr)MWXBY#R&Ok*gGj zrHd`FTyDWQ$ECGpsap{wVN`I2w%!1+l7JbEoTQ(~T}EH% zAE)um{?=VwB=z0i6?;5ajpFZ3R}j>yfApeN9e#ppX7#US+Z-@l^>7NuaN5_Me23_; zI#L7t606t$v_`=7)f1}4zVAI?gM~(fQRK%7gZd%xaRqdqG$sd4(=Ch80q<`uf+iax zl~M3L8n6otGjD^87NgyOy8!$jAj-RP9~)8K0rw$QX8P{Z*B;8P>Y$@(E3WNvIpl7ZdgpNrtHO#D?JkDT{6>qZr0@L##4nGtRks zZOVBUU4=B}f)ceRT{Q+Js}SP1<%Yc86tV9JN&uc@i2A|I`YneU4@^pecR~-nN!;Wi z<;J-F^dN9C@6VaP(ZEQ_*EY3jtA~&k5Xk?AM6NC#Xe4_{~%E{qsBoF_USJ$bZ zm6X2D#S_Ab^>DKKM)0<431A}yjDRBUjW*&h6%50Ae|!ot4hhbnuZfiia)?yyLyq9} z0DWKV_KJ>{-|C~K>o_7It(^P7*K^#+g1|r{x6EpT zSNo28FCRg;WbH|+VJnhXf^Ff!9;Pak$I5W;m&dc<6+Cq1GPGLRUJsGFvl!_NCRzQ{ zWy<4s#^ijZl=c3(%VFHENVUV1B%O<`qa+%kM#lTH!Vxn-u2O5n6+AQCPyhDmYs^XI zDef`4uRfbxaQG4$Cc##qB|Qc;1?RB3(>JfSai@+zKYrcsOJ|GE21no zfp!{$A~NrJAf-x~vkyiMPQz*WnZ$!wVb;*^;ov(Hmy$K0e%6opK@zwA!hO&#K+}xi zF8;69<+b}?*tND^CEmQ%J%cK{_+Eo&NEZEitQqtM%kHkWN>{2E4}|&m6IV1kfK0n0 zC!`nHQ2+hH<@`vem)Vq3;-;(W5;q5T@KCQX0YIZFRar-_9yeAV0KxsJM%bK3jVn$G4Jt=EH;W|%rR)9ZR;ttgPir^qDSHUEobL560IAv6t zjdcUAvE+S$o!e6|bxjtwKEqP0gJ`=g&gWUqeUUIrKX*%}QC|N>)v9)7A|4?gICEKu z%+up}(+PD{ff0A)Cl=MHPh9CGQ(jXtFuAaDVIpV+3w=$3U;A0qEYk@e4}kzl$pAt_ zU?bsU9h%;N*ne{jp9ZXtNZiD@P%>aq#ljJhv%4W*q&6%@R9p}-7|}vqWzyQH-8Sb` zWNO|mbIa?Zzy!VoOI}XV&aA5JyyHSBAqd&*LSJZF6#uU^vHtCKRH&+Cc*ZN|3((!0dwAoo{HL zEz33bx!uFOLN?GAI>!};Pu@e@;miZKZqD(hdXM?Jwu7_#$5N%1N`NgXPaKq9rU6Z> zscieO-dwK*t!R=MQ%$VUEXJfE-zAE8?Z5!4<~a+^Gv{`{PbwWNuB7?%U4O!prKY)!Dl%G}D7=1QEb?a<>SG^h+xjEjaF)`I-*+7&rTPJ8QWYl_^oYR`US-Pw0%=a`Xe%RKiXky%7ybYcE9H2J z|C=ql0lb;p??8N zlD>O@`v6q*JE)Pu!f1l%wbXQiYA?lQ&C1Su{47!X@P6@KLj3RiKgx%O2=$)L0XV-x z=bF6mb;(|u=MM6uETn+a97J8~Jp=qsZVBpRY{BC9>T>A~iRb2|{1ne408{T{pi9s* zNO|*SefQLRUN_X(BlytRq!XNS>bD6JA!kzXO`c^F-bL8n=JJ|~jV zA9Pd7B^o8_J@YG0j*guK*5q?8_N8HTY(w4`yekYl5Ep%pe)#H=s!|gN3iTycO$l^2 zv=`*rq=yV+yvIrgx0{0+b_{TVlUL!-upIS9~%pnl1Lt+bnS)| z+bKtFOW!qjwwEF2%8r`zBIB_kGJFFop$2|BvP$>b5-Wj%W7RX)Lrk(NqTdrs{_>V| zL028B0T;q^AY$O>&I>s<%~Xq^hP&ylHDtq}KQ-3jTIxc}^W*FjGu_u40-U4l-~C!Y zbdXWyRgq4zcyHTj#5Fw;v&~iAIU#fPBZdl{Ek@`OF308GMfa{G=Vtr8O7|Y*O+KvU zVM+3s|t{GoB|0e0{O8e znG3whHUEt`6`hObnf_)$-A||ggR10aX+Yw{XLfPGY;_D_3)^Bz;rVhn;I{ilr8nDS zRZK%r`iw_1o~GB3=>d)uz2y!>@sci3_zDOHz>NP^UKR=S7Lpy@vqsc=lq#_{o`7v6 zm@Bg2Tz{Noxz|JvP!w=Wd8?q?T@}Wi1`R51Y&}V`;vht%)}ARmkfV`Ve9)Y@xFMs) zMM^N?YjJO?IP#WYaaRb9oD$=u^l%_&TyUsv`K(*!XZ?1tD3VpoaCjq5iGZ;Z!m-vP z_ty7I1XF91R+^4(c30ztV{V^C1wAbI zMlAAEUcxrT&0xK??qC-y^&_&udcGGMTxMR2lmasJS#7jWHP+K2pT=B+`}B^0A_T(# zA>qonk9?<|EiKUDXWEFuWN=hYa1ZGkXZyZ^y+8P@VE;aS8K5B{=Q95n^4VOj@ zj;C-pqgpkc?LkhH!V>E$V}n2C1}408uaR;h{s6IBBs3!jr7l{*`c~X>1OTyH-X@7y z*L32OVC-p08J7&A6QCTQCyD9r?%w9aUe*niJyE}L*}$x(C1Z|eJWP+0+e`YJw;y@o zn<1MfHh6sQ3uyG`s}P+$!y5aN-0@`GR%3KG(;MhB-{KE-L-O=>3f+e^3bytPKZB7R zgCu5E-18fJlQtZ<9$3IN z{WjW!4Z31!83ZLv(l8Z^+tpN}(=2`Hdat)NA8H?fe6bpbXSyN(i9;1tLdU?vS?Na; zCWumwD;!u!w#E{>7;-jcjqnm>UgSBUY3vOucihSro|Ln0wD0p9B^KSL=Rz?~Vwy41Mp`W>VSEq+zJ6Hz!c0;vf?y#4DfO{<>s7qJwKUOax<|E57 z)fypq-yMuI_yHj(Cd{9Q)9}wuq4Z??6V}H&^;0Z|P2()Z!tgW@_QRSX4zp5SB(zot z?(SIc^7`%-jz1k{`K)@;vUWpuRI6uiA1E9dEmy@NpOV;2 z>DJ$(ybRRML1EygOgKU^W=3m77a;`T0ZpHc5uSZG{#JF_O%bpgw)+v^y4KofDe6M? z|)z~D)>I$Q#=T7{Y8%4e?R3+~TY3jBR*z2u9|&@@;z#+F0o9g-nUkC4F# z8YB4qCJhbS}8O2+YTG;Y&tmt*t{uW#I36=r&tH^5} zf`lHN*j+jrQ551Z?nIZ)X2-UTlW%%A_#s{ln{-95ZsT@})R~Bt$BO7Gc;aM7a3JCQ z37XxOuQE?N5jJUr-l3#?vl*o`IBbZ$>R5jen1ad<34r43>Kp`M=soadO_iOmD=GNH zS&N<0pwGTej}<42N}qiqK>fodfvis;;Zbq13r``B2$@c;Qw3jJVBlubhII@l566?+ z!4Z9mdj@s%!!)`de{$6_(a?h+x253CuZoOkvGaCVY@w0^3z$~oFM(G*oMrl12?t?o zj1@A>zK-CJghxq0b(TzjignKnm(DP88^UVGc5_gM78qS+HSMGqVkdm=BChbrq}}6c zF&ky^OXmWg$={o?O>^`acjo5q?z;cGSGNgq-5e3Dkk}~iB$gdzP=TSx6LUAy153%r zfTas+HAkOTVd`3O)H>k^o656A6{e-6+r<*F=bs64)G3T{`3}{}5 zK8ulRNMYBYaD?gfD8f&0{|2d8E2O+sZVYWi8n>IKtO%p#O<2^Dp)~&95}opgf3bQ6 z+!>p|UCezhq#0|`A(RuI(xKt%yws++$r*HHZH-5a00mp$Ka4~rLafQTgp2Avt4~#x z=K+jnv41(wP=44XJNJpj0BeXa5!*$s4iSIo5xnP6&%v?62qJ-qqmDuVSU{)08ON4s zASp!K7Q5fSgOVkIdaKIut5Y@{dI07%bYRx-w5C+X#)}bRy7qUOB)NiHI`rhUae|(( zVC_&F$S_H4YHfeF=D6DRie}4C1KaNnQ%53ZMS*JinU(f#7`eeERY>{=f`ttJS4+Ym zC8I{_-QIAL3_k}IdyR7Q9ZzFSrsiJ;{cHJGH9IHYUjy=5$wU6C#8iHPc~2Q^U#wDL z{B5yY!=7)(or&d@NRZ8H2Hc3A+cGFFvVT$fK&LBQ7#(@?VF_S zXGl`4c;o6XyyIw=t++W~wCnJmPxIAqekiI?bJnk~e0PKw!7%962Ihy>nzV$_;?WLZ1awRvn~0Mn#xvW&@XspTdx z2?yyIr%gPlQ3`Wovt0`t2#_6w(m_vfi9FN1N83AIEGRB)%>FD)4~Xm^ z{Cy|f#O^Nl6$9IiVv>pg4U7WZ+-leSZvi% zEEwG48!PxQKaiiasJM7;RuxSS( zo%4Gxp0EduSqbXm216SmyWiOFiam}J`U3h?v9<02w*?2>=;5k|I*2q;z<`X$(2cWg zU4IqSYh$7Uq1mKB#@+Xi)j%`Y)-B3ms55yJ3X`y&nM@Sqw(tEl;YeeHYH5L znW&Az0tnI)W7{2jV?+y)`*T3Z5AFq_Mf;FNFUi7{X_61?BZ0~JwuAz zR82g!%vqc61T-q zPa@w}05Ef7!AH{r)$xwB10jR^WQiQjIOYFN%}!2AE`k=)$eY?ye_Fnrpf~X~N(2d( z1RE6p04_fUc@D=J;4q3i4Zy$t$`M*kKJz3pjltx7zvJHZzVOaluko*c3PC_6f#B6qY{_Klx5r&3iX0s zI>RI4XIQM8Cs10csIFgjzMI2^B{A^TU@e=oMN?bTt?XpX39Nm(h&h+}JBFPBgI4~< z&q@uP00)xt8Se45N2)P+(6C1+&`W=S`9V|P*0ER)muVfEybcs7grtI}+vnk`+?I4Q z2ZW&iO?3hSbS6m9i?$X%~jUK;hk~jXEQ(c6bI}eqE+j#ZZdG2&jPk18$7hGQp*e&6`Eb_^uIFz z+p9!(!Hp$e!V8!}hyd%8ZuTVi2@$H_a@NeD!H>bx(`}A`y4N_k`g*AmZ-g`U|`@e?`l#9;`1$--)I8Bf_7WpSh^~`08L7{K)Ts|OrXi(RkQmZC7;Jf4 zmtc~JWlOgLCle|Cn|-?ze5=LcoX-2Jx?&JepxLN=V5_nnFcq^}_9&%9NGV2dmYQPj zFdi6R%hs2x-b;R?l^&6Bj-A{my1|gWj!D3V&>}J>b#51dZH+k+cqKzqggxQQ&TmNe_RK zMQ=wWP~^MM_=Ab7!@!AbuJ$sgOb54uB4XO^%bMjuMvzKv368=u8|x@hGKjDCdt;_j z5*VA=7dsoq$!cm%CsoC9F(f&NUkk6;F6#9T!{XB9pFnvmj@>HFVnbZE2!`%;-jwQ9 zrZu?OxHo?1v`=7>u{<{toss@)QBnK9WSnUm9=veR6F!?m1nd_ff+ucvT=#wFPwaPk zyCLNW32*F$FUNYmw+eoRW^e9$&d!EYCcEqEu=9>G(}KtUrn2iPdVQ{|q|NRpxCdkd zt3&he!0tazU}K*!IEpU-)l>xE1b-ZVrv<9*9Nm8F)S2N@*vkX=j1$HTp2d~>3u|>o zdnX-oQ*4s+43C(W@3P5M`rUa**qOW9=8%eMBo2}6>`m+B(FOw*u;sV-_XIGYk<#&BOJ+DJ>=SMU zTiP3UCwVSwLvQp7OMXdq5A@-Ol)>{bT$lrsW#L$WoAgmBQ#K4~!5!qkyyT8<$G0rt zkVqT%7Qr+cN2!+qwjS}p+;vDH`S?5{JwCz4qg-F3-k9*+(iK$3s!R!+yMG*X7me);_PO2pu4ulij2%w9H38rjyL&{w zpViZB{pY<~C?d&bW;Y~2ZAj>$C8tP_g%_08}6GpL~Y^ZGH|32XXVn_0Z)%!Kr7cW$(7YZwiO?|Cs zW?0g5aw?-0txfUu!p0Q-u)>&B1Kx}4gqWvL>T%wp#sV4~RC0Mjw3HV-;?xhqTT+krC6M0kLv1Z=+ zywDK>`cZ$JOIoU?-vfWw-TwumE?b@8Vs5`ouy2K&pWemK)i2IUjI94@SW5b)>-+W5Z z{=fhkzypxV6aWAQ$Q2^c004=EpeTjF00yN)tC(Hr1?qI4@?;dAHNohv)mr1tfS(>4 zVPHRl{vMj*@uV~EQWaZxSCgF(xuOlZvPV~DjYNYauAk;DOp%9 zSK0pzM){f>=3BliRJZ`lRg6KQCj=vgvzgE;ni8 zXI4ZHDgxI-5C25~o%O%~0X{|^06X9e5Zer#0kE|51&}eD~P&Foxnv%Cnk=s zeraS6g>Ot*5?Yy8C%fZobY{T6nxNE82-aOerd4j64Ka_-Ix2&dNO$}fg8hZHVm;5s zI`e4NzT7FzbVZ~8K{HK77p{mByRn0K){l1SMk$b=#%~v2t1w$FIS;3m-+hcLm5S~0 zZOSF!(T@%PvRE)uHxpgSGO>o7K-h-Mr!q1k`TPQY3hxh|rncXsOR*g}A;*LijU|}* zXB(HErfP;lJi+y|&9-g`v8(+BKdt$(KDaQ-a|qs$&6^&qX?MPXji7rTWZhIdE6@A? zUHWcxkzTc6wxCv&M9_M+aDO3&tuwgI=5>QWS$Vp}vOaaCvg%G+G2g(;P_U#AUW{H> z0J>~viA0t|?DxJM7KgJ=?=OgXZE#d<@46iV&7zym00ktyKt0KIat~yYBVKZ<-d1NH zP-Kpciqz?f5evMubTqWTlYvl8I=J+-Vvc#X{$ST+%@Rt^D(8hWNXiRgIy)TjtT8IK zkDLusv->L7G#jVw$o}}er-wouL9wA}O+se@wG#4yFG9(mPcQpTqnD84?dD{{T-6Ai zX{}Y$R~Cj&zIkVtfsr0wZs?jb+bi;Fdsa6E3lGXgKgC%m zVHfTb`1ZDP<2C*n|Hh4>Mtc#(H$YVeEuii`mmgAS0Ms9PawX5i8K4aPA`PILh5;Z%Eite>B}G!7Q-U=n3e_ZkkY9x00^AG zi{uig=zcw}RZjl-Knx;3OzxfaC1CD=%WZL4I3TL%9Itctc@1?jq`o2e4^#o(sIrbQ zZ3M|3Tnd$QVeg!YI1#KWtnp}NWt1Yr9OKY5n<0_1bnts*W*_ZSs)*X8bY78B&@GaN z5XEK$rWIa#8Ppp*GGrcl5lyLE={MwoHp8;EX3r7Df?hI|>fxsg&HOeb0bmV~K^hMl ztBcF!4Xs2HtsX%In**l6tm7j%)K5VV*|OssLe=JbDv7GV^`B6K7MU+)ZNzY}POI;~ zS$s200mgN};M3mlYpCJ!UF`dbJwJQaC+* zSeXdx^yEn7j;PL%|Ehc>p>`N$;p3dg29qf}UUveQw&^BB#VhoWDu<{+q`k47Ht;Cj zPGJ*@_Z0@jK<|!W5sb8l3Eqklf3UV9V!;Qr*Gihm|nHUKx+Frj~HKR&99r=Aa z>`ua^lM@U5J?bgfD|0RIW064ktzG%B?=b zqSx@OgzxDO8TK6^_*_R|Ep}^v)Fp8598~MH?5{%naq37lj9%8sB3tLPyS3;hyj*(^ zQ;**SLA;-bHD*z&FvR5yvI`82Cag*UWAw?w*P6P(djS@Cof%FYblnjK@HX%ZvF^t{;|%c>#=SrP$j%>hQQ_V7Z@tA7kJ?u3u5P^eln|`P)MC#P`zrB$mZZ( zuJ|Vzad!zFD~SHGxjHP*nJ23!kNix2sIz?=mFmu^>RL34=B#owT;OQF6ESz)SA^OP zhv!UCPYdD|vr3$#1)nSW>P(5AB)77QY(zVf0w&QVc(W3R1=<3}5eJ9=D*h$NWRb zhr=k>VZm4KqYlNIT|JPD*QFBWN^zMiOyN@T>Kkt98fvU$iYBGeK5N)vRBO0ir)(|k znA#g+{Cm-d%{8RHZyeiGgLeQ_B<7kDxE?JZ_MF*Z0JF5%04|mQ00n!F&?3OGh@1cb z0{{sS000u;2wqIi1ax+-09;*Jtr)046OYdxj!wJ5@3?lCP7S}0l6$0l2L2G<6@?vu z7S<26A()MYgne~*Ufiym%TcPBO8TF*Yt~O@4_^y!!7|ca0002*0iTzQ zbdLY1-H=>hZ%s{29k3RX&de--0&YJP_8gRF*3R=jlDa&bu=AW4S19ts0gDs5e9s`B z3_t*QUI+~mwRUBRYL5aACLrb?gg;03w`a6=Uq7N8{;5BCIikwBs6$ zzcdcrFc0&Fbuh}zF+_(^Uc&E_s?&cAIq!)8039_!o0rFd0t65LrM0!P8wbOXd@uk2 z0{{>I?uSp9rMoZ)k?*wa6W0u@Ze}>Zi;GJBZ`fTmAfsV%^FRJkyTlknGg?s`nV=$N zbBMf3T)xPWp5TG<*!^rxJ^e-y@+0xsD{u(eIm*a01PhBO%M;O4Y_|-ivUh3`l6~LO zu$s{=W=IS%>`3!y8nb9lYus ze>&o<1^t|bQgX`z4}&`++_W(G&ePU5+I{56H$UFc4&`~>KtOxWxK2Y~hx&Fr8cgFR zP;fNeW2U0V+Sq3i5=#~qhT$bR)wuU z!ip3fQ(lN{;C(MGpHJ9BKFR3nJ*q0ai;L+zMQnsxZHNn}A7oMp$z5g}vsraP z^*o(CPENc#Bv3O>reiD?+>^#d8{eA|u6;f55*NdDs`JQjoAN^w!KzlU+_s_Vt@xU! zk$01|H(-~w`26P@S)a7LmM85*y9ZqGN`>$M?EY&XSMaU>-ho>sCpRw2G zLaS<8wl|CUe(l$Ei4^e8l8FF5j0fZ{K$y@x_Lv5eSef5zVJ?puz|VtLB56Mw54qs` z-kAg_n)1A)vR51Z1Z$i3+y=oDNhn&8=+CfE)0)cRAvrfIvD41P`cZfIa-Mn%xwdr=aE1K!vbykJpU%3jn`pyxGU4(eH)}Ot zo4WBfm@hvKZ7J5sFbv;+9jp{SaB=9!1%pumHBfqn@;R}iK*pfJVpY;Jn*Tw1dp=$; z3~>D6eGJG>T#&~$V5i>q^}E4FSu&K#EU$e+5V@SWfx%@9RrzN=*1;+xcyk^A7N_>X z6C3FKrP$_oyMhgowon_lvnR8tipTq&i3yzyFFmPkH7nW)4aMmptP@i*+=dqle0zNy zOdl;&zHmAdLio=ffsbTz1>!;NaB)cT;s$={9_S=Ud^gt(#&ID{vW;c#>{TrYadsS2 zg6`&3z!hx~DM-YXED^2X8|80`QxkI?rq?)NMueWqrdoRbPU@W>!3k5;A!VrA#S{Dk zw(IZ4xq~bh4uK;*J6~0Z{$*Sr^!Xb>SfU`C%d*9H9iW@usV{n{IUZl(`FcMkpT^Zu zVmQb{TkHMC>gsLLQos7=S*^<25iIn1!14!511lR%iegRZDYLdfCQ^nkh2dNwFWpZ2 zpF>EWV2$Yp*bbS)Cq7N?vF_MP`D0PB(v-f{ZPX@SvmLT&fw?XGjwAjOF~Q8}&MNw! zCx_20285Dl>((Cv7 za=fT(nJvRhc(%>1*|K>hys{pD6Px_-mR-zvBF{OZi>2hqeOibR|L1K5ly&O$vuWG> z-6BuxeE(+9EY%K3`unWCl1oeGJ$|B#?|qG>A?1f=CMrP%0rJ5n8YK0?#e9B8w~8ow z^d=j79h)zEZ0&|h(ubme1v#|SL4zS<+)QO!7)WUdb@QDp(m@Jz6!8{CL-1JeKv&em@*(S}tId-x`hy{Y}xSz0ekauGu zJOctu=ckmnu9?8U?fr??OMPKR5m$qgd*>N~YsIl4pe&LjVSHmB;2h*w8RJ2Ymh&7W zsd&x@@7khX=;?Fd6r{UFOkfIzdcdw&>ytHOrIX0214`Pd0cuKwCAV!1?wOcOv7_=Qt`JPvU)r9tO^NRwg~K*ZV) zn?+#QoR7h|v#h4i36hyH%Z(&Nq-wBL(JB>)e2#-|3g6fL~ zc=J99i6`SYjk=4$!@4<3aW$dUc_KbZGg=QkAb)ljX%f326KU4PV6!9j?sM=q?MYytBqnYK5b_9XQhdHd;tH!;X$SH^{VY)j*q~ z6wJ4*)9z2W7H*M-KU^rvank?}OTXeJngVE&4i$us=EN^GxMVQ*t8M_k%S~EBACF5U zo~CP0Vt{PlmkaU<(_u9Y_~{Q zdpi8GPoS^2@lGVv7EvF2VU@o}jsX_JNcKv$IN^J+0GDbQWni)ozc-_{&k;V_z7ozT zAakdCdQ%F-C5s{XY<$Xgq4SvY${|A-kM8OmOITsWr4$s-9BE-IxZCUl$^AkkgED?N zvo|Juw1pu)fwEkmHvzc7E>-t0<(K6X+cv3oghP{3VBmB+3YLO~B%p`q@^d8ROD2AC zP$OMu+fLx55(S8zjK?(KLHV+^w8 z5#zMRGt8*La7VHN?@WX+tpdpRgspQp$}ytai=XU5bSYJ*B+JVsO!{nB)FaiZ5hnnD z4Bzx!dmw&xUQvoZ`A@f~MURAxyYi8{|h{}vp`}xT$(hEfRcxyd4O2mu>;t^o&bw? z8bkl(j(1c3@Z;lt`0$Qk-aq$vTgM)0#WsJ4nS$rmqPx>9Bo z%z9ge9mg4CiTWj&`;8*qhCE9sdw&KkJM+*poPOO){D%JoyirzJ=&goH5trm zb{D(Ww_~#qnEBIIffRA!-YBemXy4#?Eak))IR!2=pE?0x$*F1=pcW$gKYf?T&SaH* zL<}VE*HIiCra4$zv)>&Ui*YbJq^zP@wbMQ&HF#h{-rfPnfi&W9(YV`A@$lIeRh_+@Ol#+rRpV;tPqL2Zlb2^U8Vu zB}3p-t#EYfoq(jQRcZ#2{zP-Ev$a-IG)}#=&@ZmFiT{5r!A&jUvL4th+el+NqSN(N zycs_gd7Xk25Cb$)3eJFS;olv%;k8kpKDJCi8uNS@%t2L|%UZ zobdj6F0tOJbyQVGVWm!^xp7Ez@xX#$28HK_h zUUmC++qHrA(uLSF^ZEaAtvj9h!*@Z4B)WJycGC8}TCSitCgOi4b$Hq;)2XB4N{PE0NWbnM}J) z&A!`!nG~fsh$>!Qfk8Mp0ODHj1;^dR~lv4Av=sr*a`@!KrO43BkmWuc9W zKX4hSqe*6fLz_wYHtgR%%#JEil2QY3sC^|T%Ib{)P;IieJCIBF?cHPvm#A~oJO3W} zl#xyMNN{}o%2gOdfw%%xnzYVKL&KI4w2Qc-`3%dZBrecRa2C35&t!8qN<=CpJqDEc zwW^%{)gg%OH22bYuGo@eRCIzev3uuQNFIK5*E?j73ib2kuKw!pTr;iyk+e!F|NPSg z1Y{_uVeZAdva5w6`Z@e3($EXzpnLCyEx549IEsL$@5{lGRzygE`=H6gkg;D9sN|wr zz6KD7(=Z)YBxP@YYj7*EiUi=Y!?^KpyCszuH_|}iR zv~}Jv954?OK}Am735-6v-q;Iflv?D4_)44#>ifQcu~J272JiYX0?|OoY!_#ll6*_{ z$!}UmH@2F>gnZ2cgI`M#0`e{`aXdyTEc-mM-DNzv%3#L#(Hppj z2KiaLfQ1gJtQ7W*xXEl>LNMd97uRCdES##!)OgR0tX6SAH@qGPBcOMd_sZk?E!k#k z0whtW&?G>y6ii$iw{GMaBS6E5{w#>JMSiDG$r=_T(nR2+E;G1&jKA>cl-MWwuBA*6 zm@ez+u?J4n!2yjD56{HTA1oko1AbD7fy{18iko*8n!feplFRX15ZSja6DC^aJVuO4 z42m4-sC4DT^|F!XxwD|#luF#D<-|PpUc}NlIe>6>a0Ah{h{)e^t4Xn=tp-9wB~v4E zgf4@Tzp2YRAhy37Fv0AU`grhcXT7|8v%9!%wZcf>DS*vCOi3zII%5N}oEr=~L;x)p z)+rOgH-KiKXTKMLnK|8OGO~5;M1^M=w}BXFEsCY;N+)(%wMfBty;q`go3zyc21(h( zw*vim+&N1H);5CEG715%O; z_Ha<69jhNG2kTGIXO~9K38t?9t&O%Xj>O}r!!JCx>=a=Hh>n|r58m|?U!In9MXzo{ zF=a=GNe$R5oqfHnT_g-tDdG|E0DqIBEm@U@hp@`c!*w!lft;D~^N0~t_E&6ix45gP z%Shc|vfJwFDt?7wVO~tsNheV{o6bpy{UA#hX<1V1mGVH!dd_7T4(0&l?Ob4}3fC?Pdzg4uYL0^LS zi>iLGa(b_;5}?!K9%mO){(PM|W8xYi<=u?-Q<7ga@7Jhy?A$7aWnh^kck9s&Ri z6kC^oLkuG2P7pViFoMPt8I8GXMAXBjIb8cN)ovDEBRezDf#9qKH+@Y_sUCvz$5XB( zmgEMrOT)McdTl)Na>F8D8tPq&xl>L=k8#1KVq$g5ih!SrFGBvpzkQLD_@0oyqj@;# z!8TrdSWeX-qqr7Lp(lQ^SnRiQiXh>qNLLZ;4|d<(5dZ>k!z`EKVNc6lDGaO2kJN@z zrr=Jo^tAfXWS+U>!lHNNdrIA?HVhV`VXm~S=w8pY5l+}BYz#aIDi`QkNw=n~BfSZ| zoemW%?fG4-7r?)|mKg_Y1+I^r5>Y=N2f7W(rr}0ag}?Zay1ob8H_}m$54q@#lww=D zV%VZzajVKd9LF#6c!ij<$rIUw9Z^2bxvJ(k(`7J?|YD&k^Wua*G_-J!YOowFxtFn+4#T^A8rtIbqq~C>zYj zNFQC;&U14T*mepitS1D zy-JInAaS1nuS*rExRSo~!?e zvfLU}e3-8mswLEFh4O`{C#rY$o~_SfZU9X4d#oEIwn!tqqpdao()320f#wZ?<|<45 zrah8*Y3T0q4+x7>uvvGy5?6CGg#|__k$hO2;LEQ>yb`pmk{Xx$T1m(@Dps;zd2liv zbz#W0ee4*xc*)Igl~Tel6sDriR`rZZih?tfMXTK6OQpTQCjVVCa>)BUVy7C|LVK0#~7S+Rx~lCxr7Ymqv4E|$w7&SVv!CcbXJ;r;arR)6uyOO=UsY**TCT3VYwfQ*Yp%jN7tVcG8a z36?_=cSA?vH(fX%%ZeB15qI&6$ag@CD^x?@-WZ!G&*U)G^23GsqZAEHm5uHVx`zKNO;-3FwC~Kof3P%$PMECp%2sNdFsmlv}1mrkJ$<#izPciKK z*zt!36V)!d)*$&08moi{Ppn=bZofMb0H@M3OU;yaK2s}zOQg?g0Z==`-d=*@HZ<2;uqlq#PN}C%3r_Vlj!JRI|jn*n+&$PY%d*0Okxi?uqu?9HQxM% z9wa9yweEZtXJ_nf)qBl!!3j3pN?$aVY8-E?C5{{sSrEahjDH94t|{f_cP{~6N6NHf zc7!t>t-f6DKclK^dWtT#PH?QH%!e*;4mL-lb+@LROhF=d41EBbadR;*4FQor$89-^ zPcr~cV#M0RVyWnCY2ijztXfVwSSfLz>hQsXfyi;)KR_^;YkilV8#>Yi4 z2}UMkOnc%VOc~>?1<)by7f7BwjqwK9 zRJZvO_D2nqL4!NAEvFz@rowFm<^#sTLF<*o;-jAJRQHNAo{5nokiWr`pJ)aZTHbUxV8&; z!H@+)|suUzm_f z+l45tlIu@ZwCpIo_?(&ktJ;lsLHM>63-G1W62t)K>&^7ic_pyNrEU~$D!)$EnzVM- zZy(9R7`4ktX!-dy0)e$i>7ZFan4iI{2aRT=7+SODz~Nwjct0#jcbBGBR*gEkGI{Lg ze#Ll0VA3}8#lRF@Lf6I`-+YkALm_N%$u10Aj59ZVLv8|{ zk9G_U&bVu#ikdXHLuV2gRlZn4LcwV>5q%8b$e#~2ITeSq}KJ)?od%m4= zH0YB9Z1TQW#6qcywQsliM7MX+9cj*GP#$k>{|3=gSLgO@8Tq$l6$5$ej^HD}uuZwg zdIRrbgr}FQm&N%x%cP!c;+KX5MSw7zV;*!IW7RMk`Yd`9B+#p#JyoLeFrpH}liIUurz|D2{|7AI<5 zy#KDbuTMXYB070#=#&f=`J*@0OPdKtGt>x$foQ|+Zl?~JSa@VL;WkE{u3mmIrx|W#8+{LX<8Oj@$sx`Qp^FOlZ>e zRT}MA!!%!9PDWN%ezNfgHzYDGV-0X#?M%sTb9x#bF?+paPP6?$gm+D4;lia$+p7+e zkyoS2R|e*G@9Z+}Gq0hp!WwDE4cwrD1SFLw%JCe&V>WbvJRNrM3eg+$)MM73z5y*9 znQ49Or|*Wqh4(FN<$dlA4rm0ccW8rC6dx3N>li(P~>QTG(jFoj)LTaZ0z->E1^(e}OISYTUX zp#5)LOJ%ag_dyZ5iPt;5b4aHK3S@{p-U!*Ow+rTR7!j(c)o!EpN6AU~@Tyr+7*h^g z^?{eAhZqY6q0A&}-wiUq`z9Z4+CqYVo4-um^xNw*l4Y5?Y7H18{IX1C7gXcX8!X-g zcLMxRuEk)e(!Dz=2ccfNSp1r8t#5zcwOWMPnPQ-5yh^)xa}n7?>KWe}TX;9@w`XuD z_4!iFeo@o0RTasoo_l!5XJ)Wb@ zX|%@aOZ!?*k{x{tP3D;xv9~~s~$ABBTGWXdH=cP^iw8? zZ0-x@tGzKm89?4sR(X$(yE%F#>o#r!t+LzHQ4^RCgz(AxWND@!H^rtgc^ZeY4zBA^ zVI32hX6=2qAw{3==uK_Nq^nEaPj~R!mL&hXZ2)4_C@I#|v`)X)$gA?>=O3xYaBhT-BlNv1(_a_5ET6;VD=p}mp9qN**^xlH zCV&^BPmarx;6Q*`?Ck0u0QiU*PaBd*vV*bnZ<PGhg-1D-`kq&wzmW(_?|?KmX*2@Nf285# zRP2b07YGX>YG!;B1T4ObEq#WmYht-MRwwwS40i=p#x5TH!rJRII{S*3DOX~W`lY|# zXaXx?<Q@Ayzku)crzEFO>;^HuUT4gqI0|w2yeXV<((<+zD!|X5;j; z#C(gOr~y0IbJ`*Y_Ct*RW0QiI8ZL>o^j~$LIJUM;Why59KiPCSuR;GpjE+S543_jg zN{+E8ABfV7w=r(U7po;11IK@LUtH9vzD8pRm=m+}Hiq{vqO5--%2cgG%#x-|KR()*7MZX7FT1DM(25XD=bUMxTJqotQwM_Fg>;@$SN zk@TONhr>(!FFfDzzPED^T<@okRf2LkiCJMv=3!M*a~C$E3Y#OXqQ!D*eh_+(25nVI zH%S_g-QhvRgc}3UGn4=IE%zePFa%b9ee4@7gHNa-VFmAH-2t7MZ8YXuJ;z9AM>bIMC{IFwDPaMGcunZIIuw)}eX5*A@_W zQjb2|PiSYNpZ%BRO&X>I+jX0^rXd<>#QHdkDyA&q54-MC*i3i?n}fu+Q92F3Mx|Zn8^_yA_+<7cND}r3}Cuq3bYI1XLBKmu_V$@6vaAHIII7(w1S4%qD0ei1v9;pK>YfnGpY5k1O%;6OGF8(k2uu3}mVH9wX4h z`h&$%k(Cl=F)-Q#m{4YQD|2a76hCp%ccwqV)xK9{_k-?E<*U90ap9BXf zF_Ai8%%WM0-ctYmt#$>E*0bMJ48kab3R`L3t|qXBCO{%ei-_3NGs0d3~2kX|t(dxV6mSn(h$Z zyZqv*IG#X=1g&{(rULg>&rww6NWkVrH5clgF^ps8^2@#Y8wDC{I8ZW$Ykyw~mhJja z)c`^s$e+rm2n}{v!hbxj4tN?Yw1LKNHuQ#97mL0ouh)^TC1N|T8Z7ladH;uOQB8fr# z$H*lU!^K_{J&y2H1WuTaRXpYonht+p?&G6Ms=HlVqimMhwKh4naNYggWCI5liGvxq zeUig<^pn^jr5O_-0Os^@5>oo30-4MHsD?T=`YC2FE^6x3HF*z+&Ed0PPgl0)S{I&k zmsoR9O^0{WTs)spt_BjvjgqLDr7`194GG(CYYki13@(m9`~K&>x*YH0cRg^LQS#x~ zv2a{JSY+9+Sf%xFEdK5%Cjf>tw3AGLH$j5JTt}m{8 z*gYIci!-6QC?qWkCk*|z(yM7;><71&v(@X!E!wjk%KC2D<=9XJV!|H%ky7oOV`}Q^ z&L$5KRvvwZsh=7cSl^|y35$UaID3tWR39~^Sh~)_ZIW@6hIFt6kRGT!; z!M|lp0{-|L?%>0_Icy#I%90zqUhHEJb5-IJZx6H`_bp z+>eL6*F}2K{%IXmRTctX8g&JGLR;CCc}LJJxLSB&cQJ>6Xaz8|9M|Hu12NOHkDY`w zcA!DAnqgKS%mEc8f)m}9aOf;%4JMqmQuVKR(rd7T{3)!fixHmbO;mRKwK0UYbZv{7m%mFot% zTLET3q71J3*$=sxK0<)$2)=NQ--MTLb;l~-u16~!5`=0$ z@I35)vu1=a7E|bvkSyX!W@P+EsEAsJ(^%v(;!KDiUhNzs>eW&YaQCalEd={~+_k)b z!_J4MA1K zN0VOz7QaA8uxLP}BJMd?$Yfw@_Bke5`+$2kB`G_2_s~glV$X6(km_~!b`v_i)>N$*h)KKKranNwmjkP2>gK$%wAH1BPZ2cCT@A zLGzIL$W)6ImBE-NM7?U4hwyJgFuhT8-0#Q6P0oH=<19Hh5l$^Erjg`_gO^eu>=%*$ zz2TWnDCy^i#J;{h|NUw?ANC0Xq#Qkl9ecZz?D7zRSKvDF@nmgmj}w%{&&cqc%j($| zY?C~M$f3jbOb2)Y1qwA|B?+74UGEkbpU57}MSjT{a-d|V+U*KGf$@=mk{89!@rhwUc0R(E=ieQ(M(9+Qf}`@JpcYo;A4g* zxoq~wWV;*rm(~w$V7!@{*}b?Pc1)yK@Cr6Hcjponl!9jmwu}RVp?v31jXjqYV)(Jl zD~F<00zVqn#pw+^re%Qv4(1;JfXkjnjx3IwM7&GrAS(9}Fp`w97F(8n24GH!!%ra> ziX)e(ZjQcsd{OS{QSg-|kajK3s(-`k42s08l`$zkXlSCEi!)DzE@g zfEirQK{o{nRY7TS2? zv3Kt5bjuTec(aa150|qqVj=(Kl)pH%hyA(#fVfP;F>Ck%FTS@`(IT%9wx0i{TToY_ z#IDof1K_inv_|)l9b&<1Fy=FN#Bx9951Li?NI}{|52QNv~7~roqaiE z0nr#<$I!Fu#E1i$vd05s(<>NQzBb0H6_P5;+~@OlX4w;V$vX`c3H`S(26Y3qb>2wy zNuBj-Vp55<&_t_iD+?1WIZ~>%SKivEaAhI;I*jLSh~#`*IvBF|5`Vb8pF$r<@;J+>K1DJ=5{>mUn?Md zA-tvc0}0dPtQYK9!|pOdFdL5Ax{)Fqa-&9yg|TY=Xa=D-C=4i7&u83c!_h0}f&|1T z%XR6WlKn^hWo%&mRr!4l_hSHbU&cmmSr6-7<*0Pdi28UM_@6(N-Q*#t&*-v+@rW}3 zS76#%-I>*>^>OoblYh%!luM;1CD+tYDdqk0&U@bce-?1Hlu<=2|c7=j(vhWd~p-1MF;>ymfYS+%CfsHL&TRz6ozS!w}i ze2vNw@h}SxMTQsl-W#nx(bgd<=CgFCZ7 z^5m>sKt_{CD>n*z1vR{gUy`M}$-=?Kj^3*wVhhf=i~b~l8sh~-Vyg)xB!|4LL`sBg z(Npp)HR)dr{1H$hLlM|9x=!xwcf)+Y6#A4|6X67`9!evj&_`f zL+ckgRC*yBpoE=bL)LHz zL}1{TifDxEXlazX*qfH?Ojjxc)9dAEeQFr`Q|7hnO|qned8r|e_E*=pjR$wtXi*ZmL7?9*(9x#@U$#>YHcAk7Q3XPjRgzN^hF{#%yvJ)!mG5v;>lUgR zaFX_BHyHgC@JW$fJR$btjYYQu3NF20%gK&=*t+-01e#HT7bynLp|63KFQTQ7xg{;f z>vH?r(0)#Z2#0HYIWCQsyyiCt~&FqalS%9g7i8Bd6WHMlchOinQB z=P@F2>*3R`)C-Q}~ z_M}`jFJCmZ%CiHHJ+ak)er7{3R^QKGLruDa@xhn@wR80#BZ16z+a2hX%g{8)qsy|C zw&Zm$5$}`Ifjprr0sKYDIb%{9t5SC+d%B_q(Xk7JMgq-s~tnK!h>~8=H8XAE5-E#ngXEmUO9%xxMW=Z+sKT@JHLcwB~bcU8}YZqC3*c z6yTn%wp_ftS^LUN6DDNGth<9LSw93?Ap^HlNuJ7`RWB}UsSld3aAg+9vSt7<9pM?R zNykcDmhy@JH@&}{lsOwqF>kd7&feuePZOg-Tg-tNwA^Ch#x97sDhstklsc)hoBd5lDd}#+B{$g##^POP!T61&dX|F9F<`p`g9KItxAPs`Vgi4Y|0&*WIIbw1D_f zGivI--)54OvVcz|QOebpLiW+9H(0dmeQ>QRg$O{Kvdk*E#xW_-u8-{VgI&j$U_C;wrF8~}xT%I-~TaD-)>pNQs`cBZ#? zL5dP`u)*e~IutFu);A;yHO{(kSYUizL9l1u-J|T&WR5^ZO6KQrIZXlZKTwEsutZT< z8!VGvh&!WA8KwzV5K#75F7PmJ4c;m!=4<{vSPZEHK#Kimmkjoo%hbiE*sh*XCS@~p zCk!`|#i%@7!BdN$!`NaYsDnope_yO>a*+V{j~mqwN6MVZnw_Av_1sLGWGG{ zJmvy@Z2Ojg1}^r0hFzD60?;dBWHF0gp#yCL7SjOphb_*n>2zg->Kuu}8C_r=(Rlh( z30dzzmwts?dnx$8?ija-{s)HsesM0J zXWWIL!J?53GWnOB@Z3U=y>*KJD8z1NdoESjA{V5(VJMiP*#JGfNs-7$c8QnKD; zXL+mgI6tre;F`^6YA`aXl;RI!3qzAC^HwmOKLC0Z0*)kzgrmv_Dc@xx8aHU@-E86DKU6O zy6*WEz&f?M>uDYiP!bVR?{UV6=6Dp7IGHJpp_eZVb3ls}ZZOP9;~#KF_T@M~&`%wIdD?>{`r)--q~?usO3;!v z3dq$ko~Sis)=pWB?#16Fk}HiUbZs;T0qdC(xm}f`ub4Vt_XH+Fx>U9B=f>c7trQDABkGQV>ir>Rn0-fvo7Z{`eCvD5&6`(&>==1VZ>CBB z-P1(h(=a5#28M}*8Bp-bm?m@x_!*Tb@39`ykOM&yitthVK9+k<4mmyL8Q7e~C)`WM zipgT#hRTh9&mq}`DxmihBF;YvYVc(XWhH>Q`;AJlN??s;L{h)@W;A-RZhq0Z{edf_ z-A>a|{r&-3QH-UA!?b;3Z6R6wiB!!qV2-;2I5C6Ex`QwdOH5{^xDQ2p9wqh5s&$YZ zw7FO^RI*bkSc||K*#)yB#MtVJlM?QjMS%MpWQ_5)HKi&W?bUEa__Q<1pBd4M7vfVw zb=R<`n)!@{_Y{N)#-OH}=Qmrb1yg*WrB~lo1aR}hPmeG zL#_i1v4-tf_ozta_NzuD=>|Fx~- z^?L?q6l0<+2t?S~U56mq^43PAh;RH1Z;jfr#g1&8co(PEA`nYbzQUya;9aKUOf~*` z^EJA$!!v4+13r)bMkqeux0f3+AO-7LG-^dL)higwB+_r5BrP@p2X#=OiQ}nxK26>y zGZ`n{C6*25HH>NGB;Bn_@cNWm`KrAP-8+_8OdwfyowiI2!(IU%W#7~nzx|13yY$Q} z0Y4Cjum5BJNiJ~}GjX*_nvh%;Bw7X~X@GV^IvZd?yUiqc$y9lsSG@hKRKsvy*-@3@ z3RYNLo)0)(?ti(;h)>3)R%^dFv1gVdVe7N0`(N@=5%Eji8?K`2RhTrx+TrDhx(D*a zlJ~A@!S!7}%ZWH_szx=wB{fxl38r1q7%hzE5-h_fiQ%B?g>5;D_bkRx31r*8XuPDe z%0ReeBukb@J5E7??3Zj5(br*VPLQ(RVNNYJY@IL*$HiZ&j3raTa(jtgFDw`D><|^JCusM6V&7xWK3?j(Ez9OkF9g%jf#xmu`??ejEo+E~QQ(^Tp z`c`nrC)HDwidX{mYup(UYkc(*x!z40GrGsxM?D2n;qo@HVtRGsYC?%k7(T{CUbq^V zVum;eC8~dPqDI>hcCyG(3V^kE_;($qnVN<)Egk!->q_TIkmsRCI^!mXFCdP3sz@G_ zd7cR{aLz}(Bw&6qN$T%}Q_ES7&Yr+W=0kZCm6k~)(ITq(!`J~?Imp3H%gcnJ&TJb3 z6RL{oIxvknG*B_3rS5*PS~x$N`jNpMAFz(Fm7bSJpiPF;^*nlt@=CV$dcvECkzOhV zjN;RPH5zHg?y0zfHZueadMABGVr281gO~;WjWc^374v%<=ex>rlS6F{C7bM(6bysr zJ+MkMLCsY@wB_(B?jSlNW8`^vN4?l|Hyq_RzM2 ziK$XrozOS{NAC0hc{oCADaP8(z!_sGx(=p>NS0JiR`Qn6gWiY~Mhcjq56O3w+?=(Q zZ1-Ae2X2XV4pj4hB4D|5XQg(AfXf^Y^!Nnvn|@Bp68EIUr&N`lf|W8H%n_5B5~nM` zZTy=JUFvfO8n2t*=h&68q%*M8B9bf~Y&#J2idzLHVfIICEGkSTV-cuhq#`99OL*Yo zCnL(neMKNm6XwXQ{{nLZyvT3FMaIbwsE*N-P-2ZB%CGV3k$g<6!$L?Bs~uiStf{Gs zu_;2tL^7&v`6Q4^qgyP!=YNpBYc9{0(^qeqcGz1^i4qJ*Up#x7-PI2EV=qBow(QIf z3@lT*TVTrLbSkB9USFPunADs>+QCgll}dF9E=5dhp>?pgd=KPoj+a+u8nrrysyQGr z@E(i8vfMP9BZ5h0_#^=%HM+9Cq`tRzrA~ARD5$;2%x$H!_sshHzro3WD-qLF&no!5 zs)_sYMXOD_sC@Ax{n)W#7p~UFxo*J|9`iGu9$rkP)x$bQv(vgs4m@_m0jgfR57fe9@7auNA6K6 z-2W8J&?HSS`;XcyC74UG_VcK2Y}HwK&F zKR0PpevIq0x%+b?Bihd7z8?&k#Z{u*wy*YRYBh@#pPrx1#ZeD~YV}N6eqdaNyAZ0M zDU4;%($_(S&6iHvu_{W&jxjBq0gXfs5*4SMEWl;#hkaS*+zd=_)mPR0e40&xx7ipi zK>{_gunv38%D(zGd(kd+Tpix@aJU)r0Gx;;iv}`C%3)6&8hs@0mh?#VZ|# znfyq~LKAx>cEjc_LF)NIHy`%u+#30(%MMh+5c{q|t>Lp(Z z!y@FyO z>gm<)cxbtiis+zDgc$3zYyW2MPH8 zCAA8!JIQ6_HyoVkB@#$<@0mY0-JC-jzNTf*KR+J(oNKd&?s0EPG>T9{re|jvvZtby zLmOxBTkHvXL{}ehdV}E36jwMn$v6_$=w_}^5Ign~v)U`?_q6r_g3YR-iz*+s!I0?CRo9N3QYI#1_@MX>6r_%9 zXWRa@8-AM7B5nwg)>(AXkBG2Oy{Md%4MNH9g#jSh8|2X%!+)vxKgilR8(5_@J#=JM z!bcoDJlKr*c(8=|Kgr)e+1s`_58tw0O_p1CNU4()QF-#^`fw}y1I2HI%arvZavuSy zpP)uVw-V$C>OZ!2{09?Eb@DF#-WKzec6AAWqBS%bI@Zf)suy0LA)haI9lAA;jz?O{ zfW2`39VdcqXKkE)lM%svsP779aNGv#W_Lkypyw$w{tL4n#bRHlpvzFg7;Qd$$uMRl8e(+xD-c_T1g*$QJ-u8j3wMe zcts0qY#C*_Of`wBne(f{R{!5@lIcY8g|Vt$eJ$XG{vw*$2XE$$Umw4lLxhK14HUMu zbeFJHu@5$(ka{x1B`@_VY$>adi7v3eHRey4nX-@XMMp|3=N4TiYNAg<_f#}y7>t+1@`4ic`lGhGXOe^!gNT->xHpJ&$cK+BhP%FHHv zrc)1Pm~3eA3E4CG89kHyj&DmdtJ85EC^etd#tLYpj3;;b?>d-w`fMB7_0Shx@1V#g zC_KwOwci*B6D^-rRWujND|hixqM8OBjv{(?6LQ1@HbI-Q<4f&Qwitei057}rnzdf4 z<6(OZ4$+jD8{u$toB<$h6h*&fv*3A8Q^LRn1lSe)QDk72PTSA} zgh@qdIjL4DpjwDDU#<@#a0X=#yy{YzTBNn9I^C~rkUKJi+HX=EX~m9hgAn1nwPM1I z_ZcCCrm}+c(l_W!VUH1}!oTYdK`&ue%kk01TVdT@AGw=NhM9#}mKmlFMDCvgvi#vYPzQ*rn{9sSVv8&xgo-1oGhyqL+%H|ve1cZ5G>?S_H>1dpMZf7p8nM0 zU2MJCWKJ4?^82qbc01(V?ide0+c7s$IJZ(P$%nb(K5`;l0Mj%WV)^MB6ASt|9^h|@ z9;q~4*kya^LwJV!o(QN2SyNwvn~{UC*zPKEAn3r4lsuM$^q^=;#-(Cns>I zyZOTG>_y;<@T~Qu76TW3fRGvCRTF=U#hFG!J=Al&O{kP|DZQYTDES+dmq|$N2cC)(5 zys=V?DTSl>CAuRuo74T{_)pPBL#~O~O$bn#r6)&(*~24DPH(!$W`A5r*VGO6_Vcrz zm~&_!EDR8#-&50aE&Z^|cSV6riP?2Ayy;Y((t0pc2LdbW7QMrOkk~OZ{9Zj1c7!K;d6k zYm#z121UstWGgULk+hD=iW(?24q?ic6ZWQBh8-S@fVwRbV8riWu|jYzptX%1f41-n zj|0?_@6C6eT`pUY^wt1iTnP(TTif)#z2-&q5syDvDw5|Hre5gP_daN{e=4Md3;K;- zk<`%xbUAbWTz04yz1O1JgJmN%ivUGm`#Sa(1o?jw6~hL-y=k0Qol4xRYHO|^&j-I& zGYM5WZ(Sb2?pkMyrJg@J z^?7fXmlTz^CQf88683h7%QV6Ze+%jd{~5iUVrhVf>orJH;cK6r+sig#KWZ0}1y-K0 zwS~I!P>_I^wN(3&=C)$4prcDx(%o?HoPH0u5lt~GiN{?f{JZU z;5pOk+A|mepLz^*m9QIyqt4B2F0Jhk>AvNGFe3lJ+)D@!k&2Ir^mvRLGs#Gn;h@iQ;sPH8FBm9^^Qjr>X2K+0(Fbba>7 zvkmI<<3-KkgV{DL+Zk$CJ|aUd-UG;G)7#o7U;0gxX#r8j=evP%Ar!C4`|P;Umon53 z-s%yz=(hW~RG@is4XyuTO=3g=)a8rIW+&!F&Lg6lO&YF-RUy|N9V5H`jKngnz{t$x zpJQ9jBVT*Z#YAR-(+vFMu6VSmq7Sn{^ukiFq{p>=S!3~rQ?TX(Q2@dgdOZy=WP&JX zf*iRSb?JY?eJ|D(8ooV)>pODy$nsP|B<*BinsY>|=j%WIuzm>K@44}s8>Hw-T^a%t zm^n90c&hiD9#5X<4Mog$>+mkdrafBcXulk<58a}MK{&4~55wU@ELPg49~@R2{K3p~ zW5nFu??Op*0V)QZtMRjH%r?2iUdhO6&!V>ubXNevkXT~Q?f`Av77ICQY%E(0%EZu$ z9!xsA$Wfu;cLX{V?$jH5nU1Qivtn5E770rfFwORtaLP{`xL!HqX2Gw&T>1E0&;|;6 zS&nY`)Hos(D_QlT*Hf@O4yaODLZsJ0{bgPr1KXhZ}KuTuC1`du)jcmJqFLm~1-2S z4jjM3z9g|Y|9=uy;gsUtU3fZ{ZvVFd_*^&nnjih(p303>6@B$i{j_SM8-2b!=E`yb zu9hZq(+=2P>GA;W{8%Z@+`PkHCvQQw_=yw*EUPV?bF;L1t%HD{+StEKePRpicQrB2 z)l{9~Ht&9PsyP1I4aZP#etad;QdtBvHk9bgcz%c7HX$u5-{Zz)t;N_y=)iJXvTIUwW>)KmMeVi33|8Pp@ziW27Pul3#>`#RLm)v~#nc6D-6%GluoQ z)%Py)I;|a6(|_u)(Y5p8dHU8+=3R-uf+r$9aV}jgkES)coVjhV|Nm+vAK1n)+Eb#F zH{nNDHpfxH4w$CDKhluaT%y^=;H4V~W7#iue%BqFaeJ5Ua0m3L7TWJa5h+qCwtrU% z(FUFYTV)-Ae`rypz{;$vBF3cS<09IA1-y%+g#S83_HaXS)7d|_qOGvfb_@yph@xnT zMk~cwKr&BVQ>a!?NjWSr7M3=jI<{uGGis*LUlu4#zynwHMmnnbXWc#yzGu`GUsAh_ zk(=Pwi6^5kudeOR7KFe74W{Yuga<< z1(LBv;^g|dpIYmO^(Kj^#mHukK9G_2-{A>RXTMLUpr+ygPmRWZtA!$+R1Rt5JqA&& z)f!2c%>m!8vj^pMi(_GdZo*Md->PQvo`x+=>Jddp*(IfIYltp;ru&8#Br%KFs#ojh zUjIGif*_?6KA(paNNp@d*!I`~={sOab(g~~VZ{xmKgn{FBnu}Gs@3wa0`hJlM(3&C z7U>avau^!)-dFU!GGFN|828>jin4$`ejGVPH_m^tX4#oDWW;3%68*C$~XdsL9 z@_!hvJRD`#C`4^aW={^MS*FKq3#s63)>uKe{oQ@^)FG=F85 z`149wJhD@vWtw-bD=(81b`&MPOv7Q0DFjz}34QTUqDOtPg~oRt`S-QZ&-E1HUwjMo zlpLRG>a!R0`}_Tq+&8H%$w$_%bRPIA5n895nUsP{ua z$PW3FgQG=On)bVEoqvD$(Sq=PteP_4c=%^H3+A>ALo6k>K#uFZRBMoe3p)G5C7n=9-^ zcAsXsYaQn}J*&8_-EdI|r@3tzO_1P-by{xqu)LP!p$Z>fQw)%X-0P3hvJjw&?++f> z&6kYsl%JAc?Wz>mtMGJH=_PuAYmO}2IA-{`8%5K8sBx`E6|T;A;k!kVrdhr1EORIA z>V7^WG_64Nl2LWX+0BmMBo?Gtq^z5A|F6`r@WQi3o(8E$`{ITSh!jRNesqneUT74& z(b^G8-HPv5f-Azx|H?~x3CV5V&+VCw<RhcoO9@e?8^7>^SS&{^-=(niDM z+F42(#)Wdy|8jZp4z1K#;<*R!*rDfK6aFcn1^i43b?@N`b(FQA_5`A3JBb3;#o(+w%It zlC9yyMKV}SLJOT@v8L|eVfV7U(3aAb zE}V09{G5oX`QZuCTAp^){t3QeEg%<4G}&%>hN<@QKiE@2`Zqp#CkBYz{!>joJC`p} zh$n=N)=PLeSVDg^Ch3{4nRk#_tjP6!eE1TaU>#@N&81yROmJIO%NBr&-xg2f7^54G z$6?IQqB-`yFiDt>RdtU6W=)~|COrg+E{tMAUcsqLjj&P#Anz?NUH6-E9>!GkGt*59 zfd|SoXA_k;23Gl!Snr*{M|1pPTF}&5&qbib=ewl+lZ4!0zUbhGs63ciDs5#LZUj8@YACN)LNO@3ZDaAkHQJ<_6|2~Tw@|UO(jkY<&((J zXkbI(w4foLya;nxtwdHa_^t<>wSE)5MjoQ_C7uWAZ0F$D=deN z7c3Bo5flI@v6TG}fpjUdgQuuL0|f7LcpP0jB6SkQ<;PP~2@J1cH(Ws0j4)d4CAu=1#~M6I2BOE z{BV6YL|RE&)u`R*1|Gf4T*aLcRIrn#t>4zZ3=1bf%hsHfj;tifjoO*k z7N#K|W%1C4LvW+(jyU}pI#p^a`hFPEGpPl?B%RIfweTvI{{O>1RbD8LTRz%2qgIjr zIwPmf<hQ!AO0+SE|e7S0(oQm3 ziCr4$34OI=ig=fGXkfWbAwqPitB2JbeDw{nKwGoYWFM)41--%qhZ@zeZ};e*^w}JF zuBJ)p5Rp8N0NPXWRMn`L9kdsYYeVnU+)-iaUeQh|WE4tq-fm#5T!=TJ*jnSLMJFHK zGuS%zQM-2^L*-RZfoVoyd94F&6I_$MnvpRdD*RxeTQb-C&+BYo*I47?!V9f1ntinj z5}u7807POwWxXe`RmAAIS*m)Soz9oHFeWFDrS)bTZHE@e3B&}8VX|~8kngAeL~rbG zX6M%I-a|*T!qAkuOxlKxlN$Hq8=}|A7%RM~t}HFHBG5K9n3{)1SmlNfEEG%kM$0{T z!c@L+cD4j6HwbDoscTUrYUXa+jlijdDInm{gKo#RKJJ^P_c+4f>MiP+w}1WCAosb} z>8tn+6;N){#YeAuEMvaM##ai=Girm+wTc65HVX%5vA7GEzgTO68EW&b**SbwBPeQx z_)xWRC)mR6A` zn^3q6!CPpIE^C1INY#Yk*4!@#y!oG_xTOc>a+)MMv`B|kI4$agS>(wMTw}Tt<8Vm) z8ctVMGDS9>N6ra1D%H*nS32C|b!Zdbo=!mNB#XbD08>KLbuPu|0$Ezu*>%{W$cKj5_I~%e6!IsT zu0cRYrE0h2MNtYdSddk{cMCK?_|qW`b!)$s&w(ZdcI}z;_i-`(LHel!->}Ua$Sxlb zVJL}f3{cmRidMLwe!}XBNBkTVR4U6t@>++k3{Z3a*C^ac9|-624mmBJzE(JA0dRR! z%|b$wyC~%8h@cm@Dcr8-uNw<1eJhfws!E-cWB5_DR}@H7;*HxO9>wxa-O7qhV_2Ci528zgIg9m>hBx!sECwtxn}C{kbuF%&-=8xVF{#+e~(5$wRI= zv&I}5MQ7Zl=CoR#udC9EfXl=K8Z=8?U+(Z54FBk8eIIaBh|C6?XVQ#K9AN=Tlf7us ziIf?5*K^@46i(dg{~(|^nMq6%F2nvQo}>ZCFzzh#>%f5ftv75-|Ek=b;u1)E)fhAV z+}lPVC=uI1hPWH=5jm!2+@;e-=x-KJh;7+gEgLeECFco-1;wO4sr51X^Z|<)a^pY|^ z(M~UAc`6l5ZuQ5;T6BDMfm?KKQQ+%#&h|_-_?& zW%qH-GH5M>WG`cxHxSz!Uyx((HX*wR6zG84T5C#ufUB90qmG9aP%G&w1q6Yhd0|be zZpB0CdO?;lhmW#b;8NBe6cNXYM z60=zNr(f(>^V3C}9-=UgLhDMT=Cnkyfye;tMnRWjt03Ds&MU}|T~AnwAelJ${Z0>I zL2vGF&Yk^Ph(L{lr4y+^q%rTeJG|z^CmchyJT1)51l0_?#~!ncZOzqZL>$#hhDC*nl7YCVTbgah-d<`F~ zu3#qf>JzF4RcD#KQ*X6iw4D^p>{HR1u5)F>NHLp7Rl3fa10}VY;8YBBF9J$ff~qsD zy?zrR46CXdFcGyUo=7dc^Z-D-A$>8q4vhaky86fsmn;X30dE3`&D8$dEc4g#XfsfQ zs|E~i=H4+ua{%u!PwsGXY9-U=i)WhAY!nvn#FBCk9!|_puTzaLVXainHPLap-5TNN zHuWd_Bd~$&$N&#{%^Cs(JPWp}H?-fnvca5jmttpy{RBGc&zIGJ3!*G*CwYc0udpvr<@J218R-BSPA_MW z7|O@-oh$tQHUWV!OcXX|T{<7(IDJMCB)oSipTY=zoaHDQGy3EPkfG#>+zZV^9rR@# z1$hBMRZu|@HHzv?M^DDci}LJH(} zRfDC>5P(XZE1nN6F0V|>0rUOQO}>cbQb z+|R>3Z~pC(Xxme+cdAQsF3117$eYm7Z@W@Tny?%@et@t}dKo{)Vre9&#Qn-8oda?< zcWZ9(e+gzv8xSZPfrY39?Qg3iN^oW< z@@8U|E0A<};{wI%TvR7rbtHk6fUcfU?7c|b?RvY~z_}h;28-Mt|I&^7Oo-K<%Hc$( zTT4(LDA&n$;pa;JjI)C~I0kWMA&ZEkwuAW3a4_!u>RV-g#iTQaWFGk-zY+ zC!>VrN_tUO4KgwX1}m~f`T`^&exJE$c8=(VBy0OZRr$%+eIe*#`>jYMLPC0gfO;=^ zrHR3y%19WUF`}4}V#40_8I7s{c$g=EP7q~6kp=S5sIDW@4}Qv-%WQp15avPIGHN%R zH)T0SnOwp4wtRXtmKTDYWCtfF)qceN8mBE09UmR)&QgU*I*h)ho$5$u@9a9}Soy|{ zS~FOtGsA#|D*Lb9vlSrzngdN#9w2c^)tZ$hO))_J#DAFh8@qp-YADyaVNfdY8CtJS zaEFJ_q!;QbWkJwBYMa6-k_oI-H2#MuWjG_jG~KXh39n;Hl7WHYtb8Z|WmB4*t8GmJ{nBvBKpZ$RF)3Iw?OIOcHu?|iP_ftNb*;X^@=Nr~&o(MgWLc&9 zB)DX4upT^4_X|w!ofJPngEdx`Ut7&DxXTjiZdehA^i7B`Aj(s!MhsW4C>~?Q-m<>S zIE$613bwZ+*gb__@(cas)aVhV< z%(Uv3kEc;Nnh_Kjfg<&NF(5>PQ+3?T7Z9?d88tQk{H0u%!w1q_HJC9oJ%4(Kz#}bKGcX&{2PmVAjl!iY^~mFT-~b)V2tQw zAeOu*ud1W74?$L;1Caj1;d-S-G3ha(qy*<%WhV}HFt$A>la;m43N&+?LzVV*@P1O0 zu~SdDG@BR%IIy$W2Rk)dG0u$7<618KRAIDCRIuiEGLr8>!Ht!<+<6Jhcm z7Dtt+{@L-Xt2J>kn+1C-E8Wo4e34Wdnv@2=X5^ta(6!qIrH2F+b*v|WB?Ec5@Y|#< zxED+p(^UsKknFZ!i*A(GkIx=$$%V~M5yF@}Z{&k>`m!`;>DS}#(YhQ$0Jn+F z2vk^@)6Wd z9^S8KMrX9qM3m>MS>(*aMZkV;0Kk!d~>`=%*t$&*v+hs5i=WP$E=>OLrB#UqKiF!EHet)TxRsY>6~V>gcz^y1?ufmxAbAvlVU_Fn zq_#a`OWbYX(uJ!*KD0_-9V{qKbz5ix#mT*5a%?LtDHLW+v{}p{m>Z^mp>UHtMjv(i z7mI%O)QvC&pgcZWBER<-r8_qEvEteqz(fyGzJV*)sgHQ^V3Uz}iqYh;0d1;~z2ca( z&#KVMk*g9PxstwgX`lQr;J5Ihef9?ST4M^&lhXtUX#THo0B^WvchURz^q(`Pl`*#8xGLg7(>o> zvxs8svVnT}nmMTP>Y!y{MQvtTH^0Eb1){#^E=WeEYwi@E z63*qWneOgdTk6Hh+Z}lEc?C_``9G}5qGgYi7Z=$#%g2kdD8?` z6_3M{LZM$I%&V{t3iZg`p_@}+0Fw-x?dAdd;)DPI3>8t?;H;iXQRD>(bAY6fGtrry zu3I2AVRKxGNstFv&N!Q%wkXIR&F3~q{q0Q>h3ly`)%eI6oBA~0cMR^L$bg=`$1Wc0 zJ6jdD|EOED=cZ~;3rJ)%bIK;Cz0P0kNb9y;)9kkeIZIihEZk$mQ=u^n!tFq)wB>?$ zGuE*|;~Ezljb_7&iP-3=nVtd=764U1s=vb?(CKWSqy*A#5i6duvxzvLi?~{@LV{&AO+6QrNNE%T!u{$0oN#I5D1_;ZP8M>n*p0ZV{Q5@^SQH(u`&y${zYfRJ`&c*1)KCV z#Z+=zNZ<$Hm@v7G>v9xcZ;%KL+V&2ld%_iJCo$0cbb&Sya<29TEx^9tW4H!gYtKM8 zzgY4KyS_B}G5fxCdXD!DXDEhr-zLVl)Qd$KfO5S7XgU013ModD!JPF5(0i4%Jd$Ia;Nij+x8%Cd}QG=2oO^AukFhF1=Bxd*_(<2jM|fcjMr` zE`F)#Y91h-`AxW_HCdh&s-DT2A@XeuPVSKsXM8|Dc# z+;+k3=5|QCL*mjfvt0wIk-fEvoi;|gCk_`E-QpY!#wkzOW>yZ`FkLx8YVixs{t7Yg za69R0%$4JxBq7VWV)WD3!nX~Sn(mDaDr?5J7ydL-o2dz|3^|Cx^7;U0 zl*8dxP|jY4L@zCyh*QhnuxPSR2<_#ya0?$P8fr6Kz8y=}4{`@F^leFBEbRiA5fCW$B_#NED`AFz5_-0J^FH$#5ySl2`LuT80?a zu)74WR!42b(GslrDFQQI2`J^IHrvNTS3o+YHaxgOCfKskH2IEh0*vv>#1(sv&?&T8 z^%KE;hLFk*@Vz{2k}2bL*(S&M;U)CHJjQ)7U3BIsM{gL$@F&(r_HA%BnY!S2eKHCX zO0cGDU{i~4Q(r8FR!xw_rpsn6;3!cvwf{|R``+Q^UAh}@fT<@tD(ubXqDBB#^F-B? zNEuELN$2&YUio!NDI7DbSQ2P*@!~7?LxS_OEr%UuwMMK6vup{xV1L{i@V-3W@T`2_ zXWt=^+)f2?8Q@e!WCGi{^Y{_`-*2#GaGDfwrq=sGHZm6YZ3MxZGwHDypr`Q3ZJiQO zKB*IFkh*z@$GVTh57*EnS^_q!8@IY7A@u@g#rLK2^i?HqGv9baC7OV;VZjp-*UO4+ zHpt6(Dl7`!57;FI#wVy_yuQ$IR^e=bbSoG7+*V{WwZ|R#Y2q~L#dQf47bq-`aXZk7 zdOtJFddVj8-Zv)c%zvKl*KtromP0RAi@)RXI%3n}sh`dY*2{0Zj}J_iLBrioB-5g= z%q2kYH&OM|w<^A`W$qepCQ}#O4MSbnIA7H+b{XCTBJ(Q{&wfcvLC8ov$F~ficWMb& z;2Wkb@nb`^glIqQl4fDz`OT|!#B=+aX!J8dD&IY&r2m8FKUF4>(gI_d{b%oE_ITv3 zkKEi3T{Z65S|RB_9%k>0^y!<$VwL#AknfhgMBVTod(Yq@*G+YJ}c z9tzpDXj85rJ(K>GqH_ZRuf+$(?=H$bt*W<6PJ8EQ(!vtpzR3c=5dr`NJTTuVb;~A- z-IExAmr{;VGI<|G+VtlyH08%~23fBp!8}RmXG&gIcXJJ*PLnK1o_pmgBQt-L_Mkyo zX;i6o9B3?KRxX^Z&?v0C!O#k-6oSOqOM9$-PochzCj^WEjBv3GXWgPO#HsEQ|%zDrj%gr+9=KlE-E zT+~{^GSMArWrFl=M%-7(fHMf zNU_9;`bTA#4s66Wb7hxsUn;cVi$5EpV+^cb|#ynQov)M~ZB#6AJ-SiuNP z6=s?kOszh`0QeY8n%B^2&ZCvLxCbg7Oe2 z0AU2^v6D-7+S|b}#_*A;)P(pZyl(>16EP%Y`8qj!+G}W41y3-j^+3}Ora>(BQBI~d zx(Z*NK3$jRk5$CIQ_#qU?TKbQWFbWFQx1!S(Ojj|OAm*Fg~~ed%X7K^Y5bl;mY4zk6&HQM`=CP`$&>2jz?| zkH-_^6HnM5k9Ei(`bo&~Fa@ToEG}1C876xy-z=aHZ2c$0LKi$3%lI$zHBSrAi8v_a z=C`{-!MZcec3>vX{I0&0g5-y~p_R$8*703QebzPW8U`i(HpCSuIQ8W?I#_`0Z zcn>i{AdQhcxF`RX7aR9t8wcf;uYj`2$Vk4@mM^kW^`%m|gnmkKAv@NMD_3yJUejiK zi#qpy(_AuZv>9(=?XA~2W^Js`DI5UJh8WBr+@%R6CSkj_4-BNMbwE42%=9S_D&okX zr5)Sqe#A$qz!1MD`Q-cVO&P1T@(*g*NA;Z!*fx#+S8*JhmN~iNdNntSSzHsfl$_$p zp{+z%aM>jE=+WaCD`Y!ONm=y9hr6L5ET1v)*rS5ClbdsdM>`b8B7n(I*(EYG?msmH z+k-KOay#v1^M!$RZU$!g><`_(V9GP+p@85AeFKW$pY`;Iz*j?Tt z*u#_`I{U5=Qpl9O^Y61{blVu>!5{p{Oqx)S2aqv#WaGacww+Q;YTx%tCK(c5q?j>< zBSd*Q0gEinA{BM9C^mUAg3M|n=0^17yBaXEPJ+_|IJZul6J%X1w(5H#G*AxGAlLG= z{@1Qns}es+ASBVpEu1UL;qAAC?WVu@o20opy-D5dkF_hAkp(NSoep_HQpf5+oLLU4 z5V)*l3AF^D*(ko{j-mQ$kWc^T-&e#cy0Th9A-94*ekwPt!q)jac1rRH!i;gY^LP9y zyryLp5;J=|Ckt}wm$ORuQPtODfjBIu zV-7g~)WNVK{^9Q6;vtt>25U9=?cr>NdW?3`I9?WmitGQpbkHowQd zFX4s7&@=dTr?O*VtzPDm>S+0zpB}-%Q-!z~|LwDXb(w-o?ih7qac(pMAUmDR-*q%V zgR!=;NVT2;i;o~ImXL;i>#mkQz~VD&KQ(y}2L1joe!4&Wx=Z!$Za6oYkbc#$8@;zd zA$>O9ZBqw~PqFpI(TJ&v48;`m!KR3nfknb#oVtyga)W{Ll$kaeio-bj=-hL8DNGk! zxK)`sLd~1Da@Fqs6V3+R&syFEX_A)GyeIn%?9l0#gt557B;srBa{A5g(9L@By^{l# z^1uaLYODWOX~l`O^P~t{{Ve-lUc0c+BcCdVaLWcoAjO2%d3_pmXb=>rLCPXJk648U zKPfM2hVtvv|JHpO%F~WT=hHTH`%+Ja5lCTBCH-jzf%O?ly2N_4mv?(Q#Sz+9>6Y^4 zw+9-*w-WV6$*K&a4F}LGtW##{Ggux-_8(DV@3uLpm|>F4zwh9aY2AsExjdBP6qTVig{BWxqO#^Lf16wP9IY z`Qknm8uP_g2QLZ)-yCIqMb@F!5H#VIRmS!x+KZyi^*0clIJNWn^UhS%la|}|N5!?QS zgwaMz4^d&Lu6(;8G*!sWh`_oYW7TZ|^2>rNr<|;WqG9c$(x-M{==+x)=fTH8oZB1w z@51sRZ?%2PGGRT>bl{>LJoLAB$xe=nVlz-N?#4U|5g~gwz64zzFfI5}e8A`~MYkBU zznO=Z;tZGK8Saa0E3RzLTOdUMj{V&6ndJM_{|Y>^&3iLd&2GzC`}V$E&$3&mql1US z@w&9qa6%TT+>~5R*gcmzw*ap9SyC-4b;(YSMshMP<6L+$3aiK^5|dhH>gZ`6S3EB2k#q7;-cA6^ld zXJ3nG=4SL$p^<*{tSb6gNwkpA;x^=|xxv?N8TGnvuJLh`qQc}+h+KwbYF7x)MJ2ZK zm%f2TH@hlKP~$$&4hmn~^#*klX5R$DJCzszSqj8mK_ID7`{v?3Cvyf;o46EPXOQj^ zhWa+(ahlL5DZ?;a8bRbb8~4!Hohvcd1a86)wjSS+FoW147?zgUPM|vwC4CN7e?^@Ob1~sRejns4)nFAT@%pp}%;TolSS-$jN^ti%8N7dP2;w2=2@QS z4EHRNQ?QJ2nX*8c8L?W;k%-cFyLcKo4}x#j>uR#^2wsHuRQ~I@?c8hyBeQ&I7A`bC z;SRBud$^Kz>VyRile<8(Ax!TrovrDrH#+7s{%folH+{c+Qc7 zySUu3TD2}?Dv4lAawYl?x7Tg!r_sq?cf!r(lm8pd797offepVCaKAqTb|6W=@D zK#}=^e^ayE=<7iJ(2cl>b^+KCClw`z@%2EbAX?bH>hGcex2X!`zQ8!5OxHWGG-aIc z{jZE2J4-L#tFEGcP&pa*+6CKsoe&gmG_RW168K4eWt`}D{k)-zTyxBkVCB|yB)yV7V>nJ?Kb^BCnTFs6Jt)A=2W0A$ zBx8>F%QSrsUh)$#iR;v6CO@-CQ$6a2qWW#ySO^H8kquwQlw-;bo?L{SyjY$Sem9YA zUfABoI9uQSULcO;r4)Ao(!2Yo0R-EVJus;JO(xS8fLjhsi)|6;OdzZ?41MrQzY;Wo=KP^ka8JT7Nt9_oH`S0dw!HvBm#>bd678zs8%N<#DB(l6 z_Dt)XbUByo80MBU{mj#AOAb1!E)QaS1eEvlTqU!7<+9*0H;KEir$XdP&%4h{Wzpi&VR=3UwqYYhS;_7PMfF}FIm5&D-yA~KQZUh%ai zdgN9jc+IihfhU=YQ}%ZySCakc>hj+^W}=b42Apt7Br%D{c{gVxfS(@}Ryv4%l2_;VmrSx;+M~pGp`ce6#Et5$9Bx^x zQ=}KFYt(04`yuZ#+Y{FUa;^Mz(+KWsJE|lF8hvQL`$kGxI))`li@DNK z5z}w~Mx9DzKabHQR?y7bk5Wy09MXZh6AE@rt?ia0&ZG!s8%qYQLMCJ|B4%3^4RdaR zZPS5>BUUtv_6TxBQoSf14pcqH1^wp{v=toQBuR@n%o%=nn1VT2##r);_7sB&v!gjB zlYTg(8okv#uw(n`C8H5Fb*E}vOhEuO=y;kEGJ}iU6FRx}$Igx|4Zr%4C0<%LL~-h# z0B1^3@?G%8pk`9A{m%+`4w>m$NT<@%Gp>8Bw(NSz5ZNLkrMru-iF}B96tkIv69uox zy%G#GJ(5^BFT9hvA~a9^cBKTRjeoks&Lr(gLlKl}x0c}6#5R?^lh6Snrb~;`+{a1O z9^jFPSq9d*K&{zlrz-E=)>Jdm<_r14tRZ4|EBM|}CoANTx1g^o86}LFz6BU6*|W4Z z{J|{o?`%1Wms&!G*{er;{xlfp>;#Vf=99DP=8rN3s;&ed zRJfy#69{j56=q6qV?=WyH`;2MSPSPM;BCTT-dcm3Xr^UO4BWP3J$0A7fXi5Ewce&S z?If+^EyOC8;V&4KjsOO%?B#)i<9pilJaz6-O>>nJWegS0i^KN#sQCMsv&u%J z7PDzjs0NQo`Hc>xT^}r5jAYr)9~1KiHbs3XqKmWD$i#G!E#atp zn_b;;=4uEJoo2o5C^s(LVG0Ru3FMTAKRXz$Kr*m8YpoNIx70Ipj))sx;)wz=f?R38 zbBuN7KF8hVecV>I< z;lLLIL5;w72c^jVEH(ZBaLvDyNfY2;aqK}T&7Z`1Y+__%rcRafa|8J1S(CzJMZ zYdK?Rd`chOYZ=Z+__@fq;D<4)H`Xm=H+O@@s8eZLD=^mylgkyKuSZEP1pe^SNKV=I zaFo5%SngFazgA*35CN=9m>YLJV%KHz2v94)90usMA6rx7N zUE5~Xo{tZbcEz8zpTm0~?#pv(+5%((gCZ~m%A_nFW#^A7^XC~e^x}^TLJ!9%6nOFu z9Fp;4W$8bbgQRRb2sNtCyOdFCDsjGz-;FxLU8r8!iUV^CwIesrZ3wQIf${jyL*6r* zn@vHrdTZm?4^qj5*@8ew)O7wvmqmVCl3W=%Hi#@|#@_lE$OlWe+H`kSv%~As_IPH5 zIUr4yVxBniLD>RJ`&q$8w{4v=Ed8B#8cdPe!0FqhFt#?i$NfJ8#?6{O0VV7bR_0Id zm2b$4TvdKHqg9m=%?{aECST=A{r_SMO|iob5J@bu!6rjm+5IbdoH3WXD-Y3gZOD); zI`)OBAqNd}(XzPI_EoV=M=h0oPoP;u7mk#k+?~3wc}4Q&oU#)*V|D@z)3$LK!1U9E z@VMN_r|GY*<8;3>`}An@=$5#wJLAjGzn}&Ijr$1M{IHge=by4=lOPCaHd>+94|GT+NLoIXAN4zHtiq50mop(tDZ0F8040@%0d#k zNelF$1_5Tfp)wniBUUKhzG@o1aL3`Z950XXZ8r1XZAxU<6!%jB^J@W11U>A1Bju=< zEp@@_dl=u~!n&N461tENI~H+UIf2fd8{~zFqBzvjJfIs)rWy_wVB~Z%fh)nVm5Z^W z1g#{PB{aeTK{Hk$XxHA-e=8W*RN#l|PpmQ#3L+Cun@wT?@SQ9G`)fRE#$}X+)f@-a z928-zhN&7MtSsyI!EHPAyq`6Uz&49bdtm`I%%Bm^$p~FtFK#KLT*C~WPas$`)F`^S zXQGypZW!7m-ljM(r@B+KXhnAMmEUuiJ;%F#(rOzudGXh0<4p1)tg)cjv(kP}rV!{M z%Q&ODgTRO>Un4q|eq&kmN*9oh>Am2yY>wgJ-r4z>2`XKNweHVBQx*3hHg7)W?kD#o z@E+kapVc(^9E-EzImwaqqK>hyRz$e4e;xmZzrsI%R+` z=ney4q209!jD#|$6tE;O+q*)9b2@#WVBImIv&6ONZ2YX4*$-Ycm9MP%jn(Hd>JvE$ z(g-;#@s3*Q5Lm~#=8e-i#f3`tdFtdubzo466UgK~+XI?g7%sh&`9MM&BH0C>j6}3) z5Zn6}CvbaN*UxEiiip)s@{8Z$4wt1gFJ{r^N9gE4G_NnS$pLjT#to7=j0M06J2668 z`6+a|ooOD^Xdr8?p?`8Rs*?^qlZ#`~xb%KMct*bsMWzQT1xTaR(I^F+8&b^zH!eyZ z>Ap(T6um3Qqu+_k(?TEfrPz)~EQguUWsbnG-s# z%1sw4Mi3i;*McvPAM^LN7G|N%*}B`_>@-M6Ic#n&!{3kI_L*PS&rU0t0(oOh2L(|L zuwh%urIho7lUtQ9;)8|5M@&D7mM&1$Gx4QhU`PD)rT6~?U*}*{{y5sun~p~6f!mI~ z)`A^Uw3;_$DoS#Q!0E`T{G<1u_PsvbYJQStL>EX5`~ChAoMkTGA8m!_x9So$+^@%G<6|j&NV}2d?LoDq{5eH=J@`PKrCy0jGcIZV?xr$9UH&n2a|4gKPLp z5@{LI_eLG9Cb<+F!R>=kU?5Pv$8FL%42YvidGyU^kc~VixPkGZ7iMw*YI6%ZXdjHW z9MWuYte01cyi`?Z&7p#SuYH`A0?942#=xFN?23q);pF>aYtS4RK5mD7y46~QXm`SG zyXNwiSW68N+D0p+ITqZ*5M{)t07cLG@AOWsP8-+{9l9UhMz=EEJ(;e`xeDByuRCbY zq8`gamZv==lb$jZgdUH~OR$R8tjO%Tqztw{&1`rVvqsGv&vLxTYVzPw581*wcNdO8kYk(f9>QB2@TDVp-P556bQAJ=hF#CBrzF%h#w}K?% zl6f^582%jQ?QW}Jnvi1P^I_z|ebJp?dB-%pOHexcIe7p6GMjJaF8BwPBP(8>DLue2 zuJ^7mTFy@C2THV5%!J?Sjgf4f6gqd#tifrI|Bs3i7p-FJl9R^`c@o)47hn~MV3Mt) zGt4^uj*op{$r=}UUXW3}ls?~_nmY2DLkx4a0Bbc?Dz0lb{q+G6T`qIpPM z)rcm<3~h;{w-b=#XI$dq`U=YStD;1mf?^Y`<$e7lESM3{Siwj!IF29uhH+gDoVNRj zJt@(7dIcjCO_C73a$8x5@OLluHa9E!&O2@9t1<1W^K9)8b(WyMwjpp9)^Nc!M>b_& z+C$75;2N`fZUw!dfW@Z%Rq$aE2I)7r25&Rt4NUQ^Sz+Hq_8v?UV%7R@s1hj#%bq03xnS#s~hJse4($3 z0m4EAR>aWD@`(fl?BzMf_I=IJ%Sn3@7~UoNsQE)7DQPN#uM(k^Bet5Uv??wippP;z zlM>^Rx0u7pgMLGLSrH}GweQ|mk+X~NZ=*5LrznbT`=RVWVzhJLZM9y-A_m2;(I0e+ zXAkj6QhDmeYD<8?cinzf?DNZ51(~ezmvzE89^=XF2w18m(q3wvE-4z==ZH2tbg>tP zNg&lQm##8K6Q4*^Huv6vZV*4ALs275KpSKVh1Gdm(Z}Anh{%Y2P;6(1!5cL!rP5!VH+DZ5c0`z1JRho1w!Snj-e0Hukg^}#&e{htLo2_Ao6MuH!sD2Qb4*r zt5--ZP={`UV?^qx(W4=pYH-j54kGmktl92s*1Ll87#g&nn~jijRl+p_m}4RS{sekT z_(8T-gT)ODPQf70G;phC!yw4!O9}T}P?pgnrzw1${j>`9Zu>HZ8SjmIq|D1RP}c1P z_*o?l+lNF?Y^vt?RSvSBMd$#rzR^6N6zD}}3Yu3W5lKE6K;J@O#|J_(=$g3IJ*4Ls zB@tNcOfoNDXa9D%LFPz|q)J@dW6E|;k;Fh5#Mv=c{+3nL1|pq2C&`woBl-5-Q%>6* z9Xdj8&t6(1FVN){R!Q_*G~qM^=g4zWn_4^Zl=UjEDmQK5oPknSsFTzf{*Mgiji#|M z4;I}1#YB3}RtHE#1+E4tQos*H(Hbt*sR-Hqw@bB!9__ zf|0zNutGpj>mD?0_Yow;rhVpw=t(5+|6#Tn#)polYr)5a{oJT*+Y#i&y#qGZm0yntoT?ma;Fyr9C*AZe02rK{l%hXlad_+vmtzKnNUHaTxoF=QJ zCnZ|UdE;CB0(bj~uC2iJr)@cwQqNpTk-AHP0YJgBXH^oivP-=`Jd3RS@gcbB*DOp?kSTd#O zR`iBPuPY~pH(Jj1si*yH1_<|?;~5#U`A#F^I>%ruWGNzN#9C**q&SeC;C1bt-;tpS zP6zK2b4V@BIk6eu%772%Q?!=AU9r2=Pd{F*K26={%&dwKxn9JHYKA<%#P9h1>jVr;27(@NlGcc^_|-TiWF6!F-XKc;U7%`$ai zv+YJ#e4G3Y_Ns;G=Y9Q(pHo|Td$P}Xmb0!_Z?2JtU-K8Mt`MW6Fl`e8^jh{!T`(Ls z9ymI}JU5`@FiSI|AR_Vw+w^PE>_@}BTWb{5*=(?qsed^h)A(x|aUcdut;&<%r1V+C zs15sw10Cot0!2Vsukh7U+mQO=+dp2ncURNa{^m5`hmH2jPnJso2@^z<Mnf{Oyjw%=QbDpv{^$C2e@ZsAH)+p@ePhUIx4r(hW{4>&> zs;nvbV&BU#8>Ws4j<}l{IQLAzUXtQa5jZ50VMUTJkw+o0HDJ#c>*YEjkI!FO=+cy6*xqqJwd5%K4GKn z%yl&QZbtEbcQ2`LMQ7POG9wC95+NuY1I$JYG^mTk@BbL)Wzts`mNtPJ$)dlYeDv&& z)b{u(hA2X91PO@TYl93xS|QPah%IdP3vQ^EyJll#{`9Aq<{_(mU{D&dDT>FKz>461 z%W^egB+kz34E|_Yf*}J-1wulRy7w9)q!C+N_#)uVx5?&f#e>0*Q`6W7rd-y4swbC4 zo1f5m(aS6_XSkQzcYSbyFWh~2+y-w&o8Q8qEp$J|~toyc-Oa(<^cB~bqIG-6ThOa|_PwIvh?o%*S(^VA? z%L+M|ugTZUUx3f$@IHPvb?@f)sVD*ZD8Pk14&y9bfbHzH;W@(C;QT1N=O7m1R3|u} z*YL!K-|gD8L;<8vE|n_9j6_FGB*?axxX)>PtHoPX55Ew-pa!g` zevI(>CqAbUNg+I$CqA1a`fUi-?7VR2HjfwRqZuc1glaQrv+!1q^~%VhD4Ygb$C_x5 zMx{^TEf;P7A|mIbI*5^fRJxp6PU~sA%ZsSPLK~c-l;l>t+Xu=5_uPQTfYbOD^s@~qbQ|H)6+FqZ+&HAO<7Ez174-wEP-OX)gi0Pb z`ITW6eeECuxxf#^75$F*Rj)!k`js(8t;z{Tyu_EwcTpH{nchLNPF`2gc$;czL{!8e z8%BUqPZq7dlC$Bu<%n?jP#mq=ZP;$b`vV4cpx-+@#ayIr7X!GjiAsLSd{=roy-v$X(mqXo{E#A>+*2OK69oKP(W#g;cn`1VGE3ERQxO56P2-nDOPQyJD`z99 zT^i*?*4pfenci+OozAg0NaDXKjn3EcIhw8(y7&%|5{I?~DR8I6D8R$_?go5iXJ!4JXCd2in4M$JG=k86up;L- z3bgW{T6U%77xJp5Bv7EbN>x;eGZl(rO%_#LNDxYErcmUfKUB41Z{XlZX-GZu30#dGt&X z^(OmiiPMw-X1uW;kLWQMH_XP}yRhOEPzcFVR~c5+Lop5)Gx)E>P@_^R6^W4ONOJ65 z8umw%X!W$W!75Wh)^_v`@6+d!$ZTl1)1O6U6+~3Y0aV1Lj&YoerA)2Q5-Gv07*#Gr z-7&_Uh2=YX_S4_8dd+BC^IQk7VIg$4o#MDagarc+rY?~QiTN4$Yir1>UD3tJaWks! z^*MD>5<1TRmPxVQL|V`Iu>_-r@iGEp(OCdO24WNWz>=qGm<*HqJzrp)hKP z@-#}EN9A94m0wd{NK|k`G=J#WPgl<)nB1$O7V>Py>RDDN8N3)uaDdFb%5-4I2oaH! zPuGlzmJ>WOH?24e!7@f|qp4UFjer7pclL{deUb4X?wLBBUmybSCa8VI@O${E|2d#- z&gV$^5$_PtLstt#05|FM001odL7T8gfdT{%|No`32*7_40sMdf00RL3%|-0p@R_z; z=;lT~TfJorXLCBeT|A z@PXH2w}!GMKEI~+DD+$0#)EAjUv!omEfXMA#Wt+(6vN2+699N+T0}W!9C4o{GYw%3 zV`!)*5?t|AZLnkx&fmn1Z4$W_q>T|*;Zk_c6BEzUOTtabJSOFH>8vZ)sCXonhvUYt zGCG2=xtdSC75Upu%!r|Mbivx6atiRJtz0Y9a+Zt+ODB4*r@7&4iRPclp=>eYM!D+; z`^(AcACYibl+r!NVy}M*fW0Rk@Wh&Ryfy8Gq6Rr+ml~;PgZ$~;E9UV14(&}b_(h?Q zFb#u7@IDZ=N->{eLF`kh>0S*}Nx70h*?ESKnJH=B#uQKu%?M&Dy1ma`&#|vN{MWV}0r#Bzhs(oG z7itkB`FbWV0eN*NRr8l^9mIsn+;pb+VLGc6`xzO*H{Eg=4~$IP>Sb}3Sb8*tFMF~J z@kTqMT~cUT*FF%B`Q3HIgVE?aI)YN7MS2SahqDellRf=@orKE!35Fm8i@nc@eF z6Lu@YVvn>l$`}gX3#%O1`y$~d+fQ%OKgQ-=~yXg zF&S@y+HqJJFD% zwSfHEA8U`Ut7#XX)2q%ih=G408cjN{;RfS{6W6`?5D5J;&~8J8A|ZdKl{{ zmxa^`KLEOW+yE%!_3-!0qdBvWy(RDhO_BWZtOB3`Ge_-eU}ak4xo$8GTxUHuarQHt zcX&vrvEw$Nhb^M9)rYNoMkAb&+jd#ygmGTZ!Gr^Mu%rwen1CVZ*}&jJ!jt*s0*S!m zJoln1)9j)6npQDlCo}NlyGtY{gyJXh-O?v3-oU;oP;xaG9Kr>ocLv) z{%r-;))Z(3G#xeeMciXrwR2yGkj>=d99lFB9{XILtP`k&5&fS(gHKCu!9Erx8mtrv zGP0%J(3uQou0N7YYy|jt12f{bllJNv&xn6QSam6Hg5D-f?LvTE4l^(u4O1dv+|NDI$ zBiP9`LmCaS;0(<0Ju#LHWvD=IFVF9HBdd12u!8csA3~p25;0p~n>%Uh`HWemJ34Iy zNm0|=hRvBLL(i)rCK^QSyL+3`@9mPx9TS#e3y4p)g2+=cI z*TvAjfgD^dEmH!#grc?Y*%~IU>Iw(`CG%pbOMno+j`Ro_zwrl>v7SV;$p>E-Bm;Al zyn~G?!n;qrQtI(1XEV+G^Kk*rX>&cBEACE=Ua^cBS^X$qHJHC9lh$Ui&|n}~1s~@2 zb6*Si>9G9uI%PBcf|*=pI2?nGx)_LHz-rqQ&!rKIGC*UB@DA6BqjICZsre0tEFs#! zbsi}j?b3CFVW;WiMB=x+^*@Ld4rpb>pI$?{mV>fUIG3losVkDB?6`D8t~q_6UGHG8 z*bx~FIhjM*n#M*9vQUeH){%%L9inFes$3lvIxl^}F$RGtJ^5#Su^4ADyKuKUbuPm* zrh6MDwS~I`!pLvxYsv7TX(Aqwk;d?1n{`Ax0^imDBlNNUIDNq*oYpl|opGOm#TS{T zaU=4pVGS4K8)Vswj1$s@lZVTq)m_OIs`pr3m}ytlvVzUB>6f$X&uidXJL}XtSdbt@ z`SZdPcd-?oWzogDC@1Y3Z|hZ%DWkckXSJCh9ThR8iiAL)T<}u-wtZ1? z3t;zXkzhYFI*JKoE2V7oW ze!1S{w5C=Daa&%YIRC!eVhCtB<4uF%R>I!s@yO!p3gs!_Z%E& zLO5$=xULW(FL#*K9u8w>R-Kj~mExFC&{psK%p?|!*8Fa7a88abt>FY``ql{czGR~; z-eBHlwX!pUT6&MFUu#x;D?>ZNoPI&$MUZH&{oMo3*M1m>%Y3)siO)Oslt&((meh@> zk$B|bd*9~mn9COjum{vVl|pAnSHzidV!35(8zuF@x!_?X-m8EC(C)speKCA=F{r@1 z(11Wa$x{SlTigC1#T&0cIGlG!HD$B5B2e!=qMQfT)~7DqJ7p(;=o6j1JYQ&F_z>=F z5kP%1JN+!tBK@mITm11rzn^h(Ocix`tBK&X53McLc$|6_J@eas@=<(YXF-1eU@Sq%Nx5wqjwsbjckw3kz}w*W(N5^K~94AG=%E8 z4@-tVN!#H^TuvDmnxJj5g{{GcpkIbzlHVh%-#_cr7x(R1}~* zd)h;UdJl_jP-tOeGcfK0{DmteX&uzG;H?eU=RTy6@Wc?yrSC@Yo~X}!0$0b<1{4=} zj;A_OPYZk}e2S$|ZEt*7J%j~<naG8Jol|o1Ca0b;zG0;xR;SK?SSr`huhqwnnkp%{2*3)F$aCKFs^+yv z$~({f>SVU8%T9;fvmQ6pcC?|)H9QdEc)1m9s)z4LC1Uu|DH=EnVc=m`!J_l#b-nHe zv=akYH?eK_=Yx_S~n3ulZ!WnG3PrE@6HVJ`F+T$ zdv%*5r-5)ey_xo^713f1Pb!^Btq`^ZrvApWgPd4xLk36YR*zceF(Dw9ciX`hn;6)RdW(Ooc?V$F3XZb8(rK9}cL%%fEmHGJPIA8K9 z-S2D&bI5Tr3o}uT{NV+xMgVDwdYzn1K{XjhWtWJiX?oje%o*)`LiYa-+Qq@Qj*)_z zk37|e@`{<@J!pSVkfxdqsXCQD@l@YY#x?3p*4q9P$N*WhBTai1#}lq_>p-4JR+*EC zmB$38$vt8oVmr9PO!&u|l(Y%ZjN;CMg|5u^;Kk<3lQ{q7UU_0|^a}8HTk8eE&(U=8 z@M{KXmc6u5Jq{TFI6%k0pT>2FqPE$T085fCfv~Fb9stjVc;|F?swr&MUGuB=DO-7@ zoMsDmTT`R(RM9DO_VYnHJdvie2pZ%G7sX=ocb$cR0vOk;daf!UW}r>F=A^h>M7Rek zM!>C@6YoFktW5{M2t@%)#A^5p{Y_g4M4m$BQj~ju0_iGx`3Y0{r|$97|1wsJ2US#3 z{Z-lB1Pp|+%7s~G&Kmc&R7t_;O7>@Y8{GNy%<--+%d{5?=sdQ;Gt+Rj^wH&|t3)mH z_^o#i>sxpdAM}y_fBDIXL^kqiq#fpvvUxf)!urphYlC3anf8a)WuO+`eAB&!f27*1 zCWBJzEf-qd81ShW3v z#hMp(FZdKhXaUrh^BpDr*Bezs2;vWRXi&hSe*{X8M+GB&Ncg)_zv>x7{+^%%&YQvr z-o&Tx)%E$nlb$rK|L2!$4HQAtNA zUQpFUF660?E7nTu(R{pGNg0F5JKh?Wyj<-+BMaj?U94U=YjFFVMax+_tq^b`LO&?b zDg{Ucg9O1Dk+n$w?_K*+>5Mw_tvFPZ4hTFfFjTHd&;Eegl30aLcyVu4$jYAltxgLI z{ab2=H2~v7^8f4IX3_TZIERzszWrD@NJU&%Po$q4Uz|w7Nry!%JGwc%a_yE{v+hzV z#=9M2!-1>j3eX5LDZ+wl{6-pb&LSY?+R1+AFq*Ss_Z1tcGEKOeDKK7jO4-F*Mb>ko zdZD1XO{u!|)2AtZ2_9h;hH>=msYv~^o>q-RK~P5t{*KS}kom7o^E3~;wifCLOD#yv zn%yD=dEgPxPkGO$1pCuHih?%l-qCQ@!dqs29;0Sdts`wl6|ex@4@yhIp|1>SdJx$N z2t+{lKghVHi6P5?TXX5vfXPG=Hk0u7sd~fmLh+)gU`SBt@foUC%1e~rm6v{xr`|N zLXBfQa{YdXGqivD1<$Acblyv;2AsB{knGe zS1zk}|F6sLu;L~Wng<$m;c67>5sDE+_v+{1MACjiMMf|f0{l`%~@zyg3F zE3XlUL!9K=_T2KXoFXJzcwRvsfO*&8S)!RXvAfS?u?=PAoTp}OS(x9X{PH1i|1u$u zwvGbH#5P@R;6_{n>B3*~0)en5UvuhO^_Q+N-vi#u-;{Dyl_zkizpE-fShvB?=`BQ@ zG`iOTt)xpap!1EdB#O zkEilS;GDPxMxxfvdly0J`wjA$`3V;Js}`dTc&06T?yZa7Hp$w^lWYGrdSx*dLQ!Ih z`FZsY`SsDRvYgy?yfe671K{C0VhgdvFB7W8PsldmRJyloS@sp{5|G|jh?_}8aSPBHPesa~jN&^Vs*XC)2*Hlxsq9@pOd88tUD!je;S zH{?e%l954Zw`uL|SXtA=o1DsJeY6~KxbEc|y8f8hOhmWJ3lx2E}&{gq$)LDZ!ixP7e8PnuyaUGoeI@C}Sf1E~c+WCO5o4>N!R zWkRBDly_P6`Ov^wp?bn$r>a;^Qd@2re^@nEWY$^d&T^(OgNN(d0E!Feh6^NuHR=&LN-Ql6Rgp9<( zf}UofDqbcx<&z%KZi#@b5S;slf1MIdN13FN*S*tRTfvQ_+DrwC<~!;W3AR#oypMTl z)bxf*YjYynd8JyhbR2c^lIMzr(000BQuxQ8J4p>w~*^ z^nfCzWCq*f0>U3a2 zl>hlho4*qI%n?)fCOArzhk+}hqi8}2(DIR)Uq4Or{Nyd{d`QC|;F zaL*->V8l+7rUsRs)CdQn|I6S_Fb^SKn=~MbKu1_(a{x9moCE0Fj0wRgsh#o~hB1J{s`7^1+rU7Jv-#WDd_%&6Lg&t`fB< zE>u3|PZ16&`z(QzN}OFFh|i#yq(<^Lhs7e@^QdoT^B{y_^O_pDbvteSHR(*PfP56bx(k%*IEiXw`e3|i9Y0}Ku*`Z)O7Ah4U=aCQ*TD7 z(EAC9r`b)Cxl>uroH{VV@*|S+w7h9gZF*N|7dV<>jmp|3tz-ltlvP#FGt1h5fo->o zh#?lD>jBuIe}&|e!@1dv>K4$*t>B}{dRu6yyYz(>YXfDnk5 zqINCWWRfl5&v@GVen|~*= z8ORwp`Dv$SIhjc20PbCxW-uKc@gCnu*c(JNNB(`kHSj!E8Ho z8LKr9RIyq{*rzLQ@NV-gBqI~5n)m4&O{f(?gZwUor6SDPi2LX3svOCIy*oG!wL^`q z$r4Ouo8D5J1@ssw`^T)kqLYP3LfL^aYg27|1MA)%v>3hAye}pKiIhR;2qd3ne$tk#F)dxp%aDwXP(~r zk-!&(#y%yFQ805sINZHXQ#Zg3*!t>56cGx{mMb@$6mjnC%X}KdQ#)M8{XvVMJd!Y; znbmJV-jIR__W>0u;VAnvjmeG&Z;$#OK8dMTpAXv1KT-Z9R|g)U>ibP|q1pG}Q>~Ia z0M9U~<1HXM(N9AIBA8D*@gS4tr@vxYuiM&K9GeVO1wH9>zbGPxbOjaP1nY(itY031 z-PTSNa;+tZ9{0O9`q0`;ynj3(exm+dzkzEYuoJtqDZPly2NghBi1Kw-f75GcOWBax zhxFGmXcW|DSboGhM~c3LMJJ09COe$)|E52$5! zVc4c8FH?8oPze8q)Jt}62EuS7Gv3WGvTGA??)+DTupC?&M{H7>mF`oC$bM>uH{d6#_NB*4t9WIfo}|f6>lLPrujoq&?bg2 zyl~-c02x93vQe+EMO`jaaP=FS^c{dMNzlP{!h&R~ttRkg|L7A*S0?0O6;dVQ<#1(s z6`dDgMK%Ulb+a}_skj?bt?Al;d?qV>j#_S)7y-Wq1WknOV(jXa=U!bzn1FBp=C1TR z$9*46r7vyCLV)n&=kG#h!=Y%DVVYDm){($diN`7H4G>}6=iT&oc-}I*j^Rco#NHBN zK>4*D_o5qna?*5w%L#ig8xAVc<;ZJ&=_@d<0RFsH1Fu_HZx@bNo%y~}p`rS#c22N3^i&85cM-#{pZ1=0k)A=8{Bqm5UPOE zX-hDl`8YzvLHC7HY$53Mc6Vuv8m#U?&y3FuioZg0^Bx!}?IMLP@qB+w77{;NIWj5} z%d!Q?4D%BqN0ia{p%*dEWF`2?-Uv~5)TU%9r97e~ZR!!argjWY*Nk@hBWY@A1RJDy z4t5ar-NCe{UcierwsFktu;W;EVO?fYdhm(p!p=nfsD=f*b9$TgbV##V1x5dIVv

      xG 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

      brSd>i2Z0BtRl!w+%* zU?bFjjE^b~hG%8$uXemUE(OQpZH60lWxtnd3LPu@6UKY=e}~86H^keCmjj;<9xpx* z$%nI`wXyo6)ol8o4_9t{9v1j-#4PsT{{WLJ3G^JaKhO@tb?m$z!^A&YZN%Ymc&ncK z^yS6hEIiZ`uRo%_q5l9{aCrTQ`1$XbW6d+)Zzg+CN7Fx{y|Q1aw(ZylYvOUZ+%4A) z`2Bn@78`py2r&Nu*{@=`a5z4$;W^v$;`VM9YsAMtWseiTh3BBvZ~p*gTqnC9W5=_P zY1=IOKN&IN<=3|xY;&OLm(;$!E%<)8?44Wq`!5lHW*kZi8)y4F>{lMi!u5Xy`+pOQ zz~FAVW5o7+Jh%ETnhdS~0POd%OYHm}2N90h#o}=?-8(*fUu(Ai07d1XvAlkY@#cEZ zg~#ln+Vaaj&BR)s!#aQ@ZC@MHGv&-hSKT0hyBW}E8n$H45#>mL34K1H`1KCc#DKB^4j{{XY^Rm~`n>3H7PEOzlwT21VojsDq(WZ{=+;%$cy zZmhTEjy?1o?W6P)voY8(;C7a~HxrHY_?HKV#pkv;aPjE*XeC_#07Lk9k67?9*`Hi- zH~CY6pWfSU9z9eWwBP9W1^)n3Z;nISGUDf!Jvni@{5{kgH5aJ+0Q%={%fuW!d2u=8 zj}Nae!$DOK{h$7g&*5-ANyIN%@$>p!(=Im~+Hp6>#nnLx-}YtdCp$(StL+VC#_bvL z{{XtjKW&%Ghn9lE*ZKkK%a^OWAFFX6T5$H^?ZQlWzi$t3z4LWYX9M&v^m=ax4^rY? zyR>m|!wz}z`mw{q{#ppZ`T^<(^260UQR8vA+wpcxHsX6+w)#9jc7uFx(0;`q9v^4z z?Ql5UO#62J%jtcm7k=!hE$_al^(^mP@cmK1bGPxnkAaHuGyAyjy#;l<^hbkf?09ha zj(VdmCj3qrXT#5*(P#U#6&j_z5^>#GZ^C7yxF>5z+I7+?%GzU(3C5fkQNNeTK{wI# zdjl5v<7bxC)G4kU$0N3^vM&&aLtv_10Nx$OeOYy9l?9fj{T=KrdiM^`#vad>dGWSf zczi!;p4oUPA-DPs`YqAfzel)(mizGdymZ?=`na8=9rNnX#X-@~K)w2(u>Sz~zYnqT zTKZoP7l=GB+r`5ly+IpOevS54J$a80ik+9o#N%wcaJYWcJ-6^sR8Ic@X5OKB1_}n~tbHj<12TeerP$(1% z1pl5O9~Xfx&UpmO-oIech5**-N;>G;$H$K13IUm6crZ;c0>zBCSB z8Yr85XoBOveFwb0G*g$xfy?7S**-K5Um6E5jRTj)fy?7T=E?D@k9mA(uM=&%`B5J7 h_|#MO(0R+_LGLe(Q04Kc9KJLja`@0Wd}uPD|JfcaX?*|y diff --git a/services/web/public/img/feature-page/feat-discuss.mp4 b/services/web/public/img/feature-page/feat-discuss.mp4 deleted file mode 100644 index bdb5c952b6c1cd08b0c0fc473c4632469027d4d1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 214985 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 I{>w`L4}WhUZ~y=R diff --git a/services/web/public/img/feature-page/feat-todos-poster.jpg b/services/web/public/img/feature-page/feat-todos-poster.jpg deleted file mode 100644 index f221dba570f84356228dc47530474bffceedcb6b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 94730 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�Qu zo76&OBP;UOMKg9;9W!!JAny;_Y9H)=*e^Dqsjm~LL_3>{vqGbaL#iza6uD7<=EZp0 z6MEN5Bod4}bM5)t68;FCrtv83%f}H|7vCO!uc=OrC_zE_`S!4cx#%RNM3{@5s>G;1MVfCw zC9fPpF_tSnFxdxL6!mi?mS4aikEkuQr8FM#aL85Z_TgiOI=MN# z?X?a$RU#=EBqmjvQiz2#;Jt5I6F*+w8^vmCu|H_}C%J%F{Oxm9H@%#M?ObgJkrBgB zr$l^TnWvt~)8hgvpjyfeveL1F=5K2U3#aBdv)-jQ#+q^^AvVt^lR{ZtOGB#kOi|5R zT<{WGX?!5csF<>q zX$wQdd0H_BMGl*k>=vM~*q(kN>u#rS>D$l*a*;zUaafv%UcH06R0}tihpsMCF2JoH zg{bOo<(c-Sk=J?dW@A)_^g?kke0uMcbb<2Z?Bh#)tT~^gGz+kqc4=K{mW%$X$r&-y zDpFq3cJwY4*#B7q7x>Kb@fd6oBzGLFTESDQF3KTl5P6xVmvnw`s@VXK!xk7}I&YIVK9nd9Zi`TyvW`W&yGy(aR&z-|K9XX+P+& zk`y{c6+~BCwwt{i≫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

    1. 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

      2XY9Ff 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 O#?YRR7a&$%x%)qvFcZ1} diff --git a/services/web/public/img/feature-page/pamela-marcum.jpg b/services/web/public/img/feature-page/pamela-marcum.jpg deleted file mode 100644 index 66741607d1e19c2f8e227c57c1e941656c29e6b6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 53721 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 b`oF90RQ&a)N_?Fu Date: Thu, 15 May 2025 11:05:32 +0100 Subject: [PATCH 117/194] Merge pull request #25617 from overleaf/dp-projec-owner-email-overflow Truncate long project owner names in project dashboard GitOrigin-RevId: a3c98b359dc880a3f487c1a6de9a2fe4bb4913c1 --- .../frontend/stylesheets/bootstrap-5/pages/project-list.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/services/web/frontend/stylesheets/bootstrap-5/pages/project-list.scss b/services/web/frontend/stylesheets/bootstrap-5/pages/project-list.scss index 6f93dbf454..83b6fbd28a 100644 --- a/services/web/frontend/stylesheets/bootstrap-5/pages/project-list.scss +++ b/services/web/frontend/stylesheets/bootstrap-5/pages/project-list.scss @@ -384,6 +384,8 @@ ul.project-list-filters { .dash-cell-owner { width: 20%; + overflow: hidden; + text-overflow: ellipsis; } .dash-cell-date { From b56556f37b5b6521198301130c7cadb43047f33d Mon Sep 17 00:00:00 2001 From: David <33458145+davidmcpowell@users.noreply.github.com> Date: Thu, 15 May 2025 11:05:51 +0100 Subject: [PATCH 118/194] Merge pull request #25547 from overleaf/dp-remove-reviewer-role-flag-frontend Clean up reviewer-role feature flag from frontend GitOrigin-RevId: 0cac59be58b0350c24f57d3e63898246b2bd6881 --- .../web/frontend/extracted-translations.json | 17 --- .../components/review-panel-container.tsx | 10 +- .../components/review-panel-header.tsx | 21 +-- .../review-panel-resolved-threads-button.tsx | 7 +- ...review-panel-track-changes-menu-button.tsx | 60 -------- .../review-panel-track-changes-menu.tsx | 92 ------------ .../components/review-tooltip-menu.tsx | 14 +- .../components/track-changes-on-widget.tsx | 36 ----- .../upgrade-track-changes-modal-legacy.tsx | 109 -------------- .../context/track-changes-state-context.tsx | 48 ++---- .../hooks/use-review-panel-styles.ts | 5 +- .../review-panel-new/utils/position-items.ts | 3 +- .../components/add-collaborators.jsx | 23 +-- .../components/edit-member.tsx | 50 ++----- services/web/frontend/js/utils/meta.ts | 1 - .../pages/editor/review-panel-new.scss | 139 ------------------ services/web/locales/en.json | 16 -- .../review-panel/review-panel.spec.tsx | 2 - .../components/share-project-modal.test.jsx | 1 - .../codemirror-editor-figure-modal.spec.tsx | 10 +- 20 files changed, 47 insertions(+), 617 deletions(-) delete mode 100644 services/web/frontend/js/features/review-panel-new/components/review-panel-track-changes-menu-button.tsx delete mode 100644 services/web/frontend/js/features/review-panel-new/components/review-panel-track-changes-menu.tsx delete mode 100644 services/web/frontend/js/features/review-panel-new/components/track-changes-on-widget.tsx delete mode 100644 services/web/frontend/js/features/review-panel-new/components/upgrade-track-changes-modal-legacy.tsx diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json index 53fc6adae9..cf47e358d4 100644 --- a/services/web/frontend/extracted-translations.json +++ b/services/web/frontend/extracted-translations.json @@ -38,7 +38,6 @@ "accept_change_error_description": "", "accept_change_error_title": "", "accept_invitation": "", - "accept_or_reject_each_changes_individually": "", "accept_or_reject_individual_edits": "", "accept_selected_changes": "", "accept_terms_and_conditions": "", @@ -127,7 +126,6 @@ "all_these_experiments_are_available_exclusively": "", "allows_to_search_by_author_title_etc_possible_to_pull_results_directly_from_your_reference_manager_if_connected": "", "already_have_a_papers_account": "", - "already_subscribed_try_refreshing_the_page": "", "an_email_has_already_been_sent_to": "", "an_error_occured_while_restoring_project": "", "an_error_occurred_when_verifying_the_coupon_code": "", @@ -925,10 +923,6 @@ "limited_offer": "", "limited_to_n_collaborators_per_project": "", "limited_to_n_collaborators_per_project_plural": "", - "limited_to_n_editors": "", - "limited_to_n_editors_per_project": "", - "limited_to_n_editors_per_project_plural": "", - "limited_to_n_editors_plural": "", "line": "", "line_height": "", "line_width_is_the_width_of_the_line_in_the_current_environment": "", @@ -1434,7 +1428,6 @@ "review": "", "review_panel": "", "review_panel_and_error_logs_moved_to_the_left": "", - "review_your_peers_work": "", "reviewer": "", "reviewer_dropbox_sync_message": "", "reviewing": "", @@ -1489,7 +1482,6 @@ "search_within_selection": "", "searched_path_for_lines_containing": "", "security": "", - "see_changes_in_your_documents_live": "", "see_suggestions_from_collaborators": "", "select_a_column_or_a_merged_cell_to_align": "", "select_a_column_to_adjust_column_width": "", @@ -1714,8 +1706,6 @@ "tags": "", "take_short_survey": "", "take_survey": "", - "tc_everyone": "", - "tc_guests": "", "tell_the_project_owner_and_ask_them_to_upgrade": "", "template": "", "template_description": "", @@ -1874,13 +1864,7 @@ "total_today": "", "total_with_subtotal_and_tax": "", "total_words": "", - "track_any_change_in_real_time": "", "track_changes": "", - "track_changes_for_everyone": "", - "track_changes_for_guests": "", - "track_changes_for_x": "", - "track_changes_is_off": "", - "track_changes_is_on": "", "tracked_change_added": "", "tracked_change_deleted": "", "transfer_management_of_your_account": "", @@ -1972,7 +1956,6 @@ "upgrade_to_add_more_collaborators_and_access_collaboration_features": "", "upgrade_to_get_feature": "", "upgrade_to_review": "", - "upgrade_to_track_changes": "", "upgrade_to_unlock_more_time": "", "upgrade_your_subscription": "", "upload": "", diff --git a/services/web/frontend/js/features/review-panel-new/components/review-panel-container.tsx b/services/web/frontend/js/features/review-panel-new/components/review-panel-container.tsx index 269ffde3b6..f7ee56f105 100644 --- a/services/web/frontend/js/features/review-panel-new/components/review-panel-container.tsx +++ b/services/web/frontend/js/features/review-panel-new/components/review-panel-container.tsx @@ -2,28 +2,20 @@ import ReactDOM from 'react-dom' import { useCodeMirrorViewContext } from '../../source-editor/components/codemirror-context' import { memo } from 'react' import ReviewPanel from './review-panel' -import TrackChangesOnWidget from './track-changes-on-widget' -import { useEditorManagerContext } from '@/features/ide-react/context/editor-manager-context' import ReviewModeSwitcher from './review-mode-switcher' -import getMeta from '@/utils/meta' import useReviewPanelLayout from '../hooks/use-review-panel-layout' function ReviewPanelContainer() { const view = useCodeMirrorViewContext() const { showPanel, mini } = useReviewPanelLayout() - const { wantTrackChanges } = useEditorManagerContext() - const enableReviewerRole = getMeta('ol-isReviewerRoleEnabled') if (!view) { return null } - const showTrackChangesWidget = !enableReviewerRole && wantTrackChanges && mini - return ReactDOM.createPortal( <> - {showTrackChangesWidget && } - {enableReviewerRole && } + {showPanel && } , view.scrollDOM diff --git a/services/web/frontend/js/features/review-panel-new/components/review-panel-header.tsx b/services/web/frontend/js/features/review-panel-new/components/review-panel-header.tsx index 950af503c1..ab5d5509e8 100644 --- a/services/web/frontend/js/features/review-panel-new/components/review-panel-header.tsx +++ b/services/web/frontend/js/features/review-panel-new/components/review-panel-header.tsx @@ -1,35 +1,18 @@ -import { FC, memo, useState } from 'react' +import { FC, memo } from 'react' import { ReviewPanelResolvedThreadsButton } from './review-panel-resolved-threads-button' -import { ReviewPanelTrackChangesMenu } from './review-panel-track-changes-menu' -import ReviewPanelTrackChangesMenuButton from './review-panel-track-changes-menu-button' import { useTranslation } from 'react-i18next' -import getMeta from '@/utils/meta' import { PanelHeading } from '@/shared/components/panel-heading' import useReviewPanelLayout from '../hooks/use-review-panel-layout' const ReviewPanelHeader: FC = () => { - const isReviewerRoleEnabled = getMeta('ol-isReviewerRoleEnabled') - const [trackChangesMenuExpanded, setTrackChangesMenuExpanded] = - useState(false) const { closeReviewPanel } = useReviewPanelLayout() const { t } = useTranslation() return (

      ) } diff --git a/services/web/frontend/js/features/review-panel-new/components/review-panel-resolved-threads-button.tsx b/services/web/frontend/js/features/review-panel-new/components/review-panel-resolved-threads-button.tsx index 6f05a555fa..93192800ed 100644 --- a/services/web/frontend/js/features/review-panel-new/components/review-panel-resolved-threads-button.tsx +++ b/services/web/frontend/js/features/review-panel-new/components/review-panel-resolved-threads-button.tsx @@ -5,7 +5,6 @@ import OLTooltip from '@/features/ui/components/ol/ol-tooltip' import { ReviewPanelResolvedThreadsMenu } from './review-panel-resolved-threads-menu' import { useTranslation } from 'react-i18next' import MaterialIcon from '@/shared/components/material-icon' -import getMeta from '@/utils/meta' export const ReviewPanelResolvedThreadsButton: FC = () => { const [expanded, setExpanded] = useState(false) @@ -20,11 +19,7 @@ export const ReviewPanelResolvedThreadsButton: FC = () => { description={t('resolved_comments')} > - - - - ) -} - -export default memo(ReviewPanelTrackChangesMenuButton) diff --git a/services/web/frontend/js/features/review-panel-new/components/review-panel-track-changes-menu.tsx b/services/web/frontend/js/features/review-panel-new/components/review-panel-track-changes-menu.tsx deleted file mode 100644 index 85f5cb4c26..0000000000 --- a/services/web/frontend/js/features/review-panel-new/components/review-panel-track-changes-menu.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import { FC } from 'react' -import TrackChangesToggle from '@/features/review-panel-new/components/track-changes-toggle' -import { useProjectContext } from '@/shared/context/project-context' -import { usePermissionsContext } from '@/features/ide-react/context/permissions-context' -import { useTranslation } from 'react-i18next' -import { - useTrackChangesStateActionsContext, - useTrackChangesStateContext, -} from '../context/track-changes-state-context' -import { useChangesUsersContext } from '../context/changes-users-context' -import { buildName } from '../utils/build-name' - -export const ReviewPanelTrackChangesMenu: FC = () => { - const { t } = useTranslation() - const permissions = usePermissionsContext() - const project = useProjectContext() - const trackChanges = useTrackChangesStateContext() - const { saveTrackChanges } = useTrackChangesStateActionsContext() - const changesUsers = useChangesUsersContext() - - if (trackChanges === undefined || !changesUsers) { - return null - } - - const { onForEveryone, onForGuests, onForMembers } = trackChanges - - const canToggle = project.features.trackChanges && permissions.write - - return ( -
      -
      - {t('tc_everyone')} - - - saveTrackChanges(onForEveryone ? { on_for: {} } : { on: true }) - } - value={onForEveryone} - disabled={!canToggle} - /> -
      - - {[project.owner, ...project.members].map(member => { - const user = changesUsers.get(member._id) ?? member - const name = buildName(user) - - const value = onForEveryone || onForMembers[member._id] === true - - return ( -
      - {name} - - { - saveTrackChanges({ - on_for: { - ...onForMembers, - [member._id]: !value, - }, - on_for_guests: onForGuests, - }) - }} - value={value} - disabled={!canToggle || onForEveryone} - /> -
      - ) - })} - -
      - {t('tc_guests')} - - - saveTrackChanges({ - on_for: onForMembers, - on_for_guests: !onForGuests, - }) - } - value={onForGuests} - disabled={!canToggle || onForEveryone} - /> -
      -
      - ) -} diff --git a/services/web/frontend/js/features/review-panel-new/components/review-tooltip-menu.tsx b/services/web/frontend/js/features/review-panel-new/components/review-tooltip-menu.tsx index ebb176c0b5..85fe5830f6 100644 --- a/services/web/frontend/js/features/review-panel-new/components/review-tooltip-menu.tsx +++ b/services/web/frontend/js/features/review-panel-new/components/review-tooltip-menu.tsx @@ -34,13 +34,10 @@ import { numberOfChangesInSelection } from '../utils/changes-in-selection' import { useEditorManagerContext } from '@/features/ide-react/context/editor-manager-context' import classNames from 'classnames' import useEventListener from '@/shared/hooks/use-event-listener' -import getMeta from '@/utils/meta' import useReviewPanelLayout from '../hooks/use-review-panel-layout' -const isReviewerRoleEnabled = getMeta('ol-isReviewerRoleEnabled') -const TRACK_CHANGES_ON_WIDGET_HEIGHT = 25 const EDIT_MODE_SWITCH_WIDGET_HEIGHT = 40 -const CM_LINE_RIGHT_PADDING = isReviewerRoleEnabled ? 8 : 2 +const CM_LINE_RIGHT_PADDING = 8 const TOOLTIP_SHOW_DELAY = 120 const ReviewTooltipMenu: FC = () => { @@ -190,16 +187,9 @@ const ReviewTooltipMenuContent: FC<{ onAddComment: () => void }> = ({ return } - let widgetOffset = 0 - if (isReviewerRoleEnabled) { - widgetOffset = EDIT_MODE_SWITCH_WIDGET_HEIGHT - } else if (wantTrackChanges && !reviewPanelOpen) { - widgetOffset = TRACK_CHANGES_ON_WIDGET_HEIGHT - } - return { position: 'fixed' as const, - top: scrollDomRect.top + widgetOffset, + top: scrollDomRect.top + EDIT_MODE_SWITCH_WIDGET_HEIGHT, right: window.innerWidth - editorRightPos, } }, diff --git a/services/web/frontend/js/features/review-panel-new/components/track-changes-on-widget.tsx b/services/web/frontend/js/features/review-panel-new/components/track-changes-on-widget.tsx deleted file mode 100644 index 63ee10060f..0000000000 --- a/services/web/frontend/js/features/review-panel-new/components/track-changes-on-widget.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { Trans } from 'react-i18next' -import { EditorView } from '@codemirror/view' -import classnames from 'classnames' -import { useCodeMirrorStateContext } from '@/features/source-editor/components/codemirror-context' -import { useLayoutContext } from '@/shared/context/layout-context' -import { useCallback } from 'react' - -function TrackChangesOnWidget() { - const { setReviewPanelOpen } = useLayoutContext() - const state = useCodeMirrorStateContext() - const darkTheme = state.facet(EditorView.darkTheme) - - const openReviewPanel = useCallback(() => { - setReviewPanelOpen(true) - }, [setReviewPanelOpen]) - - return ( -
      -
      - -
      -
      - ) -} - -export default TrackChangesOnWidget diff --git a/services/web/frontend/js/features/review-panel-new/components/upgrade-track-changes-modal-legacy.tsx b/services/web/frontend/js/features/review-panel-new/components/upgrade-track-changes-modal-legacy.tsx deleted file mode 100644 index ccc5f1d452..0000000000 --- a/services/web/frontend/js/features/review-panel-new/components/upgrade-track-changes-modal-legacy.tsx +++ /dev/null @@ -1,109 +0,0 @@ -import { useTranslation } from 'react-i18next' -import { useProjectContext } from '@/shared/context/project-context' -import { useUserContext } from '@/shared/context/user-context' -import teaserVideo from '../images/teaser-track-changes.mp4' -import teaserImage from '../images/teaser-track-changes.gif' -import { startFreeTrial, upgradePlan } from '@/main/account-upgrade' -import { memo } from 'react' -import OLModal, { - OLModalBody, - OLModalFooter, - OLModalHeader, - OLModalTitle, -} from '@/features/ui/components/ol/ol-modal' -import OLButton from '@/features/ui/components/ol/ol-button' -import OLRow from '@/features/ui/components/ol/ol-row' -import OLCol from '@/features/ui/components/ol/ol-col' -import MaterialIcon from '@/shared/components/material-icon' - -type UpgradeTrackChangesModalProps = { - show: boolean - setShow: React.Dispatch> -} - -function UpgradeTrackChangesModalLegacy({ - show, - setShow, -}: UpgradeTrackChangesModalProps) { - const { t } = useTranslation() - const project = useProjectContext() - const user = useUserContext() - - return ( - setShow(false)}> - - {t('upgrade_to_track_changes')} - - -
      - {/* eslint-disable-next-line jsx-a11y/media-has-caption */} - -
      -

      - {t('see_changes_in_your_documents_live')} -

      - - -
        - {[ - t('track_any_change_in_real_time'), - t('review_your_peers_work'), - t('accept_or_reject_each_changes_individually'), - ].map(translation => ( -
      • - -  {translation} -
      • - ))} -
      -
      -
      -

      - {t('already_subscribed_try_refreshing_the_page')} -

      - {project.owner && ( -
      - {project.owner._id === user.id ? ( - user.allowedFreeTrial ? ( - startFreeTrial('track-changes')} - > - {t('try_it_for_free')} - - ) : ( - upgradePlan('project-sharing')} - > - {t('upgrade')} - - ) - ) : ( -

      - - {t( - 'please_ask_the_project_owner_to_upgrade_to_track_changes' - )} - -

      - )} -
      - )} -
      - - setShow(false)}> - {t('close')} - - -
      - ) -} - -export default memo(UpgradeTrackChangesModalLegacy) diff --git a/services/web/frontend/js/features/review-panel-new/context/track-changes-state-context.tsx b/services/web/frontend/js/features/review-panel-new/context/track-changes-state-context.tsx index 2d77ef9d8d..73ffe78b5d 100644 --- a/services/web/frontend/js/features/review-panel-new/context/track-changes-state-context.tsx +++ b/services/web/frontend/js/features/review-panel-new/context/track-changes-state-context.tsx @@ -17,7 +17,6 @@ import { postJSON } from '@/infrastructure/fetch-json' import useEventListener from '@/shared/hooks/use-event-listener' import { ProjectContextValue } from '@/shared/context/types/project-context' import { usePermissionsContext } from '@/features/ide-react/context/permissions-context' -import getMeta from '@/utils/meta' export type TrackChangesState = { onForEveryone: boolean @@ -99,25 +98,15 @@ export const TrackChangesStateProvider: FC = ({ const saveTrackChangesForCurrentUser = useCallback( async (trackChanges: boolean) => { if (user.id) { - if (getMeta('ol-isReviewerRoleEnabled')) { - saveTrackChanges({ - on_for: { - ...onForMembers, - [user.id]: trackChanges, - }, - }) - } else { - saveTrackChanges({ - on_for: { - ...onForMembers, - [user.id]: trackChanges, - }, - on_for_guests: onForGuests, - }) - } + saveTrackChanges({ + on_for: { + ...onForMembers, + [user.id]: trackChanges, + }, + }) } }, - [onForMembers, onForGuests, user.id, saveTrackChanges] + [onForMembers, user.id, saveTrackChanges] ) const actions = useMemo( @@ -138,27 +127,16 @@ export const TrackChangesStateProvider: FC = ({ !onForEveryone ) { const value = onForMembers[user.id] - if (getMeta('ol-isReviewerRoleEnabled')) { - actions.saveTrackChanges({ - on_for: { - ...onForMembers, - [user.id]: !value, - }, - }) - } else { - actions.saveTrackChanges({ - on_for: { - ...onForMembers, - [user.id]: !value, - }, - on_for_guests: onForGuests, - }) - } + actions.saveTrackChanges({ + on_for: { + ...onForMembers, + [user.id]: !value, + }, + }) } }, [ actions, onForMembers, - onForGuests, onForEveryone, permissions.write, project.features.trackChanges, diff --git a/services/web/frontend/js/features/review-panel-new/hooks/use-review-panel-styles.ts b/services/web/frontend/js/features/review-panel-new/hooks/use-review-panel-styles.ts index 59006db354..7e7dda1850 100644 --- a/services/web/frontend/js/features/review-panel-new/hooks/use-review-panel-styles.ts +++ b/services/web/frontend/js/features/review-panel-new/hooks/use-review-panel-styles.ts @@ -1,14 +1,11 @@ import { CSSProperties, useCallback, useEffect, useState } from 'react' import { useCodeMirrorViewContext } from '@/features/source-editor/components/codemirror-context' -import getMeta from '@/utils/meta' export const useReviewPanelStyles = (mini: boolean) => { const view = useCodeMirrorViewContext() const [styles, setStyles] = useState({ - '--review-panel-header-height': getMeta('ol-isReviewerRoleEnabled') - ? '36px' - : '69px', + '--review-panel-header-height': '36px', } as CSSProperties) const updateScrollDomVariables = useCallback((element: HTMLDivElement) => { diff --git a/services/web/frontend/js/features/review-panel-new/utils/position-items.ts b/services/web/frontend/js/features/review-panel-new/utils/position-items.ts index 87fa92cd80..54489310a9 100644 --- a/services/web/frontend/js/features/review-panel-new/utils/position-items.ts +++ b/services/web/frontend/js/features/review-panel-new/utils/position-items.ts @@ -1,8 +1,7 @@ -import getMeta from '@/utils/meta' import { debounce } from 'lodash' export const OFFSET_FOR_ENTRIES_ABOVE = 70 -const COLLAPSED_HEADER_HEIGHT = getMeta('ol-isReviewerRoleEnabled') ? 42 : 75 +const COLLAPSED_HEADER_HEIGHT = 42 const GAP_BETWEEN_ENTRIES = 4 export const positionItems = debounce( diff --git a/services/web/frontend/js/features/share-project-modal/components/add-collaborators.jsx b/services/web/frontend/js/features/share-project-modal/components/add-collaborators.jsx index cad9d03177..b6d8491596 100644 --- a/services/web/frontend/js/features/share-project-modal/components/add-collaborators.jsx +++ b/services/web/frontend/js/features/share-project-modal/components/add-collaborators.jsx @@ -14,7 +14,6 @@ import OLForm from '@/features/ui/components/ol/ol-form' import OLFormGroup from '@/features/ui/components/ol/ol-form-group' import { Select } from '@/shared/components/select' import OLButton from '@/features/ui/components/ol/ol-button' -import getMeta from '@/utils/meta' export default function AddCollaborators({ readOnly }) { const [privileges, setPrivileges] = useState('readAndWrite') @@ -178,29 +177,23 @@ export default function AddCollaborators({ readOnly }) { ]) const privilegeOptions = useMemo(() => { - const options = [ + return [ { key: 'readAndWrite', label: t('editor'), }, - ] - - if (getMeta('ol-isReviewerRoleEnabled')) { - options.push({ + { key: 'review', label: t('reviewer'), description: !features.trackChanges ? t('comment_only_upgrade_for_track_changes') : null, - }) - } - - options.push({ - key: 'readOnly', - label: t('viewer'), - }) - - return options + }, + { + key: 'readOnly', + label: t('viewer'), + }, + ] }, [features.trackChanges, t]) return ( 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 45a04dd99c..46b0e21443 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 @@ -14,9 +14,7 @@ import OLButton from '@/features/ui/components/ol/ol-button' import OLFormGroup from '@/features/ui/components/ol/ol-form-group' import OLCol from '@/features/ui/components/ol/ol-col' import MaterialIcon from '@/shared/components/material-icon' -import getMeta from '@/utils/meta' import { useUserContext } from '@/shared/context/user-context' -import { isSplitTestEnabled } from '@/utils/splitTestUtils' import { upgradePlan } from '@/main/account-upgrade' type PermissionsOption = PermissionsLevel | 'removeAccess' | 'downgraded' @@ -256,21 +254,13 @@ function SelectPrivilege({ const { features } = useProjectContext() const privileges = useMemo( - (): Privilege[] => - getMeta('ol-isReviewerRoleEnabled') - ? [ - { 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') }, - ], + (): 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] ) @@ -284,27 +274,13 @@ function SelectPrivilege({ return '' } - if (hasBeenDowngraded) { - if (isSplitTestEnabled('reviewer-role')) { - return t('limited_to_n_collaborators_per_project', { - count: features.collaborators, - }) - } else { - return t('limited_to_n_editors', { count: features.collaborators }) - } - } else if ( - !canAddCollaborators && - !['readAndWrite', 'review'].includes(value) + if ( + hasBeenDowngraded || + (!canAddCollaborators && !['readAndWrite', 'review'].includes(value)) ) { - if (isSplitTestEnabled('reviewer-role')) { - return t('limited_to_n_collaborators_per_project', { - count: features.collaborators, - }) - } else { - return t('limited_to_n_editors_per_project', { - count: features.collaborators, - }) - } + return t('limited_to_n_collaborators_per_project', { + count: features.collaborators, + }) } else { return '' } diff --git a/services/web/frontend/js/utils/meta.ts b/services/web/frontend/js/utils/meta.ts index 220cd0803f..2e0e40c49d 100644 --- a/services/web/frontend/js/utils/meta.ts +++ b/services/web/frontend/js/utils/meta.ts @@ -140,7 +140,6 @@ export interface Meta { 'ol-isProfessional': boolean 'ol-isRegisteredViaGoogle': boolean 'ol-isRestrictedTokenMember': boolean - 'ol-isReviewerRoleEnabled': boolean 'ol-isSaas': boolean 'ol-itm_campaign': string 'ol-itm_content': string diff --git a/services/web/frontend/stylesheets/bootstrap-5/pages/editor/review-panel-new.scss b/services/web/frontend/stylesheets/bootstrap-5/pages/editor/review-panel-new.scss index 074870d0f7..5e9f53b7f7 100644 --- a/services/web/frontend/stylesheets/bootstrap-5/pages/editor/review-panel-new.scss +++ b/services/web/frontend/stylesheets/bootstrap-5/pages/editor/review-panel-new.scss @@ -250,75 +250,7 @@ del.review-panel-content-highlight { z-index: 4; } -// TODO: Update this when we move the track changes menu to the new design -.rp-tc-state { - position: absolute; - top: 100%; - left: 0; - right: 0; - overflow: hidden; - list-style: none; - padding: 0 var(--spacing-03); - margin: 0; - border-bottom: 1px solid var(--rp-border-grey); - text-align: left; - background-color: var(--white); - max-height: calc( - 100vh - var(--review-panel-top) - var(--review-panel-header-height) - ); - overflow-y: auto; - - .rp-tc-state-item { - display: flex; - align-items: center; - padding: var(--spacing-02) 0; - - &:last-of-type { - padding-bottom: var(--spacing-03); - } - } - - .rp-tc-state-item-name { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - flex-grow: 1; - font-weight: 600; - } -} - -.review-panel-tools { - display: flex; - align-items: center; - justify-content: space-between; - padding-left: var(--spacing-02); - padding-right: var(--spacing-05); - flex-shrink: 0; - flex-basis: 32px; -} - .review-panel-resolved-comments-toggle { - background-color: var(--bg-light-secondary); - font-size: var(--font-size-02); - color: color.adjust($rp-type-blue, $lightness: 25%); - border: solid 1px var(--rp-border-grey); - border-radius: var(--border-radius-base); - padding: 0; - height: 22px; - width: 22px; - line-height: 1.4; - display: flex; - align-items: center; - justify-content: center; - - &:hover, - &:focus { - text-decoration: none; - color: var(--rp-type-blue); - } -} - -.review-panel-resolved-comments-toggle-reviewer-role { display: flex; align-items: center; border: none; @@ -333,27 +265,6 @@ del.review-panel-content-highlight { } } -.track-changes-indicator-circle { - width: 8px; - height: 8px; - border-radius: 100%; - background-color: var(--bg-accent-01); -} - -.track-changes-menu-button { - border: none; - background: none; - padding: 0; - display: flex; - align-items: center; - gap: var(--spacing-02); - font-size: var(--font-size-02); - - i { - width: 8px; - } -} - .review-panel-resolved-comments { --bs-popover-border-width: 1px; --bs-popover-bg: var(--bg-light-secondary); @@ -780,56 +691,6 @@ del.review-panel-content-highlight { pointer-events: none; // this is to prevent mouseLeave event from firing when hovering over the tooltip } -.review-panel-in-editor-widgets { - position: sticky; - top: 0; - right: 0; - font-size: 11px; - z-index: 2; - font-family: $font-family-base; - - .review-panel-in-editor-widgets-inner { - position: absolute; - top: 0; - right: 0; - display: flex; - flex-direction: column; - } - - .review-panel-track-changes-indicator { - border: 0; - } -} - -.review-panel-track-changes-indicator { - display: block; - padding: 5px 10px; - background-color: rgb(240 240 240 / 90%); - color: var(--rp-type-blue); - text-align: center; - border-bottom-left-radius: 3px; - white-space: nowrap; - - &.review-panel-track-changes-indicator-on-dark { - background-color: rgb(88 88 88 / 80%); - color: #fff; - - &:hover, - &:focus { - background-color: rgb(88 88 88 / 100%); - color: #fff; - } - } - - &:hover, - &:focus { - outline: 0; - text-decoration: none; - background-color: rgb(240 240 240 / 100%); - color: var(--rp-type-blue); - } -} - .review-mode-switcher-container { position: sticky; top: 0; diff --git a/services/web/locales/en.json b/services/web/locales/en.json index 97818e2116..6b947e50dc 100644 --- a/services/web/locales/en.json +++ b/services/web/locales/en.json @@ -42,7 +42,6 @@ "accept_change_error_description": "There was an error accepting a track change. Please try again in a few moments.", "accept_change_error_title": "Accept Change Error", "accept_invitation": "Accept invitation", - "accept_or_reject_each_changes_individually": "Accept or reject each change individually", "accept_or_reject_individual_edits": "Accept or reject individual edits", "accept_selected_changes": "Accept selected changes", "accept_terms_and_conditions": "Accept terms and conditions", @@ -154,7 +153,6 @@ "already_have_a_papers_account": "Managing your citations and bibliographies in Overleaf just got way easier! Already have a Papers account? <0>Link your account here.", "already_have_an_account": "Already have an account?", "already_have_sl_account": "Already have an __appName__ account?", - "already_subscribed_try_refreshing_the_page": "Already subscribed? Try refreshing the page.", "also": "Also", "alternatively_create_new_institution_account": "Alternatively, you can create a new account with your institution email (__email__) by clicking __clickText__.", "an_email_has_already_been_sent_to": "An email has already been sent to <0>__email__. Please wait and try again later.", @@ -1206,10 +1204,6 @@ "limited_document_history": "Limited document history", "limited_to_n_collaborators_per_project": "Limited to __count__ collaborator per project", "limited_to_n_collaborators_per_project_plural": "Limited to __count__ collaborators per project", - "limited_to_n_editors": "Limited to __count__ editor", - "limited_to_n_editors_per_project": "Limited to __count__ editor per project", - "limited_to_n_editors_per_project_plural": "Limited to __count__ editors per project", - "limited_to_n_editors_plural": "Limited to __count__ editors", "line_height": "Line Height", "line_width_is_the_width_of_the_line_in_the_current_environment": "Line width is the width of the line in the current environment. e.g. a full page width in single-column layout or half a page width in a two-column layout.", "link": "Link", @@ -1888,7 +1882,6 @@ "review": "Review", "review_panel": "Review panel", "review_panel_and_error_logs_moved_to_the_left": "Review panel and error logs moved to the left", - "review_your_peers_work": "Review your peers’ work", "reviewer": "Reviewer", "reviewer_dropbox_sync_message": "As a reviewer you can sync the current project version to Dropbox, but changes made in Dropbox will <0>not sync back to Overleaf.", "reviewing": "Reviewing", @@ -1953,7 +1946,6 @@ "searched_path_for_lines_containing": "Searched __path__ for lines containing \"__query__\"", "secondary_email_password_reset": "That email is registered as a secondary email. Please enter the primary email for your account.", "security": "Security", - "see_changes_in_your_documents_live": "See changes in your documents, live", "see_suggestions_from_collaborators": "See suggestions from collaborators", "select_a_column_or_a_merged_cell_to_align": "Select a column or a merged cell to align", "select_a_column_to_adjust_column_width": "Select a column to adjust column width", @@ -2214,8 +2206,6 @@ "take_me_home": "Take me home!", "take_short_survey": "Take a short survey", "take_survey": "Take survey", - "tc_everyone": "Everyone", - "tc_guests": "Guests", "tell_the_project_owner_and_ask_them_to_upgrade": "<0>Tell the project owner and ask them to upgrade their Overleaf plan if you need more compile time.", "template": "Template", "template_approved_by_publisher": "This template has been approved by the publisher", @@ -2402,12 +2392,7 @@ "total_with_subtotal_and_tax": "Total: <0>__total__ (__subtotal__ + __tax__ tax) per year", "total_words": "Total Words", "tr": "Turkish", - "track_any_change_in_real_time": "Track any change, in real-time", "track_changes": "Track changes", - "track_changes_for_everyone": "Track changes for everyone", - "track_changes_for_x": "Track changes for __name__", - "track_changes_is_off": "Track changes is off", - "track_changes_is_on": "Track changes is on", "tracked_change_added": "Added", "tracked_change_deleted": "Deleted", "transfer_management_of_your_account": "Transfer management of your Overleaf account", @@ -2506,7 +2491,6 @@ "upgrade_to_add_more_collaborators_and_access_collaboration_features": "Upgrade to add more collaborators and access collaboration features like track changes and full project history.", "upgrade_to_get_feature": "Upgrade to get __feature__, plus:", "upgrade_to_review": "Upgrade to Review", - "upgrade_to_track_changes": "Upgrade to track changes", "upgrade_to_unlock_more_time": "Upgrade now to unlock 12x more compile time on our fastest servers.", "upgrade_your_subscription": "Upgrade your subscription", "upload": "Upload", 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 7971ff01d2..d667787810 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 @@ -10,7 +10,6 @@ import { docId } from '../source-editor/helpers/mock-doc' describe('', function () { beforeEach(function () { - window.metaAttributesCache.set('ol-isReviewerRoleEnabled', true) window.metaAttributesCache.set('ol-preventCompileOnLoad', true) cy.interceptEvents() @@ -650,7 +649,6 @@ describe(' for free users', function () { } beforeEach(function () { - window.metaAttributesCache.set('ol-isReviewerRoleEnabled', true) window.metaAttributesCache.set('ol-preventCompileOnLoad', true) cy.interceptEvents() cy.intercept('GET', '/project/*/changes/users', []) 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 cdeb0adb6f..91a64581da 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 @@ -94,7 +94,6 @@ describe('', function () { fetchMock.get('/user/contacts', { contacts }) window.metaAttributesCache.set('ol-user', { allowedFreeTrial: true }) window.metaAttributesCache.set('ol-showUpgradePrompt', true) - window.metaAttributesCache.set('ol-isReviewerRoleEnabled', true) window.metaAttributesCache.set('ol-preventCompileOnLoad', true) }) diff --git a/services/web/test/frontend/features/source-editor/components/codemirror-editor-figure-modal.spec.tsx b/services/web/test/frontend/features/source-editor/components/codemirror-editor-figure-modal.spec.tsx index eda6dcec6b..0d80f9bde8 100644 --- a/services/web/test/frontend/features/source-editor/components/codemirror-editor-figure-modal.spec.tsx +++ b/services/web/test/frontend/features/source-editor/components/codemirror-editor-figure-modal.spec.tsx @@ -509,7 +509,7 @@ describe('', function () { \\end{{}figure}`, { delay: 0 } ) - cy.get('[aria-label="Edit figure"]').click() + cy.get('[aria-label="Edit figure"]').click({ force: true }) cy.findByRole('checkbox', { name: 'Include caption' }).should( 'be.checked' ) @@ -526,7 +526,7 @@ describe('', function () { \\end{{}figure}`, { delay: 0 } ) - cy.get('[aria-label="Edit figure"]').click() + cy.get('[aria-label="Edit figure"]').click({ force: true }) cy.get('[value="0.75"]').should('be.checked') }) @@ -539,7 +539,7 @@ describe('', function () { \\end{{}figure}`, { delay: 0 } ) - cy.get('[aria-label="Edit figure"]').click() + cy.get('[aria-label="Edit figure"]').click({ force: true }) cy.findByRole('checkbox', { name: 'Include label' }).click() cy.findByRole('checkbox', { name: 'Include label' }).should( 'not.be.checked' @@ -561,7 +561,7 @@ describe('', function () { \\end{{}figure}`, { delay: 0 } ) - cy.get('[aria-label="Edit figure"]').click() + cy.get('[aria-label="Edit figure"]').click({ force: true }) cy.findByRole('checkbox', { name: 'Include caption' }).click() cy.findByRole('checkbox', { name: 'Include caption' }).should( 'not.be.checked' @@ -585,7 +585,7 @@ describe('', function () { text below`, { delay: 0 } ) - cy.get('[aria-label="Edit figure"]').click() + cy.get('[aria-label="Edit figure"]').click({ force: true }) cy.findByRole('button', { name: 'Remove or replace figure' }).click() cy.findByText('Delete figure').click() cy.get('.cm-content').should('have.text', 'text abovetext below') From 966cea3d8b95d769762433bc69c9b5114ab15a50 Mon Sep 17 00:00:00 2001 From: David <33458145+davidmcpowell@users.noreply.github.com> Date: Thu, 15 May 2025 11:22:29 +0100 Subject: [PATCH 119/194] Merge pull request #25550 from overleaf/dp-share-modal-proptypes Remove proptypes from ShareProjectModal GitOrigin-RevId: b95fed5007f72e4a57a65b1d08d8fcc9579b3630 --- ...ollaborators.jsx => add-collaborators.tsx} | 25 +++--- .../components/edit-member.tsx | 10 --- .../components/{invite.jsx => invite.tsx} | 33 ++++---- .../{link-sharing.jsx => link-sharing.tsx} | 84 +++++++++++-------- ...r-privileges.jsx => member-privileges.tsx} | 11 +-- .../{owner-info.jsx => owner-info.tsx} | 0 ...aborators.jsx => select-collaborators.tsx} | 82 +++++++++--------- .../{send-invites.jsx => send-invites.tsx} | 13 ++- ...modal.jsx => transfer-ownership-modal.tsx} | 14 ++-- .../{view-member.jsx => view-member.tsx} | 16 ++-- ...-user-contacts.js => use-user-contacts.ts} | 5 +- .../share-project-modal/utils/types.ts | 9 ++ 12 files changed, 153 insertions(+), 149 deletions(-) rename services/web/frontend/js/features/share-project-modal/components/{add-collaborators.jsx => add-collaborators.tsx} (91%) rename services/web/frontend/js/features/share-project-modal/components/{invite.jsx => invite.tsx} (86%) rename services/web/frontend/js/features/share-project-modal/components/{link-sharing.jsx => link-sharing.tsx} (87%) rename services/web/frontend/js/features/share-project-modal/components/{member-privileges.jsx => member-privileges.tsx} (59%) rename services/web/frontend/js/features/share-project-modal/components/{owner-info.jsx => owner-info.tsx} (100%) rename services/web/frontend/js/features/share-project-modal/components/{select-collaborators.jsx => select-collaborators.tsx} (86%) rename services/web/frontend/js/features/share-project-modal/components/{send-invites.jsx => send-invites.tsx} (80%) rename services/web/frontend/js/features/share-project-modal/components/{transfer-ownership-modal.jsx => transfer-ownership-modal.tsx} (91%) rename services/web/frontend/js/features/share-project-modal/components/{view-member.jsx => view-member.tsx} (68%) rename services/web/frontend/js/features/share-project-modal/hooks/{use-user-contacts.js => use-user-contacts.ts} (85%) create mode 100644 services/web/frontend/js/features/share-project-modal/utils/types.ts diff --git a/services/web/frontend/js/features/share-project-modal/components/add-collaborators.jsx b/services/web/frontend/js/features/share-project-modal/components/add-collaborators.tsx similarity index 91% rename from services/web/frontend/js/features/share-project-modal/components/add-collaborators.jsx rename to services/web/frontend/js/features/share-project-modal/components/add-collaborators.tsx index b6d8491596..8606fb11fa 100644 --- a/services/web/frontend/js/features/share-project-modal/components/add-collaborators.jsx +++ b/services/web/frontend/js/features/share-project-modal/components/add-collaborators.tsx @@ -2,20 +2,19 @@ import { useEffect, useState, useMemo, useCallback } from 'react' import { useTranslation } from 'react-i18next' import { useMultipleSelection } from 'downshift' import { useShareProjectContext } from './share-project-modal' -import SelectCollaborators from './select-collaborators' +import SelectCollaborators, { ContactItem } from './select-collaborators' import { resendInvite, sendInvite } from '../utils/api' import { useUserContacts } from '../hooks/use-user-contacts' import useIsMounted from '@/shared/hooks/use-is-mounted' import { useProjectContext } from '@/shared/context/project-context' import { sendMB } from '@/infrastructure/event-tracking' import ClickableElementEnhancer from '@/shared/components/clickable-element-enhancer' -import PropTypes from 'prop-types' import OLForm from '@/features/ui/components/ol/ol-form' import OLFormGroup from '@/features/ui/components/ol/ol-form-group' import { Select } from '@/shared/components/select' import OLButton from '@/features/ui/components/ol/ol-button' -export default function AddCollaborators({ readOnly }) { +export default function AddCollaborators({ readOnly }: { readOnly?: boolean }) { const [privileges, setPrivileges] = useState('readAndWrite') const isMounted = useIsMounted() @@ -43,7 +42,7 @@ export default function AddCollaborators({ readOnly }) { ) }, [contacts, currentMemberEmails]) - const multipleSelectionProps = useMultipleSelection({ + const multipleSelectionProps = useMultipleSelection({ initialActiveIndex: 0, initialSelectedItems: [], }) @@ -129,7 +128,7 @@ export default function AddCollaborators({ readOnly }) { ? previousViewersAmount + 1 : previousViewersAmount, }) - } catch (error) { + } catch (error: any) { setInFlight(false) setError( error.data?.errorReason || @@ -213,13 +212,17 @@ export default function AddCollaborators({ readOnly }) { dataTestId="add-collaborator-select" items={privilegeOptions} itemToKey={item => item.key} - itemToString={item => item.label} - itemToSubtitle={item => item.description || ''} - itemToDisabled={item => readOnly && item.key !== 'readOnly'} + itemToString={item => item?.label || ''} + itemToSubtitle={item => item?.description || ''} + itemToDisabled={item => !!(readOnly && item?.key !== 'readOnly')} selected={privilegeOptions.find( option => option.key === privileges )} - onSelectedItemChanged={item => setPrivileges(item.key)} + onSelectedItemChanged={item => { + if (item) { + setPrivileges(item.key) + } + }} /> ) } - -AddCollaborators.propTypes = { - readOnly: PropTypes.bool, -} 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 46b0e21443..6d806968b1 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 @@ -1,5 +1,4 @@ import { useState, useEffect, useMemo } from 'react' -import PropTypes from 'prop-types' import { Trans, useTranslation } from 'react-i18next' import { useShareProjectContext } from './share-project-modal' import TransferOwnershipModal from './transfer-ownership-modal' @@ -227,15 +226,6 @@ export default function EditMember({ ) } -EditMember.propTypes = { - member: PropTypes.shape({ - _id: PropTypes.string.isRequired, - email: PropTypes.string.isRequired, - privileges: PropTypes.string.isRequired, - }), - hasExceededCollaboratorLimit: PropTypes.bool.isRequired, - canAddCollaborators: PropTypes.bool.isRequired, -} type SelectPrivilegeProps = { value: string diff --git a/services/web/frontend/js/features/share-project-modal/components/invite.jsx b/services/web/frontend/js/features/share-project-modal/components/invite.tsx similarity index 86% rename from services/web/frontend/js/features/share-project-modal/components/invite.jsx rename to services/web/frontend/js/features/share-project-modal/components/invite.tsx index 3ca6f60d66..e9d761e4ee 100644 --- a/services/web/frontend/js/features/share-project-modal/components/invite.jsx +++ b/services/web/frontend/js/features/share-project-modal/components/invite.tsx @@ -1,5 +1,4 @@ import { useCallback } from 'react' -import PropTypes from 'prop-types' import { useShareProjectContext } from './share-project-modal' import { useTranslation } from 'react-i18next' import MemberPrivileges from './member-privileges' @@ -11,8 +10,15 @@ import OLCol from '@/features/ui/components/ol/ol-col' import OLTooltip from '@/features/ui/components/ol/ol-tooltip' import OLButton from '@/features/ui/components/ol/ol-button' import MaterialIcon from '@/shared/components/material-icon' +import { ProjectContextMember } from '@/shared/context/types/project-context' -export default function Invite({ invite, isProjectOwner }) { +export default function Invite({ + invite, + isProjectOwner, +}: { + invite: ProjectContextMember + isProjectOwner: boolean +}) { const { t } = useTranslation() return ( @@ -38,12 +44,7 @@ export default function Invite({ invite, isProjectOwner }) { ) } -Invite.propTypes = { - invite: PropTypes.object.isRequired, - isProjectOwner: PropTypes.bool.isRequired, -} - -function ResendInvite({ invite }) { +function ResendInvite({ invite }: { invite: ProjectContextMember }) { const { t } = useTranslation() const { monitorRequest, setError, inFlight } = useShareProjectContext() const { _id: projectId } = useProjectContext() @@ -66,7 +67,9 @@ function ResendInvite({ invite }) { // if (buttonRef.current) { // buttonRef.current.blur() // } - document.activeElement.blur() + if (document.activeElement) { + ;(document.activeElement as HTMLElement).blur() + } }), [invite, monitorRequest, projectId, setError] ) @@ -84,16 +87,12 @@ function ResendInvite({ invite }) { ) } -ResendInvite.propTypes = { - invite: PropTypes.object.isRequired, -} - -function RevokeInvite({ invite }) { +function RevokeInvite({ invite }: { invite: ProjectContextMember }) { const { t } = useTranslation() const { updateProject, monitorRequest } = useShareProjectContext() const { _id: projectId, invites, members } = useProjectContext() - function handleClick(event) { + function handleClick(event: React.MouseEvent) { event.preventDefault() monitorRequest(() => revokeInvite(projectId, invite)).then(() => { @@ -126,7 +125,3 @@ function RevokeInvite({ invite }) { ) } - -RevokeInvite.propTypes = { - invite: PropTypes.object.isRequired, -} diff --git a/services/web/frontend/js/features/share-project-modal/components/link-sharing.jsx b/services/web/frontend/js/features/share-project-modal/components/link-sharing.tsx similarity index 87% rename from services/web/frontend/js/features/share-project-modal/components/link-sharing.jsx rename to services/web/frontend/js/features/share-project-modal/components/link-sharing.tsx index 4e9e60c28c..d235bd248b 100644 --- a/services/web/frontend/js/features/share-project-modal/components/link-sharing.jsx +++ b/services/web/frontend/js/features/share-project-modal/components/link-sharing.tsx @@ -1,5 +1,4 @@ import { useCallback, useState, useEffect } from 'react' -import PropTypes from 'prop-types' import { useTranslation } from 'react-i18next' import { useShareProjectContext } from './share-project-modal' import { setProjectAccessLevel } from '../utils/api' @@ -18,6 +17,16 @@ import OLButton from '@/features/ui/components/ol/ol-button' import OLTooltip from '@/features/ui/components/ol/ol-tooltip' import MaterialIcon from '@/shared/components/material-icon' +type Tokens = { + readAndWrite: string + readAndWriteHashPrefix: string + readAndWritePrefix: string + readOnly: string + readOnlyHashPrefix: string +} + +type AccessLevel = 'private' | 'tokenBased' | 'readAndWrite' | 'readOnly' + export default function LinkSharing() { const [inflight, setInflight] = useState(false) const [showLinks, setShowLinks] = useState(true) @@ -28,7 +37,7 @@ export default function LinkSharing() { // set the access level of a project const setAccessLevel = useCallback( - newPublicAccessLevel => { + (newPublicAccessLevel: string) => { setInflight(true) sendMB('link-sharing-click-off', { project_id: projectId, @@ -75,6 +84,7 @@ export default function LinkSharing() { case 'readAndWrite': case 'readOnly': return ( + // TODO: do we even need this anymore? void + inflight: boolean + projectId: string + setShowLinks: (show: boolean) => void +}) { const { t } = useTranslation() return ( @@ -113,23 +133,21 @@ function PrivateSharing({ setAccessLevel, inflight, projectId, setShowLinks }) { ) } -PrivateSharing.propTypes = { - setAccessLevel: PropTypes.func.isRequired, - inflight: PropTypes.bool, - projectId: PropTypes.string, - setShowLinks: PropTypes.func.isRequired, -} - function TokenBasedSharing({ setAccessLevel, inflight, setShowLinks, showLinks, +}: { + setAccessLevel: (level: AccessLevel) => void + inflight: boolean + setShowLinks: (show: boolean) => void + showLinks: boolean }) { const { t } = useTranslation() const { _id: projectId } = useProjectContext() - const [tokens, setTokens] = useState(null) + const [tokens, setTokens] = useState(null) const { signal } = useAbortController() @@ -190,14 +208,15 @@ function TokenBasedSharing({ ) } -TokenBasedSharing.propTypes = { - setAccessLevel: PropTypes.func.isRequired, - inflight: PropTypes.bool, - setShowLinks: PropTypes.func.isRequired, - showLinks: PropTypes.bool, -} - -function LegacySharing({ accessLevel, setAccessLevel, inflight }) { +function LegacySharing({ + accessLevel, + setAccessLevel, + inflight, +}: { + accessLevel: AccessLevel + setAccessLevel: (level: AccessLevel) => void + inflight: boolean +}) { const { t } = useTranslation() return ( @@ -223,17 +242,11 @@ function LegacySharing({ accessLevel, setAccessLevel, inflight }) { ) } -LegacySharing.propTypes = { - accessLevel: PropTypes.string.isRequired, - setAccessLevel: PropTypes.func.isRequired, - inflight: PropTypes.bool, -} - export function ReadOnlyTokenLink() { const { t } = useTranslation() const { _id: projectId } = useProjectContext() - const [tokens, setTokens] = useState(null) + const [tokens, setTokens] = useState(null) const { signal } = useAbortController() @@ -260,7 +273,17 @@ export function ReadOnlyTokenLink() { ) } -function AccessToken({ token, tokenHashPrefix, path, tooltipId }) { +function AccessToken({ + token, + tokenHashPrefix, + path, + tooltipId, +}: { + token?: string + tokenHashPrefix?: string + path: string + tooltipId: string +}) { const { t } = useTranslation() const { isAdmin } = useUserContext() @@ -288,13 +311,6 @@ function AccessToken({ token, tokenHashPrefix, path, tooltipId }) { ) } -AccessToken.propTypes = { - token: PropTypes.string, - tokenHashPrefix: PropTypes.string, - tooltipId: PropTypes.string.isRequired, - path: PropTypes.string.isRequired, -} - function LinkSharingInfo() { const { t } = useTranslation() diff --git a/services/web/frontend/js/features/share-project-modal/components/member-privileges.jsx b/services/web/frontend/js/features/share-project-modal/components/member-privileges.tsx similarity index 59% rename from services/web/frontend/js/features/share-project-modal/components/member-privileges.jsx rename to services/web/frontend/js/features/share-project-modal/components/member-privileges.tsx index bae354858a..c2b7bf98ef 100644 --- a/services/web/frontend/js/features/share-project-modal/components/member-privileges.jsx +++ b/services/web/frontend/js/features/share-project-modal/components/member-privileges.tsx @@ -1,7 +1,11 @@ -import PropTypes from 'prop-types' +import { ProjectContextMember } from '@/shared/context/types/project-context' import { useTranslation } from 'react-i18next' -export default function MemberPrivileges({ privileges }) { +export default function MemberPrivileges({ + privileges, +}: { + privileges: ProjectContextMember['privileges'] +}) { const { t } = useTranslation() switch (privileges) { @@ -18,6 +22,3 @@ export default function MemberPrivileges({ privileges }) { return null } } -MemberPrivileges.propTypes = { - privileges: PropTypes.string.isRequired, -} diff --git a/services/web/frontend/js/features/share-project-modal/components/owner-info.jsx b/services/web/frontend/js/features/share-project-modal/components/owner-info.tsx similarity index 100% rename from services/web/frontend/js/features/share-project-modal/components/owner-info.jsx rename to services/web/frontend/js/features/share-project-modal/components/owner-info.tsx diff --git a/services/web/frontend/js/features/share-project-modal/components/select-collaborators.jsx b/services/web/frontend/js/features/share-project-modal/components/select-collaborators.tsx similarity index 86% rename from services/web/frontend/js/features/share-project-modal/components/select-collaborators.jsx rename to services/web/frontend/js/features/share-project-modal/components/select-collaborators.tsx index b65d4b3f0a..464c5b5368 100644 --- a/services/web/frontend/js/features/share-project-modal/components/select-collaborators.jsx +++ b/services/web/frontend/js/features/share-project-modal/components/select-collaborators.tsx @@ -1,14 +1,20 @@ import { useEffect, useMemo, useState, useRef, useCallback } from 'react' -import PropTypes from 'prop-types' import { useTranslation } from 'react-i18next' import { matchSorter } from 'match-sorter' -import { useCombobox } from 'downshift' +import { useCombobox, UseMultipleSelectionReturnValue } from 'downshift' import classnames from 'classnames' import MaterialIcon from '@/shared/components/material-icon' import Tag from '@/features/ui/components/bootstrap-5/tag' import { DropdownItem } from '@/features/ui/components/bootstrap-5/dropdown-menu' import { Spinner } from 'react-bootstrap' +import { Contact } from '../utils/types' + +export type ContactItem = { + email: string + display: string + type: string +} // Unicode characters in these Unicode groups: // "General Punctuation — Spaces" @@ -21,6 +27,11 @@ export default function SelectCollaborators({ options, placeholder, multipleSelectionProps, +}: { + loading: boolean + options: Contact[] + placeholder: string + multipleSelectionProps: UseMultipleSelectionReturnValue }) { const { t } = useTranslation() const { @@ -58,12 +69,14 @@ export default function SelectCollaborators({ }) }, [unselectedOptions, inputValue]) - const inputRef = useRef(null) + const inputRef = useRef(null) const focusInput = useCallback(() => { if (inputRef.current) { window.setTimeout(() => { - inputRef.current.focus() + if (inputRef.current) { + inputRef.current.focus() + } }, 10) } }, [inputRef]) @@ -80,7 +93,7 @@ export default function SelectCollaborators({ return true }, [inputValue, selectedItems]) - function stateReducer(state, actionAndChanges) { + function stateReducer(_: unknown, actionAndChanges: any) { const { type, changes } = actionAndChanges // force selected item to be null so that adding, removing, then re-adding the same collaborator is recognised as a selection change if (type === useCombobox.stateChangeTypes.InputChange) { @@ -101,7 +114,7 @@ export default function SelectCollaborators({ inputValue, defaultHighlightedIndex: 0, items: filteredOptions, - itemToString: item => item && item.name, + itemToString: item => (item && item.name) || '', stateReducer, onStateChange: ({ inputValue, type, selectedItem }) => { switch (type) { @@ -119,7 +132,7 @@ export default function SelectCollaborators({ }) const addNewItem = useCallback( - (_email, focus = true) => { + (_email: string, focus = true) => { const email = _email.replace(matchAllSpaces, '') if ( @@ -200,7 +213,7 @@ export default function SelectCollaborators({ addNewItem(inputValue, false) }, onChange: e => { - setInputValue(e.target.value) + setInputValue((e.target as HTMLInputElement).value) }, onClick: () => focusInput, onKeyDown: event => { @@ -230,7 +243,7 @@ export default function SelectCollaborators({ const data = // modern browsers event.clipboardData?.getData('text/plain') ?? - // IE11 + // @ts-ignore IE11 window.clipboardData?.getData('text') if (data) { @@ -276,20 +289,18 @@ export default function SelectCollaborators({

      A z@6Kr+KiZ0sFUu>;W$$G%yLA5Qs`yJTHwrvi!R>4vR?$vFbGJ6TZ^}tmL|VyI>p4_p zi9;|p0-Xt>`(%wHK;rsx1>(G-{!?dKUE(WQSG6bm8r;3U7#Q7QP(~`z81AjFh_~K+ z*>3s?y;)4H#k&Lh2ro{eEw5%dWn)Z!&6D%~0l*cM zZ=)4o^vs~3*@T1v(|8t($hZBsZZwGaf!t3hd@lPgpt$$olvD7{Ju+*QTG43-VBOQuAoVBq0R>gKJ?vX3Wflf!K_6EvXAFleETW}1da zmZs%=MR6^g%{}63b*2zn4I3I+RZMbxm~T2Y=lGx?kEuAb{7rMc8xoWI*e zjNs;^fWe+Colx1lZXy>CQimoepyX3BW~QPalbKS*QP8fIBN4eeD9b{~I}1NPXnKk2 z<|g{;JuXbx?ht|A%5CRvac^pee(rAT=WHeUEJku z7~vE?!wVc}npjYRb3||SCtm)xem148R%vk^lU?LiLWd7`td3ofxas}0TBLsU`5gON zR{4|_xj4VduIxA=yR1pe@v~q|5)u;n9P7^=5oJc`kzOaaa%Gq6*<09dS7wdtt%g0}v#tmN9XYC0~C=K@7t4cNj-#Zqd)??xJufbn< zmu!>fGEK;$%y^pi2K8BQj^VjkB48#)B3&9xVGzBv6xSbRVX6_*5*6DCm6G1vIGpdF z4qcq-as)1rS;(_lhI;p>kn=eoX;rZo@q|-Sz{gPmv&nCz8%4%aOB)yAY9n=I+LJZ=qup6rj0$H&(N?OJiiWd zx^3U+ov*_wAtfu6W{X%DIf^HOCk~@|h&Kp`+`q!J&>A}*Y13{%hA0OJAmEUehv^+z z1gYVnG3~RZ*Q!mb8)Fxq@_ixO)^Fl*WG=u#zV6F|zZPYGnr?$wfun+5Ya*pDi@KWn zx^9u1rsT`rQc?&%s^z5%p?xwDU&NuEzAbTc_6tlc&Ce8>D*CT#3L!q;%=RhKRl&F> zY)oet2_@SzuOm?P>9^o6Pa3tB7kEl)G_32DN)T_$quRr5XgNsG8oV1=Jy5TYoiD)n z8sJnS_x}HGOlscX^#$?h@otW7E5lh+^@ROKSXH6;;(EK`V+y@)K*zv^KW4E=kL(_5 zTW@Sfj}b?*od3QXMM$AD3*Ferp7O<;4#yiM*f+`Es+dd*h}Wd!;gU_y8`oFY5zQ$v z-c}>4&>)R((WzOa1r5Z{&N#}kqSfs3uwnLm9zmX4Iv>?7uy6FpB`lXEw8WK1R@E8) z+CkR%JXy^IW7wuMJp(bDq%upZ_PC%>5Ypfxz|lp=RJ(FrEcZOxk;waoDz84JcClhO z=zb+`ElMzs;*k;bZ2cUJ9vAgplP>6*GQ!A5i>nz_4!r$em*RH=pcV7CHRAFA^2HBW z1H%wQ{S@$_`O2-Owr_hn*{@V=EyxAx!_+Eb-oTA1-vOe6ehQxN;IRBOzd;1wA#k8j zZee*RsyM4%fCeSp`ZMv(te1bj^5?So?_a?&&>F_0pg|Y(Q5Q7;JQBV1vqO%*$`^KK zO)WoZ?~C+;>-1mQBv9+k=5!F3o9^>r6%1|P`j~|da-0KZZW^78xA`rohK zizg3kum_2aKE>jk3E{rd*w-jlNvr7wC{1!v5*^!eWaBOtsJNyyH Yc6!!(_ix5 z02IOz@@K^MTIU{`>Izr4DL3OKT(A7mNCSo&f8k0J4qj&AN=G>1A%8yCDKa3F=A=FU zTuA%rJ?|Ft(@bm>t+3rEWfV#jWU>UNvO>1{Hv@0XQ&XSEbQ!igM6+7va2dUPG=FJ< z1O9!NZqDKmf5aYl=<$C@=hk*fL~Mea?ZB6nfV+P7RkpYNWa#Q>?;DDog0R>xN0hi0 zeK6e&8nHF*EmVl}Kc;M)1+7cfCE?fUJlg6&pI3y(%eXcT>>vd6n$y&I7dgW%E*Z8) znrN(7h|U|tm^btC0^{B2$el#BuF=um|`HPYwcjZ~q?(V)>{T zm`Pd|^a=UZ&@D=eY<UED2 z*4=PXy6lD+Icdx>fHsQ-PqPL$+SuU2^O;KL8b91*$g2bWj7v2RIIUek%hUBK` zq?ba<%LbS>eLn(ibb%M*0@N-XlH0{+y|8Hth;+9#&3Z)SV$PSq@Zhtj&pgHYD27`J z=C58(7sun3t*{>33L@BM=7>4kj3j0)qRM<1b#cR}o}Y_D4_D7secilLfT-J0qq1V= zS3bR;CGYsezYBoxM-Ae_%;`7a z#{WG}7(N@W!JLmM@G) z#R7tZ%73pF{A*w-7-@w8D$8#p4=k<4kR`dLTXq9z0d)o|6D2Y*;s4eA2(?9#jK_H^ zWj<7S_`Jl^(~dCeTNUAT__UCLD{pj#a7M&P*0N&Hsz;Q;fDLGUQ zh5aA9VKKRyh7^Ld@Vw-S@fKBkgCXa%6!~`BR3?h-Jfw@l~ry__L#mFOK ze=n?01i(@-ZjIvLpNP>41GJzwumf1fDv?3;?|{L5{>N{Z0J+WngGX9Rslb^t1i+1LIsqGj8LT;2{&4XcQEYQsUP;`EH255R12+WR z<{pQ{q4XCEP+J^H3353~rIbnl4hC2Uda83gBJe+4fV^>({r`9am51YRj#X_H-t zBluf{e|c3v0CYG>6SHKQHE7h*L9ia=Nb)}%1HjRcC#D5J(5WI0)`0?(eB}1g7X13} zI`|^62IN)pUwC~ij{a{5_`%j71d@L<`&+`l#S11Llmt2b4HpOjEnOTS@Hk(=l$KJc zf5Z*|o@>4Sg%CsvG7w?}U5N}StN&pi4wmE7FVJVdmNzW%h%d85xMqLrNytdzCCBOA zaR8nbYEG1e;mQKy{lj(qxYOHau~NDncwDmY6t9YM1!r@}n@GZO1AxBUPn@N`b$VRx z;9IQ$qTTMa%Mi0;9}e10N$N;O`Lo^;exJW5AE5jUWH&gH;6}OF z?WHBfWcl78zwY`W+ickzZ7@XX5>f3Jye=vKlz{rjd1A5N3dUL1S4FTTWeiQ`}RrIdKVd|6;3>>MEvadOhn za@%FBD7v-y#vzlIU5pH^nOm!{c2XV4&AOAW+^ZH$g-wq-yZtMcHmt47D}NM4ji_-| zZZRzfGVz43X78Y&$s$jq^qcC|g-=Zs?}TRJ7a<;N!qE&V#*wELL;JMm{kemJ;W4in zYkoRDK&oOJC-oypW0G&AJ1|gVYu^>r6u#I9=|W|hSgGh$6SLYr#X`17I_rL|l-g`n z$+atVaKfA%yUT_8r1of}o17IINvqOgG9woid!!&Jmb2wrWs|Z@TkGKSN|M!7xK#hP z&2%8o3+>aRzJg97^x17{G&u2%A24X4%u!B6TsjMvD;%<6lsF7EliUp1$K?X+{y!{a zghzyw3Ym9JljGczAx6+ju8WXsxTMmau-&8;C@S{NLu1+#nseNhmWy0TF`Sgi4f|Ji z%!-RYLrX&Tn{UaWu6T2K4Rt==5$shw+cE0i;ul5BGUVZhigu}<7Uh2%eA1ja6sy_D zi%#+?LiQ-bUyl>miL-JugXX|9Fac=3G3-LZ=Ju7`kJ-DbyyKRBphY}EZHlRYbsUeOd{L;HItq8HVO@sxNh#}XLe1+s-A=R`_`n6K z)2RfsOBCHTEjpR*l&U5o(PQh647g7viPj$4DG4Nz=6!X@Vhqfnn8p)?F^t4;MVp_J zeAMHl>td*Esa;doq<@ntEL>7IE;({S6!$VJU^;<4d3AliVj@?~LM|}E818o5{6zDU zHjZi`=;@*9M+82CD@&gLIZ!~Ih?BGqu^{WCS30}1%W-844$M>f7&PWZ6yw6ThiL2%1oh$Buta96lE*YB9t`z2LvMoED`_s>hoed&6dZ?jx3( zz=o`D@RnKn!Ad1JB>?gZ2fvpk)xQv+ z#NK;8dN+%+dRfZB>L}nJvk~ZU8s4m)x8|)Ohcli^VE#@TstAJ#$w{5omb9$EsFglS?h{Y-oe41C%!=FH7knN45NH9p09=g8`#kSoLhGKv$waHF5{6v}Q$u33 z`D2*I6E@F?FFz4myl}(T`zB&Lrhso$oc?9vnG8BoEnTw-Thx%5h|AYbjjerGuI2`* z6NcH-l|oz4Z6}s6i8)>O6Z1>qd)bqG>qvNgLV+vJ3O4Asj?X&Ej6w0l3q9ubjjbCzpsdG$21?M@!# zNndr#Idp(G!{h$Z?%ojP_!_dD`*0@zkaw~wbYF#WWm66gjDzV|T`j3DAfb_<}xcj8CX z?m>XmXQuw*X@Q&Pa(!Ia_Y_AE0AD_W1ni_Ok)b5}_fy|9f1dhY1-E19r;`FETU=ue zT;Dw!pnOUDCMhdLCQ>fJoD!a}f#~jKL+z~1+(iL{mL7YmAK0y1VVS`c>K-#U;5mbJ z9i&Vvk)wI=_)^gC;0ed!3#`S#mQLsb&N6+XXdD3AW>QvT`|<1u9+(V3A8Z&GsA<^_ z5q4VucASp}yw0)zel`(2dKi>}9@^k>2!P+B{}X5SSUU2j^!I_}PaIZ`A?OJGf9E$; zU64L_ZZ~B4?H=t#V_X9O=c2E2Ko?o@Jn-Wv%@P?#GXMK#_U|kh0OLiXC|$P}Q&1ug z4KC^3l!H2yRKiw;Hr*i{?AKI5&dogDSBIQ01ixu9(l<|Ch%wSdslH0OLEY6zfER;T zSZ>JA*Bpa^f5~%0Jtgu4we;ZOzE`MowcOXL_2FQc1IsL~<)f28ZEGGY zibYwq`j@sbF69!fn8Rzfx;Yj#f~szOv=ju@IFX75XP^DK`F7dXc#j|kB#hL*B7%|a z3qtc{npiIJLIG11-n8qC7~W5P%i$3QFArNHvd#h-1Wow|m3({*6U7bZQ21&t>yizuVebmZTH&(O@pQ%Ue2aK&rqlI588=c6m1>rOwHQHnP!LO zGl(gWeR6U85f4Q%U@j&WmW}1X8mwT3`k>ComKA({9 zse;*gv1YN<*9b=Me2?QKqVO^UJ`YWbgfmhN5pKrrpi+4z{CHcdnCp;BuX@BH@{M%Uel2Je()jjI1cocps+&qr7fl3(b?In0#yXJbUP;4=ZC)X0@ub^!Q47sp%iPcvnEpRHW zxJa|Z!nkh0cA~L3mJPqylv+fXSsgpYWuBVn=@hCYlY%MLc-LoVrL#EZhE~Pgt@t@K zj@}+W+*6TCV=;xUagGFTNBUUqeblUMJ(D^`SW)HA02yP2_!b_5!FB3^EskVy237oS z9krC5R8KzX+!hJAs+v^I%4m*K)wPO=lWEn6)_tQou!mVerZ99z1w-oJbs>|-T=Xl$RvPxn$V;)7`>?W$@G z;^~ua4&c0Zn;iE&Hc=_SGt1 zGwEnv5?}4!vYf=>!~uUw7?Ejh9i?luUO*mklm%uWajK*z$DGlHM$X%NOodSmJQH2@ zS^p13RwPqy4^KWDOA591uJ&Qw(l719jqfj!yldPq!Vfn%J4oRe!36!M)y$bP56)Uk zt%A)V=oFnhHz#cxRdO!g*`W8caX2Fd<4(`Q`<`n=tIbe!7V=$oW4XursC}1Dg^J4`tbq$I<4}Q^Chtcl(UBJr7i??(Y zu2+uGLf6v*t?mAb=aP-p|D?R1)YSpIbJU^3z?N*Uv*C{6{Y6p%E=j}}=pEOuWGBUD z`D#d?TKhXV)uIi3v)7^Sho6W_(<#$NI{q@ZGrEvNu<4*Q=(Q&@te+JaTNgutjK5x2 z#V)vS4Ot12OMi>E4UMWAs|~7!8Mo4 zgaGsF8MffiYFR{0d{t!{8Z8H8J~B!Uvz6xzUp1CNdwj8UVIY{awTfOylYH(N?WHVQ zuPXN^ai2zuiP6HK11a=c%d>BS#SHMWw8hi#-G`j%JALj{{7tt4+>ShS_=ZjN$wyNf zX6l21Miy5u50CM%-W|ez;hX8GyttF(nJ9XX801`Qt#s7=jN@2%rB zJ_vdEu$ouLLnjTtfH5DA@!#z^MrobhBB%ZT=z8mbKAzlv_>EIsN^y6W;_mM5?rz0e zoZ?zsiWPT?yA><$PH}fB#h<0^-S_jmdyapA-Ar~jlVm0{lULB~+r~eoK~^{sYJ3A>yKF_t(t_*!BQ5)WV7@`> zQl`(Y1}YWP=flN=N$XK;Kf2}h$KgW>zam0@b&X2|j>QxLJsOd2Mnewp)blXkYzdZ% z1Tm4!wTe?-JBq9ZIuTL$#fzmc67Hu-!glLv-^0csf_JogquIFI+dsR|1iARD3R$GE zK>gw)=06rFo-wCtEq#>JmbHLuKz&>9bw?PV@y>&vYzz|{j{VE8ZkE+{QTNz@vQ1|2 zFK-1iMc-RwlQ_M#(^3}CcA{(Ab?{=fV=5cx3s=aV%^>BwrRBj#$j0slT>1l~{)WKfkN{^L!r%nR zf?DKS*?e@}aM+>c%DnddeA!wF0s^JbsL_}#z6@7>8Ei()jL*pRCRCoCI7|UVfruZ> z!ZGU+4Lg3^ZdYU6eb1T;u*-0~9d#mJX{)P57&%3))GPnEf2x<7`;2g)KmaG+y&)^H zR+?q*^Covr%%qcX%JhSN-=XyP=kSQO-QW)C`B4NYz2Mx~EDy0=P|kxMlxWkwD;1i4 z*9p{JSs3yg0)62#>{n40Rg;5u%H4*tF4mWz{9Qy^;aMwTyH7s_hlG%=gh%0SzPy&R zQBZ}C$?n{pR2a?B&?C!^?`0%R!i~3#l_`vM=r7!~i#s|{flqVoy=Qaz)nE<37~!C` zHh7dt6Y-USe37h=)q%slxXHCYe!3-E{8vD2oy`t|Hq1&_7W)DY#c|>6qy8MJ4-P$A z{u*2)a)mDP;Z3k0E(O-3GpX{d!<_GrQcdL*qA~}#LO$mA3ky?5qhg?8b4G{H9ZQ%>K$=%p$K%8Gpq=ibd9AB^jGRB%$qB?_WeQ0-NK z;a=e(`@}J?O)ad;QhtfI99mH(vVdY1g6Bp+0Iu5#RLmSePJG^y5E$Bcei*QUz;1yp zHJhYWwjsIpM7K{>Yq(K<_d#Cm(uukRxg2boEEZ0-VrV&_#(TJJLQmK#E7Cb1OJLWP ziN8Q6440d3wm6GDt+5-M3~u<2EY|&js6G>6fT@hXC|bOU$XIo7&)J!-f#R0oTX{J1 zTzB8B&aqS<=thRL+;Ry$i_gcO``r#B8gfH*k1nL~GT9{;Lj}_8+5JZ=tv16?N#X8vP=qHp}m6Vwt~9`6)j>?Ty{=&a^(eGFH_5^wKAM&nlND-iNg{lr?WT z`-#>iM~X%3*CE>Mx8|G)3#RkzTPZDC-Q@9v#| zrNsm5#X<;s831NFRS7Dr8KDu(6@Q@Oa)xGcW?n^A!h!dzo>*%O*(KoIb^Oiz;H~le+Bm5^uP3E#>zlkfxLPIGXFNjyw_^=>Ca@{=?mxS*Pwrv0O1xP**$-aZRxCxs z<6CTMHUt`ru$O0pU^pHPlrGQ^D0Aff&wi`!5BA2M2}`KXqbw{rPMJUnQQu_y5(6zvh0XK~|scV=k=V9LfG!%mq>!VKvQnD8};L4PeFM#hcZylrdEx3e}>VrByo3@-Z*3+R(uHdsBHH9~ziU#$=M#^@?trZ1NHXXMKk z2L)JWwCq2$y)xZllK!-Jd8!^H0ebcQOMe0HD@oy(_a*=+0r|e@5k$Z(GWcwTMR$%d zN}a<4X!jg<0-ykPK%)Q379xarj_2RlFfTWT|5yP;_$JOVfC2+zA_iIjm_O0~l4htx zg~e@#k%TGA7Z6vg_%(1F=+c<~SE3Ct+bICz0o8yp=|~clAxAbqMJ|*U3jKQv;YFhV zDbPRm_8Sw0N+>h=zh%>jyRmp}vkd-KW4-nQ!eFQ)B1jI48bC0 zFXu^Ng8~Bq8ep9w>#syW(8sv+qj>BE+?_D&XB{s+0XT%LztZ?)^uO00UatNX#XqhB zl|QopIsBjM2v!|s4#0w<@i_6JY;gtMg#d;?A*B@aKU*chhyLex@;zt^>WP4(&b)Nc zrXN5LthZ##evtW7?QeMaD>`b;Je@7^)o~F7huUgPHnd=lx7N#s5%3*Fbk2Vr+A-Fo zc(4dwbQRc0E8~k=ZwaLp2mjrn`FHu?e>;KW4n|ek&SuRr+qIwtxUZT@2SDm;jv$SP zYS9CX#f5!8`_qhl!)>s|BzaAl&8D4S{8??)$CWE%Xy}R=In1ZsrXeTj`+6+3%8Pe` zsxC~D%+sB$-5wRCH^Vntx zdA+ZsFZou@V&7~n#A`Uch=8 zLh9gq!3Khgs4WEz+b{&77({1E9l_H9Lcmcsmi;E2G=(H8$+Z=;8qS8)%n;!-5kg%Z zeRX1%ZqxTud!&9L?9u24+n}Ckd6u$xU$8X%_z;Nvt@aSePwz$71OPt6e{2&st-H+N z83d{{Sv-B;k^^_Q{wN%7Shj;KBzNtAT=4Stjcc%Kv2}DR2_@N>RBY5vRi64aJNo$%W<24IMqU*4P$;_uU8iG4c@9+h zT`7?|OvtLY>`oFNHe)sfd{fUfj>Nosb7R z)2mF*ksLGLDQ~ipy=f1>ZH9={i$^eLevEnU-p&~$7zBwwvJB@M24^(HpBEQFT(15u z3MUDcIrvA_rX*f;vJFNxjGFKOglf|_g!jdjFP!6PAe686^#+DvNaJ8E+9Q3-+_AVD z-D2#aNLXM#&%n0y?FT4PC1N8evZObeM6u}>=lKtd3-oZ}7q6c%1Dw71% zI))6qnH{?+DLFuw5KagX(|0#v*K9O^(}XkywaQju-h%f10A7Gj4F1(So(IBk3c5DUH zkYl!Jf>E>X+sqM(&>Ly+y0lgMxDTeUhq1$Y?!!G z$sPbZFG@Y(2w8OUu&AQ_O#6V7Y$2gJk)-9s;F0(Vs!y7MB}#}HPvHGzr`36-F+|QO zPGh>c4805SYkDE1D0jE9Y!U^=VMq8xpT@`W3lab$i}mG$gfb_O{_jx6=H&biaujMY z<{An%_=cd=%CYThGSRrIFUlaD&PA6v`B(Vw5ef#I4RPnD@}feR1$~Gh0()!mn&6?A zcz`75m+AN-pldJE9+bl8PQSCV>_)VF`L`OC=*3(B-0&rYKix4<;K@G048m#aoP292 zY$-)A;&TV9QOSodDAdADKEB6X`i4gf9sT4uA~ZNuGm9Q0iYW_?d<|d%xPs;XDK=<( zH8h`Z3EXuo=)RJ?541g`q70o|lIMp6`seZf%VAgJ-%PwXng!RaHF zjJ6w<*sf=UrF{(NeM7Ox>f5L#D5x25rR8=iqJ(p|!gW`oBGn@;$G+f2j88(k6)Dd@ z9#TiQi#Rk9c7i~^cS6*KGBY`#Xe!ia}lDmZb!~2N;93TwH!xkV|F8Z2%Oq)e!vF4g`N(bvhQAZ+fdS~y_D?Uh`_;CBz1Q&3Ne(`*L(+IpMS8Ab++)B>co0Txq3}y< z(%|67mo|w|81CR~#GFYYuo}nW7tQ4WSVaD15w08{+E4271cqGWD|;&F_9^OQ}XEW zaa2obkSoHBqmQK)ZkmXyJS%k&vuqvq-?f;@<^#@2&`yo z=9CI_z?#}Dp=Dd7;B180WoTs*gpKF$Yn?Xs2C{|afR}I<%DjqvvGISZWj&twh0Z$# zYK%KkULOn!7kaasd%~C$Zc-s{XW3;=)6^_jgwrz%f_gZa^$D5r=n5%a5$jer%EBDE zHY*&2z837aDt0Ni9G2xAZ|FbPb{BqX@*7o723NojP=L*w5hM&#xT)*o)Xy zNGo1ieqPo}-#5Mt>UJEh7(kZCkfk`LiAt}$Qx<9%tAx7-Yu1Nscr zbSim3ZvKktF~9`?pFx!Uqn!v_afSuilQyLx2w%a-;Hl{qA$=GVjlzN=tI)-Q&DY}_ z)rr8j*`{Y>;a?^!Uxd<;#Q&#eoghDwFu^n>8yWKMtN(NkB}p5Rx^5KuK{?`-`0Zkw zi${FfFJyzD*w67t7U^W@Lgnqpq~bw@N@=ss3u8hagf40?(he;H11@jEvZpaEie&J2 z0xtGojpn(v=;04&Ua%8Enik7gU#x||DFz>TK^~+F)n=#Z-mS%~K`EdO;@W^tkX07{6x@e@-m;KgO!wU&8_U1x4b+aGmml29n{(E^K`2pjDV0A%Gspf5!&~jDJU2 zRkOOF2Vq#QHK}q&Jb&3jpIi8B{trK z7(1+&twRMSEa^<}BFd!;1C1EYWOJ|YiG*-nP%QBs^M^-*Y=+ky_MtaVNp)|hMKlo5 zz3w@lvY(|OAK$EKmuOPx7DJ#^0^f0w1ysh3U%+z5_f7`&R)xES#*n#Qt)238wGI@go%85$C z;vuzXT~?aGTcgwMz#czZM}@6ReTpzTv4RL)Xi;vkw@5z{Ov0P}j=awBoxrz5h@T>- zXxVJ+GXtyh71ePwUG)s#fQ|YxQa%e2l|d)rk@4k4vmvK5hRLOA##=U7T1=x9M7h=z zv5I8yH~RaIj#f0WA{ZGlyIPWyhH+O^J{ z$W%`_{4+OXG+K>%FVDP#VmhYr;Q5DG(+CUOSY}d9fohY4x`Fno2UZZ>h!6N-P@xp1 z;ZM(NGL9B5u{DN$aPh}vRbdR}Kj|$8eFmV^My|WvVJd@)1zxLQdz_gx z3QSK@4JA&wPc4jdewRDkaNc<}{^Y}Qd{5wrj?oX{=nYGi($Q{zfvR_rWDI4rY;SNg z`XOOY@_Qt;E)j&gQ;mvv?j9BGZvRR2In2#d+o3 z6A`-ZsTiZc-FlZ5=-c7QzfF3NgqM*&)kJp+9B35po5f%#6KjAH4pmjCnH$yYv zf56kpocQMaRb6vW>nY7O*2ut{?uczid50pX7n|m5ZIg68P%&&9Uf98u$kcYpd@? zd_NPEi&<(W{UoHS5TDkS&(H&3UxOMQK0&#i|5#P~6+4*Td6nBX*U8q0Z5GrvRY%Uq zdjCA#JRhd!qP*0cB9$^Re~c^$=^R5O_~Y(;NAQDy>31&?GDy*LDEft;=BAw{Y=YlK zokMFQzAvIJN6M!*BXVshJV&Ci3CCxxoQ_NLZ*kKo1*>l`x<^o{3g?`sVWc^jqZ5ki zjM{%GLkN+x7Kj)Mb#0)1Y#G;#Ylw@aPbpoSw<&^(ARV@oxBT2i7=Dj|@lma-SaQ5| zLx*o&KS?5DX4HAKlUA8H=zR*kC&Y)Kwy7&o7F(na*ov;}aJ#px-TU zEmR`wNO|EieiJdjmh6$#1H;iVrIPxYY-WzyEX?%Uz|!2l%|s`Afv=EgbwKD>y;8`m zrsf1?(J5DU5XI)aSNwqF@!58E)Z$NHnr{))E}Y*zH=L5dKMv{MCEj+PfXO#8 zltJ-3mlD!RC->si2PK1#_o`7}BzPa#mg-G;z_P)7BHEUjlyeQmYlgc-h0ODUsn6>i zy7c`veJ`AWORy=u65NKx#Wn|q(q63!FlJa{Q?Ldgr5jZRt zBB>Ge4=LI@uwWO81A@>WfQG=h%V7JDakt{mbo_$AsyVz%M~Tflfh;1;h?I%D7eoD12^d=o4>|Eb80A-;mNbSiP_w2xn}nxl8$a+AfNjQo((W1&F% z<-IQ>bQ1WubYiqu%wY2U7F8@>!hB)Q&Xq9ij_ z-u&TQR*1eNk~k832p-2e#Xf$4>$SpLnd_;k)eSM-fs|^b?J0+FU>t zV-<`%kG)Q&SCBs*jU`y7wg`*GZD3wK*zg|0Dw#7&)-)#M7Z(&ZbW=UJl(!c3Dn(ak zKoy>X4oW}RxAe#genYI&EV-KeGQSr^LZ^CH2P)Ts@F=+(ZK^_?E|gmGLG~GY zmxuScE$(n$Pef2FXc+IT9|T>C3cp68l_u|BKZnG_U1R`p#BZE5A)zde-=Y6MCh6yh z-@xQw%no6j*NPp2g~c*M7)gE+QwCXSsFD51@wNSUiF+R3R|-LzwQrq(*#hXu0T3<2-;(IV^ip5wg}-TOd;h_y^#wj8Hn~&VQQ!Z#d(Z z=U>?4>F*j2mA{RTqHH@8UCIpfS>)HPsr$jJ*`u#!GtPV(=APs{c-)8tK;-QK>==cz z1a1G*hr$E9Y`EV!L`i|b5(0vAAA~&BWuHfa*qXTuGy!np6psG!?Nx1pi~DS?LLVmL z=if#n@)Uu~#IELcMg9i&rvl58MEFhovd~^U`pRZJL@!Y~>Cj;g|O<1>jT2phNi zWpxDb%96wX@U@e`9;IcM7Tu&D=r%?W_pErek;>5OC7hxYN|y@$pF-xKtc@ECKM^g) zbom8B1{T$UQyW=IvM*AMdWOLNmGmX)=#1>@vdtCIe>kY_)7`W?a>j8aCsBo?f)o+7 zZ-O-wfOJ#@(jOWrUEu*&U!C#;c#wl-X#tW#;Mw~2>JEDfUe*t^;=r}R;0J=+%ma1# z1rUP=LyrK2z^7~dx0nD}GA-MGY0ZJ!FbVJLX+y{|>-;sKiKI}v*8fx*EY~0yg%cBR zPqdWH1!+W0D&Xv~#|1+dK9ir5SQ4WS(YuAPv%)#2H4r!@zzYa+jfVfVDf@eE9R36u z>^;Gy0fhPL00<_-3#6gEz9e*JcjWH`&;ww5R%On=bspj!OA3zA$tur9w0Q{I|aJND+#iP?g8`J%YYOudNFZ(%698a+c4Pj7DC-Ch3Qj?Pd@d z2ha)-Xu$9X(PyasA2p$eU1EOO@t{Wk!Y|5WI^Wj;k}yBWUJPR@WO(KyxR=Ec;2pyM z3KRqloYO%F=E66GNcW-vdj^3~k(!12`GE;M6_`+bp(}?2G8VM0mDg7&f3-fYb&VP= zs?PHthlg0LN4_3^*!rwr5kp_^YNC~ji5F+FwL8^#LnJnnG7t*3Sb;jbwua<7CQd*p zylygJ>-9Dkd?dvJ!DudhpYQwn_lFxNelH@UmfcgOVSku?v_?0JcMqayO{&eI>o-)q ztHVM(L-WS&lsbny9|php&1~ zf5MaoJxlSINFTT}*R;Vg^FXioW*fH3-Bf6IO9YzMGIx51)w69m$zFjo@GlqzS7Omq z2f-RV)?X6MLA6O8AxxUNBUR^$p43WMAEBPKj%E9tT4Yq&@|Pr`V{-Fh3j?Y1Tur0& z@s$dv=7`1a^Q3GZ!8Vwf!cBrz7E|N&K40>F(m-gL9yxN#;l3AE6XiL!E7{p_*`*@Q zdXt+dOPApp6W!fM{Cs#2)zIcyme7joe0<~tBc0O9U34y)Gdi4W#e-!QrJ7av6msi<^=U|1CAr|#)JxMOruZP zfhB(ytSdrVu%#XgjY=?+@p6+nc;@3`T4{D>1!UYfW{20OC6{TfgN`9s(I^s~R>qYY zm%ibvd0kF@tC=>6YkxL8aoMG>Zu3n3&05}`y*lIh(j}!X_GS+F-Bac{JLWL;h}}5<_KDEwS6D!@mwbr-f^k;rQA) zC`*2H3vtq!f{j8u$8`86)BEno1-h{oSC?l{7&e0!>snc2;fVeqHoet`HRn|?wA)l< z|74~qJ1>iVDhmS6;G=YZ3hKk`J4>}Tc74kf^TcxG4yyoENVNh3Oik;zAq*D5ylzsWI()iLr~dq z3+0jD?llu#s5*HU?t*8T(*L|Zw^;-!Ww9kzST?1rlOL9MT(j;}nSe5KwIv=tbV7jV zy!e%=lq0c4tE)#5iVYTVr$v{Mb@c5wnu{>|Th3ioi^rslMr93pk}TJFGwtAH{nl+ImO&W!wJ_QU0Dcz|{PK)d&?d~fW1%E4u@~MR^U+1@v zy1hN%Qx(BOpH+pVjbj#Mc4klDmMKl0NFxUm-*+Pm|F9G7prF=tNfuNv^IB>5Bz%=q z6-&2%LAJjBXie?2Yo+oHzO7KqWY}^$9Mi+i9@g`skj@mDb$C>|zfYTjsM~p@KhcQG z#SOB*SYG(hq=v~u!Hp&hU#b?f1T}bKj2`&*>qelXn625f%qZYUeQBeD%n;M7Q-EgAbsELHk1uFowoT z1&1`m>dYXmxp8tYehE!FSCj%SIa_m_0);EZNvV#yXdsYr9QcMgG6VCVX=%X+cVFM~br(an6w29mx) z5(PcrQd2^ywuPf=Wod%R7agXfH0byZiF>g=wLNT^SprMc)JOM+uw#h zUeT3$to#WF-O8Z-2Dgt=e&mLpEH_CoJ!a#_4eL*gF+FSa{*z-Wl1-lW`QThjiTkp% zvQ`4TT=qMg_L21Reo4w}UIkH`-j3v>N`9}3>Jh>S!6Rp`XBw8pShsyC$BwU~n1?LK zI2+6Nl*5tldej(c?)2F$+Q$v?g%ycg*g6=|luC|26nBI!CJXIw63;*fz*=zcw@mj_ zy~otNzy_n;-at5&;)L1H_Ncnxgs9poO`Ex2Qx-V=sHrWGwN4lDnxZX{WfG z6#B}Kk*|72X5`;xPH!OKMWL8N;T$L4!`|Y~iZy!|nee#zYk#wzJzkD*>F%n=ee;%& zTD55MI9f<<7p_P(=pEPI@uyhNcD{nKL1_E)+Q|CH`^|ZQVF7$mo03;2VrOwCke1Am ztaA~}4kYiYDLOSKyQyW8pygZZF{TWG&)G%Tcxzl#gP zp*-}-!0%+yWu>^;hA_w>Nf$ZrSEj~@fL>}L(U+QqLyE0A-q*(-V+y}P+A--UMx@=Y z8f>yncJbK}=S7pS_R9C!c71r|66T-peqCGzln1yQpv694KyKcKPB2ioSY*bUO0T)D zc-1h`kjv*Uj-cfVv}VnpVfgZtbd`oqZ!(Q!wU_L3_r|dmB?J^TY~@ z!5NuCr6w0V=XC}J6~miP?G1$oq*KVk4d+@_Vz_xex4vgs?u^C^G&td_onFH}k;9yA ze6$w@I=u)1$`^}EIku_r=835xNw>_2su>;L3WQjIz?hXI@L&eVo{@wp^xz{ZP~3aX zV<7k8c%kHk=cLAX49rD$pm3L3(Xzep`5wAb+fXwHjWAtLP@z43uq=tf6{*q1G1ot#t& zf*#wYgXd{;nI84FV z`wm8j-a{I5JPV5{R5hQQtumFhwB8oFK7ge=(Fna6k_F2G(-E?2!rh^m#rb39v%y<% z$@C`Vo`Ct{W?=yzjP_a`b~CublANLZhK+k$dKKPzsI~&)sCQlC><8^RnYac^x;WzL zTCKQJ@-gW{2lFaXN2i?A!MhV!BE|Z3DX-a(pM|{FEGPIH75u&5h}3+sMX!0C@@tA8 zZc|eQ5u0%M;lz9eUdB<<8=BlsD@X<77WO`!rHZ4KB_OFq73-|L;U^PD(T8kagUw1( z_oIO<73cn;_MqM2b~n~nKv9*TZzw{#s-k;tEPWnOMns#F8@D*w4W{qc%o9!2W+e(6 zkT_xc~Y9 zCN)oGYMrEM@q^5LbihBC4cBZhe?xJ`@@iH75jU3vn+jRjc!HwefWeh1At+3YMoS_@ zASE}8z(p}&*YPW!?Iv*e5Z_)`Ikl6+Yjoy!5OIeFjej9J^P@^pcr(K*+)uv0FXK^PF}MD~2FxRU-x){w zVu96@7(uTsYXD&x!tkjm+y3SgzVEkMC@qk@V?J_lmzkPB#7PSXRUOkE>eeK0_iD+& z^TQ%lF(|!Qg3a6^F!*tq{muNl4m2KPj$*J7)pk7^wHX9=%kP8x1r~3lLWKRU69)T* zOYceopGEeI7vZ|73e7HXuVU4hi9Wc}S`iabe}k)hHa|0FOAA%CatW&5#=An+!HT#NGXPEU*B3}2Csk8 z?)flXlkY&)PA+IbbZcNEzkPNRQik=K^DYdl7B2ux@cBfLM@@YvExn4gt}3-TB-Fp6 zc3B_w^Xa;0X!h}PK*CDH-kEuI;%=M6k3nrT6yE%|A2mnxvW>_vhhKG)d?b|4Uxr5I zu64>a?4daLCW<$+RXk<%4r@Lu#9rxJ;R;`luM-r1Kh6i!_pW{=MEi!Z=`#kqcKx;c zPrc&~EVdUSA!&k>Zq(xZJ~|$7SEdH;R`dGv-e$j0JYXu7t-Nh&^o0(p_-No$U?1f$;M+@5Tj+S7gnT zfGpJ)UnNPE?>W0~jJeFuAHOHLic@lg3YqK}PL3)C6L!9#OgQ}b1Ha8(yQNfjlEWPz zIk$2+Zf$43^~&Z%%MLDi_UCm0zSNdFRhyj{6{T?HfmN732g(rf$8nM>w(+__Pn@sT zx_#~x9Y_B17ZQ24#;x$>8_}3wY^-V47K=!q^3@^~vmQ5vJW_2RK67xA6%zpazcHbZf| zwE)6^$%S5)ZB-EUN^Z{8}D!P{FfJCG@NQZ_Ku(v_Cqx-Z|eF5!MF(c*x4z zQ3PsX<))|Wyd6%9)Vmwe8Cz3uJA)H%?VDf-e*Jnz628?rFBQw&5gfgWs2r+TCp{^b zkg?-SGvX&vN}KHzy*bH;gSLPfO5MPF^DXk+(F2CYW+wfc90XD>VhpnTO=Cl!_kQM2 z=3o7HJ}<$Gj4UH>aj?>|KQA*~hg$C`CaY8TKLbgLNht&iVO8n`U;X7;=?CM&YU~`a zA%sSpXOa#MVRk#$Rhf@E*6Ch*aNIPxoUreOKtR>bBV zh<-1Qvh%jgjk!joYEmni{ERivjqn5S`kmM0r6!1a4~GTA0CUGRLY=xkBdqDxHr zO=oDvQTB0e;}N9oWrphoR&LQXd)Qd0Sr2pBhse$5%`1tn(nTLOs+@SSg0xJTnu03q zq4&(DlX@=I@Job}Oo7YqOYekcej&c4do@yNiB#viAXb=tf7sn&3{whHPp&PC9!mm) zS*=qH?oh(}`2}+yw)Ea+TP~C;{IDCCB}Am4*v)8hJ=MAEuFruv!8fOQ`ou;fU<2$D z%0zWI{HEhWICEnsED5HNM}+1*vok(XKhBzo^_#?^3Pi;Uvfdlm%Y??$Vim+^QA_q6 z?K24-*+Sje!qj2^plq-7#QX^r1QIHOrN%x0-;!7?%-z^%Z%|IZy8NJGJ*Q`$C7W$z zGBcjgS=;?x|8e`X6O+QHad5FXOnV&|mIu|(v$V+hSIfP#^AS<~WL4EY_AEiBMwle< zbx*^CKP0j#69PSi#5^Rb6eq`sI{8hL6XrK(rHnlrhg2*oV2IwE-f4N@+{dn+So%(k zYOXeFm0p$f$d}N1!Rh0;i9b#sToj(;E-0}oG_VEMRxhU}zUDN7Le7%mZupcLUlgB> zGG#Z8bVc*|axBd^WAzT)U!ucijjbS}{^05gOScQOEHu4_b@^GTt2%+HxK|dzF#H5} zv)FXnvHQMNjh(L9AGc)5w6&leL$ZzC22y~X9=%I$Sjt6Jl!B0QrY~5`DsZnb>E zN*`Uo`4Q+q3GKpO=UR|CGos!)obN$HZ}_Xss2~5sQ%9)ITo0Q0DC>ulu;nMY)2A-n zqeD_IyPsCM%HeOK-6vT=bk@OpA3+QAuQ=7_g0>5PIYm?(gI#8NH^wO|@{?kiW?;C% z)3Zb|e9qTq+(Z(LxI5{nloXBJgrvUfVTtTJ?4zJ0l*-y;vDWaHsLIG-q2X`8))gQz zkWo^?<$4x_*clNqt{_8F>LYNYy;mZe!ald^NKv}}x{BOK1j18XOo!Zk1A5P8*l6^y zjSfq=VIPCJ6hFsx`&{Dw^`6BS>iKauefBuPK7Q8aJc=s%d)JH%o5NN!cmuABj^g@B zr`kk0DZTWi0|Z#d6Z1R2ht}Y4c2@(SVaCJ?3=ggTu{hmuOt~PAz_U&;o!wT=IM(|w7kFVeMAnU%yNv9E(C#t0xs4T~F1{^YQ{E{}jrhE~4cj3vx2|ky@@o1Vcb0c#dsGhZYI9s) z7+sBz#MEt}j#`*q10yI!9jvQ*ObANg6GQD{h9biV_JT0qaQ-8IL1&^N{6(=PIXj$z zS1T!@d5yGer#UhWgS3_VSdBJ<)Jh>G)^ci*%}vZa3EDEO-SOu9ylDsWl*{*+;7=)x z2QzNs(Xy{0aRd(^V#{UE9T4at3pD-tAdPyj@SPR2RfbI{7;@Q*gX4Kx@1MMw#CnY) z_~Ca`e%fRC-Z)s;$@Nb$c5yOqquy`_U?1;-8#&}x!dax9=Z9aO>9r*0lIv9x#C*Sf zgZN&I$Df007fl{&Z_1r2LP4()IpyT7X#&XuDM@rxkXu#=I#92-?mKosPhCOfAjMsD z3X2Y+q_pMhjVE995|#2HKqgD|xiy>iJH|+>|V6oV>0Y3x8`RjOkY%>T$tK z*ww=57N3l$)>5+!@f%dR z!W1eUrv|#EZ)#@bduh7VeS#65La^XA1X3L*W~(;x#JUmtd<8^|djed8!;;vD)%ruX zV-Ky#$ApFOD$}$v62+sgL96uPgAN}uT8SWY;}GSrPRrBkQ;`Dc`8@KgfC>}&AH{i< z=Nq7R=eX&}JC})3%RK2D)6CaZ{eAG*HDLG?-tw}17?LC_uKpTskPn0WCVT4Dg1gJ` z9o>1HMoS99{l#Z!NYT&RKV)Qs=yWhk&0Y0^~)~dlW3`= zYI>0{yGx%kUNzx`fLfJHa_aC5rHVaG-t`R$k?D~p8U4^J><5e)P(c}aIz-%*<{6e}lWLu`)yA4rC?@kgpB!y$f;lIg z-B%gEjK6RX$MfiZRjV^ppKron_)ELsBEG zA`Dh?q^4g%7ZD$x#jAQQA<^-;!H9Os=_r=$(dP;E2FPrcu=On+bQwsLMzleajnk$! z)r5#R{Juny3YJC7p(%J6dd~ECyH7S;6Rt&}qQesnD+^3U)|C7n_(B)$a4Ru-BoE>%7b3ZSeNl70<(D z?e1LIKX+t@lJOLE8sGZZCY%L3XD-xIyyD+X3he%wexBst=(l|q5S_Ky+1BDGn$ z?Ty5}6hnq-z{h1!4`chK3q(dtOmLeIrOwf2arocUah{vm`Q*^>vhEV{{SsO(!UFfGzaQXi4QD?E!}_Z z0;2ub-e<=_HP#8M=V`Si16=TmP_ty-yc>y#BECQCxIDSCE-cvEZdXGCR7Ufs5~N;N z2O>O2u8RCps#x6(8DiBw4Wq9+g1I-Nmgts>c}d%>O}vffHR1gsc5M+I?1Vgl57Q{w ze3h;#qvE^dYQu#T$$Eh9^>zHz)e`<_K)`~Ak;q3<`p=gxaq32|U5TNglswwJkassr zxO9LI4}f<;w72`3P8ZqMS{{hcFX-Z{H*uer5{zqkOK{E{{cEc^0n+ z?i`!`4l3MFTW3jl*{Ri3Yiq}>#?)D@Rz%OZsEncN0>qDFi&W=ze!vlZj`FpUPKV%X z&o9*bl;UF8k1_ug>dDg`3ZU4%+Wd!TOeE`B@(-|<>;;fd0eYl@sdKuFDnh2Qe^Iw)ZmtBv#x6u&FUk4hmx(N>K_OtI;Hh8af7 zzzgKlQa>d$U}dunxPs0LTv73{wTk8C%J^JaOn$NkH1d;=bkN}~w~DIGk!d4m%^A~1 z;>q`LgN4^!kp5^g-V&K;J!-Q+sjjofvC+F!dD*>0b-jfy-~ZJ%*?@_cUFU1Ml|1J4Bk*zu0Nv9vIs?2h%c7i z+e<=niZD>_bO8$-sagsW?iYggkpW!Evgi=TZurdT2NF&RT&25n8Mc&xhyb%&#*Tbk z7OWht*p*K$IIc*4Swteqm`EPMyo=WvfJ@(3+yWay-(XhbupIPKO?(!?0B4}&%*uGE z<$-M}m5f>~(|CngsX;NVtWK0HjMOY2s{j<6mF7mL?KNLr9NGS^zy_T0+F_Q=YX;~P zzPlKCfUJeUyEnYY1HDlH{ZSY?4Eprfm`+bIRo-|Bf>`W%2&bKn{b&e|V<&jBFc(@V zC=c_wBxtEWCyHJ0Sel$bHhk8(1$);KIzPC&Vr?m2JC-cn-f-8x-k8NWasCFJ9=Vi2 z(S1fC_zClykOVt0#D#aOV{3h}nGIWm=Awrtkre+rkwg+C3Xz+X{DTS00@_lYjDOf6 zJZKP-?`~rIMxNw~+Z?2*CD%6ofNS6;7^)t$D;I>0vG`z=@ZSuFPSZ-3=byq7OMU7F z;pOK#aNK+1r!P+c(Jn#+gl*SV_4+PLHQ6AEc{<=&l{N0)CaCH)X4<}R^hrFmB|QfI zF2H>U)!B9=T-_)!AGLuSdb=er1}W~k;tMmceMka^4sc`P4mE(6oRhlg7|_)=#48I5 zvzkFUN5cp63hVIXaM%MOtN94k|oYp*V?g z|4m=L${E#b6Wz}3Nbipv#T!9UmC5KaugBU0KD7^oT~ zUkrRI-PP3)^$Zl*WTLiHf5CJ->P1F)mODwVU{fCNfQ)s2MoH5dQYkIp_ii$7+!a~N z&+@&QzC7j|f~yxG#Run^ET5>(V!c+}^T^7qbhYyH7#BwCu@oGpMpI$jQpr;#K&%q~ zU{Kgc&IL;+js*X2$ZgWurJRBHZc+ZFv;(b7>zA_@bL=4z{11`CQCW{54^(OF1(y8- zluih4Csh2>JbU+D?0hgqLdxZ5qz)wRMg|`k5y_RpiBI9WV3f*V@q@!EQt_>Z*JqaE zX3c2?GFm8Q+F)|M2J?BjX7CLE}OFY3J zhKo;GxXq8dH7BzY8ueR57N?tKOp5^shb6+O%|L!5;&riQyDkywz|3`L%$j1u`-vIl z8tE{+0k}HCbeM#5n$kU6cIf0-iFi?4;!A-2B?lK-L<^X5;YF`2<}ZH3@P~JxMLil< z>Huw|pN1-SBU-p=g_MZjCo23+BRE#;AYRfe0=v~6q(R;uM^rhCB>Uz>UgvtEh7ppsJ%2s?32P?oY;Sw=|64V(W)?IBT5QsLa@=3pO4B}QfC)7-5x`vdT?37qVfZVcJH&|4JfG00qXG__V6d8Yw? zJv@vDD!jCLhHrnMs$MYhAdmhi>0Z$lTTyyUF#Y8ePgg>BJOy{X@R7~%g+$D4g<2C* z&EYb!n((8+&A%*&CxknZ^p2?WOF{J|t4ve<-X`X*BGHmCV^YS--PttAN)>=#8|;BQ zrOxZ8zK18S$6D0eh1Zmm1R2k>2V0Gc|MmW2WwZI8|EJu3-DqH&$_L@q?^dzpfh?%a z!JQtKKIz4X6<1=h7F^ich-MPG1Z04(!$MA;4HDTdy>BspbUfE_V5-2oV4mO#7-REU zMC7957KG!7 z!Iu-sz&`2szU2MUO2LN1(j!ws28vf!BuDgqc$kQgb{g~-xipZdwh9+lJ<%zh^|qAms=EZxUCfL7PATCH?KB#0}7*zrNTSVNZR1FW6@kt_o`|In_;3~SPB=k zDYSMwY1MDS(*wQ8Jhnc-?xFr_Nu}KS+PSfSkKJDh3-f@d_Ih;vf$>4FcvT30v@Eo2 zwtMaa_Wy^3y`#2-{pb79E&Jbc$z$H>gbpDFeLYRpXUDzCtR)-v#2FigBR~8cX2}97 zuJx^o?B03>1SdDKrnK;cO)MDkUFU(M?K7;7GyJw1OtvhsAYlaP!Qo>3+(Xdgv3q0K zSj@lL?M@B^KoDo?C!S0gTFvwZspx~cfOBO?U&0@YbN~m@_JJD(t3QhokYGvN2CF}3 z!=oR&JiBb4c2V!rU&+-S!|&Os8&3Z}P#$T#tXef`n&gHpJqSKel!IQns0HvpJeIqw z_PEU#+br;TCX#LI6`EhBFE}*(!XFPIH=sqMEM@sV-Se^j)7?F(#yGh^J2=+;@XMbd z=Om(NBU`O_9GX-fT3Z#WHn=UMS7>a={99%WBulsuLj( zP}2u{bya3#plczvnQI!()Y8WO5a@g z6H7K9^7tG#k%O^cNL)ZF7@5h%-4B<}#2ec+0fE&eU05ZA{Rq%jh6LlDj6oQS(-;^t zSKfY|E)BisgI-bv6EUwS5WpDNekd*Y{{lUb-J#3ISB<_BD$EwmrLLuk2uybpIx`K^ z9bLHfq5)N&LVTIeNToGDnh!cv0yd4D-m)->R{e&@6qMm6674@ba=Be=k^Kf@QmJ`Y<7&TQ@eaMvCj zY#b+GVU!|~xDzTaUKVH)hZXkE%|Locfg@hoJ$&((%k90(KDj029)_GK+cPk>klIaE zj`2fuR$53kLt;*@YBcnsC1O48z(V_99ALL2hP~iYi0*kr<`e`J!k1npb7&=4(;R?2 zCs5iVH6OOr8AEet>jp7#;5{yMBkxBHaNYk_nu#`&Z%aYH zP2QJ9;ibTW2bKlKP3v6NffRkz$T~Gd=P1Wv@Jw<4>R+>h2Q_I=v-|t2gVJHE;UDt5 zuN1GTmdTLOPWxuRinBsWvd|w+u-cWP%G)h8P`c&Gzn;HFGq#K0u zD_hyeDKSp@?ZQc657oX`fpk7EOnH_fdS_Re7+j*q9n`bJ-HSrMjXPgNkvz;yp);qu zz8@N4)B!`aj@oOsePPOd!rMslmR>y{PysedkRL_%Xp)?{Ro{PfcY?dyNUAf%B29A z?&B^ai^PY0=;sbiAL>A9v4{;HdGCXCYC3Ttw%u+%oP>$6lL62~4bkjy5V^_1}mzpr+CN4fov zH^PKQYaJPYk2rwN25H#hK!w1VkO7Z&xVtjRDVxdCV%&9MeiLfaKO~=hm<;!AhiT~e z^g$kvTd6RV7~#MW4!MnZ>2uOPt%AtVb`M>a=&&pq3#loE3rR37s)O zL$Jg!v%q;!b>zAAI7P#(e=`WB)>eTFLsXEK`5OT#=yC)L(w$g^Pi}1$QiX!i7dwT$ zUT%cmq87oz`VS$H_gfcvI6T#ywIQ?HPamF>~4J(Ja`qHWa=LM z5|5!rDQgXuM)hnDC+~~t8-3~pw%t|i%4Xnx7+^G@8y7Ctk;E#7I-UpUO|b7=^(Yqn z%*7)u3k5vnCAiH%nfp-)7GofK=V3>3{rx@6j<_nd{=cytUjYZ2gfchQF=v^<0-^te zT^A5VX~>oP*)NPB9rIO{KfQ-LC>;PisLrP7O1)A6aaW#CdHC?6hYGBI(KSkE1;DKuJFOlE$$UYzq8`cYzmV^S_lJadCrU!z549|tdF(X*F(1e>ONYE(1NZ3pzqYO% z#d`Xxuj5=R&#<}%0PW~K8V;1s*L#hi9hcX*>H~rn2`S6_sCKKIvSu{|UEn!`EVZ_!L9-9+ z=`}xWNh|w+Q!uIR;QwzOL9yU#O@@+@_!TqeB$8;Sh#xIuYjaLxBF5q3djCKj z3}4#R=$c0y2uJZ`X#$NfPER1p+xr>OjQ*OmkEt;k}!Xe&Cqb`A$JhbVuiU$p>*9MIz*dSL)@IC z?TfpgpKd?LU$4hGssz9~g1(7*3z{h_kl$nYgbp1R+BFGq|boSHy1qMukwR z)ccF)^HV}8po9z!*uG;ryvdPKy6o&7NkyR6HJXd!vH{TB$mut6)v0=`oH4%b0bY3- z@wabeR~ia72A!ozw?0tEEYyi~YTbe|DsVk;$#|&L!TjG6V3c1au=9N7SdY?A5IZh>lClfTa4JQHisFox{7hvqwF@r$8scsjIS$oUi>gmnYVer{#)4m@f(7n`*u~~I z3&?UcH&a3Pj*a%ShXkM@;Y1Sjl^o1Fhp7=9ROBlyS( zWjwUjObl4l7#?EEnDX5Vk`*%;;cXtq{KYsads^cd#S@yVf2>6i#-2u2_t}e@Fytmd z)@3xp*eAXBO~7qs*sBY*xXzYclTW^Cjt8Qq=foYJqt1CDhe@pSD%^l%%hKsoY$mUS<%vYfLpQ!s*UX(4G=lk$CEoIkRPQh{ zQ7dQ>0b2nk^wPe{-7o~RF4EW)-(Sk`Rr}(@&shfjxs(v#kB@e*K{RCE#pFi;)k(zPDTYL7GELSbBKzR8lUVsAqa1Lak~h0Ws}_kV)uV^a9! zQxIEg>>w{w!F+#b2@c+%^gz%Cvu$RmezeEN#dS0Q8tm_6KEfuPy995oLQ5|cnA*}G zbmkJFs|#nO*{kXkpvZ!r+N#>pn;}%NhVg1UiLw)op$;EEbfj*sxxV?Kqea=Ts>aXh zYH}I26n_gL_Mg}+0GAu4x}%ap^vBhB8i4;^bP(x=bZdYA=)KWrPHI65_ZHPBX+$`~ zWghL(k>DHb$*T|Qsi#e$G(5Eb=-is*Fz|kmwW-5l2%06TeJ|q3&R7|8?TXcEg?Yjg z{|r)!+$%|V^0wbN=My-Ov6LUrgCX-0{B8gfvydM{PZ>%xh(Q{~5a9aF$+H>x;EZUc z2fjcFqCuXi5QfuGbsQwv@$n*qnx?QS+nmB>`6r1VM~mybGjoYeLKldcO)$iLWdA^IoilH;hABVK)^a7YRBPSZE*t*R&4SO@j_f&xfvnD@mYp z6Kw(wX;!&gui;?>$iZTW>!e?WCb&|k6jEnoLX(wc(Rqu?RUA-0%Y#C?^qRcTV&tkG zX;Vm1q%n>Wkhm(hXN`mg4oGpyIYxLLrW9Miy1rj2g>SU*ED=|BTs#;byzKJu_Az0+ zO6R!h)Q3*PWT?JusyLprK_M!Dk=4o8flmH4S*=Tm`X?n%<%lM+2S-RMh_}H^%6+m^ zBG1`qKDDUMQ{vjYdo-_q`ud=E%iuiAPwq{SKz-j-G4Q?U4cRJ|^Sz-03e8c#$$-~L zIV%eTp<>6+jGXd*46Q*zw4YLyt%GId601;~iCh)cnEW6L?68EPV-IK@HK=Z#( z?~yC4!s4=fCBMwg>(^O<_j66BSCvFOZgNX^HhyB3p+nC042>vqto6YqF$fA8q$PhE z4ffM1`t?*CNnkML5*4TR(jY%z0&gEdG!2=TB|`fd}T~mAilb42xe~;d29`*sFjp%8 z!tysA-W49!DFhuINlCdEFP1I}Sy#h0jg8;Y2_5rpweh=*n$T!@Ogx%-Punr*!w$sg z-R(ft`VctmvK0?IMZD#0zahhLm}`D8id$qp>LR5~K+NzL`uX9glhp2Dc$Kcn*d9~^ z*MEHG0^?|#;Z$<pmjs-aG$9q?kF>wR!eeIBxGKCJq()^b{#$^jOQ_V)M6l zW95?B1@5Ze#^++xE>sLS#eas=(_V(bOL0bxkm^B5PCv9!#W8&==ZeqXJWvzEgJu+W z{Q5T5N^>@SwADvB@@|{}__`6zby;4gpH-&5qo^r5lm{a-)2hu^h7zU?eV>LI??zrApGI4LS7jWdfS{qQ@_HwS2BL-|5}>UJcolD7*RaRC zp82B4h3sx?ntcqOv>q{~7w6ZOWOp`GC?8xFL^+H~ZXvn*wcq3N5_2SO+2OCruTjho zVrp^-yj-XP+!w3{BPJAgU)#ex$5@s@MiKdcATZ_mkLEz{o|EUZyUu_bBUgUn@7o_^ zS7iaU4V)7Osro{tYF+UHV-*HuFaE@2~|J|7my@7paHoXj1RlgpPLZ0DM~cWX}9J2CjU7G$!&A zuSZW#B?s_H_os)4Cn#$$i%_AVU!JG%qDlGP~Of`e!<+EMAj zB4ZPF$Tc;!>pr$oXAHv!cPIs>D%XN9?LHd&dtlOF zsoK0a6X!=gnv%gp0+HSNsk}YmRQ1p#O>hIDg9CAv9Y(~uaz7ar%Ao!qvFA}5^C72C ze+b>IT$2CGMZ!Tah7h?jUw<*W8F2ATNbiAI%s@ah%^@q~pv@~L5=7@93WKgK%lj*Q zGL42oqlJNfy4A3p$j(%0E@P@(17@#m$U#<{{sGt$CDA$gkIcj$a||rDn zfVvBP){vozm@pknA38@7QQ7YAol%G4)Zpj&>`;Ct+5T2T1(#`gUI)`c1Q zrEKrdE;Z)%rwkB5cx+q|*wY7h6_sH%Sxzv?d3hXONj6Mz2VmrzZ9h1mWdxPwux;uE zS{|g+$I|S&;edWy$MOQ2$okwm#2}og%oUx`9?rfxAZ=d-!@OuEcptzN$w+P!H;*cM z<##xcGW8^7f{h^94%TG0Wfc5rBIeEnSy)dpImkwm_DgkNW|fi@%r#PeWuu+24huqt zj6$k*we9?-^m2vjMtN4Yqn!{mbqccS_VaKd9`1{*qLybrVH_TyOjs7}HyS$;)TG=V zmw5(>UHno|)?L*irSXm^MA;mS2Qlmj>3DX0b=Mpm+-YoQXni&BFsXX_NNr1-_rPHl z%RWbB2eL*FsL#>0h*KYG3Gs>Zl7O^x8KKp?HDqc@*UQ^F$K4qydtrJPF+C z5A04JRXGr?S^I$vBX2NBJ#ZF9y=5Y3GMsG@ozE>>E?|9oklVQhjX>Pt=fXrKxX=B2 zCK6LrbJ>7p)iW1b+>qvSUDX&-n?NSvd+(JD%*U4Cjr>E!Z$b}s%Sr{2;Uy3{Sm7;k z_$Bp-U<4WbgV3NTZr2LS2PKs$*?(GnT zIUMbZSgjJ~0gj~Tc+u`CQ7mTw8@sK3SULD{^anE~+xt?Am4KIimk{P(!EVsw$BS1{ zT}~#*VtLlzg!G0NjF%y3Y?qoLZ1ZVGhfuR>$eHy~fO~>g2WdXxZn3Ll4L;)}`9EzU z4zECPV>^}Z%4gU}9#8b`c{+3@AqRZxD%mp=&iBexTqy$Bse_Tp8Y%b3FWiV&C|w6`mRR~uV_Z#7o7*es_~;4-2l-k=ms%8pG;8&3@{v#@Nmf_JTo0A} z1n@hzK8fK3#LvUhUuDIk!FqtzMCwTWEKb8GF1bJ~!zh~b>kel*$3#D2iG zAoD+q0L$Qq!`Qg0Iyjy>H%;f5T9&^$k3mg_^Q@7AtAp#x7kI3IIta9i(fq-Ns4h^-PL?rtylClMr&Y;6yT!KsV^f9>c~N&T_!+F_h^Ij0+sG)u0)fRLv*2qW*3OpVYsh#8 zU>#%4S@f&rNUD#(&)uQpcvyPvif=WQ!X*vVX1Hwkd`qWL4Ns!9bm@(f*?doE_G5}5 zF{*F+?sh)MBSQ3`V%^Q%qXc=3T~*Y>+b@yY>!6J2{v&HJaWj0r{iNdddsn~E4z)cA zrwE9>+DwlGSF}aT?#~2Bmtlibnes1ZyG8FpfZu^;tcZ(j-bU5n#oi@9irS&g+UNiJ znCVv-c3x(}MTaRaXlRTXbYwf}O)KqP_enYmCrm>OwEjHRX%=x=dUFj~>oB;Ow{dAh zpqwtesrzZ4h0|Yw3?#bmfi8o(_rOs%$j6T9RFY<`6v-&gP-*(WGypJMmg-2;CK$Gd z^%U1r(gL+GfbVclzH`{d&o8Ot2aUBm=tF$O>_@t0el_?3MKjCSQ{dv8(yL%ES#jOs z2Pz0>+C09Dog}?6=Pm2@dDSf)W9h&wRldwP=p&NXsfkAW&%PpR5iA%aI7hM>MbWfv z`ja!K;;y9UT6uRphhcjntFONM<-jXbbAUYn+(#9rciI4Z5dZ91D`QK-5g5J5#GkkTdFoEmT2?!*= zOl%FPLzK**y{3@Kj4TwgfSFf8aB`(rDc%OnoKVxetcWIlF9!5FplQy z6x%IuhMoEA^ZA-t0bJGlnrs$2&CytN$d)u8Y?0>X1t%u?hAb4NXeEx?{wv@y2Oy#!yI?8JTs#VelT z(CtM+Kqcbew6%WS96BwiB2vQS)laZ^^vD4UAOkAT<@hLoLwy0$1*}y$7!*$_C4a`#mYDhE}o#MyU_H7-9g%lM4=hO=?xC zP7%>}^rv5R;|KSVG5-oT`_TG7bJV*WR+Ak_uR_SCGfYDspzp7Cj1mr0^^LHJ+Qit; z0AGkb_Lt>p0!w{?2m7xg@X7)Uuv~jlNnPe`a4bD4`85bjW#TX6*6= za{iWCCGjp^b{N(^XFx}Vvrs0(6BCXOZutCNteu;Rry2s;*g?e!2%y;||98ZKQC_x; zLJYSzVLG5?Qwdve72bf!@k2rIX@Iv)dXy0Yqd_cMb;+(5)kdgjYxPkwLih(6m6-XC z{dL6kZPlGtbIN?ox80LGp{OuH-psIX4nq1WRXbgA?--G7T~mRc_bv8JY(DON zrYCEQvQ>6}51*1VafXyKN5dXA zN}iRCi=n;@9+CP+p1N0_;}>&mc*6jMY9-nH>xaugaA;YDn}g zEIs%Q7N!wAWOG9e8T+P*I8rdhw`+&26TgAFcG!FcZ{Y8KenqW1kbI%XITd1axCX7n zi^I0M$CL}=V>OVoG2xjkg&{0c28x`Vs%MWGKny1Tq^~?yLkxsDtYF2>UCNoFmu1}g z!LKbCGpHBFytd=tEv~;;)G7!bLg@=3xg3khdf^~@$zy{IMbK=9mr=kc+@K?_5=MLz z4}XkXN*hNDndZz#0ny%K`BQhbrUHq5uDMoP{I;0s1g|BQxOw^$cuy6lD0^T$lIDfJ zc{6a~S!02|SxhC{sUz3~c}B4gvBh(wu;(RPu3B_3_Ft;`-YArd-7_n@2ZLD9lj zsbC{4ZrMG>T3w7V#6X>(4lv@dvd_c@!tiQ_0er>MGm%>M{IoCtxrjMKZpTo!o6<7i z6x0PKB>@w{lAte_T6_b2pMG71vR{mk7jG< zhY#hs_MsUlZS?<qEZ2&6d!Nk;PEvsbmNG8yFk(fN%Y zFAOn|vPHkITv z_Grnw5giW=&uq0s!pKL4kN=~Q0PrQkso{{fVCOb1x&z#&@JZ6sNf1gWD#FmhE(i zX|G~#=1-KzU&#dLT@*13YOL>zi&YmvGwxj)h!bT7?blj}oRvtm$-ildgzeP|7Yy9zegt%( zxCpWAk3DNCwDmZbI&%K=*4b_xF(lraYIyoYUC& zEvIktlWqq;pFVaN5-#?E$sSaC;RIHC@`Sk+_UN2hp_)pNin^5m|D|UEVz-w*1^bWE zP9AW;&6BW&KU>nP;8M3Fb4E+6yK2w!x?yRcoCNJ`UZ?%5w!-*v!vN9^GgY=gF}hP4 z50H2PE&OLcN>oUKhr)g9I(k)aOi+r`>4{L;gjY%}UXU8%&}T5A_t6~+>#X<1n6aVz ztM5&I-RY@6AukD@>}V@q4)VU44RllfT zg5z%?ihK;D>ELqgd8mKS1aaS&{LL2C&%?oc~OJMKWFF*8C8Qi{v;3hETR=FD-g80iLKl?~G z0k@&dG!Wgyo2hl5o#)=tGqBAFCLn-BobxHFhP71Y(-MD5u@Xr!wNTvI%&JX;0u z;(PxEmj3I(HU$lvS`L$ZmOxxx84ctZ5)YQtCx~zU(C*KDyN?a9CfTZs<4U7F(?b{_ zDd?J%Gl!cdpa-(xwRjuQbEKwj%}=d;;&lZYS2s^*R2Zb6wts;sJm<6;#h{T8M2Ai<0Fo7kTiM~tc>sNv zb?9dZ7wy@YdAPW)!fZ7*5`c`bx(=^Lzu>RO+yVJOx+$JepISB4B=$~eMu-Oi6Knu{ zBIyJ_ETU&NfYyF*gvp($2(AU81Tf972>A`1$8JRW|$~AB6)X*Sf zTKdTXH1!G~QYrUe)W}r7KKU534p7nl|0>fp)MUums1v?cWk5dahQzY%SVEduOm~%A z&nlqtEFkg68EY{^13qm1tZu?Y?!AXWM<*28N|n6UOm0rvtZX5sTtvF&-1<1Jau?+gKQNiUq!hwLq9}drl_%QI|JkdP5!eYG+0%Hx#E%T>zqo@@ zFfo9~yFSxl=dY%arXTgl_$F4QZU9}43eo?8lh$ORh0?vR-bud-rMjxlp3$9- z_J#_Z%93(TFp{HkHvL&Nbyd?qg^;p};O{k*cvOz$ia3rXc8ZwLZiSJ@nzBrWOVYWJwzen174Oil3rtu1y%HsCtVUM zxHc=v)+~zBgj;L5D=I`svt^zv6Y)k*lkQ z#e?8N9ZXbyus$Z+{H6Evmp#0kn7c7e3gAV>7y5-m&6=F72xE?tCKCl_VvZ2eU32r| zQjQ}%L@%*Gp3zTa`=dhlFmNn8fy0KG;`+2iRTs3truU$L4^77^qIAqSrpc8`B|YZz zD7+Iw+$HacK?0=JNcST_;is7Qye?H%*F@I1=wxG;tX(VvH(EX2Tv$#cpC5TNm7Nnd z`{PM+Wa9`Q4!N&7i@Z$lK6J!Nm44N%_TYdhr9EFIuTT}lW|ISnA!t?PNM}F&MQ$4g z530u2*pFN$C)wM-DA21WHQYNH*3SvwJX?wZA%V6r8O^XR4YXr}fdXr5zq%-FoorIXC&b_0=7BiQDO|K~ac1 zGd#GK_2}|#*kg3}IHBxxrq3v4)VLLqW}&_kS3{m`@+Hk%CnNEqT7RpaCgBbrXjiihqiqF+0h40 zEFCbao{oBD{(8{dFhMp}K9B-ei?i;79?|vVLE%@$9IKlVR5Oyp^h`}O*z#{ZmCO?N z&*H#Osc&&#ojS@55A%m3&+|L4or7(-fllKH9E`*_TVybPxI^SK+8^C?%~yV^(9P2_ z_0UMIQ{^oSU$7rW>jyfG%Mf=$GwHX#RgA@uq|?yZJ?dQ3$tLDNg9A2CeB7 z`#aVdDCkQWI+e|Myj~iHtfOP_O?aeoQj8aE=rSW!74nvoZ9XUm&%4*fiRQF!?{S(S zRH$}81z-P%ZE1*I&*U2gbAMRf?IO)Xu}m)k9=Okh37x!>)!)9l*Ua%`s!j92$VXpmrwQ-2u|_ykfi z#hbecj+K(oayQJqls%01iQ};s$(ZszJE2A6@WwBhw9#Or`0$fr@i9Snoj?`HRS{387*Q>NiKj6LD$V@OPjNw5HYSdfWgTSr+z%#X zHm)|GOw7XVyyqEOROS<~dG!ye3l#)!IJE&rY08v@MoaE}Xr2qBC9l(o9RN0{kxPa& zihE!PPEoFZwf+@BheUK<;a0>hfLLfX`UAHX^~A$QY?KHJ%%(gW_n9v$*>jQoX_JEl zmj=K!>d}Vrx3IRib);^E*GM>|~SvR*WQvkMjQ6!=fnRD_uj=cH^ViOYx4TR`_% zpAYeEjjqVgTLz2f^q!~thCdu0!nw5r;X-gH(eOM8if_^0WfPj*6W#$b4sXp~uD33S zLFF=l5%Gvle82Gv&wI^Ny9FwS71q+7(aDaPZ;1p1lJ1LAs(UfYmYf#&xO9(%q#Z%3 z$mRFcH9NTGEPU{2u04^(oq3xjHhP_;V>y-8ZOyKcu~ldAHG3=<0W8LaWH1*c#h1^z zH7x`ypnu93O(d(n#_6*fuJR>$fk~-!n*6-%nA`!I=}9K#kdBd?X;3=n33^7J^2)9X zj7*ZRxZO%*C81X{b}qLbGD@MU&hpuftr?VuxmGS>hF2nINb&!*^SHEpn~?NRaM0%w zXJV(5jls%73U)D-h?z5zbR!;jk>sqn)i2%Oz0&xvCCwG^5bK#L53A~e^1@9=3i;`I zGY4B`ta&-CU(EN(8-IY0zYj)#{E)T{)fiof?9BSR>_T-scVQ-hU9-lO*p%~`c&_BB z#n~^M@g~SJN7Q4!=&JPr2xepj!SV((cMxsSZ1g&PJ~tOSN-Ux}javXI$M1}uYyjJo=3J>P>LE|B|u*$JQ5gKa?Xb6r5D z<5xU9m%Olut|>7}_!y~c~%J)ztG0R~B}!A@5{rI`p_N8Jl5L!7($8r)(4sKz=X zO&>^L=Nq`J(J_4_VzBzDfU9uj@^Ud20lQY~X!Nl1B;B4cSghL5;^N-0@~KzlJ`asg zDAMVY(0G_y{(ilhKC!11+XH>XS#a7>V#$3g~(0Rx3Z=G&0akb*R2klsik4sg+{VCqfvA4kHlq4{L4vd?=(@SS2`pPbpg_{VRVc$sS>5T>P+XPy>klq zbsa$hj-vISm*#^W|NORS?i^~#Y}-bPlLJJ-{(lXL)Q_|*kg(&3qDoGXz}FUNFgjCM zt^Nk6v*W(8qOux&{Bjl0X3&c<9jrIKZ?LvVq~c0tBh$&lCQ zSl>`M2A!N)NpU&A(fMO)*Hg)KR>g9q)n?)7nE)fqv{?h{J4NiC@ ziRJudPi<3)rrwQwG?9LhlH5|CHI?@6tQG>okkSEC8XO|a4pF*;)iZX*2zG?G!#3SI zo@1enkSEcF^9D@5!=km3w{K(G%Z`Ux3U@iTUz!|J;Icr~T2XpyPJ4{+-?1>iX?urw zL1=LzBx$(9$O-XTD^BJ(s>T8V{pQucivziQUp+pI5X&y4y*x4NjDBkcKX}qXwhC6fPI}2uUH~Kj;izht_NbCT zAHxebtqXWhjK~O^T}H~6d2~umV<3zzB$cV+#g{e=s9|3&Hze2>8aTp2$mR%M zTFIkTQd5cZ*=pi!iGAfWuzxULXAWJJR5PzUvV?lrCZ@@-XDu0m^PO!vg%ny^zk^+P z)0`VRvw*Zal^9IPCtx3z5!VT-mesNl$mt#(L?7OShn4g{LSp9Q(8b-weeW{t@NR!) z6=-o88OJ_SX2yeKUXwN`I7&)kj48xO1KW204qhk{~PL46R zLUTI+Wh-mSZuxn~jdn%IVojNJ9#kfmG-m`DOn=n0diZm4xm&AANb#C>4#_dEoUYt2 zX79nXuNNH9U6-;c7C%OnK?STXnE|}Om9YmDy|Z7g_2Rt%K|sF0_3Hlk6IZK%Y3(mg zxo;T37Ljg?+>saJuv8^oG!()^XzI-WH?7DbMbjY|0G||jO{ZHkWyTEpa6*qaq3Xs* zuS8$V^*U}gAm@WXalvhMnsr&Ln8qkrJrE7Rlx|;J*-!r5)gVNT7p$I{3(*$H;yu8d z6AB50gO*qQJB$8Q&^c}NddJ6Z)k^DeKjZ@6_(9bl_I06%We;+#uuj*KOj`{S+4h#XA zk!b%K$%{)E_!CP7$IEjJa$RUzG6%tbp6=s8i_a~K@dvRn0`(9sriX6Ije(JcP}dOS z(GjCF@@xK1jy9YzV%w?GUk=Sk>stX7+3(OCI2`z#h0!txfSTX_U6}|YC>(M_d_UKJ z7dP*z9n}X(aYg(sk%IYZ=J76nuj$B=2HfA2fEuirCO;f-1`rbep0K}1!c0GRJ^VRB zjy^_IC9Mn&7;ytZ5b0GoO?FUKeZ#YJl^zKNTz9o_Eg_&Y~?fzcwcRP=&?wASs{64ltRs-H@u1%qkOoB^Q7Zund zZS4r7gQ4i*=2Mo(;3L!h5ix>gFg#$o;f%DTV{~ry*m9sv zb|CIpNHKM+6C_yvc4@ObA6YlPX#wfTab%WQlJDkLPvXlAx#!ngUn25eVCN29W{NDR zROKOmH;Q{|xuc&`&L{`2wxqe3MnPx!e>@)GwyQho;9qk(Tr~ZGH(G1M8>L2|*4n|z z2L9M+#Z(CHl`^x-dDUOzF60PcFi4l-WGV0&!uSpEp*?m+zhxb^s~WpLqKY5=f}q`U zK3@BAY>$l>5PDPf+e*q>1j>=rI`ofr<*+QSgx)}h(iCw9qRHQlyJhC~S4XtI$;pe?uJ#x91nyY3?m6P55@Mh$?S`Zy zu!`|mqiDYH{T;whLGC)&(D0iZW`_HMQtGc1GX25r$k;N92wszlfJD@V4ElJ&5{AE2 zwwH<62pC~t=qgFl`fVJXEC`ybDW#634>g7%SJhGCBb`74uDXTp-B{uGk5-X6n~g3n zARJQ&9CCJ2C&{{BG9UL7md}i21-Y(If)o1rh!(%L0vXFm(8q+!a7v^a*Tl2~nua(N zH2G=`0r*KqQYF(G?yfksQJHYfxW{^zg&_+!h6dl8r;~nJfjga{XZykSllmh6ab`uO zmI%WZI2Q@OzYeMSJ`wo*;~X|#nEHVn^Xd*@q=9-opQ(A7{iL_lY9xs=6OtHl!gT)} z4vORX^j^sUS1lZyC3IbHwV#IEX%pd9hc-$aE<9ZW+vB-l+9h{cJ8Mtvl68BcEKKCP z(&E@!yyVT}b#9*Wk`sbhhllYPFU1Tv%&$Wl5QBAtNdvfpOE9@Y9N<<4Y4i=e+f42r zM)zH6&Th+p6fG=BxG_iE;iX@uzU#`Vn)_L00J5?HE4o0o9ZzW0Ou@GS9t^ zK}B$AI1r(2#cEkQcXW}bM^dQaS#C6Fn1@By=7>ES&WIE@VK}`vrSswJLJCuJs^b;B z!EGBL;fAeq_+m7uVpQMOl49{`SOEM2XXX+->xabjcWmOyYAbXCr0}nx>co@(2g&dT z`aEDcS)6N|%Cje_LXQIlGFm)<_eMfYx)hL`X}l(aJAfTit;lRW54f(3q<2+YA@UWM zy?=SmO*(DUBmFk6g3drw%$l3^oso)gsugrE66Q-)JsUxG`2!*9?JbE*$Y6XTENwS{ z0)Ab({q=ygU;z4$&;-sPP3h9lKGxL z>LUfOy9=b1-#wE-z6(vtA_q=c$v!rL-)LP*Yl!)kg@O%dnF_ROu}3NEu&Fyz^7}5^ z_@kn-!7>MUB*$r|EtDsvE>32lL1irKiT8YP_Gv>Wt@14 z>~_yJaoHKGnXz}d_O*6yU}@v`HMENSRv9f|eHAC#YpDbfzqTbbF zx)cv?B@aXPqu(mBg6njdbMy|LH}1*MILSkGg{~IS7SK!SZ%GR;79Yu(@uWH`g*yuIwq<#Jo{A2vx{$4x2vpZE&JX`(3ljQ#77;mG3lEnR!s)K8o-6*BrC-t4 z*ty7P z?C*wzsS8*29pY}$D|ka4to6h^=ag-v93|lx+z~yg-!Aop?}X)DfR?C`%%E+LW4Y*5 zc%=U|!FlWl!yQ@9S%nppMSOM=7pCe_&N1?_&)*h2Qb;k!!#8%D*c~Qmt_|i?<$}B< zlOUqQs_dE19A)=!1sXeF-U^Y@$1C1b9|f&((7T8s_HTW+;_4Y+2F#f2>kb|<cAKq^Wq*?@5wNH8MDhkf8_b!}lB}D+8tAw^ zrW(0gMmC_PY7fv40Bw_UfoVk?)O@knTi9=NYKJS1D+A9GU$=840CVYnuQJGbhy#Oke6{ox z?-0QOaHs5V%j7g|)|J|gCfg{sr1ZbmdZUEfq!Iw#v`lN#Al`DkE?g)SG$&Z5 zcvJh~C`Q!^^VD;11Dg?I$5ciI$>aR(q(r&l4_c5zqZZCy`bQC^(XxvEHx@D&(a&?6 z0FLI@4%`C1k!jzSD3d(X;9a|KNhwZUKjk2n_Fj%8jGe3Y%WU*a(u!fA-4B0|2Bt6vb&;V2u4C!&A8anl|G&38vMpNr|ttJets|e?>c51 z`tpKFWZzZW_asG1FMasaCM#>VESl}EXtRD4q+6%iWNYHlkEM^olbnxXpxa%|Z6JlW(f^ zMt2`k41rZ4fR|v*9dwaMw7g=MCNU{keyz?1_8aVr1d^A|U~SW<(p+`pluxgSAe1O1 zVQjveJ`&xbUee@dqUc1}2tTtS+a3zw69cL`kOKtz2c>854;1uG650u*lJOiJ&1jsL zTO7<;>pxy->Cugc|94e9L;sMH7YCR2P(A(!bN=QwisL3`F#sq$l#3{JC(hLwI1rF3 zH@S;sun3~;VEVTA*ek6^xs?NO7P(T*Z%8OtG>L0RUEpA;6<>z=RAUd|gK!b$cOy_M zcLPqQ7bk)vMe%4oW-aq*ZWt|br^t$coAhFlO$2yM_Db0^Bf#OY_uCYton2=S1|ES2 zH0rOMv?)lsd5pag!Il!PkM`)C`iZ2S?n3<#e93i^5|sNpJ96#wf{a7v)Q3;uu?^15 zUN0&87nprZlrA3j>n&fe^y@@E4UmulL-htY5|w5iMonPbW901o zk4*0w?Wo827}O}zMQ5KzvByfSRSl5f*d9JKU=BL$Y2#7YYp#Fhb+RHX36K5aCdaCI z(wDPYpbU)lpy#ymRv@_kZgmoQv_TSl-h+WBIPN)Cu7W_Ij}5P_6;0B;txuRRlbtp z$CKl#(Z?;8gG6Q3{nx&{jJ^jC)z~!H;#1hh93lc>fR&N;vrgp0@x`k$m4!3yzzyQE zAGdyQi^!(@?46z%*LspC*DdhpIFVJ*}eHPPp@yoPhdpKokhSviBp z;OlrufKnOsukMG3nplhdPKt?$MNkBA+l&l>UM%Xj7TdoIG?m0qUPCDe4Kdfd?`@;y z7D$>$+0)cFT}lD_!~Tmf<6u4nG*UEW=={^F?mOt+u-QHb7&ifq1pg|!jjL6Jq8~S6 z;)T3uOveY>A6N9>+2DWPUvPrh4EXWVe+Ze|kd%ZlG$@hC3XhFsbz$Hos5yIFz_}rK z@2bM9SAd&706*#5x3-Je{-tJ* z-aR_%m~7)xEl@S6L+^v<7^ze|aF8ulkYm*16kqaIIfA5fBI>357n(GP3~Qkpgb6aNRs`47MZ)A;;tls-EwkW!3wia;KyLJ8ysvmUh7=7McM4hx4~I?TkwhE#GbwO zqY?9wt7DbN3#(m<6UH7vAK;F<{S<3oA%9ZrhCmD*R_^G-Sl4evIC&Cy44?7P_LIK~ zv18R8TPFbW+4B4~RPawS*nU_pXKou$h%m}?@8MovlV7mLUJ$v`&ZiB_qyaHG>t$M+ zdTW5M7&o=sWTCg^=p`aCF;OJ?9SaHp)j{L&JwewiISNwZIRR%!$-F7UU7w;}x|s|~ zObE?`4T>0F17my;@I`OPC@GaVZLMlPbZ0IS-zDs}0*zbb!WW$#8b>9TK2dQzF1p?x zwZiThw79fyxpd3X>1eLwV!@Jo{DDNJhyk;_WXMu5KaVf!+9ul=B4&OM!WvYau1j<) z>sNtcyqt<>V$fz5$%8dgf+6KeHws6%Z)ea}P8??VZ%7%AjohL5V36U76z)_i;QR*JKLE@bT|1~bsJ82237$0bmHTU!>q?jnf($ekmMIl+TotSGJu|4`B0BJdC z5v!CZJM7f<(S|=T0z9-K_%+rm^kiwn%e;OH!YZwp60K&~d&6un)p0G z(I*MmUsF;@l=%Wj!CzAd^_6BaWJ=Y&Z|c%2Z&ZKJzmfVKBIUXRNop-3gN_yFW};L6 z!Dj7O#$G3r+v?O1KeT)82A^!$Zf|F{94S@OiCvgj_6H+)2Wj)S)XJ$`~c z2Fb6)+_UujTEW!D=)-1}l!2qC;`roS;aOtW?(qT#Uq;KL5C>G;lnc_Lb_r^M*9^>c zocB5}4QL`)qx?xq{X^>up(esFr%lb7T`Z0jI(Os@PbJqXFl`KAn-SL@Sz(KBs1X@- z(cle|rGuH3cddt&9omWSz*+gx(pxaF;^K?%yOt6ZncF=Fo7LzPThtGfZ5tq*g;5e0 zR)yc+4R=X@{K^xxMiE;)`Zp>11xtC| zMc*fur?n=lI$B(D6kB}!Xt|t+J4DD|*gs1C^r{jFlbRs{C|~4-@x_5SBu4qq^qNk* z>q&<%tAF?|o-ri7n!}ec+Wt zoOPzwkGv23D^Drry$+$habAd)lu-TW5b#mp?Q$lll@{+u$e4%#ovKrex2r0Nf5;j^fzaM3E z%zL)k84QBVl{5YdpUmh)GQcjwZjqz zNM%9CrtCRBJAw~j2)x+R`k_SV3nU@J)j${Gi8Sb=7dUN;49R~wSMDdB`tZ{YI8S%c zkpd2|=SxgqfNa|f*OPPy06OkqkB z``s}KR#F(dQ27lidnGxoanR9n|8_Bj?M;+f})|NB=FJub^QktlV#LsO5LE^Q7 z?_4iW!(Y3EY!|+R=bzzkiC(wb5Ju1^nV?4!sEW1C&G^TTQKuv`A0 zyi~70Sn1fXw%*&HkHz8I{w6S)x9;xoJ+@=8n#cZTpHw#|;6ryZo9o>;7v!LO5jq?A ziy2QQ=C>LQh|)9~PCp3Dxt*A>EKxp+VDMfkys&L0w;dRfr;nXiOC<%0d?R(ep`ZW0yvNRg z2A{hCd*FDq2hw;;zzEBKw~p{8CT#5bD$X~%=J0RiK`KG7c`F@p%n2n>7ZEf1n#oWQGe-}^{bmt#H=Z-I?S+~yuR)aQp@;R- zJf$k`7ZqzOPpYzltIS@-fo4@^uf!*;BKth?rRoi3Q+$_w)K_ggxGXUUNNib;rGUdz ztc5=p)t(R$zy;68dIhPDyFW>MI1}PQZ(e+hY4Q|Vhgy!titI%`atJvp!(0(x(cqnE zG7Ix7XO(>X7NEmG2>8%6=iNmwM&FL~UeN(>VfZL|W04LI}<5;?R@6C~9zEAPrNpF?%h40&H4nUJ#7r${PQqCn(V57|1_GunxZ*YD543IxpsCC3#B1Rwi^u1@C{*UcZ0XUHw!&R_nPI=oLM50aK;GET%S9u zJFsXE9>TKFDNlBtO~a+<6x%m6mVo&DfEF0Ut0=p{_0;mBcfpV~36AZzLr!#SLZeRj z@Bwd>7Vv1Y%*g8fEQAlICRA;Hzt;>cl@D@g9R7n^{7mupVYURv>~`!I2PSeArk%SO zK>~}jJ}8D9?*$#hJ>+v*nAu>B@&&}Q7y2y=`iG!B&mUGy8#|ytG1KRAOq0f0^;Uw{ zD$r^|G0o(L$KYKY34Wg492imszD<$blHaN}iWS@mSk{B8-ayEsgijWY#HTI5^B$v_ zW8%I;{+gYe~B?K%{psZqetC#-^VMJ=DB;RdVypSTnwXocjHsqG{A7GS#5 zo`p1mirn1vvw()Rj^U&(-f9qEC_(apgXQKTI3y`KHTrI}(Y$#Pb<;PoPqdIiiV=L_$DwX}O=p zZ6B^B+jh+mtuz1U{|(T>QH8%6<9V&w%^GBr57APJrWWw6n@&CSFT-Vymt?u!hYbW% z6US|J9Ul1y`lfl1Q+9VM2gxIhUMv9)A#(YIamlyk(Uu%G!tXvwl6M)!3Sse=_RSJ2 zVTH>+7U>{yyb_ypXDj7W_lOUNBT+6{n3)vVnVCsIp^Q))D+=^Mq5b)r2>Vw==gO7G zw^%*U_h##!1*dKi6+@UX@>e;uIz?)r;$x5P4ii^_7kV#?xRmlh-pTivMJ=ND7ynAU z!ZS516|aayw@Il!EDi?ka2(M{EmpPn00+2w$o>DOv~r*exVF2Bbz@+PI5>nsTIh%C zI`dylFmQ9;zHHgncvK6{+Uk!%Cvh|{-QhaJm23Fn4;y882chO6(=m~Ef7jsER2q=y zra@c8-jffq_we52q^6l4wD`px9zhxZpxoGKuCIMz39bSZIP8bFbAc{6gBhF=0HYCt zUl?o$0IN-y1+}xoYTK0nrwDlH+F8Bi-`T*}MMO}_X?4P_SA13V>E0;Cv95+opn{pg zB+7m07a9!(XQ`2XG}Ox!aU1V-8Q1L|Aa}n?IZV@W&6jRY<65C^(|XJdrHAQ zswpmm%Ibiby88RvBdhkN!t2(ZJ>)*DNxIL&fhKlrz0|8C!`?gW$obH6v)p zPrsV*Da|5Dj{bIRCGzcpLpe`2|61o*eKKxB?aJ2$$a2y^WRl}is{W&V5Qsw$G`b*n zmuEJI!&89dBgpu|*rGHa&e~)iy>8B4F#5?3DKq70YhzrzfzdsWwk8Rr!#R)}utE{R z%?u*u=DyuuKO_*(3Iy1JbSt@yXua)oD;%!aS2z7kYu%RWr8MTl z${e^MIY+U?IjoRplQ7vWuhBFUIQGuz<6cA@X*zkyAIl4~8&1yHe&Y5Iw zquOnV>jHDZj?2wLKsE0%u=L!`CxRml(2<16f63c*S(x^n#C}XyoCaSN#O zt5Dvc51Yi}Gf*GA9(FQPioFx}l*r<7XgLg0EZ_Ly<@dgj6OLA!d#q_!$YzH_ny}lD z^H>F0t{gj(Z2O!KH_*bTS~U{n-{}OvLp%mo^n?5;6_t`zmuqpKR$>94A8e`SG?bY# zaSij|qeqa;M}JL=>Uu0~-=iQIb>vOlkCs3)Gnh}agDwDXpJz>&LqNDJk;;R1%lUmBO}(rHQ~s0(@4tg{n>kZ4o7;hEX~pXzNu}jk zgtD$^8vKdRAC04TrKaE|=U#5LDda2R=Bm!O`#dcz+Fv?~--T!1_X^`9p8K$SoeMPk zsi%1&v(dVRUoyD#q1_TOG~}AUo&mmajA6czq2bCTEa*gjtP5SZG*pbeK6kx`u$Te{ z2U1@s?N59Qpra2`fuBcbwQVP6e+rD>kBmfTO7X#Tn-dMy9m3h`r2xA$NTDJ7vGR!X z4jsbS!}&M?EawO1Y^*@&;yllag@XJ|Km(qF8DBm4sdO0|hf<5))BDJF zT}L0xIP8M(Z-^1xLl^ri@^L*%>de+tioF#gupXs_vLKF&B8-iL(owiTzNR?Uo!3`F ztX#dD=w_38iTU6+VV&WfDoMkd@J}e8ro08Lw?Jf6Gr6Dvm4kfr}nh&ZB&@%Y2y(#=OKiDh5L)=#5nvd zlsd#;8sgCVB=61rhJ}F%pP67TQny8uoW7wnELGA{vLH?v2xS}CTF`9y1ZE1xiN3k$5A(j9c$i*W)MJd|GBEJ#7_Cx3rX z)V@03_gQxgZv1@}(;2_)xSVn19Ch^`W%T#yYN74opEl3LvS~$=Gy&|7XAZF7L2F_W zZzpzwp@?O0&;Rf~_setdBA?yx+Xz;MITAq0qL>NXnFOkK+%6>J4vXz*_Zm#=M6;Z% z#oF_f{w3rYkGZlURCC#?@G=);BmLI3nCMLl28zcEr?g^X+2x&)MqCx4+PvP0^_aJS z`wmRj6i=SPN>=E@ljs^|4xsMI{2u6_cv>xr&{@B#J8APCmwclVtl;;Ov9c*7TH8>& z4_F?O*q#u56s^)uhHxSVcbE;7{re5Wc&Xn60t<~Us}?IX(ZKe=F%O1=894PD>p7r= zNeFru8INqma}+qG5Rg0@qoMaqdOp-?zOdiJJUH#kC zDSM;#tvNM%auf(TD4dwkF(8{8KqucysTd<&XziuX5$7KdRU`a^73T8@0q8)^d&=gW zg{i>dG-`6Qi-lM@3*}T?5LscBS!mUNI*aOn#!U-qf5$`a1l+(ev19dBoF>Ww=sRz)Qq0 z__4QZ<~e#T19X~)7)>7Y5bws)bJTRw`clta+$K6HIaBk|ya7*4rU#Z^PzMj0E^g$M z3q|#hQ}ZG%b*Dy7x@6hmzuqEU$3$A0(UY!q?)OlUs3-8zLCD6Fxv}?hh=oZoquo9L zomE84<#S{qYGbr+zIIF}fkWQ70_#3-wE~Scg0JS1N|x5v_iyNNVoA44ozmr7K-J9# zztbF7mYe;0lk{V@(;-Z|hX8Y+fH(`}3~r;m&EBqBLI1a4>53}z43Qd~&Q#5`?$^Ut zP45vz=cA|sKcV)OGX^JEtd80`Q6#u_^A3T{v0w*5v(w$j&9as^2~M7V7EZ-;Tz^)D z8)8SWVvSx>LB3!3wwsTij&g^xA<94)h~E z)5#<6)u6={SQOB>5^_yhy2K34!AXk8E5U~7&nn%IDiKYU7z3s zu_I*iFcWhV+K;e3^)OAbSdD@+nXe_f35yDpTo3?#W$_Y~Y)0jQ%)#v(21@(VD zWmlC!g!Bqg^;y2;vJ5hD8#2Lz*BE#OMSq!#nwUh?dY}zn%d_E>$(2% zIC$aO06kUtPmRi!ukoaa`Lg?@EzX8XmLzEtc4ImJv)O;~+Rnuk#+oU!)@nX#Uw6ZRqx8FB{;NS@H|8Q0Xq@xa2+D^p%j)_?_QD z3&wnEh_P)#w)OKsuz0*Q`M*cPPhY?BlVAOlyCvc=Dd)UwY4JloEja4Ui*gk?_bM{* zIP)>5L%xnwV|d02-2j8hGrR<<5XPVj0QsBKFMz%k@78cIojujv2*}68=(NL&eR~%ma;b%<2F_n_d=#Mz(q$+xc1VIuewQ;NMB%My^usFbHcB8#vd-N^V%oii6{)6*H{QouJ%}R%F>(d>xRgvV7oCFc~SJTrb4y3rmmAxMt136xWm>D z+B9$FMFCn2+Pe{u+P(6b)bOFmKyHph!PqCthHDpa7G4@8QPGd>7e^Hl-{a@y{k>mY zAb^k?Zhm=0g&2Wh>P6bXG(P3ItT4P^ZXWBQa9vhoZNB6Bz#N z^tV}Scq3p(!H>>FaFMQh;spsVN+ATP?x&&_wXcRd(M8cgMS;n>|KwXhFI5KiRXn`f zu74V>T${L_ov$lH{WzvonF+t=EvUK0Dk(H;iU?-#ez!_^V97-#?9jo%+5Tx=vRl9W zma4zF1dbB?{NH6$A59S2K@B{FKl5*L$%{Za6G<>kLvR@Z>rKhq=kfVzg0EbO9~AUO zg8S%)K1}{k{q4}BPq>03lEbgczq%A!M3qwA@2!=bt7+X&37rnBQYTO1^BBA2VfN;a z*DL+{?6Wu}adN1&K62x1v*W9#2e3cGkDVlHpgz^d{?PsTd6FXIB+(dT|ANY11Zj>5 zQGYPiPvv66wqv@LKGy%2A;MuCVjB>5Zt?QxGiTww;>@_I>yBI7sf`_9K%x?{Mp}Ozckb%D@(0AVH7D73&WMmP4yQdCDYd!beDCCKCZ|#G?gcCk^rM%aT(C z>A_~RX7CmJM?|H-s6@`ztDe1|b--IPvna6W1b0lWQtpg9NcvY>Eo^nkT$HgH>SC9t zL~;3oR-nf-$cw$vr4A@2jWv{QbVCV^$9cZ^vSS8Vzx@3}k9`wMMW$Gn=P^fG!7X}w zYTlWXZvUHkZ{VRvI{6NW89kUAgw6e~sk&AXil>KSIDc+0KxUibhZcM$pnQ89LH?%k062DET~Yi4Ui=M0O$!Q@e{j^=bgF9@<>^ z&*W0AtVtw7`AAlT%Le>V#kx}s86g=C+*^RZDKd{qLt(DW{Lwen{wA>#!Q?D!Q~W}a zJa6Ug|DGQ}q?*KJ_u28*HmJ0b)h3~)hKW>3wz(AJ7siy%u+b2}cV(m=Dc*u<#rA2s$CPI|ytt~lTPoL(rga*4cQPgS05 zp|!vU&SgWHbP8hFVqOwGL|I$Dqv!neif8E%+p160r!CeDNdT zJ1kNno#Hw0O_dCZVWt*}RU#pJ5dYbjBbj@K-auT(ScW*DJ<)6^KQpQYGV-K^ZfyLi z=^@PLn=Uub*g71m3OVTir>KC$bcy=9AJ6mVY#ByC3X|-`M7?%=qiyi2L#Vd^YxKtS zvqtqB8{~RNU!Q`cY;+47#Ffxgn`&(8{SQ$L?**kW`bugqX69s0Hm8QC|&gmp{?5Kf34Ftym#?1 zWDSn>JwV}eTNgZZ3?Ojj*n4_=KX%81BmqtBpMu^YoFbQ8m2+$aOC737(b=XD_eP;l zK4N>^{%2WNX3t2V)XfaWoPPkGmkSP4(bSKfbgK}DTkUSwhHhy{7K@Fwx}c&Gj*{>C zR)l+8Pm$@Qp_newpP)x(tv1*ihuY$|KR^rA1p75=;1E*K{>>dX;Z1(*x zxJ=ejbo52KBl&$cVAJB9_eh7qQW9gbSb|;ztv*>^FA~(S>;*VThh98q$>o!NMZ_NHHC0LH5Hgy5p9p; zS%1i13hNLg*{oT)JIDn4XvcYNL^)Xr$0VfAj*x$qahRlmg?YoJgjD}VCrYqTM_57? z$F9Cr>o#<}t4Sl{aQMDMeNYzs9v8N6q&`x!G1x4WtIF6>*852d?K!IjA#;b02@L%8S2t(kl3yzk_+$EXKp zCI_`RxuyAZGC%2vutzFf(-S5@*2oD?9-xE^7wEdj9C@$^aTjhyo)%p7D(uZ8gn7m~ zj%--RI$rnGR4xw8uWNU&uuO)!ggiw1-p>_-BBiCY$W~kLDy@&YmM64}3H3jhyRJc{ z`Z}psWB1-6q28edSUt~-5&R5&U0BE4^Hb&Ws!Wog$tfOZAv?;#^YgK!r>$f9C9xkrshF!>n% zODibBEW_^xB#(H$c@!GxLrVp;U=^A+vqJ74mN#rEnIwl*ZD#oX_3FuJMFu{XvXNf2zje$FGl+x5y_pc&A;ggrNHhNb-yt- zKc(>g5-sh%E1)US(DN?HEyQ8Zj?00f>TEN{R;^B*jtWi_D&QZWvh*Qy)zl&P?5bwW z%`ZBgEY`yEo@{%6$KfSVb?N@^o3dQSJf`K>S<}?1?~m_dXOlFI2W$Zt`=eW4Fp;YL z+>myDh5oc`_lAtmo282mr>zuv;RbUfewR5^5k5UPPDrGC7u7{=su~XdDuafovlI+E z3vUX?v6{zOY+em;BfsD(3oQp=(SdJ$6!)Eu3j(2+iG$-G1^E1pt^%7w6nm`f#Ait4!50Ost`u(^30Ul$3`%98rQpyQDefss)LX%IB?4Q~a^~|G+_?QvC ze@a12CSYUFl=38Du|84?KJmNldLERydO5I~S*T1CNpSFlAQ+Hv2|8B`d>o|d0frKA z7sCGX8y*Jg(mB3nc}g3eRlv@+bmaLt33vsP~HH9YQnMakZn0 zLz-(qH=&wlekDVZA0@s2u~JlL=Uk9%k#mSo>97(!8e{b<&-}yez3g1KowUd0#B^ORKMT?7RHPozOTHkFHIx2 za6R|u|2Bp$MdXKqD&}|@hi0dbo%dMdiuak@O=``Rzq0hlzzR#EreFWvgx1nez)BGq zl5gJQ!+k;%${J z?4{<)wRuIh}m+qwE%oY`dCR`wBsr@ZQHy zM|QY>q*svJ8obQi6>7{7IJz4Ww6vNN!Qs^}Lvy5hkn-dXM9u~q2TH#lB8@xYOoB<@ z3TzMMVzvUu24IFMv%(-e08k)DbOR^u%(ua<#vV(x&1$FPL8I353{edpb+5`EP)TrL6+UN8qQp2$}ToMk5FcP+n|aE7`STWa~!Mq^p)!fTm*(&iX7N( zIXLC<1&$w%@JVBM*|%6>O;W#-@jX1!>_t|XcZhN*;=4S*_{M!Zu33kimksxan9_-( z<)Y=BdptW@<}V6|;h|aOjXM@-04F=~c2WS|L=*7%Q`pTAM;JhY z{CnKdi++}|Gp-^%2oPx68b17*Zk36wCOUB*Ogr_$-M%o5?>Vh?h9%R4-=x0XE1gUV zY#CWDf;-PWQUm#grIDSTNM@4#`RJI@7?PRIL@}TpY{iJ%^9C=l7p(8lw&bi;4#5LetFV z3CI;#RWbU#AyNjXRACs!!h=t#h(kPFnkrRpqOA+e;mUXme0TLIs+H7j%YU&KNA6Bp zy0my1b6ca)g48skI00-ER$>O_*c-l2D>j2L`_6oAXOYt1mGODOx??dVN8Crh?@L>$ z>?N3?$eR0jjr|nHNxaDE)m9RnTeW_eZK7Bgg`|Hq{sF80&s=zHAayS8mrTG@?|%nC zeB5MihqF%-8$?*G>-F^(Hau$84IIcX*g`Z{>Ouk!pkY#^R+zl%o?A)Y(ifajHK_q{ zw*&kpSoe!0%goq)8xM0Z|bEeTpal1J6Yf z00>$A6Lq=GYI(6!I*%Wy-aVtQy(Q(Lx-JE;s_Ud1ilAxr#I zmQrj^)`8zA&gp&hp|x2vS|OKq_qE+hR;Z%CEsibJ{I+cZw;{Uob9Jnp;2r=Kz^d~H z7-IYi-4DH`$y`jEI!(9Tx}C3X{>r}$CodWJ zdc*i;Yyc`i)xYqMR292~`YKogiBHg8#YMUt{Ev?SOnA1LO7(S{t-;c1x?J*^N{~JW z2578BIHkqQF8s!WAa7K;7|+T@;=X?m0A+)oE0bwC^@iYWxFte_qZh^ItR<{hTF0S{ zb-=ude-ZwXbZEG^>J>Nn1G9N=l-s0_v&s!!`21PaeaoIg$oGs(w(~qXE{5UVAR4Rl z8rRf<_BQ;GL@cifmzz!jtE?2jxoJDIYlXrpKGu`sl?Id!jcSj}MD4W4dnOsgY{BW* zC%OY92g&V)RQuRmLi)IAMhyBlNNU&SI!cyHdv~37z+tt)q%N9}dXo!0K1lip&^JC2 zexxV}9Gzm{rf*Mo2xkS!k=ip2fDJ3}u8v*WXlW&e6TI8)m0lqnv?d;DO96s{$H2P2 zNw3=~#v!Xwp=5PP72n|{9?{~U5ejhK*EPcKxY8nTiarE`J&W6AuTWjfw^KvAO#53vS@VF7C6`)sTTy%a z(MQvImGg*#L$vA4N$r@*msV8dJX>RkRQlI62mp>RQ|91?lwY0@7@sdTEkJ&$pdR+W z^uZ!LS!D^Pq5CYJ?C|pxoE?lE{CYb?^b(DZJ`$*L(Go(h67-rGR=C>$E8G3}mEX>g z`oU@+dB||3Y_5z@#CQ|@>fDZ0DUehK5RGBZ66Vh-+GzuR^7YZ;OaikQA@{|R)?kO1 z$mC(S&G420qgdACx9X=qZ~tdLb~H7;+JPLfxo)|te7$MO=^Wqr%Gdn!F16%m0}fsW z`1}jg+;xasWUkj6H^AxOyBE&ce*5hwBR)ji*7UL*ieRk6ss!_SxQ4zkO2rb1+4w>L_MR_g9qgK=mZ#fR=c*R$Z75}*~W@n;DYi;f+ z;r)ERG~@fD8(XjADlXm4Im2n)m|LM*Vw`B)MQYnQ3^hwmDQ`4TDAxi5_01j6u1VCZ zuRw(CyQ<>!M^#fjOlITMhZs1Z;pwG|*)a9u54=l&Nh75=JBl#T`vZ+<_^U{+qOsg< zrYbKRom+{IpDVdM3}0BHA(;$~y6~ByuD?KTTu~TKHIo3)Ae$~8|Xvm!D96X|tMDjzOZ2k@SCzd}V%!92<8M*@CC8C)=~xU~lf zGr|z1w0}QhM}A3RE(ypZ#sa%mq5Y%Z7O$8JsDmBb zTl>kIRyV!Nz_>1urRZkzv-=e*$C($9R4YuRelVgv}YISL;?i4vezVK5qix~q<=YYBH9F9cEf8&%9znh zc0D6t(}u;+Ndo`f_EFvNul|M(jfWoPp_HT4Yj%Btos8n#yS}O1|4r^V()ogLz_5Y; zQi>qHSR(Wfn6pSoAdcKql65AC>=$NDly(+;-p}S@n;J zt38^61m=sU?@(?=O9&+`>J z#c86^7eAydDPjya&OcZ{5K}JMT@IR~nC7_l0GluI$A4JRkzT8;#6I(|sdaJ{dZ@I< zzr3)78ITt%rdYGUeX#&+Sp1hQQTa2}T_js7HeH6EZg<1|f&2T7_Xryo_9;S_Vk1nE zN{4Ozd}LM}%jeK@Z|1HKX>bw%4=&63SoW(K(-34?SZE5qgn3wrF5B?5P6ME~Cl)E`W z*44_6O%jfc6&V7kT^_H{o;TJk{G#90RjH_NAd*G1anT%uXSc>!E+6whL&1gf*gFxg z345f=uq5B_$U82;ZJ=E=d^XnPFru~20nt(d02gcuqUJ{ExV1VhmzDYbUNw!%Q*Luu zE{+AMUMq8o$d_lPX;!5GMMeFp-T*$PmIJ%Pr&s@uYVxObwzyz^2%v zY-FR>xAymch;KzPsITtn44FC!$A1%Va zn=H{|K&Owr1jMSFP?e()&-@$48d$2Ti13(c_gNZu{Da*bDs@Xs3t1kNB?`1&LPGI` zGCD@V9`O)W>u{5yX?2}hYoCAb&Iu2+Unqw?y7`u%KY9A_ovk}r#Sq72)FlXro5MH9+fY zI+2JkAKWQEqMm+?$EP5Xi84VioW6FlPJI2%t9pAd9;z-8YEWkvnM!$v@OztVyKNr0 z{(#1@iV2W&#bU|gkJIC>v!u!Nhylrp1TjwZjg~J&RMXpw^nMjx`+jjB!5Z+Rlv zd_*kN^L>0Bf=meM1Q`1T4%}qeZkcp>?n4reFoBD*U&oeqM3YTwIOxJ5 z&MBmw<(GPQHxhI$m9P(MM+s$Dgh1wWd4nDYmhvR3Pyw%SgGaZ^eJ z#G}Ksh=f6C1o-;_S$#lY(Ncu+F{7m?{O0D!Oy}q-bV&!lvsX3oS8)WIJ zHj;O$wbDJr@@VwF&<@x`FGNUvfaso1Z_jx;#sq)}38XKmGm@dEH}XQ<9rEPTpA~lG zBR7engPcn;HflzTU5`AV|A@D`^xf$fTF5JY{jXY`qc;^_uk-O#KCfc8hww$sbQrtr zSd0->#()3Mb7Ja0bgm3P$EuE045PDVH{w=1LYsxGlUbHWveKdQt36-sfNb%v^JsIpI zoQ&Cu!Vo!>&Dg$4yi*^o!gBG;#zy|;xk%sM=v{m9sybLsY zt~Qe)ELktSfR-%9F>4RRd$QH7dI+WVf;I_T`-3Ab+|3cK{=v3xoZl#h|U9>}cPX4|@@E>}Zn4GX%LGBoXPH>lAhn{ri}@gQlW$ijU{ z#S67yd-E=6|#+@GlUl5lvdbt(0QH6;-&K) z!BewDoa*`Q!3ok!@Ao1kWHCQhI83(q)_%(B3k8IRkwZDUNX55DjCG!9CKf1|CyM`6 zn?3xLH!^}5Y+tzco)wBTxP zMGQkTc!0K64ZNIGBEA&IMX%D|bg&-;y$^#N#F4I2;C*yw7q9B_-xdLZUH%R`6!G*( zqSdk`taOx^?)6&bN?KOv{^0FLZ@=4FMT3(fi8>@k-0r;YV&?0PbFRKu8s^l3iWJCF zts4BEZoik7m6K-Zw^%UcNQCV}U4C+z-B?V1Es|PLCeWVdV9P!yr5jPL_}74Ew?O9h z{O-{Og&CwnYnDuT8BVh*!hw!Q4L?o!z9}CS%Mi67?mHeFxaGg$%M>t9*iA`6OLuZ0 zRK!%CpDFpP`1J7SF02^+II(Npq=%YXqso6;N#x5Uv_!aO59D}iO{!mg)-}rAnvYAWgRuQ9OEB8MZUj7}hBeWYR8og;s_mxj{T^WtnrHascOmx2n7$zDo}5GC z;GWO1n?s3*spCC{t)+el%}Kh7i2$pO4#OLgOu}9c)?ol5v^QaZpG=dzQfCM8g?B-w zr8z?p-sJ{3#lDEJfl1*>4{8vQY)Y98eyUMUEhw+4N6f;HjkEIZF9&%F!2pJ~mahIK zwIb-TBxDBmCxWV`AivJR0-{BJ_Y0y{jN_Zg97%+Y2gpZ$X11CRT{304>bXa*j zjKCnZh|eZM>7yqDp>KxO%ujRcM*h+nH9@D@)Ke$*4OJc;$f`)vG+8_Lk!ecu6e6$wJ&&Eaa7bsR%k$8$B&zQl-9JX|ezP|?7!UO(4{P0aUT;t@ zK0Q1Y@8o4?R$7eUbjt97)I$V87^m1r(}qZwnnyFfSy13xxOl0d?_7Q(LE-C3aCZ3* z;rVXSYNwumsTJsSLYFz_or4>=S9$T&94bqEWKT0sTVYum^BxyYWKO~F+ZE|$1P-$& zSK7lguyL-C>qL%rQv(hJ9wLOpGc3E*$Yvht9<)_zC&@_ien!Me3D~pvIccS=T1*xR zfhKub*vnxMGI}3(UIbIB=upcl!0wh*9=M4&YD*+Kuwv` zm+sNL0&sPV}?~W@I3ltedc?4(V{<^ySY4&NGCe7pr?QE@g;vYJe(4u4IPg29xIS@ZRW_ds^;6niEF$FtQDgL99xr;N zzcqyQ6YkNVtmQX5&FJ*M+%DaLcE35N2j$vrmRyJ5J~r|_pHoDZeYP)s7^+?_!wxa@ zG1;itkyRfdLFWP6rC@TYWpj!faIUaRO~&i9H{gDU&Q#HTyzA!1BH&a_Eo9a9ZmtW0n>ctymNqcfe$Ms(G%!F{YnABzd!jA1i>^_ z8NO08K+d=_Sll623T=E~l`yVpt`K5(*O0A0$bf^wyNCsPr+Csn^z#t@P%)H9)*szF zQE7Agm<5dXBX;cliLb*-0B05N#+>w7v!tl;y7Kve234yEYw8V*6{#xfefw&HCj{fp zwI`O`3skf8vfof8kpRpfdslS|xNRKCZLADdos6X>n8p29sPR!0RvCf+|2T47-g1oX zQwln^caApZ&M@}}ZsYeFt};wY)T<$?UbH^hr6$=ov=|~%(sMY_mF%t6{vx3+_K6J4 z6$C>`Ve7PbfOQh9fWG;z4j>E}N4h~fg-!3OFlK;^EC&R4 z`=f1UJPXUaIkzC_$c8yw9btaupjGXu4Mw+hRC7E3>VLkT5U6ZP7FYl$HqGHg$ik7@ z*#AwVx2DU;45ib@;mpHV29BzYKoC*qG^(PLf3Ht*XPB0U`n*qba^tQW1RjGyhE>eF zgSDckW9MLq#Ux&cSg^Fb30xrK(zLlw%1Ua~OvXcgskjuQchQ`-Im}~uN&*{LYmeRG zvz94JpIn!Mgn0>DC4ph2ax;c=+a`^bNhem@sF||4GkcDEel#PSy=}Mvx#h~JN8Xrr zrv8+~2Q0SoOIaY4?7r6?;+pQFP&r{0+5Mg}o_xB0wSR)g(mi@#;c=7;G8EO9B?_=H zk@B0f1dZ>r5lUyBmB^7CZSZm=LU1i#&bq>^injX!Z4*ET6Z!Cq2VxRY7uIkpRD7%_ zuLq~D$qBH{I#ch)IsG~Cu^_kbXSTLVXW+~NLd8|<9S3qpe%0z(4loiQD80EDT>TKlV*WhyVcxZk9gP&XISpEL)1;^>FH*K zHa;&M%Www_%%SqjbFscm=b53i87NAO^Zv1a4}oqf`MJvoSh2R#We%PYlshTR@I;T< zY|;;w!wKL|?|vn9=_M3}V{ghKf(3S0Nml!tBO!-hviV?Yxh#!RD}R+9G8RK5M-J;h+{klO#MuO5+fOjYv3=# zGe}M6i8#NUpT{ESN}t@>LZF=o&(Nr$52KQI)i>Nkal1Ls6dz2cMnHXMZ`A}0jZpGB z&48)>0fdUKSOQ(0+>Y!R)OT>qi;W0wJIXQ`^n7$l_76aOZ)H{8LL5WbHWY7;=9CAz zIvpRCMOEgI_A5X%GaN;tY$X6#s3Zz4z+w`E6cUo!?^qlM`Nd)A@4f_gWi*>A2;Ode zO!ACuPUpC$G(heq66~f#j^QnrN|LXw^a>KT>8xxdaYWs&O`U$|dQL@Wi&Vr$YCrO+ zRfu#Ps%|ypu-5xvOyT%jpK@As1rmXlAP=$&pZ5d=aKDZYk6saj&1OSsBUeKxkFx}G z_-{-Y^mP+GL#W=NYGz?btzj-hIlQrUlNHMF0TvG9Zf6nmXZyfWR%SuLLoXT&@bia z7nk;P;r((39d`zEGtD&KLF5ROPuiho)Mp_F-NlQ*p1tWtxHEgx;y*~NDPq3T;c42M z!+(}v5TxGY+NK~DRVKZTqCU(ri6VPAZs2Z?o!01_sAh6*b6~isHnZ@Bg4g29F9xeF z+h1bN9m;aY2me3O%#aXkD${bS>$G_)Wu#CSfmHo9Xe6zzp)>BIG|17&(1K#8{FNa+ zE32>C$#X2j!_Ntb8stCm@S^2l>|@Q>(4>;u+moPO2+K#ep=O&%NzxP6Yp4BZ?;>{- z7um{GOky3S9>_uFe#J)(XH#vM?$=xHGMnn;5ebTI$T5{&(iO(*9>5#TO|@1o2oO3$ ze0Ve5NW4cN0_f)Y0Zf%g64xH@)Hr^+7}bi}SZS7j_-^^ktWfi$h~O@CZ~6r&A37Lg z?otg@O1T#o{yT#y6T3pLMlzmwA0sK?w|8Mk8GLupE`W1q%<*T*VwZL|22TXJmdP4% zpG;5h4iz0=%oL#l$u8@9k zNI=!E%aD>)bM2=rH2QnySZlPDBExrQX|CY8fJOp`@tJz6KdNCMmgbgz8!_%S6A&AK zx4Ix)San2uc1ZK+rNm3$GsaLdh|c3^tK7{sUAwjoJTtDqefdnf^vkmr9~;TM9lggk z8Gk<<+;$D9G1m=hq8Rd7@bXgLuiz`^$38t9XK9dGrE2fx=(-D8-^)9D0gkYxA-#ID zgR^&H2noVdXYO3K{6QQA+!*}j$Wd4g`hCl1ONqWi5<*XXKy_-b%*vy!*V#&Cx|s@G zMZj&JB%9|E^Nk(DtVd%%8ma(4k+8CN^gFzTRXi3ibA(7BoA@13@LVF1I0hl1UaG2GFS8B|532*I&!L zI;_qJF>Ku9OU%J>sNFBn2T#n|KKMrchAEpfnzTj>({EKgKo-)7wDo)}-sfD{6~d`u zJ4rFtr>XwDLPqLL9pGS#Jm2!EQ+F+9bMNI1%MtX5|56*ALB4!6qh1zjIKQz|5QOGY zWri-J7ar*8)%&O3Sos@rmft((K#N2%M#OGIh|)|AupBUpCVIMCi> z7;s?`%}9iSF<{&o1YnCZ6@?5Uto;`Ab8Cf&zgFQ;E4-s2a7F~Z(C@%esnvqA#1Bi1 z`x2lUm4dEQ2zJOKcTYY^P~wdKb{_1R%>@xv3Y$fx(e31))#AfT7L9Tb(Vy|rXAD@c zfSPLrPt==t=L`L8M{hi5rvR_Xk?$c;G06Zv%ZVR$z#J@w>(R1o1hqKy`-7_DDTctR zw&`~QudTVsbseVuHxbmNm?ZO{F@Ae$_+58eIa?*r%Vsuf=M}}r{0SuY;Q;DajSXnk zekUk^zSl^<9t(A-3`E~H#p=5F2$r7Y+J?Dl=2UDlkAvsshA$%fJAj$>{&uIxnzr{> zt}3s^Jw0;r zd#x=I zgH}>%$Z~S@S(YHKRj7t+4o6wi8^j95FqeH=6R^yUNKoLf0@1fwO9N4ypX_EiAB_lm z14Z>!{F*3gx^Za8Rd{r>gG-pnMp>WzZhD(X&4NLH48}=rywf#+S2pAiSQ6r^qM%_e zQ>`}Xa*Zna&_A)pnT!e6Cy#$KosQOvr2u8@NDd0t$CLol6GAA+!=aqggBxj3ag6y0 zk(CW_Bd&L|%{+T(e!8~upi?*U?2q$A7#x-rz;YJp5{B3YL^oima2p4sz`zF z5~#FI5H-x^G?(@rQQ`V=3sF9GsRyH9{r_KKNJD8L1aS7(VdK3MNb+ma?iGqOaY zQZy%h{>tQ~=mr(o&n@7^xhl_yACd-nzz*9PJAofCUgKgqH$IhQ@+jD;a!m1`75KW) zMU5OwLFugA>$Ifsw=*Kezk$@&nzL44+;Boi(;Z$Or6l|Uf_WE#Jh8emn5#N9Evxq{ z)m-JB5jWt_G@VohJ_oXCI)~tdGx#`WdJFAHyXWU;)rq*VxH99K4s1o^nxlK2=aZRl zF2VwcOV&R!i4>~WgUv}UoPdOvMJXBTU^j;6oa(7_A!E~2+TgxCEW>*>ZpUj@g;pH9 zBxyc70l`kIhIOV8HiIs_j{iT38iw8@U-k(WW`ayTkH%#`D+~0=yS^sm8?+*78!}01 z6nWVA>oN~n*FuD@tj2xvWYNq8?Llo8@K%;%6aV@H5 z1`LdlGKH8l7{k{E?q7d^xdhP5IWZRigC8fCZ zWJm>uJK%nJ2?(Eb^R>TN2x0kT-4~54mUCq;Y^Q5c1_yXDOn=AG)fYJT)I0v~Eu-Um zFd@eXip$88m^;#ey+e2-+OQBua#t?3^0d`S>UFGkgn0k&d!Tqw%a?~^07#7L!2Mag z3n;ycgwqGd1z1Ke&`Mr9W@Te&O`nS0PFda?go*ytU4(yNOpJTtzqg$95>3068C$bC%i-b1D$R1Xp4qQHdb=y>BOl3c3Tp2ZG|6M@JUUN+YT~1 zHOKuZ?=G5cd@CqOLwsZIFLjq~)jX#Bj$w>%EC=?J$Err9GzYz;IWT~awSpZoiEma6 zx^AH4cysSvk;je^8c&lV{2O#ptyW>Tt57XLRI73RzkuEBI%cb0=a=y<6?qn{JX+x) zjLVl~2nrFsL7W=pBsPW?^bl=EG-sV~;HYf(F}KQL=p8pTy3m5tB_EG1i$*(6x2*i7{T_|8WLhCnCQgt zD+c6$;2j)Z8y?dHYhqbac%4RnzSdeVjsMHiSgGXhiXQ@ke!ugt&yl=*5Qa*R-RUDV zK$fjk6~cPmgz?xZTDrU5%f8E2=I^~phXnJUc$)@YND%G$H&N>-i>_U>I!26*Sc4#8 zo;*mBgtdk?cv$xf_o<5pZEV$iM7QUWM1hy$dSlZJ9JUuz`W^ed1@ORrOI`L`0BDQ0 z=LZx$@u#3p53DDS%=rkr)uy0i9ki@x5X=y-*4Rf2Dd=VV@Vy*#^yB<~7=;OtV4Iu6 z!@j&GqxnJSM;19i`qEUbAf?S(#_uO8{FmfYguC2ZKk`e};#`$h@8YGC3c>c&GkcpR zz4Hh;!qwe}kB2C@_$EY}b+n@5~MbAs~F4C-!=Bp#KpvRhZReoCTJ*I;!8 z>IP@kBqQ`?q_XiaP65XfHyOhPjek8i6N7O!Ejw6#E3dFJ`C3ZaLbq}w{*wv2vutv= z-ak@436~MvQX#A?G*4-*>!%XzQR6o$(n*5VC}Xe-1r5xQusRGmCi6$K$+g&UO>S!q zH=bmTg5(n+RPRMf^}bA_x~h~0X~M3+rohL*SqaEjk-aQ$d>fr${(1xBgn$wSTKMDa zsDnGf>!vi)y($N8*z9%xd;dgU*0JDgQ0phrNeRL4Kw+MtXw+t(O z8VKr?&nJ^*tW;u+=E`3iyMYR@aUUV619;Fp`GPONctDXiJqc7xS<3uzLGzLB#)a8d z@#i*CaQvarP3{D~S5D1E>g#?oR?W%ZfWYk&*P^9!lO8I=4;L|MG+UVCN4V&gDU2M@ zqgY;^f(@5^7Yn9Q7FM55myWP86RC+s@4vG8rc0Hl=f65WMK4$KRD3!})PWdwNow!A zzpeW8PAu|trfl$4N;+Zx-0#v|(!UsshLOaf#Ds4=na#xp^7NSEelZFQY@m;_T#;Oy z%IFW{U~w7|)hhgME5^i!xyOf~JE!velIYzOR=s&w80+0{$jP$z-q@u0A8)&_Fu}Eo z$>P#qz7M-aKty$6_e`0cn%TqOq|2rj76FaX0M%o{SQEUVdAWwkHu)+!Q5%Ovl%JAzxfnB0k)o*O_Xw7y{H^``g@shC>dE4t*cFm~;g4QfF)d{<&JrD~^Oa6+;~RiB>fO*%0fB(o{@npG#J?3{tRfBKWNn}8chqJtXyMc@}B^1*nT_v zwwR4HfhUhAhXf_+8q&^$doIqlo+cnLBge(6QAk<`dA&aaMy`5D?kz8szmi%C! zGox79_>e6is+MT2=Nfm4Z_iv}J%!JQkW;)MePBz2%)j#qJ3g8!aewQi|3#v?qWqvu zoMo>rkA^X-lnS7`q%UiY(0+;r832qjWrRd0#kW&Qy_WV zPS2M!vAoyu9_`Ecbqt%2!Yi_#rNeEty??+t4cIn-O*(IFg2P@a42P=N#Af z`NyFrxN-qtix3K)DqUKQ(osj{?DX^;sEzFA3pH!;A$0%`bTvytekE4kXL!6=Y4wBpsGq4)q&_u<&y+9G$#V! zUTXl_kpY*4O|QP=9?G-F75~OG4Wy;9s2714D6{#-EWP_6jXi0R}xI&0k6T zlN)*K4xx!@?e!8=0!;;+<$hGMVKtSQLZvOF?Opv?@c#d_Wz0YE))e@p?2d)A#0u#X ztLxW!VN*A=IL0!nvr!FZZRbaPbwGg&m{9uh84=@R>12kOCV7kuhKO+*vI*v^m01RD z+p8t+9EL(~8ND*`L?Z@dck_Ud%B&El3yJ`%9> zzZ-QLyLqu4VX9E}_Icu=Y}L&5CY3?JyU&D3d)f_wXLYDn8{4fzKr%P=>SJO96Zd8S z|1d{HIeP18po|Q;eOLeoWokKg4AG&qtJOl!3fq7RqHpql|5xKno0If?@u7n;#LbM$ zXDvh7yCNJu6b6G+_M~2XOIpb2tInkwW{-g7p8;vymej8G`o@i#4v9C;{LCA*xg_b) z!G^;sCLQ4hLb@Ynah0|R;MB##p#*1}^pl~Krt-dNX9|FJqd)A&;<1={%~6bf;cLio zfS!+j_q#qd42+~u1;Hzk3N5nuWm~w8O z%RinHBL36i1?Qe3wK-^Jjx4n&u?bM3xLn$(w5yLj?ComPiQ!!n2S$>G{>70rq_I)! zH5t!JRWeqQus;g~yRovg89irYDt7*YDqB9n8!H(BzdKI*r>ka`z2!Eu5$Sx@HdMMJ zF%jYHH`V}UyJcoT;{jKYy){nKFqZ3nak>$ef^5S7dvCBprw}c#HxUrCG16o7?#Usy zZRDn#3*D%b5Pb>q*$zhfD=2BU?7Ri@mDfWB*G-Ual{_3OkjN1$<@oES=`{^bSBm^4zGa@6C*B}wTOiW?8Gi1h;g6<0hlv61ovCpG00ud)^E_d zx=i!(ibOnS{9MC2gRXVCf_kxo-HH6MVVrK*jr&c3@A0J<0GK4wv)6!q+Vp|*OJ8pH zrV5RLu;0Qfmj+5J`;{i&`q-g)(TpCZXQibr6|pI>r@tcfR4O)^zJ1(0Yuz>@ADd)Mc?YITU8hNL~DDG9s=pna8AQmH|hb zeOH8P8YBS|-hv2OZfH#FT z^bOP_P zC^qO#M8H&y#ns6btA+2eaabc`t$5hIoKs@*4SgFC0^Un@m1#>9=k}4QBPju~`H9k% z;v2KVK(#9S!oAiz|K&n+laN^@1y427WclSv&YCa3LzclA-PrRiqhK4uVqxmawf(xp z4G@Ey5u#uG0vC(1D+TC-D{rkZEAWq777&cf6T!iVZHRH z!hvdMesOQ!@0sfQ;iGj_B#n+GKVe_8RUOyhS+pk(thbVn3L%LtwjoA*q_PPOTc(4* zSjqo8PbtQ=+zQ()?!6f!Mo&L6aHUzy{N7O8)X%`{m{vZCan-YFLBi->SEu_s{0#Ko z1g^z-1B;7m9Fr~(V^P0YM*ic)4IS?)U!X&iQJIpZ#2&tQV{#$c{5(DCn+f9E2V%bS z7KlB6NB7MA55m9Lb0BAxlCb5^dDy+|Ywnh8RxAzt{knsT-0hy-&{xa=0OTRlJUp-d zOSBhQN|R@HT@ScO#*&Fjjz6%tds$ctladbh`0|v|iP;&=)2rn{cM!9``i z_*G2yBA^txT7Ak+BGj>yN5_Fc*|h5DK*OF9KTYjEwsWb<0qHBksNe1oc1%eMwQigs z-DaI4a^gAEno&#KDLr`D?$?JMnGgVG@ID(-@00mr(049}Kv$`6H*ULV^fdIHkgkA$ zlGQV(Gb;E8k0pqYAm8i!I}C*6wSS9`kgtK2`FB}_{UD}HgIMVJMQ+DF+tv&B;7MJ3 z`^q;|k4P^N`>VyLE@{R=aFh#gl#u~Me=Cv4{z?7&A}kSETVQfvK>86Vh{ac&DV00L zOw4)yBXM#Mt+D*SY2$X`;pGL9*|{qlBgS*_K%14cV|oqu{pF^EX1MID+}Qin*o<;$ zk!Ff{BuQ@^cSRjw>*9!77|{&Do`9V|=%nK-%EE}rJo$HN_-A}#r!q2T&#Ef_wEMg9$;VDR9VpnWHDJV1xA)iy=;@4!46jJeYmdl zaMyuQHqlU(C*Qe)ni}OtPS(DU7UMKjZe->C#}j?nLxdy)vTefnBp`3=&YScUi?PFc zwNO!ZyE)$(9kog6%xu296WoaB)^*>>SVYBF>TOogjBp-4p7vqYoRZ46&)sw@O2u?Z zxj6{k-a`;5vfBfz-T&9bR`wLt!`k7FWOLQRM?ZTNlI2x!Tke_ME@ruQ-mV&%#q4%6 z-k21jdxl2b+hJm&tNI|sG|!?Xz`XI>mDx@j0vr2ZiFjSs!Zo=AL_bU10BrWft42~1 zNYri`S>tsRb05;>tY^$JWcor+zmo{4tUoaqj&L}g&WGa|hCe1@bVy-NTVjR)QMbPL z4oWkMFvPS1Jf9c|iijA2o^lY&ND4{&Ffo5gMcQl+HF^B^~G9~-kiPx<<|*64-8tX!2irWOe+*y50AC=It9E(0leEO27!2Oy3&hwB*u-DGHdQ(Jb^ zp1eP9u^uN89aXowRbG>xc~rTSNZm<0i3~Exf1DWRiZ0MeXFurl^S))9FYbqhZW6QZ z;M27Mb{Hr{n# zq;N=EP+^&0mUZnX+eo!hS%9~UL{29S5H)|~p6{eIVoyna)N{J?66rJ#Od$kSA-AdFnaJMyN3CYSgF< zx7TARm2}|yivDK-EgEq=ZEq4DK(7y7jS-+^)ukwBs{+xKxZWLo)(04&!uWd`I-IZ| z6~1I{L`v*;(0;5>FhOrQ>e%^aO-AN|lDS94Q_npgkv8@9Ec|(O3ZoTG9;Jlp=;gz8 z%)=7nea5QKf&R8%N=pZn+PXYp$bZ^TIe%d&r3`=WT5_#h!XHUAvOcdJ(N5yka5@i( zSLhaY74w#Kqmti830JB@a# zQKH|N_3!KE#Y6&Hu*6&_mB|fjkj94zZd8cm@MEl9XTzP~5I#+S56+_J285O?hg}`f zu8E<45(j8Ub}fLl8IlDGP!_D9z7jf!{ZcNyT@nFxqVloj|XRu(#rwtykithLso+{nr zcbOn{f_72Ns=Oy1Fczlk^_5)3MSOeWVY*aBFMs{>2*v&#X;x&5d(*s=4M9=G`KBhV zgs^f&cQg*D{izwFEV0gu;kSc9&(NWv(Vzr`9TBQaD^K}I435?nAM#wYU}rQb1<#~M zDb1P)lUy|^O8RX_7>|B>3R(b+LW1`BgQjMK8B00YvTbew%uf2kM~DBn?w(N+*ClzT z>J5MqxHgh*-mJ3K0Z?9O>YZW(TKU+Uv0gwM15hM=9Qmm$hKp#`Rvk}Ql<}OZaP#v! zsw65N(vaXANE`ww0|*)3Kjc+rKE~vaEf@IHsDC6DK)Yg%@~s>BI(0eDfy@{=fdIEu5v%BUHUD29JkZ z9lVHS>1B_)Y-j*j{LiiR9){7V&xv1E+av&Z;!@E~A1RbG7^%SwBI|n=Mq0ScbQbxK z0Z7k*e>iyu5-QtLc`vGWHLo;%te=cp-hp)GenVmSN;epPGT8C>#J@0Kh>i7S6bfL) zFA;UlORh`3Mpma9h=qI)KvZ+MKRtYno6lvC_r1LSSS4#HrP6grV9Yn%i%*nY3p6H) zI*i~@sgE!Kaa{=Kdz^?v7KT#38MQhO8#Nh+5Ty$wyOh*m^*!3FTU8=jBamt^ecMX$ zHr|R}f?Zf#ipHAA&T>^Z|iF@ns{n z;wel(FCGl_`VQ+bg%3-;bbfad)I>EmT!{!|Cc&@6j6S}Pxk(v{qrxhR*5 z+|OZ}7gN6bN`Ku>kR-uQ{pOjff)mK$(?XIq=1H2%7+Ct(bdUMM4qaZ&pw_UgUVNc> zJ5ZA3y6-Ue-JNU_=JzMungCsiNi4JQ-+wX;5Y{`=+&fw3{9QgVd1<63Bo;1C;6YAt zk<>mHoXY2(-R-s31A!I)+&7AawB=Nu3@u}0)w&Zu_|&U=puRsbk-VgyxYN{NYC=JX zs>vY+5$Z9a;f>a#J4uPx6fl8xLkDU1gHuG$fVjG~&h$y*30)e_EYl($IGEl{YIfgll}uyWG={PBA=;;%s*Z+U4FQX6DuK#Pe(Wduvo7Zd*TjVPqePvdzGz>vF8D`Q$VDLtI5=Toni6m0dT=xn6Et`rE+^Uw1O0830+^x^2!5Vo2-w6VChtfWa#MyNx+9&2#(^=|!cNi4jKpGfr<%Z~d(YN7V;yg_yrlJU$nvZ(M$vyr&V0h$jFGD=z zz=c}I#b+OdsOFX%^jo>C26yc8cujz>o_~dRF?gvc>vl8O3qpiiV8L}lHMm{GDhHKh zTcec(P(!?pTKXV($k$Nf!cJtm5L3@e#Y7LHUy`t2={x~0n=>>+EdT%m01_z^IaBC* zf5zG(L(Nis406|PrTMOy0~#I8DSAO03xX6PG#@6QPBz6Z_XiatFE+}k@%U#f(Espi z*7lXaAk`sq1rE>J|6**Z(!6KqUH>+LV=oJ4&J zi@Nth_KyKX?6AbXRzo9ULSQxl_6M=4HfVH-Tz`~`{-zw0{QgE~4D$3G(j8lX_$P+8 z&W&*kSlf8|jSATtv#rCH|vCQhHP!(y z4I5-UM(>%znJP4TZcB-SKghBucR>XTjkJh3{9@d?d?LSV=Ot|Mea{$+DGth8lx-1O zC9@MNYf93}24 zOV|mSb8HnWE0&u;+bdkLUlmx3FCV(cwQ&6e9EOW8UqB`44P{(~>xn`9xl#qu^1& z-J8bfgq(dSQxAO`F&3In;<1#Q_K-p-)FMVCmV@^o38lMuFUvXjG^V<@c&4p|(RXgd zq9JcT4#03matWGo6OC5;AjG0zaQp()^S8M!M^tK5LRP|t;-h7mT=I+QN4s}G_qdjV zP?~1FNonRxsCV163&``nx;IK>+q>2PmwqlTUmIpp*r55H2V9p1Lq7p;!})zV8rNMf zl)MIQ>Uo>_YaAMW{sJ1>deVPw>3GF3NMmRj#<4!B>8IIrZ&bc%5IR{(2?9tqEoPw! zRB)9L3cA&lowl#vVP;pyx{O7kAjKKM70rS)Xt}T*Z|eo+^B}i2o#U|BI6?5@gbIOD ziQD8Npm=<5!CWeL@jDds_8!ZqafkO}pL$Yl7C+@x`{B&pf}Pz?wjwgv^B)qJmk}!I zoH!VJ9NC>?ZMj@A!DcN&*XAydASG=l*un3;9&5`ywt`%(QY$xz^tZy%*grG&Nxm-* zT0A@Y@ssE6rrF93gQ=N5$Q5x@5dGw<3*u7UThhV?H5Fg+A z%EefNiZhv zN8{RW#kibde2uqrv7Ahry+(I1Q;fYC52;v3@0-~al}cI;jN@cfv8lVM6zH&WzZ4o;2e0Lw{z1*W2U894~jKHg8i{aKBjdQDc9XhMmch3oTBN z9EL};eJCvqIP*X>QOi&94b@GfW;{8Lg)w-!165qGr0ns7dajV;yc|-4&b}4|l()!I zkx~N>vC4qL)|M2V?C$4Ii8AC3*~5vFlL*eY$_DpSXK*$%3ojn=*WPP_P^HicuW(Wh z)aP(el+Tj-pG5(6u|EHZneHI?w7w735t78B?n$2}l`ad}UvZYYLTKJ*HF1(B$y8W8 zd`-Oq%MFVypie6XjYKwmG zD~|t0*=&W5Fz7ah*@$fiU+}ZKT9)Ltyp!Nj#+XeVJ~ap?Jkb!5BWwD(k@ND{0#q2v zv+{kPf`J{URUpDkZ28%;{dP&97B?9d6%FHQ#!COc>8XrfcxhDl%gToJW|I3IgZ^E2 z;Lu>&As27k~ zyJ6!Msfl@LGV_$Dm>b&884T)Yn&R_J!3JpZ1$BM1ZynFP!js5^=Z`q5Q%zJWtoIcJ ze-9>zEGvU;CYQjjkNuC-v&X7Mot{|bXVN%fUFJ_nC{E@YG4 zmf5ix2Vk;U=?Zc>yT=%DlKu2Ci4S55s)7F@eSIza%>nNCp)4ewgIi^lQeY=Dw3ObF zABaX^hNq?<8tZrO7$AE-A6eFRIYq_Lb2(1Gk7KrdJ46v>VY~09N89~enXutlt{Hy) z4r~A5w+Cq=@PRjI`x#xu$S_eIwp0c-2vj(<>%^U>i`$vFc{>eF9#lqm^*~CwMbamr z-dPE@I?VS0r<2Kg>VukNO&6{2S{MO8nKonwK%sh&k+;My^ovd81a$BoAoll|6-QR_ zRmjrCYst;?`;ooWpE#Na9g3(DG|C(86B!lxEDe-gzNbPwnE-z&bIFaT@!Wpk`~`}D zA4mGA#QA4^$A`1nwUUQt@rfSm%#Osr?$lX6_@z;Q_k*#dRK(qSJBVV!fUV+<=jhXg_Uy{lJ^!?uB6)3)R8Zc2xR)vXwAGc6U@zSEN>? z0P4!X^~yPX?DbCbUT^WeHf86(CWrk87bJGhRtGV8^vLy_kt`6RcuGM7z69oz&g zePFCYJU-l3qI$M{H%)Erx8I!P8o{fakf;!;zk&6RB$x|+TB`~jzs-t26EMj|+wM?r z4%%hx!YS;=muI?%n!okZJJ(UopF zye^CQ{Lq-D8vzLUhR9=1NjMCFVRnDsyn0Kz^GYzMq)FXY6thCt$zWSqn6K96qGi^SwEpn6WKWoHEY-Hv*{ zKjMdL{X*BfZ4%xoJo~`s3vDBT$#moZ>L<@S;RxNer8G!Uke(3EdP|dEkv)p5(Zx%%jKc9vyj<3efRC0ORkfVn&#{ijS7s2-Efmp;)UX{RXfU;qL>03@MM zl%@)27{B>y!C2;iKp_9_4!cKYpJe{Uh#-<4V178)n+6|xvJc+y_VG6U z&bwq*!EyYfDW3o{V z9ttdnQq3~xFe^08#q9Kb20f2>U&yYWqCHMIQ2f@aP&n|NueYpHlrLfvzn`*_cObR6 z4@l`%?BT|8GukdP2v$8#Skw_3p*zgB3GAo5ud~AG47DlEHmtObGgG}0I;w7n<$G+6i}KTja+=cXWw-J z2d%n(l{XTvp-iMMwOuAT**+VpwKo=sg!O!Kv5&2c=%6A;n*1zgOB07jw zVcc$wh^OXCZi*V9H||m6s5xn^eb5hJo#}^1Pl3mjpqiI4=8S&6fZ;i2GtWD13fbql zSsZ?qg=4+(Irprm_Q$;zEE|c_4`c>(WWIb-07~dkOrYl7UMmKef?EY)U9~a9hJek+ zOzj=9h1npSPaaDx2WwaMir(qE|Z&r7rA;NC; zAI>;WtO8=P)`wd%ax#OTognivKneujl*B}kn~JriFjHJa>3@#lm6&Pdv=?aUmj{Fu)ND;$Dlok zDf62HnAXuYE840oeAk6Ayq~tGmt+f{y;JYblusp7FYf|vI=ap)3}hv@*hup*2Bd(l zNUKm{3xWp-v(K9CHXsWKr%l=Q93)2NAQ3x*-y7b_uXyk`m~l7 zu2;YWH7jBv>M2m|syaw5a9P`G1!aGu3-!|_CjD^ps#$9jli4;o5tP=^$|;jfGEQzb9*PcfgX#IS{e zD;8H24Na(~ViV3(z8Kc+$mkFy^=>k8R0&kobR7Op<~esFpV~0I%nFflZP1n3%`O>c zL;I0SMkQwtq@(MqNjXegL1UEVf$s+)xp*lWfLh43C%R zf2#Z?|E^ShVdR~UWzhd2{`L~>qXPFj8TPo z&M>~1%L1k#$&_Bh&u`Zf~{ZMgRdQ{ zBDGvqny`?++>fm5A2#PFmdHHTgB1~d8l&|;P1jm@$ozY)vhTCIAAD^AAX$(3oIIdX za96Kjky9h*s*4W#!xl;$`nZyUazZOg$Kzla4e<9~wapNjS!xX~^tyB*fQH|5-mk<{$jAp1Z!(j=8*qKgK2}yvOfTz<;>X?hs;ZTw&5g zMN`3~->ywi9wX+9>0|4P;1tCJLh_832}Jw0AxEQmYx*JNC|3du-F=Nc*Om0`GtL^r ztA&}L+s&`>H~-n7!kA@zn0-DiqSWLbtzg59c->8@2)lg4z!ptI98xj44#3f}-+Q|B zoZQA?2BXU=0 z6zc3Hc5%TGGCuSw=+Z(H`~K2}c7*xxs8m*M^Vax(jTsZwRuS|Pj;@E~skylxW7QWE z&^}PrQq|;Thd*Q*CyqNBVTN3CFR#J)1Vb%!*X4p;qJGtHf=4s53wt~B0lvUxOaU=Q z*JYhQ0sS;=)BiDS>S#1i7T^C4$@~XV#oWqSbA7J0TTGiMHudlbOEpM{odMw}rC-R^ zj@^cKyt&v0g`yjBcty6ZbQNr(QcunX!xAL-jh3>c=NAk#twR@1akqY| z)G8$@Tz&f3BEhdpoCqw7*Va7?pkrmi9XB%R?XU7|qt2s>lmE|X2d6*zh7xQhp9kig zbuF!(5b;wTwdB(0a^gNdyxQci&Fpkt7!h9U`q(;PV`koW267na1>a*_Msr7QO^D|T42*afOIIib64CZNJcC@$6WLZQI+HV%0v-a5ct``F(h_i@nGePu_Q= znnhJ9^?}(}VTp~9-sVLd_m{!&l?~_(*JG?_=<2|II`F>CZ=CIGwAd;e%uaa>TOTt1 z>C9>*!8dQzIg$7Rx;gckd`td$_cx>m@sYPD6J-5ulf_Y{BV|M!p>$9VUzxNL%i-9xpRpJ|@8rnK3PmV_l8l-~tq09(2x>hjInv8qaJ0BUyjP9zY zbyn`+0d=s*z_^Eo&c*`f5Q;V;0A^l74pVPyIs4x#j!=yK=q?TOFEksYTibk?EZr|Q zv)V`DkRYLxH-k797{os4+cK8Iaz(955^gP(g5U66Lv4rK!!)Hmg&6q?Ql*D!exF2D z&{gurT|;G&xLaw9(<4fA+nC*Pnly|x%{Y8sO7`sAiy0u^;)PMa>0$C6#{E>u@5a?K zGW3{`LcjOMRWAEy4l8fTct5pcyrK-flFJ(_E0;92$WLXQM}gYs;DIIj%2RDH)N=_H zY>7?h6ARMW19s;(23H-Ymmzn$umU<(ux9PQ1KOn5O@Qe*(?3Yljbwz^XB~=R;XLec zOQ&`lIluS|)k;^*(ityd0K1dlUmk%XZbOBn3YMY(rx4QBcs9tX4I^2^@5=69aGj;&;fd;-r zu+1PEadX|9WjNH{DZg#r zgg8K5nq0?*%@-H`kD*G$<48@+vigu@4T~b{*SMwwJT~2r9g`A(#ihO^bhUiX6a-ph zb(55ukYGb#elh*(P2tgg0p{eOOe#S9U}MbiaUoOtDrP!;7Q54fRN^T~2jQ;U!@!h* zNq7JM(nQd~Y|SKxUVn`yQ+vJdrarl2uMvu@on%SAEx|HQs7KDe6TE}ipaFytQJW=? zhpL>*^~a`@5U6>+els@D!^!5tDJ8DbZCFLpfyrxqxv1bTynixMB=chjZ@o3VzcZl3 z#D>G!y|$}~$0f-|&n{t*I({fp&zQRPkp}e}g<22}vlP>E;hnD5wdU`Q3Yu!WxB-ed z_t@l<*nAzRK7u+xs}^Jj>xM+!-C+zT@LM;*t&OdJoDdJ&l624h-vJAqg0QttpU|i> zjaUcy%i7nW-7kb+b-B7nB4j1rv~0gaOcuyB)A^=}zx;`NZ^gXhp}*f`D zf`x)>AY=y*0*f<*v?jJd3I3i9+-_SUQ2__=tOr5X+AMk8RyDvKACxZD8cAYwilJcf zMe_nFZlTh->-}*?DTKzvd!#g!EG@xP$onOS4?7D%qOfKSjDz5uM@hW2AW{$}9)82J zQ4v_?Hrud*d7hR5BnNvxSsO3na?Eq~UEIcion!?4N8Ib=3|qk(Bk#qS+mOl71o_Sv zT@oSdV}sTEF9QS;{aSE1QOgRy#o=d|wDQ!|eY|krw9DMUVNkp5ylQ_3Y!7w5aBv zvRu*vQyuK#n4w<&#j4DhA>j^X*hj%uw1XY)o*o6?*oo$jk8O=$KkWja5%BYKeEc}Q znz~l7(NdbfL0XG5hZzD;Wz@;;x+>*X_mpB+y%A&J_?kb?I3Z|%jxSD$Dl&LDE_B{d z^xhK*!@7(NM2qV~q@b1(pph$1Gjj8ePB~8azL}o7!(eP?{2}F@Zn7Bty?^EQ0A2&` zU?t)O+$&sn7N?sWeZFO9+s0@ZcVpD?7iEWc|N+HE{N&-fiH@|47 z`c|%d^-`nlI&QsBO5sWfg?TEW4G(l(ZIY4jr`(*>O%Cx!{HBd`!mSe;Z3~>}$CPAu zPN-@VYD9kfkN3aL9(9w>*0G`6SKY)qTb;#MxNf^NYqGXrE4)zENL z#APXKv82kG93Excle+(y^xj4WZ$rJX_BEAm{*GX9K>y6||HyrLS=jztY%vfS`lmek zhE+O^tCmJ=_5@D@s*)vB_(ZjSp&U95A1cz7)s55Iu-orW9U4cF(x6F534c zUBrx=wvH@z&y!q9Y^I&U+lwN@ZUX_+bkVFJ-N5Q2mF7&k8H7WO{9tp?l`eWG8_*KQ z)I7(@&^RIpEyR3J;q^CrZNeRT!@KMttF^sa`4P?UOK2$}XWf-4+`B_od3?Zw)6nzn zeaP5hNj>;TEnS&G8>QakK~~`bon|6U{?1=XqBVa|xIj{_b@6d&o2UV;mu`erR+0$R zRNay?lr{tW-*>RdR2G5#+qmPl<8x?O6ZbQCuz3P@u*uq`w}y{m{zfIE*OQAVv{z&v zCg0VvnPz`J7;dLCu%zxUy}Y=VCb69u1%|WVdnla0;9DA3ft;k3eE*ieeG_<#-{RZw`R>;ce3}rNorYyc~+5B1o*E6uEQnE;Mypco8D2v zA4@6Q{2?h;8hKyli;M=0K} z(^rGiO(L#B$aNKns>4?-3;Y!Eaby(TM?+uAa=3*U^k}KcTllpP7Kd0*ewrFX{?TVI z_24smQY)HeB<_xJ-yPACShwn|8dlOO3-1m1u{I%bX#XqNX#@UNF`4<6ZR_HCxT<~a zg_(2XNB2f$z~Fo;rvRebCZCuIAG;_h?&gfoC=umy>-{g#Iac$}a`{~LlYWx3qAN08 zyw-pt$Fc$1q``YfdW7RqtE6AjPSKsq&u|BnJ7gD2jcNt<=4eK!%o=pr*cn2S@)O5e zKXdh~Lqt4X#R0Jul2$qCxSs!MqV>hgqx4hRR|x#a>_lYO&=GL#?O!Yh>DVd2%;e8f z=z8~Xf<%eO0lokQf5quy*?d#55yOPdx)uJC%cp-8 zI(#sQ5=JGQPj-?7VqmiK+LDidBq~^~4Vg>PZB>(UbX6{QP0$MI*M$sXbG77h&zBJ+ zaKo5VefmjSx>^*I;fU!fr4EqaPzO{G2Z}2^Scao~nkPE_S;f@tGABeZ1^|CA!dtc( zS|9_%)m`SVUf4_nOvC;GW?Ze#T(PI6fGLb}efHA$OWfPMmI(|C(-f&0|G)GXXTwx& z<+Op*EtXMPrR@n5)Md1~1eqGTY@_xWYBI8UcHMq~+3iJjx}|kFk*v=``_ID)q+0mq zx^wra!s8U8U+~C4zC9Jghxu}9G@ml_`xZFx(@)B)k81*7xY7UYagI8P5Hj%M1<4m= zNhXqUzq@VcFyo==wC!xv0|bV4FZ@SB6q`rDXZ`$Z+#fWJ{pl^7(K0I*FkZF+|MAb+ z^GWOnRj6zU;7BLO@E(9gAo~68a3ir3o>Ij|`NF&5K9JN3bO-RueGmdU z5{JU)f>C_t4rL=N{ef>IA-N2BzUZj=w~$?sJHBjx@C>$SiRNM(+lTZgXu)Y%$Uz-M zpcbgUe$~?DU5mqiJyW8!z^CA2{aZU%Yzs5JMDx6FeQ%gO;nqcy{ElUFQ>jm3aQtC zt8lI=E8!|})00+k;!(1_;y3a0-?~{XAI%oyf9#9r5NiQw9j?UT$*a_-t6FDDswTO0 z^IR_0aMn0P@`a#hl$J?E zb)Y}S#&bN{mLe`9r}jOmYL-`(Gn$9w8xz{-2(C~p1nycXZZ7R8W9N?m{cFd&; z@@Td+c0tJh#Uf$?BG3iUN&8CK;YY}af^Coo!)rz(mS4aGs?@2dwCA8%t{ znL0(7H)t1a!rVM2qO1Sz0-hVBA}<`WENUSIaz&!(JA#Fc-`b_P* z{|IC6UrnkazX<4CWp9@XB{KH9ovH%l-&{t9k+d(c^U5l*>A?Xn7EL_ll)g z_3LoLa7Q}x#l9v(*xR&U>sm7|o&{$`&T<(QJw~DMdH5qeD7<+hE&Ziy?BHKiXlg9* ztcNAlm1ZG&;;RW(dYLKd+7W%Je89330V3E z0-Ox0XVFh23nG++$$Z(>9SuTik%6<_UJK6G`{UqZjIs~1?Dt!4%dAabcBRK$$u2b$ zvcniwSF8zcyFYM49$mCI?W*)Q_M?0v@!Y5VSlVhO8;0PK8D4Sea3(kVf)@Kjdc0JL0+ z0i;m*&AXm%Bx!?}Wx^@hUHT8tXP9=zc8T`@sL2b9hcS2pY+$WmW7|?BL=||b1zRW54)_d<4i6qJ!|T8Y>7NDH^W``rw0U>H zG~{GyDlYahs@=t;V%=2eHM#IIjGx8kZfwO(ThGn!L(y*Vxw zd-%n5H%$X0OYJ{tp8lxolbM&Y!y4Pf#&0#6o{NK9JDW~S6aN3IAkZ$rBgP=u)@Z~gD8%3CA2?7;8@8}0k4 zUt7sL9FXg|vPxcg?W{58s<`#W*9{4eVv3&q3#ihWs_xu!=IkvMx{_Z=c!Rq5EhfNP z^;FF3cxtjnm!<3i>gX zD9rPMMCA2eG5E9v7TLk&cK8$r;>$1!f+JahrxQ!5!Dlh6;krYiu{p;H|?Ri0G zRrd_qNq64Ktj6;jHHzz^lXYo zYU+%X_T%csC-y(>`mH!PNnrDUd>&PDjDr)A_|ddtRul$TZUF71uuSl#3a2aK&iK-c zg9|*^WgvsrTov*eA!bkv%36m$^%5qstGr>^NhP>qUyiktE)!O-_vsT607`UeO5oMV zUfobRhPz9fJOd3j;uq#NOV=}3ll0DLvP)4*JQH0%x7M!N-{JBjKS<=gaw>F~ieJ?S ztPZa;+vtp2c7F@f#%=-W6xCpTY=+@)^wA<;tY>{{q6NXN!>o-;4n2+hj?&P#6sKN* z)~ZQxFA2WyuAQ>Ro=LQ-q%r(E^i-TPK;HHEC110XsPnHyW3reVHbFPE`zfcksr(&& z%(3CmlDlJM@K%@ETaIhKH;yl=85K(4bzp5nQ8I7NP#xz|*&tU#T8JjW&kin(7LQ+dk? zrT*p;>CxwhoxBG!Bndnz-ly>H#XOs$i34STVPL9IRn%hO7M?7@YQn<*-RBNLv0g3* zk}BS9_p@*Bm4B&rt4kh{pH1IKR+tlo77tucTV(RAq$lR5)wK_Y(U6GH$xO{!`XRSQ zF!e*~6A0KM$dmYXqN%hAgtOJWll29DK$8_qofwOD1J{vs(X2=Bfg$IVOIjq&f=?u^8Y`lyAX zRV{1mQ)@;d8X@j*t{}HPYefExTc^uLYC6t++W{KBX_RK9?mxZ}_C?9rtzELlLq-_q zo0rhDFnzZuv3<6315+AFvZ>JHT5i~RO)@$?zSA0Qjt(Ho%^ToI1al4G27o~UAbpJ7 z+x~ZL(=}^yd%Ff08n%?Inuz6TXa(Ak0aONn@c4u&1Z`{#tA=s zzTX#M&3rasYN>p)?HUr41lf_BXmHJ&=PSwM0KT@0c zM!sIVmZdUT_J$eG*WWIi$}F3?2Fm(X)G#i%F?l1QXBTxMPkiMdyWFD}^CE)4Hi#cg zs+wsS99~X$zX8_gNx#`4O0wyZ)b%`l8>0A_eA{-IC#)5(N!Z$I9!9s;w2i|MFvIe~ zd@@p;JuvF_Ze2};>*1@5-ZhlqlsYJf^7Sgj>&i_e8*9yz)|ENj;k@r^Z+hkZOG=5u zkuc$N4f;TH8<^zEthyE~E=k5{gBz8U2-HK_;@$V)$p$F+&yz`^R3R;PEA3%vo~Nf1 z4$P}>eC)jlgqKohS50k)J&!|wENLp0a3CGW@n-|n&7(M{FL^j5<$|C337{?f)-3Z5 zJ|!+-J>L~T29yu`?TDS)wF&x+xvFDF+CYRGmrY71I1W@X@=)@V^N-S%5ya?3K2!tu z(I5i!xW2F0c3#C&Hf5srM4-a-740dW<)~OiBJ|e=P{@e_0{Gy*Mp|HRe)b^@#{RJp z=?UV5Gy7|#)X}>-0j!+33dH!@7grW_+6EgropG2bjXZsk@da_ik(Gn{JOqcr;j9&@ zipZNk91Foc_xCZyDHeX=jalK|-ozg_Mr(P53n0;%vd4d)VA0G{@ZNF^nR`9?+2&{n z+m{3157ZKa{}H(#uJrg|{H`8Nr6#O{TYFoiyC0K1a7Ah`x-+J(kZSf=0B6AA zoNtDS4WIi!KBNhmemSuNfW&OYtG^<~1S{csHZXU%1+T}A@3raM`4G^#`5w3PR<#Sq z@*VW2EQJ#b7VkS!)ly-`hPD5{V3<-No)Ggo$1)x!5JPZMsz)y}J~t_ zEz9Vyqt5kMIjC4qt}f!RCD;h)ifV&)6eIOKwqehdF@27y-d}!%jFX$}yoLRw)@zYDd`2G`~vgxv+5w2>>_?x9F z{07i0xaTRU6J4c>j^@_|gkJP^U6YiW0A7wOy5hihli^7e%xmwsOqQh~h=i+@tP~o` zcr==7ubzLar-ZRv{WB8UA++g{_V1hMM&$t6HlZ1@4QBTa@8_&h2;;0~whfujoO-R| z{h|5P0J4pY)|r8gUu&(^=3NG5oW3B0Pl4bN^jIOH)7;KT5$BPTU2k;gpp2mDvAlHk z-&dk6IF-_45|UN}2IK>bxwQsloCa^89~J@qzAHXggr^Y@=Ez4tsDtT+lIe?2X|5?p zYx+_4NYVkgOuWL6l!1|wHnOXBM3%qpW26c258hRMGb)BAb^J4p$M+Lnjfc+e&r$W{ZzK zsqXpP+fLo5W6@`aJmwGmKnxFn9Mk7_G^}`GI{|0e?OUxamee< z#(XfO|O5~Im{){+&C?;2BuJ3;o@tgH@l95SZ0yGas%{(8sP5?YLwUP@j z9tl7F=a9fuHlbzzoJ|)edP_9IUia%P|91!T==iu`0|+y-e~cDc26wyo$WvHP*5 zLyijciOIt+K850J@(fq0iw%I)9P|5>sqt*{UCl_43S~vG`xwi|5iIIwZ7VSw8$f{d zldsBfP^7JC;7Z;r=_JZHKzc6Va8rB0VmEL(h0(2iMjq*ySk-;J(SKPOU5B_Jz|9+@ zY9HCr|ES7*?bSqG{RUm|)!0X8GaVZW{;hYVzC$JXU3W6x9OaO=)W0Nv{c4D~hoypPv$CHX@*Rp-(x+zNu@AGLA8$pRNWTkCATk-;^N30?ljHANBEI zO@?P>6=mlsFMzu_P{`Ne-KO|R+gU-!;{=bxvfkM*#LUU2fi4>F7GiLSw=-`WiLx6I-_1*OMwxrC9NI*`tm^QRCXeUOpj zy*uO8r6H>igt!uzI-?9Ym8N`gF;}5&#ogV(PJpOKh+r~Bu4}^}WoE-8(BC}QR%PdM z@xp&~Va6f<{%S`EdQX}LD=I3`LgHrHRMNOg?@s%hB_ejx88kPfF_bI*kQ7SgRe13B zOfk-nU)~yw;ILHw&F1+63s%OM5qUyM&|=}BeYO60Io`6jdhFzcH-$L1d46*#bT*k(4?h>K%fzvy#DX~=Z|T6T}5 z>N_9F?g3$bJ#=0%Ju!O4)egh_4Z7k-PSK)mM?5I;v8@?g7NM?6qp|Aw(C$?Vpdbw( zqFs8k56RTICFhWR6*IK9oZ@vcC<_T^g%UyzeoGE!BWO?r9cbSL#N;FgnSD#1APePT zAzC%WBX$PO=~D8@&4Fj04=m<(2do&5UOak-j(Q?2HJP z;z?$TfMAR@4?=>WII&~__cluf^%h2_Z!<&C%gOyC_N+Rz;%#{R2>x036$tVy0)6J< zp&{YJw~$OZucBxu;49es9?NZ)C;lAEJ!z9^qAIhk@Dhh3i z5u`{(deFBl0e|cyX%_Dv6b(k!m43G*2HXj#F4R(4wh9dfXbusKgyW;Aq8KOCSbq`C zAgO^%Wuu-*8-mt(!l7)?laE)SxFUBj=^Cs7d`U#Aw`=eqkPn}|_lcX32Ld%1-38^; zVvWPma$=or9?y9PMVAPEXD4yj2A!cYRjDO)!T+P9ctc2&ev}|(0G6=>sYy$z@1*Un zV{tiy65bY7`tTabG(CP`m@4W;o~;tJ56mxDpslqyU2$GVyW@e&ogf@Ws?ex06+w7_n|1{Ufjt8T_INeBW#>yO|Ab{%yUuIWY zvZLz!R?oNtTvH@7SzrJs`SGzaBn#Q_cBT3(OTicS>m3U}4in9i+iTXvOM2vipYXA1 zie_Hw+;Ah9-|IH4K~+|nb}FNlN{t-_IRF(rya$#FJ7gmXKDFdR6_2zCF=O{)rAPNB#8%S}u8f^K7%uj}SSsL1Svaw5i z&}#`+%3E86c^~Mf6pf2264QT(LojaK?_kGgo8qdjT=B^jcppnSRP=dxK@N4RdmZb6`Eb$II?`^m^dRbUgPdDQMlx2aFE#QgE6FD) zQYIncUichzbK5gR79(P*zm}D^lKdE`ik<+tD<$J2Z97G=H61i{#L5ZeQclh0LjMmi zu)K5tRaJ7PuRkTnD_YRGN5~{Twyg&Pb}cw*$nMljBs5MPpKQLG3O>T$6?WmiCk6_TAnq+ywM^bMhT|QTYaTwd2XtG&0A#daD?UiyjPJ#rU zLWV0Ko1WS|sNZHj(?FZ$@(9qt-0n!1%OjO40^Sh>`2((eB7@k43 z;G-y8$tjkl7_s5D44xN#E|p8=xnz*I2!#|B)x|tEQ&m6X7I42>0e*QGK1|!IsIU4l zx(FOQx&_ifB9LIwwk*K|`S!Eq%RGF)XS}rg#AcBRz+P`|ZCf%6=x-PI$gjg5MO45C z=la*CkcGD62dK_-Au*)bOOR+@^15fQ6ai7v@*^e4wUW)Ej+fUVVWJ>qN~1j7II2|* z4%YFf5R&7$;ypkZ#SHDe2{={j`~SbTO@z#4mN{fr88bv?nU#VGeniOTq zSTZIenKPtO8mNp#rjkbE?|#_Msg6G9obT`ZyZ)c+f3>e(t+nrGy5IM6uV<~j*WO!7 zAHQ$?o!-kFH9W>D1{oVf>(unx4!$tIHNzp(b*YYVgeh8(w{3-y;oF`A7hTS`+|bfu zI6FaY&@9-V$#6SMy3fx(`1~c6w5ia9i}QC*6&}>2df&!Yd@Mt7+guItY1aKGcOIR7 zM@AWKlW1WZ%3>D6n^MCxBBt`uk(}C3_@;PLzkX7S+*Fu(d-nw_oN3d6%nbcT_E@F{ z?|Or^w6kX~#&*RCPVGOYZf3aW>YCd|yJzDr47W<&_d24yv!QYFUF(Z@TidO{c3zKn zdTaTsI;wsse%O>|aBa_O{i~zqCvScWdUoV(OeXs&dg98&9s3_NlpjBxWwAOZ@Vx0A zhXj%S*ZsRZMPj-?oIm`YVMey+a^f==9=BuLXLYAP)fAl%6;YFZB9-KO6sdu@CqWw#irTwM4PBZugEas6m+Y4 z?{?hALPiged+$Fk$0g3Y?M-j- zMos$IDdO>&T=6@i{r+#qU-`e2G5iqpCaUX(ZZ4y4vdG#S{#FseE3Ia(B~EJjtDK5{9PHY$K3YC9XG5C3pKew%{&+oSzkrcD z(FJ)E#?5zJqg1_;#5n7ee9M2z^=lNF6%re~Ks{E*EoMccj82^5AV@tDH_>p(w z>C|@ZS9f#cVnTb9cUvTdzWg+C=&G=7{d?DUe(Mu2uGd|Cu6z45b1j*vA^*CQpZ%RE zb>#O_htT84-2-=U_sKizxKAkS6f<(X&e;2cLO|f#>B>;Ch9~dOTd!hr(oef|jQQX} z_MUGqO4E1Qb*wjkZEbV8HBm@%=;^G}UGf4ly4@7dPCPna(IT_?%o~e$d{Kf*Q3D&d z7NnY6>8aMAQF*mG&${(ysLCai(|lWcvm4v|4fgG^@pwO2-b2#C3g8eU)iCWW^ zJYyfHVy_dVwJ>&ZVH;re(0#G#Y1`}@;)`qMuANI1v75BvGGo|A5=!&IM5CK?1K*9g ziuk=(Med~JU7HDy{5bQhwzS!BZQq)jwUwUr^Xs#Z6A#U=2oWwF4SyA;WA3!)-bxbq z+2Zoha*`?UrW=@|Yw5fdb+P4_Di>~wljqjSZX;=AJQ5pr|ItC5__TuKI*zm9S*9N9 z7S|KcDyw_GzaH#G#1yOuKbu=`DEhjKJ={R-*wKQ!Q!aD)GeUwR z=e7~~@7OOW-=p%Z`&$E_fu|lZyB{NSfS82PrF|ST4!ut<3ec(NK zwbZXwS=e|pTd0wdbjxWGL+QJx<5wAQ(PBY(=}VVONNMEuwv?ydN@ovFQ{;cm?LO=o z8T8~MOKGUQW>ri_@rK7A%=8-u3!MG>Wc9~pHd^f4yoYl9{E7Wl8&3#bi``o7+2L?f zS#{e zz(nI(KIeLCx!W!~GhGT)yKsj&Mhtg#G?Od#2^ePH`Eo~es!?`W>)Df)1AGcmOq1|c z0ONw=7Ex#83`3s!r7M`~61A4SA9(La_JMIU($AjGp_AFg2FK(d$h(fdiurS=0qa#; zxr-}IvoIkhTb2En@8*BFaW9BhWmAAl>h{)9kN9)(SK28*ywHh^xjAA#;YVjVso{S8 zxfsvJm8FGI+b<0J*O9C>3%OjSxn+BeBhdl1mMs2LQZ#1bqw4GO=vEn@++%T_bAm%% z@x!DyXMD??fGiDpgX)Smu9aG4gExw@rY-m^8Vb4wOucs?7ecC=S^79Y5R8@F`buU0Iofz{3?s!Uos5^xkag zuOtqiaVUwbTf;+jP;=y{B!i`AM$6vFS`5P?RT9Z!BD!ZMFno_`81(@u(@&RH3YkY` zPWh(34)@K7ICL=vehwz5ktl*GUWqi6J2T(i;miA-aZ(qe6*^LG68AmR-eGiB>ocDJ za#j_Id}{IaWNrSsi~K_`t_G^AX*Y51_{?t~zay#>Pwc6uyJ;eH;8vP}m90rc+0Zj% z;nQJ_5R-4yYL%SWJ?5tqy)8aI!A zSO~-8*zT=%z=~X)CSE`h9Td6i6N*p;N^`IpsU$MzZX_alD0z;y8YJS>Q@2{M;m*js z4mjp;dxi3rbJVfY)cg-PqlvUwXs0XpM!ZbW3O{1%M9NG?QooTq>Jl!TmvRsNO+900 zjTaHA7~KT)%A}IaCDO2O!Hs9+7VaIoRLvv~!S|G*Y&9-f;OeCXKk)m7GO{tunxqUB ztbu~Ne-`BMQog{)PU%AlJ(P_7SprQ~4K11lSN&*y3uX{1sC(*rp*sjxw;!xXJ_FQ^ z{@!XL>?#O$5tT%t$WkJrPKXPRbshqPB5FT-Wxv3nHAd}`RMq3oA&4_nf`Szt&|tMn zvZQd)!k`qHp6$8SsTkIZW(2&EZOd848`;t2ta6k;SP4GL>9!NPJ|6Y#BZe^>pdoR| za_}8D({U8W?{tp2;Q@2s_zc1)zRE1SKoY0@~NQSmv`f~ zVSKzzH+MFx&A70>p`>+>?B8sfKRs{gtKO7zbaVVJkv#{DFNQr)Zn*Sd9OWaH_l z4`RaBOcTWjj9m@hRPr_JU0SQ3-L+(sl7}<*PEX6Pv*u6elzbD>%lNtPe0|#C`?e9O zzGnISf}Za`>K@EA6TeTqDbjw$8<)26bteM5dJWZBQ&(^JR+q)R)_+eZ9W{P6kx{7( z{n`>5mbDxd*=jn<{fFu==MP_eN+YQAim^ZVx_HekSIaWW#t%{xcE&3oILb2SSE`m! zjXNpL1X1t*(B&Ivb@d9*9*VLQtw-5qE@NX<*KXY=Rz6g6WTj6I{8s8Mn`gGpOhyT< z?J4?)Mu+O3ZCcCe-Cb=tDD$eXqI8>ggwBi6Z;B11LCQyun&kE+ZGXuYCbl(*gJ_pn z&%MUY6w~ILc{WSLE3y?k7$v`3?;>EgaeMyIMWbgFw)(sR^G+j7jD05S%LCWPLcV_nWu`?T7Fd`qc5%Mul3-{O{rzA~I>@ce3@WtZZ z{Fj=?osE^0n)>=R48=mTtc+{tyG~e1_u_@~>+zh_Z-`^ReZ5yoVb@lCXqG$v_6td) z`4eh>4tr=YGE%Zxfv##l@6}%pP-@=07}i?SS#We+kXpzQ)|3AHsnfleCPc+IvKg_o z8VW_Id=k4|o+envT==ECa{sh-ta7iCC-}A1r zu?ybWBq0B)k>$ylT>xK-hu4^RdBhg2Ui1EzZ~Ji+qP*SVJTv4h#Zs-x%m*b7PcogS zI7n8EuQ;-6owXT8VpXAx5shpwgWh>?$=Z2C3!US=a3@&`VJVUx79nOSBw`fxdAps6 z46j6}*7l#PDMB@F=xy{@)%Ff~VEBr~S~`^fu~sG2g2PHjS8dTVTcL~`n_^9DmogN< z`lRFbM?v~$B{H!S*v()lf%QoznozQ9s6PP1WO~8uppvK?aTOhh3kM>mzO;_8_2H#_ zalT2VEglS%Zd)6iB79^|%gC{}c+wW8ou556xYK^Z^tDQb592q-l*8TP*-CaQ24D2a zl!qAXw>FK*N@S(=uAI+kFIC1ibHYFjD#<3ox^BT?HzX=!@AKD|?s=&5s+Q?^%J^)=wXqqBZPo&kw0X_< zOV;~pOr5~4BVF_=$qvGni-FKymuqyJ*{S4rF}Ft!-4#em_zOBHdR`-k*N zs>F|S(FS~JDRlMIR$+-wrd+ow($V#O+sMOJHZ$!nnK!s3&j-BrWVm~NjBmWhMUbD8bKWmOwNeog?Rkm1o?Bq8_X+k8ul=G}N zU52-EtTl~%&zp3J4ad%yYvoOwce-+gl%2AIwaF=+aczl#qS zwGpS^@z()^r^GKx0EHUa&4Yv$mKSiqRX^cdT5s^QnyGiU`n zh4iBfBdX6xxzy3p;L2L{2Y9GNUOmu1aDL^vQ)ifEW2R;FxvuVd8bO|Tc&jmw1i4j7 z^giNk!L`Xf51vR&^7_BuNSek$Mg`YJso;dHN^(MUKOdgT(vz|jUuok_+xlT;7D7}-HW!BTktC^)bff*VT#rXnnOXyzd_ zH{1w;|K5&9SPCUA;RIWQ8lZxuSpKD8ln~@U3WA{ppGsooucN$h&c$4nq2d~qlb9)`Ss!Y zqm1@n%II2FM%S`3x|WsE^`i{J)k@c2WKbunB)<5y!=s-xgEtb2%jO%RuV%Qw#Q}MmDJA#kJ(>&Gm+8|-F`r>YkYuC#A zP7dYu8lRnLeeFNjKdarQOi?U9|6tkyU$A+SdUKC$XB?09Q7(P8{0JJRuCjdlx4mQj z8(9ab8e)o=x;Ur04N#^=Gkzi`iSr-MN=ufxPt=sH}_Ty1Y^O=X`uo8W1jY%|}!nuz?3`QXQA3KYjD3k669v*&J~ zyZIqOx~MDY1jW?e4OrYaA8(1|8ii+Ttfi_YDy?pC2TicIp12uRcE;pQtfseG-si#X z-!xt1%O`{fscCWr?A11o8`*Aay2>0;w$CAM?uD+?vsa9jk>5CUCcVy>J9pjno8=Xv zUnP{fBhbEJ^Qo}JyKB9+7OzmOlMY^8Fx#bePGq0oY#-)a@{c}X0x_#XFHDi zJX{eR?7CTCb4M@R#BP}xy`4pZ*+RN;b?#YLSOYHD1x?u$)a~7=WPRUW=a$NcaMnBR zxvWp-&rkWaU)ZrqJ#=ihi=%%>TRO+P%Rxo)Av%Wsjw|!ur60KSjqwAMX8!3jJdZo? zXulUf`@Wa>R-EF#Gd1~pIY!4UwWBYu_Be3bXw>uO4a+1IUtbOnmR7na_5DNP1Trp;R(ol>f4Sx4Kr&g&+Tu9;U4Ij1uxXO0xx3IAOO;%UjNqeresjVm8C zp^Nmar4cNzeSGyI)j>UvCv;Dm3$`bfKV~KunL5?;@fMxQTQTy@BMxSvQ=B^wkJQc$ zJfJ&zf%6>2h>GYq)BZ0}=e{&ZckwgrZM0VQugCcu&=5%zKI*C6SWsTqRm)JC`^EHG zlTgH<*^O>jg)z>)7e*R2<1e<=4Dq zPUUJ3(n@_E(r;bbxxu_!m1{nq+`-;YGtoj_k$D5JZ~9DI#ZBbSj@{~WvD@XUhjFZr z8XDT;xeG7cE3g`(d#+&6%#)EwA5uAb=HlB9E%U@?dmXV4+Pg67lB10+jpYx>I!T{f zkyoz_$$rfFI8&17QEWh~YSjBP^TnAJoHup8WgFIhP0ah2iCZh*En0VK&D(nMttIN8 z(vLa`=(I&{mN(pee@B@^_lQ!`Lz;(*WP3-dL)z8-hm*DTzb3(}r%0IJ7pkUn{JXv~bc_3frF6PN5w;BsRO1MOEoc!jp@SPEdMJ+Ub7XWMu2vx|ume#N2;e*@UIL z{;rk(L({R%ji~~hM=B;vXgNl5x9LfwYe;@zHk10q@zF4!DIg)J1l>~a)t;$d}sg5BVQ$*rTSHizA&EZi8Yku*du>? z*Xg?MEyic(+F}$jRq{@<$Li$ON(mb#OL&b3UBf z8BAp!ts$9{e(VBYQRt46`4szL6V z_vy)Dz0EY_bQ*lNw%hN^f30e~a}TFf$AD))&1zz_YPGO>1m4Ugv$P?uvG9)zjjr(uHh;M#s`~3k zxu8pDn9}6-(5H`B66H-($6hfLvJ&#RsWdofC|m8rk<-+3%lHy?_60ZZdO;};&p{k< z$g}q{6`!|j&rfu93|@Y|vf@GRQ96SQya@x6g1slzCL39gujDRuV|}rkR;NYL(!y0g zKBTgeL6q|#4Owpo`Pu4?!gPZ7(poc!Jv`F9&W}g$(yPc-H+g(!HnyVSO-9y{>UkZ7 zy!gt2mijp91bM43(~}0(H}(e1g>$Jf)?6=+>-${VT(wEf@e%K1jrA2T^CSAT?+7b? zls#1`(o0rfm-YVJ zcm3Me3I>%o4s?%wXer+xe}1oCY-kwngXe*y54Gj<94rB=!nT?@1d3c1PL4}(Iyg_9 zuk%5~St8(ioJ7V@ef(1hHb(1|apiIZaIU%&nR zg_qqyWjn2lo!}GSLZ6e;m1RrfH+*gI?OAzhx4m1U{4T!NiUE?`oH10bL_YSB3=*|{ z98K;_r&_I9!a7J=cV#pjGk9<++QvZn(~bjWk>|Ab-VXeDmUUI)(XO)_m?L)bJ&c*P z%HSM`1w1b)vQ7hf#aRoQXD(( z8#kxDGUeWd57?A>_7>mTHxtJd-nbPvP!9K)UpAa?URj}NTjrXo9v*$kfrvJ|-RQxG zY5w5!dtV003Y969Z|1Ezdi0qij;765;EUUElCitgT^xgiD95Jt7X0e5LPkf_)$a;E zFbNeqGH7wQY!h4ArX*WE*}ZJn9&at-<$sjT)W|5?qd)BE(qnB%Z1J%9V@136mB17H z_cbESElS=?_>EqA!>f=cbyG;3ImpT@%8Qg@miLOFlv&~miAVp zZ5~K5CMjNFqhmZWtrC4ivSx!xIqADyJJgd%sT7}!k$>bpFxp;ak%I{|Kc3xsYSXFd z&{J3j>AL*QyGSZMrUJ680&O++D$;&kdF9pm<9YgYdqk>^)zE3Aq>ECn})?m6kl z$oYZjO_@6#dtOuE4;2-UiKkyVM!ZpZr~7BKIr>M_<3-1^8R!mey?l%0#(Tg1dQsVq zp4Q7r1Myo-WaMG9fs8HhG@F0MO5Vo>v!>ExSP!-4Zhq7p9fz7&jAcOwlJmv}3B|6Q+Gf^zoB(@pk+P*X znm1IpG?iO5WNonZLvsJeZkB{0+#1IT{VQ}QXognxh|b$?IrWA{uCF&^SBU1D@%)3a ziWM>AcbRu9nJ~QRywX(i@axcH0}iFBi@xvs1^hd&4%$Ue9qO;*;4yY+*qxw#*yusd zi%Na%htBWbuROuTmR~X#uOMV^!W}U;tvMX|7c@-3Wd{2TpZTn%b_f{M`;crbUG?N^mP`;Ht z`yk-X>j4X`!(QD;p5Rg5QsYH+w3ct^LB08r&~I6eb)xb#5$;>SSVW@*$n4FVEELZ_o#uN;oP29nAD5QueDmFbKqA(oUvtuV5*0;0>k3B+=k*=L zn9?4$+OwSfe1*}9>A;|U=Cyhq{uKgq@0>N$Ei@^oWZTYuS{a$t7%feA>QtT6A?b6h zfiY_s@(a_Cho4`YMHKx+e)}5E_Uaxz?hkD&v;sV|>6#5D68IO#ufA^P^v1L#f?4}R zj@vyaD}Va=(7cba@a9QLCe~)Th@4$%k49Q{PBpvnF*nE9kb~s3Y_K{Av zups>DTbn4>z#WVUcuT`UC3#@sEw{xtJE&h=2=~uR3`TEu!aq5`O4kD_v$JD^dredE zH}wQ9chtTUOg<|uDrr}inlQvE&f7AnJQ+Wkk`xo5no{1hncYsN88uXnZ5=$o{yO87nUP9c+`g})0lHLb zXDxYG+fb7x+4IJ1E&Xak5ge~_>*W4YmBhReZ!-3nHs&O)`**pEZBsFf7QLS*3oa7g zF##8k!(H=5N$X6qw}y!MUULZLnHnf6-z}yurZ1_rB3Tn3V!6hbOje{6=aHaO*zGs7}? zY*P#yJbl6|C5pREt#_Jf=w6mES{+AwoCjOW-xy|azrE`5Q%k#}indLWT%@nDOM5Py zQybWMzGsqs$Mln&yN|x{9;Y(gc@*3BVB64h3oXgbny)E7e3}1RD%g>dG%xhN+)`rw z=X625^u`Xks=i{LI(AaVBU{fLzjKGL@siK_&BhMT65gkKiySy_x99xJyI51Yf#h{u zU(~(T{B~i_cV?UUw2V(`3-hi?AmIoz$oU**5@nU0adh<+EsdQdmwEW>&N*=^PgT5s zR?MGVHCB-w>~l?boY(TfHnmh5)A(&#@S8*eB{v%D^k~GlOwez03suDn6+!MfG!D-1*M7~1I`@_rsEQWVbxfA)foMa zsoBH4Y#Y7}jVcH#q|&kH3~ck+7Je!UaqF;Rw7r#Xqn)g(iy!1Ry{?vSl_Sy zOxBZiY3^jFGLJp}vU3L$%eNktYFYZU)n{m>!b=ay;0)i1x%562dJ=M``oPxs2nDWd zM2=FnLB-i`iM)oK45SBZ&YB-yEjLo3Q~%DS|J^Fq6p`fesRt>M*OlTN#M<^>d`$9i z`dLQlcJsNs-4aIOc7Cnf->$%BO8A2KgF0H>w(P4luWWcMRP?6%%)r)_hE?&rg=eDL zV%O5lIUT>5b!>~~etn9!g{H;@HR6%^hwTHI4{Kx;Jp6Whm3Ya6W0P}xs~lM==mXMf zy98#drdHan>?%vjM$q$mc? z-qpK2HfFwItL>JKH)(?cw}>VzYa&c9i7DI~=bM(?BvE|g^PO1I9HMsDqucji4Wq_q z)hU&pRhVfpu^QOoz0xl=@SAt-74?mQd;=*GGx{rRn~k3L%p@0tUs2lpvD-q8oWs51 zWkA)5>-zV{H!>eF@yQ~t+qT*%`Ahr2WsT@>wBcUkLzgQ~=gT-B8sf33OxZx%cP6^} zosZXPBC7pgtwd;_+n45gN5o!{p$luO=APW%$dS2wiu3Cwr_bS+zBd^Aw$n zf(!2nK9S-bt}9cm_rJ{$ZR~!iH1OhL)s_~WP~Xn|`#)P!%!K9RlXdZ5&owo_-e#+``%x9=h5*HLL#uY|R&C!< z`%vJPMuYvDVDW1g9Q*lpZr+Kz@XY!OgNl`OzGT{q4mX)s;x0zzG43hS*?T!NX!DMr z8JN;iEl3@vyhw^KHw`~`MK3US+lJkRD{FnVVr};G)zpn)F4GAsw?Cbh2z20X zPfze&Z}M_3kL8-})Vs?*?J*%j#BLrB3LV$Qgqf;we_NmImFb|B=~tL<`t);2NOhvB zZTziu18f6qa|aR>G_!Wm7pjVna1*<1Xz?zZa_rw@@87#pjk$Fod1%*Edt_$TUM$k(yn27XL-S;buw{&yr||BZ=VhGp)?~|(V$qQ6gO#YF3GRB}#z;rWpw{XG8VRakjJnmP;3cNMP>C%k~-k7}b%Z(3jkSMOL zI&yxz{jg8=$+m*1@i*3ICU@3*s3lz}E#hGzmm02^u(#~K$9-|_5nbE*R9;gB;dN;_ zUi-ctqbS-hS-(HKf~c*ZLBZ+9oMb58IwAbY$mcjd32htB&WV$`l^Wj^IjwI-GYz;j zb%>rnqD9si)a3Iee9IAyZEuT`*+MOHt7L|Uo$?sRiyM`sn$V>h+(G=Li*DO4Ttnhk zd*h9Fr_Qn+M|r&mz)(W6L*&Ll1bS-N6YHV#;zv$T1}?WH5fg*dV`yJvu=wQ zV2jsn3m?~n%d**}%d)3-Z&)?nx)Teu?^w7EY_F0yx9o-0!iC?S_JF-vCG>B-x>u>| z{r*+gJ&sK}sEqLMcoQ${>j_=gPIz0=) zo%Nt_hPL1@$DiUr2;u%~%GzbMtzA~z+GVw^T~-^PN)pq5(uQKWaL<@kC57`RRSQ$b z7tDb!_91v;-)Vvw%eqB`KBWhW#FkYgu&g41Wfckhs0iI>h5-eBrAY9fDw6nxqQb=i zWPYVcW?4nD%PNvxR*~$oie#5nv`!^S?)GvocL*=X$a08OZGv&_;54I3$`)O8U@d;E z=9#2=q-PN2!sj3va4A|#dXU|rNu2U4G(mQP9HQ^-Fmc+;3epz-5k4*s<7wv+UKTzJ?^i3^9lhWmWTMXB&YKE2%%qZH{Jq92_iWQPc?LVxEQ;Cr zhZrbGZV$($fE~nGEGx!hSuqxW6$8tT#h+rBTegcYd?;tTN|L4eFXx8(X;T}1CbFvW zLYTHu#SG)7TNjCpw9LLXzuHqT$MpW*UD=7S?)2I>p8B?0-SmuBIT%fIa#pkW1e%!D zDk=7qf6OJWljk11ZjOfn7F^1nw#7NXb=qhdUpmarmeKXH2Ggrul)8ry0z%XYbKxfZ z!$OD-B6XD{cm7{`Pjx@|{$Zx=#cdiaN~yZhhsN+#HhtG@Jfn<7)LN>jT_%~%+8-Fu zT(JRqg2eM_L(URDV`E5_b2}I#B84J2QUlz+Tg=a0oD8-icp4DBTrLIVEo&Y9i~lgK zfs}+?f^!*$Q67K+A(IaOTGiq^7_KOj)~KX}<^6aXQpRvl0p&}K?w>vrJ`9Vj1SQ`k zG_w9EWASq^DKTx!IUMt&48r8ZE-NGMpUODmbX2JS@|geb=z#U7w(XDg2b~*& zF=F^&I`X$=NyVZykYFXKAkSxVnpo+9|%DkSyshJ-VbXMstZ1hX8jW%AZQ=e{#-5>ogjQ{FlBmRIVWa_ zf3Ol}d4~4~E5YFzw;xyv+7l*~>XL||PL&&jQG?(x>M-tw@ILx*%%gK}t zKRB^CTr%ZyGLlyi_iz= z>tc_-Eb+&~44&`PwB2j(=78#GQ@gwDb%Y%TU-yMhzjvG0ezA!ZbW;P-m?dp-QU9{yerf3JtX*F&@p{5~K2J|Fx(AN)Qa{5~K2J|Fx( zAN)Qa{=OdgeLe82>jAjWxNyD51h>l;?lr=D%;?@D)F*=bp7hPT|F|zjHIGzpVFD9@Cy*>~HD5!2j3&{L=5A&cnZ~_me($ zlSO??@%}l!zwGCy@#Nq;ZYb^x{ShzpvhaK;&HGPFLaoEf1C)yFAFXlM`)e<{y6%npMUG$ z!f^-l|MR&2wx7Q{UjFD0>HL1(`DtF4j`P2Z$N%efg>OXtVG%kzJ$=fAD~AMyBy$LT+v=l@sz3Q`l+ z#a~=sEX~`$jq|_t_kVgG{kMMoGTuMs>%UzOe%bF&$Ls&r`QqQk_wSD5f2;SOo=^X7 z+?xbjNecB`QOs--{s98e*Q0=cmC+_ zAFkVf)c=R$|F^7D|Ma;1x9h=wTm64KKfgRa{^k1ePy6|Ai`T#P=l^f<{a1R};QhVb zaNUF+@Y?TtRvAFqp@ou#|M2j~g`TFvOU`%j)D_qVppVp}?;NZFm;nv|wP7dhqvxoM zkzm_E*djX%!H&w%H&u*)wSY9h51>9!pQt?(fa-Gr=vS3E0n}!x9MwVPs2wM;T$w1? z`F<)x{i1PDpFBU6E%mt`>}cGjvBY72$^RuAs>=#58)9L*2|(|4A=UxNCsdBcLF@;> zSE`k;-3p+-QGX~7=u35yfEs|}EC(!&hw7m303e$HfZ8F=Fz!No(6%xk_FZrpO zwMDv+2DE>TuwC*G`G#U51egP2z)e5};0Dl`x&ZPCX<70U=|OE%0T%%Ek79TbFa->O zFaT*q{i38rBh*rZEwciAwIHEWpzfn2Ti0m?iEwZ85AW&bZ9*PT^ zBTfK~hvrNaK)#~3s2++TnjZv;xe|c-ME!FDC{}1rpgxfXY*Bns9Yp}e0hOb?LUp(R z^gb=}8MQ}qg2r6~pnCQI8kZA5^)CUa4`f4rqd7wD`~i1B0nh|C0!R-kM?T5|D5gjU z^7|BE1|Yu?s2&29DFdni$}{9M$_r$70ptNRHtHYcCCYcCWyuc|Q#2N8gFthx0w5nz zA4nUjk94X9-w0^9zbzI@kP0W;*ZMF*jzvrfMSK}qx?d41d0`^hsHs^ zqq-=DXpKO9AfO&)9Be-Wjs%Kei*f_S5^0nHrUB$1iXG|)^^5vPeJ21*bBy{%b~HZ9 zK{Usl0D6wbTk;9{fyy}nRQA13s1Aw|XeG6TEsDub0ObN|hsu|L@hOmg6kFuik{!hm z%_o{a9{~A|V#7%Q#StAly8u*oDaVj51d27PkH$o?NAdlBY@`ddTk>fL_EAot=V(k6 zZ=?^Q0-$3N#mNjnxqv`+6kFsQ%0C0(2w}?wTa40E&SekPRSzQI4Qv2kA#K2aPKdVT*h~WoTR!dlV1w9j^>q34X8e{{|vIDIou4O@zC=qK!LFT{WxM^L;WHS1M|sUW(JteIPs1jeJG>-aEA`Src7|KtNI`?>w!)&EcapyTT2{=WAG_E+h8`|REa_27X= zYYF7o(?{^vvTEUU40^i~G3><(0CMcbF(4JVg<&sc08_vlhzGI(WbeYTSJVKkMXzA4 zUfBcwz)2tR0P6G<0F3~2&`SdF z0V=>wzyk;ez_;Fez$AwCfe(GKhW5en&osHzqeSpwk4H}LZt#CQtgKMh)@m4JN!tg+KqfNvN!BL!Fh5Tlv1KoJ1_&3wdg zE7Ae@JHMps0Z$+eXaL?}II^`sJBC|{2Q~l>Kon32bYeJi`0hWs9fqTTb)CW*I0B>q zBN&d79Z&a<0qBEa48yIF1Wp6*F&rcK#^{IPm^K0-7>=0&*ac(*UBD*{ z$Fd5L!*HBAz&M8E;s(?K@R=(bsK;KVS)*1)2cppN|F51&#t00ND7!#;*j} z1LuHR47ZL55Cil9C*UxU2SCi>T!<5}0FDF2KqrP1BnPwrh^1f&0Pz=`!*D_n3!&{8 zP8f6x4`VnHK>+#_DF^z284M>1^ChYb3}QGj&@Be#Vv#@^PzLm2IC0P?9spbbsxh1d zXqA9*Bw#)ysQ{Q~Nk^a;{yN+?0DO~rgyA-j0^)!f5Dq}wjT!*NY2!5vw+Z$)T?9HX zoHWE0{Vs&GIRHAOYcZS*F<=bD1K_hv4~CNkow6|3vJgMncNk6%w9BmnK!Y6kAO|s& zs{^JnoIE!G_2pp>mGsIRA z{4xLy27?&R5b78@0p|gDZp05bV>sgo0P33112AtUy6_iIGXSV#xd}J`WCITXuvx7D zgn%gww@Vsu0b+qHpbB`2;jD=O@YNdT+8XBH8ho+_pR8Mew;0Zb5`g)#u>l}gZ7u;f zfcpUGv1I@tCboM3=-W0QXaK-ZTNu}l1JD3Gfm1*c&;-EPcJml+H^gT52!^v41)vXm z$aj0lA&1RCDu#0eyJH;i8pAn3e4N|?m{TWc;|#}?b0CIu*#SVmE^`>pRSl@XaC?Y> ztpMc69>@_l3P2r*1ny!ucO?MI+`(7(9t^kF3b={k_K^UpfFFm7g0U5x14Ce_s z=m|b}h5?YLo)8PqQ4Hq=vG)R9upb3P29~Kz#PE z0bp+Sf5dRUOaR2v*AX}hBmVTIR?zAkh7ib11Fx(kxfDe!d3;`%hTMz65 zAVz86ZyL-+8pJgn<~SY3O&`Z_m(~H`=OuR_27p|-bRWZIhyXBu8OMNJ0LIPe0wBJb zoB-&}+z)`(%rf9E0P~tf2FL)mKoEw@b^?k4Xq&?WfS#OlKr4pJh5X4i2i{}2%j&>M z0Q6s33v36#?<=4+4}8hX2j(!`RVcd(^L}+2!{x)=_6^6SBKHLN!Zk7Yj0f=)I zE1-wrYGQy#7_N3DAPT@-)g}S=0PwAD9k3ZN24MWUV?Ytmi{a{Fp6a&&prif>a2J@x za1Ct0R^T`QIoJR(Y5@NlIRL2N=!OxEox+I5IRPbLC*T3x2Ks<$cq@A~APlGi=D(dfIk4SnTC4Px_|?47&r^$1K`Va5AY2mn&Ac%03*N| zfO<2hfy+P*@D?MQ#RJTM7_b$v06a1LDr(>Wkb~i=bbtsT1Go+JVR%MkAQrfT;h9zf zYXN!S5>O9x1D`QGGbO+c$N+{wFpvh^1ReoHz#N8WVE{w`GhjaeeY3!LEET{5;0-X3 z;aLR$4ZsHQ1)_m;pd5Gz%wTvnMnDXJdTdsJHxLP=0A)ZA0QJ~mOm-na8Gv!w-GOGH z7l5%iRs-t+@Q33BkPB1;9l!{NUyB2v{aQ6(CjftdeQh{!0VoEbueB2xo|6LL0;B;w zU^j3Ohy%cHPKX01#DNpyzy&^WL7!aU6Bqczbp%KPihw5IB`}HMxgqAL4d6cT8kolLJiLG+U;?-T!N6G{4*)GZ;~1Wo7+?X!0doLi#tSjyg_!Y{0x-|K zy%?U49Dx4#AA?hXrE z*u}WM|2--A4Jt2N4-ZuLkH;C8#UHKW$=z$~4pvrs+l9tBR739|r`|qa7Y9xW^x9u4 z*JmHRPQccGUj_Xf)fV`TEeB3UuRws}7F=4a zxI1m><^O)~_uhN%I^Q`vYi0I4v-j-Z%z9?e9(n#w|NQ}Yq@|{*2Do(#0JwGY1N@x> zC)SNf-tTLc6IckkRK zA|xatc|i1lgpBlt$jJUB`0s@HzjFVp?tgNBzW~VZ-`XRPBDh5kxJ`bGfc(~99DwNt zB?mdG0w~20w?H&ODgtu-J+$OyL=)wIvcL)gpH@O?sqh>e$=l1Qd5D zS%vlQK7VXWNCgymlT`Tiz_WHq$zW9UEj62+S8~UpnDXCQ05QRhHaP(~KoM}g?ri-Q zFl+sqbot)Gkg`D;=3u|>AEU}vaT-Y!1}k>?5<_=Sp7GtanRyUsly zr;Cz-t1Ki&^b!yhcKg)l)^?J<5J#6Ql>TE_5$L_LimX|=m^|H)z@W@Kp^eI@lj96M zsfEsMFJIur5XER_N0vTr+M_A-RqEQ$7nL4_4)u8|5Kfdh?*qvuXZ668Ua_X{$W-M< zo7Xv>}x!3`Ow4hntoPr9|{{@;|5YEWwV#u_jKX*0sN`Nv(a0E*pG0i$%M082uoz zy4)-L%Lsd#lnkYu2l|5$k3#5drpF+5Pzck^2^G-zc13$E9?GC~m`{BiV&VEz_jG2Tw&hi~WO?3~fwx{kvX4MROreZePx9o^vmEsW!8Aav}IIX3C?7HuMVTN>aXY;W`mb}XBTkUOPQ|v3xi?bJ$>#(i6!TSVrfCf!&YoBTP8^3zfY5OMLA6LS=*elku4Lp= zleWFK0UT|PJHdq<{-N8Po!X}jH+?Vpu~vcVCym4rQZVPyr(S#`6L?i-o}1EUb{6D_ z;&Y)a(irdyS`fk=`t&Te!;ZEmB4WFs(q~~C7o8azeK53_AWKm;(;0_t`WUC&-jv+z z+p<;tf_M-Ik1ym&3x`u-`Wsx3rlRQDvLoM*QsMNp|xuVSZ z@Os+VBxXgFO!v{DmpcXb8iaCrWKv)J5wa$ER2Bi0&1o>8f1 zc2XrsI0^=jOYe*0^oHsu($gy!MR~c1>U1Q|)M+k`7T&B%t-_?sy1mtqa>s&rrRXWB z3m*gGPI>{{c_YrqAv7p~Oenq0G)0vECK}ST8k0RUq^GH+ldhTKCORm9MAf~@tOlo6BlC1(^dng|ezQE3sD%AI z&DDYD`uDPigYbR+Ts;1A%)$#{7z}Q+nXk@lO{RJ3B{-^I^3h$t*-xtjN>K9N}d~ z+H@K-BVJcBzADS|^EOV*j9(y;W)D}UvW6DPT+aq^m2loRDR zc|pi4Rn)HA;i$*N3{3o-3TtJbu>BhTuS!@KjUR|X`=Dg5sEK!BjakXWFj>fhn1R7Fv z{Xt~NZwU2u0bkb7`0IG}dxdcDtRqyI8d1G?eOeGOtTMJNIrOym2ge$d_+P-|CantV z{zSVOxfzU^#gS$5Z(cec=O1n1EiR@!`*ct?{Y`Yn^JCJC@t6NS!~Wk=aq`rN;$C0K zGWK^G-zwMRmqbSAkJ3uy98eBwng$tY^7M8e0YF&8$GuhDZ`AcSvd;LvmxvkSr*QHl z{yBq2>XmV;$EusRKF->V3chAA{6hhlE!apT9J@w@v;dq7Hj=^sBox+mx9Hig8{Q)* zF^Z!?IBvM!hu3O|y^-)QdFZJhiEnP6J|RJ$&@BI>oblp@eME@AdUyzkLti5RdFbus zf7KZFguIYtGGqA;LieWr+c`JZem|3n2;GTXVj=liUeDfVc2FP0aQZY6?PbAsT!lqg zgeh;yU2{!{iE&XE?EeLjd5>Smd$#$wjn(Mo;$8)M*M!yFQW%(hb%M6}D6M#S@8Qo^ zJbwX^=)ZtBp{9Ro{{s3xAH6$+dC=?(76!d~iwgVlYMGQ~;M(vn;NJD!Q=7A%zX0C9 zfc~oG-`0f#6>|+eo&))z!J8bl!bbZa=MV~OPrk41u^rR-Fvn-mSeXslc^pX9XycdQ zjRq5l>k1vl$!w#a6^`P((cRCp?K&~>O??61TC)wJM(Y0pjCn;*Npd%axQbPqv{!s| zbV4#nC^v*~lG4VXU)(zbymBUmbm>rUM>XLqO@+FoubvdE!ZI;UHA~z}g`dx#i%1+-199m)E7l?-x>S zW+8um=Kr&?yZ>jl2Gy*tS3Br$An^0>UX%n~YQ=#3ZwXr<-wQeyz8Z-mme@I%cly@j z8M?B8a~A9OD#|1HE9K}eAF&l(;ICinqQb(`0|5mGQ;dq118a8GKPt{BE1eG6_~dpC zgiC9=_4V(H4~Sc^pC+RUg%k%ucPX^IjnWYt$&4+FwG5txx;C>5^8SvYtvwnE#Kr1= z#xu5r?jJ#E!_Ub?+}H&F0*LEAyke=c6^E;Rv@LxE!fucCuA$Z%-p+!>-YOhZ8cv9n z8i@OxV7Ht^W$Oz%Y2S*Ku+eRx<##-48|$t1jBzuKjeH+q%p67SWuxF&-1PB_5_{Cd zu6_v@Eie5>2`nQFaut{&J%=3gCn~wFiwZP^dX@bhE`@}QyB6aqc z6gx0!O-;E;m2tStx}-&j08>1UhL$da_#2qJ+Qz0t@$V?PA35xfeOTaX^ zm&T`K_t(`zK4a4S8J8ewDzd+I$9Y`NBPK_8*i>*+mgx>66DiRaan0gFe3=owFs=L9 znj4`y0}>>hcL-mp@VGo(o7`7#Vx~vXQcpmgrBd=O(-C~T$sFa2 z@nDLty}20^LlQZ<9ivFMeT8CD$+*$wFg$d(ZOGAIXZ@i235v5_G}E~vKvW=7KeGyuv(cj==-N zO%1H0xT`gBA1AeJeK2@5a65Odrhol?bp)5Fbb1f-(%n-xThF{X$5DsX`m)?&gkVWp zdH3(4u_c|%Dd!O6r*L)zVX!7gQ&gKhy;OopNlBa!Of$Nx9?>v&5Zj{x-WbsJp2&*w z$Q~<@&%*Ji@F4uX{%E0l%llG|L|IW%8KBEv?ftXJsBsLhJTh6FoF(HBMmy3&K+2vnK_8HZ4)Bv z7N&OO{aWc#Cw?>B-@^4CJC4l=Xt0T}nJoCQG1E}i$XsFZnca|4t=(8&DM_;ZgJ7Oo zyPpDsUo$v6;BNdat3Vz9N_JkYF*NRQAC9RCrtD)Ixhgwh3bA1+#Z5_+UOTnde`mS! zj9;KN)rHAlDN02WudX=do0>Urf9mqKV0g$rQvJZpOJrvfr3p7MIBG_Vn1q*8K(ktTqifL~b=C^zr=WDLm>p&5 z0_e-~yU%p|?RjThj3Ry+SnD_CJ$3+ph;0WRDiqUCjCTo#N_Fq73sYupzHJG}Iuzm; zz0-RUOpcxJv*_clon5y#&{r!-pa>omOXl8ff0{4i>#U`D$04HEYHLe~VM}b;E)3UI z;w{?ZsQBRQZMKEkJXv8|)tfxaRWS8FDZKNopw9KP zr3{b%3y|Q4W+ua3OmI%-@R@4Hvegn27&|=?VUb};ioBjJ%kyxbx#QpuB3HxSAnq`sBGG|%nvxKY5BRwsOZ zSa;FVe!WQtyYv?T(gcoj;A7CSr#$g!3&^Lu)T)ZE7slhg%{g@GNU%T}cHA>NWFTj; zlo(@x;B-jdE+l5tD{(5?+YT2=kK$CX7cQVL(k^bW273bC*c zOl8X=;WgYcbPWC$l~^U?J0fjGvIG9aGdeTf9WlqJOs&ej#gQ`5i?UdlXuw=JBxg>}KEKOrd8&%X(%kWv5as*old^14F&+h<3N*10=+jQoj5E8F zaD@#9iu){nvXEzFVr@Y_tDO73wISackCI^lt)mN+=%)09i6q=>j+&;2IGobwRs8sa zGPSb=iRexCqrfQ+Q27!Uh42q)dS=sx_5NV#Vjo<1_Jp$(Tv)`laa zl2bF3{#iz`_>)I28%pm@S&mKFOled!#2WxLbR*P9HB2X~F#ae_1tbI?D_}K>aMB*+ z5?-+A_&E?(bwRDi3yQnbQ@+-34woE!ClH^X?~6sEt=rYA7;$zM7iEY{Ot7Ab+58$n zap*O7^K2dWguA&BWFiypYXqgJhP%G>TTQ0d3Nj`fw^by=-A!{#+`L3ZssOeRx<#rW0Ey(#3BaRV^Z-R>Y9Hnf}g# zRBkbvGZ!c#&p7Ue3>BXmkUSVKrB51&o%()xWbe9CtlL9T+p)Agukq2>gZqb)7o_iT zibXA{bhd*?16TD~dWvRx-l~kaysSZ+I#6b~aY%Ok$euo_(FZG9-)4s$G&Z5;pwRYl zcW@WLXIrTH_);(h>as*zfK*3dH3Stddr_OM?tl6RVwcLg15k00N3L-DTByFE1rfC# zc>YV+Cy`|VUURv*8WfL#9}*w-cI5YpKhS%)N+H9{ipP=93`AY)OMQR7 zd?C|?0m=*sQ&I}cA4=`=fr2dR&*MC6?(|hNqN>y6BcoAs)5lIToP9D9VJvFfG zIhrO8G@D*$e9Bm~{sl9({&Xdc?0e_Oyt@~P$jKh?qtz02xzs{uHcu`zi|6*dgr@=m zcX0CE)n3-~rVrdH6a2)ds*(~sdm=9dz8X#qX7@Jhb6znar$vBYQ!L0qs7)QsKq94) zd>1_f1!Pdh^MC}UfBFEATq1HSV`J7Ew|+pYgLB-gt)X zCljdC=K-a+pxA?Gtc%?mN#N2hOH$L5)lQ|ySA3bwuY@EE{2>o6$;9to?tbZ_e~)yx z6OD$J((vGt_RLHD!WXKuedxBKYPl+b&Pz=>FtC$+d-ec>M8D{{YlYo+6LjZ>e0>;g7M! zf+n`q(bd`Ab-^EtJA5DHdl_sI7bkJ3u4?pKx2sc52inmIntI|$`TFa>rB*rt3VxKC zIq7BSiIM` zzdG#U3*y1)iuihQv<`(%BXdOcpL?jDAiq+wDl49-7%hj6CRp&kgSz@BANbHd39*{`^RCBx;oW~f9z7rBHO;|&6|eE zpE*nyU*>Te9u%r03qL>N6t?na?TFL4eok$e_#KYG98zc{pKztWRe1~Zq_}hpqDyQu z9UYPkkKI!7kIL229Unq!)0wl14&eJ`a0L7ij26U;60Y|Xi+vT zE6vT4c6AbYS@3Clk&J;|{nVlp@w!QELQcVffBf|DV|0G1p;?>@uEM94gyfqt6k#R3 z@hxCJumRa9F>3;8E%{Wquyq|-@nw;Xu5yKPyz*pNXW%?nd}F(7uEkCA;QG{$C{n>f zprazimszFBy~hO}0ty@}6u`41B?`2}HylE{m90O2I0TEwfZ3{w+Bv!4r41HLb$#O7 zJe?Oyoda8a0*>;|zqUTUUQ5$iFKWYS)=tk28<~@`=lG|>fY#$1LQETsHuorHl9)Jw zcAT8{^7RP{y}H20oo_|U@v?auN&3;PuIj>B(2Bk`+P{Ez>19{gQnTb`qEd;9@}P|> z85iY(=##22`Wy^r?a=_JSiv^W*KQ#@jL4x`{)F+Wk*5{Lp7H*tgw(3Vs)`N`yEu6X zO~rr&>Pc#v40Jw^NJS;+(Qyvb$NvtC|4%7l%*`I)$6w8_zMDNL@qX?eYVl%1Vl0K^ z+AFnGT1~Z2Y)gF;-@U-s0;Y!3I{7%#-v1)DQb5nzUP^>c^yV)-@^{C}?c#VGMHGsY4b^Hh6 zl5U#;6i-GoLgEtf+{MCps!0<+?8Os55CQ|lME4?i z%l$blY*go<5FV9lSd1^GioJ%a8{bZ(iq7&;G{#oW$|UKCa;;EvCCVm_T9p*3zT;PG zias$&o!f{nmcYkZy3Uz%sDV#~OrNtEmT-#Ho4%VC6&wSh4wA>RGMs$Sj71Z?($KWN zxmL7GUd#42sgq?6Slr{w&{7UTn_w^XCCyIl^oW8|A{nJx^U_X4>n>v*9*;m|f}pKUgR;I;lfplBy;|)_q48f^-zAmU`$nv8 zw>ss0s!lP1O6fark1Z$J4**5j$?3@1x~KHazQcZ2dD7Y(&Xej&bKH-Fk@y^H?%(UN zf`!&ryRB#$^XE=Dyff8%6x&~zIRUqLq zJ<@G$?_4O^)h^AnhwF6TujW|dB1w1ttivaT%zfKsh^ke%?I?-?u~wT3WiPhCr~E#@ zmd;~aZ8hipTEX#h_@$qFyF#52&6D0lr`k^CoR-qhZVQV-#UFH$>zs?3FQ6AscT6ie zt$%upm~VaXXFw*Om}GK4&C`u6VlYiL^^bJ~Z%F8oi_Sjn5Nm7n&E-n~pA+YU#%qFe z)Ki$==Md?+@(|~!p`2?d-C9oY_A=wrmvz(VU))l!>~Uda4q_*}j5*P3k}z`;`Tf>M zgO+ut+5T`L-FN+`%*?(gjs-*e-Xl^QKQ3CfhTT3_>I7sQjCSWidgy&`7LnRwGPB>t zkZ7(#luD>Z|B#|29y#VIv?0J`Hd$$1pBY9dFkJH|f>E>VOF)(c_RQdksbk{YYeY32 zM}wBFiv z#EQj4BgbZ>w06a#LMagKWJ*V6KrM`5`+dTZyV;UsJAb~0y=IX)ycZ#@id5iRzWCas zE8itUBq$O&>FoCDFThCvCL*AhCLl`!4xvI0Z$~zNJ-qUblXVH*mF~IT8Va-7>{s0s z%}nsa)<0fuF9}u@?LFjGLuW7jx|pzL_q%hos%;su0OpVb?Qkw?>9iT;kL^!c=UJ*# zew~V3gIYU|qd&nByNGVqI4FE3^UT@S!(SYWUbZHTKrS%~_j{agy43b!A+BX6b<$CW zA5#9a9d$5HcYSgc*h@G%j+-!@aB;owEU>AibW zC@0U(eL|~_rL?@qF&X=L4e+zx1|EI;hQqSN&WO0#vZ($dA10qu+NtG+Lo}s%+Z?%7 z)26(m=cL)39^6TIcMaCQBN32-}Od1r_|N`?8k^(^>VR3ek-)Zh}c zFW!m*=N&_F()&FJzp2~01O|tYv*U=G%v}Xe@02u|wB7gwRgcbS={;2uYbOiSNPDcM zXnhAmO?Ac`J1$M!mBm?-h+8YsA)LY>SpcbuKHb2dGE+v3%@vCVd)pg|QZfgr>lM6G zEE21N4z~DoY*Q2?CPVnKOPEQP+bv&n6+rA3(`TS&QX`xi_vj!t&|$^dqYdJA!L*=W zP=oGNCqI1r$=}9jigH{u|N1W=#Z?u%+Yzb%RqmXA>DsrG{#{K_f;jFv9GiMsZp62S z(+MO;@{hri=sGp&X{uMbj5bRaa79g$`v2F`P^Zq&);~L4DsXkb-Y?^28OKHBU>wFlPGqCtZ|?G{nnVz z=nWYGG^jrQ==h{5)EwjQ6LlTKe9I3A9lDGFdK>ZV5Bkh||Lw31Uf8u@+ zF#;+o^wh?HbCfebyh1ZU&^vNOufw$nLX%c2^(yX#eN1HLH$RvgM#0W#rU;DBd#HBe zaT#-%5Edb^wQT|u&b=OTV|d^03-pXim{{vtm&e3AKkEA=%MeGE7>U z6b_T!@RHf*T&iql+iVKiJ`5Ln2GzcN@~7f_&eut<7hHO@m&)`pk%5lEpxD`IgJ+$v zjV8DdBMvssN|AmNQ>N3*Z4W*)6Tmz}Ly zt5qxG4f>0C$@is1Rj5)Z=2U=sSC||zq&R%^7_kj>m;<~i%|xRSoHSJ zzsaf{DxqmBz4jZmtbgVMWj(xqIWdIPTZ0t=*Zr)CQmY zJ`=9572ut~maJ)@SufAWZ8^Ko+`&)6e0&)W4VgITMN5j|6cQZT^Q!etm~s7u?w|n| z-3|NzcobSa@0Xd6?J;>g+S6F~(~`T{*OKEcT)n2E%cDZa>-UP3imSW~aX|0R1`w!E zkywo?^(3V`mD!tU48$ytURWs5d6kkMMVv=)<=O@e3UXwvB=T(@lw)v7Sv|E%#q!j* zC7xcY8xY~>XLZ+!)Qj}2OT>Z_I$>a6bl?JXXkJfG5!QH2d{Q*A`4r_r$gG$KVYFHv zFg0|S+2+qM*Gy5dD0?TE%|@O=n+5X}seitwl*gWhRtXbM@;S07F!(=(<^L(0P<<}V ztn4CSuySNjbduCk^fZx^3#;-Tq=rjJ%nz+OTg_(iNBspn$|5yR=$^Su_zRe(dsJ4J z4^%;DT7Z+#_hGgb%2UeneVb*?bXVy`4$sS4qb##zf}Zthoa|AuqBC+kSqoXL{{o&u zMBw9iI*}j(-Z18aZ*lkAz9w$gTtuj3RG_a2STDp+BA$ud`;-5AgX!VtCd(3#_SnOe zF;3;r19YTIURmPAS$FzA1uoOn2ES)i&p++LJWTVaFmAER(+;QVNf=v#8yGol&Xp}M z%PT6|RIU7K&%ZYnF!Vqe<*I&?P>^}qT-s*%_7!9AU%=J7S51rsFAfTJ|2SP+r~Z*T z)jPjA$EN=a=+K_#`zo@Rm6CK7^5pv-!4@;+pU*c(8#hXEe+>T|{snY@KG#{ z%J86O*LAr0V?jz^^tuSJa$YG10-0}kI>z9fY7K$o+u0nTCNk&F`J5Lx6_v1unR6K; z^%trD0F_+H(NVk%3#yByPom-YI8Y=rI^Dvdk7jzxH^Ndzaa=(4A$QRgQMi;Kav z2L)&|bID!1!MU&aOTT$WnZwBZc%iiu>_}}9haBhweI8xtlU|eQq#qXzAlrDLwJ!EHt~CgU!7r+p5l?v@_q~ zAx=e2;7lwg?UIcyjM|*)uDJyUO4Y-+<(O@z{WFc{&Rn!Uf3lX?c^2&g&cm={dnmnf z*}kpE#H%P9d_f}Z7X5CeuoUFE3_`&l5$6( zH4yEN&AXXF*4RXTp zf9PqyS`!~eOfX7*3n*}Zty9wDa)?@^;_>ovD#)P{9@4Tfpch?jLOKUABf;lyl}imA zi~?0aNsh{C9QtgZVLEzZ$D=6%MV|K)$8-i|lKy!5-nBOv{>s{0Thik=(v|bjdvd~e zs7tvDu1M$JFq)iFSYW76Tm@|m?2Q-`R8P>o*%>mO7#AQFaSRYgfTY>#g+Mi4$;Bp} z_y&_~Vzsv>su7bWYjk&EX;|H5edM=Rms-#Hs&EGJ+e11$JTjov@bscnO1>oiV4+_h%iA9FkL$we_lCZ1||y zsJqvW+91wGpp|w~-HZ=$i=PIqx1#idZe7)SMRw8T05le zUYX537QF&oYVh>zD&-+B^B6IN3i2CxY{?ZUFYPj=n;i+pgfSV(K3rc$O-w2Wo--;@ z4oS>DZ$fgYPgO7O`rJ}p`c5t&_09?uluJF#+_S%JQD51R2L#4}KBZP6(dky;F%$>* z>b13<<%@+@h!x@3-TH|wsj~yK&{BL~Dwx3?8_qrAcykdBaEwS?@#44o@FvjgPs8KD zAhRQLqX>hzhuyZk;>Z|-{U`ME%HpVj!wH8|m}fLn>tobPT~zjLd9R*Is22W&J$YI` zX?p>GH=*&%ccxFXJt7ubLA=t4iOFOY7w|(HI(9WRM=QX~+9dmgdy{Opb zuP+WJYf^*w8BD_^lNBZonq@ZCnc2VJBO*_n2gjf_w#U1l(C@elMP|MjZtXGiTtSHR zHJ6RGMO^yUF-0$fzRdf^$ds_s`oq|tvhE^c_7B8q}LnEwZm4k1gd2$$6B|1eU zg;{yjRC;!pMi<5-hr)#EPTlD8jLxR+YxTSq1H4Oio z)hTTpzBv91NdHG%gmH8IFCahZIQoxmU-@22qwYV-FTA$gQW}Sae&2{meONOV<#I1L zT)+0{OtT?$*ga!mzL6(24iVgx4fzX5P`t@lfyVN0Ky0$yi2q}F=^p$%e?iLo z^r{CLvJ}btiDb<0@-N`u>YST;@40^8;G^}bSn}7L+fk5bTYt=0ej%y#?|LH`%S#|k z|1|Wn>mF~3?t<*fn1V-VhVcT=|BHeFxqE#5Z%v)zn+E(BXngN~8gF%jO?GAY_wu_m zL!aVCy?;v>i}-EcfO&Oevn5N`=ieW{qkosuX4Avroi1F z08-ViiG!_B5#b@AnO;pzy>hksj-ZaM8$D&iNCuF)TtB%*qB{D7mZac5>G}K$jhZUe ziyb=K?kwAGM8*R)H?%Ox&4AG`ue$Q43K|y2 zRaRc5q{8%0g||UxSX5KCr?4w-l1;&rwqU(wLj|7w@lU+Zw4ksd1ASGYm7u>Ky^&=c zsw;6eIRaG{gt0+0a;+~?MpnVb7m!^vuk62C z3)*HTmrNXzmwJ7^!@9BGZQ(wU#=X=s7hgzZva2x$eKEHstaTU!KHn}CW_ne+l{(a$ z;8*-c%7ND3b#4q|6|oy&^QMvqxV$$3luMt7YbL!cwe}S1g*X-gMJH5H%q=8;0UA=f zAS)i^q@R0Mnw|D9nj0doD_O9KW0N3)CZY4KVcQY{8JP${3kOvn4QN z>y)E7&_ZIEvf_k}*)6ZW& z!L6dkGH`D)zt)D4H{e*A&x|QJa4;#u!DL?wz5Uz3yAHqet|D!DeajbWJc`=fuo+eX z2tIM1S40+3n2|3&3QQi5a?d@Q9%ytApJPYz{4@q$S2Al9@gm=gaU*rR`Um6jyAjWf zrnL@joqLhgMzr(HG?rgJ4RB1z-Zg+*rWh=BC$ChYdYA>Od9=OTRjP=uUhkT%shPBC z&#+RmxTso#+(VP{IXG}UDbhkJ>|KEv{uqOw(4v?Rq)cDRAB4~wQ~Ok0##`DmDr6Aw zeYmvEO3c!7pS&~c$RFT&#luT>$wEGKv@v0Zp*F?9y@j)kK&#-Cnu*d z6jgjmmV2Ss84|y8a;VmzCX$IxO?KUsBG0W$HllmnH6^?tmK4l^`(R18s{aKz9tM~E<3(~a3q zwHf|cvYQtvu!?7k^q&a)WT=3e=1qg}h;RAvH|8oa`i>h|iC3wY{LzRVu#>dp?tD6L z8kwuM^e2o9*cu(HK2+o?!%1!Z~2P`bCz7+9{P5Rv$mHXJO zfWD2l(lEl*!)+fj^(~I7l|!}0z1C%m!f_ddNxH-cJbXbNMsWa)hsZK$eo>Q249 zl(#srmz0xiJNvjdqy!#i-<^`=uR^5$>YBOdq*Awrr)B=qECS@S)mKuxkq^7L3$rbof7ebcP#1BarjmAPH7PR6Z2h{+Ko=*39( z#LBlIq>i7~4@68dS(=flx_?b*WX$oSLp2mS6l-CR2CmK@JrE+OsNnIIya+YaH_ll~ z$9r4)OSrt5waA{tZ<+qsZcKe&qO~Zg=h6?0fQs|CY~AkN@#(sF_0IgeT~v22a+xYj zGkJG`UZ0}EI)624zi^Gb0WQ@aHCq*wk6Ki_J^VF_dVATOc^5c)!A0#Z62vym)1w^G z%KY_i54_bpOUeqxs~blS7Z9H+BP;pxKSTxpdF*xL^^IKRy{xV;;}^(U#=~~>n_u55 z0|1Hu8u$M^xlIJP#r#Gk>c8Zl+<6Unqw@MS<>sTix9&V(Cm6cDC)cQ@h`aN8%3{uV z)Oe`5CBMr`)sQd4)}F&*j$drlrIEURBUv->i-U3&eK$Mwpg^b08?1c@d2*^9SJ}wn z5auPaMZiwjF?3czU*xxUQm1F>U88=)Fb z=u#I8crhgrML#XX5^F=(x! z2Z!oW($*~z2k=I+(_o-*VF%Q`FY)u+h!Hgu_a7D2vIi3KUDhmXl&&yzo%66>dE^q^ z$agL_nJ92}us+8L)^P|`a&#E_Qr^>z>jzy$U1qmPrpXX4Hme#t8%O9#9mJ_6ne1Oz zEEvyaboC{8)RXOY@78|xVOvY}bpdVY#nhSa5jjUxjjHZ?Vk5mcml3&(gWBy<^PX$c zMP1%({-c(?wQtFeUC6hvAp0HMbAe+>vhF&`og#el&557J!%qy^D>J)F)rZZnG!nF6 zXX@NQGt(LS$6zbSPb)2xaOAIEmD`j5c~JoXf(|3clCBhzKixQNdOt3lo~jm?ZCsS> z;1I+T$ozqi){!X2@{|aDvcHHfYQbTodS_hAL?rb6=6ucmf*# zKh^&4+12{%vgto3`{x6G8=;dq=XP-e0J6i!0L6x9j{$ccaVOoVy?FGjNadEwD`%ox zw}~DD)W6++cIVbJ!1LR$0S}vw7I*-+j`iy9>78&VB z#8E3*dMaqv3wm8Kx3ItV*&{dq4DECkREf@N7y_2CsZx8g3i5D6*mbG9xUm&&Ew48L zl6ub~VJ}W0?|ro2*iz}2ba`Nu<785lMv9J;lCp@=(8{jjHcGNw{-#JsM;Mc*NxI#* z5O7I=VKQ?S5uBRUu%-0tPKMliuK}nu=)Bmbn)QVI>r~v-I6giNpS5t*;OHp->5Oqa zG%fRBCF9c8V@14Aq{)0Wcb%`NNz~R?wnK#Cx5j?6gvhK%wsUg2g{XXYvf^u-_rar` zdQ=@#TC@5n-7q%txKXNx)AOv&*lY5c;;=a#+)s}UT6{m`5NgKpibl|VZTetsE+)L` zm|`yG?m`7zPVn{tGn5~a}xN& z;Antgdeb}8WKG0t+l3gss45}KDQ6@$zrh!9GVz|Hi{2!!ob5&bM5%hrsZ`~ZE2tuC z;51hKFCezT4py4J|K82hx58w1hb1LA*2Z?@qiqbV%#bm~Pg`lEDKw8+O}47Zp*kWp zi%U;xV*C4DhK7?0aVsEHy$N@ea-u%EsI#>>D1Oj? z5UqiQGk7{yN3=KQHHvYlR|vH>oU>RVo-&;k@M4RWqidUfTQ?1&`hIkMGEVIDvk+3X z8zfWSIVx@#BN9HWd1f5skA-B`{lu;*p+ME^Roy^| zd8L{NvcRCbqN;9A7H%4YCdl2Ee-% zWM?SMYa73;J@53qSk={dgJ?tQq?V55nKV<QO4!7=gtX^m%!2z-P+|d+!76v8@00adoTl;Iz4ZjzP`s94>9?Et8I+E5}&h@sFJOU4iD zUjM)i+m>Ow5BtsU+IpK(*bjCjSvRg0@^UC=H0F2awD4ed1q`a+`K4n~Y;dkmifW=} z`E?_P2Q?9Ra_ex-cVm;d6r!yeVqp>_>YEpF+$7C-x6)ZUS|PHKI%jT;w|~l4RZWDO8MIAS%I8Ye_V=XGV^UG8&KEuvjxVLFLTij;>cMBb ze*s%N@%xOh+h2tP^``BbCLk)M$ylotBIJkO2i9v?wSu>KU1(&3KuD)Rn2?RhKevRH zOw!`}FnSLRZ=Z{Lb*xqV780p79>^684d&x?HF7Om5`K)-j_Y7lvUEQy)DLF{-?Eqg|k9f8?$h}17K9ak2NAW%*?CKufO*A= z*rPPu*d?+}A!#2AoD6hJ!gn;e6H1j%9sv)ntZtWlq5?TcyfiU=I&b!f_Bm^b8I(O@ zPU&GG2hUAJdwjbrF+j0nw#SXB<_ns&;zodJVjiMZiUC)P-F zaN>zSiHm7`Bn4F7xa5-uGp#8(+t2Z8R<&+7bQ@v4ollI!i}8>ng(~(j3|(1Fjcztz zGmB7`jK8Z=cxKx-bF!8sC9yA#w60#@#kPxPQgUJu2l@7*vOihPzrHT&0E_-Vq`h}s zQ%%=48XGEAib&|a2Zhk9A}v%2O$bFgNhpTi^mhX#IPT-Eh@ z-Ii1n&9H0eFIY*c=JM<_*QgSxfDrt;9vFheb0M+)Oh^^OFVu((2LB79Sy5z9r0}g zgKgo2G(HgE*-2e1;g{MQ|1M`uRd~klu&f`PKLqnrt!ZIWk#&HNmr_mv~f;1NW z^Za6ZFM`wh4AgKLRRyx6!L^qlFJ1w#!UDNe)Z$}HBi7PLPp|u4XX!C5_FDIal6cJ} zS6V0C)KXjIEKG}!%UV>-Sg{AK47vH6i#+0d1`&-V$e`p_QQFD&z&MZ=er4%?HRZfb z^^?`xOyjy?C4KA-!MHsY``VZ5cE-;ky+JK^pW>5b=c6bpV@f6`%l3s#8X zbHhXK(b_mkSZd#E4CgXE=Gn&f)Mj(4$8x5bU!S~@5S9h9l{~m!9WP%@y}v zfR<%F-!{4>$Rf0UaC7@Pwo(pL(sHYh{wUt~(PDvtN*!@wba*p^Eo%hbag>+L?wiZ4 zQevsn3TobDBQ8z7!tv&Ee(u}W5N?}8cfq6ZsiG!e|A6KQb1ADPv@t`t0V>WlZXv${ z3$N6rP@t%9)bAx^j~Yfx+RN42uhOph=PN#h z9<=q+PfT{>1Ee+&7>}Hy&VJ{P_L17zn~L!2g9W4KJnJVFGN=S?)?3e+OS2?%?E5!# zhg(Z6zGJC98Bk2%Q`3F=sqDSqK1M+aIPnHdpzd<0hGu0>>^dfyYq~4;z^Jzdvt>fv zJRx}~F>8l}3r>@Qr@3WvlQ-Y~S(*NW!i;$}O?Lfnz=`hPfWSW2h|lm&Z~mnZ_io+0 zu0nKs{w#X`YV9827FFWSJKWFi-TI?#a}$twO?A=FHTf|y3Y^8-)Gpm;kUnRFu%{IN z8}PzauISKwvR5pKbiUyiBBpKZ2AIAVv46q1t01N-wiey0@o7OSQ{|`&fJ!2Xsjj!!%+M z5`CU=5Pcp31#-5EsvtJu?Xj;Ow3CXO8n_^nkk;_q@7Mf~QhL9ceIFJ*9G|=CWIbd~ zLDZH22BpmHmU9}{0$oxh!^et+r>e+|gr0&o%p9w>S~0!-(W)+OrI+oaDb=y+!BrNW zq)Q$o1GQRA3yT-Iz2U0dfe5?w@s#GhBD4w=Jx`Sl0nw~XPmXQ^XQm3gDzNvXVKP~^ z3a?6|_LECbQJ>Y8^~l8pmw1H~TM@hDCM&swLhS(uwPaiFX#pxHOg^w~QimsHy^Wfk1T6_yH8D* z_uc=ZVM+6VJE9>;YQGhD27Yi4JYy3FzD0)?q3wr2amZmt(ka=tCVDt0AgzNNm%2z08i)1uN9WR z^sTxu97bAwR{bu*DD3@_>AvE`k~rZ$FYedettqrbOB45Ys&MQ*=K@RtHI4k#V+-6| zE>Q3Q%F}!Ps(xCvrSDfIn^Jt&8J)~!*P7%tg};dXEjT_`t{|KHH=y&X za&-SN+OSbG#ckjz+L^oaK0?IoihIJ;kCYSSy{o5NXoxhElY)g6mx}eqb*&(B9h8Ll zaPNwf%uWn_^{`c@@mr9afuEdH8uNJy4C|FX32!@i#23A6j%(RmZbQ;kBUWB-igdWw z&d+Quvrob3hOTl3P2t#z{!$&PiAUdbdF63xVK(D#6e{G#mfSI>o?wUhUQGu`MK=oy zlP*I;@5V@Tk<`MC1TQq>ld-W?*jBkmlgld@yx!NEmnIAYsO$hvOCuwi@KN4D(BX9= zQ>=-X%ln}Rbbf8e8S~yte^)oGI@GbG(4cshxCZ=3#T`afF3A@3KRq76GXN1(dDZZb z0d)tUPsCLo-!8iuNrY5FuHT|zC!&w{h#zRT|5Q_X;BiEBcmROqEA7#%yUDA~QFa|u zv+w>AoL`y4kZP|#`ZPAAzbsZ%;}K&PI~AaZ&}zP|)h*0vW}9m8bxm=;K*MP`YgLoR zotXP-#8amUQC+mp_c{X9h19EESmZUcvwr=LI`sdoEF=6kpq&2y=wrZ503eA7o)RlR z2G9`=LJFGfQk_s1Bi@cy%^) z8CTyNdCESW4@?4{ZJ33;|F0fC9J{ede3Ac~a+^EvM7irn+N%!Rrl)%iU)mVlUWCwG z+qx%9%6>DF9iY#C9q{(L65#eA5gxn8pLh@O=kN7bBX0pjcNTDm4q&M?Kn0pe*uN$V zD0E0OeJ}U&bGNAAVn(4>MWc7`;#M*cl*DMLK48eMO;Z1r_q^)@x(Z?)LOoA3B&hDD(5LdLEN7Y>fpy?>8k9hfhP% zrWFNx24nq~32#RhSu2scu_lrUPQQJ(vQ~i@7NvXdoWnhg8vI<08s8k`q~oRQvb=iK zt-I`JAVkBi%+v*Z#4rk*Y|dfad(X=yy?xihY#v2Z{^cN~!R)f&6^pC)m9k{-dQ53V&d_FAW6 zNnvet0UEgNvM+rX#!Yu?WR0UTPP098Gk)h)kRnwJLE$`@?talecXiOFgRyspDl4Jf zwDZnt!&c%OgAtxOo+N5Fk+llwRG$*P=4s2>;L;+662#^5-iIm;2r;ryU@RA|aSy%Rzo?Y2lA*bn_MriMpeY#v!M@F{mEyn-j#S9Ih`6=J8FYCHq}GNmRK7$T-`|ARKA!6D_dxE;UfhpCK#{Zy!WI4t z4OLvJ>HmMG%?I{cna-KRU~6pmX3P_#&>ifTUKaY zwVoPyb5XvcAWgBC#DjDXRG@`4Lg&W>$579Kf1pVA7DAVkjb(8lA#3G)@4*`rjiHy! z1MvdP;2kzk3?{ zcl&k?e*I;jo=|xxJ9F@BMP3wKO|$my>w5glzX7D~Ebp=3!{_rQBYRiu8}FBCJS}cY z8+odRX`eq~J-fA?>C>plXW(Vi@Z%e=!z<<$hk*BF8+sPC>yn11iaJW%NeM4AM}a-# zA1j<)qp;LjalAU{MY!sN`oeND<#Ce2gV>aydEmzM%*14+PU9+cvZ580o7#OjdU|?l zMsNu5?D9x>xvEh8>1S!zS$T0wx+4ReRj$WqBehEFueos(A{c4#@OFXA~A5&-E(>YFieLhmMWM~tI=>iaV`Y|*>J{>5hW)2SM))`G|Fydl zoA~1h-Fy6&xQ%l^zV_$ypJ3^AK*7JZKjQQT0H`zye*C^Mda6d5lQE`tZsq=`z zk?_8|we{f7O%@P=$~NvZ)v(6D)ur`l{Wr2smhhE@w}h-?^F{41^K!ZPg!k;`;; zr05V>Q|PajP`c?dv8yzLKhHSav?T$iD&mn@R+tZ}aQZqr>;Mze9hsEwL(9{SCM}`el3B zRonNQM@g^~lAx}&_g?2=>lqUh@df`c%0FH}VX3`trdbwMn(F|iJGavx z1Fms~yuL;I{rn#^F*k(m`;$MG&)Y?+RpC3>I#>+7nBiKKNLgYipM~~Rp~9U=FE|{(QcJK%iK`9Q2l7Wj#Keix zGa{>8cVopg0OT4T`On2mziJ_D`BBbQ1Y<3T^hBJ{lT7W4Vii04Z=$JKi60DNLkw|D zGLmw5AuemlzMg~GTO0^#mT(b%MR#kaVx;Wp*Z*$l-~xZ@y`)q7uApg&kE zheTt@T{mARn|M*^JAfAhm~}IYt_C3+a3#?shK0f^7p9)=32TsfFPrFr9A`to?Dsx$ zv+a$Y$s2toA8TC@+{~xva$koa8J^mh{zl^*dd-KAE<~u*YWv_wW6@Gu) zBppCT*p<874!3;R1|IpDlKbv&L)A@1#cb>5lCYmg8*<@kaV4u9!#x`>QJ+RiaBZDH}5EY zE*^Kl7kRK%DQq6n5*Zv+Q-fFSY-`GAU`||=7&-Q&Y{Vyr`^PoMqDbo1Myn?+RHjNWyf*USF~SP3 ztydfVQoU}o>zr`v`+0(AFnYQY2IE(?Wff#}CJYTKTN(%`P^g!bPhG^jAi7~iazByt zNV(EuL!vs{$A)in;zLg7^!H>-o2rL{w!K@u#yE$djP^zdMfn<(gWquHCO!k53)!aq_uxmfd#XQwre9jEx8q&-Xqy3_z<{5n>F%df44nbu3 zm~hJ%OqmG(#o+P|bgm$W(e%6YeVal9ZpqhUF$Q3;5+u*yTKzSLxIY@0k7Z6BKeg-0 zS+rKqp%a|x#6;UBBREj0g6~hJ)TOr()(g`D8L8BQx?cWc6eDuQs*<_9UFe#7@R)QanUlKjGNw-j{U6&v(>Ck zKWuCaAFod|T-&gpz!jHzN8c#fin|+r6ert|DKR#Mux1rKxw6gmt%4LyI2M=w!G86h z$Cs;-MXt|c;N1`G3UV#E(qZpk(GQfe>15iqFOZ-5WQe=R-O;k3Pu(ubEb^$c6ur`I z0tS!I&(Z3%!6`NUnWMMZam;InBttQ2PM4Q5(n?`B+=2Upoo7f{bM>B%DNYQ2}@Hd&DA1Y2ngUYvG%zZap*49aU} zu(lcd;=zzA=G^=&$L4jxM8I~)c*X-zmAGl~O*rR_Dx+4jtqY{dd(g=^c^tdU$`TMh zX5NSvX|5KNe?7#WMOyUPP{-c>pto#pRDajVt zM&zMn{)>l_{~DkX0wCJk(L_Z*szksOwpso}z&-Y)$4^+_(h!Z#dqkV`;$H#oKaYEW zT5o;Ktd#&gB8?@PsO(3Bg4yf|M}|jxh)|GYASQV zn@Y7W(Gsi+&reF?k3qqw8adjYLdAd3r{jHXo$x}wCD!(d)}wO;#Shn~){dFlopQC9 zTsVJLi{edz96}0osX%z6ifw81GQ7B|$!9jVR^j56tZ4B!+i%q`x53_{;io{AK*Xj& zfnK9ZEPNpD`G|~YvGhWf*kug7n9pB3QLTVeH&j+Z>rEUhulQMaFW0WE+vc=@U!zBu zL0CIH58kB?X1-)-?|U&}Fx1pCD9l9MDe8f)i~~iz=RpZ4KR7-6UYQlx$@t3N--YpL&mPPyL}6tzJ&m%Q3peroP$bv|yY$j>QQ{N2Xs& z?J5MnDz!py9?&9Hw5`XQ>s{=heA?r(G|)6;@()B1T+F*^baXUqxZ=g@c&JNzpc)sL zH{Wt}ML#^zPj|_prr%z`N zxcI|jG<#WlzO6C*z~R+1tZDp9jCcr{W_c<`LiO@*f(i|^zzX-jjD7IFD&?-|I%r!g z-<$>;Bs>ZJ^s$@I{al-!P@t)`%-`R@d6Z5m7j@9<)w3Qflk#$9=3dKy=*(fPVEeji z=6W{&Vzq~CzD|A=|M#W}E#q9Nt_}a&il>T8pAYCSh-p=WaB$|3gUFBk58k~nkbXU- z@N|z2jkQ|lmJVSo0jT!Hh$i9HOR6%l#lI@`=m*5oRz+;3*X`P*3M30OKWnQQ9^?xu zh9bC$F@gO4m0ff5Wb!^%Y#JV4%Ou>A0m=|gPcYM`TO|oi!XizOptc|qVtSgTBxK|u zfc$WQ26_Ku!L*DU>s2-9^BVMkfNK-4yv-MuDao9{Cm9?5rR^bzX6v1psud9fqgY6< zMQ`rlM$W*?h%{?T_fo4b{z6&5&3t^|Hcn||2roA zk+>`Y*h$$R)5!u70n}4BLjZRG?9a|sX!!52KfA`x{)C+va=CNuzdU7)8yN>M%KrKt zXs`gp$4Z@Xxd1U}I2%9sGKvznC8M$sm*k}uQB!ma#@L z{cf3XU`f;_h6_CWL}X+9CBPuB<73LO5F3gY%?-?6Er@qsrFKJk3Xr+cRMB zI3vNI{yo`SvUD5%C70Gr{4n zjysF*W0hitGGdgS^HoEqhmAOG@JVxaVp|r8MU^o|JXI6|8wiCZm)okp9O^6ZaYyj% z6P#^R(@|;to$+kjf+MnU-JT(Ym>XHXQ$=%UgQ1vRKfy!;epl_+XBqSA(o&1SE_9IA z-vHkE=a+xQ3toC8{%~4wXw^Iy;%XdrSt-%WnF!|w3%x>Gsq-Eid zi#vlk-^jQ9Pcvaa2e^q~ye@aP4Q{Z!vA=E>hDrvuXnyPWBOOa_9w$b#_vqCsn7?*X zzxXyCsMg<{M)QTI-`U-MeaKPl^@pYAzx+-&#n+y2xU0ZWZbn z3tpLNqRSmd%2V$Nmm~)^;C9jytFQ`HKR2JaKwFO|AG5GY_OO4Kj)z3dwXWWC*F5{G z#FHaaMcXf)9Jf8`PNn*wGQ=Hk5GJD~gWwhO%*zq4O6G7 zbEMy;o=U`|2{H3J>r;I4oqgGlPyF`TJ0r=5A$3K3S32tVUXTJ}OIJw7Ef8pXaC`Lk z4d4-S9S7&%cS-1GyLvY!}m0eoFSOQ2l-qA=&YKC;1~riTc$cxlHQe z5RI=`*UCfU1+fn8f7;Uc=kc|3|J-i%@F%%UpLa^A-cX}1+ zNJ~2x|EQHpVhXU%Y&GH@&$cSECjAY}c%ClD+MFzD;^-hB9H7G=#m^(X8qG&OrefxK z5F6r$3XmUsM6U10D;KNQ;Yy>=xqOa}m!mE`GILQ;d2JAbQd!>N1j+jLK9(IqEQa!5 z0E>vSmUjWah}um5gXr$dpGlY#i@-PHO&q;ft`MvO6v-)` zbROI9x=bWMa!SY=L+AR`SkxS~Tq661nNYn5mT(VlYFuM`?1H|tWu3YCT4;_+WHnI+UCqyyYC0;P5K0Mo^M>-+?Y3m!5c~pFlx7`K7o})+2zLf3_ zEMih6i5i#@*MZ%XZKrS8#B29y|2bh}h=r&Q&&$Tt43h_hd?2^JoKdxR9FX(tHv+bK(d+!8fS&dIXA*ke|yH$`7yy2$6f{(YUUlUKPpgUE@a zNX&MmX{amPGEmJ?Q1H7{yor*;{oM>6cNXQqIE}`PKgv?T^X>yPU14kH$4PGS`)A@c zdC9#Y9$dh>qc~cGqM02RN-}Lh86Rcdm>V&Mk9|_PEOifbC``G{Ic##D7FAI(*d}0v zm)DV=R%v?vog;z&Te+71E=bQZtc(K3)}8JC<4)_S`4Ibp?R|IW#iRg6;mA0(m|HzZ zOn#+98Sg`j2D%oe`j6RJe|9oR!!ymaOMAtB8WZkW=e!w~!YkjWi$#}adUf+q=9AcI zLg@-1$HpNF2b97uj}+cz7)99~?D2iHE0$xSXdkbZ#W?ZJNx`1TkC!zp&0!Tg-pVqN zlfg7EWQUltOa;PB41jYO@B?gd2?H^!j7S67_!FJGHhRz@{<7_1ZI)5|x0lW(k3%Zd zCvt6Fvc=)}peA!=7>Z(#(==cG-KeV5jfeMEI;rXhYD{cen2bF#;tgmS=7ME&q7k!D zhVQs9GF6|YnpzY-`zKPqUX6s4dG#c_;F1ku^fw?7`VxoWw{+xBqnla1k1;4%SN%?` zcPX1Ry6|0z6r)U4`y~js;w5s-KQy{XCRdxc*LA=j&) z>dJ?55YW}(xM9dc4bmBM-?KLd59rd+oO?K?#yty>Af#9_M{oWlK3J>>%YT5w;vgeo ziv`!lcn@w<)a=8DJ8YD~1-ZW%?`cJhREJNnCUMAeUm0?Amz`|SG?OIXCqCLEUj7b4qvVSq7h022&4@^iYiQvrXXWKXcTgr4h(gk4Fi~Y0p&obi$@pyb-XTgY1 zFk9QVju`Kz<;%{(H(!E607P<7KfXUUc5=xwge|Of65%2uogy&p0v8nHf;P;kB%S4k z0-DU67x*hu^UchE1gXS6N6 zg%JKGY6N_yI*yP6!_>W!C4o!v!T2jzz|_ z6VK$hSqw3Q7L}!>QWKdySRflJDJ1dFh_aF;1Di5p*TNT)*i%rm&`3<3x{(Vcm&h#l zytYT|dJKlzJKd3(;VWct#VyzCLer<;!Pr8x;ewLp?xXGIm)6)TriU)ZJI!;L`327mUIT|@#kfI8arfq8oz-!qzwG?f%viNQ z!l3l`*582e=BTO;XwAN56GwU3V6A!Ky)5m>Gr}atoTfX|xG>l3aM0=0$48eV^i%vz zv}NkAbo}EJj0uy|@4Y;K&`vUJr}r)I^=Z}vqn-)uSLTj}`8D0-wysA(s>jR?l}DSN z^3!NS7q#slm9QGd8AjaI%mItsr|;SH{yXSv*)o6=%3GooUOipO4suaLK%s3Sa0EaUD{CM$){v=mXi~*eUf$S(( zN9>{xV|3Y}g}S3MP>hcmD#bXI?_?8SpYZZs^Fxq6VI=*P-KzHp=<))>^d~gMmAyfi<35 zC>wJ;Gjd&wl5|b7?op1mGcq*_^auTVw;2NKm}1Qb#xOiB={TH8P?dUeo%HcuNJM+Kh740H2f?E2YI(gBg!+7l=X67Jhu$W#!Yskn-obXOU zc(!t11?TCu(4y-^$4$_Hy3D31Gn``2w3w$wlZZ4%-p04+-4v^UE{E~l5~52rY-bsG zcX@jZ2eY{!7wg}k7GJ~xO9{h9+*Nz6@1%}B0X+=}U6tw8>#nA;H<4FN>Wbc@UZFQM z4ilY4${Z1)t^xJGX4f=6Qy0X#(qwZ3bBAfiJ_PB#Ot#P|AZ_a+X-?oKY7eti7y{jW zga>%=KV^w$NInj933+j)s)y@f9iLeD)w>INxh+UQ96{cdv>CX7-A;pxSd|Rji><4NtXl5<=S}`3vZ8be0lM_A=|bq?d)ag1W#&;!)Fx|h9FnzD3%-{ zNL5khod(6$kcAh`B{1~}$17H!@WZ$L=-93gSC4r<=osQ$)-{u_-7NVc!`(<%pgB>Z zk<@UJ0Wb5AlMNQKlu-9Vzw9L*H1I;6eehsgVpiw%aZ_b>O)LS60Phv0TXERUH@TYl zN3nd6y?slxA{*m#>u~zzsX7J975Fumv^3&HnMH}mX;J~rFy?C~2k70rDR>&ONR)M$ zn0&@&`)Bd|cgk1!CtUj^USp#hOiEIhe799zP2_!VOODQ~f0*_+VEpq{#tPd>s?V?T zzM%`gi%zP~M^>tBu>D_O#+Q$&h+!RHO*CG5f~Vh9id6{dcShC1|AaB+RY>IR(dWi` zlB_Dfs9!Q>m=J8{np~)-T*lpdWpOz@`CHY zOK8mo{S0JTdSq20iN~Xp5uS%sE3c?LZlK_$gDI6_zj&~?2)oQ8<5v6XiNgT{2V#7Y zInqN)5vko?O=ydt-u8fLtI{vp^&|LNpxR=t4O*GFvppAxaX2B9KQ_U#$l->IZMUNI z66%I@T!hZi(thbm;hE)yG+1UDB0?rVOnPDFscbt#H8BL0HJ@cK_UXrI`G2y zj;HA#+?wz8b?d%E$M8+O43!gk>}PMDW+0%cE}@b;>wwmbc`8!!Lav6K@0(Y)=8EF2 z6#EGbJ3toEyZfT)mYTN!W*YF%MPV7kN&Q=psCHtRO}wlk_OB zBQ#mw)R}7ikaKDomE6pqnC6%Ce_$~4jIBcq6~kBXuC1*L>2Y?18qPU{chJ*L4b zk=AhUT{f#akGCom{_s+BcbDXfgV zf@y>shHoo>yK=Kqvz~U@LPQT(mc-dwDol}<+NgGf7%x6-Fwi)gV*So3CTH!#iKH?$5 z=0>tJk52PYh@1~%KSzC1UxrVHoxWRx9l+QtBhcmYb09I-Bv5$lXLNcQT)u383rZC5 zO>(l(zCFVLY;AwamUyb|7gKNDDBveD#X&Q?hCYwWzlqkA{aL)E`eo`?GvF2z=)&Q_ zW#ex&i@Z{saUuBfHrVM#?g3d;`V4u+^ZNd*cZpFVJQS)`ku7e{QuiiuO!-vu8<;{7 z&=j)=MI@sF1K(|TRNdRCT8DBmsm-V@S|;PfIqHm7?8A3x@JtUU7JR!g@~EZaRwRf~8tNoWDREcrzCO&f=^gwvBp_tWUTSw`_E)va70_58 zJ8OtVaj<6x&bd`YD@%YDXv8-y$0oil31mACnxTL$oO%(8S#?08p8$3eDFnNJjRUm`n3T30L0@R#h^sgFf*t2x*RWxg;sL`FiNty@wh z6IKnZ%f0B~j;!GOr{w5~lq>=5)B!zFL7t{zd!}S;$cCh`NN?4| ziQ9YJjam>`xh4&Q7(mc}P;`sz&Wc%mJ7=FrMfXnObK{}f>Hr$_njK!IF+oJGk~Va+ z7Tf%W)A(0M&|}B_(MWQX5Tge>0_`fh0~45*q$7|_LEWPvn?ElNFD+kEcio)q@I+flPP$3Ox$J0u=m1 z{`W5KcB3O>j}^#oZ_(bbeRk=E?~Ns@K28v6XUaM=WqXd*}TbaO+l`=x?(il+p+uXqPk5D!z@J~-9J7UVrFa%3+G(lhG!)&jM!P}F~> zKI4+Z3gLCd3$ATzxp59h{TRmjCyDa)M==+*cID(_j2c@s3`C?=BDBgOM2zF7irNit z1cXszN(Hfj2LCt*1Vgux62+7}h$kBjMg91MnbMb3vs6Ip*)*1)=nR#}i7W#sSF=hH zs4*z|Y->zKDQvQ0=!8OcNp|$8Fyd8V zJN}@WGvj=xVwCv1tPDIWP?I7$sGlxC^IszNekV}94T`xwF>)W|IRxX2mS-TZI@bdN zca_Nu#5ku$*EmvG$^pR5O2r1*SSJmkW$M8)%_4ah_U5ae9VAc>6*T2x-TEuDNg`V$$=$jj;5AT`P9K?s^1c?)5|5Ny`Gh3k(jbYAxYU>YQjUw0Y_0XrZ|=xT70`P*cF;B*U)Cm zl@(jphNEWbSXvmZE2Vv)`-F&5rL@&6Po`^Ldzai5%W5YdezVe5XzXxEt-6kX>kH2O z)K9*J(y1micVsEk^FV{-P*F~c=5UB7&U{h|cQ6}cZn73jg?q7J&o-v+c&jY-W0WYB zhW*kobN{^kbi)?KRH5=%_Tr5LuaDIXcE6sSZ-=I<^5WCT3G|%$ZN!e-t-WZ|aFpm@3a;GZ2^w$mTcp``f_`UwsIt zK7`ptKg^6#MOhOD>BUk`8;30xJABqqY+p;9f06je3m{+wd7~TX9kBbX?

    2. fbDkVL^Hy9u*E3(_PTY(H!(Zs%Nzfh0}{DxHSbFo&&_$2x@7=nfv*3Ee1=36Ph$ zwsj6!G<>|R=UrKf(xxT1_!aHiU+RV@jQ^^3bo|hYjCHMyoZz&?Z9`5>A)hw#>?&D6 zZ>!+Uh5GEfE>g|W-jMX|XVLYz7QwESXScr|DXTJRp?b#yAevtU4Pd{*zBj?uT&2vx zMO9Qd+#*g+%E0ebqStKlg2)=e2qzdsPbYWG4d>uL7vUYPC3IbXgol0D&c$Q-l9;I3 z6glSBu-CY=1^(wznDPC@c4@;@Uk|2xo-{KpI3HmgRH3yfw+lF&v%Jb$wfkLke+6(- z_~QWOxw_RAqPEM_a>(&+#0^MYWBAwgulynHpa*dMuC1l-9@s|cG=1Tcq|X>kB#lYI z{}Lgp0#VNrY74xn*6`Y7J;0?ai7nCeqO%J=&jzF4pL{#Kw}>~W9{T9kwjxzWD;;|q z92YyO#3p?>(IF2HhI?C4>%jeW+dtS6PRO`V2w7O~^K0Fd37y+_3?J7JSfYsk|Y0!t{HS zkb=#o2VPHs-Ks4gV#p(FBRn>gVqY14XoLYfdqddlQ-!#*|ExF6mkcx-x3o1#)t{JK z3tvd8qz0?#^r2V!K4C*Q(upj=>cEf2;)YSBkg&EX2yo6q*9->QpY-T`*L9|77jZ7Y zn$5m8*UNkB=lb(XkkLE`!jWx#`f!k3A+5a=iykV!6irO;WCAeBe_2ll^r8qDI^{DTTeWne} zznU0bTvsJ=6?@bPiAx?FR9ys7a{x17d##HQhxqe`5()IvNoh)co#BDB?HV_VFBE-f ziP4`6fgYRthQ-rBzR6@D!>AnJfix|&+!vO?#x!PuhpDLBJ`JaqWQ$Al4K0pk(WMH~ zT#PGxyl`!guH)s_n&OI*PpK^J?@wFSu^V%qz#|B{6HGDUVgCU!KLa(Mhc+0Zwj9r2 zUuAH={sWwNq17kE&rT5y(fH|!1VJ738E1=-jb$}yeY@@V=N2XM&}AVZreH`8dq3!S z!M|_sFe7vgEKZWrIDC}KQ{ZHQDNTIm&zGQyx?b#pr_bmlaVc!{LuU*WMT+y~Uc#V!S;T z?jM@t(IFMkJL|G*=Y-CsqF@;M5-#)=X$AlkJ_XFd-JKW^y4~DLTDci3k zdRp#j_3q-dOT7?~2JS8l&AJWZTDd|R_l#9J)Txw<%bhEN;W4KmG~LL{?yoV}vgoPL zqpa`or1#7%4DXunZJx_QnwD}J9X*Od&m_({+cNNs4rF|gFENz`zeL5E&`39<_*WXAPtuk=Ve8}2^cPYe zg>3utSy(9f@Qif~(;XhffjRF|qNO`2xF!dRF*6CfI0-%6QwRgr^z}c}O@a~SWzbT1 zPPN=gM`30VfwOh8sLpgPWQ?5O4Hn#;t-2o^Ws61#)0M_=IW>{ESBrzs5B9Xghh{-3x;4?#yisp5FxM_&SI24V#F-c5Cb(=5^- zZ2_;P+2Vdb!ZVj+@m*eMd57$2V2A+`SHqQ6CS|cb`7B*B2U%~J#Yb@{>H4k~Aa>tm zZNM{4TlKMO10|Fl#E3mT=z3QuCK3mm>(|^U_++PLGc2g)ljS&;ccoeZgw;svSQJFO z6rtX@r5Cn&O$1v`YhqJ-UacBheWQC@_so7DQ)@J3`W}&PUIHW7F#d3#0C)k&ZvUbD zMajyH-T5bXTDgv}SV8bZbySt|`TY6BOAzOPfTdHu6A8uS>XnpOlY$Pf#ep{O!;K(U z;+5GY8rav*e48EEKx`UdZzcOQ(!lNjKS030tx8CvWD_v!F@fWfqXuGV6(XW{6(WfN zr~(^ENu2-xVd}w2N3Ev&Nnu3#flU$-6d(^=Cy;dQClYx*FoY2xp%7>(V=$t)?pKK_ zH`H&C+Zv$`HV~WK4VDLE!N1t<^bjenqWF#ugL@gFz2*Vja~A$_z+IiBjmdss1=%^) zUF>LYs=z~b^FEv35=0rL;g!KmPODQ=R_!n|E!^d#n$hjL`#*7?#U+OJnxLV0Xb2BXY$GH+I0#>DgzTJ&uUYol?S|PubdpVfN6Gfbw;SNz4*V>w ze@%H&0l^f`^tFkfy{`*28px^4R!}12i~|i7Pn?gZ0s*(VrZ*F1xmo1yrtMoM@+KX> zBp8SjrBmvLW*lbaRO0=Tq}Q;xs~{a7;`#}^niZ5uF`c*+)URDD??$@POHiZds{hr! z2m5Pbe-;G!Aou&CFNak4IaM1t#eXP)Y-c1g&sQbwi_5a`#)!8`4~MVzdnpjnAw%-$ z=<%1KKg8aq7LHAcU}aY6mE1yL;=2X;YQe zwZr-cpyb%1V^U`%5PuI+v*v^-IaP?mz)O#Z3@UV~S$RY%*Ir6Wh9j{jc;Dk@8ZJwz zMM%@EgXoq2)>z`zqa#tJ*!oR5+%iEXTr?VR@o{ngCFJSq0$9KVbjyT2dqf=V7(!YJ z467}C7rE(tSc~slNYvk}cti%-&VV}7>;jgpW|xfBInHS{odOG}Yd-7PlQ^Nkf`PSp zeuZyr5iFIJbd2)Ao$Ack$~-t)F63#BZ^W$vTS+*Oin=6Nd`e4z0h5*o#oEi86;j)+ z7D(e;yOd0rZSx4c>~k0U$Q^liPl`E?E~M{<#gtRyo2~bP>$Vq@rkSgB1XjhO_NJFU zLB?3J$toyspmGYqGJjXFnGE-cKRZJ642(Yb^lych&fCUHs{VmbL4Mqnbu+EFeRmt0e@bB~5e^P)yOUNUu zzN5;i+E$8rPTrYf^mLFctf;G=vNh~BZb@OcEUCcE;TIcswtO!#EWfMG>E}V7m8S3n zh7F+C5sMC(yX2XNgfIRs)fRnZ68*bY5iK8XD8;!GI$@Jx&YS4tVC`h2Jwq%weyU@{ z;xLz(aoVb5I5qsFQr)l);|FmjGYaLa7HKJY@7!r+N4nhK*w9M+QmlJU0nDtshc>p< z9Z3<4#K)r=x4?Ux1*+eMIW2{3D>eoTdNJE%*a`DXo^b!f&V?GG3weBUN+HWtf(j*% z*!HD}#Mn@L-@z*_eh|SJYw>tm8iUUL^^M*T8QiU9Q@$})hd*CJ4-91m*S^^F{yChs zSB05ux(qX98c!8UwCjal@3YG@o9IiW&uQlOE2gLAzk=&O0Y8AwL9kns;8F+60RoW6 z>pAbMB?+x`i|8*ELT0>0@K)st$)6tz|^e>C@ZTouY zakhjmUnSXnU>1Wzn!WvEwQ;kK?-sVd@X zO`rHdm*vIqCz+n-!T|s1BV_^tBzqZ$uq~knp{F7!w~gOnZ?69bYVjBPz#$cNt(DRJ zZKYHs2f3l1{lY!s8v3=SJQH0z>jN?GgwO4m^}&W&2T-7L%&joH8V3Ix;cxX|YpXvb z8Fl7a&opqz`-HPwi<6_ZDA24g3abo=@(k@>zHfng^VCv+aVj6oQII`w6f(r##toy~ z973aSn(e93!56|9xai?g8_CiCSKJ!=J{Xh`f{oh)JTKyYl#IH(6XOU@9e{3{_k8u}$t02;3WMG!tvXXX^Mx7rceH!$sYr+R7xIAJbA zOK_KM_upu^&^^@vfp>j`eWYndNhcdQCWh*+-Tcpy{1yCPk|6FtYTD{BrrFXv?;uRt z9CxtQ&24Eh3ML3Kx%T=80Ysu`q=fSCCqfn}3m&rUrE39xqjMmJGh#$hykgQB-B4f+ zl`5Y9?cW{r-rY%NxKes&VD0-pEcA~kTpakmgayxE7vGDQPlvDq5|~ z)X}ZJ=Mg60_M541lL%gB zMhVARhmFhqh;nS|*16|;{Zxuf7 z&sZ#!?7oV3pI8_iexRJDOkRg+5}9~d&@~B5v4IgW>Kv8?>I%8%{lAGHUv-sta|d|u zd=K2eq2KEyOz$0QWEYiH`#ssiZ#2>^h$nOKQGM}CjO`oD4+yRaLS|A!B+bxPSINQg zt=vnTSnG+|YFd1OEHBJ86;SN-=P8Yil`fuGYy7uqP{}6Pi!$(#h|R%A{&u0qC?&1)K$L_fw3&(>2=AB#7)O=#5T6E?IaU1s+!yj@~f$J{Fpj=`uNAOjQhg2Ip5%K+^L zY}pbZhIM>QNWSHfN{PdoUIo}dEBZixjbr;ZDk7$HJws zA#Z+Dl3hPA3tcdWP%Aj1U6>;t?}p^o-LwGB`8f+uY4QCqZl;cV6M{(uJnJL>p_~pLQ@WL4SluNilj|!e2F#hli_lRb3KySyg zZ6J>2Q8nI)F!jbh16(RzNxY>aV8vgLDf=JBI=q{qC40-p&CqGufYs+ zi6xAgr>cSyiB84DyQ|V1&40;t;v;lDE3;<_ctOH1yJ=O83fDrePZQAy5Cd_2bmN^6 zXB@1=6?LMahKBkMCT)X1d!MlEhG6&Ch8;zSncpS97)_QbGBAi zAB-40G4q>xnSf>h*EI|o2+*js{Sg$Kq7y|hBA~TO#_ei_rVZvCOWAg15o;lsi37j^fgfdwI$VG!v5iIyS6hXrBbUSw1B6%#cuW|z0WVg zkSkHTcAOq_D9DNjcRc_CKlDP!IW8Vkki{$Jrk3k$zAk77Ch%vkQflnMMFjbaG_rLc zI(?XXsKS7_wfPQ}QQbh4e`6`{Y$p?-!x?&%TGUP>WX6ZzvsYDKfu^z76tbU0IzvEk zv4()WCpi9lOtI`Nh}fVy+)&Qj^K+ncE7cJi<*Q+-3~ll>R|o; zE`c5hwz4j%UjzK<`e!Dxbg*V{pl@+XuM4%ZjS$r~(bqEl)JaJ6F!(N5s~&k6 zXOqt_TRIlXtavVMY9C`Ufw7;|u}&#RW*_UkCy^Cp*KB+=L!lA-DuLN52D{(dLQAE+ z4>f0Y40b=k+gSsA@n%SURzJE66F%Fq zW4~YY1OBOozOSVL&n|Kew4xA(&62N9S%M|oKuEJ$}GDg40w$zv4+mHxl zHcMx3!aBAem-)7MKP$bZx+UaF+rF)nkvKJabpveYLOet+qG~9%qSBTR2 z@s22Iz*D2RsefQyQM9qq;F$)9hfSX$N>iXC0DV*u9AV*<#qPp{;!7|d%yp_?YD;yR zHJr{{6VuIM#j#o4`vaZ&KcsQ+*-}QZ?bS2Ihz!EqzsQbk_ z5Wn$(xOa`&Tq;{LYYz?w&YO)$7?tY7Q~io38h6`y>`m=J`W=ys73U)q($&|B=lKv?3ovRZ-iyG3`>xmswU?&gM8_tqX`}k5+|(l1R@^96VToFS=& z_0uQs?AQ^al17-EB@4jI(Md{=%4_=P5XV$)R!56y4kv4Ks{wWIC%#;+b+*n>u^Sj1mRu~BGc~bF%PqO-f|lg zlyG|z{NS4w?f3JLm34aGeJR+U24El8JXQ&x|4X&8kBWF>6SYekfUyv*5@Afmww1;4 zx1a9FbagyGIsu5CQi~FKRKP4B%tI<-wcTclhM+xSrhfc0IU zV5iX{$q@Y%e`v4m<5r>bM6mpNNXy+B#5@VV>#!@%=mu#%5L9T*|Vsa-qa zEo;bYYw)d#bh%^rZcVh;s1975a7ZhCD+2`lr~8)eT^$(YfOM_PDet8q4fi<5SCGAd z^yuVl4rZQ0@zDuDQP?mx{uSh-1k|%W7x_8Qde!H7m_q2r%#9g55h~4{(YV}V9dAbu z-fYhLKx~C?(uKsi6Quw_*J-xh2R8x)%}iERRDixw+o0u;mW3vRuAehI6qR}k8ah{D z)0EZLLibKkZY)MU1u?iMGc?wuDBzi|YjqEQjA_WJT65*HL(1fhb&?snFm#}U2_w&- z0Qq1y{gB|S5o2JNHw)krRA&O7Y7F+j_AuNLq4RynboGlB&{Z z64AR*xencpjOI`EBHCUJ4gB44KTJ;+ULi*?B2Fq3>B8U zShv?6C>U@!Uy~khj1g8MC5%q-$e6W?J|3obMYfr-(7S$#nirk_)xS*lXhdL`5H~3S zjn>d49jIvVzDi0F8xS_pe%stw6=ST(m$=lfUrpQ?ea#U4pK(HbX6Vg?UzJLGtgbK(5RWCQ}scufM0aKYyOWVqawWj8d*??yh*}URr zWK!m`%DPu4(O&d6KwPf_JK)@LZSYm0fxQySxGMZ)&7q409u1YvlDnr{q+0?S4@dSs+pZdbArh(Bp)hEoEmFoRVYOb5y zAxs&qb+z@nuz@YAW}1B7GNHtySu*@Lp5n(Ul)74#3oCT;dBj9lFqUl*pLwb63L0MW zKn9FJ zifQrq(M_dj9Z3pX!zn=ED#NqC21vCl`wFEiF6UL-2>tyno>-* zZxZa!Nv{z@6|g%SSH^%hwD=)KES?{%~W( z`<8hJIqFim30V+?C-{c%b9t$|h04LH4GGRBZ%0~kC4--~fY2AjFa z3h-ZqM+|bpJlKHiqNM6xxiEKxr;S{@$}~00C_sIJ#d_rBJ^m1YNJETY1Ho!)l=1*U z8dJPXCDJ$WP};Y-FW+rWwCRx_U!ze_@d0i-%YLLOl68s#`QoraCp72pTwl}tzxCxO zvn8=ycjjJ6q1-VSY0P~G(;4@O*M34rP@e*a%N3_f%A{T*r?9twG#-qtZllh5)%*Ev zCgoPF5?eneyu=|eR5Z^ygu!HH{Q_Z-1oZ%%FWfBwGsfC~PSc;S&QTOpc{4$0(!^HZ-Rfg6zS&@v|(d_9Zc-L|8#W?haHK7*No3sAX?a`Ruae2pn1Lkmcb z^h&_1SGE8_^GrE88*-VGf&UflB<-ELm&nbq-o_3X#oYU5nm|c)n8bP{A%uYm13Ul+UkdP#9XP`b{o3^0w{{ zCX__o;OZow31Y!(ifHR1?AjVn>~fu00sb@UG7_UgDQA+N?hbL+UKWnrYLuBw%a7du ztJZZq`COl8L0F|@&`9}q@1eW>Z?GlPew+jHb(XOGe&A1%7+ z%PZX}Tr#zH$vkH;JfI}RrVK6v?J*E!Y%jRH%TYE?Ryor?9Ji65_U)mdL{!~^cYgFkUrDZQHse`RMjiMQj+&tJP2DE?nXZNdhd#DeBM}Y zLTI#S0!ipz%`Vm$F$k`5;w?704$3$tobB>4i7(vVa`!U09hpV)53Wi9N&f$ruN{Grc25S5t%4nXK6{$xRUW>~$phWUEkhKT<>cQ=BP`eH92tE) zi%T5lBE0JuaCyg$T}TiKdT#u&az(Y*4zJf7UWbT1$U|yF18&sx3B$SoW7{=#y(+&% zJ&(1yTw2dBwz!Qz3f2ecbXB-Q$|kgzHYl6X{qB^4!UQ^&F(jG~BBE(;HgVV)lv_^y zSwydpqRIg*sx|z$p@C&|M?M=M+26*E2?s?alXNKYA<_QR@Gh2S9$h6}oaDG-e;=C|2p+;uaC|?{ffe zT1kaA974pBk4_NcuXefw8BJ2SqS%1ml1eP5d+#_=+kXm92Hyj9i%;pFPAFZu{lKzU zCeOc|a79e871I-@Gf6AXfu;$j;!cA=DBvXIk_)PYV+N_Mu@1%^C%IBudcsVR*J?=j z&1L}1^-Na`riT3#`El|g#yG#u=hM>~P-3swygg^zM0b@S`*dEbdc=?A$(~9=2-Qil zwKyW=KQNL)|L4I&8JwkjjioHNhWmQ}4g?na8+vnXP^@H`SlDikkc|xXa?i8U1YirH zXiaWIEhbJE*Xr;IFmq^EUt7ZO_vBDI(Cs>>umv&KKovcK(08|`kY@Yyx3uj?!DAyY zc`Hm~p=EaW_N5|M553oG0nAsRbVZpjy4|2kdX<8WcVI`?S&@^5*c?Py@S!c$p5RgJlT=yYClQfYuwOB2bBM{pz@@C5Z(bvc|!(uYdP-UR(nJR+W#Tha) z92tH8`x69gz@<161i;iKDOzBvB!p#oM@V!jh#0NGFe`gd6y$pI(elcR@bwFo!J?fY zGQbb>IHn^8!IR6PgrdX3v*#{qFEUS~&vfwHhmm>sj|HU z$@DKUfma`t)=zNs-r(JFq_1ds2s{=y$|Z&82=a5q-o#af^EL8_ro}UueIg+5e9fl@FsYV!9Ep zKt5n!ZoQwEkyN~t7z_j%QQLS4m|%%S18ofwY)TRI$YStoxAP||4{|2{m_Wvntq&@e zsNcez`cg(MeP|v7OG|4qj{l`WoYRSlkX+Z6`=5@y=5Md`=g+Qy&%BMVr z_dC;$qGlWRS$gF5(qzHNirgw50i@Y+C1nJlRsw;F1hTOxm`#an3!c%{05dfbS`sP1 zabBQoi_iO?PQL1jGHtk(m1T_H*BWSBr)zTh8}0b5{}YGTu(eJ7zJzGy54uc~X($#z zHyc|e+i^5TZdx-Wosi^>14I7ZdZu+xwoDt>26%_UQ2>v@`dUxHf1-sCc$geLuH$#g z61JO2zFytr9Mla?uWCN^-ZTnJj)`kV{rC?|`GIVt{eaJkkc0V82Tp@Cs9H?_8z}^W z3xfggU;}^*Y%=3wH|cOuJuMGRwJY)%i&kWn-;CyGltN*EujD1jcuWvHBjdX)C1D?r zF8s^M+p5NowfeZ3^@<6MeHWQ^7vGKI{F%VUqHSw((AIhg_nJt}r<{X*%IC-1Kp{By z#|1X!Bew-KT3#FngrzUl3wcGcx_AZEkM>+<#KRu!<^_Ot!z%uY&!m7dA4Jbx{hp__ zqYb{I*jujX5yk~nCGH`YD_qpf2D_F5d{iEaOXi7dI)`L#%4mB^3YfX1lfSM1P6Z;y zvS|isHQzuJ)rk=J#MCqFI-Z^QfxCam|C`+tchhciIDCesHbwm}m(~dS7_o8`N2$&e#x_Gq~GLnB-IQ>xb-$ z!K}#hs@s|~8b+GRhyr~nj&NbSE@Y~6@|0s&%2c%V0(*Hd(HEojW#4qy z#ny=DtTeRJ?@OkfK$Pl4gH?#n7LWYPbV6Ii*|bylUuU8L24ZSm#Vkhxn11I(6&bAz zldbP|IEB=rbJtn(lFG@d!iep*Y>ss#m<$P@1o(e-Nc!;ataV}$bUPH5Rb8f9Klp(w zHv!+YaL*Cw&A;SLzrGj-55TKoKHh5^_47MxN!vYR-65}7gsd|;FcGzOp}U4;V|;cJ z_O^-$lgllcPU2+hy7ANnmx*!enCM_A&_Pw`t4~s%g7Fy;5-78tz9o`1bc{+mL?x9& z+d4-(98i~5E68lz9Xs7gOw5$7)?`TR1J|CbQQd^Fj?J8%qvaqB$W=i`*mCwaL9tEl z1TI!t)&c*DkCdk8TW$lD1~IPxOTyt$*~9_2616nd#@98B08}Dx-ellw$*d+hL~YCV zT~#_4hJEk|hlBon2FXKNnT<_Tij+dT7Gjjw1WNlslFZW3 zI#`*+N>&^Csj~tI5=n0XXxMo6cnQI3*7rR*Y&d`B?KZR5y~!H{J`29}!d&Qxf#asn zV{Ohn@0WzW#B!E7*A9?)q)X!F`}QoK;q8Nfi5ib)!Ssum^j1;=3MVtwsSc2-G|mvS`p%yDO|<=h^sadWNUeD69VMG>N!g zXCDH*3qtl*q>Qp~Y3?11l05Y>(_85QrfOY4BO4r1%QO8aIphL~(65t?u7Y@rbQX)q zq(*Olt+;I;>)FLc3b*ClLN5TDGc#~_xn+nMeschoU(~y8ti3C>rI!_$!}L%CtZjEn_;Z;s_QJH$O71>|Un_`D-(!m1Ia{Bn|@PRGjphg@^Vi{-|GW~MCO zo6vW&z=SdueWB(=O5gN7*nKaiYdU2o?M{3vejcUJYdl|2K!C_!)gwew&2wp2DFW91 z820<8JPec<-XlHlon@lhxdzde;!=LNC22cQV{No-q3$eD2bUe>%X&K4$vy!3O3Q6$ z%D7wZh;21hoOywx<4AblZe2df;A^ceweArRr}*I36 z?fhoSdI4>F=|r^%x^IIG+a|4|8&6!%Fbey)A>N|v- zvw~N`3ZSD|Pf3cF)ckrnZY^GfBc|H|Vj}S@oV-eF&@rqn`X9u9Wao1_`PA&S(Xo7m98wz`u3q6%t-#+{yvh&Vq&Qw zR)5|M3HFk*nm*>Alx2$rc+luA<2`&8`WbcjHsR=^FbVV}GwThE z{~NzurIZ%auyZ@v!+%#|h=?6<%A_CE8ulGr3o7)n0koj~*yGFNtErut*ZukG(G&*| zRrexUYt4CBlJ5|wq^m%+K)E?ktUw}#LL`TCb?J@_a#SN%u;V*1xh6@`2*Q%M@w^lir zA8LAes8zY!UyFg0SK_{K6Y_#Vcx@QwcR{^bxZGjpmH9o3!cK*Y{{rJJrKRuWr7H-U z?RS0CogBvOshKQl5cEY8v~NPR71_gX(%fd$iXNGSmwVz2UrL6ZrTy0(T`Rt;vxAxv z0WUA%150DVgG&8DD1IDjdwi<_BR0xYm>N~93oR9=HLbc362?|d`UOFFOU}~27y@7A z!Fk2c$0~^x4HHe?HJ0(HGPK!+@6DWJJj7LQ$ZR)jtGZhFCT<5ss|fhzEOjA%+ggw^ z)pdMfaUabmS9Lb0_iTaVZdYX|?+dLBPd0%6;`34Z|HbXoaZ}n23E0 z!}rO~rI|{%9Plf7mp9h&>v@z~$2HflSU3TXz#w_q6qTmLhOU7N_Re_0unspCQ_LZ; z(olVkX}{cvz-J3>ANEdA2_spGD@P5>Yc3a$s?8@nOWL?)X_XWmHfk!*Z3Zy+$+Re3ugpl z3GBc}PdxFXy02pfD>8Own>M%uPxd)YAR1XHv`Kr4cK{iGUFEdSI#E!j)@YJm$Nho*RX zt8{3S=#sLzH$;jwS=e|6FV**Gp68<^e!aI2~=bA-wXU)wO-D zC3n}^3W&7E#D5|^A+c2FpU0@Zg%WUor)aO@Fu)d;;Bzxwf%CWi7Ox0Of%r3j>N^~2 z2*6|GQr78 z_tDZsyh=i&`Z_aZ4%he#${DTZs;?J*s@APT&xrN*Q4mvru2LFH50(Yk>S7e>#HFdnwfAl7KS*y6fe$qTxZ|Lvyf%hq6_0)ic?p{DvJ45bfG) zEN4G;KY~IB#!-N^B`*d4+!o;0q+`_qK(wfv2lWEsZ1&+uNd)c$^J6_!KY7XWB6M5* z!hOkr)We#ycr%T;FNRQ~LUFOzai%~dz6+kH=DG(3`RgrruOtLe5 z7ylV8_kZDKHyL$-X4BabUM=)j=U9jYD;3gsU_KJtF;y?4>Mt-CcWDCP`Jc{E9Or6={F03$2mim4_j#Bf}K zR({31%^TtfAwBQ)qS}LtBnihK<5>dsA@ym@-==;!g3MjbFCQjl*7K)(8^P?D7&vm- zEQGLZ%5)&|d`MSei3*e+?KQCh5hk7JtCNEU`+2*W0yMhC5lFk=4-e|q&47w8x-N9l zUlF5|9i$hA?JkJSW(!+I`nyc2X10@J6PG9<-_hG76Ovx7e_6p5-O~iU3j%RM z#W#8*eRJz$@FnZ_2a`$;hJTd(Rd#IHJ0H7DsNAg^554!176e+*i(&^9wpOx_P={^r zuDp>h1S!#HFEQC_9D_r2FK1LcncIxwZ6wtIs2$S~ebR+T9#MCr5cQ_BCP4D*!jJ8T zw8QTz2WjCbh_-L0`gzMPNlf|JV~kFSRK|y+;}rrb)w-$pLXUt)M-Sly>@I0yr&hvK zM{&5YgJnc!+kqB1<~)xmRFypQ5n1Y08_f+JvGP?H6r3up9J}$`zE!fCanCq8>n{*| zk&i{2uy9BS^&jGDnS=)wvY`X%wr5%J}ul%TT)bQLpyiAmF0D z31w!yi-|ccii2wfaMpOld6+?oonDgI0?Wt=xvlmmFw#bF>QD%gt~jB5vgQcS)S^%$ zv#9>Pc`!jaD_Ycljzu-QplZx6qwSlGlT3*aa*-JXI4-dM-O%&SrEgm#Bz6{j_7Eb# zYze3g>ls#J=a3XFU96NCRD`+Ug2kY}rrNz*%VB{i?>*7frAWOHbVbRUsMV%v{7IO| z2fxqNm@3DcFTh9A0H;q`e4^=>6q|3gxTzNST%ntYdMl?c5D%l<>@9XI{zyX*MyzXM z+S#(Ns_kzN{#7{lg65A+J>uy-_=E?p3Jc>J)l$TWQFyC&urOF{Fe{-b9PFqe-?3-G z#TLOc$lJ#!er-~-{Rah=v5WHvM^JqrDoqjpP+B1#^DFwFh)B_dO4hO*CMFqkUzvcA z)hYLom^9N@?@YjzX7Jk-y#N-4TrH^vA&iKMVl_4bU#i9QF)RYktZ8}L|_#xcAvNWGl858n^}mC(5fw& zlhs<|feuiY+X99wqTeoL4e@2*Rh(XNfd7=MuJFW;T2wOfXRlqYx{b}(s_NFEwzcB| zGmGfPrTJeB_G^Hfm&==DbYA<2#d11D3{N-P>gF>vf~(3pm0_YT)hpFD64lZ-e6hQh zr7+r)JhJ>eb z3G=BP!|~AA5?k<)2lfkwN{q6hvsF+k9kO~*YA~IvPmb#~_8mQs#IKESUr@FSkTRd7 zi=7Tf<8S+*aSFnuCdD)N3`-U%pyT0207bVvD3&e-bC(JHVEfU=F8({zj_e8Mu23@R zOB4=-|A{Y;!K&a2-I9u09LJDvY>72erk*xCkx8uD^fCIzm|#UrQ{@cD5Bfl!Qhs@h zX-8%M#Z?dVvezZ(85kN3Bc{E$ z05Qy^8@rmW^6?7TXz{}~0CRoz8&Yp9T$ltKQ555ZHplF;qZDpN`YE89lH9V81&b)~ za)3zIr>!DUaH}fgYrIKW8UI$k9ri4Kupe90iED?)|2tk(f`9-3OKCxxBFBLO1P}lJ z{D(XQ&=z1JhJcI=z)b;c000933IA~T{;eQrOFyk^OS)o32LKVT;%fMzI<#0yfrw_%&vJ^H5K!N&aY1>?| zL+CW1799rZsw`jp`8`ZNuQ7TutP z_Vp=+m7#aTo7&zf39E{GMXPc=zbR6~oXAsff?}vmq!kD_v7OL8KUiJmIR5^F>z=l| zg2|?3XAlP6IGd_P@5l&2!FpDE4^Bgc9}L?i2e59pvb??D_K%t=ic_*w*A#ZDxq^rE z>;^7gdu%G*@$>_pzE;lT%lYVF=RxCWKA_~*HE#_s?79kHFeKPrz=cpUs^c<}i)Tq8PO# z&$>%31Z;2kJ4b-U;Y>dx`2G@P(5gxY(+YhHHeJ6vRZYigw#5!7lD{WLD{uUu&(Q&h zC%6RWQ(F8qR)^+THoZL?S92a`KeH!!W|lU*1E`Yzv#lekT`N=L1hjP_D&#?Ke;L_d^r-kMEbr3xReN)7(^g7OHgk&ppmh$FrBUKgKxBfQp&uNC+fRG+B zNA-A_o1HkEX{rCUtU%S_0V+HaUH+YCr}lcw?IJZyuyw&4S!oilI1cBtFgcSB8nsP; zQ#ysvS+i=$6j$r`$X#9V?+IaAZpwh0hRZ^5Y_*a2z@Y02oZ2HSD9p~lb}D4g(Gs@r z5x=lr69?~+lU5EiO;43(0@xf0z28M~p0%1CcHP(<(HL2jo=Y0Q{9OUd$NBW@C?#IV1IE`es3e_NAi%Y$FH87&e zEk{Y{VRK2`QaXi?$BmR&P+hWJD0fqxKuMWQ$EAug=BnSEtXlWXPSN^Zf?~w1LiYbZ4|KR~cYGIQX(v2Ve3v;|q{fj^>8n@XOe8 zcfT6bvfP{GDBqlt+pXb9%w*v?+f25`0L7#L-RlGKgpn}BC4i9^ z1$5EC8d-uTGr&0^R<@SiqFAf`$1S`q*51Exl!sNyd4e8G``%~b(l+m+Vzi~G!MmHE zvbI_efG2_bZmKCn6>QhuEQYyNYcozyJ)lz=13m0&?-rD!F&F5waO-zdL>UWPhLnIx$lt11j88i}hB%hOgD{ z3Mec|rOp6{Ke+}(#LXgsjIsG(tOXa5bli#je!6Jm><9t2$^k>6bDP zogj#mtNlfE3YPn*QXj?5xzPmM!tFcrPfI;p6C!YfU=)6$wpmJ9$9BB_kCkwb(o3UB zbw!G@yU9St;y^`JSRaxr?w%P{$;{SS42sik-f~T#w*$`|&&7(+os@qySDCr1BuV1D zqc}O5xG=$=ajZ1T7*1eTggCoQFOFL*tLZbe)hX##;Y7&nT z&l52$eZe&sx~gKoV+MGZ-@c#=<~mqnOsy_{KFjO+M<>0wpcCS9!ITV)O8fv9-M&q= zfPQ+KI_Fjf8d(aDeN%^x2wonk9o!NgFP8CL*jVU78#bTVo+VB^LI>hYgNpIG2!=$= zD|pQxX$C| zkbC04YZHzCM7E4PE!Cz0F71+tVBqzb*<|z8^&S877D{ht7q4e$gpD5nML@d0s+%GM z>IGun89@bKULL0bCed**SF+vILPxMx_>xcR+@7lpLGH)p_QZ^A9)d1;#WX^01V0V)}mgkOe^i%nSXW#X&As{AM z*>Nowe}h1G=eJ)k z!cuFMGoCc~*d1v0R-;r3c~e33+dQlrMSiIWVrPb^)ZlmBd zJ^hSo@%wwIH`1R(Fqb)yYr_rzl3pp8 zi*F3haoVx!5GJm_Xg^Vgu#X53qr;EGgvEz#h86{Lh5P~DrIq|}01!XuvKc7rQo0bN zZvM8uof9uUK&>az{N5diJ-$3P3D@z++pl5C`Hd&mavH>_e}g((h;bM*+xPfXE#fKa z-`pEpj`S*(j&OgQfsOKha!uSOkx~S{HRLnkJ4S__KgWbH_e7XKB_E}|=gBV{`iOe` zNa^AX8MD^?y-c)6HP-ETdq1C0Q$!{=@tySkb@;Htwtr!b^tK^AJ ziSm@dv~OR|SH;R)|6+gu;atZMyKL+nj#DsJa?Tk?)a!e!S!dpsqxxLaCn$(NiFLF_v7Zwe&jvKoeApf$s&HAVjnKPE&vEC?= zBW13|1SIdQ@Nwk-%P(dOgI}=S{v^*wy!GPW7$?|Q z9cHcvbL#=mM8#M45)0DmH#}9{Et*s%FObL8E4Q=w-ew8P(1;)*x>u`yNlqo!jHZeO z%DQl9M-l83F_ouf{_{4Ne+@uV^yL6N_xfo0v^*5*m?cf!D++43CxlC=I{B|(@y9IR zD3)Q=Za5X`qc%iug+V1S!Q9PI_LG)%p(zOh5wET=3{HwL_Ekw(cQ-Vz6>hdDeDOsp zi=54DQ7yT16hsa~(f+&f;ZfAbUg{ecFb|fP>zgT2jHMd2ya~&Qrh1cQIricZ<-Ov$ zOV@WApxQy`S%Wq;uGr4#YTtXm32>kqnZ7mL0)&XYTA>K?d_ix+P}hb#+`3l~ z3hrCRD5;?XSZ@DmCf#_F;Jq6R-$S`$*1@*-M0tJkZup;CU#+YeCxs=yw{neZ2EU6m z&M4jDUbtEJQ!Wgf5uE{i6QV>InPHNAf2T^*pK7*=JHB{#+_!9#x=b$LU|uEydgxBE z`vhr7-R|w%I9_R_8|#ej*N>63bVaIzL?|-W&I=#O(XuCZ;wQTYX^`~K@yj7N$C5s* zU7vJdG+d+o$Cg3MQ2jPIrc?C}+X<2bicgd^!l$IiRx5QL`mRMNd=5>WlM)Z~g zk97cou83_AN&d|o_%Vqh%goP0PNA5DaE1vOb3Q(@{683##}10z@((;2)Fk9yMdERm~HN}!2I(O>vzOZY{4 z-AJ5`z_t;LcE>&fB+zMRm!%n4UNHI$3A^UW@|t zVvAP%ieM|3Gs(h|nV0_GVjlKZs?5mA-@<7*spjIyllUFxtqXTQ8rIlvZhINM5Y2qz z7)UCmtnaS_7jZF|xmZ3?o2 zjcXT0``6t@NIBU~j;{ey=;b27sNquuCe&rmQHwB)6yvlTR3zw`KK}L6=qxX{7@fO2 zB+5lnX_8-|c_jewB%!JUu+#piXH0A$Hc0Mq*SXh0}SvvYPx# zKxmOUf6xA|g)JYN1n(ZzKfL<+Sh4OZgSONaD%_^~X(G4=mI&g()!lWk5EpD6v`fTW z+)w)$YM8hfln>l5=u^|OJ%N;`ln0v%_mg3eH>>c(cGYHK@L_4Id7bqt+W^9}IyUV@ zJJm8gyKa3ON)0a)o1Qn3{ZCLhc7w+hxp~*reK`OQ$|>3<8df0Vo9=qolLU_-GJ9Wh zI|!v=Bq0okIw7po^$4#R@zKlBk$BM;1?var0+5p+GVvgJT&LNBBePSG14Aq!e+4hd zGb;Yhkpn#eT?-o(l`udp(iObwCaR_7LTBc9da0%}B**VeJnPJxm(5NT18RZATatwf zsn!wWWEfQ&CznfT;+{F40SJoR45>luyrFn+)7m)>;L=(1*MJTXeITw4p^|jFNVgK^ zT74g!CAp+99_4g8X+QsiALS1=Kq{wpTUho<>pI`vdd%UMV$81!#pAGQm!!kL_eSO_sgwo$Cdd{3xh_GB ziduZ$+;{D3TWwSFyb>gu#&!*UK$NeX(`E6VFkYjbM>1zh4(PASeb?#>6;W7v=gLD} zbeGprCD$xaG3M?ouzEJ1HlSM`o@IJU%c6d53?T3z-L29B8TJSqW^{Y$l_Mq`L~3eh^>3Iyf;lN4zg|8~T2V&?x4rZriS;$x9s zfA18B2G}2bH7*r6Mf?n)Pi-Y`UaZ!4M@Biytc$**g|$DO8dS@8$}%gv8hH3eLnZuk7V{!OXG4t8^w1czG_cq{5p|q zVDa8z*p<(jB3cX8!%`v`8%>{afuFH*Nin%B6HshW_gthc7FbmW2qwmYT*1DwgWT>x z2kqAn&MJ*uWE*^x!ceC?&$rpi~9ORv7>Qv2w+>!2t z^>86}eLQzFsbL@yc#R_4d26{JNHYiuj3Y*2M4PJC21Q$NUfybkzyXGFvg)FMD0n>SE6O(6mtVN>Vlne|y% zT`YSV5apZtiMyWM1=Tl!x@P_o0#!B$u!t|(QEK@^*a zGau(>YXuG`b@A2gO>iv4lhPa-$=Lo&3=mp2gzHuX|Ed1*wGhmNSg>PvR>Y%7gN9*@ z;mCxN=rP~ekSk+EU^vq-GS*&XVt#nzfIdTr{XCuOcYpJDZDGI@Hf)qj5cDTC#IeU; zb*>!9%Km(R0ZUk+08yG_F>$D=?V|Wr;e0>Z)(l<({T_}j)+79cd7Mc24nEF!vn~;d z`_*6zIbI2>?rOmmB5Y2UAix&Jehh*!@BjV!=~DVf^J5Dcp7T%@cz_n1iatsEH&1-q zSGWZH%?Dc;_1Nbv#k}2%Yv0LT3E|=EauLs@&@(nQC&%E<`rX3ArpaiQsN4!`!qS%O zFce=zDWA=9iucB)R(#E8u{$;$>K$K$W>vqZiJI}d7Fa6g>ms!E7ZXY3$Hcx^CO@@A z`X#dUtCn7-#aC=zI4_!a+#dk~y>+gPWywzDMU;$IFYAfd#}Zz(8KWEZHn9qE7v-TSz2 ztaC`1-l#iLEGbibb2zNqHEqCYc-@rvVw;Q+LK;|o3i%elO-k~fNB46z37@T~*0>!p(rZL~4Nt-Md`Apq5?3;~|L3X*h`I1#TT{m5R?0MaV!jMCgcq^EDl~3}Lrew7nDgC9;)<-hp=zass)$D~A z_ax`NOLOdEVoCK0HHZe6fwi=Qr>K&!r_m-m<$Zz6MK4p2Zj-8p&p7Qm$)Gq_BOr(hO&AK3fzRP&!(?vee#t$QbUX;y%*k^o zCre2{3b`(7>fUP-n5cRjEpioK&Ok~x)$G**H$_RNFZi}`YQ?}{UI_dK3ZYI-rACj_ z3R6*2X5E97h;e7bEx|sosXM~!dJPjI1tD9@^a)oeW0j_?{!dH1?Rrhtw<({KiH93E z(c`{=cuZ~HZN&h2ai=B5Ou<7loMTKonulpu+;hBkx2zar7wwf9F3aG9KS96XXeFFs zdX|T0Tj6({#O7tEo`UzdGH@uZ){abwvJl~5wMKJ{((pSu*wKiUFCtI%w?&Qz`*lM8_@`zFgo7n{0oB}A5lUR*4hC%_W<*_mB$|*12R-q zs2+;)vE^TIBe~F*`jEF1oeVt&CuxSZpOXfb^>>HFzvcfmo=#tsLtzeiws5c&H3y%z zpYRB^nALARi*j&=r$j&Hzh}75?C(=3b6?k5zO*)s1P-aKgTa7g1Qn13wvcLdeLAN@ zsYoYByw5E9cU0kHnSl{KYxzHERX7F=pVWvjy!b-kRXR~tNeM)i&n5GoPyms|bpx|n zaV7tB%@+j_ijD_~y#B|bz#Lk_vVw|D+o7uJLI7*iG@Vjvhxq@uGgpvJoD<>3F|SVR zWr{p(eX$}F#&hc{=N3`645G?6Kt?|hMord#I)B*vk^S9O_HNqiJ`U;fm)h%CJBIYb z_I_%kG^2}{OLz3Ime&by_Y8mAo&!|6n2=#k(f>S(Dslj3j`3Kq+9-v#j2)~0^Tr4r-U{kj6*fImPOUEK| zOXhqq7M{?Ps~V>-JTUZ@=$a-vq|gSrODmv<|7+9vnyGP?DHGFs00mD=t7CZ{7D+$~ zR7M|wJuJIG9N*NZQ;7ZRH8E8@z?f?$q@m_(L{KyD`BGZ$t#pf~UCP9la5F5W*}1nJ zM7Ph*%ggCjY{>$-{ml$U3lXE{u2)vBS%PG#1J0%%0K?BrnzY&g!;76>Pjb~+5cEl0 z_)_YNX;YE8JYUKT_c73M3KsLEOn4#;##H2&I@hAZdUU^!ZtCr2fnzR&ElpZ+T{(!t zJ;BU`7v)JH#{t%mcK#H6_b&FifhZQZy=KgzM+!__^TLH&JOLKKscBPTFhld7nZwqZ zXs4bDGWGIB7iKu-lf9hUZJ`RAr+>~gNd>x98U;$ep(6_8S^zz3*?iIoQQK1?u*|% zA*@H1jv4ANzwB4ty1_vGMM$d!;d9U3w zs21mmr2Bl}n2sXMSxBkoUjbaeksJuFezKI1t5JRt;P`NF0L1+un2jkuHuQ-ETyP;s zk|TmONPhea{>k&GV$4Ev0!fEKdhB{({WpAG1qef+3hK1w{v{E)CT-(BbqIF0&U+?2 z-H|-=rNX$jHK_Z%y_Aa(hihY%^gR6vD=uvK!oHyS8YR_CJ21=K7}s3lNK1OenYX2! ze*H3)6o(qZ8KT*?9x+;D-5$}^dEVKtd;ZaPYT%-zH804~36xjj>V=tk?-C)=(x0sN zZ*QQDwZHX+anc73P=6l)d<;hXenK(wxvrehzP%t3dv|@euq}+#mkb3~Do6dx%-WvA zX|PZqG+s)R(hi?T`L8k`)g3JDpS@c%2qd28N!mxPZ9=_3&%b#Efu+r7YW7G+LYh3r z1NbBY zQKAp)PWVecU20njpjG2HVzB_V)RwT8mTx4d(IS>c;3^DjxT*Jrq;yYCO0GKeQok5z zLHDEiUfmcarSez$8ZwB2`7TZY(BhM}DevDR2Q950MjSLjh+TDA``@blEzSKw{TXT) zfa}~`_~hC&n{+7zAs|;!$6eMbOkLakSjUf}JK8toce}$a>6T^A5tN^%yxF{1cE)ND z3zMA|PG2+G*kt{Wk*M$T3E=SyN`*u`lLFGJT4$TkR$H>o2R{|`_QBTwaf1#tvJS_l z5O8)gp}LNMDN~iRF_1PJAeWt%v$bwi?PtC`{QU(B#91=TO}EiIa+22FIK6Z>Nr^kF z$2%UQ2)n6)Zk}R`>bdo@#DmKCh;N^LtE^!AZ8($cu5cmXLUj=>&m<@yl*o~s$&>kKHLizmD-3O^sg6D{ zy}*U=`{95SPbAu8s6n%dC+0x8PPXEM!2=y}IgRFKa)iu6*o|AV z<4s3_I$`Cw_XjyY&vdt#ZB{-1Q_9}JGlE<3DnzL4pkvuPta-YvC}j4QD6J*1IME4|3$lDQ*)luCz7=3PB3lcw0#P&0WWFl@Q6x+mLEZ4pT1H{@Kz+u%RP0=xz3 zblpOMqNw!PH0aXg=SR7;Ct&$vDT`F$St@+ut?#TIm|8gF5tt`wuQ=XQH?xlTnI_HQ z>jGs9Col2amilvb?8(D*MIVS%IK@HbRj-N)xy2ZoBxk8*cLi0~a)(J#! zhZBU$m!Wx5GvR;2UO_s`GXy>8A{7FvO0Q7uho0O%R zuyFPW22W#vYc%0aB2Rr7t5jlO8{tFT2I-GjJ|eB1I%BDYB-7BP!xwD-n(7{JaZxpa z7w|U&7#od#PtBC|z;ksV$XE3DX+-|MTV$OS%jI0h0H$`4|99t3Up?)TEF`#)!Rpy` z6wT}SWo!EbH#z8i7m@jv(o6%{zd^3aEh!(Koc1qN@o5&33WVODI$M*x5w6?3#+z5` zO{z?3`daNL037u|Zr;wv@t3WsV3+G#)iC>4tL&IXMZmb~b<&M4t!CNj33&Un z49qCFs`5nJ>|IKS5)&N-xBd0~G(Rs{>3A?7`Z!4^xrdn;Z5)I2camLFy8WN{c-v!L zO04y*e0!nK7m-E6*gM$yhJl<7AF|mQfON&V1Vcp`%!@Ex9%>1nzM$jO-G?P*X3`(O zDvSqp-Xs<~-}$=cnb7TFzo>rbnD`vBaH~GYwP<9H#tgtv77|VxfA7(3SPIzYI3zod z!bjNKYC*bEOgY6Z!8oph;(ixi?BiPEN8NIdC>#*$u*1~dUAlwG80-VItax214NvrI zb5dhBfBUnKs9Z=}21~kP)EIY3yK;ehi?TvuT-6PAy3llNE=M@V@u6X?*ip^uG@btO zM#`HpPs4n1hTp*nKm23_+==d7SZjOCr({}+Fb2~#Z^o3mtDI=nAjB)Z3qv}rqRW73 ztrv%wqEg9GzhQYb#jVc!c(FOI`e&Nc;w@n%*F}cAk7DGMk;j`qjDzdo7Pb8)q_7k_PyxWwIiC!joELw@&0Q!f##6%@^}G`Vb}}Yby)2 z;;9)9f{ee$5x&RF!Ni~%fM7r&)t?Dx`|X{4GWzjP5-k9^9nS4;736~(ylTZEPMK+6 zG8W~+;sWL}2A$HK84NZqr|pFH4H`{xYm0LHW)U@lZry-Y3aHr1M?~7A9n1~c0v^ns zaY9H{#F;W=#6Li`3l^jfk;U+vEvKEol!HC*>iowkQ)?HB1^`;sqj%2&4u|X~p~wCp zPym5~U!Rj+wP{&&7STkR4Vwe$5i|RyY5erfo2{iQL-iLdp!SNhB>x!; z(l|3yo&Ru%n_VlT`tplNB@Tp}N4LeyRlo(U*seC1i;ngL6b;+1ka=>|@RXOZ%cuXO zduH7AR!Dny^o2DTK|nh55e1e>H;m?g*J85;<;wx^k89O9?HpNg5i63@Kv&=8@CbB; zathKS@*S(SkqDBjvzEKYA%yjIauan!ij}&b7dC42|G5#C@)5uZcx{Ur!O$h5v-Yhz z!XAH>BdgJ%0QvBT?C7FhK%ImRas>BVDe~!|Q%lb(CO8NjHwTU3neNCC+);aFcy^O; z$x^^61~<(q6Z>wmY%QjD_d+Y?ipQTqke(9ghGymz50YNi4-Hstn8>!~=Sw58^v;6# za-Ne=I0Pj~rv)<}_d7o?m>)w-Ky|2Vav{|@74*Mu zId>~qbWz1h{h+29{o?X$}5wt11hT zAz-V9_qBEBJln3u722R+gGF5f0ivgsfD6uW{ z%(#olr2_V-Ie1}eg7_q=&k|%!5Me(y^X)210mCL(gL><(4M&}AqFQ!Y<({g=6t~Yd zLDdJw6 zsQ)?}pCEw_@(DiM7?#uGcg>YD1B&Fu$sy5~+Xc^x4X&f#ynj^0av-!fxgy&aV>?l3 zc-xRDl*W?EIjwn`KKxt)m(~$%h9{xVe6GCtn{ozaJAYHD7h&>ln&2raB&MpmPa)g>IwaP#;9Jg_E3r0JJ801T zmM=-MGhNe9Q$|2gCNqp*qqN{_g18OfUeDUwFG|!`CPEjv@11T%2o%)klrZt#x8rE+r2E7^^(`NnGZ z3|>MT25X(7X|E(Kf5KBZPVJ|EULTfE;^5N!0#(u{8VaU7t(fM?!Iv8wQ)zriNgXG( z>!*}7VxYy6E>Vwk4LYGF1RHL4W%|vKxW13bh4L}#@WIt+_7g{}T1iL94xLcw<~YI- zpBK`jH^ka<&P7ljN5TY^hg4RlL?;gfV0jE&q2aJtvd2H?6ZIF>V4qvhHO{p* zopTW=L}x!uHl^%TC{m&e_>h#y=4{|a`*F5zZCABi&q=4bwY%1n_v~wke|BW9GOK~8 zwiR(Xvs9f4#xfJu%G5|5c4hjG3GBMITq6W(0{gmGAH$j_BVA4eWSAI*OQ!V2o$@o1 z?%vAUkk13#_Y7n_oFxMrd|B73>;$`VA5jB6%O_O7`@Z!52+@A)Y7Fg%qToquFF;MBp3EVb(EZtcI!!O# z0km#-Q5LfElIY9UJvntpz_qkG^(ot$t$cO_!}~CKa`X9A@yce8JV1iM{bq2S)C*6= zPCNq4#StTZDPKs9LZz+kI^RGer=fI53?%3eCVVfze+#-d&nrU<^$X6jowCtD^3XNr z$WZS|i=HUFg4BrGo{WM)xP4(aPwWYwT$lB+OI{)60#(1%RxP@pI)4D*xTGcx@ZJcH z3Mmg=r4l#DeeT9Fo{4K=({pBoJ=6r7C1#cZLA~fj3Xm0r2X+TqZ4Zn6y~j6g^*z!N zw#E@wAZ5f_Sy`=%Y#m}EEO;)&3#ps=i^-itZHbWJ+8AcNR)kt>n~kAlAss-+BR;6< zX$VSoQe>4Hl`QXDE%&y4RWYbo>u{_Sv=Gdr;p_*RmzQfmI2;obJ~GKCOLriD}^O+qy?Q- zfUgS7OXb=LdUF)<*$T2MkZodZ!IMGSf;Wt_%dOoZYPly$XuqfDmyu8SYlLJ>^5y#k zuO91$W51BJQW4UpD}z?LHb$K|V?|Z9PK%8ttIZ@5S}=ozkcAOvl^SQt zlih=%aSG6~c&Q-SoTH{X2}ggtzGW*d%v^W0eYxinTGYJ>`oOH1G)v3kGqWP?LLGGc z9QhY_V6MqpI8JE-{#9~CJ=OT{<05$s z&6&u(mmJR2+o8QA`x{lpni66RQvpl1{lyzr^&XCY8{~YPabj6cF8DMn$Wdi{K-*>N zN{?7*5*XeyM(W%9-@3nPfYrMnEc5y%Xo)oo1 z^PDrb?xsDcPqzEn+fEM6+snGOeHXI1-Mj}m_Pf`)^TVqXCH)+XF&SP&B9J4jSPTk4 zUY;=P={lRBm+=C~Swr8iNaN2FErDh8%kD6gkMz|mK_}bnH_zx9?~Aschh#K<&jq}e zIMW%DuM_TYJ_08zeoVmJc$~airMnGBs6t)-M5rbc;$;6uh|ADZ1!PR#$%LR2wnne3 zG;3B3brvjuFNfIq;M&O#IFlno`*}1U?yClwmDV=28GXTXBLs3o0?f}yhBkplpu>u@ zY+f+9kZB4a@f&Zk4!cx)k?RzaDjkC;a2IPg$=DYtgG&&5b;$s6X>RN*qeyUNiGb;d zHX0QlF4zsTlk69~M~>Zwe0H|KJ~WkmGWfUvIKp>IyosZ(2W?cEHl~prP@d0u6A?&? z$vQ)e5N9f!y}vTUz#!e1%4vYj>VwHsu&%n9?dj#F>>&-ByTkh%)dbW<{I`N+wXe&ij% z&s9)Euu-)<+6h+Vf6^IC^)o54Nj#h~y4MDip5eW$b5%WH8E=)DvvHbS?%?TYLK0lo zSMcURg64dGjlyT)FluP}KG>Zlh`CXGe7URQu+&OPnR09a`v)r7G#y*l%YWVBHl!hr zfAOcD)+}U42MQbJH`zH#9hHd<9O8Dd-a2X5;2_oigY}E6OK^R1M9B;&R0J6C1>^xL z1hQF*3u)gtverOO|6&#kA?h-%EM-CL!%9qi8C~+uWvgnwic8)|5$F&}2eeLAO3FE_ zc|8Z?_^WjSihFuAePo)pS*wXfTIFS|zVR6(;^5=0Ja$9XN^=c_0LhT+{_g6k+Ft%_ z2IyJq>tSk`7GTNET^f*Z2B7lQKIT!u@p=QGH|*Od@(isPi0afUj@v!p)Ivjn)p=|W`*wyFrk0ReS@6Gj(K|)mtoT#3WGPggLvYnb}PSzoZ2a5 zW$OZ41&Wja%C@q(rO+7| z)mZwGY7JwXCjT08b@Vj=QL&4X5StxZFN8Ds;P8naBFsxI&Il$=)q;?RuAX#98O|Mq zm{?K@Np;t7?~v~h<_XizBvpZAu&a*e$tUlw<*`AwY`QmkMY2zVfB+Bhp5WH8@>ZKM z4=mP`F@UUu5J(j*yjMg*-$qOx?yXYkS2GG~+yDR)=G@+w^hb(1y8G@fK9l0OwH>5O zq^CNhG9o#TFUu=vrE{=~O440D}k=|OPRF7LS!o_t7L3zEy;t@SMBgpp_*0`yNBbUudgo>bLcZJazs97QJ0$U0*NGiF)cc-Rhau}T)nGBV6o zZ!tFV)KaetwTF|I24Y+3#x3A6PJDVu+wC&xS$c0u%`c@Z@=E33rGSX2eYl!b+!Z;s zOUmM}Jsp`7`XI%42lqHQj_;{9qFV^HWoIiYiiZL7#x z5U~75_ADvLt@Nay?f=b%MUpk)BIvQ~KnZWS*3`un5>f;WE6-_CI z^N=wCDYR~qTKM++mrspw*(x3US_t86AjBOlE=8mq^f(=ZUV?a=@N8UC9roPjN70pAh(MkQ)V@isvN=S643P`Fr z3FLVAX^=KPFvs}I%j>yJ*$cHWH=$bK_EL0kGk0%>~lQOc9kz$35gl%t4tkaz#A zqhtG~g#cVIYcVNp11cqFBnl&&AP*s@`60KbhJL@ZWt5+0A8sRRvxG&~3j_0J1!?}< z{e4YF>geeF342w1<*g7}p7toa_#$;I-@XI#P;w)96OfhHCUKR}uv^<&98vG^w}&zSWLCh|}Tm0G6PoI9-jtO(hToIzN?*@i70m#t^S3i$0ryBt{M~904zM zKPKWsPWG)@Z*o8j!A>o_B?ND`-O`BDJPI=i9>&=U=w&6gXQ(}ypH;YDG%kaK-vU!t zye5jU0i4YGOnSk??)I|htJOW5#`Yn5<`$}fe_OYz(VHEA;Pe$SPv1h1YO z?r#au0jQ`FzEWcmnVsVE3)?q0NQ2fe??LNyMBa#Jmaeie94KXj^Ek%K-IAo@N1jNR zbIThMK4*MA@2+)I;ldJ_f^j}a3w`_ZyewF*p+7@5y2R#@L(~sK8Wvw%IAv40@~m0+ zs>r%o4qeZU@VCbCPR3l|+k*fjYI5@L=?g?E0&?YPv%zcAz3eO)(+HoW^Z~RM%v{`U z`MhK$?SMnaL5O}qdE)aP`Hm&7U+3I+XBTGAOqyW9cb~I%n>@RpUe{=3?CLs7=S=Yo zFcU(8%j`Gbl3mAy#^4aJE-h$d0b<~Mn-(xtjaE_gTzReWcJ&8^8yp5I&C4OD+g|bY1oocenhM#$=1drERX?` zNRqp0?Eoefr7~y}kD-r*izL&7ayJ~(L{;0qnaHW@*5vC9P@Z7_2;lry1MQrqACBs5 zHu^`PY!&sbqD60A%y(2UL!m%1M7E4>d96ME`ep$A9;pT7c;k2IVRf+OD{$I8ikq)B zLdluBY0AHe4eTRT#aa%vLQyN>K4x9Xg`USL>Y23VYNYF}z8_W6F??)zuhDWw%AeY1 zi9ZAAtjQXB-xD|%$h}*5B^Ais#KRicqjBA$^TfA>GB77BxY$zmB+Bmh#8j+%{ml{G z%8|$dH#jqM7=WseVqJhCNvaykJV#bPBh2QE*gmB}e!{l=Ts9b0_9_s-_O6YN;szfN zLSVsAXev3VHr|V_Nvl$iIP~88pJYWG4$sGFk~l0J1O(YsRQu^2kH(bo>!_5YzA}N6 zCcHu7aCA2r_h+!wt`Q=MA_3rsf~L!{?5{p-wUV~RcoXCWe(6L32^L$}b|ewtnNS)_ zkFihJ7Z;I4-7Xer-wR#rQw@K}#v%HSWlZ(jq7E;9nB_5t1)y)3Lq)7eUsrLaYmt13c4gtdeyk~fbKee0?>cw(f=B%Th4dm^)@lcuM1mPv+Hr#gH9_*q;stp#9Ebtf{Ee^aIv4gfWYVas~P~ zTLf)qNxL8ffAojdD}_qY7`a??LR_W*C*X&p?3xYlcuG8Tnu4M}cJUhrG+JnFS_`eg zLS>KWab@BP&o)i#5DE9x{ycE**s;JJ=i>CNdHjcNw0x=T#P;+OA9eM)^RF*|FJq2LaC-G&6CXUm! zH^evz2S&&(z-7e!CC?d%x5l8TRu`AUuW%!G*Y@rtYDYXR_WddHMt;^9XnFj8(4Q+D z4OS-X6emorIgopcE$9awg%Qy(kH9MGz2NrLO!i_o(K+4dX0}vSOVXf0Qu#1wv5i3cg=(-_x@hMtQOu{bLJ@=N~O06=jx6spCyES1cU}t7=N(-D7hxWV9nr* zGaIy&mAOq1h|2BR)&Nu6m9thb_AHQ(34VNz8d;-0$Xl32RUV!-98i90k<^ENTznhz zvWlLP&HpdrW*pqSO8A>v2c564SU3kZ9(f0eeTrAQcQ#H z^^Z#ys%??gl0XB`{`~UaD?o_5H3w+)zc@g>g2cg04qR#(mikC-Qph%yKDnzy<$QUXh(`P70USQ2j)P zs}H*n$_6k($hLsdmPCji)TO>HZVSG^py!g z0B?Q8G(tdLrnbwKnGwvKf*4F0giLIl|5?BbkPOSZwlFdVoG(l5SY%HyO{q{EJ z#M~1UN{y%XDDlnRh8-a7FE@PQgIYLSF=EkK7*0MM_}^?#JF-%v(DifN_hXM|l4&UP z5>u$3M_z!7@>pbp6+yFx)=g1n(82d;ZUx}i@0*a%l_$4XkNG9Z9Fh&i49cwOQEq71 z|4v2nM4KC0@8QgvsEjloeIdjM0mK2QvrToAfKJje6xo!9fT$SfY|x&3AsgX;bkoNf zD6yrosB?uB3mX_cbjwiFPMTTU5d**m6OtkbAb(_R(zkF8L|EXmrWoeJb2Kk@_~jg< zH>;};R^&}XlmhDyuh4N8YC@_x`?70+$A##8hBkp+DtoKbPE%6s7CWSk()E{n*td8P zH)Wejf$Fo?s-+%7)?g=W&7sWWYfa8#lN?Rogq&gmx*+ceelIh)MUX!o{^_~u*;O4| za#*+1XnoQ)cv&J9w)yyB!a>YLnDl>$!$TFoiQ3Iy=uWZ}aGG1Ih?%ek6&uCTUHmg| z?L1mb6O;lXPiRcPFr7~0u0kvft?_s0PmAnrOR-BSU+t<>Df{uHvPVj=n*Suf783+^ z&9%PQ6@Z8o!H%N2q7E^0cFctCBRZDfzL?o>bI0aE9gyjum1`712Wl5zNDmAL4^9hq z2C1X+X<#E@bF3@MF6pCZun=ycfRIyH3N14~+$bd63}ftbYGmo)gDO5 z1t3kN6KeV2q>bfwl2>xU$>jpxO*&tkcHYv`@y}?(Aw5CZprPZPQ7hfXM^oj4HT^Re zV&X64c#P3(J+kRvl4G8PQRUmLx)j+&j~@r^Urnr|fT7lJY?*v-OVo-T40neblk}r| zgA>k8`>TV}FAq+@saGg52ZAa^>)PRw?<=$`S9I2_=a6GN&cm&?Z$8w z$k(52>+=4rvlXLAN9&a9uCf`$t^G5c`+SitR}~b9XS2PGvTIjiPXih6%NiBVee-MM za>`wa2~u*JCb#px8`nOnr1(yrN(ota=Q{FkSRO(aA1?M#ru=O!xbbC-vnGmtWdjV* zK$v+BALW~+0P4~jf!QI8?#QU|hX5uBv<9iT6etQKyD~8)6KGM}cJe{&!BLT&Lhp?_ zE>IIR$RjBDg+QhCq}Ouo6|hS%4M#+r+<0b+ddkBy2n}m@CXV@}W3gDE3{r*y1Cb-? z4$)(-M9?f-dK5YpoPPa`;58o)bA(UEzDivGvfxEqUH@TlODqB_Go90qJ77v}1s@p< zfm2xzsRw|)5vtS+{G1F}e4`e|i@GcH5#5<5L;gJ}Ms}F&8N^R>zTr-h$4>1|Rm)ia zh*#jv6Q!eb>fk!^OO5sLD_iZIn|gkFS{))kUSVjM47%N|pbGk5?7Qz)UySdvd)SUy z9Vue33)3Z36V0KCP|(%flbMGvV4@!u+8}t3Aq~g&gPKJDlt5%6-<5JDcFT!>YjN(K zWj}-*1`*zf=u?sKd65Y(w?D;jkt>XJecZ2C6A}2|p zsaNhiaN~6ED*$$&sA!m>=8mZYDer_4{BqHaoL;O_*? zFprw1n;``v1p$Q3gbFAl@w~E0?AGwSY8p`OyxPDaNI7;ynEK1ngih8Fe|4RD9`(ks z-DK>?RULYQH{Q+g0dE)KKKVTwy7ZmmfL#lQt}uSq#Uh(YHcEvLh%C5@;;7189&Ay0v!?CjBCX+G>0hktUJy7 z0?vpGha=lrL`shlRs5PLRvxecyp!X>zoc)po=)5)CXLm-@Z!7pW4)sZm1RF} z%G|3dI7C%-&tFX2cx)Q4K8(F(KpfrDEj)v}LvRfeg1ZD6T!U*MxVyW%6Ck)I5Zn?Z zxJwA`?ry-!PRT!;u7>iKuVABCf>k=KO>U0LezBvjA$SNF2S7Ehk;iJl zS0SA2pYx3(z{pz;_fG&`1#{|q|#q8bNR#W zMa5SdUR$nS*W2^Ka4ww&p&I5Vb(q@9<}4sKZKk9g%%xv`e8Ezfc|FGkR?nVGZi{A1 zReY`sI{4v;!e^1jl(m6`+D@zYpv#wN!PE}NRrN3n7kaD3*@~e1MzrW zdWA6y%8_05!&`m)TREc(@z1v#LYlB3A#xB1)Bwq*xAx8rBnyAub6DdmQ&;R~VL;su z5QMItDNs2~F-Hnd(!XejnVZlrjCX&oHun@@I8PdIvj1~hrr>)9W0y~Sonz{eU86QR z-htTdSLqaQ^W#wNlu*q7n>$6^_edE=I;_@>AJ&6^q`V}X$ za&qfFU56#iQ=&@!5@%Qa8Sz0TcL^sD7RbBh*4B8VLJV`+*r9wN)5?he^UYZ#Wx^rHjq zyK`0s#ADS_ht)OL2asR!8gMy=sQvkbI0DyChoKd~>c_0k8b@}#=I0`k>U>;19ay^q zPL}ZFI^C@$W(LxV>3spv59(9!lbJoJ$c9!>J;Gj9At<^ z4o)IYIh$xvj>wqj#wX`w5F-BFRGy*;ObHG!C362vNe*cpg7{hPTK8*iI!Z?*mjZ@# zbBslpfdz*|#zJ;jJTfxL5$S>h8C!BcsvNVDu{uHMSSo2QoQa8EH7#1~4-y1Jd#b$) zr_T{hPF|*|LHTg>3Pgc86Rn6_mwY$LTtwnI7TbI#Pf)V-ZHsQoOIpQbj?V|yc|#axC0AV1oqSCkHdslJw%!+{*|Xn#6UX)8#sJUNfO&}qp2^2r zPbOwccojr70UU&fyUGJ9DRO8Nh{oYgU+XqB>nAg57JpKTmUTkI$ByYaJ+&wDkdVA@ zGElv*HvPHJbZj-}+d=OP)({|XsoVMU0ALRQxa-(2=>(oEg1q78?HwFrDWrY&krpeMYe%>gC}w7KY0D;E-$G|iKNm>SL#r1sdwy}L z{IV^e>6V!(QYW3-o_*lT!OxwzGZO{RhLPb8;p4dL=*-YsXN%9_2*{)zotcR^9!xyl09aKo!<_xBMlg$~h> zZo5xs{lQZ6_~aO~B&CN!6z&6Ap^ZLpGU1_YE#A%}dxGGo|TfaRvh{{Hd(=$UN3rCqepl#cDS_ zVH@eYsb*}34((ejL0wa9|9@s&W~il3C_)j$O_JWb61(|`mu4vxqf4#PkA&s>)^#EZ zUP}z!X{dFFp*_*q-Fj7ZH1aU2-bO^dZXa@bg;x2=l2^9`#>Mx z_tk3xuKSnT5!!n^jHs+oeT|hO$0_?}fA!{Ggb22)`LWHWhA= zgTU|7vvW+_&GNS12(1uzSeV-L2(5C$6E-AC%iY6hUcN3lyk2y9b@ zIqyol)$!w8r}kjh`|#5P=M}tPnsdCe{wu8KgOb25fskIL8RAs*Mik)Nq#hat#?G78K}kJbLhLuz6{9bFA%6LqQT-0< zgkqGOwcfcA3<51hqeQJkrTO>xQHW1H)Dc-6KWdB;&^`*n@*#PQPF3Hk4u*P9Idg!R z)+$H7yRU|4%KmB}X=AuGA~j6YKoB$!jc*G-c6SIsjf|oFy@;VKuzlY@!0HZZe|7S_ z^5v(TodK2HP$k2_IhBT>wpiXfkB(xShH9Jkq-{GVtXy!UY$LghSK_s4I!w=FaGY^M zE_4bG99~7<;w6eGTID(PG!fW5HHcmv1QPj`a~5{ep_7AoqcPb_B<#0L+Yn)%`ICd3 z;r%>f08<+-&+R-{8)gFVs&sao0_tL9n`Jn+8rFh_ADl{2BwP+D5$)&quICk z6V|(DHAwrWJ<8+G-|UR11%Cy)hfTMmA^O&C25W7-laNr9a7Ehh4y;sy6@F75Vjvy! z+{|2`J1i#blT{2;hIFC~U5fb0= zuZbI*y7w4Gqf-LJoF*j9DI zl76$M5T>5WgB~l>ZRTmsHKlUqBOJL9hT-;SEz2ffKc({t0SfTh@%C{2&WizaD{!IM{EAX!BD8;*|tviyQ*W5}SIRxnHoE(wH|8Ov0$b z_JE{rNs+|jMcxKnI&Kb`r$NE@CSZ8COKNI?x9X)I$UWc;vBTwJKV;apWd?}VkpNI~ z;A~M807@(S%a9m4P48P>dW~C08XatO_X(Ez@(wyX!WcA^y^QKVHMEt&CkcLHcsKd^ zkdfaJOmS08>uAlzLSMalz(>huC5uz(*JGu?BoP2)1gq#XdCe)!_L2C|x&_4$ssljA(F6 zre^=diC@B}!%DP!hCElRe|MpF)g!>XG>j^fcJi$d-EA$zTt61ov*XR-ByL3>%%Qhn z(8Oa$naup%v;hbNg$+p;gUc2EpbgY@6PA|(L;HLy6|wKLBwvhVRRMDtDkkPhVd~}h zKHb7~MD>((ylu&#V#&bOCxuKc+d1Dge9W3FB1yBXIlf^Pjpfxs34&JgspC~WtFGXs zSa5Mn7)|@$23^mLDD+nyuDuEqy)0w_7h^K{))>STE&s%#*mTmdC3L8=M?(1;1`XeZuk2Hy(EDpo? zPiqq^Wp&Rt;rA8zN}c`b^xf=GQ||&wZ+KdZ9v0h5iJs!-@fyXB&Lm!*iipkxSG9iV1&84_tdV5ok?Z< zZI~zn;e7oD9#x8hlaJUM2h;9H#WU=VI9Yruxz|~9ONbJ_6>6l7^p0K*`Z^p;aS|Bm z?Omh{Q)B#Y;dj>5=#xud7m}!$E!c9eyqo;%0vT1O^(guactyn@a#aU`!5&}36Lyo= zV-gbxD|7sfIjdjehLAbli|Uuvw**Qj;-V3L`$EjhGR~nUoxqacrBcUXuv=Y}dY)r< z8b%qa$8;3m%EgT`!tmROhFf=6)fw(w;DxIpkZf7tDLH{;kbZRdPHt<#__qj;L&K@3*Zgh39h#4)wF)jzeww@NW3myrRgyAVzwT`tFFEPdmmlj!Qun zaNFjx;TsVT_I(ILIP9=V$K?26oG47B1O0Ht2TNpB6S0pB(V<5{ax)=$%As8ih#+Kh zASP!>wocj06^VT&ZJ_ymW}C%>tY;Ddck@N|7KX>aU4KtR5vPysr;xq&mJ#;_hX`cqg)Dn`5B{f3045rjIKfNfEs zAxIA4`Z3Izhi)l=ItoXtTH!6+RW(4-uL`miTUhoUzd%=K*u-j>9yfKW!0K^NU|pMW z%*_SRBLH-{|0_D8DJjL$ceh7a2)>;l*c?{*>4)N?cb;=k1-tvHasd+7S+Kikf2s0a z3F(G9)QJh&7}BoQE@3OaHIGAO5F}N6l085x1TM7BkLef(&s~^s+&^c26M0ywr&b0Y zpH$%OFYTLjvul#Zqu)1;8R>aIFzO zzs8#!4MRc_Uc3tYRo`YROWs_eCt1WjOM;Pb70hmF6yikr!K#FEf7$q^-wf;01 z=4RY`mIewU2RSPqDt&)z?ALaizL!rB>96Tqn$Cm;mG82FZ+r8MzE@{A67l+L)cdaW zmrDv1Hsk-$LhH7VSxWphyK&$qYc3d-2Xr0zL!sc+uUCQ#90r-;YLjDRey{G+)Q<)2 zrh`0}s_|)Hd@{SE8@VM6=C+>!JQHxX2?`J%X^wuoa|Nl5#YbFm7jXi?b*KX}I|?t( zAW?9enATDis6V#syQE6~`mY^%Hl1IJ{*bZ)gjU7C02ZmKNf|K9Fb$s^vAt0@BFe{J zPgjr^_N|luO)O>}WBhC&;Vv*x6C~YK;zgH$F#lDpsGsjNLF#widbZ_=FgDIqQS9qU z_#Nj9H5K|ljg%u=UVl+q6@ZI?Lw?Q+aET0~=RaSj1@Q$N1S7-tI6SC-IQeR8f;7nM zazdlfeevUVLnXDIE$4eIJ0YXZV6s_;dPkR|daf^b@jD+TY{VaCDhRP3POXubHOTlu#*uW(VJ|y1Q4GLB##piR3knL^6FJntb(~#P)Gd*UTShkKWxL zagEmMQL6MB^A^@TKSy0Ikj**y(MK^rnZnsXJ|yCN3|=F5ZbOKog4!7Kz*x@F9kvwN z9AEgLj)z(z!v!B;lCIWR7Eb|0q5wqlqCLcHuWe@_heLgFj&fZP3rP7@BLezxfOXm9 zNOt8b7|5V9*t!#zfW&sl>q2peB^xD2^V1t0KZ!X_D0k~)A!CZstiG&>;~oGB$l0ar z1v2YO9|R)EUJmnu1;#{XY2tbp-W7(S1dHl{lf{HHX)NP!fSf7 zg3D1Nqn7Ui02p-u=0$&qk)HsVH^=hB_=A*kd9odpD*?C(+VKS2BOg!JWYBm|7HBKO zE?>`>YFF?Fm!wOoak6Lmu$ag)Pr9ibmlCY=82A@s{)Xvz}c=ZO8i%q z>%8Fk%{QinlBV1jRyaGbA3*s!inGcTl-DLP9w=c4srv9aZXP+dVOKb`hM;n&!1sNN z!6qA-t1l21$N|7&_%BscBlbgj_oh4flZ_qL3BM-_7S7Ibq1xqhFl_FU)0?23jyyTT zjIH9%HRO<Q>2C;nZeQn_s;Ps6)6A4Gn9UKB-1|E9bJH~%{?^aH*# zp05mh8zL}h10>zm^dE%-690GU)dUUTpe+j~PvaulFY!O^S{fachlxtr#1-;P2^$^5 zi^RN1SYDbcQ8%r9QXR^A!l9?VKR6k8eM|7*?-a)jnNrP0Zrn5Y6f5<|_KTuBilN2F z9|$`x+yrk6HcyAbHH@NcUTNL^T_wKa_fUYLoh!ZW?|{J-I&viZBpn2T+_ z@#M-CEoea)Cs{hSZ*PlQ$hjL(Ux8`tb<7np?1A9@AnAT#0Z_@)z^CnR@X*hj!XT?d z@0M?lZwQewu=`$@a=@uwM|>2RzP1%&*(+crkMKm?o+KXoI?+*^QqtmC}i?W?#dj(ov*prno-^1iJ`? zkO0XJL;2gT;rYU`x`5K=s5b3gr3v+gP;QpR6>7WQg}_#`sA`SH(`QORUC6mf&js(k zvVz!utvY=(Fce4~Wk583mCODCA{nkz!!^0+7U~kMd0e*VQ|#9lAX^H?9OFM`g(GPl zU`o$oi_fg{Qlsc1A2N0DeI6L-5ivP2Q z;M_-j(jhnRFSYg|P;p&*Klk+U`anL-w@g82x{A|^v>!t#bbvDSN)<^8+<$%&Ka6mo z->d1ozJkb^^u+ZUb#N$1+^I_dz%~G|Uo`oj2`69ZI&kch`s^Lsx1-LiACH?McLa}# z2->$of`t*nk|3uR_N^mvBo!FKw*7{p)Wg#muGQoZ)H;n*_Z$nXTlimYvITM~Wq_Ep5}y}RAfg%U($o+L zy309t4RCx1I3E5xW00;7y#lL3^CtbY@HxG+%oM;0mUmDij>6gDqx)G;J=1sTDdwD8 z`}I>i?oH8!Kj1F_&W?K#A!1|^9HKu6nqe;Kz22C1i#t|l1UwISY2u%I{9x~oItD{` zALr%CiO9Xx)5e4+d4PEt&8h_efLFxC$^APcC_QR(|A^qiim@W0x1J^y9}E{U5x9x* zS91}0?KJy|raXkJ*_z^sL}> z4dYwO>ifNzadQSKH-&uNDoMxeTE^!nH~@IBQ(TtypJMiG>Edf)n-@?>dN{DW)-I?8 zK4ocgFSCmD90vPQPbZ|7T#G^&L{oSKiAY>FVXo3g^n(`+VyRGUhKk2wW$uQd-(rdB z3^-yAohIEjExYiH}({8x2zvsEKX1 z#TAa9TV3jb7(pW7?418U)21(->gSeL$-VJmz zbgKBp35rd1yaX^r-Xpe`3(s8MGW4D%mMzZq=w&8Zn*b7>Te#{u1&Tq=DbmF1Y^xEH(~ z8O}4{^K~R(*Y1sSczoh1EzB~xWjf5*p*XNE`@F6E{XMe<(=DmqyDKL@5%aRm8`Hin z+6o`+Ug7R<97U#wJuO0Z4hv5k=0yxLJTBpNUSZjr$~ti_GTf?_Du1N4uak9>buauP z!&BkFdgrg=t#MW!OGxYVN=cH&9o$cADCoPu*H)YvMO2rzWF__+(TF#dyb{ijFZgphO|kg z(tSePtM6!GC7AYFd7wXoqS5@v-ea9XO1P%A9+fFoaTO8&Vc{LQ)A+>Gc6E8x)TL~K zrB+?9g_r0qkNoJJVEK_yWA5-QBFlCH)o8`~=wfm``OHD{!e!1Om5#~`Q|)`a(Y5v9 zxhah26=;|JP8qBW<6}B`|$eS#(y~Nv?5%{uuPqg+_+7w4wrN^FOmznw5Mtd zX-zIqPw+(clK1Ig&1OVHe3LGsxLC>D6TxWUF8oiM#nf$GIzvj@Z|wEwCBpFf+ez5` z-7oYKoBIt5CF7A9)GW)w9VOou+WmZv=w1E@uw!m#@bAZlhY#0BvI$=QI_f>bUW##j z9f_+@ZGRUjk3I`_bQOaTUF}VvhDWHFUe2NWJiFJ9P$BL)rwr6P<2{V~U*;u+m)BQk zVPH?r-xaM?Vey2U{kl|2cXw|ReK!HqE-TbF=ty(@(=U5Wg5z1@9F1GG!|obA7}0S! zgHxHv+*9{~7i}Oz#}5BtF#H%)!n?IRI>bs6VoUKht4>z91GTcf1M2a*uU~n2k()0x-9s*COw70d3`Wc9K(9sZtjM4QvCYZ; zUM~HtqiYRpc8#z^gc^S32F~GB*XOabvO1?q-OdbzR3-tgfq>tEf}Xo=Z!q+2AYa$q zPEyPd_XRm|$Fr1=Gu&G+5@)t=6&WQsjvJ9Pe-vptpNc+nKnl4sENpt6K?zkyzYhonvAhMTwr4Gr?9rVuIrHFh!6gSf^V82)*vfE{(psU3m&Q%q$4c?_$VheC$=8w2 zoTKu{I^9TI12ekvvr@iH_n;^sQ?{3goX+WUN101fDfi>&{%>Tl7%Sx}kQP(>7J z!OG|<@StY=n3>4ryTp2*i0r6;+DwQODJLSZrTY|#cfOLDEBY0zE|zPqRoInO4Zs1r zLq*XqW%{L8K;4Wz_6PAU!JFPIo@`}r7N?9;dI%#V9>VrXVFJc9fN|NsYI&D=bpL{=uQy%esddusj4Z`q}p>S6ys3600w;hfdUCFgG7Wcs#}l9o2hum zY3h%da$Q#$4zpHu0yv2SP7MFYX_OGwfU?!kq6{RC=n2gaf<5xU)Qq~fnRMc7Fi>jg zO+(G2VmmbQ2m$88F=2D0kB+5II7TM)W%Ni$ddWY_?|(|nzbni`FbLhxwcP~&aJ9N# z48>qA;HTlnb&aB4LhBNbsQWV;HDiidC3s*Zq+Z2%TltzK>B*@QuHCZUiaMRvjW<0W zUb?PKCyrcFhECWheINX>_?T>wUt(q=p_o^Il2A91X-gsvx$RCFN-Fy0b8xqw3j9{fv zgty#wOL$whxH}zC(dJ5s#EKMVvWSz-03^`CHT>mGr24kmBUHCuVJ?UmqpR4ieH2ms z+=4+hzfV-4fo?ROjoVVT?#(x*Z`1DsR0xBMNnp>262xkebYB`lU{|u?g)hLO)8wLV zDib=z)^QdDDp#eFFhi2f?EB2wehyJ1mUJ#WZa(GV>@R15xB=11y~USR)hb`3a-N4H}lyW z1cD0&z9$7|H@<^MHppgpoe<991U_l00ybuI59&POpf=h38B|Y#q zM0DQ_XZr*OHa7tiV4>J32bchxIBi-&DQw`-d@3e%>{XDEBo<@Cp)~Gz0G418MbU40 zO6JpUb>D}^TCCkiSvEfC=XSt743gdy`gdyvSl0a~bAZkpNlLv69!XAE@PRlf&nQ*U z=xsTA%aRZkJet`KU0OR-{C?n$ZYs>abvGizQZHMfh~^yyvGrXUE}q48Y|MS-2G)1s zwkeKUq3+APR!@`Afv!-v6x-0*A$?X5583GVKzj%OnK^mV7@;B{6d6c%7s~&l&E1cv zS6f$y#0H8WQM_vk{l|Qm_TC6n8%r*fPT^wB@9HlwUPQwsW z4F!M(0cQ{Vz0?3>EZx&t4L3$Z{6SDOxSlYlJNO>vnbgQ-Kw85Olg^Bmd=*4S{3Uz@)Aju=PsS*N&O+^i zg&i%=yMY$brfk@-#>YF1joSWk)Be{bt~x~*7VPQNguNO=zaQ5#PmVm~med0l& z2~8wyujRr&uBqP#`;_WluR@jQ)B$FI%zj({51CHe#WKI&tpceE4N*uQ#hM1fFQqc< zf&q=?khSIFg*%w=8u;%}(Gb*i4#J3BsM>InhEWFq^8u1R`0Zc20R{g>DuCMH2&u61 zln}&gV-J48uj_D`Nh>X1ZQSi%*Y!ZgHA8N>cxTpg7@CYXzq{0z$$f`)FlNr5sqW@T zx(G&pQJ)4Rdy?UWD1Tc9L)Ss=6a{(xsGVgve60%*&N4QtwNdeP*{c{Q=j42co?00Xof z3oqCEZ=N|J0WlE=_y2qc0wKF6%d&?-Fl-mH9zIpAzOqDS;*(ij7GR%1sTPioeqW4p zhDUAhg2lH5pwIy*{}ADYHRha9MC_jgsz6dA7G<_;xxZ;nkWOkG6nR)VFaTTN0xzQc zCCz08)~p{dG`l~v%}Ju3t<^L}ls^cT<17m>qX5iac<>Kn!en#(fo~uI%y892&FvtV z((RA_fDtud^uhwr_!`NefIGCFuCK|DId7QC$1aXSJ?)6Jd-+HxEVw*M4cSMEDW$+j@p?*uU{=Cso4*G#j=y^Z%@?Q>Mo75!7y zT(CE|otm5Yiv0=A&p)YSmwb*;EH4M0QE;13G%=ZgA$r$RE%26imQ7YMZXw=)vJ35#{ohTU_xU zGqZb}ZJBo)2n1QeP5bHO*5R$g8zO>dG&e9ls^`mbc!(0+$@p!NAY+an2Y$dRz}B>T zm#<0^?T2<-{a|pj_14zO$?FF5!|JpD;MB$mS6A%PaD+ixUk}~Ld^fQuUgCj*(=S0A zO?XOIhu)?=`XonY)8G5G{WzUSn8B$9) zv-_sF&Tq(@WShms6(l<6nfeIlnv-0LaPde(=mvA#NU2eDiFsM|5t?oT6GMXEZ?j-7 zX2L_Qdg13E4p9+Rn^xx&{DZSS*lTT!xAzE>Xn!{K6ks*#cY^xa^leBq4Z;Hp^ke^+k%NikO*3vetU|mu+hOiz-?&Xcg1b3 zWW?&ZNmeGir!iW`&DE!!my=F5m6~Z`mv18;xO|advovgq_d1qOP3HsqO){EBHuD{x zOfmugUMU;n7UxTdcI{J4U(}C@Ge;}soScYHqn^B5p0X_bQdh&Ds%m{}U)EPPv~*wV znpyRmb)%9ilkKaDb&Z&9WjA{c^EGvs$N_;M_0zWB+zCtk1xb9#m!hk-Zzc?*n)iOl zj7&IBGu*4#(^aBGl*f<1gyfN{?{iH@KlLa`NtBE^^ZcRMaIQo|n%!9ABahG@yB1`r z#GcplWl7ywX8hAr0)E!sGd0C;qh#dfj{Jq`@DRvlz>T8Bhp(Op}c*uWqw_uc4=5{eGrQ&<=AJtjc_bFyF(rXQu7KILp^B$g!kx*(i$)8ut3an2_ z0uy`8(%*Vp>Vyoe1Nm1Gz80~gI>E@y!MT%qNpFN3Z%WiBfird$#2~y5qb#xCEF1!r zOG*4T@tLaPn532pI*b`X2L0;;Hm`S@Mz6H%gUB4@G;Sr;gn$1}gen7+EvE0qoAD5Q zwgGM*dqa7Ywek&&6|4N^ytQgwZOJa-JF(3arO~h}@v-0fRg&V8k+1eX>sPzAlfL~{ zlot1Pp+|Q>cJ=!Wj%IxPn3B;)5($Yu`S6+jeo+UUkW&#dtY|4a;Yeei-wP$J*Jta- z(~`kfFgSKkD@zhJhf)v9cS{DLtfYQ~38yuE_9V|!M9$oEA<2UUDIOU1=0xG@IW;(^ zGxyP&T^<=)8$>?DQHAV&_XukjP*1etZw0VZT|G^d(38JWi6rkEpw-H{iTKkb($WCEcuW zoQ5So0p-^x8VE^drwNpYDT|#Lsx`MZw{^CS$zBcQ73KG^$Czi&Bsi-~@8n4BYS3g8 zi`vxoWQ)%zL~t}Hwffc;nr+l5g@S&+Bi7A zsu2PfZ&mKRb^c*aPV>FMLt>ik1G};E+mg40{DUD$Q1q;dZTHMHDzLws``(UVO#J*V zewv2gEKSd}($CoAs%WbH)i$(0tY2gAXYF-DIY_~iJs)YrwPn|_~*1+k8@a`kBFEmAAn$x_7 znO22Lyd8JhLZE5kf(6rRpVcXxqO951?X&k><<1mAb!N^|=j@peNvBKC zS1Q>$h_1swM~@3ifWw4p-RaxjTfFH4-9Jf>UhA6IH!-eIP|&eHsv%gI@aVE_z@Z|t zN)#}U7;P%KVqL0xz(UWz`4P6Kh(K!*%%JlLa|c17i?VMBE@l4Im{u>qZe`h;gRC?J z-7Bfs*EfA^_PTO6`Vw{++f9kG<{KFU6jGp?=04148X{gJId{uB%b&qQ&n?I1);2-~ zn$hRbJDVNnRLqMD!5INEr4C|26zIK{c+2`GgJj-O!forAq#7c1C&Xj~0bfNvAHTMR zyw2j4u*o?OrM*4(T@luAAtzx$e`g=|ZMpD+;i2Kc<|On;4C;7CrH?LS&diw-h587b zg)%&jV6?~07i)ssQM+ZL44Bk}7AEzn!_fmSxS>uFQOwb79AnDBTLwkmOD%?E25%+4 zcLP&0Q_ZR2-di1Ka%AhWJ9{p%#C2adg6-@X3>|M}h;Qqytdk|=wZSnp!kDC=B~c_q zsXaTKd0m-fxlP&TEe+Bf;4>T;=w)|e%%(ZqHvH5E){H4g$D&8zq;Ph0Qog*pd(Z9{+}@L7l1|h=s9`1%#AQOM-+UFIY$(%B zX_E1Qx53vqWY+v~H6ccT*`y|I>-s~y6UAGNNlPXrgC>-(uTG;)Z&ESU&sWhV#ihl) z;3gH*wWdn5_~?9pggNhRk4_&rH^O(W303n63X4yQ$m<(h#1D8xbJy1omBzIcUE;1n zF$&Ut;wA|1heDhS;*PcM`OV{#a7%X9zK+Xw6E%@|5%EoU2tOD9sjXohppTRIDn|`V z$fcUtRD?xgF7vi})Jj)qCGS;8egSMBFX51U>IkU7D_@ zjRJ4G8daBtGGOD4wv8wK#yNu60DUa~bMTF6`6PR$sEDFkhXr&?|uY{92}KQxgx!XLrBVyl?%j z*Cf_@`Ps7oZkvUwDP5M_3aunhb+nV}9g&{G&p+P~V0Egm-;`Ju3mo^7UJ!|xS{dDg zAiS|Pd7ndP*yl!K^^Jg@B^`KAhunYeGN62!2Y>d&F!u-HcTZqKefnXEll*Rnbba78 z(g$LoWrKz)Br9__xG4X%`yiUn5^cO8fATe0+uKr|=ri21%+tM@=*{X zQBmIqA#rEmxI2}j{0~W!_di8d5=Y?Q(W@-+&-O;&a-fhYmz>5f^er#5(>)31yVV3G z;&%q+Ae5i9&z&{@$ryD%IuyritE&;db-b?UQ5P(}J05NSR}e3KC(yk{JF8o7BAsx6 zkp5J6{SBZPk~I1im==&T!KC<)iy{p1&o~Y)0iZ~@3gb8qIV?W8jdVL@9<86wmX&?A z^gEH9wa%*TY||+fVdxO@F>~j@2yl_T>vxS!_aVuwE2jFDtfnFQw6DvHI?P?>7c`81 zZ~TEEfFlt{e;e8>{m{lVEf#9J8*%%WaX~ac4Ofme+6@V_1?-qcRBxfi$~b6LRQ6$D z#qdva{QrohN*y1l9!pWS`rf;fcVAgVepR+V z7r4{M0)PR%{n1Nz{Lii3n2}wYDzMkkb^}w)f z#-rM5Rx-!rzh^HUlMpjp(z%Z#%*IS0QJ&N9cNS2YeF=8Q4UgxMx_W+fo zY|T43_O~s;(e``%t5$_UUc5;2dtkod-Lo4x`>qpLTbc0h5sAyBqbu70A{>RJAH)5tz5k&7x4nl&?|yy=mf%e4 zwMnwN?1>~6Y0Xw&%tkK|FF=nb+Bf$Do^9b8OiBIKq`N^1{HY5U1ojq))cS=UyK9K5 z(g~tSph_>TSKR7OtHNPHg&@~QcrwaEVhvrU0v%eBC)t;|q)7cx8wZY?i^9Us!bt72 z#A(AnPAF*i>L=&T(|Cc0RAGL)xqUMTGhjNVxVB&!tmiVm{$JjA}eQvWEQ%c z^t^K{g|FXwptenSM^?DTJ%Yr|`f3&v_)QEHX=C~6;;kcem=av-Zt8i^o` zbDqP=vNP)b%wKBfjj=p52mYX^3rWAG{nu%f>B+x$*%5Mpt$cv`vb-uz^$Z^}EcdD{ zyinr3FhxrRELbI~j|Ok7vTdJ}lx~fCRnVF|2M@MxMr==zf8Fy`8!w7T8@J&O7w3Mf zh7j&+V0>Hc*KSef-BU@1FOtxsLQWE-r;>0q2|dJSpA^QW1<7Fw-SIZUMj{<8Lgfr) zs0|D;BXyUQYjiX?6j1BP<(gz#;=wzz{sO^{h4)EbHNw0*Rl7$~mH#AkPwcxZPYJ`h z4UcaT4?(V}KJ^vGn!?>K9gjn+YMYKrm_-aq^*H6u6s4#BvPqmY_^VFAz=f5HA%YO* zc#`*vOnAP(d~||vTqCFB0eBh141L|&{-lt?qIRNQEjf~mGKpV}ros4p!-j_FQ|HIv zk!o(3KBWW2vxSI2)NfI%2UZbtC!k= zOV|U+ek%LBeF!t^QJ-5h)cMKImlu@m@$d#E8HCod2_y5$P%kN^4Smfq2iIE`yL{Ce z#?kQL$Xi*6M5le|t3{x>nr~{1zwieJVQ!=@pS>pKau+GR*TRKvPG)khb83QyIYH!~ z+mSWGmVQ@Mwdsh}W_k)ZaW&TnNAmUe)TMuX!d%K;eE)hv42x0WK5f-{=s_!?{-jhY zJmV%G0hel_OIo8S!7l4MX5!FuHjxhP0W4)H7p7^l7Rej<6j}z6e%a!d2Is)Nq&0+J zOBHRoszYkjA^JGvpu`Kj=2IaiO4Z6B5mve+MIH8&@3sM{_ST29&^bB?P2o^MhJE9? zSe_AB(h2Rr1JdhX5>gZZCI^5)75NjE3TN+HZwZG9x>}2J&fxO~c`j5!eOC9^{B@%f ze_@f^fMzn=_nrs@Dhn55DpU% zjx`Vta8C|6Zb1|>H#uPH|N8ql4~GdMcIT<3b0$D}@xL zJo^JQdN-FRfF2HoBB)dD8vvRCA$kA8z<|gf##o zbY5}@jjlVAR1^Z{87ro(1T|i;;e*Eq&Kr)iy9Hz<>k>jGS(cddJhPw7{rSg>rt&gU zq1;{yei9@Heegx%&(QLBnGbI{&FS|wK%if`tcu`d$9sCFZ6>U82n}zcn2)!;C>9fhU|D!1{~`mispeF)Pn(o;&+3AWeCBn876GV^LE2Ux^~o)Gc1Q3 zRtqNFE3Z9KF7EKoD;gRfI$-@`bz^X1l&Go%^yz9&gM~~-Q#`g?8!(V(f3Y_C197Q> zb6&lO?5|?qAVz3{pm-s+X5@m5Akb~+0(qsAf*P7@A&l;*A2KB;zme&4>n~OBI>gP) z206DH5Sc9=ZXA~Xkbd9dQlF@ZI04L;sY?J?1;Bk#%Zqr6qNsO-K`0Xt1MkQhfVI%0 zh=vU~n9}ZHT=KR2g7$)6&dKZ1XZmgU10#g@jQoyPU*Uq(R^+g3Rh42glZA$W2hN(1qc3Gw5vMx~6CivWj0mEPTbUHMJ zL4x{#a}eNM_KzN4oSiL%38OO#1SRv2ELa2@@4)(Yzy0&N3oGG7RbAqhFNy*&tox>~ z!N}fGQ7(gz6yLkd)SNl z94rQck_60H!8sT&Y6J3(j(PjX1CHmaD^rdr?j$Uoz{DSB2+vjVpybf!FZY5%kGssm z7>PjI0Z9N(4F8ho%wF0CNjtOzE~&(RRUI*f=|x16C+*Cou(eiP;A9~Sl!UV`gL-&D zJ;YLf8D_uY1G>A-r1Tmj9ian2I)Y?ingUCu|2iV}x9S7Gq(X%`|0ncYVz9S}hWpL) zZWUY@Yxb946)?9)DhO>?_s(qsKcJ>&O4%DeWgw8gvJVP!B%9RCLTAghgB1K;tK}F{ZnH2AXy~~Dx!X^qRXtc)8tLRT}JS0OU~zz?p0R~F@$GdZ1%^nd2rp?#_2plKrb^E3eqQ)l0RVFCN z>q_AW>Ed3^@C@mhQ}vj-r$Ld-=A6vY8W)v<10cfz4wBY3-Q+4b(L~>0G2B3dKUNXj z%=9+W=rR6(?VSl!&D;C`_vh3((PSu!rjvw}G>bwbNl9Hp$xt-jCXx&#MMar%-NKDh zhRia%+^ZtVSX^Y5p(JI0b{V-Lk~zJ*QNDQT6uj zxu$j@(_sMKL z(Y*bUJ4J^jPw2-^Eu4PakK+zHq47X8riF#o`1{5NZLY01d%A7N&Qlwgo6Y=gvR^~T z{rZu8Hr=tz96N1iK>JwlG}$#!;06UwpI2r^wv{A5ep4>(hKCf~<%5D}_v^iSXUX|Q z+v}Oh>trPNk2Kt{(P5Fc>5W7Cq-I$kGBuIQw&Cq8`gAjHr;JmcR*3zo4Y)L4ZJpkY zR%z2R{qp~nqHA-k(xXyt6XoL^GVhnq6gtHHFw=c#jr|ah!WVV%Dq3!H1}SGmJ={FP zy1I_h|%pdwD? zn*7IkmD37`yw2L^ymew99fgU@qEdLo+9TrxkfH-u|6=wS2<^n-fKjb&Hkn*W|2tS*2jxZt#os;X}6KlpizW zXYRCTzecS)1Gd{H?ELOxneyMo=8oxcX=JuzdhU0#CXH##rMJ_W$=U7regkDlft=hl?a;bG|-;LX6*!h=;DZWES zP3kxJu^OIY3euhYHf6?M?zDAQ#P~@g`@e`w(B7JI$F_%=_lB_44m0XDEK2NQ(O{#b z(pOt=scxNVR${>qwf>2BtAAVnq~DY$;cItRd*rx{3eVefEJP*co!!NyH|pCU;zzAB zbwnFVdZT1XVd$RIyJr<;jc4*@sEpyamiD*(-7Ed0WrRf~@=*7Tp#4bDv&xIuN=4 zcECZ4u(`s7X=}%%*__DM`_XgK$?K0+pV+tLOj;+O?GJaEOK!DqbN|w9yQ9SmY{EL| zEO=hFp)O~5(VDyS+iGmT+U?W;+v3@mMw;r*J3jH|#F9%v2Wyx2TsC`|(vpnQ~cU|v;7=R9+m)x5QkS-bcn<#sUTti_LRBnd}6o^6@xJ1m`(?vt># z+ls6e-b`}IT*(}>T0eRy!3J}gMzp)oMLa)5FV|pK&A5EwdI#@yD%qRsd3vuD^PKyP zJI@xUhUw_U*dFM2>#ll%8UN{pxcU!#{n9F^uz`x^JtpelR_?|ze6!~oPia@ePkm~5 z=h&7fJ8PvbDCQ+7WX~M8f99BY2Zvc(hiK+JaJAfLRq(zS_tfiIlW&b^qiJ(TYW)h2 zt}4OXV|kWF-Ia$`B(O#HM+SzOg!X*k7jx`@TDYu&jeOARi}5!v-+M0gUdF)8(_F!; z(Qkx(rEG9=%7*By8>*)+IL@_YQ=dx9pB!2)mEdJ!K^8 z<7YyT6wTqy@BaPKod$PKoV$NPE!O7ddihK7Bb8JRCmAFJb+F5xG{i-|^GZ+tptmLM zm!7#eqE@BPaZ78*`?YiBPbFSpZ`yll+da2ce?MkyHMC??A6xX#?~6JenydyT(7;Wwz#oHA-Cu?wG4)YJF1=fB5dh(t?$01}X3T zb-q6m*Zoni)WXSisV+yp*BLZm?G&45De=SnWx_o>zn=JXEN{eI`HfS8t~K~Sw0hJK z_dbNlx+83%)|nQv&CdXuc8g_MC6v1j;|_qS*J2JIA~U5pGVuH*055q50VoGO|Eh{psL}i{ITq& z?w%UOXv!V?kcw#u5MuEUD}~=QS8cWwa1k& z6rJqT#(S3CF@3xF;34}Zz3tAPmbDqb9eQxhX+w+oPxZ8&{KoN3mx!gm9XXzx-)3Of zo7TNYj9v9aXTqz%8L=a_7n=sOZ}2s>HQPn&uLm^I`lGkv5`Pvt@r;&7h0VOb)^9}R zr1(TDb%|1m`(rAtA*lh$nii7m(K+L8H;?xFjWPa?#a%EGrpIO4(rRjcD*9?aOXk0y zNw*95S8G*vq~4=Z-rS0@Oo^jekjHg}=$9(s3?Ch6V>LT$k$ICaHy}>%#OYmh$CvgS z^aL}<+)+90XSiYCRhG#q=A)rA8U}w}aBUN>S29lbb=?DHG?%z7J)~cmdG&GY$H6K| z3L$opUKz*D)oMr2xve3k=yAQXT}3;k@ks_gtZRerdLR8OYEWopoi6%K3QcRJ`J_G% zZ>Mq^cl+l0R8P__l3r@2z}Vi|Gj-FQhsRcG1wNlLyu^0L;{|>Ohqb+-!_hhuuifKn zHn~yo)IcrXVO*5J%OxU#v17KG^VJj=56G{2cXIh}`E7d|Sj_IPo3>p?xA%?jEYCk5 zS>rmPzEE&0T-CYktzfpO zf3$o%BJGUAq7xj}!6VX1T7L({ikiF&UT2Zgu@6}^n!^l&NrWBVdM3Rn-Zw@z{n&tlg5FMt?w+qJIGCur$2=CzF-+0h=6D*t zV(B^V$}Q=9E&KAGR_D8PmvukRWFlth(KPJ}v0zM7np3Wwxylc`N54uk3CB-CbTg}p z+8yFk#*6@?;3_u8s$(p(+<9j+*+`w7Lp?f4y6zwu6W_@%`^m_%@-cHABU1V@Ub1f< zou~&utcKKx9?Q7KD2`!rfg2rrb^ZRPN602Gv%YGJmKMHy=&@bkd{@n@Jhg}Cvg z7cae0PNPn&)BVJe5I+*f?JYYLv-S$T0g+oJE#vbyv6H8ct!<3yKL4j_3b$GPiyE)et$wkU<9lzRYj}8nh93OV z?EMdSRx|nfYaOM+LuTsK<}mM9bN|fbn|51LP0G<6juE|n{Kfk_y7s)Wzwkz`{Qv{? zSZ;F420XVW?~C3bCht4fxLNIGWd+(PEYK^6Vgv8b_~QMMj??N{r{y2=nRM}R&uPt_ z{NDR6M`eHfP@cB7@bVa0Smjqj+4d z&+Qji<=eXX9{UuQCZ|5t@x*iV($hL!{F*A3d-Fp0;{HK?3$EFvFg_leU|YzHaf%)~ zuTAB&m%B%o@JDp!t!3QA`C}itZ@cI8sZX2ywvt_3Z#~cqKV5WGtKFi5{SU5E zP-ZtF?@Gmf8niZ-nWz`uG$Eai?PEQ6)i)eiab05fEZO28_Rmkc#uD!yqi2$ftP%VUbpDjv{OOW2Rf@?ON|NrwQY*$Ypn(xzsVLk?G!tMopv?Gd@La%@uW;Xvz& z>n~=9b8@5lJME}FdSd$V{RNpHhmX5|IcCFHp1$&x$J_6R1U2qlYX}us;3T<`|ul&4nM|`B%xGHkZ41 zCABWpO>?Qzxbm2s2^&6i9i}Et56EyP&Y|##c85W4SWn{>ffv}s`L2p%hbXjfe^^hu zU|Y_C(u`>ZvjiPw6xZeQXNByhJFZS>*djDt4+a{rheDg-cv);oyuZ7$M2}Lr5%|I}>%;?ghkb ztuwd$F(mtQynZ&&D)MsHH2EcE`7eWGvN~ zP~WP)!P6r|C880KUYVm!ZH;e)_yl^RP8j0T$|u~U6ec&TV059VWuE|_;P7T0EHcoC z?$AGf#^5;2!8_C^*q3CC6G8$*{g6?N3=t`9X?Ay?raD!ha9@(457o=g2oDxE<&Ev9 zM@$Yze$VuX>7qb2m9s4G$SJg4(Wj+OQGtXI!*)QHpwAy9nu=TeRBl8lm<6=ja?V!0 z8t<`g{z!l334PiO?)Pbw+bFNgl9GHlRpK$@F6S>-HjMY;m>{6aHu0$4p=$Jq8+ye4z|rse`=@$wJGD`Np{KdOf3nx)bW{63#G%!= z#eVo&`F|S6XZ!xbU;lO7zgGW%oF;#X#wCe7eg0&g{J;83zotpwKgHu~?f$RjKgaPe z*3Z|@oBwtB&;Ixynb%)CKfZPyi1yVM>*U|=lmB)-{M-HeYwiBG{r}hg6YU3oa=!Sl z#{Iv({=U}!&+~%Lr!DvOR_AT;{}uf0c^CgZCq6&7p7*~y?$APx_6b@a&Heq`xc*zY zzqJ?rejYzvm$V%JUt9-#mj9Rj68qz8+kM_g|Khyzwfa8G|3~Beul09mHBbKQ{=QcK z|JQYgdHl8e@Yl-!)js@h&lCTtKUHxLHU;aKKJZ0L+S;LRj;KcTgXhE;y*$9_;m{h` z0o!IeBUJ)jz%bAqbOp+Q$_dI-nLbblDuCq0^^~VF+8;=-DxfbBSphos2KLQ)E2JKP zYmD9LsJXEI%Xq;kyxgwA6UWj}`a$=uQO19!V z8KQnkAL$VLi)4kkWvoIv7l`Z0w^T>p%)z+1!AQyW0f78SJ~aoVr&$LxL!N9P9aPq; z4^exn7q+S=c`E@?!zTg8HL*)?6n#rZyA@vaxjxNS^BZ{fl~%`?K~BP)>d! z{RH(*&^#kM;X~OBq)wPK6OeiV8Xxsd^~iJfNXhq(KnQ4ll7BF#xCKbbue9#SCsbzz zUIX$ywV^uFCH5JmWXll{2q?BxPxg~PAw>%@AfzuBMsChK<;3 zz^DE5QHNu5d898<-XFt440|LaL};t`2_c2Q3}K^L5X$!8coFi45VBza`=0(WOr2S9 z*F~hrJ6}R9p?i1;bpDFG0w9|_z+7C%zDJr1hJ%rS=8QM?HyT3{p!k#Te+1CWCW58F z4GaY?fZEYK$^)^0#tEC*cYtKZ0gRE&0I-44oF#vZ2iPka#wnV!)<|KiXpN6XIvETB z6jM!H?oL5Ux@cdbe#tJ1n=zmm!3S&^5cf;A)A>XQXzpkM(!Usx4q8{@@sS>a;z%}6 z0-nuzvH|N?#wI~qU9lFj5#As}Cn zjij6AlLa7Kx&S56JO@N=NMhZ;AeoC7S|%2l0XHR02ieOHa1pj%#5}$@6|4l1xik@60FC(N=a_51d4N^mKBgqbMSo=AiZnn> zuFL{k!Ec~}v8(X))f7+&?tps6%Fw3F9(aM70QF_}7`p}?*Wkx%Hoy~LUR}fby0!&e z1L*&{Iv4;jf363B`5>FITgmu?*HWMdK>OR!eR~c-Z0nL$_k#uC zC$Jw}X6zx#9#%0{(FI`a6_daT#wxL9DYfTwIJZjNjE#g;;G1g*S zPy2w00Q2MNA%HkPeT-iWA`5~5eE+N%JOK6BRulmI@Er5}`9W+UGQbLq2h#y;c##9n zf>(^ysQ~!9ZY1~~ml8FMy|D$6APbxZ;ruHK@$uB?jW9V zZM%U`#z~)LoQy8;0*k>`aEEbnS|AFX1Qm>v=K(d~!MJv}7}tIf2mwi8Gr(Jx9d0tN zV>v@RD(zA+MwiV2jcQa1S)%^s5O_)&;Lz zc9{=y0PODah;dydfeJ7Mqroo#?Ye$ooJt=s7DR%jU?V_ZDzHVh3or+0qZ$krfSd`IKzmKJ*F<|wjJcZ>&^m)VJN$3`*kog#S1cm{QhXIQ~_F6xXo(~a@g z-D12_E5`dch)EcRF^S2GnZ)WhOyX<~o}%?-l4g}m(hq6sCMH=pg-JfU$@pq`9cS40 zjK9i~@mEJN{(~yUf4GV9D-JV$Whvu7(gB+pzc!QcpSpmbKsDn(QwQ@H|3w<(*J%KM z@FU~D>;=|?PmKR+F}Th6ue*ck-~{8p(F0Ey|7{Q`Vf=R<05-gPiOV!+u!iyL<-u9T zZx{g{GX4iE0GmGCVf>Hi_v0jx0np}?1K0+hGk&8q&}9ONWuS@)B+Wnq*b8nk0lyuv z1524epaMd{0q~Itq_lu7@CA#&DJE#+2G)ZLCTQCSj05pt7r4R%(yAZ|Tx9|oH82G% z2M57TCXkf`=ui1D6Lc8^P}bE8>;UhXKxHt9V**w5t@@S;)I69#{Rk6iEC8iUAe;nN zfS&=zqv;KHGC{X>OrT{7Fb=JUOwe5uYyl-qpp8DYM=(JT=y$`DW!!VL5n*SD^4gHtWex3ZXqr1T3RTj z?Y?yH{hjmQ^W6XbXC+xO=NRvJ-?1d~nRBkcR{q++pwLv+P{qK+#K6G3|6u%u!cf7$ z!@k;V#K*@cB_Vx4LGgf^ zjEs!>&x(v1@V6=8pAE^soBz}OpX0wiU;qd(ud&&%F##A@08DHE=3j#tO!p+QurdGe z{m+7fi-nDciBE7}t51P}iH(hgjf01Wk4u1wg^P=YiH(DSONj@dVgpi(h$`dLu!9T? z#f%&r{bp%>BO+7NN;p)~OB?CLC5)Sz`{thb_j7^+V4GVI6X%S3M8^A2|1AFrhk<#I zkBf(YUnv5(CxeBB@h4mNr2bf7VgV?zsi@gR3~+EoBk;sPkxf8$hepE-<&w+3zt%8F z?#-|O*Z_>j7{3%op6~sRQ`&xi^%7kCT88^Ey;ahqlJ}iF-599CPrFnQ2&6dCw}F%3 zf!06?CyntQF$pi9a0jLu4)`XdibF{&Dk#LyC1Y9Y#}jDVGPJ7=O5w_JiN5VE)mL}Q z$cNZ^fa;f=GP10_l-ov~k{jVQEE?~=@i|F1C>qB_kmftD(|qFGTm}zD*RLk?5!noN z;tiJV?!KO~k!pCCvEv4|FBACy@O%}G+d4T001U00IWi+xLiuH0J20T3ooZ~nX2BI( zQBimcL4!>wnrmpB7h~9Bt)J-&{)Tnc)~@Q|Q$>lP#0{#79m3Omb>MiZbI!X~rd*9iyY58Qni%d|7 z>CG8<&`7Z7i!sA0%$L>%Ur1Mavy|Pd3`!L9>8rL0joG=0wqBRhT;6CwPQM?VafLO$ zjbQWqbM9&U=}lwR97`rMILpUISsBS5xXngFhZi?zYGnw;A;@hWWaFoArC<8^p`!`I@g7V_T5yLRIdYuM+1 zVetJnrjIM(+Uk0X-&K!YyRC!{7bk1T|DiA2>#mY+yvw{xO6fj(hn?q`lOgUq0TQa#tY1=N zO&93IS*AMF+AIs-uo0R&i2CDi#E&oJ2c+_&hhpKFk;V`F`I~<=D5A8EbB)Na?)FFQV{{;mAk%`azCatxkL%d#jLpGuC& zwotk5bz$0DBj@Q;^(GQr1RYMEfvtV%dky4=!x7K^i?yeV^HXUCj&iIPej4I@>H|Dc zo2O^gCe)v=m(2nNvkjC5`GUvDM%C*QJ}Mf%f^5*jn<+8YLq$-TIE4yT?*qoWvkp@j z2T7Z9FbFmny!eRl_I_4z+MYqzP7Nz_a?688FQLr=nFcZel2IPlr=UUAuPM6&vmZWx zuuTwuGL*`*o_M8GD!WluAmIaG0yF)j*S8y7zBbEimt(6oy*@p;E|W>DwKhNQWS`3)ijK$X{1=t#jprC%j|;D-{!tdUVYM{q+&u&YhCd z>A9J>6AZr>l)sNB36Zo{gQq-V6dkM1BPLY!i7m`5TN zCl8tJ=omy~e=5{8GVN*k0inx#?Ib*ZT`Py{Hk$7U$BTBnx*P(ReNuJVD~u0e#te2} zqsU#`&Gr@#x@2&lQq7{>M8aa@2GnrHXg)&+_{b9U+^d3-&Z6rU`sHAQEfEO>VU=_~ zEHXbEbUG2<&;=6u6&mf-qR}}u(F|cq!W5N2>w_VRj+`J(qFmWC5=KoMp?5AQ^KB#} zAt_NudUsTcrIaZ1Z9qL9#V?@wCzxL*XKv6|YmO8b$1qI?P6M>h6h$N9|GG!;$(B$a z0iCx->bALXv+8F_fnyb(7kS!#hK7Mnl5vpC;8lbI*nE4hxbdll2)j1(YE^onzAMe% zS|YSN9=cPpqOO`U`240gr!yIiudUXt@of1CqaX!C%z39Skd;VsEl}2}2jfcLM13Lo zQ{8e@RC--7vHB&G2C1ZwS3Q5kn8~cZ@LutOr=e}86LS28-?Q&ivWLN5ssxZWMvAF*2&*g&)tt$ej1FwrMR_zL>Y9;|_DVNBeB~d`*m*$&RaSe0J*b@}eYBUy9d1^|aK# zMZr?vzB@Lgq_?v6aPwfj)`v1G8;=e)v2^azo_`Z0jGQ!k!{^5u3rM}&viC9~0w5av^ zx}BR$X7UlAXFPlj<^=2X%1^=#E?02uG@YEBaT~D-)egeN%(kVbW5JMbcQnt!>z40U zn+LIcLomb%4RMyFuRLYSN)~nraFX4S>ar(&kdVt8?DufUfU|A6)0{Lqv|Q)wUmI^qk`5ha<9S`-rx~92VPc&_Lwc6Y)wm zMun-iMjhlMXR(keuv!g_{5scJm7N}3kjB}?B}ZY9!AtZLvO4^ZlH=n`a#Kam#^S8J zFLS0j%|mgjKSwm>X!|^#g}K~pN!Fjgt9>4rmW6$#uQQ)lgnPYuj0ajN7FM7ic4lqc z=wez#;u=t=rxM*A@HQ|E+jQAkLb)Q2G%nuw-pao(cqO8msm>QIW-yxlYuy735en>P4%~%cD(u;3zjv^sRts-;&IJh zst%MK29!SELHW^3I`IUrKNYt`b2@|Fm7BJS4odii$%M#$Z-Gr#T@{`m(*M-@3qy0p z@$otCceP)pk^D$84KmTeCn@!w$rPUO`0AeZV{_FN|1OneaVV8gYf}{q>g4^zc=6iu zoZR>H-x*c)E%z-dnAP5eD0m{w3?OS|GV*fm{Q^zfF={S)p97+vx+3Pb95-vMf(!o6 zEfiQMmeJISf|Li8w< zS|37S|0*DelU;<+Nd6Y@%eobFgy6^sW3$#a0cyC8uf4Uo^!xAd+aA;JTe&fseg}i& z@}vo@u6_mjI18>`BYYYwxaaWJ-ylbl0oUu4&IVfWg!!gm#Mgdj3u05Z+wzN{ z93g%TU1@k!A@&vTfbbQ2DwA=YTq0(vhVbL1&-;9>L$B5cfmD_I@V4JC+gZXSjyzCF39_O+D-X8vZZtt= zX8=b}X)7_6Hk*pOGo_#0nu<2O@hD_xSt0a!RUkcV-uY1i#Cc|LNBiE|I`^AjxAaNY zduhLMu*?-(r_fNys*hQKzKQO6e6XNKOsSpuT<~Hy3a}5r)AE3ADWHBDeOr#r$_Xsr zeQu4qZ%7-sKWEB4x8fe8UC(t(X`*-AHO5w9A{7N&Yq8i{g;eoz9M-FEyKit9@-~kL zP7}5}&SK#SipKzJY}C2Vg_??w>PLKeM$^}vv**)r{Md1*&sNnzFW=(SkO;NycC5Hq z&7{y835jyOAFYUoclS^;;n;zZ>L-#B5=FeL%*d%r&yy=k%(U*VMxW@g1!mlNmC<&M z=Cb5h;Ar<;Xa=m4GNafYoDbF`xX=oJ0;<$I=+G~Ni3(oOr!8=^z3qbqBxc0o7+~wU z;1sIL(BRfVxnfK5=Z)0;t^81*p4DG#=WV9qeQ)?w3@Z;vzs}y4;X-3pZjGEA%gS`> zKJ@d#aBEfyIRm8z>IbGhSHqnC`1&Eu=F=XT=S`@hMSQI3goX3bo~e-1zHEQb?Z`)} z%C1`&wFth`NgQ>{!|@I`#dCp6%cuP642Cu_M~nT$=hN|zx)?7D*|<#@3{0i1Um4&x z2xPTV=L9sc^!PNTv4_WhYua^BnUorNYBGQcSv4?_@?7YeYgxGc(U04pCgawD7J8JJ zw92!l0=+?tKrn_v;I)@$m!^*GUtOdxWjy9A-LJ^5e7mfp;nNfBx;V~2H7O2b-+mjU z0eDb0Op!06e54>_JE!1dZ36-^?K0-ltJJ95*1aI5UJzF5o)i%|_bK2tHV}o9bq=mDeB#-L0 zDaF4>rPTNr?^Yg+rF~`MCsIV2J-{X%W?3~s%o6m?jLFcEB#H=kfv|QK1i2VLu=ib&X1%S%LVm4_6rUhW+xFKuQ4_1zqa)PoV4YbeT=K(Ae5r@JM^ax;dp=47rH+H)=oGz;hIoHVCvojfFLoi0)3lDQAuz$G1QE`-rx>dIru2hl#=AX8=qEB(596vXZqxgM7|a-i7qex$=EtU3Z1$DG8eQ%?|Qh^Rec{WE-^> zkYq`_)K_LE4@vjW4$1PAlJq~5_C*0DQBc+lNdrO%b-~fp+{lH#(bU{>m%`cvTd|`N z;<1-Q+pIQ(mf8Yilitxh`RuvGS|l1x`pkC)+3Y9d&T}Ktk?)o=EMmS(7m+poLg1m+ zKj};nhb%8EcI6%O3Yp9biIzpCVnZe9nwnN9OI^0OIG(=yrvD^(#^0saH~oagcOu`6 zM_axkDJ9C+9_6d%Ok)!Vjui-Xm255D6_}9E+EPK{5a)hO8Q|KE-^frihZwPG7fyY! z>lc4u9l$wkQ)xOtPXa@Z!9)pbDB=9jrpV;Vzc37vLvCGVyR48;9{@YvEd)0m;xU=2 zJ&aF-`~S+Iu`C@>gbq&yBl#h+lXo!!iZz0r3o)6MYHZ2|3Yi4N4{2~^Ts_J!{W((N>8s;7}u8>BXCIKs_V8PC0SVwW$QWhZ{3i}FQ^%3;8j!Gv#ebgskQtVtUHa^B@MU4BiSC1uh`zG* z2UyoGuo(6>!1)JCk|V#o>I?ccaO{VG0Oop!YAeplGB>pNSR-CCbMH13qwOVzW zo$xj;D~}dy2Q{f~I{6XSCB#;>2g?@MON&%|+iWdNQHlhvl37Lq+VSq{r62PM4xr=>7Y_2f^hX$ZeF({VJPc+s>e7XVh)wJ0LOvn_1}thyp-;WpWIh2n%Z&cM#Pn*?-V| zGE>{ELlbSHvxtLB^`rS`Q!-mBx!60fMQG!56OuviUxZMI=cClS;Y1(jn4_Rp=m( zdD0{)%v*DQerS7?uc|Fuo6q&F(}-;n?fSv!lx>wCz_(agF8M2qUTO@t#549&6k#0j z_ghV?h3F)RzhX-HyQP9>WhnS9MvLOFnUu_zZv!_P^69>Y6}_J(cWo#QeU!KX&Sf(& zKx%`-7jbNk$36~sOuI2K49-s`QlD5DWhXLX@#$2fJ-2?i@VE0|YMw z5vJ>sXu-%Qp)|U3UW5#+h`}!4U6mNp&U2aD*8Q_YuCAKR8@xmvmAp!Yo`vE(RaQRL zDqz7vW)Qe|_k^L%W^s%Tmq&Yr!7PQ8g*~lw)?rWt`~f3FYbq1u@6?Gq{27J&{k%nW zpY)&tAzmNHM{Zae_+Sii1{1lJD_KNG+*~@b{ zB96Wt^oFM838LF*WqIJt*bDn zW)h`bnp3;LyE&)riY|iE=Zmhas0M?g=C{+}s#^MB+qr>wZHKJ>jx_Ss>`%pLtQ*&M z`MXChVw@{)6JNOwE)Z|17ory!_asoppRS~vFvJM)vR#o z*GT4tMAdkuG!nHZBOPjd#5rci0%!9p`y%X(HHirNIG;tS&kEU=;vk_F@3z^oKYpiLHvzDwvmUSvp#EMnqY@pGZ0aIp#4$`;2>PU=%Qs>o zIw&Fry5x)ZztVSR)L(fk!u$P(;9P{zgtL>3C_z(xSpu^1yh>cC_FZ9x^Bl*pvuYHM zSGpG&3hn~y_2cCZ#5*>*{_NW-<_F!tvvJ~J;ElidFhpE$5Tug^v$ip**u{=z*kzt^ z>O-nq(}1|?>>)pGElIuIkBKby(d;^o*3W&~o1}FkPoVK>ZMD7ZHXZsJaW7ldTasp- zTGk;>4+F;fRvSf>kBx28dtMEfG^)eXR*MEwO$MJPCX>lSY*M5?4}qFiLtD^Rz9`SO zw{5evUg=yhk#3*(k$yAY*`fl{)0$ZqBFyDuBL&8P@=%YXDnXhI#`hwbcG`GX^IAhi z8`EgdWh8`$os4Z(XHR$hFy^Pu{Ah+`1DKV!QZ z4e3$U6drayKj74N&lVDpKGVoJ z_*<>@z1lkySJuWF@#D)oEh(P;hj1NVgT4_suw13**8y)P-ssI8M}nKpgMxvqO*D~t zPOWQcm(Xs{dRAnw=F#Yn=;L0O%>ob4>CwrU1d%{#r9Hc$$(KQ}pqd56vbOS6kHo8r z#CJsM*nS^Mv!ks!n6Hg>t$w%E>y*+K1R={GF)o&ub#XYiG_G<&m;JQC z^W7%l5`~*jV=krpsnJ$0Nl-1AA7fg7H{ix|WYCMC#IAA>iP@Aj?bIS#&lX_ zZ_qm^Evnhj$|jYL<|W4vT^IDX_$RuG)nyh}IvenI&`gJAW?ahPj1y(vc~j17Ri3Ky z{2Lmsvx1l8zd}(zj2e&G>n@d=c)-(;}H{2hrmMiAIyVgP+ z`|9#K;=q-sTlzkkFN2y}I`lXORV^PbBC#9m7jtw$LE{;CDV5yCp)JMr*%cvXCUcqe%X;+MwD zN52Ce`7-@T`^|nk^I;~9HY)Qo?|@32GZd8k>q1Myo((;`~2U{h7% zG>COWISo~)-De?SymRl!%Osq_8j_$Ig|&Nw3Ke%;T*tK?wX~glkH}?uzfduF&208$ zzCV`bCxiB)9{VbhZMM5iTLt7VYuOy8%wx5quED5Kr}i%L**2Ntc;SJHF0(ZD*|@~o z-qob@-!4*@kAHcD1LW%Wn5u~Q`uLWF7nJ0)T?nSzg~=65G~?%21%edskxp%5id z;hBy1ggOGx#sWNp&k6zd5qEq!6yqZ)Y_Sz;5=`owZxVd>WQF^~mLk^Jb)})~#c1#Z z@kiH={17RRc-lAu!swGfC~*^`v;Ec(Z3qK@RhFJQ6$6T4;XD@^mTN zlOv1pvMhg#RQ`3lV65bbABDsCv$wzksi2!#i3T4I! zQ8|%S01afbw2kxn&^6!5aO0YQXGAA?r$-*Y)+S7hNc9}Mp-d0!`nkXu zFr$@TTRP>~|0{2bWx>FF`&EmzE)QQ@eZ|p(;;-J@#b=WkL*O9-vu+b9Nb&88RfX2q zXTgm$ZCZY~%dl5a9`@p$Ou;qVEgJ9L;sdtTF0v-*r~x3sb?rNS8xPwc-7MI|s5?bb zoGw^hM;AE?Kej)RU&SK>QqDd<(%@&p6*e~ zPgQnVu@x_VSAov>ML-GR{qf^E++#v>aKbh9GdQCNL!m777- z-C*@~nv8iag_4S&@-IGUeU8(2d*jmn*^5rX@55Is>;9GmQfUIBZIV%mXWF^EoyJiqvIaq12djA16c__;-1q6Vq6pw2 z=@WKhUUulPD&9b3aq3r+`@5tk1u?P9SyKbSdcrdF>ARQTO#Ps=^o}N0f=d@s!u^Q(9m$99J zTPwCH%8q6A)|G9d3H^<^!9rj{L{$zh*_P#0%$o(6QOX`n>geTs$ca1Q33*eBcvoyu z7gpTnonz>_TT0ps{ ze8GOk*dPU#;6+P}OjHy3F`Y-^9po|S(d*7=G8I{dN5;T)JEJyT8n)EEvaXpiY z?KYN=3^u!?Rck20(G}f&`o3qciCs;C23<5gL&(Kf_K~F_;|l|^j%(vmA7`%Qjh8?- z4#x!B5Sz7`<4Az5);Yse!7cZgO-X$=U4TztciG@XNt8oIRg*z9&Inv(pXT#JN$J~K z@wkz^Fv*3)-&Imp&ePk>?3(bMILYtLWyC9Ae5T&17SPx|hy4z#rH#v8+TN|_b}fL$ z#6rcO^QFB=8WZJLOS7)IzpH;hZ^#CGvMYrq z_!eONO9>)5gRJg#WgoUKevOx}G@J)4&iNh`eycxDaFMW<@lNn7d=gme(2T`D$HZ0m zG&0g!;Z+6=6?E*GQ)az2d%7j+M{iA_*;w=X>Z9zHse_pY;c;nuc#Wqt9;-b|7|G1c z%exv6!Q~<@5wU%Qq*kYT;GtwfLQt*{YBg{-h#Q;EV2;Ex(9$NG`sm1x%y@8rHd&_5 zbQbyT+4B|>{)U631!iRXWOL5;0H8&Mlja%eBb4WlaOx!=^erz_s)*;H+Cp|ct`~#C zt?NmS(&**5>}yhMfL&>bhUn&-LRrmDy|TTl`JdSn#tALI6g{P{L{_({zOdzy(Tl@j zZxo`kGhG~=rx&N`-E;H>E%}Uj4w72q398dls~IEX_l`Ok^AXQ~E8eS;#YC z^7yhjghShN)9N>i2>Ii^j{43&bAciS`{rC3eS*e_vwnS|sU_qIZ%+Rb`|am(MO^xl zUyuH8;@kPUiH{cJw<_O-_jQ|vUp(dCCEw^Rl${q=c*?y?e*CBYv&Gmy^{r#aGIl&K zaep2zIq%o)hL!xJyQ;Lv?rF~a5_@4cUFc#PI;}VUu1xf@1CyOy&N5qqCaZyiRIDXi`n?Y%k>ZPtG^Zeml*$H{deu(ac9Pk zH6H&7ar9rx#@9O9{+(t;$b@GH*X}0R)+LII@P|OoA;r}R(F`J7Jn|8jd-fk< zCg7zT!hWUq#lr9Gxoa`v$C1G4%cmN=Rzk zc0xtQtd_=MavfB?Kd5S9*zAiVjB;cL4)D}et#Gk-{)$tN8Xz=Xp`0qqWMbhmA&aY|7#u`tfWD%^xq1J zPZe6xm6ek*Ywv~RpV~N->ntQ<8XI{pgT8!EgYt&9PRx6E`S)fPg~jgXwjXq`=3--* z2%_ku-=K-e*k9}5b;(mG883!3MxS&01{Xbb+_`al+*#OPj|ci&e0>g8L1^Nd!-xw4W(b%$n)DLrMf z;)}u9@RW)9%kZyPx0~MJm$QjTukq@HJ@y6LG@5MUfP=bYd8l(r*}pJcSsDY) zoCDMjrNfy9U0GM3fIVimXO%Z@*VBkveyZ7W6Gjp~pezITOes~#5ewFL3AL;H7QOh8 z54E5o+}D|A-}pjK=V$`pq_o3h2R%K+(ixXbX@Bo!NDsv9K=wG!weuX3t$(4-Sd=W@ zstl3~IpXPb0WrV%`r?p0-z&d(eX7kVYBYh4$-|H1A+3TjWrcacysV~aIZV%R^JII8?C^L>Pp3z?OJ$o`6Nd53bY)EfWqVLEXufb3L8R+L?77AY2(M3*zXOMxb z87)7K{P6Tevc8UC^K(m+>n8&#Ut#!zCUUB}r4+oku)X4Afl$1)2{pDu(YK+)#3Z`V zn*t%Wn7sz?UQm|#*C%u0OzXUF`DebWI_imJp;EW=H@5PpC?}M(GflME^on3p>c^cr zs5{!q9r5{@jnQ=00;#yta0?Njq08I&1RKa}JzZm3h6g7R4c;Q2-qNQXwPDKaQcMA) z-+ULOFbWJm`hut)9gf4gTG7yDbHQn(I-w7GjPb?WQDQ88!AwOQK5@#bKmQB=KX=Q2 zt%?rRvwxUyf2WyJngkx$|BtSqndQRS`-c*=GLOze#J5a4;)5|8hg~S2pCw5Zm)KVg(6+%72_#{$ zx3h^h1V8WQCd^g-fiFg4M9g5p4+x0b@TYahjOS*|fqBJNKGEqE)k$$*PGhd2sU=>< zvcEnx^YT_#@C=rKeaIU;B|AmcBO*4+7jHkcut<@yyKACIBi|%}A)=pqU!)ge!c(8@ zesFGUvYEgHp~KN>krH2KS*~0&J<`AeTDbSm;ilDa#?rSqS&qZb2r3;6$?B$8@kGL1 zd7Sw5YRPj%;xm$w@sW`{`M{ggR(u#4Yi`cFHO5|bb@q;MPE;Y@B(}h{_BBd4{4?o+ zf-yuVJ5S%;ox}9i{otA>dDtrmE?xMqSxT7WD;^%6(#VQ&zJj(hlsk#lYvvLVv=|e- zN&x`?=b&nlxSuWi^`nK`O5+M4A(s`rUcM%AkJM>ZuI;Fl84@274-1e&%-7GCHj-fn z=LbYOwMRl|bRfiVp13#qX#q3N2jBB`6dO~F?PB>Xe zMt>X71E?VMEI>atf4BUx`&RG}!4MK(==V+NQTE+zCA@l>yUyW->-?bcarzR)?-sW$ zzQD2L2X_C!o?a@p@@~2Qmy&N$kL&*i2zYo7d(8|G3_>`gPCBWd&2+bgu9OIPrA+4j~h{&zuaG1{y7^#-(Oljz8BWB z{~tU%f5b=~+n*HN2SyJ5&4%VicE>78{P)+F_5U^T|G}>8Sbp;76#iY?C4a99@$f!x z_snd64AU)>f2)kUH~IT?|Ak>0lJ~suA0a>H2j|z1n;&H|&HIWQYocXj5jSF)^tCuC4e=i>|1fKk0b0MiYSjAi z_V=4Yo6Q2|c)La6uRjmFAIMSn*4Q2DZ1k1SIju_^GECNj5QMG7_-HOuN5=)Cx$)sFW*YR z@EtS%gVOK^rT9Mf3(Nc1P&}4vUb{cupxDCrf4m&d|FLd)@a$hJ&wqg={{z%=FXI0M zBKupnk?XyPzXAJV$R1ricOl|=zvwu+44O5=R})hz-fK;o9i0<=1hDPjxQ#$E20WX7 z;FJ`{wc^wRyzf@&t!5o=sqe&2Alf-bsH#?Ubf~d0tpnLj4lz+=h*GxV)>*19+iaw? zVVBj`9PxQ|NnRdb>0VXsVk_?X%NKZ3E_kZ>1+%FH?E~=gRUW#>5#-{(GK(|{1XOlS zR~n+rtQ5`Yr#n1@j6--%vbC+CY;CSNX(wF(zPDl3xmTeVJ*^%V25H1}uDoOd2@cbV zKRWVjL_j@2dBy;MFGXxsjSic?V4l$&wiybp!kSDBlpRl6{Y*E8Vx~qSqR{Ykj!4Wc{td%Fe|`b^WxY{fu7AiHW<3 zMczP-;u(<;2Tp$Wlx>2qD(8e7wAEeZb-5QZGAgMT_qOIIIG}(sFjid${Ly)EOiC}S z5wP>g8Th%nL6kdDhIV-Cegc7ZTbyh;_*E$fc`13bQ2E8Kk$GQMj%@VzHBB4TJ19^- z7PO+%uDi3PzRAKB_9HV`bGpaJ*|aAOk)&Giv-VNbBV|LnwlTtkC>~`6-r0!wOMXi( zXaA~i30ZOI)OHV!J4ziUEoZ@|!pwr|Q3;w~?w-XuHBWYFo2bv^aTkEkjj2C7-I;c%iXQBg{m}Q^`e>NMeCx9n zwX_<`_K*@=o~DUIH#@4kbs6y>zm9on4mu`%o}x_UTN?EJfb5baZ-Rtr2Tryd@0m+# z+b)$Mt(Z<9%DlNupS*qDuGk2I|4>|&YV^WUk4uPDiR08L2hr-ht`iyn+`m0C_pbg| zQipFGxfBVSgTj8^6Vh+U+*#vBoa>%h=Y64RKh@HLOUduQKp#?Ii`4;xzvn$=q&6H3 zGhU){vr*}7mHd9%#>!sTNM9|i1z9(6qP&Qp+R}+{@9na9@5oTM+iWP}A3C6rZ*g%( zCaUou)z|p(yY8A^3IDp*M_c=!1l@|y_r3j$c`x!kpAKalcZZzqt(|o%+oVMMZQ-lH z7H#V3{rhhv(v#PmK9z#i>KaF-4UIEN_fi_4ZQ$sYk#jGFGcFjFO|NbOKR>(H@~{ zjl}t!dtn{T&>NlzN++=Af~9{v4yvk#Kdty%K$b}Y*gj@T?>Rqbyv@v$jM8|ZHQ;j* z7?#QhtikY3MSNlyvNYvFq-05Y5iD4EPo=C|xvIR3pFqfMj!kt+9!+G&bRJ9)`Wv2O zeRJ;|R^>)9s%bw6xG?Z-;e>lM_t*GveJHq}uqmv=5Oa7?8{91gKOy}mJC z5{n)uLo6)wTqmioKiFM^YGYz=IPw*kE>2!^e1W!!&$CoGE9y*NTlyX<)>EgDlrWO_ zHZg_?B&MJs27FoYIG|H{6(wo%oG-gC=Xz$&1(3dmC^x=nc06J+$VSu%)pOAw(f?LoWv1Sif_Mc{1vT zR9~S;jZ7Hc<56D0+&S8wPmB==|FdO@^sR`5!evE>=&3T@)3?;-2KNw!036Zh z_oI?Y@gdm^Zd31+UII%glNR z)X$s%^W9xz5$)^vxqL`dx)*mMakpv57bDYJ(dnD#sYJ%aVO&^{@hwDu?w4~s z!>{JXUF4tVrxzg^g&#-x_$mHWG0T+N+}=DKt$pFsrFo;}PTR1QQLqB%llJTsd;{o4 z*(6}=Q3z*xWNs5%WFsfm;i7E)FlNs6O5O_#vCTHt`*|hFEaO$|=!r=a>21YYH$%0h zn_PTae$!&VZMIuqjmAKO66u7dT!xCD65mg40$;mGW@NQyAXnq);Yz_{!s_-VmnICj z@T%9+h|_G6V5U&NqdK>}E|z2`y;P?x|A}WBenmoLny&Zn=_pa!@!XIKvS?8W2TrY> zduy51ZZnqt{C+CCA&uFd%E#{>eO1}hJYt^qsh%O$R_=-CoiVNkKa-OP4U@8%t*GRa z_6h}zZasAUw|?h$66Lv9s-#)$e&w>>R9wg9C@tnF(se^H=V;O-j{7(8vvFttN^iDj zt%(az3tuMRI1;{H*kl?EmMzf6v13xCIT$LSRf_xt2=BZOqbqOs=QQi+n{aowv+mam zVxRa{7%q1CaDV>hC4=I}iORd6yO%$X&**rv4#t26JR&&}((szd3iHw|^g!p$@<|AP z&X+JN6baT9#{OdxCiF^uLKEFM*7K?zo6(xM>`g?cuIN6~VlLEu-zzYAW}%|sNmxj! zV@cW<6o(D6x~`QeRREn2sQcc49VV{q4+IUG1VL=>(khy|ROpceDGmi4y6nCvozdkt zFN09QHFq@v;wZ$)v>C;N7nx9iS@NtzCtc8L@T?K?{*_n886Ii|bUJe-19XH!=Zk@> z$+NUlz8dZmP0|b^QtzI`70*h8A}eyvUH{tKGIzxN(ql z*i#DWiX>!j&P2P>w;9G4OujQpY@2*Usa&I0rNWwbcL$Q*N@yO|zAgz#?W`Qv#j3&j zf=saLSl=bV725ZgF;$hsXgZ4+$9<;<`+FKv&p6X0(BV(7%j66XzqhGVd*V~8XHRsZ z&KcOhmlUVP$FUK8kBau?BYpIBl6Ea@ALhTO;Zqc@QvW(J-7D4R5&#Bk64LT>$O$&) z01O_vNpe%@Q!fYFjNyMiD}Glz?tbc+RXb`>hQ7bYe@L((OeHg;vW^#=GTdd8Mji&5 z;L@}{i*SbY<~xzzE)OHc6=sJ~Vp=HYu~#Rt88Il*yJuo!IH(lCINpZ#^vIlrGngt$ zun6*=y=ixhW7tz#Te9wzv~t|L>UO!*luEs_mb`lmBa=sZ~QgpIw%q*=M>sjukI{R@)S?wQI7H!rQ+0NJsi%t@{e+6#=Obrb|;Yv^dv0NR9&e z-F87rB5xEaNgHbY!HIa0c{+Ha`vIMnaXS?aEl12)_9Bf88yEWbuI4@++}p(AaFtT< z47G1#vhnO4hQ4##M|%!|WoPTDpt~~5vly2&RPcP(MA`XR#!;=;pgB1fpC=E39d7YF zQG*vvT-PdBSw7x6(W=|&n_oy>S_%_vQ|I85*GV-KTYX8YyJ9W6AUQf0(A9SCehihI zcl2}^qI6cn26HNn8jB5Pwc48N7zc$kZ`zA5a>&6=q^I z2{j$_eRdKluWw4t0Rz}PC}S6*Ys{vT%FiS z3#k2||7*L^T|FrVMO=G)Oi2eBY1iIET=pzZ3Dd(<+65@yX|hu$bhT8A_hhyos-6mJ z0irmZaOfWn82)VhAz(7E8@bRDzbad@TI8ifaJtz4*$K7LH7DcaifB4)mmE}6wvdI< zHI~rwnZJ0bgb|pYtPZ6F@|9GjJ^yL)d}DUY zjR2t@x-S~q>y!4?j3wh0E779_u3P}x06&luxSmk+=PEE0K;^pJN^T_Abcs~FTD55d zJQd8|{wNd}*HJ684hN}jAgeazeS zN`63Trs+-vo+BDx+THz3*v5NMMZrI@a7@_r0E!W_i}3#8J^zclJ0>u{jb025>f?ONO99uERC z?s6&(v0|ISJ8?Jitzp`FHHZ8Uvc^DOsosS?r<&Rq`a>nIuO1Bqf(_T(&c7@m<%QsC z6$TUH54_eh%29>*(D~VSl-O<}EJ+J`z9v++O|_p{{vW#DI;yRx`xXTX6o=pxDFpZ8 zPH}e#6m5Z^O@aq0PzdhsTHKxB?$)A3f?J_Dv_MPc((m`(H|`zpy}vR}GP2G(d+&43 z+H1`@SIyD2Y)UMd0XQr4`v{wutY!s}Lt_CvD;9nd@`m-QJTj!te7&hcEl-qw!dAxJ zx10jh;3|{ixJ7JdG_FlnkX;Vdse~+~i}KWJI6YUTPrOnOz{DyWjsj3&hk?-i-uqMr zI;O=-M6{hqn^(K$tJCLOP(5hj+C=(DL?g<$D#Ft{0!X(2=)+?ABoItF?^^hffT~7(6EEUJ8TUB;UfWM6qEwg_pp_+Ke zw?ls)ds16+^FDuS3Yr?4SZVbt%Kok3;zr)x^I)!kv6dddo^P7U$OoPriMjKql)LMLxu|hQe^$3t@DS;Kk(|tdNiU(o!H@-W zY$S}1M*{4M`5ts|3-$x`R5h<7ub??1T8e~k&LG%TPT3U^srjAA0hzzQpWsF0;l!8D>Al{Tdq;k z+v`llg$py09Xb3(Owm#9ceqTfX0NVQ8XWJ99n1nMKTe!})bQ{|8f;oKkpjvmi;X4N zIYpejl|)mhGw}9LHA7r9eA`0Nmdvv&>O`U+lH`muTD$jV=Nw8;0m9#Fb_6(AwTmxg z_dZo|?mtBDFj0-*R8zG~xshxIw1~i?rvqneWEW;8VvmYQi{mI2+UtQDt+{?I!y0w= zi3ySi&RrH|sZpZwvs@JzgwGwZu*m3_!B7gfYRaK!W1YP93aTxt$X2I(h;|UFR)n>m zDsA*N&$t$asz2x{slxTVuLDC&*!TEl(ob*I;>62#Idwl@iyW@wxxaYP9SHHX7Z+Y7Z(vmUBP+Y3g_}~ywb?Buy|BfKoZHyRhu^SR7 zKa428%f_~H_l!td?LYkGkOSRUz}o7AKLKdmeG+xa^05se9~M?`lYQ{^6|MG2I{TV9 z4!msF*o*DT2_>xC7?`>+QQMCk*80)mh5Cy|eQnmtRB6&x^F#FbDO4OH7809&zH8Ts z!!NRG++o#TqvF3Ay=K3euNkK^9TH4&e@dPd#hOJliuqM^z;~B>Q?b|aCA_=d(u`jW zd0@J6svoab*+LvM-3=scw^S*P2JE!y#Zi154i;510p{qk_PA(~0SR+38kbr=ktl;G zjoLOt$`@v%DL!7&ASWt-C(0DP{=yR_p9p_IGX_J_PuuRR+!drlep7pyh0cSkIm^|W z^mS!s7R zMn>!tIDTrX)oZ@Peiahs+f-E++_3TZ^{l`PKsuccofp;$AbDWOMVN|$Y-A}8bCl;w zaml>|?;~RR%d~s!o7g9DTsTkck}lgTCmi{`Y{P36tZicE7~DX2GsXU9{ocgjEq#X_ zU0&=s9;(o}@VS_{PnfBIu({y{TUlaO5Gq~#rm)UlT6kfb^2kNRP1UC6yXd;A+Q{NA zCymdK{pwFca@1$2m7bQF77eYaeO*prXKtE3<=t;==Qtim&+u-~O7`6)TqjVTnp3)h ztaPCU5`rA|D9tR_8-v$}h0X0F$u2sqc6+TnM`aIktjiS4cw9JTlZAZH%s9;V#!~E* z#V;PKgIxhtCn2bQ5+v!n z{Jz{o!DvX~XUcT?d3UDAy^~WuppEVcpLQopdvw4e7v4}qe*wH>oI12Iz+znPm~8}C zJZ6?Fq&aHgeB4n$&0t^EjV(7EeF{Rb)j#7?XiA;PF1X<<`H~m2-SPv9RB`(jE%3Fy z3A6BaaU7MPD`w~Fr=cVt4oRQ6l{or}X$;XJM&6b+ZyY=bM?ocNh#jU>{X=IUOReD& zE66g_^N|DMA^bnZrX-KlQ*9=<)X5_0VTW}n2w=!WVYETjm!}?C0J`zGC3LaxND=tm zK%=bA%==lr;sOVQyJRY?=^6HK&=qv}7BZio^z92tFikq|t#_I54;2CEp>0KR_V?(UPiCiGfp<=J_Ym=2PA$6$o@BP|*cKe{4u7hfQRQL|)2r67?3)+6D$v`!o!f9c zMV8s#$#iKIkiTu;1t_07EQT-NE&P>Oo)LByg%IoAqtZ)XBX{z_C*@_)h%>?SFQkyS zp7v5^<4N>#mTwQC-WF{-??t@6YNsu~RQueRy;KE2+x) zqu$>ccaqtaHk-dHZ$FCeoD?2=t4`dcEque=L{^i<-U0#n*4nR&f3ryc?E=nTY{RM) zNT&13icO~|g$n}fDRR55r>=}^heB5wmXEOqynM}rhR52Xn_mgXDiF7XBDyJfn^{=L zoIe@yP5kuxK?)6ql`;*+a$SjkD@zK2mepE3?(@bt@eTol<5- zYR1U)bw;azwqsc!SgFOpd`7>k@|_~G#*x!xq% zu1{Gv!+k-3OiUWJN+)%5AJ1P1JdT@AhF&q)}Z zyQ>8(vW9Y9=K1oCOS>NocA5c+sXH3++Tfw^AU+Jz^Wx5%3CwE!A>1Yl`nT5ULRx7@ zaRbojo`-^3Oah-Aa;CCcxcmps<|N9&fjD4foP1`mtZUXetOv0yJzIpC;P%u$tWGJCtkfEYfW8F)wzr$5=~7B$NqdCBh=fxe0F2BbAyVq zlw9E9H=7@GNQ6x8rqsHI$)~FiPJ9!AJS9K08|2HL@wtZ75o`Dvk~{{_1fNXOzA2z-(I-~M~AMAjtXlL>xCkkA`6D@OGM*Bq(w=3=fdgnQ165% z3Na|0oNG)^K(TJ(A6;M8f>0A$P!-kg$dy#sWNawV~iT0=tUz@1yYKJWw1DaoY`mnqZ zC?OClk{lpNN(3VaFMDkbv?O+o+6cf?5|SV1h_)EK2BP#-41cE~U%v_4WJ49bk>DFy zr}c@hH_1vD_&yViQe0-GT2Z?wF=6T+z%234cmt)eDrLSP&Td{vai~3kN*g4&GV_2QByhfK0lWkI*v6fY6zBGmPHoHjF?e>%H2V(9ZI1JH!e zJl4T=MJ-gw0=bW;IBife&px|KYG2)~1T&S$3411|D4Gpb%MBP%P{}6$sj%uihTGJ1 z@m}Jmj`H~!=L(;k;ogfIoOq^|S1EiUQSJOYwI=aE6Yjh7hGMngX9^WvZXj%YhA?4r zR#JYhWS>CiFT$ZyVD4CndQGRe#lo(B|5P{w5RbI5J_pjVn5}T&;zGXugI)IL?Js&8 z3yIYHM29V2N#}sW`3FLJq)-z@#@cR7=#%!cBZcDEDx4%cY1&hujT#}(HOo@WaK0(# zc)(s2P2$LI%fcHjuny;kz%!8$?$l*CMsuqNTB63y0bvEs0K9h|uR`n6U(L47>^TS9-S19jNhEbC8fS#)sg;IZiM^qHsaE#$Ai`LgJ zpHbWd9UK;JtJtw4(f!=@l82M+NZe^8xxquRHsd7i+UPRF27~PtYszGeKrT;QH4SY^|qI zq;`7tKn6|C`@M!foWE%6^`n8ngo~>`7vEKve|XQiG&hQ6+m*U4P@;5hSml*A;1_1U zBdF$okkZbe2LU2;axO0Z2K0<5*_k$i7E}7Xq{1z5jNq} z=q3n>9M((Ojjon&eQs^4IybOg1&%+@+`F3Ua%O&NKkO)%7tlZz7^jv*(smE)J`QHM zAl=~Nh`ruvS&gaf%5Ev#zE{(x5Vi6&raYpYf-5al!KPUZ0=|I8=#6ypA|Hjaq=7QR zN_X_oLPOVCbg4&VkM3Fv1NU@s#e8OQNwmV|adNbG6?@>K9s_v1pFl0gGMC&4L}B%g ze=tPEh};0QN|s%92h?+l6QwHw#a>E>AT?H*pncw6WIPBh50IQINM8-UwTa*Oo@)Vb zRlZ88y)g_=v41@IlnacA?s;JQMuXggVbFrZ@z)i)ivBDXUNJaXvLz;2kSj3W74f@2 z7S{>xH2w=oW7>GRv2eu-%dh0B`a1N8+bqudA5}xl>Qu3^V~NXEeO0ikZ`oo7czi@z z>@~_X>-v?uS%S)^PPs5VsSo64NX?*~_}@foU|I@HrN^33>os#SLyv0$WTCV1Ox~e= zlQw)2KLDxRGtL8eRw)_BP9`G1t{av>42zz=R@gZ#n9X2gwESRs5YxZ+6w)HOd8CcH z1wo0s*1(qLzH-jD*=5I;f@eMVAIDqA5`aSZ^!9B`%SK6Xo$ycn?8vV%JC%W2*`2i$r0_tG{h=*>f~f4P~(K z-{SQXtCp-ee`fakoxMetwrb>;s*f9xXzV(m+oJlA>lK3~29QIzb;q?~@tK11XIq~{ zA+jYw=vY(Tt_ztnEf6{@Ei#qo&@f2H%gV{yOv7#A=wX5E!DJ=K$RE$eB3B|)9w@bX z|6-v{wmL%JwvB}r)GHK3=aZBjuOxelMbh{>T-~CUtFYCny6tFbZAjYejU8R@3}>;4 zm^xn$LJ5yKI^HSqM>V`j#1iroc~yL{0v38EjELqH;h|@KN)JggbR8i_AEu8vm~36e z4UJK*21!&;{(w{0j7a$@!MHJ1w4X_3pF2loKR@45Yhf8Qjz(JX8oxJQ&j!DficcXH z+|8!$ylT%WirlVXNy}MdwV`cID}wH6PY2`lvq}Uv4};`@kl9&6!**Rdjb_uqsoZv_ zfY&~Gve16(7QtQB_(hfJnDAP4)mXK~fHK=YY1RF@#t8!Fj9Slt&Fj-;MoE;$DJ4yO zcvWQGSZXo}A%Mz$+7QcZ2G-fO0$D3*4c(CURhtpvdIF%1qKjNAby}ulVP>Jb-nGln zow}=ht1!$Xl2+uk)_QQ<1@QXF{ zB$UlYAt8xcWq3+N#OQW|eOO0v>GY&*$pQDLkfzoGfqJv>cAq`PZodywHFSN$3Ni-( zsLD3*r#4hhOr+oy&Sz(~V45?9!s|qrZDr`u=S2%nKQXt{*l^(-uGFi8)~N7lSu+fO z{`n5?=COtyP=qt9SgV8bDIE({Dwp@BqVBpyL=$EQ$5aP8%})iO%amr&6=(=htG(_+ zVcDt&$!O`Xam_vz33&N#sEPygc+kEnOW`V_3LIS#({#2|^VwBPLi7wBW5_?EVn3Ff zd~t9KaE#Bms`G3==(vcqDt~yvDWCe=vT2K^Wb1L9P(WHaFBxA6pA|#q#^5dth^WBy zrg0MZx-v(AYj`+W?{LpnnC%LWQaBuMLm8I{1q=O@1c)7mY*tX!EP< zJ&X(wDvy}rwO7OZCBZMP;LO1im)IGV>|3SEM+G`Dv&e6Lcby;6A_CY)(tHofE zo8a^^>_1DHC@>nb+?;sd<0R5D9>XAXmMbk&)s8K&@kGQb6_w~Y|2Ztu^sRhTZTMR; zDpa&!uznjA*v*QltUd6?Rh^8mO2xfa5(DKGRc;#it-`x2x@Ia1&nDMIUs7Mj68ENjAVg)7?W#X(2bYb*UcH~NR6B-;4iWI*MolTH^38f>FUqPU28TRELn4w)@g1=ib51h6Z%vcr}c$5MLG)} zELPvT&rrrSL(Hwfs)Me^c3^GU)Zv38N5QxEZYFrvqSNK(-CFHp6jp8@#IF~gmk_2h zFkD>`ry~Yc(hZu#uy_QbN)z`ZmGpL12ril-&ndz7GXocB}s?UI85S{-G1NnOWsQwxh!7PxZ7Q8p|u#l^2e{6*6= zvoqTsx6z2sX=WLxz}k4>J1?;1sq>Cs5x6nx$iQJG>EfuZcDiPj;iqu@KxsQpYrl#5y}f;k~Nc-NDjBOU-tgX|ujpxRl!_Q=+D2ecMm2#g)v|2Org(Xka$Om~v;nlmdmAyu({FH-&siLH6C*pHa~S`GLQ=#9r3Ba%leh&as=P{6%rxQ3E~;_4#?12x#rFtvUVt^3jgf9R<~xiRF@dx0o@z*q24XQd1b4vDbE2aEP!$t|7$UqFli-K5ZJvV{)1UJ=4o>ghlVLyHkC}0q`RbAO1S}C#ej|{t#P-- z<|~&}=YEI8)UDWp!OCw6Z&QPRWCx|r_g&??8XGbOAd#gAMJw-r@XLR&Z``K3C(s2$ zKFlq08^M#w&kuG8PH5k0WpLA~N7E-IXhx>Qn^`H~3vo7RF(Nbz$aie<)9Li{^(tvV zxRba5%UGgTfS~XjXSyXxBxtN_%cQDz^t!?Uv}Wg|?uVe|u%>2t?!cce{k(nC!&08<|HC|X~(2kN5pm&khUfVjtB;S!@q3IS1UgS(ea zHqQ8$JRtaarL@ScSZduAjl|_>9{H_Unva#|Q0QgoN|}BNCr&)#a*hi6sX+H-+&|BF zaypKeDK*3d1$bE3%IJ^>gIElV-$P3hzgrPdx7SZ5t{Qq=}-? zxbDDeU4)XHr#Kp%zPV_t4DYZ?{ zlUNP7aK-gIMzVwu9rTJK;N59;{DWcHqt_olxwmfc0_q@zA*7}>IH8s8QEb00en9_o zy@qW#^JkyN8+bHR!#fz6$KwvR8mXD~ezhEzH+FY7S=wkf40LK@@6hU&W$pAzK(~;;syq!>*35Au2f37hK+X=*YbB+- z+J6Jj@#5Ui&#_ydb=2Rs?4nu+osoU`)++VjqWs^?o=_3K1io}i;F4;*QEBhrPPDIz z3`yo&<@p9TPA#KogBxM=#H!CPtO zNiIrlpQjGwI1jz2PGpmLNakCAoG`mhP&&P`Q_sfvtTnT4^IPMmEloDt*}cnTuzd9d zug9@tn{Vjuc-v-P&^`qb*Xz#*S+*ThSfyDVn0cA3Y=UlSQ?p)BdhJBQZ|PK`CB>Md zD@SzXjyaIPF9JH*k4r`@_3%6!I?6H*gBaQ{S#!>LlAG}dSMd$@)#Xv#iY39ap-d$s zIW$bk$prBnu1ks|jV^6E>&J7tcAMrq<1Jbyq_orH#vrVH$Jt9(tt5Oq%M6(R*T<>b zG^-cny}jqt@F^C2!`;uR!^~Tq<5`q_?`_ue@BYk|aDfRCO&Pyp*B3p}B2RTU!m#kz zk9lZ0dR#HBVGBBG-E~ZYf~XM?o|&XHi;*SrlmELPk1KJiyJ-D8Q9+1}-#0!nE?RRo zt;Bqdvfzfg2X})nI@#~ouA2l~kppUOqX;SgnRzBT@&|PjaJjNf+yPBaT~0v0l#osP zMz`n81E|F0YEEm!_42((SpgKpB|n(qS)#!^k{_?@-C? z#*6XK#*SQo>)$S_=l9n3{JJy18N)=Rbq~<|`-?uVe@n}tP%W5EQVnt9qWTiQmXxG! zy=%VBOv#Vsqp4aRo~Y%A+tq%Gh3P~%#KcBqYcl7CYo2MNzF|j|hgF$|rHBACBCL>E zCXI|Q;9qBg>=3=7#sO!Go<;hA$U#J9uK^upnD1jPkBjh8ydg*m| z!fHJzhWmOi%=G7WxAKQ{RHkkuIj$5q>Qo*2hROeBApM_#(d)=B55L}b{no#gHUDOQ zdYT4$l2{oK)%FL(eJlN1b6=A|;J5x?w2Yg&NVkNq3BUUCp2WU4dVlciE3tS?uLbFZ zFW0w=wyVOs*w+UCzKx*nt@w*J^EQ{e>cKk{qp$7tnbL9fyd8($Nc|}|F^;a`t|=9jDG#k zts^?y{~v?mBmdtHF_-qPGyiizzUcVRc|jDn=XZ6ZWn+3m-wnE%|DjchzP9aSSGKer zgX9GqC8L)GQ>aCT=>q^VC-OslnSsZUZKrDg7^zE$`HyGLZhP1CTB*u5+p1{*{G;e;b;UMI`r9%o0=PB{&W? zmytsTSAEroZvrsEU@B}UlHL&tx!?Xmf+K(ikdA)JT)s znI5L(XqRyxRm{~zD&nx{2g93!XoOWz^N7vqaN^n7WCA)+hCvBEe7uO1l4VzPP4tARV) z8h3W_xJnE}k+^t~r^B~AA3fM2Qe2S9s-E)U?d=KA+h^vn7S`kElS2N)fi#8P{)}|` zgC{c|v+)O-+XVP-AnBDPZ`y4A9$Bt*dmWs6T46pah~vvS0WyK{$jJ~WN={HVUPB}u zL%`eITp+FG$aToo>rg%5@O+g1wFou_%AghVWSUzzsZq?HL-4GeWG}UnPcjr`Gl7%j zDo=pB(ZIkSQ-)A2)|T>W3_TI~jmY5qrjG6*%$_4Y*(F92d;K*$e_ZvwBg68v(KBmQ zdSHwuE%F%HSOkaYvOrnRW{ z0gEhIlC!q>7ajdj={{BY_t2o3-5>5G3m@g1##=Cb0;(hh`7%4-eqJl$AmNIq)2Rt4 z#pIy05-WBx#s}3t1RPZG3dy|V6dGZ>RfNzBP;N4l^BCcbRUvMa&4HmE3%2CH_En%M z&RXO_vzK@BN({87?HuiuUc{S)gHsUvzi7qF!oQHL@`(%1b9CXB@p6~hIXW#WoaxvI z-?lXDE!C&4U1IEp8>TAdS|s7KMIV4d03)ORl<}KMSoy{v!n-8Ayrc9vH6*tHN>{4v z;N`VabjcHrB0p5pW-60)Cc^$g3ca^yFlra*<;9r;NUEd!QB2b6n|M1$=T{+iLdfav*^2pA zTG57i+FUn`8dHDU`a35fGNyqx7l5L;`WA+%8J-8TN?no(;uFm45~FCdmBDYss|EwR zDHETu%08ce_PF8wi)QMSnU3O;vPV`m7`%zwWYIGad!r&-NI8~O$GvnYP*^ENyL>BB z{Id9fNcdix@Ixg}jN>srHlq(DSB&Jx8_PH12k}O<_U3!*!oZhK|a;f};-?G{XG?E`_|Nw!lJ;X<0t-8kC>Ie7ykT7JWh zlJq$1b{d~wc?%EJq&fRLZ zszdJhp-Fezn}$4RM0 zE^-9B#_?Sd3Heq}H>|bb50KyH@`VhmPTc6}jW6g})?KV)tlU+CKe{W4$2UNW$l%K~ z>`@`|da4^NV=qqld7r8u?T#9!=eabd3W$rI&IYTphb60egRY)Y#UpNB$*ZrKozXCt zWLQKnQ9BSUSr|t>0j&D9UCz2MK>zeLAB}DVbL@>}PJXh!wFHNRPz-O(3X{t1?&ki~ z9Fiy6D`$57PUnK_%1W}c_jX&V|lws1P&BGHUmomhkGI}&CcN_y&~h*$R>w|0-MMN07O z^OpoD7vm$>6||8raYc=XU$IjJqw-%pXS|}o%xO2z<9+GXwgwJNKsiWz<1piXo>3pg zMQ)vzVC*3lN;l(vUtP}1wH$kwTNaP!xVZ5&@f*~Uyy;v+2o5TdPvmwjpqU@7so zO;%IMPQ<*%=L1S4Be~~xC5h>qS1-hRM8BCE20Ml~$L7E##?VF3qRU?0z{KouTN>Bbl*kN=R^& z>d5gqa=n<04sQ#8S?7hf(nEF@Dd7F;zTS|J%)X5KS$q#{$9;T1(ZpAaDo9D$S&fXW z-ZzEtE(Ek4_Dsl!B;W|^uv?8+A*5hkY!B=xc@VRq@|yxOr(t6$u4i08zUvt@vcAdc zC3%=V8kdS1Fz@T5mV)h=1HpYr_=8<1wC|+wbMp>MV|YD=`iotBsWJ`PvrhICGs59* z;>g7JW@qxX9oi}2ouU>Xm|?+sivs2OWxe++&Ivp^13e-crj>GZejGmNh?n!r+l+8T zl6n#+^pYh)-U3=`<&06mRr#RKL8KGDUbZWK8JnT3jZA|B0Y=J3e}rlEM;8}KlC-5p zD8zJE<;%1VZMmvZAquOVdnZkzKw6jNi`!Wz+UX}5q(b&(9_C`1Oxh#L-QF7ko5KGT zX#S;Ga58l7Jb$U1mse?&bv7Km{djcfNd6BmCGY+AFPg{m&#zD9`Tn;e1?JnZPQp>L z*|H}7;Y4xiiLzbAOQXUNw={>Wm*4Ci-EuRiOR6zrf(22L_?^E)i{-6}G}Ij;mYQjiGIGGmfXm{KYm0Mn zPkAaUkZKS^;cuZ0bkthK&R0y`yHz>yq-^75;40-Ne`5sni8FzPD=j2fL>^5U$a=1% z~Rvlc$(+tloj)W8+3B&@EP(jGQ?8ZcE!lG3%A3aI^AhZ3c|Bci=Jn-&>6o2NBi z6GeiiDSRtVbM75^*Sy-yp3?H5T=jxdI+Rj8jm!v+XgxOnGjcBiHt~t1qMj2NHDEs0 zf48i4{c6!O#A}~qCfaknN`|O+x-^xKhDc&zN8Rl7NA7pT7Yk%7nb7m?C|h>?St{I^ z>I_*UBr6{ZrY_a;d=-z!p+m)@43vMqs}Ut%WjRl$o@m{vkzhf9ijB-U*GAS+o}3?3 z6lTd4)^~3~k#JM@x1rojb2dV9EPL^tYE?mK93{$4T(dSA_tb-iRA!v#HE{INapFis zX(L|Q-nb)A2kXCZ-`%J%%BxeMb-*1mq-R~*wn`oaGO-3c#yA??$stPbCS_bqOR>#4 z_*@^P;<#jX<)<(~M_>AO3Y!Y6yT4MWXD}0#6)+}mxA2Ru<-Ac*!w6U3d6ZwBN6^=u z5It%wE3vW-EK@(;kQS>VPxkwK9NF|Z#CvT2qS1%ltNu9%4RNPd(fRs`ASodFAVdDM=Rad(CDyOQLjdY)CHs?;>0h7niF{*C9mObZbNBzx6Yk_vH;wBskckOoPfFHo` zww6985msD#6L{zGt?q6Q;+n7{9pMDgxC}dgLomoS>cu|o_b7+^>aN+^P7x2-h9?qJ zf@mVe6Q-#$OWM}YNzkrZ;H`lRljYK29dFPQmrPeOj_T?z&sWoB`0=I|!H#P(7}4nQ zTm<`>7qi(rr8`PF^bo*j;PB~SJmqd2UQ#Jjk6%rm0Qo*N6TjiPT!zHm?x@@}Df$a#!V?n7KdJOohacxE1;P5CLqi zgz-Xqw&Us}*7UTSUey|~nu|l>T@W>*2EI&=2pR@?ffrVL+A$>+BCF7Nm3GtpPYCb_ zN+l!D924B;GYoAezR99oVG9k!+8HJl$edk@EOxfx2Ir^P@W3D=+x04ivIG?fOQs6N z$YO@V;TM7@a1M2R5-=izx_|P1m#6AY@O0{-h0-j0nGd>PzhaUlW$~HHb6vd7dWd2x z{iKbH7}EIa<0Mqsoog(1TYRmlHZnVsiK(7PljUHlE~Ml~@Xfyw?7TNz7 z)&i@T?E&r(FKNe!J`XnO&;}S8-p{(aGC<=1?0RPQJugitkc#V1!8BQ#av-KxRT@C; z0u~pdXhJ~m(NJNPu&N(bxwr<+kIVuy;G7s$v`@!?E0QXLNbi*fG z@vZ%mD+A?9{7{l_#fv#D%N&wi@60_)yM}nvM>>>v69txmf#^|W;}9#$3xdX1AVyt{w%e(kH)4beCWxDy>g{f2o3JfOct=%-%;2j(?#j(6Nk0w$Jw7v^ zh|ABUVgUiEBiDywol$}Mm$Sa`w6DDjR66UglVE|v-zt;azc864hR>^d5kxkKC9eN@ zO^7*lawo%tD(TN)^KXMBO#8le-TbJ@h7lhEE+IS(FAZ^Ij+}LD9A0RWVT+Xt76 zdgIZiX|nF7mYa;1*^%B~l?CRx_wyGf-Cd`1nFFo1FF(Ztv)c(0lU2F21iuLGnE>gQ z=)pHbj7!qo=Dv8tw*-|cpW1UKNH7ihif>{}k3Yq6cJktC^ zX8{ z`~79!cLl=^VbLTZ$8FkJzTyo5_?mF6)h{r@)u1rKeZe=w05Wp=NKuxtJ4eZCSh!Q! ze&_E=&&KuWnV2ZX9!C27=>AVhuf zsfCc%6nqG{HF8*}^w_JS$(cHK%T^&lB)kjv_KRJOcS6aGr5jrR^YwS#vP^|t-+Vcf z@Z;Kx$xRz9Tv{IqXf>49?KynOm3VyXC}}N6$((>utWjEzH)=2=)i&b-$He|d5XDML z@~K*-{Kua<`M)gb1u|=CZ}_O?V~5P$2=+-a3eSsjwo7`sh!lnig~~+2v+P;A$VikY z^nl4BE(A`zm9R%D8xGe|yZD8?hkJu$U=(Y^`f*+RS$QP)@c5ltpNbT5P_G%z9jBnR zpA@8)%7mr;NP)(kE-87`dKloE*sqqY<*n6t{MbOIqtOR>gxb@u3tF&aBYtx_zn*yt z6FQ5D8g;`&%G{tAo`1W|(&UNyh`A~_=!|qG%@Z>sctXZx2tq~nfSDK4uv|(gpC`}M zal7Y75>L847eCa_5*4swC5W)2Uo^~nijIBa*0#oo8B>$yd~Y1wu}<&V6Qo8Z9=b}o z_dIF{yX{8|KLvVLjf7XbT=OP)tTpTp8R3M}>~+7|_UeJyIXRj-^gDY#w-qMOXRJbo0F~ z!7+hp{Pi$aEA=G&~5pFjrTvEPRr|BMn8a6PQT`6gZ} z^431`lAS_b-ojA-`d15S0B^s$RapBD)1f#A5fxga0lBOD6xec(tP0U(C>_^P4z1mI zo@86###T=z{*X*;?>1^?k~3odltJb9V=z1;Z+CQV)GQ-U9PQ3a!lyKnZx~v~DKrRK zCwEN)^*dd=HS%{wq=!7$6ur(U24_wLN~(%l$fDasb38a}_-A-xYE^K5r*Evxkm1E< z-Bqm3{XypoycWNXu=UbzafJ^t9gq8kBeIQhnn|{rR8rNUHh)~TR}CzoDlakVu{AW9 zOzBx&EubQG`NEM8nV742{>R?P?u@$?hoV5wwKjBWVR@;30> z%Z&HWRUWmh+~pT211z!qE2qsj*j?7`__*U_Gu3wm`&9M~hev9AGK10doN}o;(tk^z zWZ=HATBK@CyucaPZVz9GMxpnGz-bA8cqRdSDws|PUkZMtk0#u76<{EE2vHw%(~J~k zzEq0S6ryb6Q|j^c(zA7?ZRUP9xxTEk_I+1QGtj6l%g}0?UvzK+QwX9x4^-0fmzE@Y zP;7R!=?8q#a`uSrJQBU(OF4>Tsv}G(L#RL#yGMo3@RBD6JGlp z7O-x)@M8D8gTg4&olUjV#*h>7P!0BRO$Sy{)?s~pGCEvCTMa_LTcy<$9(`uXH8VD{ za;A}FH~9s0Wv4nJ1#lLuv`xonPo6<-I^yULqlJSI&z^RyOl5i6w#uO1cIa_oWg@XU zfE9Db9NyORuGPHULW(|Fk$6giP-Z}N-2w7URap-O0`7RlSBm#R{3 z!SLNEOBFByVJ<`DLhmE@-bKCB-738Jc39HH7uRMvGan3dL-rfLtx&>L9wHGliXt@n zsdU40Bq?lUv>{dT{|=ZRfwljMmz&Gl2V10hCOSmt2n=S*=}~W3jkFiuw>kd~3NoN&ir0DJ#H0KX*60KQ zt{AC~W2JnJ{V@APun%Zwi|y{qRBzqWxOFB;B5~BV-10mrDdc5c88+W~p&()V-Zt7- z@G93e%f$v|2+)salm`2a{;ew3{HiMK8w;pVN|p?`DtU?4HPZ@Gnvmr*!I}9%pz0EN zgIMtfYc|1JDR!r#b9pz2VO>Itd2f-wUosf=M|roIvjn{q@zH$liHqSphruvj5zdv* zXGF_5hD3!js*IGfuw#|-PIr9I-mg-g@ry<%x>LKZL)$QCN=N1>l=VT{x39whP0=B) zO{g!D#Jw#1NG`M+742+vE>0r3iuS5f?0Y>M`B*@aaBKtZ?vsrfZ_t;`s_M>Jm;VXg z{-+r7U#zV2KdkKO|B02w+Nl3M{oeF_EjrN5maI7`orX6uKN*41#U7=0`$zgnFbZ#N zHfcD1%H}5%jW7m>W6xxJIS5XQ;BV=3)t{%WBOLPJNxLcvR|GGjLODieHv@dTk|BYks>|E%i9yZk(~8mJnCb} zlMF|Wk6L1{IL?@mr(ybfCzvdMkD-@PH9@!qP=FnJk=-4)lvI_lNnX4}2Z?ZkqKGYs-ZIbaTl1?;NF zVObT-YnzqPeJSDTD4ADzqLOWmTveEnM`i;6<3Hp4mE~>3q~<6ZfTrMGB#L7Os-h5pZTGjBOGjas7Q^hK_ZD7eB5=F zoV20p6Bg`Zz=uV$E^^s|H(MI#GEXMlr!x3+s;~6?l57xsGiYU#F%3oTtm`S?x$b6d zNilIMG)bkjm~L1dF$I776uHQUz^iQ?zls48RsUoG8kJs-OjRLFSacDr*x`%=cFow# zEeo*wVKgPp_t8L)!htSOV_8)Ct{_1{TqA_U#3dRCX`vYj!DDt_S34VnWVmAo$y1nY zR&b=!jKwhs0f*ti>57TMD}~Xpu50pD(hz2+W_?3+R;+RIBq|$SKbQ5EXGp}}g;=xw zAB=?f!O~!tXsSMnuP8slaWuvtU?fgvVUfGiqi!Y6DGE9*x(88qD%<#}hXzpCI#kl} zWN3x92~RdH8k^gJD9kBGus{+;r%`QOgdfETkvigPj5sAqJ>!u%Rkhg3DK1`R^z6?> z3ye-jHMqjdavMMw<0c#O$Ea;ujfqjmEEajTMJ#1Kf6k^d6mch_^Q^Sigf(o~5sgU= zrXk8mo!!C(WQ)!xS17zaE8}frY7^Zqb~~F_F?Sn&5)NjRLnu@tW!}x268O{#3{Va{ zGSj4ml}J|KEx^7Ks*N;p3C@d7;gp}EV=9YB=NljO?^MK%*I z@B?1iKl$Spz~C|jXM`LmC=fH;kL19=IONFDgV}{sL(Hrjoj&aIA|7Q^SE43EmX(Vj z!HcAj6jIVveSKfZqj0imFf5uecFZXV%K#INL>{>xSZ^$PNh3~VB}hKxekd>2>wOZPMRI-|Glqh74S%l5Aw z@5ogN7p6_!589$bKt*=>7FuU~4X1LfgnJy-j_R8-bZuYcff^%}X%k#HQ&zKaGl{AU zzA%fF=W*DP&Ae2ZV7EMbe?e9ZeSNQ1CM(CDN6p)}>GwpVYIQ1(d+Z*V7=;BDk6W7t zYo5UA#Q`KUzqr4ywr3qbR8D-rEJYuTU*1B8 zw0WQIas~Z4^PXnb)ZSQE$KaKof`#B!aBNpSr|^l4cGeu18vloI+!$pfQ1Nlzu5rM6 z?E35lGEAS8UQEfeWwuPRuuh8nnc*nQ5~i%ArL=CD1mBPO)R_z%H(}Rt6(<%Nc$ytu zHCLF_j>omzffRoT58+6fl&{lQM=CHjyxF!1-L888@<%;&p|FTsr*X240W>iBj-tTJ zFlbTysOHa+SZ&Mf4V<_b_@JC3=ft)aW&OqPM~5gb*!y?=2H`5QH!~(Yxru=tL(`f`rIB z$@e_xJm>ek*Y#e%zl>ehy=U*4d)B?yr^ra(&b=$PiEr+Pc<1`8j;SsAl8Ne}A&yw* zu3h}XqC###&&}`_7I(gIr$Srq+iOv+IYj$aqzHefGT5}ISCGLrgq_1E!8!qWX9l~e zUv3&*Bq$xI4kD>c-fL+#q0#YjDe|W*lveKuS|Xl4ebz<0AN7rg{s0x_vaI~J<@je- zS!t;?HD$&>_1=F8w1zQ${^Bk_Z%8Ppa$kO_+FCyH z9gP?|7(bXkJZczye3IBt1rUQh@#{-DdDq7UYH%HGL%zr!7ArMpnaE*a#EFt<)ue-S zhw>Bw0;J|^4J%$S_ZN_?1*m^r<5f{D$TeX)+<=Y13d4XNqQ9I-qES(QlDDV zO2o=Xo(uz&3?D;r$3zT!_N^yB%Rccv^1PP?5~h4=+kNiC-pZR7;n3^}W>6`Sb0)q@ zP^&&VV|aEVWIrCb@WCuLp7q&i1xs1K@Nztg#p88_Oar|u^@8$ENZ4& zx$io8h~lAvzQKu%gjGidu2K=3+M8$IZsyz%#6jkM@pM$LCJ47_d7YYzGGz@&Mb?*d zC+kzkLYFTKGIj4MQBb@(Gaw4^zkTQ%W|TiyjyU0ybu)OP1IGOlL$Z@jzg&-b!aX(dd=)Jvt`N3)}Y9VA6xluo`QOov(xt z<2p%Jfcjz0pDrDS#h|x;NgrcWv#z2ig*07C%n>ec3Io3+xqP2kbW$(HDlvctf#MG2! zjMzg5PvJDXOiiRyE>nN65TEb^Q7nr`L9=33N|&RN-3#>$o@_YKJ?6byDlKAMhUibA za$G(bd@gwQ=k)NQ|0`I6S98Dp{ko~A%JPgEoDrItJT5U0B8?Fc1pB8J7o0)8#fN!R z8UxU9Ig!MBV#MLg&%|K3!hHUg<6~WvIMJrz`isHzi(gcISnS@vLi?0QtdY|a zcUsisL}&ZGJP!ouYE4YOSnz$keUUIAY*qqJhknEAagBVo=&z)zf}n&94jGWof34oE zfj8cNx8e=})(NjZ8xf9K!t2ZHWmoKtDj_JH+krPtiTk+;Zu`c?Or(!SKM=Vvh&m^X z1(ArwecFsF5Fu-XZW`81Cn`{l1eGtnevr^b%#`eisnR5)5|kMpndaB!bXZmvRLZ`3 z^wuT5H*^FOztbMSjk#)lSlhJ~!Ef3gs7O!z%_MOfm2T)*E&L{4C}w_HcZ(BOlnfSu zmM+F52f^3cTE1Kw`?rXCqmmsZS}~xgYWR2osg?#Q|Iz?T?SLVfFjqpcaJn9 zhUy2xx^zcchNpij8^}A%YfpqhW6Fr$=Gm4uC&?&L`cl9X9zQ)miM+^YIVUU_Zi|o* zbR!z<^UaTbl833(@;=A&Q*BY0j%6?ql-~x2z)TI)^J;AvdEg`*lIBIYRHW!dID0KwD2pD`;!Ia^)RFj_Q!LSN)E zmFiD{C&d5jg7iC`b6rAt@^ZxSWQ}Gq&VmFANYGuBf=?d-!O!t;ZI=}R*dP4Tca7tj z-5U02OBm04LB5oL{CH#TBbvxoRJOo^ml*8L2#RZV=7w>I2if*7Osh55=i9Nqi=u!; z_B-L7vKexWew%cgM`L-+c-pKT(O#-hhL6~vu_FbXp-6S$2K-vdDEv)I)h7 zZSu%aTu3v`Yap!Evh1_QQz)E2+&IQKw25%W#3*uMAWHRAghw4a`dTSS@e09a^;nC) zRc@EVXF_`VZKqp9a5~<@nC5j;#f4AI_P0*SLs&My74?w7%`uKv$uz3_z@^EHbX#3}E4kg|iv z=B5s;@Dgy@5w&!=$3@FY1F>l=Yjh3EOk(*PL&{%}cnX*{p}C}|ioo*(+-ay#$zTNW zi0VFTZrh@nQLf<)T-`ap^E*XS60M*b!^P=xcSGY3sw;!C&rtVjSA-2it0uU(TibFlDqNRpmj}Y>Ez=uO z<(HQuif2X=-3$(lu)9mp19=C#|RTQ5?)}wA`exFgCivtNzIPtIYUDpDVa% z@~Gn4%CYD0u#BMM+jx%WVqeldaQzv$;wY(v8_UYSm3(M_o`@;2&z&2ZLne+^gY5>> ziAM9qkNA#EwO@p9@)>WpbPtWI4(WVQzFIMcJBseDfF=r?hTZLNY=3BL7rK`5SDg`S zB$KCZ$^qa_%_y`N^JB~xtUv}h$D3y}FhRCOT}NhlG}@=9Je|uhgE{t{Asx7@|EnC-s!4kgAvaRO{5S6t8tR z?FLsw$WH??ZulJor>YD!bivWXK)$gCXL zk%>JZd~~HZe?>POT!=m9@om!X zA!NY>w;uX=CHe5dQ0mQu#A&{vk_{(bdY!vl@){k!-o;9dd5)erH6~|D5Cb5)5exP#;2ivxOhY9&4~8vyxnQE zKF+IYquJOaua`E_`(n1IBf%C*PJTq@qb#VSQ*>+2MQH((xQD!&0eMmx=OGEAuaPQ> zW^GB`&IWqhte;KVO36(f>{fqe$k8q2P)*gyDquU>j#|Q}<%wk>@bhnh4=(s1(>l0u=)7Zolr^~AGZV*9p7-^0>)UjN{X#*%o3h_lbD87IxyhlY3Gpd|Dpl(#Eik6|uUBsUoVA zt@F5a)Ek#yg3%|KO(QIUN zm_0{@`+G78(56{7i!?CMK-W>MfAXg_d!GnL{T{<+U%&N9V&k-*LMJ^s<4KBuc2t~@wF!OkKOzHt^7TNq4SeSGHe!D7e3lgvH-N2_aUEIx!WrB;)7F!fm40ZS$D_J zI*O~T*f5{XZud4epY+80Q<~;+?62|5&Db@J$*~m_aw@X7g<_l*wrlYluE!Eb5+ zeNj<=MwOIaQS-66*|FKU+Ho)pO!dVj($fxU7nTQYMXg6y%1sq1Vt;9Zv17Rm6d+e( zAJzL;4@W~_b}Al=0gXbsY>L=QeUX8$d%-wb;WO?%kq^SQ_&$!)a4`9lQ!@TE5XXpE zR(Y{JhVCJJoHBLBG>c*odBzWt`QGs!DDPMTjLRdraYa0t?xZ6e`Gbb~PX=X3C7M9_ z5>23dMGM4BivDbC_)ER`$6wasHUUY=Xe+wXi4)7b} z7`!9!H~8d>|7TE{C6%$qXv;O6X}RZUhqg(vlz#^Kv-#h~rs+CQ`ZG2f0rJn-|22S6 z$nM{ZkNb`BXY4y~BHN%pZ)?)u_T0KmosD}nIySE9)RdUo>I!zNb{F>tL{|B~L1gZ< z56w(Ue89nwsUpj5IseMLB@$lsWs@fs6AZ(T^5#-lB#c$PEJXGAsGW#nZ-gDQ_Ll90 z&Zu=J)uv6SHGWA%^>@m4o6L( zR(z^jRdKb?FeGDqv6K@3E41#9kp@_CP%iN$)P#T|xz)i-EU&n4pmmpGG!7_vCn|Spb4v zVSai!c}xEo)T&-N;qYqR-ZFx1Z$+0?w_EKN@3>!x%yDa&mLQ^& zV;KV2$PS1KyEGYgd-R&NBv3xK3JQ1@VY#ih=nMbCzf{qjOJ?}%S@TlAB-;gLfRzj? zSu>X~BL9lboQu;$6tdssiTs4jXkSiDW8szn0qRR*Pq6_KS_H>ab6}Wv>aB}+43V&Q z`vTy;d{IPY%!&R&NCNIexRI6JPiC_QB%6@P^Y^DuR@w{Ly>;Fz&l#ibo<*o@0u^~f z-i`c1JH~<|4>|N>LFDn=T+FG!2gJ)$~Hv0=)|pIz5>)zAqC(fISsZMbqOS+}X5 z_kq}ykO#gPGrX;9MS03D&znO_LBV#_h)zjghCDcuJdohY%O(|(!>j%Hx`j}%4FxSL zKNIVhUL`P*wkYY~4m?i4(vn%`BH&UKUvZI2nrMhRecEPZgoOtWj-d1LZ*>GJUA$j1 z<5;fA{Ls|*n&yq7F=TGgEZBzLI*(*{U>}#Te-qw3ZMqnDG5lVG*4b9jX_IE!EOQBY zsmj|21qhR2lG70qt0A)M9^wDEmmZ*piauNkylDDAUu zj_eiXazxa^u74F|0;t`ZVj!eu@&`hS8LKY0#K7TicIGg1;Z|$K% ztLyn|0n}_OJqC*~tNL(nC-_aR7_-8=+_2QLp~~oj(HlqBlB-y+G1ho7ewr}+${p3B zh&C_n1e~uXeVbgi7Mwk-{V>Ioexq=zns^!+dyWM`XRNZ==*qYzf%yie>S;Q$GTXOx z%%r51WB@P7U{?W-r+qj;TKyH`;=;Pc2edpLPVJ_fyZAt+ZtNAdA>^es4U_-npJYWv3d|Dm=J)89Rd-^=FPbHbPBDBI=ATO&e14phN z$`0R-qshHbt{syb`*Om7EOKXf#?jcz-F0Ro)h(U&zhI~Tb$@YM9?S0@pg|e`m)XnL z6!&rDeLJ|0m?_RE7>36v?HQ-iZ^n!8hVD_WG#SdJF=aibHpJ2d zl#npEY`@O)WL|w3^1jc{j;oGX4FVuf-q~nAYw9WtJG?OInxeW`a{|%B^5RornsfB1 zD#n}7=sG;2RT6O(RTGSe&dmZ@i-rMt#$3&G*Frd?Csk||#E@|-nL!huUD`Xizq>>w zG*6Fu6tIosdwz^7E^!o5pA@c<@dJIU``?>o{aM--=I0<+aP3E3E`ou0@5hhpVlZS^gCxy3QHYN6-unjj` zqaE_9y5dH`(>k8-_G0dppBSO5l&WbKKJ%KiV6o;;Rok(M;Gj{8Z#w4u>BhR^ftxkZ zKCU3f79R5GodEYsDA;ksD7c-{@Ws_l^~ z*>kDr>7M7tP;Fab^*Qo6eBi!LY^1y!WlpnS(@`NcKE^gsfNvR3y`jzPX_KJI@Oiou zM#nq{An6#CP!JeRtEF`lYo_t#1QQERJ~2L$^+0k8ITL zH0XkBbP|q(iZ+Lna~+GP$KrTA8&6Vu4-|kgOo9Vi z7Tack3P1=NrC?LK<^1CQWxxXp5C71Vb8%|idVmpi6JT+@I;X#gHAF+I2Hq3ew9oLHf_(WGA zQP7ca`QG&9&xKTrVk<%-tcHpdVDOjet_KM?O71Y5)@d@gty}1iW2B>5h&r*hX(23! zh@0Sk;^`9T#g|knwl@L-o|Azq4VTzPOOBE2-p$TRshB)odXU|TF!O~kK<);f=qL5D zwZ5CG=D#r_vHXCaRMam0RS(DEQfwtfvC>CHi2R$I%y4o38$7;QoE30iffpixr5X)GX>`JiIbnC9s`)is&_Bd#EE(S)FZ=)Iu{QfQ;rSE(0r;c%uDA zY?m>yXCw$Krg>9=z_y?#!i>QEauPNf%(lYT7!)ur4tzv~9o$lrpFNOoPfPtCfT_pE zB6J8ptZwqxUxJzjG$vQyuNfpK6PxN*&`6>7{t91V+Q3w6UUuS_bFBe|+jGFXFu{g{ zmuPHGMeBi!Ee&~z$c^5c(5coVqh)Nb;tmR3G_bx7Bv{H5E`W5$h;1LDNr_TxwdHC{ zI)|Dv&s?{DnyckG)q?y~MTcE&Q9EW>9| zBUU9f*T@wTQPB28RG!0ED`UoAAb_I&4Q+iJnu)vkx{TeEd=y!>3X6e$RtY#m=kVc8 z-vBg3jn8r;yAZ2zbJ3XcmpA2PqX_L%lx7ik_^;eYp60>jSW}dPM1tmqzHGgLgboGJ z%!JR>R47VTfiR$36VM5dn=)%g0c7_l1rUo9 z#l1{_%rogg<;g&E#?I?X-y!9knl5tpBQH{NbQBOQP&;k0W4NjA)H*6@BzI0eXH zz1ZaKz^*4kA;r6NHd0OTNUITQv?(+9tCHcA5Sn5kJygmDM8FB4Olh% zB?SUFqry3BNNGMLv6nT6sO3a&je=g(g3?FAIXEE6%6xLDzSYHI+lb;;x4fhFCUv3} zA7maUqX_i9=Gd?gg| zQiuQEE0r=E*7JEd?)t?%+#zySsO$=4c zaj_yOr6)6}pQDc91G4jrtW2Nyvp%q6Y&2uH0Nsl6cuo-cdu)W`MU>g3BMGe%^nIf@p(ZB~wRlTK){R(~VN zlJ4!#C_j#`b4~$a+dqLf(Y_yE#p_&ZOMOjDWKd7B{2F?-0`N0{Gb}u-W<(VXM~%WjUlq=@yudSpjz9jV427OA72xrbY8 zm0vl#wBiS+lda{@Jz@yZ#_~;b&G(2rJ>H@`WfZ%MyKZml*Sf7(LBjR%tI&c2Q|EuvHXq`t0;>X*H7`#~{o zHPQc`2mF73K`3>}eYRs(8z|kh#a9;ms3_FH0C7$Lx$Ee*%S-T%N9QqCTUfKgv;M-nTl)_Gv27OI=USMmSl^zgktg`}HPMy7I-vs3H1wD>hC=_LD3<+Z8=!P^qd(L^w@sjC1>&_*?3#t#y&@$SK4Gc&p{P|UF`BMi&2)ke9#j-e zCM{ZUoHl~qdyp;hJBk(|+jynCD9#?#?W;kWMX%84B#U_VvvO}RG--(ch1FyVb z8ynC2{n}SgH*y@ZqQV%S$AETfO!z$Cn(1qeCoY!*^9arlW2>CmxDK*!&3`HYH+JX1 zcF?K#r4IW3G>1^AGsnBIfx}c)VQ-98MZ-f*gGksYg0KVzFJq?9AKco1Vs=@~FU_9q z-oa?ydN`txy}5b6h2Y}U{5(s|A0Z&iWP1Ge#qrd`^u&aAmZ2!4&9ET^lWGlFbX;xN zwxn&a2;=u2qJYW581}k=tVjq7^yS+9#!*hWzi_rN4%F(&Sg%MuuukE0+(e~lTh*_- zz@XFLZ(u$cp@LqRI1`H`yBYgmM&m3SH;hGfgxj{8;BCTLNXr{S?9N}P<9jyn`M1(e zy;pjgmBr5jU?9F8;Eic!E1lKK(zA8Rz`jpKnPD?ah;}uTC(#ZykkI-s~ z^TQq$Gn<#(Dx0qzl_vw!=Mp)9vS>8WVM1e7}l{|+EOzP8xQ+*l18 zOY;MU$Z0wGL#HbQ=(}O25>j)cjj?6mt)zQYJr1jFz7D?2l8yoSl3ZQ=$z?kEg=v3? z$+8{l-Ne!{p-aeiW5$}OW8d$*2;L6F=O*6k`CN#}12?3j6zDJ#2&(0K+WCw;yw z%0)44WcY4>DO22D*8lV1K2xAH+v2d3Vjlz^+4j;M8%sL=<&@azi~8>5 z`9jBPEqXBRe8z5(s_{b~L?6b+`ba1@VX!TetE0Fx2?}9zh?|WZbK@H#eHx`Dyk&n~ zTvj4a&sw}`nF00)GI%Ke!O4X^XDbjzVMAXnGKyeGj!ZY8w2JXM5Ix|*GhLmXn~<$} zG{zT2wmF(45?#W>%+cXksQH8Hmk0xzeHpKaO!tGv-UCidt5PpoIeu$J8!@QOK9ks# z(;WysGWxoj*zmonesnQre!xLcql(WC>M4nFB!%#WM3)bM>k`QY3f+gwYE0 zl^$btOqB%JV#0NOO`7zsQ8`g!0e;v{ug$$PnjTW4=PYv%6d6&3PP{@`j|R05`+;qR zQ2@)m{V}OtPdc_u836%=e1X?XrxVrq9w|3DhyjA)TjC-yd|~ zUkAjki{6VSMsh_?gZ!Ve6QLsK5vZ71oQRKU38a157gbvA4*gzR0p)Ne>=Q#5$p7gT}qkTzD&AE`CGeFAm&UNgahb zBQa3x2hEb^ejtT+_Etq5uZc(ZJw{Y1q~+qFnhtD2=%D4B!iIC|Wv=yCpCx*Lc|e_a z+zs6~da_HNh+U$(^_VSix=^dB_u_#+Qj&k-!wP5aBAp8=UlR0PWFZz5veGOgaxf64 zd9Jnm96U4akn_%dH3f7HrnEm6);S@O`1sbII5jPp`Kh;#&t1~-)bURz#8k;+4q=&_ zc!o?WZsz8FCg2n^LWIXmEc=>EO6p(f$FQzI@|>})Jf)%4YqBot>!kDR-xwjM#YaI+ zdrgZo)$VIQ=61|fOIW{&C4jdT$jUn^tdjB^@qo(cfA-%xu`Qxqf zyV-e-!oLST`g7oa?g{zyUpr)NY=2`EINW;w##qYz^!{%=xzB&&iN1OEJt6BZp!V<3 zE^^(6ynmwq#+ZC_qi-TS+=48BLbW4iS6>6XJFNG+q7>pVlX%~?Lw;*`yB{g>zO&tTFzfyT^)NLlL( zK;W@o`IL2S_#UEKkbc8%CUi$;TG*qQ1TkNMMrOq5twkkL9> zEGTGn?;1FvRvQJam1KGKDD{lFh2@I)GlfbIZEoeJ^qD$->UM%S#6I#;9_x}8x;$*i2%cYM?Z=)&|FsBSKcP&l}I{>X} z(Y@t4K7ofxsc3h(Ig2}Z)#dP>yjNa7O&CwlRB%tL>~v$t-Ydc@B5;jC!yNFzRzaN# z9~6(hu;&uv>hd(xvN^||UaK6#3m0QTv(L#OY*uwI5X>ulnK$M595JmkE?xJCB0ucJ z%>xmyk*m)5d(koV976@ef2x7Nk1f50^?E`(bq~2dm5pS#;>D_cLf^1TV&R>>sA*~L zDZ6?hSB=lM`X0&hAtq!;>2z;e6|94vk}yq|u|_h8eO1<__>B=|K=!xq6jpzVcxGxI z{|I}QR+;}ke31s9mN}Ld=t@g=v-h_(g-clb8)K6=DKg7cfI8*Z;tVhy4E?s;YWtbU zSmfAyjTl;6fbcm`l|!RB(Ykaca|JaUm;3&;!%KaF>ooqRKIPUCmzK-0)1&*0uh%nlPlms#Kk(I#r0Y3U1qN=lp{4q|eq%sg3WmJD5Ax^f zv#u??>3MZSeyvMx)-dMDqe{A68O`$5z$)0@Z609x{PGF4nU3Mfida=$)WC+!mbLZU zmam~aM^#Ndc{Gj-ot7tP>si+}KZAu{R>x3`IY~OVyq6u7+t0feHHw#P1L}jwVz07# z!Zf?~v%dYt*a-_(@;3at5<{0aae0VufxfgNAaaPKLSob+LCx)qx4{aRT)f!x7+Pm(A!xiM|;sHXB+A-cj5@P+(q+#wKcTr8OnF> z9g|v5&^MD{diy-k*2L{huxTMwQe|(I+m0I9mQCn|3JNBlRk(&lMed+^7x$`GiorOI zybYrn{dtd48RJhY>bGL2=^^|aoB90h6={Xe1a0ltVK?ust=P;*EDN)E0vJ+#N?&mJ z+p~5NQHp!PW$q04ars_=W{Gy_i{|`Tn~4%ku8ed~=_yp@h;UIyPyVoD;amq-S~rHY&0_cT6x%`!_C@4JO>>=x4pZPJ z^~kriY9DA`c?RyRo8*|d%lzn!R$`u`P&1VG)NQT}kxSMD zEtq@1dJ`vUVP7cH4Xz}jAy2<_WuZB`RrrZc`{N4PR6i0M-qM*xhQ7*TJ2?BgG0Y`V z<&tIG@at?1N}J*%o1c>!FDqD9e;CG~+~4GBWyeZ}>G(|`n@g1P>}7>rTSse9RdBuc zkOwO>9TEZL@cis%CQ;H()OCGENAScfH*#-2GrvpA81x#zCRIzdH!aIjigRU|DwapV zdJ4GFro^o~X)Pp<)jzgi8%YDL5iJKQt^g)?+89=_4M23?AYNYX)FwJsvM!x>G0xFR zT7q$lF$4yf(A2gIVLmC%kT3 z+MFw-hGd?S!+qu?!uYI8v(F2#DB8M!XnZz-0ZInxp3u`$b*86FqG_*yI#33QJS$F; zO9j0h38(cVoTWD$;jdi3Xj8K&RHpST7pZnpJgX?Y~rbsj(+Enua)PwSOY8 zOqrdyQuMywELGoDF_Nv%a(t75Wg-2&M`!c{0W@3sg-!J5;;KU<)Vn4K{gy-(yKT)d zavWCxuw2izjJ*LZ)-aeyEHMSsP`b<&h~)JE_3G!^f;Gkw~#(b%7ZRB&cQV|X4lRteXa zLb*Wvz42ACuvwuK?eL-znL7Q3cTM!Rk%Uo)m0^|ew&SeSuu+5M60?gVMkVY`U(vT_ zFJJIwDjaF(*m^20#K0}N?g;Ruv<~X4^+kymd8mHW^A zxXP%6)`jLp7NlbY4sR=0rCV%l;TQOhX!THTj$I=|eUoCV%-%>oZVwBUnK7WjD~s5| zGQZt`!LG(UE`NavWpQF^y4t?XT6Jq9t|cvGa2=4lySavWc0CXt#M}N}a3vI#pbn^84kYV6dVZi z4xK%Gps`e9F`V_LwX^rk`tg8AQJk9uKYe9P8iM;3ljMV>esJO2$lwpvsoiw0F@l_G z>08qevtcW`00nyiCj)WW!>c4BN2v}H>dc07jg{>tkQqn{?_BHjSE__Li?|rW3eG7m zp*!iM@xfs-6}-{JPRnxD#x0qJ}54TnhoFQ+sF8y+O_=~@O@c^~b4+7)f zC8V+}nd}c^cIf=OvkZeqwyfE+a>L8u8$MUJV03Dw)%BhKrNAlYE(|StwWYH7!vXD% zwcB}c7v6oLcz#=0>ievy^%pzYxbUzT+di14z}q+n4qz_Vy17dHzPp_>>XDr(ja}ah zlW%U~d2-B!h=~OvYBt^jYxM*=Kaf9?ixb>Ep5Z?-rKg1wy)r?REu|Jkd&~(hN*^}N zMV6RIq<7L+cSw}NH2OCjqDRWMX3eAJ><^xeJR;NGn~pSiE?9QT8sCmzU+GUIzVdNJ zN>U$ZEGL}-CxOX{A0J>A9Lcs~(XF5DpFTI9L69xnO+wV;cE9#MRFXEF+AvUlX^qA3 z8sV;?nt`)7GcL7lOOc#vkWlLVbU2-YYRWKx2FR&1Lzuf%rjdLq2+_>({iM}my=<*)A|!k-zhH(6d)Vx3 z+t!enwR3hXcoGAR>iR+)o&>8ZS71r22bmAmPbJ5=q?TW5~^HUga__ zGb9?D?!Ci%HtF)(1=#~uh0!L4N<6^B5~H*@)l89ep9oVg;^Ebdfgg;xwQ%7gRKxpchYXlpr7-bSG zR|8Fs4YpaF)FBKJwi))jo_s;K;vP$a<@oMBNo7^h0v-#`RRuFz7a)Syum&@?Cwk2A zF`j^BquVC+)^PwgrfTCS_k%Gs=J3V(+kZmt3>UhH zaZs|AltrJZ7V#utKrwz{+1p52bm`)%8Y^q-{Y{WKirQ?2@ID3Gvaz>C)_MgvJ|!0F zVGAeM1`(k0xN>W=thdc>xmXhtM}%T|E#)VAf2tC+5i4w+PFm4A}Q z22z@186jHdj8uu$rHvkiX?u%dVPiCX9r9fafnq~PEu6Q4l98KPW%^1LeRAVG>Mh{;k9z_U2k`P$kjEK%`S+9R)t^YkasP z-W&jno`Pc7_`8mSFm~tqh^Vl%Ud{^fy-8|Z?-K(BCH4EhhcE0omhD6P()%K?De+Y( z*$lL914D>$$oMXE_|x=Q1kdevX+lw$N?yAL258w^;v?k*2GysIv}ESdW)u{91}GoY z+DJIi=dGY?JY;z-iU1L7#&KBx;!>Shzm1>|#`z81=KKUZ-9WEpjVxp@X!;vb)^Un- zyc%(m9y%B*M7K|m^6OER;rOJkCzyT7y;u)Yq~6g)4FqrdS9%G==(swg+#OA+iW)F` zDujWU(O4Dr$wvh|T@>&UTN*DDBM!mkC_`O;w^*8!g3di@5hJDS@%wy9k;r~5 z{g*DoT%wvtR5`1Y7_Xjg(RhPD*5s4T9N|eK!kMWbX|Z zTf>Z|7{3T6>RB{1W;8ImU?{p^mR85BwSM4HN_pQmZ(Y;faSG#tpsu7Jb}xZ*Ip1qS zzRq5J3ow8%v>CKk#8)LQi2laVv$CE=EfA?L<7e8@X?_3{exRkp{wg19O`Hdm= zaHQc#G_aKzZLN0xeDp+MSF6AG(G{kgi7b#KJ&*UjDq|CpIgz?J4v(#tizTs0WkW56_?9 z#{E4G6FsKH!y$A4V(mZMU;W)am$gTc^yA~5((QpSh}?Ba^DyXSQV+Xtqj{B!C$1$n zRv_@opz#dCA8Z~X6@vW^^uU;U;!U?R_rd0mnQMM>+^V=;?ezx1!1hk>J3>X=s!z}l z+Ut#;&&*&!`?owa0SB9F18;gNq8#vh<&`{qp+Za+!#aVpD!HS$*p&Lv%bhos;s3;R zeD)o7yMId+_4nVNfBtkkK5rEc5JVQ`VgF19MLVXKdI z-0&U+QhNhD=wzYx?j%*7T|AB&&;CZhSKJiO{R)X?Z>EY#rkz|*N1Q8QY}F_d7fztN zNeQQ-XeW=Z(fYb2{^{2owv^0(t(7s@uDHy?;)D6T{QK_%e2ejRj3tSvf#FJgE;lnk zZHy=URf#B0af>*AOxjcWEmxL|B8Yyx?uwqliXqY5j!a7=4pU6Q9LYXn*IOb{*)W)% z!++$`@khB{bEU*Bm$Vb}k3kRC<^(wf1gb<5p4DRDr@1FFbamLye=nc~%5ai7lO3%o z+HE&TD+*zUQXogi=fLUoCm-zh7fx6ye~?!veu^ZKKzLZ%DM?^$kew!HbZc?&-_a3y zSKsI?!^fQ#j~IVs-ZMw4#cmrW?(=-@TNuiBU&dL%c8l*ikk&4a`YN6pef#v3Vo}-f zM5whUxky5bn}bK4dnDZMlfH`!@Du*@^RaZQ#==pe`sz4$){mbA;+Z-BaV_(v-0FH6OR?nQmAl`_O0F4R^u_-0sq6!YbXb={yXQN>yj>uZHb(`^aU`7@WP)`o|#jUV9$kW##0e!NbiNQ@gHFzFS52N@NxEvE}_LYkkvt|;4o@*BgU z<@jDzd;sx>UNSQ8MOZmKP-IyHE3{J&Ui04Ki>WNd!WRfw<#D}Wv|1~(ESo7v?dnMe zb`+w+QeEliXE%vG7*vr>V)2ZVa#nV(QQ;fL1~@UE@j0tvw~27kBkm#H5w^_k&l%!S zO2ZA0G~g{em@?tH6ld`h**WA>`h>os-e>X>u##vwx;z$}G%XUXXA1I9vwx_E!K@lfz_V1Fs#nspH1EcmN_=DB2EiKs z&RDi()!&9y5dP^@KY6}=pCCMW48#8_vUxu&C6@JFCRZh@8bJ2e9sGYNd+VsS0&U%w zQrt=c!J$ZSD=BUzxLa{+p@Cunf>S8PU4uhugF7uAyto(F;8wgqDYQ_!U-mg?pR@0K z|nl5uRzFVk~fx<=mXwAIb z^^&8b+VWG1op3dV%Xq)nl(J3VDAWj4thbNK2+9w5H%&TQb4VW>?aXbZLItm5dBA%f z*|bV}^rsOZ^vQW@#zYNug!C(d{vXNnLs2a+<8#_TqX8a~XkdYMiR9d4QsxeibPr^*H{ zP&O(&E2HAY>2m2#WB)c4&qep9Djl76`Ra(W0c{2xxD}*_+Dk6_ zB~w)R`|hTy+__w58Hn9zoAlE!WsJ(RI-)gkMenI0+jK&5OwiHrlIg6#$kBy=;8bh0 zaP?%dlYSTF!`V_pD8;Z=n(bZgLErvM>Kz8c96Y=zyA^b>f7Ilp+j&6=fIzYnc=R?kL_Ak+4aJD2ZO=JVLZzzp>=IQ4phES3J^wMt(5v_j=p#1 zoGoL(%vTYAVu&SWro*2!*$d+ItljuUW;m%$dMNI87RxlBRnO}N@spO=J%W}IWxrpQ zKC}s4&SW(V7Wkza?Zk&TJya1-GUm-Tb&)yc;++`ecdW@%nE^5*;)Vc?5CTtXsk8Sl zC`sB{8Jgp-0{HIAIkjgO4h?(FFL0F*))5rqTewBSJQeV`UoEZ(VL^g+o1Ad>_q%^Z zW4)LhUtR2B%UwM`66gB}73XRPmK__j9#NM-@4LrK=#ohx`k8#&#FCbjed|$LRrZgA z%PH05gIR5eo}>ovZ{RDi6y!z9PAz(rE;|+=oDyEPcl5hlIBu4#w#b`}Gm}if$okeF zW?uPft?LSu>Me(qrKj07U2B$KhNLii$7X&R<5Z0dVx*1qJG`8-lLn3~?}@SQj`6~q z$36}FC(bz`l?c9c6h`C-tqY6cS6Z;$Zn^!c)HOS#lFecPGPE|!t#e{^&hm_2c2b}| z#AhGz=dh6$s`Km<#qP?mv@77?jw#K4)eU}YGuf!;^WrTJjP)02fNiJ`g`ByMe>vxJ zV`uX`pYq_u@24d=qJKtoscNg>?tO6Lzz|Q)W!ZrYJhK&sF_!+Hu@qXFi@rT&FP@sz z2@FMO&8>6J$p{koCW7pmG;;()eU?pW1}eS}dw-DV_r%1MR<)h@|7FP~rV*3?jxOTz zU+O*y6CL+kGks7x)GJu{%K#WAU`=lKN{{8Ejm&i~CJHl2pXQ%w+bd=j{_qGNbchlu zI7t+UW-{F!O~<i2^M{Y79?Ao$SG7P@T zLx@*b8Rm0mFt80+Dhm1UjD~AlGsEp<03*K*EfS7}Rn^uxwyNL$Qh(2fnz%?s zuSsThn|~In@>EBTVp3?)IU$YYtoHhMnvJ0mw;IUfMul6-lahg0-`hBo6S?L{FwbH6 z>b;2wCrg%v$$J}{OGQ4iG!--bboA@ChUIxLVM4;tKmOh<&k9}j22T1Z;@H#>Wud)< zmYy7r7$Naj^|3MD_us_`78^_Ib#mx&6T z1KZ(K839!8Y&|oFXuzjO^47eUQ@U-IudGgI;Fl?xvZW(s9-P4bH2bBrTtPfy29pi; zd6v~5Gdk>a?KBo0)&(sOdlkROpZpPJ`H%xEe8}A8k>|!=#0mZgQcnYo1c7A&s69}r zuRdc=Qoag+WvzWi9!VbZCrSS3FWt%!7Gvi`S)GWtzpn~vO+-9y>Rz#>Im(tUEXWf2 zi?krQbBLVv9kOjDfz<0i&ft?~%N#ygP2Drv_+XyNyNCL!n^3ROuqH_HFr4D-4(VXG zMO;;jq^D%?q9{0A3HRyK!qn0nOqJxF2`JGPX>TJn2-V&u*yzNP+8ba`cwkoFEDdPT z$*x^)W%KZo*_0Gh$o9SaUG$A3r^P8{P2w<7SRoQ7BY2Xb<@Z7$@%cO7zemLB|I!Gv zS$Bmzd;FKC$;1^~P5%4vf0vd2`-6XL!pQeHv!{}@c0#4ymkBqlP?plFl0@Wm7?glFm$YDWa2UIX@EULegw8hz;DyX45$7>u#>~Y%`}YLXClV+R*~{ zA8e$F^_%hs4htAmVt*a7^NvToH<=j1BYKYSUKZsX#aYLUr^8rNtT99E*Etw0gIEsu z{nVOLfz7E;Hyzi;Ke;!|CUddwd)f?XDiR#c0UrL6-1yhEvQWtCS&@e)Q=V{c*!6W5 zXM?Vm1!^@@rZXlMV8GJjANYN2sEP2AQ~QvY+WMN?p3LHB)Z^*`T`$aFiXh;17-}ok zUt2CUz)_IZrNDQHJ&10|Gtwo2l*`2-Mo5l1U;)!UXvt*?veqTw z-jXJT3)kdyPP`#ajRH3>6SFRL%|&qOAl5XxAH;gHT99HQisG^Lcw|^SQiI;zH6C`R zGY<@pz6uaOe5RLF=qvE8c9!FUxI!59Gmh^|a}i_}shfcaV=Z5i~@m3^b?c*0LQIn!WVEEWXF zuIgv13z(*fZ(uYa(#j)4XHiE(;rX_z*5@e`sT#M16ZAg%d93J z>NM&l9p8Rrc=fDbg`dB5%|Xo&?1Ja)DO;;f8(tn3VZyE-ST2U=!lA#8?;fw z4z;Xk68y`VQ_%oV!pv7pb>=-H3->0UQ-T8;_2w#|fn`6L;?r9{JAC2y{-wU0hqu|@ z?#^CL104%St_C#%Kiv#pTRLg`lg~UScr^rA(W3c#-L62LAyLuduwoA%pLC&fFg3Bwcu71VGd2XEEYO@G&;@`F1YUPnO48{RHj(Qv4`yRHR% zReabjSt@$${fgJ^&#~sPQ+|poEJz; zHQxQflr_ekd@{$wwsf8`DCLePR5-THRzyl{wKHOVndr6N> z;@kV>F*u_hrQZ%IKbwXPhK2Su3zz@>28dv=ll2MWF<#iwdeMZIOJ)cg$0?ZT$n_iR zmPah}_Ad|Aq_UM;18ieq7jLY371X@LA9EziL^F>yeoJ|c-INOo-&~)vQR9c_NPBe< z>8~sk_d%ZvxeP1dsE;p%f7H{UAa0_^JYBE%^;(#Ynhn{XgiN)!wSG<_iOS1DhEreV zw7g0%iN1$(S;Yrto{A5_%$smXRKpyidCM_A!JQNY;G=bMvE*m%_O4pp; zrWdzsmRcf3a=irvDj*s!T9XrNF0Mm=gzDRt`xu2zueW+Gv)W24*2c&SsKX<@Ra(+A z?*n7c|Cs5!&)=Siu11+#2F{{WKV)w~3S%%L6LsEM!ogp6IkumX6Eutc_yi$OEVyd# zqT9f4&!r0&BN-02V^xL2v}goJy0QYSX|Y(a)&6D@SB89y-3$cvRYcH;hn@G zC7K9xCn^cD5fA{vO0Ab&&O0>oME)~fxJ$53uji%CZP018g)`!h#- z>v=6vWu~)9{aSz3LD|lT6TXnB&<8#gjFr`ZYJF-F`#S1t5Ke!Cw8m~siBnxuEfL1V zUG>Z{*yw>W=f#yQ0u05syP z*@6xsMngKEBAPUA5IKmJ`X+;r+;MhMrt^%)nzLJa4Y9npr&)xcI-3ZT$|I$D)j&D|@ilC8ub3X)O;jw`;Xega z2&fKDfra2^9(78dAFx}9x-KECqmDoOYtawabhFUa|7J^K*JYsfFB=Ze!0VnSNLGqU ze*@)zg~ys+pl$4dM>Z{SC$SPH_))`VscWEPO=w5tV4C9bYo`xXX2`gLIGAP>`seqo zwZYJbs#?yI{CZlMv$t;-KH8TKl+Y6iBeTpaD23>SiA}7@0;KCfRYG88Z)o?;Dv49P=`I-wy8iWidHS zG)p<*MNP-sVIqoa8R#n{9jj*eXP$LJ*%!+S55xpcRpp*g!=Y3N+>rK@lgF} z8B-F@N3#5RTXqG_2TO8wd?u_zaA2YOcVKYGH~8|=8ux(1lRah{87mI<19!P|uu#F+ zK&m~IU(?CdwcZVqu=L1`lA$kRrhy66X!IPwF5dzns9nP*JZeOHYNjtncGuR4 z)Quarp^#@GmTYzJmPI#$v>l@-RsJSeadQ+-h4kn4InO3mfs;f7c^P;^V>0lP*K=6(8P?z-_v-q@*K9t1vfQUD31n-sC1urvweD!?O1*k1gs9&D2d$T` z3EAx$Z^kC1f6Ok&x*E&Ox-HtNB3L)2S7XlTNDGDP)fI~IXI_ojJ3le4cX&idjZ2e-wjU4CXt30W7*^L#8-SK+ zJ5^lgWN9?i@_VFui{~SZ0FADB?)a3-#@Fvye+S!PY22>W>(%wANx6*>O9R^I{^hbK z9>M1f#GLq)2M?@yfz#|3TId0Phm479<>}>^%F^TN$`kc{ zgjdo6yC)(BnX~n6LMWcyN=>uV9Bgu%%ZG<6o^bTeGr{F?^qd1W_r0p!jag%6ojcaS zT9nb-K1n4@2O2K8m*$~5Jr+^&-B&ALPqK5J!x$A=yCr>WvRt>}D=_9=oXVo0CIwI0 zMA;oH6q?ZiOEtDosLu!O9Uo@>d|wrKF&0<}*4sD5A^9INO)UYn8N5Adh6KJm5eDI> zzL5+1;C#{$ewA1iwmM@<9w?pjq|W#Qjk!m=^w!R#P#c-n^vR}J>tO4{H?d=(Rv*%< zE1sxo7rW7tirUb9W`DStl^ga=?q)VP54cx#hSGXaOT_V72+0#UT!`| zwJpc-d@|vf<4>6~qAjXR!>PHUk7Z~2t*6udS#a|y`P`Dlg)n*Qk{?U;IRp6*QLAMO zE}LxM{j&a5wh8e|@nu^4!O;G(6@LFMY!tziYsVpdR@79<1P=1^ zE783LT z{WZQ;YTF8*TAUKqFx#y6yzh+!W8whv&-=O|H<`G7x#O)dXL5_uFQ@y|4ofMiCB2#b z2em2tOzPh?QFyl{F27_%>nXaiMcDJo3g-(2>nL_&LvKjr`G z)7?+WhNASk95I4yR8iK$yls_Jh;unE%dpj<=M-uGwE>uP3*hb&mw z9cB~b6Tc?|3O$sq-G$6sQl`wTg*@QCH)mA&G;D)R8~z>aa+umVY7aTE6KOMQ)La|d8&M_2bn^F1R)%A2svFzOn7W@yk*pHz z8|q}y=~H{*QMt~Fu-as-@vskFeJli%_RkNo*=B)dD`$&BM9o;tN$l)itbKZs$K)t* z)>hl+cydx{{A>Ij7$OGso61GT`9VK zLV-fFD^CYlQTK-XG*}$Ar|Qzp{I-ma1+T#eqoXHRLM7RS#75p01>h5)8GdMejh43r z#+Wxt4CV~jFT_|F8Pe)A0XSS!w5ag<%V|0TOWcs)1`4;g84>Bn{g}l1YT#)A(~kGprsM$^3(j(A3rlk1Ww3$W9LWZ7?jzS_S@ETO=?~ zk^%sspk5_zl%1zEvfK|uROavuUJMU`REkFB1ssvvPQpPieQbqKyk1?K>3xQGaCy`-A ziP`b7KYdj7apEi2v@GEl6 z+wet(L=`BZcpX7x;G?{(Imd6Ee52iS5r7E<`vc|R(#&Gm|?1#e7bkrtT+d8 zu-KK>8;rT)C1kKX^RfpuVc4()bn88hV#V29IC;uXSB0hqp#!^~j#`g7yg4hHWENI- zHX3jo=Pzfy$MuUJL~^usbR2lx#%OPBi?#r)nMc@xX&x-_`)qPtDRRS`@2Ae+Knpva z%da=YFNvjh1ITEb`rI8_H+8=MSnxR1ay>MIAfi^m{RzskRIQ-rZxWxP_TRE@xG#`P zN&j?lezvx^Kz3gtmS-gD1}2Udbwl;_~*o7=c}7m67@^(41b-_5S&^Ehf0+h z;#7U}+O}B@d%GVikM0NIJUPd@rb+C#u{h3tH9OSaJ zF(t0DYKG+$jmrW-3)GaB1nPhpdHbo*d8p%l(`@MLWWQ6e0cR#4g2Cg%(+zt(Tf)Tb z(>XerC>Hv(!j;PCEfd~gT>fJfk=>!1|4_nWL;RPsR6*%FnI~#k>~EF~2W<+{9;g-3 z@1bh5lZVpeS`mq66fv-hCV8Y~b6DIU;PA zNlK`bS+TYyD=XPM&p&mHT( z_1XV+;9u1`eB}`jHE^f&q)-_1w~>O>Q-Z(f9l&( zPNeq^G=EHa6`StcI2GZaax#l8#Pv{<_sCTQ89-*pH@Pj`A%0faay`sT&}}G-fZP6* zTMQ-|`?)Y48^a*$c?9SUyE9!`Z+B|F-dC{p)Cv-Hou|~GPV+W?Q6CnyR^pcWQ%M3J zhWlhy2xtXnTeD30B0hTZ{%#1~(a69sn;X$sT3*k@M)-Od-Tq+Do0^&4JqMex@TE?u z8X0!r3dx%n&#A6|fD39ph$<}4&dJln<=9C2Vg!Oa-R-pbo;sxAJx`PxTJp$u4PAJj zo$SZ0eveq5R~{r^rB)Dm$=-K1 z(AXXdz2M93ertH_;IvNKup>DvH3?(awLF~!o$*oVtT78&*E*IIEVx0~C8(2()~E1@ z4sswol^T7^v~~i)7NpG9MP=(S9XD}-!Qd%<3$TD(rMwUkAGfG$1j1O8r@SlzgY1L0pk9KI$TVEDqwTAB zH=Zr5bIMRD_4rinjpr?Z-9mF_l?o?_g$5m^ta#kpA;f;qO7u&Kfz(~)Tu2N4=BF>> z@ka@w6Zx2mbe_Bj&u`!Ty!<=5X2NUHooq2~GAxiW@8{{>Z1~`fj}Rh^HNS zI-L3^1N6eN4c)!2Z@65|fV?~SdT>7sy}SQe>`E1cQ}nqUL%O)RBQEQy_t%1A=119} zI)+5l0iC+n@AK0LVB`#`Oj_-3H$Gq>N#rQ0U1*^oJgV9vrW&-^u?JqJzwRtI;WG z?R#j0N2oJo?Fa8sM^~~F?~;(lH|J+??O+Y&{;RC3W>LecrYcSH#0#RU3 zo(eU_tXa6$-^TA;`uJZcZnZ;#(DYkDz>+dqzzk6w|^`5q#gbsSVWZDn2a;1AFg zFjbQLt<{_$mr2JOI9+}g-@?g_c`nXLeNnN-7->B#RoLck;&x<;``DXPH;W}y7c28I14rX98TZ9{y6wME`I4| zwPZ_Hq6Q=wCa*Ti6WvWj=XrrNx2U3o&9g79ZC+Z{USBuF8LY?iwhTU_Gq9qx36v!L z{7G);yGfM@*-9ErL)zyX3f6CG;cqX&lk2H^CXLGe)Tm;Fi|LyjGxe3Wyw@g$zpZbU z4)Tj!WRxQMuS)cg#h9E|LS{7So|k;u_`_)z_~x z9O={{A}gM|ZL%=@nu4ssnK9(9Sl7ckfQkDNbDGy`gdc`s93#|JTDQ9)`z=2^wLD-f zpjs0m$rGY1I}2Gd1??HiS-hVJ$~YZJ@#U#IqbUp+?p@+whgYzNz_Wos_~hwm(?rD<{3e|#EbQh!P;Fvxt1*qU07nErDxI9^{@xp4(G2N;u652)bPd^YXZ%OOjQO! zBJ=VSXG?A&o9eco4HJ2@Ft5PW9=Ol&QfWsux^;hYoi7^aHm#i(I#%V5^-_-rpvNV@ zra9>5a1G_??~IG-)a+(L268#lnBxWwY~^n+n8dU{bFYmt&j+cP!#$_z zMe$yRrWB^Qca2s|NZj%8$g!E%($;_ink*x9U588O5D+K`a<-b)7CzD}1#!Rib%GEK z)K&0YA`8nUcV4mRZ!(u!fuveox(+#hu!$9bU_-(Fi5*A?`qb?L$x;`i6O&jBh zIuWg@se;#-yV3Wku^_O44VlmiNpk?;$G+Ai^8uUlnDP8^V&-=@n0>3`+gV=#5EqS^ z;CHUc@s1B2PJ4&Na?`9K(9SX{>((8wBDhLOLi5}h9nOgG;$(iXDV=}P!N;?ftE!PO z%i=k={E*n~qZB_xP)A>nzwiQkv-U-jj@#6jKpf6o5v7}x>T4&}?TTJ4)#Xa|(47dxLile0ErIt|xZ=du*= zD`Sw-ee$!fQH=aDF-7a0$lFL`rs%R>Whrr^$BHuxgEg5Zc4CyMn2Ib13IAO_SaD^o zTYClH9uy8GIlX%DYxbU|>3^q1XVDLYPA%q7|7S`Gw&>pjgRv;9ihI70H*P+^LnJs9 ziNP-`VuAgJRLUFQ#oP7G6w!m3eQpn8avC2oYHDP&C21f7%f%zmQQP8=#C7>#A>cT( zKfeD&fL0iM;2`)wNZhpCgR5xpBJ6Zc!CCut%s>{j7w(I|Nl7=|Z1>Z5MMXQbtF&91 zqgBQlx-;oZ$EGw%$9^UfT$c<4%9DQiPVVc4t2hu$+I*+4+OW=M4cA`X;RTyc#en(; z4DixTy>I*-!7W1kN&Gq;1e>MGxa!3*T|bqr5BR@ZB#}2S3i;D%n%PC{kY`tTfgiWH zxj^ma$|nKwy5SKkC!4YC<)6N!%9N9hCfow+52UfQvrsnI&O4zwP*DXd^RH7JJLOg5 z%;R3WLY^cJ|ID$OuD{+if@3i|#k20>ZeeclSau})-8{fglgO05S`Jse-#)JjB}2`^ z5lFja?#6pJN7;C6BJ(vj?$RPVnL@9LhXey#LgN*-$8q76D{ZfYolj&k7(!@r@%Agr z*<(IV^Kf9kQRf937UK+ZQ+dLf2=Rymc^^YKxdERsdkA(d= zQ4_6G`5TdJWg9D~{Z!LntR5)Oa~r)PjhV<(M#sjo$9?Jn(ZUbp?6my`G@_x?AOmAu zMP;KKPt>JR=8l>~LWI77?|2HpbTtQ9=!NPPD3KH#Nn!6zVM9yB61nAlvhc3to`BZH zitG}&Nfr+e4T>s2;V>brNsBX;nOMb|8f*jtf!Bzi zd+RM!Kx=s|e>q$kyF_VL?$o@BtMsc(`NLD5S@lONqj!%&zCUx;sli`^h}_zV=@7Fw zW?u+tThDFLXnC^Z?1D&L{*r1yX^JbG_0G2sDp>J(O@Zqpya!>-XdnXS)OC)n|FIIc zlJo#T0_3GwCJ`3hX!k~)uQ_!#h>+i4IF&bGYk>#6O*DnR%dcgMi7LEpYexQ3eIXKz z^8$0O5eFX`(Co04BnhXoiWWwv3Jqf!CSn%+lzHTXp!2s4FN1VrPINhj^G36&j6jY=Axms|WbknB>ew86yW-*Iu%UUMQfH}TeW zQ^qod!#$_d4;m9@+B5_I81M+tz?73$jQ8*4F&FjP;(shpp>-_}z9_k-d`UyFSx-Md zF+zpD>lATlYL(j`IZ|Be4rpZ-7esQ+q}{`rL4$;n8RtGra>5hdMO1v=}ETb>}{ zXtl{5bGwE!zmfs7i@`$)BSgYu_N^Q=e5Z}dKAX}uIW8;+E;{hxXBo{J{g<7k*DW4t zu~kXgE%X+rge?Z&KhXd5Kag-@e#F2M)}q%uC@%J7MW=bgM=POr7!ER+my0h>dkMUK zMzjMpC_CA&;}VSXYzqD8CP-(a87tjB)`(j?$~?3T;>i`? zv1@?rT3gMT!_g$dXZZbMn380Vt=ax21T9)<7nI z%|`DiV4~;wDFIi=&Zy;`Qh@a5jq;#}ms5sTD-C*#@ozlmxx|`|0g% zFD?Htr6y!^F&bW$0TgJakS&E?GwCFj;S*`d&)FC~778J=lA{!Jv~3w!I;*46Vx6s# zT=+p49;SJ-BParTwr(Q9S4<4Yep_~$kq1fSs@BwI4 zemu?3vQO7;eGJMT_KbEr6jFU|234wI5i2NRP}7C&$!$5`axjK=I!~lJ^&>GLY<0Z| z#+Z`)5LW>*O_|0i0)JHUaSatKz2??7(fQQ5Nu$Ml|4q7CK>3;K4$i13V*eT-GSpja z*81&y$~&#`y&~GVU76og0#~Ib+T6B|3nLXMFU6iDh0WIT36>^FbC|m+Ixwhq?J6Qa zHa~I<0|Ew;M#40mRQiXT0Yd!nUm=d7!;KBTXzP3ZvGGHVnUm9o<(@&xV(~-YEORF1 z5Q8R1D-SiSV!dV71bsbW{Wg(+PpPGfa-tbSMsI4&q{OyNX><*p#Ht>$mH^xYWZ6@QQLXJ6aH7a;~GHNK$5^y2CWhLJwyxRfG<}J;F%H#mPDy#~dMpXj+_1uy(!(JC_c}b810+WvU*-WU8Fp3eyrHRO- zVL8p#z;Y?fT9g1e8;piM*CNbFiuPbTNxF>A!bmo}d|3Gx?Bz2-eCjSn&-a~}Y(xz& z%e{O0is)D&Psp?c?>S6O9v}%FR{~y4!K!JByR58zsfW)wH{hkqbdo)iefpu2u_Htl z2sOLAPC_Wx4WLRnKHUU`{+gv#4Mf+`w@?B_)Qk}d3_8QaSMJGudU?{M8>;u&XSpAg zWnN{XAC0{%6m_j4+62F3c@j198C{Vz@xw>|^`TTY0S&N~qpQYwmr!7tR_mo|vMX9$ zC6wmNJMXq~UJf&=ISH+`4kUcPGYQ*FQHo4$n6$w@gbh@>fDRzZ}nPQRCr9%bdtgA@C;@f)=dGw4}j!#Kw6D z{_V(SSralCJ^#pZTZ(2;ZfMuy^bb2+r5c|)G{2rgBvz6w=2wR(qmS4b1~OUZAT6vu zOUg-b7S?xE6|U7w1yhUDxtQ`i=-WR;6=V0b%8PFe*ac(rem#?3s-^=dZYIAVdcGot zj4z!c_P)x}&z|!38@|;%M!B6Wqr|TqdWI}028 z^K3g#_Lg1|0?do}5aWBW?-mA2Q}}MK2ELl;%^{we5qtr=9zLl*6z5o;Dtbg*V9Z5) zx#oKv@CrJ8%gq%B`jZo7a!vE|X(U-BqdrT18&!(?Ha32zd!{B+a7?=>S(TTcMBDEI z4+3_5jO(MXRNh92?cp1#TlgJ5cCKGJTAKOXQ5m`sob}#{t1N4X_*g9)H*lVRY>+02 zH8})qa_EWIdEa*%=&I36ma&D{8wW~bX*CzhW1Vu@zslu9QFbDJOaH*})rx+zxkos6 z;>+)Y1SqzM^~BYp&l~f5u_g=FGPIwK2GsUHN;PHehQi~-XVWgm8pewb{}KrQhX5G5 zN7(pZ2Lhm{|JCpOzqCKg&`Pb-p(OWKWb;x54Nq}9cMnzWlxqEY&0>so{lmB&9*1cd z7%ysN+pbVOE{-pqs+^jH9AIB3*dSitubn3lsp5yf9C*Elg}3i180oa$=qtYY;p(tp zs+hu$L&%xJpRygFbXWFHnz+2Ykp#Dr-K}#3s(1UM)E~GjI8fk#8#3|KnA*wON=Y_V zWH<|kTJxCmWpqZTl|5y)c-9v7iwUU(W%I1Mgd-D_|!%{EViwNFrj1Y^x;XaC~@(1T!(GKqNuN7_$y<; zqp7C?`DT6HKuJjj3T&b*I(+AGeV7N1I4({MD^D1E{&4IF)~PXO#p`K15=8wWWJ`uF zB|`*)4>vZDxzz|fkkCkD5%2@6BN%K`|C^j=L6qsT%h;|fN6xg>tS%+J+#J~ki=!`u z@x^azlK%7D4U1Ilt&sF+1mI>d@9|`sX*^_P6Y+yowlQ(!1LEV^U`mqXw7LI7u#K9y=Ii&!yekxo2=t2 zFBd?(^ZNR*io&a(J-;kW4K-tU{tP#q)i#gv@m0hRD#qU)usS_lpa-{7y{n&S$%|&U z8Z2%`rb-Z+JIW^(tfqe1U`a*gpg6xKddR3;EJp^J75Zt`mXW=%9~|O(c!#oK!!k)5%mRMZLdN#!XLTa4r;H+l zc_QLO9}6@qXHFJmrMgA^eh;L{_OO6DPVZ?qKLA>1b-y9yf4`N=Q*__q0y?DEyXiq?2{P&h@SLY)mx$MdM(g{r8b+D)PUskN(Utot|kAy0wgv zrE})nHi|H3t!>a|OZ6h&k4;RUtlphYz2#PI=RDPU$B%BFxNovAvQIa1ETquvtEuo| zaaPOCifD5bc+2{tc&V|tJ;GSwEpeu$e;S+93x9@fWKa#;x5+)!=9!;M0^#I8=@-jdGlV+`ZS`$;Beg1KuB8d&=k^IQr6z zGy;##y)N{1uDpN;PxkcCRxl0z@R%zz;A0JZpuCJYp2q#wJ;%qu(y%@&>s^bJ!d*W} zicw_0?tDET1j(deY8q@!)a6Vet_FNAJu~6nt(iS8t!o-VvtA)y3JwDiJv&O~HRy!?5w_NI)RYN?jD ze>K@^H*Ts89_$ev|!cxNe1IByxDwMWxrM zFkdX@yLzx$T0(PB#)EJ2iB=eZ;SUf&5&>FJOrCC3l`CQDg#BttikmT&$rH(gmbO;M zPcoZ&aUhdN1s4txm`(+1rS6L$5G-%YqV4s!<5IF+)luq33{8#81wsRGryI8Z)^QVu z2^iV|hnz7CH(14c;Ncqz&D;AK)r9%$U33@XlnOs4Em38N*)_F+D`7+1LVlt%qmJ7g`#!AQehDXo0Wv}Y64Ry3RG5;lPN&L2 zCD>}xMgwc5QDaQM81`gpH2~b0a+A^dhft5L`5k4cU0U_1x zm>3vNNL=9)CbKYDZBUjQ*J^LyLnZn&>E_i`Pvy0DJUAMufV`5#i^hNax|od0AxqYe zO|KmqaPT*1q|LsJOIojM_VN|~ek*-K0KpVMxMNNQVnOQCGDWz9jggu+{GX}U|8mp( zzs|py|Be2&H@cc718(<3*MOU??5&j7)MBu^l6!9!bMO1gY06i93A36goWl^-s%#40 z%(W|e%n265Su2A}CGy@!++zg~WTdj;W@8h~K(=EcXaJMr5>?F!)~rzX#!*f*_A};G zK3Wfxv3RH3r`OaG1$8v9`ba_&Bc7t+o z10GnYQ5$GQX~q?Gk7Ab?YAs7A*E<~H+>q`2J#4el;26o+8NtvDfQptv-}rMP=>Z*dFm6f06_OM5>j&wJkeKKI^dX3zN# zm?2Dh$HI zVpa`P{mefvAm^2hWn(^~<&i(Gh@)#I;fjjACzU-}QH%^epFW|QJ(5;YB+gN@Tnbrs za+~8}n8%LqIBi8oik5B0UPQ}j7qAnXRuLCte|v)|BAs2%U${^~Qg(;>CJVkR&urB9 z6Gy+^qyxWXAZz&81}AyPtu+Q_5Gh?HtZqY;MpY+F2N+wB5taF_s|8C`?|lp+=Dp>e z<)-E9xa7^bvH40i>wd3k;zZ0&(a}v*qabj2Nn_oHLWwvT;~oL{1T@NRW~ssjp2BO6)nMLSe0FQrMo+liOC-W0z$bt(h`Uh zfZQ0!TF;!mGvB@onpA&K7N3!YwYQ;7hCR7%M|~PNT*5f}}-yiehGOm-l~En@}j z!HZD|mxa#xHsOrVjxT8;V#oxdKE();Jn%C{DsIy!^t;oR)B8-V@iz5T+NA{Z^>jw-$HB z!ex1)0>b1zr~C;?12q;mGP1d|AWZw{A1J($OwOpK7Lrkm1tj7`yH{P)H)0Dz!(xL_}Ya~sxZ2+7k#1(>n#?F+p7%lNd>SQGghEO*I`$x&t=aEr=Cj3dTqPpDa-D&PAxXJ5)9Vr z1D8)RW=kZQ#yX?~MWRzus;S|#toBeicTE%L83L&11eBpN69gRY>8o{$U}D;lHgx$P4C||s)KXyXhb-ZY*jl7zY6Rg9-WGe4Z}A1BN!DyGCQe3i zHa8HDQQ#cN!#|3IqnG7n?h8fJbUoFZRi8L|T4 zd<}JZ?PVxWk|J}yInz&+!tBdXeir8Bfn=%xu>b}a#E*teB1gnEL`1bCyGo;uP998V zQuSti>5}dIOJeR~@ZkPyrbOX;e}5)RXT92psc4rbTt=6dhd+|9g+s{)yA^{qh8>y> z-0*M}UWn*>8Ce~lk5xzcM>u5hH<{VvwdK7RVstmOrBR!*SJwGTl-G9)oT@0ZrdI$W zYI95A)w1&3C54j(KU~dby&J;#JNB%s(muf<=7x;!`ab>}WXFJE>z~vk9ap=k#`(PW zrd9_`>a{IkXiR#^qz8Z8n(d*HO3E?7FtLeN@zsYSjs-~Hm#ROOAL=B!Gx&)t3vfgwQ=R=U};!cYq)pzYOf%KDeYE&*q z)_!BqCV?OD9pbk<00Jk-#4lsNk7vQa|;+?+HhS#Gwvf1-bC|w%A zLd;_oBao80m|ATv==xSqlDiI#kL|LR-x!J4QQb9ea6fS--g1@C(boGS|6%8H5^}y! zA66JOz}T2o5oDzMR1iS$#qxc1;hhq(_CU_(9xXIEmB=k(f4HutTUG}PMVURsOJZ5x zy%_9QGjFq&2sSdCJ4B=7zz9#CrB~11aEOS}QJ+2`V7t+?Jeh%nzX$uuG(*D*EPZ5; zN_wA_C{2*{*laXA#}CIYavY-CmQ0(Q%s4jCF3ZQ$GCYMZ} zOKR=5y}VpsdOwE1_$dg>)km?gl~Lf%#;wNLjb^A>$5m!7FQ^&USc_#N4b30Z21Mo7aaaRS z`C9TVM4K%Tr)~9=SmmFpYdYA#(4yzPkO_-ptGlDL=3UQrweGF0cx>t$%02!@9)GPcUM z^q?*KKjJ@2`_qUH(SIhUuf&7kR&=;LvdWYdrOR_~4WRXMWtoJ=;Sug8z;_^>k&enL zL-GZ`-u%Rm-h3IF?bO2lEsvl=JuD|}U`gK%@e@TPS@q?~HgK%6lCP-CwMH|m9PFu- z6~Gvw!o}XQ4;7#WGu$CNm(sXoE2S=M!@9@&hZ2dh-Un}nC$g|oQV^FkyqmEGO?@g? zYumC4HKN+=|N7wjcX_e^EA#wd#b`jpu2TAdsydqlD(a zbiOldK+BE9ZKTN*XKj3gQDoD66(qy!&8@k0mz2n*EbYy@kODN@d#y=P3 zLO}Yz`dG|<#+OIc&e(pmY_uAS7)~6iKG`7hDG@nKN_s=#TM7#OQ8omdHg0({0;w^z z`A(CnX4B9*9qAyK@;PK2B2oAW=CCi6(ti|L-U9qkuM|OEz z!ceAGzD*QfaD`B!Jxn+53+Zl4>JKnyHjU2OdF__%kF?Mh)7&TQFMO)Q> zW_p*D=GKrox$WypvxcqPfY*G%O|1Yw<#Z100=CT^}K5OPaumJNd^cVC)RbKN}~~ z)@=TrR;=nI?+NQDIONEc8iyk*h{_ILfl8Iq1!JL=tiLqh`?R|_9S;-Mzo5_O@pcrg zi>|FfNu~@Fa(oF*$nC+QaJ5_?_aiJydL!poF;7r)KjXL~lCSQ@l$*P{V{qWtBb| z0|qySl|>jxTVTtFKCRM+gtAXMj^}l7;MfT z@DxI+y<^C>9{2xGT+08e{{DLalxDc?!j$R$dy5hUZ9z90SKtw~Q^bsfV60b0sIue| z))!<`Ax&kIZH!=5s0j1S2Z!M`wNAn|owVshV)C$lmAMjEO)He&&)H|~T5U7_uMf_8 z=#ZrjsO@n{sZEH>^SSc(1Y~REngzE=ruL45_K794Iv}w9k~!WZlV<8#&!g$rU(FVE z$jiasou=I9G>{CQ8{f<*wBr(8#jCLr&zwsBkH3P&s($LIAzAa^o%ZM#h;l}3Z~*nR z^bP%9=0zDVqiIiJnI2iZm}|&gmb)8F4n&;yv-r|nx8Ud6nN8A9>XbEh^M0VJ{3xF3 zji;Mm*@dA^`R-UC8%Sc-_?y;6rbKM^2%`c*kkAazXI0F2p~-YaHOzY(D-5A-#XTO7 zwELM4P%#P0Cdg)=eYHHlv(%$R$!ny4uJrjvTyFSQ*8Bz4qfv!Evl_+3fDSz&D=H#> ziyd{TF++G+yGcaSDnVcT)Kr&rg0x*X-&@C*(mmK!F57=kBTa9Fh<(s&HqV9dT_%pL z;+2g#doD?TW4LS7W((|AhtNKc+gEt^jEUp986mWG}DwR_03V;qZ0wUPx46SLKW5 zlYH%_ecl7`M5eqO4~u;QxTboCh&Sc&JR#*rx#ZofdV@1n^@p}u zU^OakFmWoZ>s=yC9*HR<0VC&(2#yLB;L^xw%^@i2V6&}#06JIT2Y5z* z$(|em=I&-A<}yHHLU$skW&95k<#NK!-cBm?x(Eh@SH#4CKH6PNtBD%5?b4jtFUB)} zR7HsJub{YwT(05aN&HM4He#$>?l5(8Pjo+;TTPF&45ZsigqUolAdsq}xwqv_0lfL!Xvl}*kHxkU8nrvbmI%ZTHk6Wsf&oLM(SN9z2FBWIVjje=( z!}38$SP@%S40F*l4CnALXitWG7XJoKjfW4; zVp1iw^)C14J~l{B8QwKvww1qH#I>sVl*x(7hqpF4Ahxb;H26ux_6enK{Z#57(%tjj zbKqt8Qw8{7@t;B<9(JmAacey*O*#U=NZ3@>fQ4GGDpY_Mr6&~$u^Lb5~GL5iJ9-b_22&o2xBBz9{0DteLr zry~kbXEEr!@PQg4olDclmup$s64Al^;~6K2rvc?PZc61zsu3;Yk=laZgx~U>_EU~+ z?RU7Uo;AhOS^0$?R3>1+Xaa3HS%>c$(=sp%OX*&&nO?%Ql-1sRa=mFZt2cw z7B@LHM|46X8!+L`nhy!g+?sXHL)W>edPf3aUb{1Ke^LdCaPc0;NRCyF(=wIDNguZy9KcJLHk>M(b(F_hYU(mt|U_Klg!gNpO z-ql_HVswn-$SZ!J!U%}^W@guD2WyGUMu@4k5xZbZCPmQSlVmObaTYjYw9@qdH4<1X4~bjmK;6`}+a5tGOu$bz3?u zJ2f!ggBYx8g~0M3bbpLkJ}nLS=8P1%ONqx{HdutxKIqy3~hAvXx z9iEs9>D0rEPH+XIPGB8-+Z~;O7$d)W!t>^?vkZ1Ao<wi4xP`nEx%9))xc8KlxE<%cZ2g$GROa9GDlj!}iL zm)U$fNx1~B1}-Qc?Fm431^$#OGz}^SFhik}H`P%$?wN^{YlI7_HLtUYkQ0NCxc|mT z{}HHP`a`pMbIYJ9%l-9t0{%P}!%=b6IyP|JrC{z#BZRZ?Gxh!Gp321q^(1cH=oHf|#8{zeMAB(#3DU29#_9Ru zeZ;v^v{;s9I%x;2GoQQ~mB;4_Ma1xOk}QhSNvNNrFPbWPDg{)Wx5U8e4aD`*PqPM> z@|OD5vcFhN?Ydfqwk?9h$*M@rKSv|)Nxe=SmK;Q=reZO#&u8dr4Jqfaf_YdMWMNJ7 z#ki!jtoNb8+UGY6Aa@EaZ#3SJJVv#w4Fhgb9dr2hl=yXgL~lrzPR41x$pn>uj$%=v zhX%b}g3O&>IPv~GC3c5;d^kN4sAU96rgd^BkPRxL!C9N`GS}l8(X~C+#TEkT9tKMW zqI#xGBQy#6Qtt4=uQd>jznjDH8rL2YHSfP|mhZKwqT?Rt$rxRb3O3n<KdoamB1D?i1~QaHj$k8FCd029U5Q%#4>(Sv>#QzvJf zjMOW3d#1MQtiSo4`K}m#xk>x{WEDuavk`#t_#3Nkvns)~D>SLb;un@~X0G$L>wmvEJbucaDXD)+x!n=@GoebTmv_0UG4M?T|i&c?OLK=`-hd{)JT@T`UMizW$W;eGq5%c;ww8sQsC^4&ywo`a5dC?ztV*++pqr z)wW=fC3jtA*3wuu5cGN)p-Rrp!fn@yU740cMUk%Ou}HVNY(~U$G_&vbDV0G+y|e4K z1}-jhbA0`ha8}XHa1ypkin$aA!go3BnsE0$xHuCw9`L^1?L#!H{Bp;7EZ35rrmJ>+ zBofY528N~pyH<-s;@O|l%41kBp@pt@g;G8lU#r|F+dI53y&{(*?g({ zetK4$tu!Ip#r#b!Yq}boIq@{&rZz!~C(J9hx(N(-$+%NmYd8DInF#-fn0JAkuZ~WN z>{*R92pJ|%zBdg4^+v*#6#3SPUqmd_wqJP}{r%hlUt-2+BnOuK(e$wQ4TmEi%)Pvh zJ*XHHmXPqbu^|2_pSC~Rf;&NB?P{y5X{+Fc1`v^S6;pSAOEU^?n!5Z^De7kJ6xe*~ z{6Ks0MXlhjV>+KZk`0UL9a;rRynlg}9S?aUVT|kCIKw(AwP3~h0S#AX5yKtN46sth z4TeuR^CZr{)-K@wyl!7M>f5Qu5JQX){jIt*r~){#tY_E<~SdBmL~eu^ZFy26O(%6>)<&S{C228V3PbuHtJG7u z-;02$Q(ns8-YnRSO^2?(k@4mr@@=MXZtB8MLeHR(RfP7oNf3I2Ubs*t-wp{2V zoAa5Mr{xw@{Hj_8GwKi%<_(@4c5R@0i?q}YQL`tjIKEr_il zAM;ymUYX}d{C-TH$lii4&=(6j>P-+P=qY6s0a3}+^SI{AtGei&e$HL7v0O$wZj$6q znx#&{)9P=-7NR4X;q?Oe8Fwk4)LN<54+^HbZ-Bx?t=PiB;y>Ba!Q684a@P!mrthH= z;>9kf{OjeaGRf0+S}EFzr2?rmS`sSC(+MrC=qbpCa>n}W=nXFV8Hrt4$-MBEO=r=6 zgFzYI7B5#%SD!U={I}=t9}CX^N<{zT=}jKja|!?N@6+I?%52VQ*7_j#1QBI$2ycbJ zi6rXI>#mbd*X*NYqsOdutXRIA1n%=b zDUIEiu4B!s-knRY&_zVyFwknY$$XDG;_b69JL8uWxbra~SK}Fk}lBmlkq_>=nn|RHuqwyhi8-;@*X288 zJ>HDPZej}Pxh2PcG3<2Nsb*&Lq&Jr+Z%E|hs1x?+u-hM<#X~~-q>1heUIPEIKCgq=n&=u9QG-RINV$_l)v zqwpgG3>%My8nPgWx>8k|!*Y2hp0bW*cyJSAI*nrLN{Wx=HKM9^QETO*=QWmfOz`{B z1WVevsZft6Me7`By%^fExZ9+YYV!h+!MDCTtZ&YVALJ>JSz0`CoD0oy|-W zyeWTNs%2nnIc{i|Dtj}-#&9QsjpC~sUv8o1FXKADSJ zaK%@MP$*_zh7iW*h>GiD-v+K>IX^ws{CxrXyQok^D3vG+IBwglGLjCM83tZcVf!$U z$XUM12x=GFP{*P5mC~AlpCa-@O-R_(@BlQCVRsbg>lRnEen)q7 z(9l36UOIirvi4w z6i;}#5%peG0DM#W8t^tRFD)NiHiM##Gs3Q<|2qm5Us9O*dVzLp9%K<(g6u{-+Tegz zK-(4kO{m1MyYOtaI^CM-nkL7Q4kfx=H46e@ivV{QZnq>kG2QND)R~TjO}o&yZWdBI z-`NiTqd_pN1Tn`i^7Tu8~k8iCaBWD|d_X zooOjS^_b1&O&-wjK)Z=F0s|Ax<8fMyv*Z=j#`AKvR-8`jAe+lIrR)SusNX{^t@i!> zTqsDqA=@7?r~nh+g4DUk7AdOOslr2MDW6#y%RNa+a<;zT;v=|f)sZsV*2iW$Lg#Jf zJ(eu4%IezU8lT;7Aegc;g)wi;as6mn!q>jn*`4~UH4nWOgut_}y1IqoJD_%|HpQ>C zoidxF2}V+ifbyz0@fP!XLU7$px!yNUmPP$A5^B<{?!bx%U=p3$z@XhxjfWjv{qMCVO6BA<>a&>G7s;0r1n6w2}kVYu& zqMob`1CfNBT(#3*?p>tg?l!RUUs&G?B!{OX6xc8fq8Cg7R9jIT*>iU%5}KhuO?6h! zd78`fW9Esv(RsUj-C*`1&NPOi0Bj^)?0BBYv z?N$IG6&z~q5da|cF9yQwDY30nH%I=s+691qh`kH!S%bqN>rp8&hRZ0ZxprWF?hTs< z`DfFssJtoSMrc&xq$6JucBH86qK;cd1r)DJ`9S#!ZG=m0JLviM?lWnoOew{M#By#a zd%PGso){*CwE}B$&&JQse^$k>?XRg}cjX?Ca#qZ$lvv>LVY^}q(l+sd)AIAUt`(KK z#q+%Dj@mt4P>YfFy_Ir2xXy_7SNY(dbx{t)$qP)2Meba}fPs7R#|&zS5kCglmx=cd zw+IR0%246U>-fmV9=eiFu2F_5z51UA@l57Fhv)y9qW_OC{%JJgDZxt3O(!JgMGAtH zG5ji%sOp7}cQb1%d6AMfr3j9`B)B5DcoRrM&Kq{zg$a$0(&` zp7;GAj4x%FFp*l~PF%Cg;^9bZEXl;U3V3A_avm$g@OFU3% z*yqaR6eS)&!EpzO&slBDn)EKKIAN)yW)l$TQC$O3Be6bJZ#_7mcpo6!&O%;% zSw~1>*GWvBGLCbLqs6jvyR zE{QEw-mnM!?Qp`9^!Q3WOY3`Y!dYeXLEkJ*RJ0LUKp7a12dJwi!-&Uov)je~32(K4 ziWTA|zr0u}*Ls%BF`fuIcHCTT_F^ks z;R(H!&2}`TiMD-y;nhqbix0n>*RkkKyQRsHd)gwY@*-hS0({TLg7my=Oxxxd8tYmh zQmXpIZ|;dV0gf?5ScHZE&ln)$j))rFmlx=xUf_@v5c))!9|r}(c`$idayO{!U^jZR zSG^O$=hHl9LT^1ORBzy2U|{qu05f!y%K=YT^3qN=^ehvH_NkL)*MZX2$<>ZHIe7?F znmva?>giLpsYIZb41C=6g}``_1#CedwxwVO=LvW~a4H?6(ieX&+)WwQX3%>-SnRb6 zSc=&%UflR$RX-|Dn(;l-%*a~TLRZuH|k!jsdA72Ur^F8cx@?F(!w&Ws>WwN?qVPU(F&DB%u3RY+so?r08@wj5)Wgzi+ zGG6KtaX2(`?SAue2x9x(XqCBzd5x<_il9E`i<+D%s~QwLE9OS&UR2*3SS|h}z2_)=8BW_QPsNzsLBz z?Ss2@BwYBaHlCR&6yxtZjNHe!3BcQtjEI_XJQ36i`b&2Nzrc1eJk*WEQ(^#>f}@inY`+|lxG0kN0p=PfVd3S zXOe?R{3CQyI>n8Vp0C2feKJ8i$xPEmw{{wT2$#Kw<;9>aMWMi)_z7Sux~T8$(Va*f zXs5}6eQ@KSOOVbw=Fi^5AZ*?JB}TFg|8)IdSlWLl%LFC*(C$@du<@^kj#OlvSwPev z1U=0PsIL5gNLZV|;6CfKBy3(!@FQ=w4`DG6Z~6}fLtgS=r&1ZAPS>>I7)JZfr;P%q zWL*Q;txQ|W3cdMA4gy$u1?U{E>FXg0F%~&C(6}?lKj2g}|BziL zyUr*6DYE`2#r5A4-GBW6<6Tnu4|7tjf4!*tG$gY&J5W$N>6WbfCL^ji@wZ77V#U2@ zs&BP)qCK-W5hRflru7NA!b0hZ|IK)_QT0bI-OMhkbrS(k)vC#}0nIt*iTN+kpGsK0wEN{EnYn?-zoskx)5)F<|thH(wPZaZ9P^nc=>aZJx31Gb{eX zWA$Y_Bze?58N^VGV&Q`$F!?;CoA0yQiwvrUTPZp#FvNmsL|Z#$`2wYi0)&fxLR!yy zI2zOLRbBXcNn+}!cBTWNe^WfB$aP|QV%VfS`ZGxsU&?BV_U_H>tMJtNrxtY-BOj6u z8oK-v5cURTi8E|e9^KM7%Y7CkwSQGe1Y2G~av2Q289eJ2SCHq1msYM4@?Nr*b+If` z2>f#V_E1tbuLF@3*@t)ttIxE+_8s(vvGIT-1J9p5&5I_=)3G|k`_plMn!Hi5zKq4c zCeRUM2^PTTYXjEYB_*;%wz~h5L%4G(V#_8!T zjZW*)!7`1EDWzt;1FQvuUaERsP_kohlt>VkDvs;3Krf&4jD|mT2133RN3pH8%At+= z-@JDvynmowZOk*Zd_f6+tb0x=lQt$h_WV~R*GZwUPfLjsP(*}>?0#cSC9EcWW-G8> zHX^N3Ks3g(G>m_y%U2|U^M(C95u;;lpCxx1Z$2ys^c!8& z`xHQj8DHGo&$)X3^n0bF6h55`SbVT-XPq7a=F(CnxQL?ap%;+lCTfXbvI%>OI#nNw7K$lY$yu|f-n;uktDl@Bz%;1LnSYql@V#klsMT^(Q6oy=0 zI`xF#^?dK?HHoLNh1-(5ru{8_)ja9Nqk0zl^Yow{qMi!rQ!d>pqOXWHr(^ZGCF6X* zZ}}%Lh*$YOg4DTs*nThLYy->pJ8v>c=B;&CDtz=nF0aAXW|{$!tAw;g{N?g{*HBDVd( zm1x;bO1YVyxqO#g3+nX>DN36osHw7q+^3;o+#Hi7~IUqlvh zOGYz@+I!Z&pT~*Fh3(zwS=jj2&WDECeQJ6BH$VTcUTrlh&Q-`uM5@8-#dq{JtTtf% zkInn*1-59=eVLSpR~~g}AZQ_nJ3UUjCO1xVB^H;{*KZm2^Kp6{PI+v@u5CunP1Q0D ztsd`C?@IBIT7mxvcIL8b*}v)zyJ%Z+RL+-vaUJpkg&r8RS4&y1;^ag}Ur$D%mE%f0 z(5!;?yOk5(0>|v}>n4KylD`F=hPMTuW;1nRd@@S&D~!L)2U#{a%LzC1)$vTItCC{0 zA0Keh4CEeDC-;vm+8?SK(ZOVO_xm5&=<8_j*<}|+)=YDWBv*8^qSklIJUz_Beygr3 zhX2?Hh> zpeFY{3&Xly)|v$DheLeF>}9El%Gmy%-Vr|ns(gVfJydeJgczMr3m$V*Fc$hq=^GP~ za3*BMhug5Az?rBaz16LbuL2SRW^~1tFVpT~#dM;$56Gy9e`Jec&^}Uc=PtBiJs90I zz~hymVfj6)cMH3RCO2shuWJ+nH+EqkL;eu`yIB)&a3?KCTc7sp_s=Wz);iRccEDPm zyFIW(hFdnKrWKtM+m+m{HGbcOL1A`_eBE%;udoWXR3(>O%jV!LcvD95*q29M54Esp zvt)MZ_%gCT?hlE{)<1@B3XCtQ7!z>Z=vn_)mi51d#Q!{-{%>L9e>iCWIo1fI7VBc* zij)@;&H>QEIyTA|!``*yMy8zFmhzepvCbSgwR!1J61ZxBLx(Z#&d`SP0ISb2%;Zb4=Vh%EFT0?{4nkBay2!SrO8eHPnG5}#OMz*7-}Xk$9& zgxT67X^cfyq0zpZBH9bSsA*>BhflMO)`nIT zOVhJI<$-g>#KclqEDPm`am3)LHAfAfJ$&|C?CXS6A}S^HD|;`1v#jWK_v8#xst*^ z`745$AKR@fh1FJVj`6~NXUb&yqwcJovrC7wVLy2VdC4&JPs+`=qMYkg?D|I5joK^S z+mO1=Hwj*CV;y4xdlT+9&J)5XpiMx)C2D)@H3tjpfef(xw2Sva&nfLN;`CK1ldyY=F+o>Szul<;@^qKrKG-R+(GTPO%5`EBdD2XYrPk zd~m3q=K=syVJ(3B$8+-)&E(E^#y;qQz0NLfIwqq9=FE_`V~CwHkXN}9n9x;yqpU%= zLQ=|2yGF-D4pc(S_1$79ahP|eksolQL6{eQ<<+IeBLYjY%mv{jj(9gqU@e3#S{m%u z2dE3+*7Po8puT@bZMjF1q`DMyc{v{gxXw^ZUOml3=n#9{@P;6Im?13Ii10zM*%nnG zDEeVo3Gg(ixO{swdiLLL5JkWx$a?N=EEZgCxHA8D_-9`h_F8UliV@4J835p~WCHjt z2wuEX&3leOAo726W8Z>!743=(-eKPHUoe6MPU#fr3HR?cOaZxwsRaaRNJDv`(&e6b zKdaoixr0XCpK|u*i=(Hh$T6Ttxmindu)BQfS0&cI z2f21S{cjb0!(Vz#n2r}NA}qt61bylswNF?aRES88i~;%q+HE?&2ml`?b5o!8%PqkA ztqBSo14C_Czx^Gy@O)okzj1-O3#?;s5Py-KFCUc#+yV*Yzbm7B@ z_{`IH+1Jt%lJ>zi5|Day??2aIJ1gLfg+AF2LMABU^4>!dG}m& zj-oc}kz;wmjoryaw4KeBFGXHiN_tmB1HYP)ih|;OUbJq>8AmI+K#37W6V)!oa0YXT zUkzFSVzX!MGGLN33Gi(7Y>Mks)b`0GAa3ndhsA)Er(Q!gC6aXJMwt9X3`t~llsz#q zs7RVrU|mbVCRy=cSdO;em&nXnR5uOS!^T)(BsKEItorwmgKzr~)t{%QVAj)<`V8{Z ziNQZ3=0Sjm8(U+-84-!-8oA#ika^7ok2MF;&jqm2I%MWCa1bn6A|QWYNB8tLbfm#Tj44vk%1vpL*VaolBTSN(nj1SKf62ZbPm z9k;S~BYYC?W@AnSA6lC1t=tKUg9jFZlpv;TQ(5&*a^4*cznx07+s(Cbyus{rwCQg? zq^3;=>w1CJas4HIhpG#9SewkvZbR88N>uLxtqlKayZP%K1C2V|T`QUzZTzYZBaE@-pa3EW(fDS;0JCTO%u6SE7EE2epPnzK z8D$}%VqdLQQj;TRZggjYakRE8xtKd$x+W5M)j@_I{?4)>*r5+wQXk*l%=Tlj3mn}PJoyLj#Sq7c2qYOsq3ZEGjU z*H^#ei+BR-h?39n67EaA$2C1)`4pMaAZs_tc;z)hl!RZ`r0()Bz{D9^_8gx}b$E;3M~iM(hJ zWQ`rWyEs_Y>oqmZj~q??>T=Sm@2voVN;UtooJiK{Z|c{YUpn$vkfciuMz;D9`Y;+~ zh93djx5GWuL1D`vrm-bj2&5P_^bl`1$m;!WyIJ&OUfX-BwWIjsM+{96t*@BO5o2UG zN(U9RLMX^S+Q)mizuTf=V|Om4Yx_DT$c+0~p5Hcc=LfRPIFoGTN{W+V?Hl56YGRat zG$ifNEg!#wv(GTIR&;zfGLD10} zhQJdPpr;qCnG8dEMr3%9DVT7upM%;GPEr}|`aUaiZj$t!b_P~T{(N(HK0o()i;>Zx z5PvWJS*F)(SyU7-N`@_;{8pHXfs`KVKkkn&sMY^mO(WL}$?&Z02A;tCj{ZuVyCMpa zpWG5A_3AJGNKfw9uVc=Cc#bRxysW4lB#MMLtylT`hP7-40$ur1qX!NdNVN56ol4BC zTBV*Pvr>WhN5rjsfc^1Jdu_ZdQ~dWF_d)xRpmGn|LID)QekxbHWzc(HJz}Vmb*`_Y z?a2j`$@-T#yro%;TH}b%iT$ec36P>XWG<+Tfb!H}#xt9ELH?EG$4?!jK87d{gv4_W zHBw}gj#8H&Yykp5gA4mz83`LIct;m`5I7m73tO`U1khgvWWUm&U5Mx+|f+|jz;duJrTB+cT zun$wCj7^6iZ~sq_%YXd(|1dQCo8R&u?vbz9vG0E$Ja9Yyg{Azy{g1OyqKIe_Z-cp) z6{nSTrnu0Rjtit86F!6yz@z(4x35qm*=u2a?3`fn>dH7?L_XrYXJMxbk_T%STvOOC zT4Y!ai(gkW7xpgsmQ#1q?Q^9;R(MB@|8J#Et?+5>Yx~OTeZ0sU8|TEPhgnKusKCK)@6P*GvN|@dElKc$! z&!ElOo_3nS`^#pLNt~H%YE8Vhj(VZ5SwPUM+Uo4F(i0B# z^l3+?v}?Ns#Zrf+y79u_8Gfi~QZ40?0L&kK@UbARq`~~_A>~$;jlgbB&8TTd6-@Q0 zKP3%jZ{{p&;VJ%DgZrDBivfE88i77;5(J3?jMhR)}*v4CikZ%%vyonqjbc)i& z)j;+W?vRT9PomVA<0xS}pS#<;7x6qque+P1u{ETK!C!f+zEIujsZ&zYP%1-|*C=I> zR2X0ojw>c5@;_(YkCS!XBLC2H|4}yZzqxV#S;@P%-c6IQ#JQLjFLa>Z-%Ga-8CC4G ze(l_AqA9gTb)mDtm>{r3<%gGbgMm6xO20M6b+f9~CjMh@XS{PH71}bH{^$WO^)FSU z|B62kWFvgEn<3KVCClSgIk*4Ixp%5)w3r*?QaPH2r+f$U!*j~f9wp}mkm1liS3(Ah zeV>J*q~oovLNq>!DM4PGw)na?L)gfs5jFT5g8bhQ{+q273_Db z`-?05eDQMm-0Vg})WD@tK{(C1w3-KLy9Yw8g>`MabKVgKI9>AaP630p!xWaXSH&aS zpD3tMuG|GBTZ@yAZ4MB<`I;Nk^ z3(#dd#s18>kpJycCw|LGtbtkWBy(A zgV%QHgJt-;-VzsCF&Z#dKkdO8m`~PkfCV;Z>f9d{4Fl)A}Q!dkIV~F~Im0=`PZpmf9N>2@c zh)!8^Alp+PnIQi{qrNbwwAu*itL7tIptb* zn@xEYiPJpx52X`zesuJn#b5v3|cim3OO`#$G+&w0Lgd}DlnCE3YZ zJ7Z*LuQ}(s=I_!hk0ZzP1Q_V1KmLZPx~O0`UZpxq-4*t~R}VN^?nuF0o02jjewzS8 zVtBShe_20=p-AA!@2ldu}O1ze;MiFYH#BTBIjAOO@Ai_8=AE@k)fLap4vm&EI;J9 zMKgb&Ca1A!=9^s2XB?gfumNdi+M+b@9Pp?mdE=)(R}aksed{8*a!ZqA3WwqmUf4u z7#&T-Tz)KF!%l3glQovmP^oM{*L{z{w5#@p)&8-uGuG*eTXjz5e_ zu_-5XsJ}+gxvrpCMB69BUbnd-r$x)xN6NgfaST+V#&&C^rxhOFyK-bA8!Jln?m`Li zD=XV~RkY9%^$p+r{HDE^ZkB42!+W7V+lb7BX%#nDJf^Y7+>&VauMwpLUnoYOJrks^ zfjAfgW;%?_;G;EN_0=M4q3Y2^x)tapGCSK5ObI6v8DE*gcM;!^KAf0 zQ@ygYCy*~C?s;wGisu3&K5k0y5iYYhgH&EiUz+t)y3n^$Y)=2R1pO!Nf&(#cyO@<~dlC0Zp+0B%P;MZD?N7@0 zE61KJyhmn~!r?fk4~N)cWyDT=XIk796|}k?ry`QjaXBmLVAgiH2#kg-CecJir8~i3 zn7X9WmPs?s?90*P7LR;K0KtLN!ulG|H_itU(-x%`V@#kD$CVUWjrSX}AjjlQ$_@i4 z*7a88&FCj6$(|;<^4pf4i@@7k>J9c>Qe%5)0dP5;i4LTaRDT=n@?APylp6-n#om>^R0Tp>TH zM0Y6k0>HUH!3yRUXQz8S|JEqlSQLcB4wrSNAtwq5b-$>rIIk+f0hpMX(Zt(ZP^LZ8 zmTyc~{Vgr8(r+n-s`6U)+K{DXCit@q(^MuHJ*7EvzU}mZ=uZl!*-m68t9^}!m+KzR zGvN*x{O;AD_TF-qs2`mucp`M1E~R3GRGbZ}&g4%M9bhx(XV=Ovr82ju@Pe)p;w;~} z+;o5?K_g?X2+=y)l~hZmao}<*}@6iew;^T$7(zPIqsZ8VX?Y>k1gn| zU$QM|8iH||hAC!~SAo{Ci}{hmAvGEHP%SV14)35~!bkKg0D8;?ZeV4`Vq5D$SiXe; zfjK@dlb{0~n;ct-$}fIL$KZ<%xH79psNCVTU3vRB6keTuJm$iunZ#}q$TjL=V?Sfy zUjjAcwADfD1ydPPmxf#_jyF_u-W$Fj}D5o&g+ z7din6bIrqBd;c-T$^u2p)TEgvz~_zXs@H4_C5|p@xq;q`CTx97jpq8`=WXn9wkeD~ z_k@UUAj6Sp@yBlfJLu|?$9cTFUDXXk<~u6wtH)RTzYBfuaK7S}qSM=E--dmo)kow0 z6~Js{m4^m0q}!Afw4IHJ%@wTZ@zuGyspK<&bXn9c#pJr}Q&uH4JyVOGQ60QA8B=D> za-)1Bl6mNhk|C1xhWqyizjHC5$rowjfnL?m)4KduqDf-z`7gV%8dAZ|lmAuT{?E;m z{=bO&|5e_83TZ)W^h==ecB5#`!ut&fMW1rga#mKHWDt{Lq}eOowWc}_?w!t3NVY2) zF{kZ(ctG?BT@)@s8i(2D8T`FUaWULIe`R^B?n4o%=0TSOfNN6T6^)1y-|gH%C}hiJ z0UFQlH)BR-GQOGjl(OaoCNaTXo%8e)v)KKT+BTL+NXuskya%=8ar%QNu8XT(^K}PL zckiXP+`tR+zly2(u<#C%05q_kYrNNJKK1OcoOZ43b^NqjHY+i6=HyaUr~RPL2B|tB zm!zCDOj-j_K8mw3tK_+u<~_@ub0(NFJ3m^ zpd)cSjRi9qUP$L)%{op&D?zE{3 z5c<(ErXAWjNZ2(D@RjB+B>O2A-4VpT4Ho!J;UeEdU0Lz5MDb|M>$Ml4J3DS|va>Jc z1o?JuP>s9Liab>H$55W|2(k`*@y;cqe8by{(57pC(As3JcK~v(UfUmMDN}9!-g!T)|JUT_ZYSAu1S{%JcqZ(scUO8U%lh6#jM)E zL&I}{c-{w?=9%2$Q@0rOLRgrcYkkfV(bXHF)*y}>|Nurd2GiyBzL-+_`BUcFJi8san7mSTE;j% z;*-wuqOO?c1*}WkUwD?yM9>Lq-tu7-NRdNppTEYrUa%qP%r=*lxZ1xA%e@j< zgc)G`PrMJ4H)=}aMg<=u{lIZB% zwA2TbmJ?<2H~@_u<7{EE^9b+0f7=jJGwu*&AL$jh`CM!8GOvPnY<`;_L#Gp{JL+5ACB<2(PdedW4Zo4WSS(+PQ5jU%nW#ffNKkz|1+dg zXu$daHfJvPh_pjS=vmAPmdVSFmtfR>spK!TOnx z1a?j0usAT`z(^8jtuI?WZ9VN{`>MjO$YpF+cnYf@myD7<6;qMp6Eh^15cwIfkEx#z zWU~_4A1MrH6CLvHt4!3Cbb^+&$Dw3(rMjqY+W9x5LR`c%8omYG`o+!Vpv@OioXERQ z>=xDaQZGxY73kLPYR{A6dUvF3{%i$>bJSDfX`yf4G)PckC;IdF<(IqTT%v@gW}F*UK_q`vgm+Y81e8^da@iKZ2n zSjvPSpGs*{We-m+9r&G}blw$}bxL>Od=u1dBL%E=Kh~Gpfj?f`E;z*Eey_<%NAQvO zBlV%@sg|6a!r%`ztljf0PKTjtL*X6d>|#^S+n?X=i$T6PNQiiL{Sr5MiobuKQ9U=Z zZf?$|z&Sx=1yI)heYeRTRm+U(;zyMBYrQ~GFYzN zIeZaL9Ys87>qa(~8*4h!VRdmpf!}M5uT#N&bmc@c^NRSHj5=^Y2F@CUWBJS7D8KSI zdbjhabx1($se#SAbw$9aF}_el|4Tz}gLC(X)P)i5A5;A440rm2vvq7==kdzPwZxWy z=Ml8Vh6L)m=5cp+4={&P83(3&gM<6tw^GGM#H&y#)-@g=$2P}nipx<6W^(l3RptM2 z8LQ>4KJ5DcOqc#ogz(=C8S5ASNMGo&zd0EIV@@6V1@)TBPuvxOuPgX%0%)AfLrRxYAOyU;|kd5aGzG+{nNqy~l~^Z;?7BxcTI=CrPeS1~v()P}p17Gg?> zY<;Dr=g|IT8)Tpg8jgUtK85F6v`l=}hf@jmN%0(-y%Ia+-GD#+%D;MHUL368)hLknTY6m3I7Vyxh$ne( zJ(b3QAa)P%E7-ugw*r~wcd&n>(y(SGHXtT_z&mIFFf~Ti!#|%Jm^>(1^Bv+@Ip@*K z0)gPe@L{GlCQ3?3H&oqM9O$(m;S=$__7r`wv7vEmY&g!5ydOj4;?{-SUN?$dGwm3q z=$x5+?$&wRP?Dxe63)iM?O_T~%6q!K`mE*NrpKFaW+TXE`YxdZR(rw~4V9Ero0$-8 zwuF;u^XE}3%!Rv$LBRS)jmfy^k;s@s7{JW5Ow%WXIc4fcdY{v=xB z-IQ0%k$9cq<=vLwo>Q!G*WSk-ZbFauDNBr19Fwe^ge2RT;nv9!xSbGb^iDaiRC@+m z*9h?@T&sA@Dt%AL5zC73K@QtF8Q8ckYErhbn8Ki9+&F8ynzo;ctBh`vY)oV%ajR%Fro@3H2lN9-OkQ7St{_?%?GZ0;GFpyo zT9pqgpB{mj1v%sMCi5zboXg+zN_^fe^VPTeqx@vDBUnIZZs{+)LbQhJ)5^06`bp!8&%=@BS-=iMcJy{N_!co!d1kFkZ|>PvKq%;P zT~o2z)CD%K8|EDs9ujAwq*D+=5aA27@0|S+&86J8&sxzf>(<>#O^D|KmU0e(hlr`z z$@f_Kr3U6S-CH+Qxju}(_T{sCu9X7bQvUQ9eJ>9Dkw5aKF2V6bs%Jvn_p(R{McpaF zG}7EtWgYTxy*_yjLoQ%j5kt|%G4P5@4ZDF5%At!EbUX8$zlDZ*j--NgrFr*#2>I`# zNPoEAGii6A${ihV5nR!qh_(gU3DNOQyp`b9Mg_8!K)|nE8(mCW*ShZD3hjdA3LeBo zSeJ{D^7bu6{lk}{a1RhZKG!Y3Sg+-dZEbTM?W-7(zF010-D*yZydZaRbySWAt|7Z_ zA8SzOd{MF6bAs+YZ0ApRZxbeMG@KBm^tpKIGfO@zN~?8<@*gSMpd;`D zP8)cML$=bPj_0*c)&?MHt_Xzf zkqoKW)Q!H5Hw4aU910-OpditpLk>rn6}}m4gyV10PYn;Q`>lH$>g4+^4DbvQ82gBB z0hlQs{0tG<1=S>XG3)SkLFK&12#42)V5TyvKvh~pPR_xY2-Z6G^@wQh6WUPsJJv^6 zN^3hDN=>q><*X7ejqK6XA_M_FY!+5+ukiR%D_FeS){SA_{m)f3{pWb}P_hT87FxTR zH;FUXXQi-QTIuwgOK@t<915q52ANxs) zMdarmckE0~N-5krKzu4L&A`ysz7!@+4-|<5z%XFN~V2+7bx7Wd1Q8F}WfcN)4(Q5)o9oYJrmBWNUV#tmNPQi+}5NaGs{Yuw;IXC<| zo?%lYJH4c~GTn}VXcA^f3XqwS%My-l7=z4Aq@|X~-hfdhu4&lms@&fZ0#jy@$ul2Q z*4kZ$zh%V~CiOqw%;H$)%{95pxh%8kvm2j8m}uim9CsV2(O^ZIU8Y29EAZ;^Y3}2) zPv^05E>db2@dOnL3?5A2T@osrJ1hJo(M-h<(4eM{dGx$|tP^p2`97 zxIKK7Vqg8}Cvt?%#17#hGga{M3!jXy#Wuh3q1p*zT@X!PAolE$i7mU$ue8XbqRVvgO$V~Zn6BhdnO74sx2?Oqf zf+qs48RmLDgAL5wY}}D^J7Rm_eHk)IpO0oU&fJI!qC?2AWVa@ zAMG)U@=!zKoOW+Sld>u?fzwo9l0_T{-bo4&@vVut@ z8S_2SNiP*=3^H&Y_3)*|?@8VP&osebko{}X%qlMd?}A=gaj1Yc;~pjBpr-SMDHs{1 zt0#|A2}fU!I6LZnU$8=1a$s+00Mz6@2X-QayHD3$8LKigqsG39NFBunTsky-3-dhR zmP4LR8-(b$v$g5BdDCpu%i&#P^XFd?!~-5i2R|3u4}Sh&|IwQR-s4WM2U(0KKD&Av zQ!)(NE`q!9?0D!^j`Bfu?CrH$kMu=|y*=M7Pdyvhl@I~FX$@Maz zPZ|8x-jIbeXPxcg8;G9+%_&vnr>wiw3OVKto>M8WqB;F`DKc+%VhbbYv<@}oQ(kK{ zmWokIO&qshV@m4YMmG|Tcc+BK77%6}oHERjI%zyo8ihM$j_|R1Ej{)i{s?UWcreGK zKDL)InB6b}lgIjXvx*676<2p}tzjhOIKz-zixda{3Om>(;jNt)1O z!kBB{6%+Qa@ZN29A^_j{2tYX#1fvKv(HtU#qHh-e)VIHL$_Q?J8Q?&_N+)K$u>C_rz74I!qF{HOM;sEv| zn{qYHBCTw&UtT%SK{Ojd^5Qp4NBx_DM%%{DhYV!}4!DZ{J?{m*2w!>-#O^uEkzGiH zGR_xTn1&^bgLF3ZviQv?KRU000hGfWM}5eHna?d?hI@U8_(j|P)-asSz=W8_B%BhO zupeMTSt9H!joo9;P z_kn}~#TS{0u7+K3M-5FbFE@fr(hPq~FQ>Z?utC*m2JUW&&6@Ivv(q|^Rox7qX1oZI zM{3LjD^Pu5PSsG4`TR=~fk5Zv#+zQL<1CVh$%rKJJC=;uea3-usigSdKEIFHyQy?s z6PxVC7|MA^ zdv!hpAgnmM7}gWHvRPh|#LIurT;t_X9`!l*pH_|hda0efk(UkNfY(pI=dLvS92m1x z@7W!FSA}TOFqngSTaa>*jhvU@30DW%9NWi1ub3^$G{*H&F;$W2AeS!0WA0!BztxcC z&rL(9HhI;ZHzm@n>-R~89!FH?ov#F7-e9)vza~_>y2|Upk3@>B{41N_<_fC22c4_g z)f`kTKOA5M1WK?H)KU=LXE#!vrt38}?2rZCVO+igt@jQP%CnwF?M(V5WC}Vxjhfrfs6ws)ziOScBwgKwK+VAgkjYkBO%#c3q z7*9Fx7k9EsUafQNEUFp(`nnGW{ywexvY0OR~hr$ zS{w&W_5aQ#>>^&0R>VSiS4$&5>kjnuFMdw91j(#C*1V^BT>5h1O-`L?*_nj!%=OdJ zkV-lCbUD%adj|%9Plpg9)6|{Xc?j$%=G4s zx=cGWYgYR{CmqMm;ir{M0RN(556!I1NF`49uGQLOcs&o>AUbY6kn77lNp)8)v(JMC zh^tqeaV0yv{5k&K7xjEq)?$`GmQ( zX^W-0xT>idAHev6!u#t@C2xp)P+cGDs{Nrli5GW+_{0@iGp_?)VLR>~jd3 zJw%baqB4#0RbkfC#Xa{CYnxqP?YbcMwO!*G9BKVO1>O_4KO(7mFDI*I{d;KWGo;Pj z;>(6Zzk=^nv+{;`Rb%^CtuZ5;%L08y~;-)k~kEO%&AFfBI2yV+-#raAfpadW|@)5<{M?x&$m5Y!l7-{Jn4_HyZ zlULB`=nEouI~B7ay+e-H;LVG_*nS1nE|NCVf~Ti%Yq!@e)Re!!o755kXeS%pN##iX zP1wkwEKeRI_3DF>omJYcdQfj-Ic$X#tC#mEev;{8wof@W_kd2^bdJSYZv z^C)(!CuD8%U{N43vd1Sp?NzGIf`aEKW5o~pP1x6ev$4KGem-h)f~C!8PmX&e zMF7yL`N+Y&9+wa7(WB)x#`i($&J*--~_VP5QnnT9%;_ZyWsIcifNB?1NP;@+9uvxRGu4IAWE$ zD+jm#HBug7^B?_0M2(TI1F(ntVHOFcgrV+It{|V_f@O1-uD#Pu!C!ck?lUwKn{(t3 zCcj;~>k#iJW$gw2lCG&n{1$Ke_5Lrsn3nnFzn|LRKI&T4_|wtpy6X?~oF(|7jz_O# z?^d1kf1af~UJOs1;)a)(kKiWS#?7d!`*M!upSg7r|Cy&Y?|wPhF5%y!g?kxa3|P7I z{%1@kI3bep`2(E2ll{&QQ?#*XWw%|Mv-MmhFA5bN@%k8y<{xNUyIK!A7ZBFClano>mG8 z|IFN!Ul2S>w+Q(-ko*^(ixVXQi+gTKPPt`lVWd;9*?vvpQW2!R8DvaZ^1g+zv&6m4 z?Du&7&&KaJmC~y}3bQ(Een)&-E4gPnl5fIshrp(JavJjJg;>4RJy)c?%RKlr19)jT zS)iIM8lIh?mmVxgb|tDXjbnds@4lk(1Rme|ZlLyBqaV3cX_r!okZp>cN3 zE^`uI8ps;K@AGqpfG`+Q+C`;slj&K!kN0sM29dEw9)9k90MXfq(gJ`);&aNh z4)!4Qp>GJBhOe1(nY^VD|9jxMsjbDCZRtLyw&gI-X2(R%1*^DTc{Fe=`j*K@S(Jpo zI*waV@r;@LF!6fv`oUz{C#ea%a_21$YVOUP2ZB^i4H{-Qog#HGPq4KNbMkp=JH~BR zLyYYu(5CSbrlZZRN6zQCAm-D=zNe{Xb##*q_Sb?rs!)5eK#FcSj*0G*Tmk;MYCQkxkVQ7;j zGUFhLSFd>;J3PrRVDLL^Ep`p@MSVila{+2I^{f*B@ioo(LM z$zs?Y8xC*t@XLTOk4fk>L%!)9kW4hRlq}{1-dh4^#MKP=Z!?0C{Xm0B?+?IlCzv-v zKRQL~Sq~G3ncd7OSW*NlXWBoV#F5!HXQsri^8_=IR;!hkj81S6&O>*QRqkfL3?(R+M&+bF5t=h6LNCu& z=GVK#X^yEo=`?-WS`#GAOc-i9CE$Z2D6-3|L7zOgfy>c`0i4{nr`+R_?&n*Lyo%<9 zG^rw*T;QI)x@2hN&)5P2&D@wFK!?6o_8bH+iU;suCa0MHn@zda{LtE> za9QW0e1_6g2eQ3`Y|&c>c)VW*q0NU`v&@9Y0p|?v5+$u1plWYGnK@~n2M+W2>4Wrb z3jYII23eDiyMPKtWml zC-;E_Qj79V9*054$7SvMQ4MJ{x4M@DK&axM6DeZ<=ech0r#_uf{=`B!#C{?ybF0fi zAhg@l;)|UOf?MP`aGcKfF^)xuqn3CBj6<`Wb~O?{zG7}*T+_{!(Ciqyca+M3Uo8Nj zJs*S`#ASU?B2>dHfu9ECxt%ju2d-r!n>{7`U~R1agGd=ulYK&)^>ZpIhD;_p1bk*C z&0)eT-s?HTFCOYZX`_Sdb4)_Dv{SRb3`x2CwD5{9?2h+4WPo3z@qx+uqvzEmS4!^g z;ocF+S}zZ(gf)Wpii)U?_(8ZEQjy?pcY1Y%9Hh zh!3L=$6ryA+j0DbCupemuSWw}k!O*=s#HF;o>8Bb5s()w&LGXw?70wU)nL$Sj;PYF zFim$uCMdCga+Zs){}9m}!kCv0s{2Q<$ZAHSJ#kk~`0nX7N@5#D#E(X2#79pA624A( zks&D1_vB#Y#wfh+$?pA6xU)X|`>g-_Y(%Pf$^gjbr#wTkD>s3E7DF|%b6k5a8FDPp zWVaV-&BeaJau|7&eqIhtQJkrN_bK-mq2Y_!NO|->4GBahH2+d2LH^x}&#D4{K#o{6 zYiaAG)(d$0hSijcW%Sx(m8{6Gx?w7bNV^;{*yUQ1g!;INYJMLtk~vREvvKcQ`eWW+ zT?gbOvCZa_KHNo8*l!dDb(0>o;4tXc{kCQ&$!G&_A>oOj0=t<-!t^?zV1FA6oe6ksOpTmcD?C zfDSkY>$3~GxTzHHUH+CE{l%%-J%C)-(zcCNCdi>diF?)d-nPZ3ekmKLxrG_XT%%J6 zxDwmLEH8~pLi&jFJ4Ep0xp87|uRzll77yOGq1Y?726)u+#3&XnmBM5g&mF{`vX5u=;4v8F7Qv5KH^;C8bbqSwWwY*^vptFtj^7}m5 z(#n|^E>V3zfVotmJW~E8@c>A$_VIP^I_&c0xqU{aZ_c=?!O zys|B-YKjM78{KD~CV5zY^w4_>{_podNNaJYSz|^3qrH@>ZNcdPGx>&Zd4bRHQWfUz z2Wb%gM9fEyhW@zgoDtoHyYCX>a4jwt7M@pCoud`2Y2=J~zJOzweely1sjqx=tnDb$ z!o$xaWR&T`f#mfOZnIO3U+F*uptOFk+@17$r*Mxe&a;c0a-2Do>g>9uIcePFr+_Eq ziO28qj3hk7bzm(}9pf_7XC_SUqcQ?!?a^csQyR@BV_5_Ns-ZCnz64_i=Ie0A3CO4o z$3aILN`{G{kRlMpFtUa{Wj5!5B}V6IcVfI#LyKg#2IaLPn518H+IpT3FAb7h!Qo9J zy4ftr>h@8l9&IL`$porftyvS0`BjbsQXgqVO>|r1&oQjp$MJ0D>?j|k#7Y1oO}DKb zwJCumgF@1hma7I1rx3qZu{Qq9l~vjhCH8}PTZm31zg(+I>e$4Nf%R_k=|nlCgHlQ^bgYaFnb?o z1BD8X>eG66SV=D37Zw0?tSgVdqECI&?ntpVZuX|@8jy8at`gt48oI+***oG_YP~MI6vE4V_;7-aEaNA;wDRY zlBGWq`MILrN`7}z$T3mz&{=#d8@rO@V>2T&=yN*{kmgpXy|#M(nfc>Rg5sD<1;X-b22@g0i ziGOmVGR>GZ7(F6B9bNLow={FTReifb?8mAPVrN%=K}oD#lKjVvwoWG!tI6ynk9yFUO5YZ(iR#w(`G8Z0FS^8v zooXk4GW^)W;(48}MU&nMla!1Igl&;k*Rv!zsJYyiCZ1(>#YW%3F!U*e83O1od5C2i zJl?M(Q>|apI4hBPxN5xzmFu=_3&i1sDY)f6)clZ5)3KwjwgeWo{`Ous2lreo`p#p) z_NrtP)*Dy&oG2uB$EdH;oF%<<66BtkG#@U1hchd`4 zK-oNlT;@`7vsZE&bTG==6td9_Gc<+Tbk!J=ELG&UkIV}WBsQ}Hhvl?!s(16d;)aK; zQC?Hj17^#8RBt<^{+acE$_iwph6}200knz*K;0x`kwjTKk7vO|LG6?>DzxW-bJFI=gZM28uw^5_d9DqjyrFckJg?|p0< zR!F6=@y>GWNq&rE;7&lN-1baBo=W^YmNm-Ab?Q|h)D*beeLEXr`{-29<^s6Hgv(Hhp)CkiDTFy`CR9B) zOIfoCRo%kCuTeFz)k9pV(KCTtyXxV)(B7nUhhrFh`I89(4{NmrnVTjna(a_15#k)` zA$}zoY}08IrLBVb9tSmv55V<;nr}h$%YOK$8(SKky9HZE4hEM=0tr=<5i2JNm$7Hl z?yYX6xZ;4ryXVqnqz54u=)=vsO<|^Tp|63C~|3@mD;sa1e#IhjTHE&{WOh&K*_Ly)LTtrA^53 z_!jM&XR5O07PAmyXQZjVU!W7mkm+f=Gd$F1^KOR3{K({EB==emV+cu`!YgeA@s|l< zE_K!orMBhg3I;A6b|4j>b*lKR+ENN^I=z&PejRy~751i!i*C_z&Aqkg^-4P~{p*Sd+F$BhHtHK-_+pz)N@P zl;_=~*XI*K@@FHVZMs)$ZbX`I*Uo=^Ws10+^Tbte$cP?&$o2V#`2hsV=;8WTA++E8 z65QJ!Dw|mo5rb58N3%jxU`o5%WBi>92ywzsaz*u9ye1SwrNWnMGPB z3_6R>AY*t&Ud=V!Ddy!Dt}H^;T;CX=@`Ovohmh%MmSkMIl|_O8kYKr%z}#D#3tIYA zg(bIt{*v=ovy#_Ye=;!d|FxY(pMLpUp!LNC;xD{e>FS?k=0$F%!9xtZ+ojcl>6Xy7 z7ZEQ7_p=?$9*Yu{x89Ab{Ipj1NYwPlOO)V+u3^o{8bYSD}Rl^$B~gj3!@MHO^tiII-7afDD8{? znMMNh@7XBLEA~d_(sv<9o80+8w1VrD=^)S$`n%;mhxgP&>3hFf)XS~e8m+sKBWnOT z?q$NQ^ynt<>7Q@z6B3pl+vEILvZQVl#IKnHK5fY^cl2BGry9UFj2->J!m(o+1U)R0 z+8s3>?)K&g@46-i|G|tM_1BlsV&^YhK{6nzGbhNr3rj)AZkLLi3jOjGlk+b>I~RO? zQNc%>P2V7MBu1At1t6_;kQdx`INU?6rY{>rZ|N1`HeEjaJ_n?p0sfJxBB0``f*~fctDCFft z2C=Ds(^#Ya>-+jbJ@vZLS2kWrBa}GU>D^(Qg-|T_b83Kr(Hm{|xwgZ?!cKMn?Uo8! zkq&kll|EB4!;;by2|SV}R^QQ(m`I?K^dbDP)52PItZWn3?y+6+%{y>|O&KMt973L# z{8C6QI^@vF$8ck_4d(1SzSVwSCsdaoD|cdzBI`)LAH^@|8IJT_YV=Fk-H4yC_I`CC zDNTe^PpOSHS*PAtV+<6>T|e8?0RcifaM7;k6a-$58m&<|g3c(RCRzwF{e0VHl(I;r zJY4#0Atb69=|ihy;=c&&Q>EV)+miZvq#-o@iDugLC!|1>H?^IcgO5*(YK*e+n>peE ziNbA1){D%@v^fLTsLSFx3FL{@)@Tf!y}IjWq%~s(o0Dsexjr zKk`7BP0W~Pa;rS9EZz^NQWlde4h6%qfCi?6e3Vo9Uf@w<9LDn}QSMA;7k%4aKVMKY z*PF>gp_uI)$+S~VUDKf0*H+VROK)QQgb?baeNKl7_-9gDWRUnO{L1se%fYwD#1lJr zu}BFCmgfdC;@OlRorRL=k{-e1CHB!L?OfjJu_?T&Y|lljsrepOjE7!`c=ouxsjXpd zlL9WnRPv?#FY}eZi$Y%KF_T_tTm}^Bhb+h~Gwa^gHNHt(D+h^^R%`iZS)1a7h6FF4 zN#I2Azmo^#k_^Cm@y8Fi=+QHD=$SEO*-rZy6QZ~*)}Wri!Q`IMOhe&Izxg2Fm(@0J z6|;7jPw`TX8nfDwEIYeoLe8wX- zXYw+iNV8YNxttd&^%$SP)CpL}l@AVGw$JaLy)KM6pEa#m%;o*Y3Y+uilJ|%dWkINM zbWt~(7{P8Bhah7%o6VWo&1L0J(>!b&qkpP7@+{4?m#&`hBA|~q8KZ{)hJ(-i7z>l< zLy=W6%UV{}h&P$hFeCT)cX|u&cs2vt+pj|;w;dr0v!O+zxuAo7@_Jbcx+L&GG8n3w zchoJdlkO?t1evrcwh#@NQlOgh4ZR);Ol(3(3XH6?;VdDp%hfP30^#T&Xm=Te=om2J z%N#LB&;^W;{>VH|yMv?C2Z+gDUNh1~pc+~SdnX_(A&Tfh7vHrcuOu+@n2~*ZaE&m6 zzjPUpdXnSsm&pZ@gb&T73vZ0a`ltcT zKjpf~NuHQmbB~(5te%o6?-eN{QjToV^@l)%|nGywzj|f+3M``g>Y?PHvS9V z^&5iUo$=0z9XyW`CLis6#r&i$89i2S9(78Hlp(1S=ud2HHnYTE3F1&wD+gHych3WU z3$Af}m=#SJKk)jP4C(V8zNz_yvlh?su?xlZS}>67gx*!6XZ7Z|qnUVE-+36eEhwsi zfIjQBA+Yh4>$vP9h `+k7h6)BZaG;1`AEv>w6Wdo`8DZ(Pi2a{Z<9{Y88Z+V@m zfHcb_Cp7ULwj#R+K_NYjoC?AqRVK3~c*sI*{4j#A5p^_?wXx zi*7kb$Iy^2&VbMxmnWstPo;}uC+-sxo2`W%I_w~#R*Gq80|f<936PSuf{fmLQOc~oY6)MJU=Xtw%#J{*^1w^b;u*Q(zsHlUEQ|S zwCry7sdH{AQ^~*(Yyij>Co@(&F|;dBl>q(l@}c(H?r_3#4(|d!_&}APyQCf9%RAwb z_dx6)h9`63Pqi1cxR|1aY5JckUe>%yoSJ5N9+s92(*p2M>8i$;n zJFy@bw@x72&)5)hdOYY5Ae@FmB{t{4u6{OHLU@P;hiZCimVwZ z(t+w_#^)gOfkO2^#Y1*bEx1cUO%$xzo?Xpf9g2QMfJeUqAXK>2v-$N^$rJ#?!7ufG z*hLrw%}62Rn~HU_3{pIbE4!~?S`=BHp?4A2fd*jADIN?*b)nDlH_`innO?87sr-bs zUsWO8aZ;G3G^N4bn&8Mgchi2iu=C?KJBmHJGn9ugkpZnLg#%m<{M@*XwOGv&j~+L( z{WVrt%Ax&$EM7CLnxx4h6K1wy@M{zfM1+p zjeegVx}gcy54ox%g7Ycc{Jyvccr%~{Rh?7KA2TH|d^j7)#FiJP%|}JF;kLKz(aYLl z;PDS>2cU-&F}?yWB?twY{XYR05$Nv4hon5zMc=eA$si-byL6dsrH?Nvj#Lt)JqiRO zdF2*jMGeTd=>P=xB772qco>)oitZ$viv5d0?#Z^>a$fom1qBfhQ4tfl6O{+6w%q%u zHA#~iG(wf&zkwn<64?h?E}elo*q3m|$XrA1b0Me+mq!6bc0fr@oP&#J}{d26PGq z1PQl7VnD`@!Ag{)#3sd1P(!&Ch`uY`R|t~Kk#PkaP7}&GlLV-wDZbmT)WF295Jd$_ zLue+5pt&Z*in}7GK78qc=+A;gLNB(Y2&8C)oP`%l6-DRcWxQ0I4ex&4!ObN+08Aw>j_$-0utz?-e?4}jEpin3j3Izg#!XXh- zCD=tbRr?ZxDBKreaM>VW)ev1(PcrO^sqU(Y99HqmB*%TX-s+Q@?W$xOT0g%BWsJJV#?7J#2HTP2k-YZ>Q zNzl6&KILDHFf*V~C=?k`K%h`46bc0dkP|4%EtUqTNGG=YFFluK@1XhKm7jC4Xv_8b zWW<=vC$5rQU|A3&HeNN!Nf&;2?f7UQ-E{`0w@r@IH}#c1eU4qkt11q@TL%1R8ExZ^ZVHIJ-r0h$y)s2g4i&5Q^}j|p3xZ8ouCW}H z%QGAh$yJ#|&?$(fJ{0QZLAkfJbQ_%8YFyH6`F5k36=VPd3``PG5gbt$fsqf7GU=e# zrRUw`!ns=J&7B)jGaL!%_ZHYR1lW;XVOiO?T5yp^e7a~WG`-sIr@awEyE!N&C_`bm z%3qRX5@Y};%_2X@sHr!Kk*co&U9r@azDxdhhax>2Ap8F(Th z!bo+t71G;$(V;y0zFG|L*X>hfxZSKU?nN=+few5*vP4wcb@^o+n{$RJirJv%JhL9U z3taVdN!Ob+d!KKfR^Xcj=>pgzZ<~hR1Bwc8`g(KA?@&$9H%H`z*mItqmspC|YlU(a zk;bzX*qb&}hU-o$7A7U-^3YjpE7XfcjO4~q87{iaW*ju!0N-$1LnQe_ET}jOmCEO* zuKJnycL#4bMj*gvh~hPqPCoeugm`VD3Lg`%=IShHE+D(HFIaw;(2pwzg-d6hx9jTXBJS zBy8q8F9eU~>Y%&O*F>ELNI2}}n;qOGMY)S%A%evf0eD`_#e72Gk!L{{(#ula^mTto z&!Ys8pCeTSb0J38apt`{{L~ef*w^8(Y4*n8)6-7l2eUk_AcJV2z2Yjl^5;PY^{Dkl zjy2NO#HX&3#^VAUd4?>D&4lqqHU90L$Da#4N2{uXsL;=9f^Oi=^_Rh>pC4AcESOma z$u=rnM2;C{yKk60QlN*Kt#aTzr{go={6nl zJsVtw%b2)ejT??50*FoV8|mAATX^!IpQ|ofn$>878Hb#mKI1XC%O*2ywBv1%sTK&~ zi(Ey=U{fjzdyU^;MAOk*Gja{V``l=7cas4o2u0wj$_9$`+6L*JXcA3qftAa6_;v->_D51Km z@A8RIak`4>Te@cwcb4#K`l9AK3#21j8M-arMwmWnEf4xEN;zj5#mOVS`5@@a+ zrP((YTL3i};)E>%Di8))cl@D2Tc@>6>HGW(Y?(Y(7?X5_{Dl!7(b&8mU;0*qIt2oPqDW1Lb#`+xj$;yA z2Hb7648WUJ*-%YI2uT;?T1K}`xUY!&{nuQE9m`8f_Vv^X@!IiaVNim2nYEnsv9eGBu5z z*T9@8uc5grIldB=t&~~cnhR>9u>SxtRoF9&Gxrk9Fj(IaJw5@P6)Z_ge`i|?$&h;ps7PT=#P*6p0AjtcHR28B@9ZTz*U zkrLl#mdx;KQ&Jq^N^(n5gqz@XsVJ#)M|>rMj?|kDs<5R3fk2>8C^g7yio=a>wqm2d z!*rmC;!A!yMJ83X=|gvMQP@{~$DaQ0g;WwF-LDaCjH#%D+7S~w}|(X1|)sn(3d5v zk%+@_##~+#fbsyKW}}LbE-P;ZEh`~FpipH& z69&Gs`Yi(nzO(u*0|vgc`Yi(nzO($RK*6u9{)<4tudM!yK_QexZ4nnF=g+&}L0bp( zZ`CrpCkNGe$nBeb9(k8&&wsz3bR4~}{f}bTtNRZ82e?~h#O(ghi?&^SFQWUOB?m

    ) } -SelectCollaborators.propTypes = { - loading: PropTypes.bool.isRequired, - options: PropTypes.array.isRequired, - placeholder: PropTypes.string, - multipleSelectionProps: PropTypes.shape({ - getSelectedItemProps: PropTypes.func.isRequired, - getDropdownProps: PropTypes.func.isRequired, - addSelectedItem: PropTypes.func.isRequired, - removeSelectedItem: PropTypes.func.isRequired, - selectedItems: PropTypes.array.isRequired, - }).isRequired, -} -function Option({ selected, item, getItemProps, index }) { +function Option({ + selected, + item, + getItemProps, + index, +}: { + selected: boolean + item: Contact + getItemProps: (any: any) => any + index: number +}) { return (
  • void + selectedItem: ContactItem + focusInput: () => void + getSelectedItemProps: (any: any) => any + index: number }) { const handleClick = useCallback( - event => { + (event: React.MouseEvent) => { event.preventDefault() event.stopPropagation() removeSelectedItem(selectedItem) @@ -344,13 +352,3 @@ function SelectedItem({ ) } - -SelectedItem.propTypes = { - focusInput: PropTypes.func.isRequired, - removeSelectedItem: PropTypes.func.isRequired, - selectedItem: PropTypes.shape({ - display: PropTypes.string.isRequired, - }), - getSelectedItemProps: PropTypes.func.isRequired, - index: PropTypes.number.isRequired, -} diff --git a/services/web/frontend/js/features/share-project-modal/components/send-invites.jsx b/services/web/frontend/js/features/share-project-modal/components/send-invites.tsx similarity index 80% rename from services/web/frontend/js/features/share-project-modal/components/send-invites.jsx rename to services/web/frontend/js/features/share-project-modal/components/send-invites.tsx index f27d42f404..da704d039f 100644 --- a/services/web/frontend/js/features/share-project-modal/components/send-invites.jsx +++ b/services/web/frontend/js/features/share-project-modal/components/send-invites.tsx @@ -2,7 +2,6 @@ import AddCollaborators from './add-collaborators' import AddCollaboratorsUpgrade from './add-collaborators-upgrade' import CollaboratorsLimitUpgrade from './collaborators-limit-upgrade' import AccessLevelsChanged from './access-levels-changed' -import PropTypes from 'prop-types' import OLRow from '@/features/ui/components/ol/ol-row' export default function SendInvites({ @@ -10,6 +9,11 @@ export default function SendInvites({ hasExceededCollaboratorLimit, haveAnyEditorsBeenDowngraded, somePendingEditorsResolved, +}: { + canAddCollaborators: boolean + hasExceededCollaboratorLimit: boolean + haveAnyEditorsBeenDowngraded: boolean + somePendingEditorsResolved: boolean }) { return ( @@ -30,10 +34,3 @@ export default function SendInvites({ ) } - -SendInvites.propTypes = { - canAddCollaborators: PropTypes.bool, - hasExceededCollaboratorLimit: PropTypes.bool, - haveAnyEditorsBeenDowngraded: PropTypes.bool, - somePendingEditorsResolved: PropTypes.bool, -} diff --git a/services/web/frontend/js/features/share-project-modal/components/transfer-ownership-modal.jsx b/services/web/frontend/js/features/share-project-modal/components/transfer-ownership-modal.tsx similarity index 91% rename from services/web/frontend/js/features/share-project-modal/components/transfer-ownership-modal.jsx rename to services/web/frontend/js/features/share-project-modal/components/transfer-ownership-modal.tsx index fec3e941ea..02100d2a2a 100644 --- a/services/web/frontend/js/features/share-project-modal/components/transfer-ownership-modal.jsx +++ b/services/web/frontend/js/features/share-project-modal/components/transfer-ownership-modal.tsx @@ -1,6 +1,5 @@ import { useState } from 'react' import { Trans, useTranslation } from 'react-i18next' -import PropTypes from 'prop-types' import { transferProjectOwnership } from '../utils/api' import { useProjectContext } from '@/shared/context/project-context' import { useLocation } from '@/shared/hooks/use-location' @@ -13,8 +12,15 @@ import OLModal, { import OLNotification from '@/features/ui/components/ol/ol-notification' import OLButton from '@/features/ui/components/ol/ol-button' import { Spinner } from 'react-bootstrap' +import { ProjectContextMember } from '@/shared/context/types/project-context' -export default function TransferOwnershipModal({ member, cancel }) { +export default function TransferOwnershipModal({ + member, + cancel, +}: { + member: ProjectContextMember + cancel: () => void +}) { const { t } = useTranslation() const [inflight, setInflight] = useState(false) @@ -82,7 +88,3 @@ export default function TransferOwnershipModal({ member, cancel }) { ) } -TransferOwnershipModal.propTypes = { - member: PropTypes.object.isRequired, - cancel: PropTypes.func.isRequired, -} diff --git a/services/web/frontend/js/features/share-project-modal/components/view-member.jsx b/services/web/frontend/js/features/share-project-modal/components/view-member.tsx similarity index 68% rename from services/web/frontend/js/features/share-project-modal/components/view-member.jsx rename to services/web/frontend/js/features/share-project-modal/components/view-member.tsx index 3faec91612..d4cf2e9333 100644 --- a/services/web/frontend/js/features/share-project-modal/components/view-member.jsx +++ b/services/web/frontend/js/features/share-project-modal/components/view-member.tsx @@ -1,10 +1,14 @@ -import PropTypes from 'prop-types' import MemberPrivileges from './member-privileges' import OLRow from '@/features/ui/components/ol/ol-row' import OLCol from '@/features/ui/components/ol/ol-col' import MaterialIcon from '@/shared/components/material-icon' +import { ProjectContextMember } from '@/shared/context/types/project-context' -export default function ViewMember({ member }) { +export default function ViewMember({ + member, +}: { + member: ProjectContextMember +}) { return ( @@ -19,11 +23,3 @@ export default function ViewMember({ member }) { ) } - -ViewMember.propTypes = { - member: PropTypes.shape({ - _id: PropTypes.string.isRequired, - email: PropTypes.string.isRequired, - privileges: PropTypes.string.isRequired, - }).isRequired, -} diff --git a/services/web/frontend/js/features/share-project-modal/hooks/use-user-contacts.js b/services/web/frontend/js/features/share-project-modal/hooks/use-user-contacts.ts similarity index 85% rename from services/web/frontend/js/features/share-project-modal/hooks/use-user-contacts.js rename to services/web/frontend/js/features/share-project-modal/hooks/use-user-contacts.ts index f23af1fbd3..35f9f9db9f 100644 --- a/services/web/frontend/js/features/share-project-modal/hooks/use-user-contacts.js +++ b/services/web/frontend/js/features/share-project-modal/hooks/use-user-contacts.ts @@ -1,10 +1,11 @@ import { useEffect, useState } from 'react' import { getJSON } from '../../../infrastructure/fetch-json' import useAbortController from '../../../shared/hooks/use-abort-controller' +import { Contact } from '../utils/types' export function useUserContacts() { const [loading, setLoading] = useState(true) - const [data, setData] = useState(null) + const [data, setData] = useState(null) const [error, setError] = useState(false) const { signal } = useAbortController() @@ -21,7 +22,7 @@ export function useUserContacts() { return { loading, data, error } } -function buildContact(contact) { +function buildContact(contact: Omit): Contact { const [emailPrefix] = contact.email.split('@') // the name is not just the default "email prefix as first name" diff --git a/services/web/frontend/js/features/share-project-modal/utils/types.ts b/services/web/frontend/js/features/share-project-modal/utils/types.ts new file mode 100644 index 0000000000..9636f14fb5 --- /dev/null +++ b/services/web/frontend/js/features/share-project-modal/utils/types.ts @@ -0,0 +1,9 @@ +export type Contact = { + id: string + display: string + email: string + first_name: string + last_name: string + name: string + type: 'user' +} From 2dd054d60231ec21771a93d5fe8cab305c8486b9 Mon Sep 17 00:00:00 2001 From: Eric Mc Sween <5454374+emcsween@users.noreply.github.com> Date: Thu, 15 May 2025 07:10:30 -0400 Subject: [PATCH 120/194] Merge pull request #25606 from overleaf/em-o-error-types OError: handle unknown error types GitOrigin-RevId: a52dd92a4f95cd738556612599805f41f24de69f --- libraries/o-error/index.cjs | 37 +++++++++++++++------ libraries/o-error/test/o-error-util.test.js | 5 +++ libraries/o-error/test/o-error.test.js | 8 +++++ 3 files changed, 40 insertions(+), 10 deletions(-) diff --git a/libraries/o-error/index.cjs b/libraries/o-error/index.cjs index ef08b45f16..0bb7da653a 100644 --- a/libraries/o-error/index.cjs +++ b/libraries/o-error/index.cjs @@ -1,20 +1,34 @@ +// @ts-check + /** * Light-weight helpers for handling JavaScript Errors in node.js and the * browser. */ class OError extends Error { + /** + * The error that is the underlying cause of this error + * + * @type {unknown} + */ + cause + + /** + * List of errors encountered as the callback chain is unwound + * + * @type {TaggedError[] | undefined} + */ + _oErrorTags + /** * @param {string} message as for built-in Error * @param {Object} [info] extra data to attach to the error - * @param {Error} [cause] the internal error that caused this error + * @param {unknown} [cause] the internal error that caused this error */ constructor(message, info, cause) { super(message) this.name = this.constructor.name if (info) this.info = info if (cause) this.cause = cause - /** @private @type {Array | undefined} */ - this._oErrorTags // eslint-disable-line } /** @@ -31,7 +45,7 @@ class OError extends Error { /** * Wrap the given error, which caused this error. * - * @param {Error} cause the internal error that caused this error + * @param {unknown} cause the internal error that caused this error * @return {this} */ withCause(cause) { @@ -65,13 +79,16 @@ class OError extends Error { * } * } * - * @param {Error} error the error to tag + * @template {unknown} E + * @param {E} error the error to tag * @param {string} [message] message with which to tag `error` * @param {Object} [info] extra data with wich to tag `error` - * @return {Error} the modified `error` argument + * @return {E} the modified `error` argument */ static tag(error, message, info) { - const oError = /** @type{OError} */ (error) + const oError = /** @type {{ _oErrorTags: TaggedError[] | undefined }} */ ( + error + ) if (!oError._oErrorTags) oError._oErrorTags = [] @@ -102,7 +119,7 @@ class OError extends Error { * * If an info property is repeated, the last one wins. * - * @param {Error | null | undefined} error any error (may or may not be an `OError`) + * @param {unknown} error any error (may or may not be an `OError`) * @return {Object} */ static getFullInfo(error) { @@ -129,7 +146,7 @@ class OError extends Error { * Return the `stack` property from `error`, including the `stack`s for any * tagged errors added with `OError.tag` and for any `cause`s. * - * @param {Error | null | undefined} error any error (may or may not be an `OError`) + * @param {unknown} error any error (may or may not be an `OError`) * @return {string} */ static getFullStack(error) { @@ -143,7 +160,7 @@ class OError extends Error { stack += `\n${oError._oErrorTags.map(tag => tag.stack).join('\n')}` } - const causeStack = oError.cause && OError.getFullStack(oError.cause) + const causeStack = OError.getFullStack(oError.cause) if (causeStack) { stack += '\ncaused by:\n' + indent(causeStack) } diff --git a/libraries/o-error/test/o-error-util.test.js b/libraries/o-error/test/o-error-util.test.js index c86cec44a2..b29dd4900a 100644 --- a/libraries/o-error/test/o-error-util.test.js +++ b/libraries/o-error/test/o-error-util.test.js @@ -268,6 +268,11 @@ describe('utils', function () { expect(OError.getFullInfo(null)).to.deep.equal({}) }) + it('works when given a string', function () { + const err = 'not an error instance' + expect(OError.getFullInfo(err)).to.deep.equal({}) + }) + it('works on a normal error', function () { const err = new Error('foo') expect(OError.getFullInfo(err)).to.deep.equal({}) diff --git a/libraries/o-error/test/o-error.test.js b/libraries/o-error/test/o-error.test.js index 84244ec92e..db87f56992 100644 --- a/libraries/o-error/test/o-error.test.js +++ b/libraries/o-error/test/o-error.test.js @@ -35,6 +35,14 @@ describe('OError', function () { expect(err2.cause.message).to.equal('cause 2') }) + it('accepts non-Error causes', function () { + const err1 = new OError('foo', {}, 'not-an-error') + expect(err1.cause).to.equal('not-an-error') + + const err2 = new OError('foo').withCause('not-an-error') + expect(err2.cause).to.equal('not-an-error') + }) + it('handles a custom error type with a cause', function () { function doSomethingBadInternally() { throw new Error('internal error') From 67ab5a749ab218edf1bcf0359cb4eaf94f1c5602 Mon Sep 17 00:00:00 2001 From: Antoine Clausse Date: Thu, 15 May 2025 13:20:16 +0200 Subject: [PATCH 121/194] Update Node to `22.15.0` (#24699) * Run `bin/update_node 20.18.2 22.15.0` * Remove expects on `fetchMock.callHistory.done()` to fix tests: are they necessary? * Set node version to `22.x` in linked-url-proxy * Increase test timeout to 30s in `github-sync`, Add waiting steps * Define `navigator.onLine` in tests setup GitOrigin-RevId: 75eb556e9f51b665e57497a0879b6915d14069ce --- libraries/access-token-encryptor/.nvmrc | 2 +- libraries/access-token-encryptor/buildscript.txt | 2 +- libraries/fetch-utils/.nvmrc | 2 +- libraries/fetch-utils/buildscript.txt | 2 +- libraries/logger/.nvmrc | 2 +- libraries/logger/buildscript.txt | 2 +- libraries/metrics/.nvmrc | 2 +- libraries/metrics/buildscript.txt | 2 +- libraries/mongo-utils/.nvmrc | 2 +- libraries/mongo-utils/buildscript.txt | 2 +- libraries/o-error/.nvmrc | 2 +- libraries/o-error/buildscript.txt | 2 +- libraries/object-persistor/.nvmrc | 2 +- libraries/object-persistor/buildscript.txt | 2 +- libraries/overleaf-editor-core/.nvmrc | 2 +- libraries/overleaf-editor-core/buildscript.txt | 2 +- libraries/promise-utils/.nvmrc | 2 +- libraries/promise-utils/buildscript.txt | 2 +- libraries/ranges-tracker/.nvmrc | 2 +- libraries/ranges-tracker/buildscript.txt | 2 +- libraries/redis-wrapper/.nvmrc | 2 +- libraries/redis-wrapper/buildscript.txt | 2 +- libraries/settings/.nvmrc | 2 +- libraries/settings/buildscript.txt | 2 +- libraries/stream-utils/.nvmrc | 2 +- libraries/stream-utils/buildscript.txt | 2 +- server-ce/test/Dockerfile | 2 +- services/chat/.nvmrc | 2 +- services/chat/Dockerfile | 2 +- services/chat/Makefile | 4 ++-- services/chat/buildscript.txt | 2 +- services/chat/docker-compose.yml | 4 ++-- services/clsi/.nvmrc | 2 +- services/clsi/Dockerfile | 2 +- services/clsi/Makefile | 4 ++-- services/clsi/buildscript.txt | 2 +- services/contacts/.nvmrc | 2 +- services/contacts/Dockerfile | 2 +- services/contacts/Makefile | 4 ++-- services/contacts/buildscript.txt | 2 +- services/contacts/docker-compose.yml | 4 ++-- services/docstore/.nvmrc | 2 +- services/docstore/Dockerfile | 2 +- services/docstore/Makefile | 4 ++-- services/docstore/buildscript.txt | 2 +- services/docstore/docker-compose.yml | 4 ++-- services/document-updater/.nvmrc | 2 +- services/document-updater/Dockerfile | 2 +- services/document-updater/Makefile | 4 ++-- services/document-updater/buildscript.txt | 2 +- services/document-updater/docker-compose.yml | 4 ++-- services/filestore/.nvmrc | 2 +- services/filestore/Dockerfile | 2 +- services/filestore/Makefile | 4 ++-- services/filestore/buildscript.txt | 2 +- services/filestore/docker-compose.ci.yml | 2 +- services/filestore/docker-compose.yml | 2 +- services/history-v1/.nvmrc | 2 +- services/history-v1/Dockerfile | 2 +- services/history-v1/Makefile | 4 ++-- services/history-v1/buildscript.txt | 2 +- services/history-v1/docker-compose.ci.yml | 2 +- services/history-v1/docker-compose.yml | 2 +- services/notifications/.nvmrc | 2 +- services/notifications/Dockerfile | 2 +- services/notifications/Makefile | 4 ++-- services/notifications/buildscript.txt | 2 +- services/notifications/docker-compose.yml | 4 ++-- services/project-history/.nvmrc | 2 +- services/project-history/Dockerfile | 2 +- services/project-history/Makefile | 4 ++-- services/project-history/buildscript.txt | 2 +- services/project-history/docker-compose.yml | 4 ++-- services/real-time/.nvmrc | 2 +- services/real-time/Dockerfile | 2 +- services/real-time/Makefile | 4 ++-- services/real-time/buildscript.txt | 2 +- services/real-time/docker-compose.yml | 4 ++-- services/web/.nvmrc | 2 +- services/web/Dockerfile | 2 +- services/web/Dockerfile.frontend | 2 +- services/web/cloudbuild-storybook.yaml | 6 +++--- services/web/docker-compose.common.env | 2 +- services/web/docker-compose.yml | 4 ++-- services/web/scripts/translations/Dockerfile | 2 +- services/web/test/frontend/bootstrap.js | 5 +++++ .../project-list/components/project-list-root.test.tsx | 9 --------- 87 files changed, 110 insertions(+), 114 deletions(-) diff --git a/libraries/access-token-encryptor/.nvmrc b/libraries/access-token-encryptor/.nvmrc index 0254b1e633..b8ffd70759 100644 --- a/libraries/access-token-encryptor/.nvmrc +++ b/libraries/access-token-encryptor/.nvmrc @@ -1 +1 @@ -20.18.2 +22.15.0 diff --git a/libraries/access-token-encryptor/buildscript.txt b/libraries/access-token-encryptor/buildscript.txt index 36fd724a80..6edd9f367b 100644 --- a/libraries/access-token-encryptor/buildscript.txt +++ b/libraries/access-token-encryptor/buildscript.txt @@ -5,6 +5,6 @@ access-token-encryptor --env-pass-through= --esmock-loader=False --is-library=True ---node-version=20.18.2 +--node-version=22.15.0 --public-repo=False --script-version=4.7.0 diff --git a/libraries/fetch-utils/.nvmrc b/libraries/fetch-utils/.nvmrc index 0254b1e633..b8ffd70759 100644 --- a/libraries/fetch-utils/.nvmrc +++ b/libraries/fetch-utils/.nvmrc @@ -1 +1 @@ -20.18.2 +22.15.0 diff --git a/libraries/fetch-utils/buildscript.txt b/libraries/fetch-utils/buildscript.txt index a158079aee..8b214755c6 100644 --- a/libraries/fetch-utils/buildscript.txt +++ b/libraries/fetch-utils/buildscript.txt @@ -5,6 +5,6 @@ fetch-utils --env-pass-through= --esmock-loader=False --is-library=True ---node-version=20.18.2 +--node-version=22.15.0 --public-repo=False --script-version=4.7.0 diff --git a/libraries/logger/.nvmrc b/libraries/logger/.nvmrc index 0254b1e633..b8ffd70759 100644 --- a/libraries/logger/.nvmrc +++ b/libraries/logger/.nvmrc @@ -1 +1 @@ -20.18.2 +22.15.0 diff --git a/libraries/logger/buildscript.txt b/libraries/logger/buildscript.txt index afe93c28dc..7f3bba47dd 100644 --- a/libraries/logger/buildscript.txt +++ b/libraries/logger/buildscript.txt @@ -5,6 +5,6 @@ logger --env-pass-through= --esmock-loader=False --is-library=True ---node-version=20.18.2 +--node-version=22.15.0 --public-repo=False --script-version=4.7.0 diff --git a/libraries/metrics/.nvmrc b/libraries/metrics/.nvmrc index 0254b1e633..b8ffd70759 100644 --- a/libraries/metrics/.nvmrc +++ b/libraries/metrics/.nvmrc @@ -1 +1 @@ -20.18.2 +22.15.0 diff --git a/libraries/metrics/buildscript.txt b/libraries/metrics/buildscript.txt index 74fcbdb6c6..7b952a3920 100644 --- a/libraries/metrics/buildscript.txt +++ b/libraries/metrics/buildscript.txt @@ -5,6 +5,6 @@ metrics --env-pass-through= --esmock-loader=False --is-library=True ---node-version=20.18.2 +--node-version=22.15.0 --public-repo=False --script-version=4.7.0 diff --git a/libraries/mongo-utils/.nvmrc b/libraries/mongo-utils/.nvmrc index 0254b1e633..b8ffd70759 100644 --- a/libraries/mongo-utils/.nvmrc +++ b/libraries/mongo-utils/.nvmrc @@ -1 +1 @@ -20.18.2 +22.15.0 diff --git a/libraries/mongo-utils/buildscript.txt b/libraries/mongo-utils/buildscript.txt index a4e4fe7802..81fe685256 100644 --- a/libraries/mongo-utils/buildscript.txt +++ b/libraries/mongo-utils/buildscript.txt @@ -5,6 +5,6 @@ mongo-utils --env-pass-through= --esmock-loader=False --is-library=True ---node-version=20.18.2 +--node-version=22.15.0 --public-repo=False --script-version=4.7.0 diff --git a/libraries/o-error/.nvmrc b/libraries/o-error/.nvmrc index 0254b1e633..b8ffd70759 100644 --- a/libraries/o-error/.nvmrc +++ b/libraries/o-error/.nvmrc @@ -1 +1 @@ -20.18.2 +22.15.0 diff --git a/libraries/o-error/buildscript.txt b/libraries/o-error/buildscript.txt index 81d3eb3252..79b5d38c73 100644 --- a/libraries/o-error/buildscript.txt +++ b/libraries/o-error/buildscript.txt @@ -5,6 +5,6 @@ o-error --env-pass-through= --esmock-loader=False --is-library=True ---node-version=20.18.2 +--node-version=22.15.0 --public-repo=False --script-version=4.7.0 diff --git a/libraries/object-persistor/.nvmrc b/libraries/object-persistor/.nvmrc index 0254b1e633..b8ffd70759 100644 --- a/libraries/object-persistor/.nvmrc +++ b/libraries/object-persistor/.nvmrc @@ -1 +1 @@ -20.18.2 +22.15.0 diff --git a/libraries/object-persistor/buildscript.txt b/libraries/object-persistor/buildscript.txt index 9ca6929a03..2484951ddc 100644 --- a/libraries/object-persistor/buildscript.txt +++ b/libraries/object-persistor/buildscript.txt @@ -5,6 +5,6 @@ object-persistor --env-pass-through= --esmock-loader=False --is-library=True ---node-version=20.18.2 +--node-version=22.15.0 --public-repo=False --script-version=4.7.0 diff --git a/libraries/overleaf-editor-core/.nvmrc b/libraries/overleaf-editor-core/.nvmrc index 0254b1e633..b8ffd70759 100644 --- a/libraries/overleaf-editor-core/.nvmrc +++ b/libraries/overleaf-editor-core/.nvmrc @@ -1 +1 @@ -20.18.2 +22.15.0 diff --git a/libraries/overleaf-editor-core/buildscript.txt b/libraries/overleaf-editor-core/buildscript.txt index 03b7f06791..0011456758 100644 --- a/libraries/overleaf-editor-core/buildscript.txt +++ b/libraries/overleaf-editor-core/buildscript.txt @@ -5,6 +5,6 @@ overleaf-editor-core --env-pass-through= --esmock-loader=False --is-library=True ---node-version=20.18.2 +--node-version=22.15.0 --public-repo=False --script-version=4.7.0 diff --git a/libraries/promise-utils/.nvmrc b/libraries/promise-utils/.nvmrc index 0254b1e633..b8ffd70759 100644 --- a/libraries/promise-utils/.nvmrc +++ b/libraries/promise-utils/.nvmrc @@ -1 +1 @@ -20.18.2 +22.15.0 diff --git a/libraries/promise-utils/buildscript.txt b/libraries/promise-utils/buildscript.txt index 51a6dad532..a961c96d43 100644 --- a/libraries/promise-utils/buildscript.txt +++ b/libraries/promise-utils/buildscript.txt @@ -5,6 +5,6 @@ promise-utils --env-pass-through= --esmock-loader=False --is-library=True ---node-version=20.18.2 +--node-version=22.15.0 --public-repo=False --script-version=4.7.0 diff --git a/libraries/ranges-tracker/.nvmrc b/libraries/ranges-tracker/.nvmrc index 0254b1e633..b8ffd70759 100644 --- a/libraries/ranges-tracker/.nvmrc +++ b/libraries/ranges-tracker/.nvmrc @@ -1 +1 @@ -20.18.2 +22.15.0 diff --git a/libraries/ranges-tracker/buildscript.txt b/libraries/ranges-tracker/buildscript.txt index d112f852a7..d1803898ae 100644 --- a/libraries/ranges-tracker/buildscript.txt +++ b/libraries/ranges-tracker/buildscript.txt @@ -5,6 +5,6 @@ ranges-tracker --env-pass-through= --esmock-loader=False --is-library=True ---node-version=20.18.2 +--node-version=22.15.0 --public-repo=False --script-version=4.7.0 diff --git a/libraries/redis-wrapper/.nvmrc b/libraries/redis-wrapper/.nvmrc index 0254b1e633..b8ffd70759 100644 --- a/libraries/redis-wrapper/.nvmrc +++ b/libraries/redis-wrapper/.nvmrc @@ -1 +1 @@ -20.18.2 +22.15.0 diff --git a/libraries/redis-wrapper/buildscript.txt b/libraries/redis-wrapper/buildscript.txt index 89de51417a..c5c4f6abd9 100644 --- a/libraries/redis-wrapper/buildscript.txt +++ b/libraries/redis-wrapper/buildscript.txt @@ -5,6 +5,6 @@ redis-wrapper --env-pass-through= --esmock-loader=False --is-library=True ---node-version=20.18.2 +--node-version=22.15.0 --public-repo=False --script-version=4.7.0 diff --git a/libraries/settings/.nvmrc b/libraries/settings/.nvmrc index 0254b1e633..b8ffd70759 100644 --- a/libraries/settings/.nvmrc +++ b/libraries/settings/.nvmrc @@ -1 +1 @@ -20.18.2 +22.15.0 diff --git a/libraries/settings/buildscript.txt b/libraries/settings/buildscript.txt index ed79480d31..844f3dd13c 100644 --- a/libraries/settings/buildscript.txt +++ b/libraries/settings/buildscript.txt @@ -5,6 +5,6 @@ settings --env-pass-through= --esmock-loader=False --is-library=True ---node-version=20.18.2 +--node-version=22.15.0 --public-repo=False --script-version=4.7.0 diff --git a/libraries/stream-utils/.nvmrc b/libraries/stream-utils/.nvmrc index 0254b1e633..b8ffd70759 100644 --- a/libraries/stream-utils/.nvmrc +++ b/libraries/stream-utils/.nvmrc @@ -1 +1 @@ -20.18.2 +22.15.0 diff --git a/libraries/stream-utils/buildscript.txt b/libraries/stream-utils/buildscript.txt index ad7265549c..709adeae9a 100644 --- a/libraries/stream-utils/buildscript.txt +++ b/libraries/stream-utils/buildscript.txt @@ -5,6 +5,6 @@ stream-utils --env-pass-through= --esmock-loader=False --is-library=True ---node-version=20.18.2 +--node-version=22.15.0 --public-repo=False --script-version=4.7.0 diff --git a/server-ce/test/Dockerfile b/server-ce/test/Dockerfile index 721b99c06b..c8c2626a5b 100644 --- a/server-ce/test/Dockerfile +++ b/server-ce/test/Dockerfile @@ -1,4 +1,4 @@ -FROM node:20.18.2 +FROM node:22.15.0 RUN curl -fsSL https://download.docker.com/linux/debian/gpg | apt-key add - \ && echo \ "deb [arch=$(dpkg --print-architecture)] https://download.docker.com/linux/debian $(. /etc/os-release && echo "$VERSION_CODENAME") stable" \ diff --git a/services/chat/.nvmrc b/services/chat/.nvmrc index 0254b1e633..b8ffd70759 100644 --- a/services/chat/.nvmrc +++ b/services/chat/.nvmrc @@ -1 +1 @@ -20.18.2 +22.15.0 diff --git a/services/chat/Dockerfile b/services/chat/Dockerfile index 14056c2d29..9f9efaa915 100644 --- a/services/chat/Dockerfile +++ b/services/chat/Dockerfile @@ -2,7 +2,7 @@ # Instead run bin/update_build_scripts from # https://github.com/overleaf/internal/ -FROM node:20.18.2 AS base +FROM node:22.15.0 AS base WORKDIR /overleaf/services/chat diff --git a/services/chat/Makefile b/services/chat/Makefile index 94f0afb567..40cb2b6af3 100644 --- a/services/chat/Makefile +++ b/services/chat/Makefile @@ -32,12 +32,12 @@ HERE=$(shell pwd) MONOREPO=$(shell cd ../../ && pwd) # Run the linting commands in the scope of the monorepo. # Eslint and prettier (plus some configs) are on the root. -RUN_LINTING = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(HERE) node:20.18.2 npm run --silent +RUN_LINTING = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(HERE) node:22.15.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 # Same but from the top of the monorepo -RUN_LINTING_MONOREPO = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(MONOREPO) node:20.18.2 npm run --silent +RUN_LINTING_MONOREPO = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(MONOREPO) node:22.15.0 npm run --silent SHELLCHECK_OPTS = \ --shell=bash \ diff --git a/services/chat/buildscript.txt b/services/chat/buildscript.txt index 1dc88e9fa6..a90eb1a5e9 100644 --- a/services/chat/buildscript.txt +++ b/services/chat/buildscript.txt @@ -4,6 +4,6 @@ chat --env-add= --env-pass-through= --esmock-loader=False ---node-version=20.18.2 +--node-version=22.15.0 --public-repo=False --script-version=4.7.0 diff --git a/services/chat/docker-compose.yml b/services/chat/docker-compose.yml index b830d25453..40a6e561f6 100644 --- a/services/chat/docker-compose.yml +++ b/services/chat/docker-compose.yml @@ -6,7 +6,7 @@ version: "2.3" services: test_unit: - image: node:20.18.2 + image: node:22.15.0 volumes: - .:/overleaf/services/chat - ../../node_modules:/overleaf/node_modules @@ -21,7 +21,7 @@ services: user: node test_acceptance: - image: node:20.18.2 + image: node:22.15.0 volumes: - .:/overleaf/services/chat - ../../node_modules:/overleaf/node_modules diff --git a/services/clsi/.nvmrc b/services/clsi/.nvmrc index 0254b1e633..b8ffd70759 100644 --- a/services/clsi/.nvmrc +++ b/services/clsi/.nvmrc @@ -1 +1 @@ -20.18.2 +22.15.0 diff --git a/services/clsi/Dockerfile b/services/clsi/Dockerfile index c5f46c1c19..1f37b3dd01 100644 --- a/services/clsi/Dockerfile +++ b/services/clsi/Dockerfile @@ -2,7 +2,7 @@ # Instead run bin/update_build_scripts from # https://github.com/overleaf/internal/ -FROM node:20.18.2 AS base +FROM node:22.15.0 AS base WORKDIR /overleaf/services/clsi COPY services/clsi/install_deps.sh /overleaf/services/clsi/ diff --git a/services/clsi/Makefile b/services/clsi/Makefile index 70415b80e9..1d93a411be 100644 --- a/services/clsi/Makefile +++ b/services/clsi/Makefile @@ -32,12 +32,12 @@ HERE=$(shell pwd) MONOREPO=$(shell cd ../../ && pwd) # Run the linting commands in the scope of the monorepo. # Eslint and prettier (plus some configs) are on the root. -RUN_LINTING = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(HERE) node:20.18.2 npm run --silent +RUN_LINTING = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(HERE) node:22.15.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 # Same but from the top of the monorepo -RUN_LINTING_MONOREPO = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(MONOREPO) node:20.18.2 npm run --silent +RUN_LINTING_MONOREPO = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(MONOREPO) node:22.15.0 npm run --silent SHELLCHECK_OPTS = \ --shell=bash \ diff --git a/services/clsi/buildscript.txt b/services/clsi/buildscript.txt index 831ac3ecc9..82084539ee 100644 --- a/services/clsi/buildscript.txt +++ b/services/clsi/buildscript.txt @@ -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",DOCKER_RUNNER="true",COMPILES_HOST_DIR=$PWD/compiles,OUTPUT_HOST_DIR=$PWD/output --env-pass-through= --esmock-loader=False ---node-version=20.18.2 +--node-version=22.15.0 --public-repo=True --script-version=4.7.0 --use-large-ci-runner=True diff --git a/services/contacts/.nvmrc b/services/contacts/.nvmrc index 0254b1e633..b8ffd70759 100644 --- a/services/contacts/.nvmrc +++ b/services/contacts/.nvmrc @@ -1 +1 @@ -20.18.2 +22.15.0 diff --git a/services/contacts/Dockerfile b/services/contacts/Dockerfile index 69d2d35b3c..19094d7d44 100644 --- a/services/contacts/Dockerfile +++ b/services/contacts/Dockerfile @@ -2,7 +2,7 @@ # Instead run bin/update_build_scripts from # https://github.com/overleaf/internal/ -FROM node:20.18.2 AS base +FROM node:22.15.0 AS base WORKDIR /overleaf/services/contacts diff --git a/services/contacts/Makefile b/services/contacts/Makefile index 97a348d219..963a19c590 100644 --- a/services/contacts/Makefile +++ b/services/contacts/Makefile @@ -32,12 +32,12 @@ HERE=$(shell pwd) MONOREPO=$(shell cd ../../ && pwd) # Run the linting commands in the scope of the monorepo. # Eslint and prettier (plus some configs) are on the root. -RUN_LINTING = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(HERE) node:20.18.2 npm run --silent +RUN_LINTING = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(HERE) node:22.15.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 # Same but from the top of the monorepo -RUN_LINTING_MONOREPO = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(MONOREPO) node:20.18.2 npm run --silent +RUN_LINTING_MONOREPO = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(MONOREPO) node:22.15.0 npm run --silent SHELLCHECK_OPTS = \ --shell=bash \ diff --git a/services/contacts/buildscript.txt b/services/contacts/buildscript.txt index 8563d1b71e..d498d72ecc 100644 --- a/services/contacts/buildscript.txt +++ b/services/contacts/buildscript.txt @@ -4,6 +4,6 @@ contacts --env-add= --env-pass-through= --esmock-loader=True ---node-version=20.18.2 +--node-version=22.15.0 --public-repo=False --script-version=4.7.0 diff --git a/services/contacts/docker-compose.yml b/services/contacts/docker-compose.yml index 310220bd20..7646495f6f 100644 --- a/services/contacts/docker-compose.yml +++ b/services/contacts/docker-compose.yml @@ -6,7 +6,7 @@ version: "2.3" services: test_unit: - image: node:20.18.2 + image: node:22.15.0 volumes: - .:/overleaf/services/contacts - ../../node_modules:/overleaf/node_modules @@ -21,7 +21,7 @@ services: user: node test_acceptance: - image: node:20.18.2 + image: node:22.15.0 volumes: - .:/overleaf/services/contacts - ../../node_modules:/overleaf/node_modules diff --git a/services/docstore/.nvmrc b/services/docstore/.nvmrc index 0254b1e633..b8ffd70759 100644 --- a/services/docstore/.nvmrc +++ b/services/docstore/.nvmrc @@ -1 +1 @@ -20.18.2 +22.15.0 diff --git a/services/docstore/Dockerfile b/services/docstore/Dockerfile index 60a024ea91..98ad4b88e5 100644 --- a/services/docstore/Dockerfile +++ b/services/docstore/Dockerfile @@ -2,7 +2,7 @@ # Instead run bin/update_build_scripts from # https://github.com/overleaf/internal/ -FROM node:20.18.2 AS base +FROM node:22.15.0 AS base WORKDIR /overleaf/services/docstore diff --git a/services/docstore/Makefile b/services/docstore/Makefile index 6efd053025..e44b964044 100644 --- a/services/docstore/Makefile +++ b/services/docstore/Makefile @@ -32,12 +32,12 @@ HERE=$(shell pwd) MONOREPO=$(shell cd ../../ && pwd) # Run the linting commands in the scope of the monorepo. # Eslint and prettier (plus some configs) are on the root. -RUN_LINTING = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(HERE) node:20.18.2 npm run --silent +RUN_LINTING = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(HERE) node:22.15.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 # Same but from the top of the monorepo -RUN_LINTING_MONOREPO = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(MONOREPO) node:20.18.2 npm run --silent +RUN_LINTING_MONOREPO = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(MONOREPO) node:22.15.0 npm run --silent SHELLCHECK_OPTS = \ --shell=bash \ diff --git a/services/docstore/buildscript.txt b/services/docstore/buildscript.txt index c329d7b571..acf043e4e2 100644 --- a/services/docstore/buildscript.txt +++ b/services/docstore/buildscript.txt @@ -4,6 +4,6 @@ docstore --env-add= --env-pass-through= --esmock-loader=False ---node-version=20.18.2 +--node-version=22.15.0 --public-repo=True --script-version=4.7.0 diff --git a/services/docstore/docker-compose.yml b/services/docstore/docker-compose.yml index 93a029b00a..9d4a7bfc6b 100644 --- a/services/docstore/docker-compose.yml +++ b/services/docstore/docker-compose.yml @@ -6,7 +6,7 @@ version: "2.3" services: test_unit: - image: node:20.18.2 + image: node:22.15.0 volumes: - .:/overleaf/services/docstore - ../../node_modules:/overleaf/node_modules @@ -21,7 +21,7 @@ services: user: node test_acceptance: - image: node:20.18.2 + image: node:22.15.0 volumes: - .:/overleaf/services/docstore - ../../node_modules:/overleaf/node_modules diff --git a/services/document-updater/.nvmrc b/services/document-updater/.nvmrc index 0254b1e633..b8ffd70759 100644 --- a/services/document-updater/.nvmrc +++ b/services/document-updater/.nvmrc @@ -1 +1 @@ -20.18.2 +22.15.0 diff --git a/services/document-updater/Dockerfile b/services/document-updater/Dockerfile index 436d722577..e23e5a9052 100644 --- a/services/document-updater/Dockerfile +++ b/services/document-updater/Dockerfile @@ -2,7 +2,7 @@ # Instead run bin/update_build_scripts from # https://github.com/overleaf/internal/ -FROM node:20.18.2 AS base +FROM node:22.15.0 AS base WORKDIR /overleaf/services/document-updater diff --git a/services/document-updater/Makefile b/services/document-updater/Makefile index 55f483fc89..963a56a0d0 100644 --- a/services/document-updater/Makefile +++ b/services/document-updater/Makefile @@ -32,12 +32,12 @@ HERE=$(shell pwd) MONOREPO=$(shell cd ../../ && pwd) # Run the linting commands in the scope of the monorepo. # Eslint and prettier (plus some configs) are on the root. -RUN_LINTING = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(HERE) node:20.18.2 npm run --silent +RUN_LINTING = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(HERE) node:22.15.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 # Same but from the top of the monorepo -RUN_LINTING_MONOREPO = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(MONOREPO) node:20.18.2 npm run --silent +RUN_LINTING_MONOREPO = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(MONOREPO) node:22.15.0 npm run --silent SHELLCHECK_OPTS = \ --shell=bash \ diff --git a/services/document-updater/buildscript.txt b/services/document-updater/buildscript.txt index ee013ebea9..83e9299109 100644 --- a/services/document-updater/buildscript.txt +++ b/services/document-updater/buildscript.txt @@ -4,6 +4,6 @@ document-updater --env-add= --env-pass-through= --esmock-loader=False ---node-version=20.18.2 +--node-version=22.15.0 --public-repo=True --script-version=4.7.0 diff --git a/services/document-updater/docker-compose.yml b/services/document-updater/docker-compose.yml index e33174f9e2..d211fbe307 100644 --- a/services/document-updater/docker-compose.yml +++ b/services/document-updater/docker-compose.yml @@ -6,7 +6,7 @@ version: "2.3" services: test_unit: - image: node:20.18.2 + image: node:22.15.0 volumes: - .:/overleaf/services/document-updater - ../../node_modules:/overleaf/node_modules @@ -21,7 +21,7 @@ services: user: node test_acceptance: - image: node:20.18.2 + image: node:22.15.0 volumes: - .:/overleaf/services/document-updater - ../../node_modules:/overleaf/node_modules diff --git a/services/filestore/.nvmrc b/services/filestore/.nvmrc index 0254b1e633..b8ffd70759 100644 --- a/services/filestore/.nvmrc +++ b/services/filestore/.nvmrc @@ -1 +1 @@ -20.18.2 +22.15.0 diff --git a/services/filestore/Dockerfile b/services/filestore/Dockerfile index 8e336d4630..1de1be5e22 100644 --- a/services/filestore/Dockerfile +++ b/services/filestore/Dockerfile @@ -2,7 +2,7 @@ # Instead run bin/update_build_scripts from # https://github.com/overleaf/internal/ -FROM node:20.18.2 AS base +FROM node:22.15.0 AS base WORKDIR /overleaf/services/filestore COPY services/filestore/install_deps.sh /overleaf/services/filestore/ diff --git a/services/filestore/Makefile b/services/filestore/Makefile index cc1724589d..53827955fc 100644 --- a/services/filestore/Makefile +++ b/services/filestore/Makefile @@ -32,12 +32,12 @@ HERE=$(shell pwd) MONOREPO=$(shell cd ../../ && pwd) # Run the linting commands in the scope of the monorepo. # Eslint and prettier (plus some configs) are on the root. -RUN_LINTING = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(HERE) node:20.18.2 npm run --silent +RUN_LINTING = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(HERE) node:22.15.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 # Same but from the top of the monorepo -RUN_LINTING_MONOREPO = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(MONOREPO) node:20.18.2 npm run --silent +RUN_LINTING_MONOREPO = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(MONOREPO) node:22.15.0 npm run --silent SHELLCHECK_OPTS = \ --shell=bash \ diff --git a/services/filestore/buildscript.txt b/services/filestore/buildscript.txt index 75a491c18a..fd80733689 100644 --- a/services/filestore/buildscript.txt +++ b/services/filestore/buildscript.txt @@ -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-pass-through= --esmock-loader=False ---node-version=20.18.2 +--node-version=22.15.0 --public-repo=True --script-version=4.7.0 --test-acceptance-shards=SHARD_01_,SHARD_02_,SHARD_03_ diff --git a/services/filestore/docker-compose.ci.yml b/services/filestore/docker-compose.ci.yml index febd6e4c13..e1454fa809 100644 --- a/services/filestore/docker-compose.ci.yml +++ b/services/filestore/docker-compose.ci.yml @@ -64,7 +64,7 @@ services: command: tar -czf /tmp/build/build.tar.gz --exclude=build.tar.gz --exclude-vcs . user: root certs: - image: node:20.18.2 + image: node:22.15.0 volumes: - ./test/acceptance/certs:/certs working_dir: /certs diff --git a/services/filestore/docker-compose.yml b/services/filestore/docker-compose.yml index cc58997445..e6e9c659db 100644 --- a/services/filestore/docker-compose.yml +++ b/services/filestore/docker-compose.yml @@ -72,7 +72,7 @@ services: command: npm run --silent test:acceptance certs: - image: node:20.18.2 + image: node:22.15.0 volumes: - ./test/acceptance/certs:/certs working_dir: /certs diff --git a/services/history-v1/.nvmrc b/services/history-v1/.nvmrc index 0254b1e633..b8ffd70759 100644 --- a/services/history-v1/.nvmrc +++ b/services/history-v1/.nvmrc @@ -1 +1 @@ -20.18.2 +22.15.0 diff --git a/services/history-v1/Dockerfile b/services/history-v1/Dockerfile index 0aa6a2fc0f..4231373781 100644 --- a/services/history-v1/Dockerfile +++ b/services/history-v1/Dockerfile @@ -2,7 +2,7 @@ # Instead run bin/update_build_scripts from # https://github.com/overleaf/internal/ -FROM node:20.18.2 AS base +FROM node:22.15.0 AS base WORKDIR /overleaf/services/history-v1 COPY services/history-v1/install_deps.sh /overleaf/services/history-v1/ diff --git a/services/history-v1/Makefile b/services/history-v1/Makefile index 1f03a21f18..18f6bcffd5 100644 --- a/services/history-v1/Makefile +++ b/services/history-v1/Makefile @@ -32,12 +32,12 @@ HERE=$(shell pwd) MONOREPO=$(shell cd ../../ && pwd) # Run the linting commands in the scope of the monorepo. # Eslint and prettier (plus some configs) are on the root. -RUN_LINTING = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(HERE) node:20.18.2 npm run --silent +RUN_LINTING = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(HERE) node:22.15.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 # Same but from the top of the monorepo -RUN_LINTING_MONOREPO = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(MONOREPO) node:20.18.2 npm run --silent +RUN_LINTING_MONOREPO = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(MONOREPO) node:22.15.0 npm run --silent SHELLCHECK_OPTS = \ --shell=bash \ diff --git a/services/history-v1/buildscript.txt b/services/history-v1/buildscript.txt index f3e029ba33..a1988ab2ca 100644 --- a/services/history-v1/buildscript.txt +++ b/services/history-v1/buildscript.txt @@ -4,7 +4,7 @@ history-v1 --env-add= --env-pass-through= --esmock-loader=False ---node-version=20.18.2 +--node-version=22.15.0 --public-repo=False --script-version=4.7.0 --tsconfig-extra-includes=backup-deletion-app.mjs,backup-verifier-app.mjs,backup-worker-app.mjs,api/**/*,migrations/**/*,storage/**/* diff --git a/services/history-v1/docker-compose.ci.yml b/services/history-v1/docker-compose.ci.yml index 06d5d55161..ebf15679eb 100644 --- a/services/history-v1/docker-compose.ci.yml +++ b/services/history-v1/docker-compose.ci.yml @@ -98,7 +98,7 @@ services: retries: 20 certs: - image: node:20.18.2 + image: node:22.15.0 volumes: - ./test/acceptance/certs:/certs working_dir: /certs diff --git a/services/history-v1/docker-compose.yml b/services/history-v1/docker-compose.yml index f4c885d467..941c5a6d6d 100644 --- a/services/history-v1/docker-compose.yml +++ b/services/history-v1/docker-compose.yml @@ -107,7 +107,7 @@ services: retries: 20 certs: - image: node:20.18.2 + image: node:22.15.0 volumes: - ./test/acceptance/certs:/certs working_dir: /certs diff --git a/services/notifications/.nvmrc b/services/notifications/.nvmrc index 0254b1e633..b8ffd70759 100644 --- a/services/notifications/.nvmrc +++ b/services/notifications/.nvmrc @@ -1 +1 @@ -20.18.2 +22.15.0 diff --git a/services/notifications/Dockerfile b/services/notifications/Dockerfile index 16a5c44fbf..255a7ffeff 100644 --- a/services/notifications/Dockerfile +++ b/services/notifications/Dockerfile @@ -2,7 +2,7 @@ # Instead run bin/update_build_scripts from # https://github.com/overleaf/internal/ -FROM node:20.18.2 AS base +FROM node:22.15.0 AS base WORKDIR /overleaf/services/notifications diff --git a/services/notifications/Makefile b/services/notifications/Makefile index 8ca3f983ff..1d5938cfcf 100644 --- a/services/notifications/Makefile +++ b/services/notifications/Makefile @@ -32,12 +32,12 @@ HERE=$(shell pwd) MONOREPO=$(shell cd ../../ && pwd) # Run the linting commands in the scope of the monorepo. # Eslint and prettier (plus some configs) are on the root. -RUN_LINTING = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(HERE) node:20.18.2 npm run --silent +RUN_LINTING = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(HERE) node:22.15.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 # Same but from the top of the monorepo -RUN_LINTING_MONOREPO = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(MONOREPO) node:20.18.2 npm run --silent +RUN_LINTING_MONOREPO = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(MONOREPO) node:22.15.0 npm run --silent SHELLCHECK_OPTS = \ --shell=bash \ diff --git a/services/notifications/buildscript.txt b/services/notifications/buildscript.txt index c52e316ffe..1ff790603e 100644 --- a/services/notifications/buildscript.txt +++ b/services/notifications/buildscript.txt @@ -4,6 +4,6 @@ notifications --env-add= --env-pass-through= --esmock-loader=False ---node-version=20.18.2 +--node-version=22.15.0 --public-repo=True --script-version=4.7.0 diff --git a/services/notifications/docker-compose.yml b/services/notifications/docker-compose.yml index c0902fee2d..b35375a399 100644 --- a/services/notifications/docker-compose.yml +++ b/services/notifications/docker-compose.yml @@ -6,7 +6,7 @@ version: "2.3" services: test_unit: - image: node:20.18.2 + image: node:22.15.0 volumes: - .:/overleaf/services/notifications - ../../node_modules:/overleaf/node_modules @@ -21,7 +21,7 @@ services: user: node test_acceptance: - image: node:20.18.2 + image: node:22.15.0 volumes: - .:/overleaf/services/notifications - ../../node_modules:/overleaf/node_modules diff --git a/services/project-history/.nvmrc b/services/project-history/.nvmrc index 0254b1e633..b8ffd70759 100644 --- a/services/project-history/.nvmrc +++ b/services/project-history/.nvmrc @@ -1 +1 @@ -20.18.2 +22.15.0 diff --git a/services/project-history/Dockerfile b/services/project-history/Dockerfile index 1bf4e5680b..5a88d0382f 100644 --- a/services/project-history/Dockerfile +++ b/services/project-history/Dockerfile @@ -2,7 +2,7 @@ # Instead run bin/update_build_scripts from # https://github.com/overleaf/internal/ -FROM node:20.18.2 AS base +FROM node:22.15.0 AS base WORKDIR /overleaf/services/project-history diff --git a/services/project-history/Makefile b/services/project-history/Makefile index 5cde05ea46..1d63b7a8f0 100644 --- a/services/project-history/Makefile +++ b/services/project-history/Makefile @@ -32,12 +32,12 @@ HERE=$(shell pwd) MONOREPO=$(shell cd ../../ && pwd) # Run the linting commands in the scope of the monorepo. # Eslint and prettier (plus some configs) are on the root. -RUN_LINTING = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(HERE) node:20.18.2 npm run --silent +RUN_LINTING = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(HERE) node:22.15.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 --volume $(MONOREPO)/services/document-updater/app/js/types.ts:/overleaf/services/document-updater/app/js/types.ts ci/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) npm run --silent # Same but from the top of the monorepo -RUN_LINTING_MONOREPO = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(MONOREPO) node:20.18.2 npm run --silent +RUN_LINTING_MONOREPO = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(MONOREPO) node:22.15.0 npm run --silent SHELLCHECK_OPTS = \ --shell=bash \ diff --git a/services/project-history/buildscript.txt b/services/project-history/buildscript.txt index be5e751759..0e4591c5fc 100644 --- a/services/project-history/buildscript.txt +++ b/services/project-history/buildscript.txt @@ -4,6 +4,6 @@ project-history --env-add= --env-pass-through= --esmock-loader=True ---node-version=20.18.2 +--node-version=22.15.0 --public-repo=False --script-version=4.7.0 diff --git a/services/project-history/docker-compose.yml b/services/project-history/docker-compose.yml index deed9c5033..a7db044a27 100644 --- a/services/project-history/docker-compose.yml +++ b/services/project-history/docker-compose.yml @@ -6,7 +6,7 @@ version: "2.3" services: test_unit: - image: node:20.18.2 + image: node:22.15.0 volumes: - .:/overleaf/services/project-history - ../../node_modules:/overleaf/node_modules @@ -21,7 +21,7 @@ services: user: node test_acceptance: - image: node:20.18.2 + image: node:22.15.0 volumes: - .:/overleaf/services/project-history - ../../node_modules:/overleaf/node_modules diff --git a/services/real-time/.nvmrc b/services/real-time/.nvmrc index 0254b1e633..b8ffd70759 100644 --- a/services/real-time/.nvmrc +++ b/services/real-time/.nvmrc @@ -1 +1 @@ -20.18.2 +22.15.0 diff --git a/services/real-time/Dockerfile b/services/real-time/Dockerfile index d1f2046895..a14a51b224 100644 --- a/services/real-time/Dockerfile +++ b/services/real-time/Dockerfile @@ -2,7 +2,7 @@ # Instead run bin/update_build_scripts from # https://github.com/overleaf/internal/ -FROM node:20.18.2 AS base +FROM node:22.15.0 AS base WORKDIR /overleaf/services/real-time diff --git a/services/real-time/Makefile b/services/real-time/Makefile index e9e6a7a067..746f5e8e74 100644 --- a/services/real-time/Makefile +++ b/services/real-time/Makefile @@ -32,12 +32,12 @@ HERE=$(shell pwd) MONOREPO=$(shell cd ../../ && pwd) # Run the linting commands in the scope of the monorepo. # Eslint and prettier (plus some configs) are on the root. -RUN_LINTING = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(HERE) node:20.18.2 npm run --silent +RUN_LINTING = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(HERE) node:22.15.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 # Same but from the top of the monorepo -RUN_LINTING_MONOREPO = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(MONOREPO) node:20.18.2 npm run --silent +RUN_LINTING_MONOREPO = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(MONOREPO) node:22.15.0 npm run --silent SHELLCHECK_OPTS = \ --shell=bash \ diff --git a/services/real-time/buildscript.txt b/services/real-time/buildscript.txt index 292fde8b4c..1b5cfce301 100644 --- a/services/real-time/buildscript.txt +++ b/services/real-time/buildscript.txt @@ -4,6 +4,6 @@ real-time --env-add= --env-pass-through= --esmock-loader=False ---node-version=20.18.2 +--node-version=22.15.0 --public-repo=False --script-version=4.7.0 diff --git a/services/real-time/docker-compose.yml b/services/real-time/docker-compose.yml index d40fada758..b122dd0f39 100644 --- a/services/real-time/docker-compose.yml +++ b/services/real-time/docker-compose.yml @@ -6,7 +6,7 @@ version: "2.3" services: test_unit: - image: node:20.18.2 + image: node:22.15.0 volumes: - .:/overleaf/services/real-time - ../../node_modules:/overleaf/node_modules @@ -21,7 +21,7 @@ services: user: node test_acceptance: - image: node:20.18.2 + image: node:22.15.0 volumes: - .:/overleaf/services/real-time - ../../node_modules:/overleaf/node_modules diff --git a/services/web/.nvmrc b/services/web/.nvmrc index 0254b1e633..b8ffd70759 100644 --- a/services/web/.nvmrc +++ b/services/web/.nvmrc @@ -1 +1 @@ -20.18.2 +22.15.0 diff --git a/services/web/Dockerfile b/services/web/Dockerfile index 4f5d2e5ff1..ba8a91fe6d 100644 --- a/services/web/Dockerfile +++ b/services/web/Dockerfile @@ -1,6 +1,6 @@ # the base image is suitable for running web with /overleaf/services/web bind # mounted -FROM node:20.18.2 AS base +FROM node:22.15.0 AS base WORKDIR /overleaf/services/web diff --git a/services/web/Dockerfile.frontend b/services/web/Dockerfile.frontend index 0bdfd8c1f9..ee66f12559 100644 --- a/services/web/Dockerfile.frontend +++ b/services/web/Dockerfile.frontend @@ -1,4 +1,4 @@ -FROM node:20.18.2 +FROM node:22.15.0 # Install Google Chrome RUN wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - diff --git a/services/web/cloudbuild-storybook.yaml b/services/web/cloudbuild-storybook.yaml index ac9cfeba7d..20a523ceaa 100644 --- a/services/web/cloudbuild-storybook.yaml +++ b/services/web/cloudbuild-storybook.yaml @@ -1,13 +1,13 @@ steps: - id: npm_ci - name: "node:20.18.2" + name: "node:22.15.0" entrypoint: /bin/bash args: - '-c' - 'bin/npm_install_subset . libraries/* services/web' - id: build-storybook - name: 'node:20.18.2' + name: 'node:22.15.0' env: - 'BRANCH_NAME=$BRANCH_NAME' - 'BUILD_ID=$BUILD_ID' @@ -49,7 +49,7 @@ steps: - deploy-storybook - id: create-storybook-index - name: 'node:20.18.2' + name: 'node:22.15.0' dir: services/web env: - 'BRANCH_NAME=$BRANCH_NAME' diff --git a/services/web/docker-compose.common.env b/services/web/docker-compose.common.env index 7f94f5714a..10ee8b4a89 100644 --- a/services/web/docker-compose.common.env +++ b/services/web/docker-compose.common.env @@ -41,6 +41,6 @@ OVERLEAF_SAML_UPDATE_USER_DETAILS_ON_LOGIN=true OVERLEAF_SAML_CERT=MIIDXTCCAkWgAwIBAgIJAOvOeQ4xFTzsMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNVBAYTAkdCMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMTYxMTE1MTQxMjU5WhcNMjYxMTE1MTQxMjU5WjBFMQswCQYDVQQGEwJHQjETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxCT6MBe5G9VoLU8MfztOEbUhnwLp17ak8eFUqxqeXkkqtWB0b/cmIBU3xoQoO3dIF8PBzfqehqfYVhrNt/TFgcmDfmJnPJRL1RJWMW3VmiP5odJ3LwlkKbZpkeT3wZ8HEJIR1+zbpxiBNkbd2GbdR1iumcsHzMYX1A2CBj+ZMV5VijC+K4P0e9c05VsDEUtLmfeAasJAiumQoVVgAe/BpiXjICGGewa6EPFI7mKkifIRKOGxdRESwZZjxP30bI31oDN0cgKqIgSJtJ9nfCn9jgBMBkQHu42WMuaWD4jrGd7+vYdX+oIfArs9aKgAH5kUGhGdew2R9SpBefrhbNxG8QIDAQABo1AwTjAdBgNVHQ4EFgQU+aSojSyyLChP/IpZcafvSdhj7KkwHwYDVR0jBBgwFoAU+aSojSyyLChP/IpZcafvSdhj7KkwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEABl3+OOVLBWMKs6PjA8lPuloWDNzSr3v76oUcHqAb+cfbucjXrOVsS9RJ0X9yxvCQyfM9FfY43DbspnN3izYhdvbJD8kKLNf0LA5st+ZxLfy0ACyL2iyAwICaqndqxAjQYplFAHmpUiu1DiHckyBPekokDJd+ze95urHMOsaGS5RWPoKJVE0bkaAeZCmEu0NNpXRSBiuxXSTeSAJfv6kyE/rkdhzUKyUl/cGQFrsVYfAFQVA+W6CKOh74ErSEzSHQQYndl7nD33snD/YqdU1ROxV6aJzLKCg+sdj+wRXSP2u/UHnM4jW9TGJfhO42jzL6WVuEvr9q4l7zWzUQKKKhtQ== # DEVICE_HISTORY_SECRET has been generated using: # NOTE: crypto.generateKeySync was added in v15, v16 is the next LTS release. -# $ docker run --rm node:20.18.2 --print 'require("crypto").generateKeySync("aes", { length: 256 }).export().toString("hex")' +# $ docker run --rm node:22.15.0 --print 'require("crypto").generateKeySync("aes", { length: 256 }).export().toString("hex")' DEVICE_HISTORY_SECRET=1b46e6cdf72db02845da06c9517c9cfbbfa0d87357479f4e1df3ce160bd54807 QUEUE_PROCESSING_ENABLED=true diff --git a/services/web/docker-compose.yml b/services/web/docker-compose.yml index c6a5aa7482..a423c9687f 100644 --- a/services/web/docker-compose.yml +++ b/services/web/docker-compose.yml @@ -6,7 +6,7 @@ volumes: services: test_unit: - image: node:20.18.2 + image: node:22.15.0 volumes: - .:/overleaf/services/web - ../../node_modules:/overleaf/node_modules @@ -26,7 +26,7 @@ services: - mongo test_acceptance: - image: node:20.18.2 + image: node:22.15.0 volumes: - .:/overleaf/services/web - ../../node_modules:/overleaf/node_modules diff --git a/services/web/scripts/translations/Dockerfile b/services/web/scripts/translations/Dockerfile index e033989372..f1b4d09492 100644 --- a/services/web/scripts/translations/Dockerfile +++ b/services/web/scripts/translations/Dockerfile @@ -1,4 +1,4 @@ -FROM node:20.18.2 +FROM node:22.15.0 WORKDIR /app/scripts/translations diff --git a/services/web/test/frontend/bootstrap.js b/services/web/test/frontend/bootstrap.js index e98d2c35de..df4d3f1464 100644 --- a/services/web/test/frontend/bootstrap.js +++ b/services/web/test/frontend/bootstrap.js @@ -104,3 +104,8 @@ const fetchMock = require('fetch-mock').default fetchMock.spyGlobal() fetchMock.config.fetch = global.fetch fetchMock.config.Response = fetch.Response + +Object.defineProperty(navigator, 'onLine', { + configurable: true, + get: () => true, +}) diff --git a/services/web/test/frontend/features/project-list/components/project-list-root.test.tsx b/services/web/test/frontend/features/project-list/components/project-list-root.test.tsx index 4cfc119f0b..dc5ecfe6cf 100644 --- a/services/web/test/frontend/features/project-list/components/project-list-root.test.tsx +++ b/services/web/test/frontend/features/project-list/components/project-list-root.test.tsx @@ -287,7 +287,6 @@ describe('', function () { fireEvent.click(unarchiveButton) await fetchMock.callHistory.flush(true) - expect(fetchMock.callHistory.done()).to.be.true await screen.findByText('No projects') }) @@ -302,7 +301,6 @@ describe('', function () { ) await fetchMock.callHistory.flush(true) - expect(fetchMock.callHistory.done()).to.be.true expect(screen.queryByText('No projects')).to.be.null }) @@ -354,7 +352,6 @@ describe('', function () { fireEvent.click(untrashButton) await fetchMock.callHistory.flush(true) - expect(fetchMock.callHistory.done()).to.be.true await screen.findByText('No projects') }) @@ -367,7 +364,6 @@ describe('', function () { expect(allCheckboxesChecked.length).to.equal(trashedList.length - 1) await fetchMock.callHistory.flush(true) - expect(fetchMock.callHistory.done()).to.be.true expect(screen.queryByText('No projects')).to.be.null }) @@ -392,7 +388,6 @@ describe('', function () { expect(confirmButton.disabled).to.be.true await fetchMock.callHistory.flush(true) - expect(fetchMock.callHistory.done()).to.be.true const calls = fetchMock.callHistory.calls().map(({ url }) => url) @@ -457,7 +452,6 @@ describe('', function () { expect(confirmButton.disabled).to.be.true await fetchMock.callHistory.flush(true) - expect(fetchMock.callHistory.done()).to.be.true const calls = fetchMock.callHistory.calls().map(({ url }) => url) leavableList.forEach(project => { @@ -520,7 +514,6 @@ describe('', function () { expect(confirmButton.disabled).to.be.true await fetchMock.callHistory.flush(true) - expect(fetchMock.callHistory.done()).to.be.true const calls = fetchMock.callHistory.calls().map(({ url }) => url) deletableList.forEach(project => { @@ -591,7 +584,6 @@ describe('', function () { expect(confirmButton.disabled).to.be.true await fetchMock.callHistory.flush(true) - expect(fetchMock.callHistory.done()).to.be.true const calls = fetchMock.callHistory.calls().map(({ url }) => url) deletableAndLeavableList.forEach(project => { @@ -1186,7 +1178,6 @@ describe('', function () { fireEvent.click(copyConfirmButton) await fetchMock.callHistory.flush(true) - expect(fetchMock.callHistory.done()).to.be.true expect(sendMBSpy).to.have.been.calledTwice expect(sendMBSpy).to.have.been.calledWith('loads_v2_dash') From 10091811f7813b98acf637fa71806732bbc2358b Mon Sep 17 00:00:00 2001 From: Antoine Clausse Date: Thu, 15 May 2025 15:13:34 +0200 Subject: [PATCH 122/194] Remove returns in functions with callback in CooldownManager (fix DeprecationWarning) (#25665) GitOrigin-RevId: 3652ee78d533cb00ce5ec4de1a66a959ab418def --- .../src/Features/Cooldown/CooldownManager.js | 41 +++++++------------ 1 file changed, 14 insertions(+), 27 deletions(-) diff --git a/services/web/app/src/Features/Cooldown/CooldownManager.js b/services/web/app/src/Features/Cooldown/CooldownManager.js index 67bdc98838..8c6b708c07 100644 --- a/services/web/app/src/Features/Cooldown/CooldownManager.js +++ b/services/web/app/src/Features/Cooldown/CooldownManager.js @@ -1,24 +1,11 @@ -/* eslint-disable - n/handle-callback-err, - max-len, -*/ -// TODO: This file was created by bulk-decaffeinate. -// Fix any style issues and re-enable lint. -/* - * decaffeinate suggestions: - * DS102: Remove unnecessary code created because of implicit returns - * DS207: Consider shorter variations of null checks - * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md - */ -let CooldownManager const RedisWrapper = require('../../infrastructure/RedisWrapper') const rclient = RedisWrapper.client('cooldown') const logger = require('@overleaf/logger') -const { promisifyAll } = require('@overleaf/promise-utils') +const { promisify } = require('@overleaf/promise-utils') const COOLDOWN_IN_SECONDS = 60 * 10 -module.exports = CooldownManager = { +const CooldownManager = { _buildKey(projectId) { return `Cooldown:{${projectId}}` }, @@ -31,7 +18,7 @@ module.exports = CooldownManager = { { projectId }, `[Cooldown] putting project on cooldown for ${COOLDOWN_IN_SECONDS} seconds` ) - return rclient.set( + rclient.set( CooldownManager._buildKey(projectId), '1', 'EX', @@ -44,18 +31,18 @@ module.exports = CooldownManager = { if (callback == null) { callback = function () {} } - return rclient.get( - CooldownManager._buildKey(projectId), - function (err, result) { - if (err != null) { - return callback(err) - } - return callback(null, result === '1') + rclient.get(CooldownManager._buildKey(projectId), function (err, result) { + if (err != null) { + return callback(err) } - ) + return callback(null, result === '1') + }) }, } -module.exports.promises = promisifyAll(module.exports, { - without: ['_buildKey'], -}) +CooldownManager.promises = { + putProjectOnCooldown: promisify(CooldownManager.putProjectOnCooldown), + isProjectOnCooldown: promisify(CooldownManager.isProjectOnCooldown), +} + +module.exports = CooldownManager From e282a74f7af215e6154b74a10e1aa58da2c7f27d Mon Sep 17 00:00:00 2001 From: Rebeka Dekany <50901361+rebekadekany@users.noreply.github.com> Date: Thu, 15 May 2025 17:08:12 +0200 Subject: [PATCH 123/194] Make links more descriptive on the Account settings page (#25558) GitOrigin-RevId: 21cb7c02f7a5678b4c385da5b842ad6a5303169b --- services/web/frontend/extracted-translations.json | 5 ++--- .../features/settings/components/emails-section.tsx | 1 - .../components/emails/institution-and-role.tsx | 2 +- .../components/linking/integration-widget.tsx | 9 ++------- .../settings/components/linking/sso-widget.tsx | 9 ++------- services/web/locales/en.json | 11 +++++------ .../components/emails/emails-section.test.tsx | 4 +++- 7 files changed, 15 insertions(+), 26 deletions(-) diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json index cf47e358d4..f3448a572d 100644 --- a/services/web/frontend/extracted-translations.json +++ b/services/web/frontend/extracted-translations.json @@ -31,7 +31,6 @@ "about_to_leave_project": "", "about_to_leave_projects": "", "about_to_trash_projects": "", - "about_writefull": "", "abstract": "", "accept_and_continue": "", "accept_change": "", @@ -222,6 +221,7 @@ "center": "", "change": "", "change_currency": "", + "change_email": "", "change_language": "", "change_or_cancel-cancel": "", "change_or_cancel-change": "", @@ -670,6 +670,7 @@ "github_workflow_files_delete_github_repo": "", "github_workflow_files_error": "", "give_feedback": "", + "give_feedback_about": "", "give_your_feedback": "", "go_next_page": "", "go_page": "", @@ -903,7 +904,6 @@ "learn_more_about_compile_timeouts": "", "learn_more_about_link_sharing": "", "learn_more_about_managed_users": "", - "learn_more_about_managing_email": "", "learn_more_about_other_causes_of_compile_timeouts": "", "leave": "", "leave_any_group_subscriptions": "", @@ -1145,7 +1145,6 @@ "other_causes_of_compile_timeouts": "", "other_logs_and_files": "", "other_output_files": "", - "our_help_page": "", "our_team_will_get_back_to_you_shortly": "", "our_values": "", "out_of_sync": "", diff --git a/services/web/frontend/js/features/settings/components/emails-section.tsx b/services/web/frontend/js/features/settings/components/emails-section.tsx index 30d52fbf9e..a8bc54b017 100644 --- a/services/web/frontend/js/features/settings/components/emails-section.tsx +++ b/services/web/frontend/js/features/settings/components/emails-section.tsx @@ -40,7 +40,6 @@ function EmailsSectionContent() { , ]} /> diff --git a/services/web/frontend/js/features/settings/components/emails/institution-and-role.tsx b/services/web/frontend/js/features/settings/components/emails/institution-and-role.tsx index c00a4f053a..21368c39ff 100644 --- a/services/web/frontend/js/features/settings/components/emails/institution-and-role.tsx +++ b/services/web/frontend/js/features/settings/components/emails/institution-and-role.tsx @@ -115,7 +115,7 @@ function InstitutionAndRole({ userEmailData }: InstitutionAndRoleProps) { > {!affiliation.department && !affiliation.role ? t('add_role_and_department') - : t('change')} + : t('change_email')}
  • ) : ( diff --git a/services/web/frontend/js/features/settings/components/linking/integration-widget.tsx b/services/web/frontend/js/features/settings/components/linking/integration-widget.tsx index 0ef67446b6..8d18e3e515 100644 --- a/services/web/frontend/js/features/settings/components/linking/integration-widget.tsx +++ b/services/web/frontend/js/features/settings/components/linking/integration-widget.tsx @@ -72,13 +72,8 @@ export function IntegrationLinkingWidget({

    {description}{' '} - - {t('learn_more')} + + {t('learn_more_about', { appName: title })}

    {hasFeature && statusIndicator} diff --git a/services/web/frontend/js/features/settings/components/linking/sso-widget.tsx b/services/web/frontend/js/features/settings/components/linking/sso-widget.tsx index bea87e7979..dd64f7d489 100644 --- a/services/web/frontend/js/features/settings/components/linking/sso-widget.tsx +++ b/services/web/frontend/js/features/settings/components/linking/sso-widget.tsx @@ -74,13 +74,8 @@ export function SSOLinkingWidget({

    {description?.replace(/<[^>]+>/g, '')}{' '} {helpPath ? ( - - {t('learn_more')} + + {t('learn_more_about', { appName: title })} ) : null}

    diff --git a/services/web/locales/en.json b/services/web/locales/en.json index 6b947e50dc..fc9b8da439 100644 --- a/services/web/locales/en.json +++ b/services/web/locales/en.json @@ -34,7 +34,6 @@ "about_to_leave_project": "You are about to leave this project.", "about_to_leave_projects": "You are about to leave the following projects:", "about_to_trash_projects": "You are about to trash the following projects:", - "about_writefull": "About Writefull", "abstract": "Abstract", "accept": "Accept", "accept_and_continue": "Accept and continue", @@ -288,6 +287,7 @@ "certificate": "Certificate", "change": "Change", "change_currency": "Change currency", + "change_email": "Change email", "change_language": "Change language", "change_or_cancel-cancel": "cancel", "change_or_cancel-change": "Change", @@ -297,7 +297,7 @@ "change_password_in_account_settings": "Change password in Account Settings", "change_plan": "Change plan", "change_primary_email": "Change primary email", - "change_primary_email_address_instructions": "To change your primary email, please add your new primary email address first (by clicking <0>Add another email) and confirm it. Then click the <0>Make Primary button. <1>Learn more about managing your __appName__ emails.", + "change_primary_email_address_instructions": "To change your primary email, please add your new primary email address first (by clicking <0>Add another email) and confirm it. Then click the <0>Make Primary button. <1>Learn more about managing your __appName__ emails.", "change_project_owner": "Change Project Owner", "change_the_ownership_of_your_personal_projects": "Change the ownership of your personal projects to the new account. <0>Find out how to change project owner.", "change_to_group_plan": "Change to a group plan", @@ -845,7 +845,7 @@ "git_bridge_modal_use_previous_token": "If you’re prompted for a password, you can use a previously generated Git authentication token. Or you can generate a new one in Account Settings. For more support, read our <0>help page.", "git_gitHub_dropbox_mendeley_papers_and_zotero_integrations": "Git, GitHub, Dropbox, Papers, Zotero, and Mendeley integrations", "git_integration": "Git Integration", - "git_integration_info": "With Git integration, you can clone your Overleaf projects with Git. For full instructions on how to do this, read <0>our help page.", + "git_integration_info": "With Git integration, you can clone your Overleaf projects with Git. For full instructions on how to do this, <0>read our Git Integration help page.", "github": "GitHub", "github_commit_message_placeholder": "Commit message for changes made in __appName__...", "github_credentials_expired": "Your GitHub authorization credentials have expired", @@ -876,6 +876,7 @@ "github_workflow_files_delete_github_repo": "The repository has been created on GitHub but linking was unsuccessful. You will have to delete GitHub repository or choose a new name.", "github_workflow_files_error": "The __appName__ GitHub sync service couldn’t sync GitHub Workflow files (in .github/workflows/). Please authorize __appName__ to edit your GitHub workflow files and try again.", "give_feedback": "Give feedback", + "give_feedback_about": "Give feedback about __appName__", "give_your_feedback": "give your feedback", "global": "global", "go_back_and_link_accts": "Go back and link your accounts", @@ -1176,13 +1177,12 @@ "ldap_create_admin_instructions": "Choose an email address for the first __appName__ admin account. This should correspond to an account in the LDAP system. You will then be asked to log in with this account.", "learn": "Learn", "learn_more": "Learn more", - "learn_more_about": "Learn more about __integrationName__", + "learn_more_about": "Learn more about __appName__", "learn_more_about_account": "<0>Learn more about managing your __appName__ account.", "learn_more_about_compile_timeouts": "<0>Learn more about compile timeouts.", "learn_more_about_emails": "<0>Learn more about managing your __appName__ emails.", "learn_more_about_link_sharing": "Learn more about Link Sharing", "learn_more_about_managed_users": "Learn more about Managed Users.", - "learn_more_about_managing_email": "Learn more about managing your __appName__ emails.", "learn_more_about_other_causes_of_compile_timeouts": "<0>Learn more about other causes of compile timeouts and how to fix them.", "leave": "Leave", "leave_any_group_subscriptions": "Leave any group subscriptions other than the one that will be managing your account. <0>Leave them from the Subscription page.", @@ -1511,7 +1511,6 @@ "other_output_files": "Download other output files", "other_sessions": "Other Sessions", "other_ways_to_log_in": "Other ways to log in", - "our_help_page": "Our help page", "our_team_will_get_back_to_you_shortly": "Our team will get back to you shortly.", "our_values": "Our values", "out_of_sync": "Out of sync", diff --git a/services/web/test/frontend/features/settings/components/emails/emails-section.test.tsx b/services/web/test/frontend/features/settings/components/emails/emails-section.test.tsx index 13d3a0f9ff..243d85533a 100644 --- a/services/web/test/frontend/features/settings/components/emails/emails-section.test.tsx +++ b/services/web/test/frontend/features/settings/components/emails/emails-section.test.tsx @@ -40,7 +40,9 @@ describe('', function () { screen.getByText(/add additional email addresses/i) screen.getByText(/to change your primary email/i) - screen.getByLabelText('Learn more about managing your Overleaf emails.') + screen.getByRole('link', { + name: /learn more about managing your Overleaf emails/i, + }) }) it('renders a loading message when loading', async function () { From 3ef62472bdd89e3b5473de7797320071ff95b535 Mon Sep 17 00:00:00 2001 From: Rebeka Dekany <50901361+rebekadekany@users.noreply.github.com> Date: Thu, 15 May 2025 17:08:38 +0200 Subject: [PATCH 124/194] Fix back to your projects button (#25500) GitOrigin-RevId: 98f84593bec190b24a225d4690414e4e9f1141e0 --- .../subscription/components/canceled-subscription/canceled.tsx | 2 +- .../successful-subscription/successful-subscription.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/services/web/frontend/js/features/subscription/components/canceled-subscription/canceled.tsx b/services/web/frontend/js/features/subscription/components/canceled-subscription/canceled.tsx index 60551a4970..aed8bc27db 100644 --- a/services/web/frontend/js/features/subscription/components/canceled-subscription/canceled.tsx +++ b/services/web/frontend/js/features/subscription/components/canceled-subscription/canceled.tsx @@ -32,7 +32,7 @@ function Canceled() { href="/project" rel="noopener noreferrer" > - < {t('back_to_your_projects')} + {t('back_to_your_projects')}

    diff --git a/services/web/frontend/js/features/subscription/components/successful-subscription/successful-subscription.tsx b/services/web/frontend/js/features/subscription/components/successful-subscription/successful-subscription.tsx index ed80fd33b8..68d57f57fb 100644 --- a/services/web/frontend/js/features/subscription/components/successful-subscription/successful-subscription.tsx +++ b/services/web/frontend/js/features/subscription/components/successful-subscription/successful-subscription.tsx @@ -114,7 +114,7 @@ function SuccessfulSubscription() { href={postCheckoutRedirect || '/project'} rel="noopener noreferrer" > - < {t('back_to_your_projects')} + {t('back_to_your_projects')}

    From bb66f7502766b37146da6f5133e8eb2aad5d2136 Mon Sep 17 00:00:00 2001 From: Rebeka Dekany <50901361+rebekadekany@users.noreply.github.com> Date: Thu, 15 May 2025 17:08:47 +0200 Subject: [PATCH 125/194] Add aria-label to the documentation searchbar and update the type to search (#25431) GitOrigin-RevId: 782eb2238636040fedae628b17224f2e91159a34 --- services/web/app/views/_mixins/faq_search-marketing.pug | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/web/app/views/_mixins/faq_search-marketing.pug b/services/web/app/views/_mixins/faq_search-marketing.pug index 8ec136e08e..aa41d00f9b 100644 --- a/services/web/app/views/_mixins/faq_search-marketing.pug +++ b/services/web/app/views/_mixins/faq_search-marketing.pug @@ -6,7 +6,7 @@ mixin faq_search-marketing(headerText, headerClass) form.project-search.form-horizontal(role="search" data-ol-faq-search) .form-group.has-feedback.has-feedback-left .col-sm-12 - input.form-control(type='text', placeholder="Search help library…") + input.form-control(type='search', placeholder="Search help library…" aria-label="Search help library…") i.fa.fa-search.form-control-feedback-left(aria-hidden="true") i.fa.fa-times.form-control-feedback( style="cursor: pointer;", From 75ce58d0c6fe181a8d36a5527e22e2670e399133 Mon Sep 17 00:00:00 2001 From: M Fahru Date: Thu, 15 May 2025 08:10:53 -0700 Subject: [PATCH 126/194] Merge pull request #25404 from overleaf/mf-send-subscription-analytics-events [web] Send analytics events and user properties when user start subscription using Stripe GitOrigin-RevId: 9028397a5cb256df506e14beb1705191c9ae3f7f --- .../app/src/Features/Subscription/RecurlyEventHandler.js | 9 +++++++++ .../unit/src/Subscription/RecurlyEventHandlerTests.js | 8 ++++++++ services/web/types/stripe/webhook-event.ts | 5 +++-- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/services/web/app/src/Features/Subscription/RecurlyEventHandler.js b/services/web/app/src/Features/Subscription/RecurlyEventHandler.js index d97d57ecba..e0d2531239 100644 --- a/services/web/app/src/Features/Subscription/RecurlyEventHandler.js +++ b/services/web/app/src/Features/Subscription/RecurlyEventHandler.js @@ -66,6 +66,7 @@ async function _sendSubscriptionResumedEvent(userId, eventData) { { plan_code: planCode, subscriptionId, + payment_provider: 'recurly', } ) AnalyticsManager.setUserPropertyForUserInBackground( @@ -87,6 +88,7 @@ async function _sendSubscriptionPausedEvent(userId, eventData) { pause_length: pauseLength, plan_code: planCode, subscriptionId, + payment_provider: 'recurly', } ) AnalyticsManager.setUserPropertyForUserInBackground( @@ -108,6 +110,7 @@ async function _sendSubscriptionStartedEvent(userId, eventData) { is_trial: isTrial, has_ai_add_on: hasAiAddOn, subscriptionId, + payment_provider: 'recurly', } ) AnalyticsManager.setUserPropertyForUserInBackground( @@ -154,6 +157,7 @@ async function _sendSubscriptionUpdatedEvent(userId, eventData) { is_trial: isTrial, has_ai_add_on: hasAiAddOn, subscriptionId, + payment_provider: 'recurly', } ) AnalyticsManager.setUserPropertyForUserInBackground( @@ -185,6 +189,7 @@ async function _sendSubscriptionCancelledEvent(userId, eventData) { is_trial: isTrial, has_ai_add_on: hasAiAddOn, subscriptionId, + payment_provider: 'recurly', } ) AnalyticsManager.setUserPropertyForUserInBackground( @@ -211,6 +216,7 @@ async function _sendSubscriptionExpiredEvent(userId, eventData) { is_trial: isTrial, has_ai_add_on: hasAiAddOn, subscriptionId, + payment_provider: 'recurly', } ) AnalyticsManager.setUserPropertyForUserInBackground( @@ -242,6 +248,7 @@ async function _sendSubscriptionRenewedEvent(userId, eventData) { is_trial: isTrial, has_ai_add_on: hasAiAddOn, subscriptionId, + payment_provider: 'recurly', } ) AnalyticsManager.setUserPropertyForUserInBackground( @@ -272,6 +279,7 @@ async function _sendSubscriptionReactivatedEvent(userId, eventData) { quantity, has_ai_add_on: hasAiAddOn, subscriptionId, + payment_provider: 'recurly', } ) AnalyticsManager.setUserPropertyForUserInBackground( @@ -318,6 +326,7 @@ async function _sendInvoicePaidEvent(userId, eventData) { taxInCents, country, collectionMethod, + payment_provider: 'recurly', ...subscriptionIds, } ) diff --git a/services/web/test/unit/src/Subscription/RecurlyEventHandlerTests.js b/services/web/test/unit/src/Subscription/RecurlyEventHandlerTests.js index 4928f81e0d..2528f0a451 100644 --- a/services/web/test/unit/src/Subscription/RecurlyEventHandlerTests.js +++ b/services/web/test/unit/src/Subscription/RecurlyEventHandlerTests.js @@ -62,6 +62,7 @@ describe('RecurlyEventHandler', function () { is_trial: true, has_ai_add_on: false, subscriptionId: this.eventData.subscription.uuid, + payment_provider: 'recurly', } ) sinon.assert.calledWith( @@ -116,6 +117,7 @@ describe('RecurlyEventHandler', function () { is_trial: false, has_ai_add_on: false, subscriptionId: this.eventData.subscription.uuid, + payment_provider: 'recurly', } ) sinon.assert.calledWith( @@ -149,6 +151,7 @@ describe('RecurlyEventHandler', function () { is_trial: true, has_ai_add_on: false, subscriptionId: this.eventData.subscription.uuid, + payment_provider: 'recurly', } ) sinon.assert.calledWith( @@ -187,6 +190,7 @@ describe('RecurlyEventHandler', function () { is_trial: true, has_ai_add_on: false, subscriptionId: this.eventData.subscription.uuid, + payment_provider: 'recurly', } ) sinon.assert.calledWith( @@ -219,6 +223,7 @@ describe('RecurlyEventHandler', function () { is_trial: true, has_ai_add_on: false, subscriptionId: this.eventData.subscription.uuid, + payment_provider: 'recurly', } ) sinon.assert.calledWith( @@ -256,6 +261,7 @@ describe('RecurlyEventHandler', function () { is_trial: true, has_ai_add_on: false, subscriptionId: this.eventData.subscription.uuid, + payment_provider: 'recurly', } ) }) @@ -274,6 +280,7 @@ describe('RecurlyEventHandler', function () { quantity: 1, has_ai_add_on: false, subscriptionId: this.eventData.subscription.uuid, + payment_provider: 'recurly', } ) }) @@ -313,6 +320,7 @@ describe('RecurlyEventHandler', function () { collectionMethod: invoice.collection_method, subscriptionId1: invoice.subscription_ids[0], subscriptionId2: invoice.subscription_ids[1], + payment_provider: 'recurly', } ) }) diff --git a/services/web/types/stripe/webhook-event.ts b/services/web/types/stripe/webhook-event.ts index a53c03f440..c15f53feea 100644 --- a/services/web/types/stripe/webhook-event.ts +++ b/services/web/types/stripe/webhook-event.ts @@ -1,3 +1,5 @@ +import Stripe from 'stripe' + type StripeWebhookEventType = | 'customer.subscription.created' | 'customer.subscription.updated' @@ -6,8 +8,7 @@ type StripeWebhookEventType = export type CustomerSubscriptionWebhookEvent = { type: StripeWebhookEventType data: { - object: { - id: string + object: Stripe.Subscription & { metadata: { adminUserId?: string } From 8017918063ff515059b015263d2ad30ed39ea220 Mon Sep 17 00:00:00 2001 From: M Fahru Date: Thu, 15 May 2025 08:11:51 -0700 Subject: [PATCH 127/194] Merge pull request #25490 from overleaf/mf-send-subscription-cancelled-and-reactivated-event-stripe-subscription [web] Send analytics events and user properties for cancelled and reactivated event in Stripe subscription GitOrigin-RevId: 07a4e6395be334c90910b5d421624c4daa703d3b --- services/web/types/stripe/webhook-event.ts | 38 ++++++++++++++++++---- 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/services/web/types/stripe/webhook-event.ts b/services/web/types/stripe/webhook-event.ts index c15f53feea..72264abe46 100644 --- a/services/web/types/stripe/webhook-event.ts +++ b/services/web/types/stripe/webhook-event.ts @@ -1,12 +1,22 @@ import Stripe from 'stripe' -type StripeWebhookEventType = - | 'customer.subscription.created' - | 'customer.subscription.updated' - | 'customer.subscription.deleted' +export type CustomerSubscriptionUpdatedWebhookEvent = { + type: 'customer.subscription.updated' + data: { + object: Stripe.Subscription & { + metadata: { + adminUserId?: string + } + } + // https://docs.stripe.com/api/events/object?api-version=2025-04-30.basil#event_object-data-previous_attributes + previous_attributes: { + cancel_at_period_end?: boolean // will only be present if the subscription was cancelled or reactivated + } + } +} -export type CustomerSubscriptionWebhookEvent = { - type: StripeWebhookEventType +export type CustomerSubscriptionCreatedWebhookEvent = { + type: 'customer.subscription.created' data: { object: Stripe.Subscription & { metadata: { @@ -15,3 +25,19 @@ export type CustomerSubscriptionWebhookEvent = { } } } + +export type CustomerSubscriptionsDeletedWebhookEvent = { + type: 'customer.subscription.deleted' + data: { + object: Stripe.Subscription & { + metadata: { + adminUserId?: string + } + } + } +} + +export type CustomerSubscriptionWebhookEvent = + | CustomerSubscriptionUpdatedWebhookEvent + | CustomerSubscriptionCreatedWebhookEvent + | CustomerSubscriptionsDeletedWebhookEvent From 81dd3c10a78099ec7db3c5717a1384aba9f59f89 Mon Sep 17 00:00:00 2001 From: M Fahru Date: Thu, 15 May 2025 08:12:19 -0700 Subject: [PATCH 128/194] Merge pull request #25497 from overleaf/mf-send-subscription-renewed-event-stripe [web] Send analytics events and user properties when user's subscription is renewed with the same plan GitOrigin-RevId: c21436d942e8b1a2b8c9fca5827826bf0e8b8bdb --- services/web/types/stripe/webhook-event.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/services/web/types/stripe/webhook-event.ts b/services/web/types/stripe/webhook-event.ts index 72264abe46..9c92fdc7ff 100644 --- a/services/web/types/stripe/webhook-event.ts +++ b/services/web/types/stripe/webhook-event.ts @@ -11,6 +11,17 @@ export type CustomerSubscriptionUpdatedWebhookEvent = { // https://docs.stripe.com/api/events/object?api-version=2025-04-30.basil#event_object-data-previous_attributes previous_attributes: { cancel_at_period_end?: boolean // will only be present if the subscription was cancelled or reactivated + items?: { + // will be present if the subscription was downgraded, upgraded, or renewed + data: [ + { + price: { + id: string + } + quantity: number + }, + ] + } } } } From 1440a47d5357388daca4ea6843971e5c8aa2dc6f Mon Sep 17 00:00:00 2001 From: M Fahru Date: Thu, 15 May 2025 08:13:06 -0700 Subject: [PATCH 129/194] Merge pull request #25560 from overleaf/mf-stripe-analytics-invoice-event [web] Handle stripe analytics for the `subscription-invoice-collected` event GitOrigin-RevId: cba56db820cac92a66307a05350c779e1198cbf3 --- services/web/types/stripe/webhook-event.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/services/web/types/stripe/webhook-event.ts b/services/web/types/stripe/webhook-event.ts index 9c92fdc7ff..f6e36b8b0a 100644 --- a/services/web/types/stripe/webhook-event.ts +++ b/services/web/types/stripe/webhook-event.ts @@ -48,7 +48,18 @@ export type CustomerSubscriptionsDeletedWebhookEvent = { } } +export type InvoicePaidWebhookEvent = { + type: 'invoice.paid' + data: { + object: Stripe.Invoice + } +} + export type CustomerSubscriptionWebhookEvent = | CustomerSubscriptionUpdatedWebhookEvent | CustomerSubscriptionCreatedWebhookEvent | CustomerSubscriptionsDeletedWebhookEvent + +export type WebhookEvent = + | CustomerSubscriptionWebhookEvent + | InvoicePaidWebhookEvent From efa39ee664626b1320da8f306a3568303e93008a Mon Sep 17 00:00:00 2001 From: CloudBuild Date: Fri, 16 May 2025 01:05:49 +0000 Subject: [PATCH 130/194] auto update translation GitOrigin-RevId: aaa30f0991b037c3829cebc655e1cd9086b6cba5 --- services/web/locales/zh-CN.json | 1 - 1 file changed, 1 deletion(-) diff --git a/services/web/locales/zh-CN.json b/services/web/locales/zh-CN.json index 2efb3819e0..aeb10a6143 100644 --- a/services/web/locales/zh-CN.json +++ b/services/web/locales/zh-CN.json @@ -811,7 +811,6 @@ "get_in_touch": "联系", "get_in_touch_having_problems": "如果遇到问题,请与支持部门联系", "get_involved": "加入我们", - "get_most_subscription_discover_premium_features": "充分利用您的 __appName__ 订阅。<0>探索高级功能。", "get_real_time_track_changes": "获取实时跟踪更改", "get_the_best_overleaf_experience": "获取最佳的 Overleaf 体验", "get_the_most_out_headline": "通过以下功能充分利用__appName__:", From bc78432e620b8d625ae95da972d02a1c00896e9d Mon Sep 17 00:00:00 2001 From: Jakob Ackermann Date: Fri, 16 May 2025 09:00:48 +0100 Subject: [PATCH 131/194] [web] wait for prefetching of projects listing (#25650) GitOrigin-RevId: 59fb0c74b8cf6a496e256960f7f2e83ace2c5ee0 --- .../src/Features/Project/ProjectListController.mjs | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/services/web/app/src/Features/Project/ProjectListController.mjs b/services/web/app/src/Features/Project/ProjectListController.mjs index 61131ec617..b3e5e81890 100644 --- a/services/web/app/src/Features/Project/ProjectListController.mjs +++ b/services/web/app/src/Features/Project/ProjectListController.mjs @@ -314,18 +314,9 @@ async function projectListPage(req, res, next) { delete req.session.saml } - function fakeDelay() { - return new Promise(resolve => { - setTimeout(() => resolve(undefined), 0) - }) - } - - const prefetchedProjectsBlob = await Promise.race([ - projectsBlobPending, - fakeDelay(), - ]) + const prefetchedProjectsBlob = await projectsBlobPending Metrics.inc('project-list-prefetch-projects', 1, { - status: prefetchedProjectsBlob ? 'success' : 'too-slow', + status: prefetchedProjectsBlob ? 'success' : 'error', }) // in v2 add notifications for matching university IPs From cb7d75202b8f753553a6a28fa028e3dcadcd8c70 Mon Sep 17 00:00:00 2001 From: Jakob Ackermann Date: Fri, 16 May 2025 09:00:58 +0100 Subject: [PATCH 132/194] [web] fetch users subscriptions once from project dashboard (#25652) * [web] fetch users subscriptions once from project dashboard * [web] fix types GitOrigin-RevId: 18de18f8d4237d97087ef92eaa5052f253a92813 --- .../Project/ProjectListController.mjs | 31 ++++++------ .../SubscriptionViewModelBuilder.js | 13 ++++- .../Project/ProjectListControllerTests.mjs | 48 +++++++++---------- 3 files changed, 49 insertions(+), 43 deletions(-) diff --git a/services/web/app/src/Features/Project/ProjectListController.mjs b/services/web/app/src/Features/Project/ProjectListController.mjs index b3e5e81890..c62396e153 100644 --- a/services/web/app/src/Features/Project/ProjectListController.mjs +++ b/services/web/app/src/Features/Project/ProjectListController.mjs @@ -21,12 +21,10 @@ import { OError, V1ConnectionError } from '../Errors/Errors.js' import { User } from '../../models/User.js' import UserPrimaryEmailCheckHandler from '../User/UserPrimaryEmailCheckHandler.js' import UserController from '../User/UserController.js' -import LimitationsManager from '../Subscription/LimitationsManager.js' import NotificationsBuilder from '../Notifications/NotificationsBuilder.js' import GeoIpLookup from '../../infrastructure/GeoIpLookup.js' import SplitTestHandler from '../SplitTests/SplitTestHandler.js' import SplitTestSessionHandler from '../SplitTests/SplitTestSessionHandler.js' -import SubscriptionLocator from '../Subscription/SubscriptionLocator.js' import TutorialHandler from '../Tutorial/TutorialHandler.js' /** @@ -102,6 +100,8 @@ async function projectListPage(req, res, next) { // - undefined - when there's no "saas" feature or couldn't get subscription data // - object - the subscription data object let usersBestSubscription + let usersIndividualSubscription + let usersGroupSubscriptions = [] let survey let userIsMemberOfGroupSubscription = false let groupSubscriptionsPendingEnrollment = [] @@ -132,10 +132,13 @@ async function projectListPage(req, res, next) { await SplitTestSessionHandler.promises.sessionMaintenance(req, user) try { - usersBestSubscription = - await SubscriptionViewModelBuilder.promises.getBestSubscription({ - _id: userId, - }) + ;({ + bestSubscription: usersBestSubscription, + individualSubscription: usersIndividualSubscription, + memberGroupSubscriptions: usersGroupSubscriptions, + } = await SubscriptionViewModelBuilder.promises.getUsersSubscriptionDetails( + { _id: userId } + )) } catch (error) { logger.err( { err: error, userId }, @@ -143,14 +146,11 @@ async function projectListPage(req, res, next) { ) } try { - const { isMember, subscriptions } = - await LimitationsManager.promises.userIsMemberOfGroupSubscription(user) - - userIsMemberOfGroupSubscription = isMember + userIsMemberOfGroupSubscription = usersGroupSubscriptions?.length > 0 // TODO use helper function if (!user.enrollment?.managedBy) { - groupSubscriptionsPendingEnrollment = subscriptions.filter( + groupSubscriptionsPendingEnrollment = usersGroupSubscriptions.filter( subscription => subscription.groupPlan && subscription.managedUsersEnabled ) @@ -391,13 +391,10 @@ async function projectListPage(req, res, next) { let hasIndividualRecurlySubscription = false try { - const individualSubscription = - await SubscriptionLocator.promises.getUsersSubscription(userId) - hasIndividualRecurlySubscription = - individualSubscription?.groupPlan === false && - individualSubscription?.recurlyStatus?.state !== 'canceled' && - individualSubscription?.recurlySubscription_id !== '' + usersIndividualSubscription?.groupPlan === false && + usersIndividualSubscription?.recurlyStatus?.state !== 'canceled' && + usersIndividualSubscription?.recurlySubscription_id !== '' } catch (error) { logger.error({ err: error }, 'Failed to get individual subscription') } diff --git a/services/web/app/src/Features/Subscription/SubscriptionViewModelBuilder.js b/services/web/app/src/Features/Subscription/SubscriptionViewModelBuilder.js index 180a78f294..441d9c2c9b 100644 --- a/services/web/app/src/Features/Subscription/SubscriptionViewModelBuilder.js +++ b/services/web/app/src/Features/Subscription/SubscriptionViewModelBuilder.js @@ -24,6 +24,7 @@ const Modules = require('../../infrastructure/Modules') /** * @import { Subscription } from "../../../../types/project/dashboard/subscription" + * @import { Subscription as DBSubscription } from "../../models/Subscription" */ function buildHostedLink(type) { @@ -378,6 +379,15 @@ async function buildUsersSubscriptionViewModel(user, locale = 'en') { * @returns {Promise} */ async function getBestSubscription(user) { + const { bestSubscription } = await getUsersSubscriptionDetails(user) + return bestSubscription +} + +/** + * @param {{_id: string}} user + * @returns {Promise<{bestSubscription:Subscription,individualSubscription:DBSubscription|null,memberGroupSubscriptions:DBSubscription[]}>} + */ +async function getUsersSubscriptionDetails(user) { let [ individualSubscription, memberGroupSubscriptions, @@ -464,7 +474,7 @@ async function getBestSubscription(user) { } } } - return bestSubscription + return { bestSubscription, individualSubscription, memberGroupSubscriptions } } function buildPlansList(currentPlan) { @@ -599,5 +609,6 @@ module.exports = { promises: { buildUsersSubscriptionViewModel, getBestSubscription, + getUsersSubscriptionDetails, }, } diff --git a/services/web/test/unit/src/Project/ProjectListControllerTests.mjs b/services/web/test/unit/src/Project/ProjectListControllerTests.mjs index 40c6dfaa54..827d16b737 100644 --- a/services/web/test/unit/src/Project/ProjectListControllerTests.mjs +++ b/services/web/test/unit/src/Project/ProjectListControllerTests.mjs @@ -53,11 +53,6 @@ describe('ProjectListController', function () { this.settings = { siteUrl: 'https://overleaf.com', } - this.LimitationsManager = { - promises: { - userIsMemberOfGroupSubscription: sinon.stub().resolves(false), - }, - } this.TagsHandler = { promises: { getAllTags: sinon.stub().resolves(this.tags), @@ -113,7 +108,11 @@ describe('ProjectListController', function () { } this.SubscriptionViewModelBuilder = { promises: { - getBestSubscription: sinon.stub().resolves({ type: 'free' }), + getUsersSubscriptionDetails: sinon.stub().resolves({ + bestSubscription: { type: 'free' }, + individualSubscription: null, + memberGroupSubscriptions: [], + }), }, } this.SurveyHandler = { @@ -126,11 +125,6 @@ describe('ProjectListController', function () { ipMatcherAffiliation: sinon.stub().returns({ create: sinon.stub() }), }, } - this.SubscriptionLocator = { - promises: { - getUserSubscription: sinon.stub().resolves({}), - }, - } this.GeoIpLookup = { promises: { getCurrencyCode: sinon.stub().resolves({ @@ -161,8 +155,6 @@ describe('ProjectListController', function () { this.SplitTestSessionHandler, '../../../../app/src/Features/User/UserController': this.UserController, '../../../../app/src/Features/Project/ProjectHelper': this.ProjectHelper, - '../../../../app/src/Features/Subscription/LimitationsManager': - this.LimitationsManager, '../../../../app/src/Features/Tags/TagsHandler': this.TagsHandler, '../../../../app/src/Features/Notifications/NotificationsHandler': this.NotificationsHandler, @@ -180,8 +172,6 @@ describe('ProjectListController', function () { this.UserPrimaryEmailCheckHandler, '../../../../app/src/Features/Notifications/NotificationsBuilder': this.NotificationBuilder, - '../../../../app/src/Features/Subscription/SubscriptionLocator': - this.SubscriptionLocator, '../../../../app/src/infrastructure/GeoIpLookup': this.GeoIpLookup, '../../../../app/src/Features/Tutorial/TutorialHandler': this.TutorialHandler, @@ -345,9 +335,13 @@ describe('ProjectListController', function () { it('should show INR Banner for Indian users with free account', function (done) { // usersBestSubscription is only available when saas feature is present this.Features.hasFeature.withArgs('saas').returns(true) - this.SubscriptionViewModelBuilder.promises.getBestSubscription.resolves({ - type: 'free', - }) + this.SubscriptionViewModelBuilder.promises.getUsersSubscriptionDetails.resolves( + { + bestSubscription: { + type: 'free', + }, + } + ) this.GeoIpLookup.promises.getCurrencyCode.resolves({ countryCode: 'IN', }) @@ -361,9 +355,13 @@ describe('ProjectListController', function () { it('should not show INR Banner for Indian users with premium account', function (done) { // usersBestSubscription is only available when saas feature is present this.Features.hasFeature.withArgs('saas').returns(true) - this.SubscriptionViewModelBuilder.promises.getBestSubscription.resolves({ - type: 'individual', - }) + this.SubscriptionViewModelBuilder.promises.getUsersSubscriptionDetails.resolves( + { + bestSubscription: { + type: 'individual', + }, + } + ) this.GeoIpLookup.promises.getCurrencyCode.resolves({ countryCode: 'IN', }) @@ -616,8 +614,8 @@ describe('ProjectListController', function () { describe('enterprise banner', function () { beforeEach(function (done) { this.Features.hasFeature.withArgs('saas').returns(true) - this.LimitationsManager.promises.userIsMemberOfGroupSubscription.resolves( - { isMember: false } + this.SubscriptionViewModelBuilder.promises.getUsersSubscriptionDetails.resolves( + { memberGroupSubscriptions: [] } ) this.UserGetter.promises.getUserFullEmails.resolves([ { @@ -660,8 +658,8 @@ describe('ProjectListController', function () { }) it('does not show banner if user is part of any group subscription', function () { - this.LimitationsManager.promises.userIsMemberOfGroupSubscription.resolves( - { isMember: true } + this.SubscriptionViewModelBuilder.promises.getUsersSubscriptionDetails.resolves( + { memberGroupSubscriptions: [{}] } ) this.res.render = (pageName, opts) => { From 14cbd44d9b46a839a845c01022cd885f55f67531 Mon Sep 17 00:00:00 2001 From: Jakob Ackermann Date: Fri, 16 May 2025 09:01:08 +0100 Subject: [PATCH 133/194] [web] deletedDocs are not needed for joinProject anymore (#25654) * [web] deletedDocs are not needed for joinProject anymore * [web] cleanup unit tests GitOrigin-RevId: 91c9bc60ec776757b3031cbc85c67ae1bf4adf4d --- .../Features/Editor/EditorHttpController.js | 19 +------ .../Features/Project/ProjectEditorHandler.js | 18 +------ .../src/EditorHttpControllerTests.mjs | 33 +----------- .../src/Project/ProjectEditorHandlerTests.js | 51 +------------------ 4 files changed, 5 insertions(+), 116 deletions(-) diff --git a/services/web/app/src/Features/Editor/EditorHttpController.js b/services/web/app/src/Features/Editor/EditorHttpController.js index 45c9b2427a..8128a95b26 100644 --- a/services/web/app/src/Features/Editor/EditorHttpController.js +++ b/services/web/app/src/Features/Editor/EditorHttpController.js @@ -10,8 +10,6 @@ const CollaboratorsHandler = require('../Collaborators/CollaboratorsHandler') const PrivilegeLevels = require('../Authorization/PrivilegeLevels') const SessionManager = require('../Authentication/SessionManager') const Errors = require('../Errors/Errors') -const DocstoreManager = require('../Docstore/DocstoreManager') -const logger = require('@overleaf/logger') const { expressify } = require('@overleaf/promise-utils') const Settings = require('@overleaf/settings') @@ -77,20 +75,6 @@ async function _buildJoinProjectView(req, projectId, userId) { if (project == null) { throw new Errors.NotFoundError('project not found') } - let deletedDocsFromDocstore = [] - try { - deletedDocsFromDocstore = - await DocstoreManager.promises.getAllDeletedDocs(projectId) - } catch (err) { - // The query in docstore is not optimized at this time and fails for - // projects with many very large, deleted documents. - // Not serving the user with deletedDocs from docstore may cause a minor - // UI issue with deleted files that are no longer available for restore. - logger.warn( - { err, projectId }, - 'soft-failure when fetching deletedDocs from docstore' - ) - } const members = await CollaboratorsGetter.promises.getInvitedMembersWithPrivilegeLevels( projectId @@ -126,8 +110,7 @@ async function _buildJoinProjectView(req, projectId, userId) { project: ProjectEditorHandler.buildProjectModelView( project, members, - invites, - deletedDocsFromDocstore + invites ), privilegeLevel, isTokenMember, diff --git a/services/web/app/src/Features/Project/ProjectEditorHandler.js b/services/web/app/src/Features/Project/ProjectEditorHandler.js index a85e8b5764..05e5beba09 100644 --- a/services/web/app/src/Features/Project/ProjectEditorHandler.js +++ b/services/web/app/src/Features/Project/ProjectEditorHandler.js @@ -3,23 +3,11 @@ const _ = require('lodash') const Path = require('path') const Features = require('../../infrastructure/Features') -function mergeDeletedDocs(a, b) { - const docIdsInA = new Set(a.map(doc => doc._id.toString())) - return a.concat(b.filter(doc => !docIdsInA.has(doc._id.toString()))) -} - module.exports = ProjectEditorHandler = { trackChangesAvailable: false, - buildProjectModelView(project, members, invites, deletedDocsFromDocstore) { + buildProjectModelView(project, members, invites) { let owner, ownerFeatures - if (!Array.isArray(project.deletedDocs)) { - project.deletedDocs = [] - } - project.deletedDocs.forEach(doc => { - // The frontend does not use this field. - delete doc.deletedAt - }) const result = { _id: project._id, name: project.name, @@ -32,10 +20,6 @@ module.exports = ProjectEditorHandler = { description: project.description, spellCheckLanguage: project.spellCheckLanguage, deletedByExternalDataSource: project.deletedByExternalDataSource || false, - deletedDocs: mergeDeletedDocs( - project.deletedDocs, - deletedDocsFromDocstore - ), members: [], invites: this.buildInvitesView(invites), imageName: diff --git a/services/web/test/acceptance/src/EditorHttpControllerTests.mjs b/services/web/test/acceptance/src/EditorHttpControllerTests.mjs index de38f79be4..6dfdcf0858 100644 --- a/services/web/test/acceptance/src/EditorHttpControllerTests.mjs +++ b/services/web/test/acceptance/src/EditorHttpControllerTests.mjs @@ -14,43 +14,14 @@ describe('EditorHttpController', function () { done() }) }) - beforeEach('create doc', function (done) { - this.user.createDocInProject( - this.projectId, - null, - 'potato.tex', - (error, docId) => { - this.docId = docId - done(error) - } - ) - }) - describe('joinProject', function () { - it('should emit an empty deletedDocs array', function (done) { + it('returns project details', function (done) { this.user.joinProject(this.projectId, (error, details) => { if (error) return done(error) - expect(details.project.deletedDocs).to.deep.equal([]) + expect(details.project.name).to.equal(this.projectName) done() }) }) - - describe('after deleting a doc', function () { - beforeEach(function (done) { - this.user.deleteItemInProject(this.projectId, 'doc', this.docId, done) - }) - - it('should include the deleted doc in the deletedDocs array', function (done) { - this.user.joinProject(this.projectId, (error, details) => { - if (error) return done(error) - - expect(details.project.deletedDocs).to.deep.equal([ - { _id: this.docId, name: 'potato.tex' }, - ]) - done() - }) - }) - }) }) }) diff --git a/services/web/test/unit/src/Project/ProjectEditorHandlerTests.js b/services/web/test/unit/src/Project/ProjectEditorHandlerTests.js index 8e78ea5168..0fb5b5fce4 100644 --- a/services/web/test/unit/src/Project/ProjectEditorHandlerTests.js +++ b/services/web/test/unit/src/Project/ProjectEditorHandlerTests.js @@ -42,13 +42,6 @@ describe('ProjectEditorHandler', function () { ], }, ], - deletedDocs: [ - { - _id: 'deleted-doc-id', - name: 'main.tex', - deletedAt: (this.deletedAt = new Date('2017-01-01')), - }, - ], } this.members = [ { @@ -95,9 +88,6 @@ describe('ProjectEditorHandler', function () { token: 'my-secret-token2', }, ] - this.deletedDocsFromDocstore = [ - { _id: 'deleted-doc-id-from-docstore', name: 'docstore.tex' }, - ] this.handler = SandboxedModule.require(modulePath) }) @@ -107,8 +97,7 @@ describe('ProjectEditorHandler', function () { this.result = this.handler.buildProjectModelView( this.project, this.members, - this.invites, - this.deletedDocsFromDocstore + this.invites ) }) @@ -141,18 +130,6 @@ describe('ProjectEditorHandler', function () { this.result.owner.privileges.should.equal('owner') }) - it('should include the deletedDocs', function () { - expect(this.result.deletedDocs).to.exist - this.result.deletedDocs.should.deep.equal([ - { - // omit deletedAt field - _id: this.project.deletedDocs[0]._id, - name: this.project.deletedDocs[0].name, - }, - this.deletedDocsFromDocstore[0], - ]) - }) - it('should gather readOnly_refs and collaberators_refs into a list of members', function () { const findMember = id => { for (const member of this.result.members) { @@ -231,33 +208,12 @@ describe('ProjectEditorHandler', function () { }) }) - describe('when docstore sends a deleted doc that is also present in the project', function () { - beforeEach(function () { - this.deletedDocsFromDocstore.push(this.project.deletedDocs[0]) - this.result = this.handler.buildProjectModelView( - this.project, - this.members, - this.invites, - this.deletedDocsFromDocstore - ) - }) - - it('should not send any duplicate', function () { - expect(this.result.deletedDocs).to.exist - this.result.deletedDocs.should.deep.equal([ - this.project.deletedDocs[0], - this.deletedDocsFromDocstore[0], - ]) - }) - }) - describe('deletedByExternalDataSource', function () { it('should set the deletedByExternalDataSource flag to false when it is not there', function () { delete this.project.deletedByExternalDataSource const result = this.handler.buildProjectModelView( this.project, this.members, - [], [] ) result.deletedByExternalDataSource.should.equal(false) @@ -267,7 +223,6 @@ describe('ProjectEditorHandler', function () { const result = this.handler.buildProjectModelView( this.project, this.members, - [], [] ) result.deletedByExternalDataSource.should.equal(false) @@ -278,7 +233,6 @@ describe('ProjectEditorHandler', function () { const result = this.handler.buildProjectModelView( this.project, this.members, - [], [] ) result.deletedByExternalDataSource.should.equal(true) @@ -296,7 +250,6 @@ describe('ProjectEditorHandler', function () { this.result = this.handler.buildProjectModelView( this.project, this.members, - [], [] ) }) @@ -326,7 +279,6 @@ describe('ProjectEditorHandler', function () { this.result = this.handler.buildProjectModelView( this.project, this.members, - [], [] ) }) @@ -351,7 +303,6 @@ describe('ProjectEditorHandler', function () { this.result = this.handler.buildProjectModelView( this.project, this.members, - [], [] ) }) From e3e8d944b20231ab20ec96de1ed9ca156187ccf8 Mon Sep 17 00:00:00 2001 From: Jakob Ackermann Date: Fri, 16 May 2025 09:04:09 +0100 Subject: [PATCH 134/194] [web] fetch project members in a single db query (#25662) * [web] fetch project members in a single db query GitOrigin-RevId: ca749327d4783c67a3ad81f611cd7d3e7fa84028 --- .../Collaborators/CollaboratorsGetter.js | 58 +++++++++---------- .../Collaborators/CollaboratorsGetterTests.js | 37 +++++------- 2 files changed, 41 insertions(+), 54 deletions(-) diff --git a/services/web/app/src/Features/Collaborators/CollaboratorsGetter.js b/services/web/app/src/Features/Collaborators/CollaboratorsGetter.js index 77fb7ab2d3..caa6ef159d 100644 --- a/services/web/app/src/Features/Collaborators/CollaboratorsGetter.js +++ b/services/web/app/src/Features/Collaborators/CollaboratorsGetter.js @@ -385,34 +385,32 @@ function _getMemberIdsWithPrivilegeLevelsFromFields( } async function _loadMembers(members) { - const limit = pLimit(3) - const results = await Promise.all( - members.map(member => - limit(async () => { - const user = await UserGetter.promises.getUser(member.id, { - _id: 1, - email: 1, - features: 1, - first_name: 1, - last_name: 1, - signUpDate: 1, - }) - if (user != null) { - const record = { - user, - privilegeLevel: member.privilegeLevel, - } - if (member.pendingEditor) { - record.pendingEditor = true - } else if (member.pendingReviewer) { - record.pendingReviewer = true - } - return record - } else { - return null - } - }) - ) - ) - return results.filter(r => r != null) + const userIds = Array.from(new Set(members.map(m => m.id))) + const users = new Map() + for (const user of await UserGetter.promises.getUsers(userIds, { + _id: 1, + email: 1, + features: 1, + first_name: 1, + last_name: 1, + signUpDate: 1, + })) { + users.set(user._id.toString(), user) + } + return members + .map(member => { + const user = users.get(member.id) + if (!user) return null + const record = { + user, + privilegeLevel: member.privilegeLevel, + } + if (member.pendingEditor) { + record.pendingEditor = true + } else if (member.pendingReviewer) { + record.pendingReviewer = true + } + return record + }) + .filter(r => r != null) } diff --git a/services/web/test/unit/src/Collaborators/CollaboratorsGetterTests.js b/services/web/test/unit/src/Collaborators/CollaboratorsGetterTests.js index 7bfbc1c423..dda99e04f3 100644 --- a/services/web/test/unit/src/Collaborators/CollaboratorsGetterTests.js +++ b/services/web/test/unit/src/Collaborators/CollaboratorsGetterTests.js @@ -52,6 +52,7 @@ describe('CollaboratorsGetter', function () { this.UserGetter = { promises: { getUser: sinon.stub().resolves(null), + getUsers: sinon.stub().resolves([]), }, } this.ProjectMock = sinon.mock(Project) @@ -205,21 +206,13 @@ describe('CollaboratorsGetter', function () { describe('getInvitedMembersWithPrivilegeLevels', function () { beforeEach(function () { - this.UserGetter.promises.getUser - .withArgs(this.readOnlyRef1.toString()) - .resolves({ _id: this.readOnlyRef1 }) - this.UserGetter.promises.getUser - .withArgs(this.readOnlyTokenRef.toString()) - .resolves({ _id: this.readOnlyTokenRef }) - this.UserGetter.promises.getUser - .withArgs(this.readWriteRef2.toString()) - .resolves({ _id: this.readWriteRef2 }) - this.UserGetter.promises.getUser - .withArgs(this.readWriteTokenRef.toString()) - .resolves({ _id: this.readWriteTokenRef }) - this.UserGetter.promises.getUser - .withArgs(this.reviewer1Ref.toString()) - .resolves({ _id: this.reviewer1Ref }) + this.UserGetter.promises.getUsers.resolves([ + { _id: this.readOnlyRef1 }, + { _id: this.readOnlyTokenRef }, + { _id: this.readWriteRef2 }, + { _id: this.readWriteTokenRef }, + { _id: this.reviewer1Ref }, + ]) }) it('should return an array of invited members with their privilege levels', async function () { @@ -416,15 +409,11 @@ describe('CollaboratorsGetter', function () { { _id: this.reviewUser._id, email: this.reviewUser.email }, ], } - this.UserGetter.promises.getUser - .withArgs(this.owningUser._id.toString()) - .resolves(this.owningUser) - this.UserGetter.promises.getUser - .withArgs(this.readWriteUser._id.toString()) - .resolves(this.readWriteUser) - this.UserGetter.promises.getUser - .withArgs(this.reviewUser._id.toString()) - .resolves(this.reviewUser) + this.UserGetter.promises.getUsers.resolves([ + this.owningUser, + this.readWriteUser, + this.reviewUser, + ]) this.ProjectEditorHandler.buildOwnerAndMembersViews.returns(this.views) this.result = await this.CollaboratorsGetter.promises.getAllInvitedMembers( From daa52d62fa674b9042793e11f1659213ae20260a Mon Sep 17 00:00:00 2001 From: Andrew Rumble Date: Wed, 14 May 2025 12:59:59 +0100 Subject: [PATCH 135/194] Allow an empty origin request in real-time This will only happen with a same-origin request (or if someone has tampered with the request - in which case they could set anything). Co-authored-by: Tim Down <158919+timdown@users.noreply.github.com> GitOrigin-RevId: 9dfe49f974a476bfe215768d3984dd60a381d37a --- package-lock.json | 2 +- services/real-time/app.js | 5 +++++ services/real-time/package.json | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index c94500986d..e4f8e9e702 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44351,7 +44351,7 @@ "lodash": "^4.17.21", "proxy-addr": "^2.0.7", "request": "^2.88.2", - "socket.io": "github:overleaf/socket.io#0.9.19-overleaf-11", + "socket.io": "github:overleaf/socket.io#0.9.19-overleaf-12", "socket.io-client": "github:overleaf/socket.io-client#0.9.17-overleaf-5" }, "devDependencies": { diff --git a/services/real-time/app.js b/services/real-time/app.js index 38cb3caec4..4b8e894e8e 100644 --- a/services/real-time/app.js +++ b/services/real-time/app.js @@ -91,6 +91,11 @@ io.configure(function () { ) io.set('origins', function (origin, req) { + if (!origin) { + // There is no origin or referer header - this is likely a same-site request. + logger.warn({ req }, 'No origin or referer header') + return true + } const normalizedOrigin = URL.parse(origin).origin const originIsValid = allowedCorsOriginsRegex.test(normalizedOrigin) diff --git a/services/real-time/package.json b/services/real-time/package.json index 2d5f87a109..a52e0dfcf9 100644 --- a/services/real-time/package.json +++ b/services/real-time/package.json @@ -34,7 +34,7 @@ "lodash": "^4.17.21", "proxy-addr": "^2.0.7", "request": "^2.88.2", - "socket.io": "github:overleaf/socket.io#0.9.19-overleaf-11", + "socket.io": "github:overleaf/socket.io#0.9.19-overleaf-12", "socket.io-client": "github:overleaf/socket.io-client#0.9.17-overleaf-5" }, "devDependencies": { From 0e1379688206af54a55904d1979ee70bb2a34e40 Mon Sep 17 00:00:00 2001 From: Jakob Ackermann Date: Fri, 16 May 2025 09:23:23 +0100 Subject: [PATCH 136/194] [real-time] bail out early upon hitting a redis error (#25614) GitOrigin-RevId: 2563094d1ec8017450fdfdb2b0e77d74bbc825d1 --- services/real-time/app/js/ConnectedUsersManager.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/services/real-time/app/js/ConnectedUsersManager.js b/services/real-time/app/js/ConnectedUsersManager.js index 9b26add4c9..4ce3dcdcad 100644 --- a/services/real-time/app/js/ConnectedUsersManager.js +++ b/services/real-time/app/js/ConnectedUsersManager.js @@ -89,13 +89,14 @@ module.exports = { multi.exec(function (err, res) { if (err) { err = new OError('problem marking user as connected').withCause(err) + return callback(err) } const [, nConnectedClients] = res Metrics.inc('editing_session_mode', 1, { method: cursorData ? 'update' : 'connect', status: nConnectedClients === 1 ? 'single' : 'multi', }) - callback(err) + callback(null) }) }, @@ -136,6 +137,7 @@ module.exports = { multi.exec(function (err, res) { if (err) { err = new OError('problem marking user as disconnected').withCause(err) + return callback(err) } const [, nConnectedClients] = res const status = @@ -183,7 +185,7 @@ module.exports = { } }) } - callback(err) + callback(null) }) }, From 18cd52cfa19e5d43cd705fbb4c55a96013dd4201 Mon Sep 17 00:00:00 2001 From: Jakob Ackermann Date: Fri, 16 May 2025 09:23:33 +0100 Subject: [PATCH 137/194] [real-time] avoid shutting down all pods simultaneously (#25627) GitOrigin-RevId: e416e06588b915548c83d70433f411c9f303ad87 --- services/real-time/app.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/services/real-time/app.js b/services/real-time/app.js index 4b8e894e8e..a66833d9d9 100644 --- a/services/real-time/app.js +++ b/services/real-time/app.js @@ -265,6 +265,7 @@ function drainAndShutdown(signal) { } Settings.shutDownInProgress = false +Settings.shutDownScheduled = false const shutdownDrainTimeWindow = parseInt(Settings.shutdownDrainTimeWindow, 10) if (Settings.shutdownDrainTimeWindow) { logger.info({ shutdownDrainTimeWindow }, 'shutdownDrainTimeWindow enabled') @@ -304,8 +305,16 @@ if (Settings.shutdownDrainTimeWindow) { ) } logger.error({ err: error }, 'uncaught exception') - if (Settings.errors && Settings.errors.shutdownOnUncaughtError) { - drainAndShutdown('SIGABRT') + if ( + Settings.errors?.shutdownOnUncaughtError && + !Settings.shutDownScheduled + ) { + Settings.shutDownScheduled = true + const delay = Math.ceil( + Math.random() * 60 * Math.max(io.sockets.clients().length, 1_000) + ) + logger.info({ delay }, 'delaying shutdown on uncaught error') + setTimeout(() => drainAndShutdown('SIGABRT'), delay) } }) } From 586820c34d3dacf36fbde7cba6ee1f5148f5c7d9 Mon Sep 17 00:00:00 2001 From: Andrew Rumble Date: Fri, 16 May 2025 09:53:27 +0100 Subject: [PATCH 138/194] Fix package version for socket.io GitOrigin-RevId: 07a436ce0db5a0cd320964e3e9e7a5646877561b --- package-lock.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index e4f8e9e702..5b6cb72cc7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37031,8 +37031,8 @@ } }, "node_modules/socket.io": { - "version": "0.9.19-overleaf-11", - "resolved": "git+ssh://git@github.com/overleaf/socket.io.git#5afa587036620afa232d0f7b778ebb1541d7e4d5", + "version": "0.9.19-overleaf-12", + "resolved": "git+ssh://git@github.com/overleaf/socket.io.git#7814a16800a7accb646bd34ed0995cd474fb82f6", "dependencies": { "base64id": "0.1.0", "policyfile": "0.0.4" From 7a0a6077cf4418dd99870906679e1626e2077830 Mon Sep 17 00:00:00 2001 From: M Fahru Date: Fri, 16 May 2025 07:54:34 -0700 Subject: [PATCH 139/194] Merge pull request #25692 from overleaf/mf-add-stickers-cms [web] Add pen-yellow and support-green sticker in contentful GitOrigin-RevId: 26beffd1bf75f9fdf1dba5b8e93b1190a642ff6d --- .../web/public/img/website-redesign/stickers/pen-yellow.svg | 4 ++++ .../public/img/website-redesign/stickers/support-green.svg | 4 ++++ services/web/types/cms.ts | 2 ++ 3 files changed, 10 insertions(+) create mode 100644 services/web/public/img/website-redesign/stickers/pen-yellow.svg create mode 100644 services/web/public/img/website-redesign/stickers/support-green.svg diff --git a/services/web/public/img/website-redesign/stickers/pen-yellow.svg b/services/web/public/img/website-redesign/stickers/pen-yellow.svg new file mode 100644 index 0000000000..ecd90f914c --- /dev/null +++ b/services/web/public/img/website-redesign/stickers/pen-yellow.svg @@ -0,0 +1,4 @@ + + + + diff --git a/services/web/public/img/website-redesign/stickers/support-green.svg b/services/web/public/img/website-redesign/stickers/support-green.svg new file mode 100644 index 0000000000..29c97bded6 --- /dev/null +++ b/services/web/public/img/website-redesign/stickers/support-green.svg @@ -0,0 +1,4 @@ + + + + diff --git a/services/web/types/cms.ts b/services/web/types/cms.ts index 6930603c83..48e94d2a81 100644 --- a/services/web/types/cms.ts +++ b/services/web/types/cms.ts @@ -17,9 +17,11 @@ export type IconElementSticker = | 'sticker | overleaf | green | medium' | 'sticker | pen | purple' | 'sticker | pen | tangerine' + | 'sticker | pen | yellow' | 'sticker | pi | tangerine' | 'sticker | rocket | yellow' | 'sticker | rocket | yellow | medium' + | 'sticker | support | green' | 'sticker | support | tangerine' | 'sticker | support | yellow' | 'sticker | waving-hand | purple | medium' From c4c8f521ff96c08e5feed0c95462f12df9c6a864 Mon Sep 17 00:00:00 2001 From: CloudBuild Date: Sat, 17 May 2025 01:22:11 +0000 Subject: [PATCH 140/194] auto update translation GitOrigin-RevId: 07588ad1e4cbc9028cf0de125b4c6c222e1b0ecc --- services/web/locales/da.json | 16 ---------------- services/web/locales/de.json | 9 --------- services/web/locales/es.json | 2 -- services/web/locales/fr.json | 9 --------- services/web/locales/ko.json | 9 --------- services/web/locales/nl.json | 7 ------- services/web/locales/pt.json | 9 --------- services/web/locales/sv.json | 9 --------- services/web/locales/zh-CN.json | 17 ----------------- 9 files changed, 87 deletions(-) diff --git a/services/web/locales/da.json b/services/web/locales/da.json index 6746c028b7..cc7ba02521 100644 --- a/services/web/locales/da.json +++ b/services/web/locales/da.json @@ -35,7 +35,6 @@ "accept_change_error_description": "Der opstod en fejl under accepten af en ændring. Prøv venligst igen om lidt.", "accept_change_error_title": "Fejl i accept af ændring", "accept_invitation": "Accepter invitation", - "accept_or_reject_each_changes_individually": "Accepter eller afvis hver rettelse individuelt", "accept_terms_and_conditions": "Accepter vilkår og betingelser", "accepted_invite": "Accepteret invitation", "accepting_invite_as": "Du accepterer denne invitation som", @@ -121,7 +120,6 @@ "allows_to_search_by_author_title_etc_possible_to_pull_results_directly_from_your_reference_manager_if_connected": "Tillader søgning på forfatter, title, m.fl. Muligt at hente resultater fra din henvisningsmanager (hvis tilkoblet).", "already_have_an_account": "Har du allerede en konto?", "already_have_sl_account": "Har du allerede en __appName__-konto?", - "already_subscribed_try_refreshing_the_page": "Har du allerede abonneret? Prøv at genindlæse siden.", "also": "Derudover", "alternatively_create_new_institution_account": "Alternativt kan du oprette en ny konto med din institutionelle e-mailaddresse (__email__), ved at klikke __clickText__.", "an_email_has_already_been_sent_to": "En e-mail er allerede blevet sendt til <0>__email__. Vent lidt og prøv igen senere.", @@ -983,10 +981,6 @@ "let_us_know_what_you_think": "Fortæl os hvad du synes", "library": "Bibliotek", "license": "Licens", - "limited_to_n_editors": "Begrænset til __count__ redaktører", - "limited_to_n_editors_per_project": "Begrænset til __count__ redaktører per projekt", - "limited_to_n_editors_per_project_plural": "Begrænset til __count__ redaktører per projekt", - "limited_to_n_editors_plural": "Begrænset til __count__ redaktører", "line_height": "Linjehøjde", "line_width_is_the_width_of_the_line_in_the_current_environment": "Linjebredde er bredden af linjen i det nuværende miljø, f.eks. hele siden i et enkelt-kolonne layout, eller halvdelen af siden i et to-kolonne layout.", "link": "Forbind", @@ -1565,7 +1559,6 @@ "reverse_x_sort_order": "Omvendt __x__ sortering", "revert_pending_plan_change": "Fortryd planlagte abonnementsændring", "review": "Review", - "review_your_peers_work": "Gennemgå dine samarbejdspartneres arbejde", "revoke": "Tilbagekald", "revoke_invite": "Tilbagekald invitation", "right": "Højrejusteret", @@ -1620,7 +1613,6 @@ "searched_path_for_lines_containing": "Ledte i __path__ efter linjer som indeholdte \"__query__\"", "secondary_email_password_reset": "Den e-mailaddresse er registreret som en sekundær e-mailaddresse. Du kan kun logges ind, hvis du skriver din kontos primære e-mailaddresse.", "security": "Sikkerhed", - "see_changes_in_your_documents_live": "Se ændringer i dokumentet live", "select_a_column_or_a_merged_cell_to_align": "Marker en kolonne eller flettet celle for at justere", "select_a_column_to_adjust_column_width": "Marker en kolonne for at justere kolonnebredde", "select_a_file": "Vælg en fil", @@ -1842,8 +1834,6 @@ "tags": "Tags", "take_me_home": "Tag mig hjem!", "take_short_survey": "Besvar et kort spørgeskema", - "tc_everyone": "Alle", - "tc_guests": "Gæster", "template": "Skabelon", "template_approved_by_publisher": "Denne skabelon er blevet godkendt af forlaget", "template_description": "Skabelonsbeskrivelse", @@ -1959,12 +1949,7 @@ "total_with_subtotal_and_tax": "Total: <0>__total__ (__subtotal__ + __tax__ moms) per år", "total_words": "Totalt antal ord", "tr": "Tyrkisk", - "track_any_change_in_real_time": "Følg alle ændringer i realtid", "track_changes": "Følg ændringer", - "track_changes_for_everyone": "Følg ændringer for alle", - "track_changes_for_x": "Følg ændringer for __name__", - "track_changes_is_off": "“Følg ændringer” er slået fra", - "track_changes_is_on": "“Følg ændringer” er slået til", "tracked_change_added": "Tilføjet", "tracked_change_deleted": "Slettet", "transfer_management_of_your_account": "Overdrag styring af din Overleaf konto", @@ -2049,7 +2034,6 @@ "upgrade_for_12x_more_compile_time": "Opgrader for at få 12x mere kompileringstid", "upgrade_now": "Opgrader nu", "upgrade_to_get_feature": "Opgrader for at få __feature__, plus:", - "upgrade_to_track_changes": "Opgrader til “Følg ændringer”", "upload": "Upload", "upload_failed": "Overførsel mislykkedes", "upload_file": "Overfør Fil", diff --git a/services/web/locales/de.json b/services/web/locales/de.json index 51ad4355e5..928c95499e 100644 --- a/services/web/locales/de.json +++ b/services/web/locales/de.json @@ -29,7 +29,6 @@ "abstract": "Abstrakt", "accept": "Akzeptieren", "accept_invitation": "Einladung annehmen", - "accept_or_reject_each_changes_individually": "Akzeptiere oder Verwerfe jede Änderung individuell", "accepted_invite": "Einladung angenommen", "accepting_invite_as": "Du akzeptierst die Einladung als", "access_denied": "Zugriff verweigert", @@ -1074,7 +1073,6 @@ "return_to_login_page": "Zurück zur Login-Seite", "revert_pending_plan_change": "Abonnement-Änderung rückgängig machen", "review": "Überprüfen", - "review_your_peers_work": "Überprüfe die Arbeit deiner Kollegen", "revoke": "Zurückziehen", "revoke_invite": "Einladung zurückziehen", "ro": "Rumänisch", @@ -1101,7 +1099,6 @@ "search_replace_all": "Alles Ersetzen", "secondary_email_password_reset": "Diese E-Mail-Adresse ist als sekundäre E-Mail-Adresse hinterlegt. Bitte gib die primäre E-Mail-Adresse für dein Konto an.", "security": "Sicherheit", - "see_changes_in_your_documents_live": "Verfolge Änderungen in deinen Dokumenten, live", "select_a_file": "Datei auswählen", "select_a_project": "Projekt auswählen", "select_all_projects": "Alle Projekte auswählen", @@ -1196,8 +1193,6 @@ "tags": "Stichworte", "take_me_home": "Bring mich nach Hause!", "take_short_survey": "Nimm an einer kurzen Umfrage teil", - "tc_everyone": "Jeder", - "tc_guests": "Gäste", "template": "Vorlage", "template_approved_by_publisher": "Diese Vorlage wurde vom Verlag genehmigt", "template_description": "Vorlagenbeschreibung", @@ -1260,10 +1255,7 @@ "total_per_year": "Insgesamt pro Jahr", "total_words": "Gesamtwortanzahl", "tr": "Türkisch", - "track_any_change_in_real_time": "Verfolge jegliche Änderung, in Echtzeit", "track_changes": "Änderungen verfolgen", - "track_changes_is_off": "Änderungen verfolgen ist aus", - "track_changes_is_on": "Änderungen verfolgen ist an", "tracked_change_added": "Hinzugefügt", "tracked_change_deleted": "Gelöscht", "trash": "Löschen", @@ -1317,7 +1309,6 @@ "upgrade_cc_btn": "Upgrade jetzt, zahle nach sieben Tagen", "upgrade_now": "Jetzt aktualisieren", "upgrade_to_get_feature": "Upgrade nötig, um __feature__ zu bekommen, sowie zusätzlich:", - "upgrade_to_track_changes": "Upgrade, um Änderungen verfolgen zu können", "upload": "Hochladen", "upload_failed": "Hochladen fehlgeschlagen", "upload_file": "Datei hochladen", diff --git a/services/web/locales/es.json b/services/web/locales/es.json index ec1a9d59bc..0565d9d541 100644 --- a/services/web/locales/es.json +++ b/services/web/locales/es.json @@ -32,7 +32,6 @@ "accept_and_continue": "Aceptar y continuar", "accept_change": "Aceptar cambio", "accept_invitation": "Aceptar invitación", - "accept_or_reject_each_changes_individually": "Aceptar o rechazar cada cambio individualmente", "accept_terms_and_conditions": "Aceptar términos y condiciones", "accepted_invite": "Invitación aceptada", "accepting_invite_as": "Estás aceptando esta invitación como ", @@ -112,7 +111,6 @@ "all_these_experiments_are_available_exclusively": "Todos estos experimentos están disponibles exclusivamente para los miembros del programa Labs. Si te inscribes, puedes elegir qué experimentos quieres probar.", "already_have_an_account": "¿Ya tiene una cuenta?", "already_have_sl_account": "¿Ya tienes una cuenta de __appName__?", - "already_subscribed_try_refreshing_the_page": "¿Ya estás suscrito? Prueba a actualizar la página.", "also": "También", "alternatively_create_new_institution_account": "Alternativamente, puede crear una nueva cuenta con su correo institucional (__email__) haciendo click en __clickText__.", "an_email_has_already_been_sent_to": "Ya se ha enviado un correo electrónico a <0>__email__. Espere e inténtelo de nuevo más tarde.", diff --git a/services/web/locales/fr.json b/services/web/locales/fr.json index 2b006f75ad..b60de8ed5a 100644 --- a/services/web/locales/fr.json +++ b/services/web/locales/fr.json @@ -33,7 +33,6 @@ "accept_change": "Accepter les changements", "accept_change_error_description": "Une erreur s’est produite lors de l’acceptation d’un suivi de modification. Veuillez réessayer dans quelques instants.", "accept_invitation": "Accepter l’invitation", - "accept_or_reject_each_changes_individually": "Acceptez ou rejetez chaque modification individuellement", "accept_terms_and_conditions": "Accepter les termes et conditions", "accepted_invite": "Invitation acceptée", "accepting_invite_as": "Vous allez accepter cette invitation en tant que", @@ -971,7 +970,6 @@ "return_to_login_page": "Retourner à la page de connexion", "revert_pending_plan_change": "Annuler la modification prévue d’offre", "review": "Relire", - "review_your_peers_work": "Relisez le travail de vos pairs", "revoke": "Révoquer", "revoke_invite": "Retirer l’invitation", "ro": "Roumain", @@ -996,7 +994,6 @@ "search_references": "Rechercher les fichiers .bib dans ce projet", "secondary_email_password_reset": "Cette adresse courriel est une adresse secondaire. Veuillez saisir l’adresse principale associée à votre compte.", "security": "Sécurité", - "see_changes_in_your_documents_live": "Observez les modifications dans vos documents, en direct", "select_a_file": "Choisir un fichier", "select_a_project": "Choisir un projet", "select_all_projects": "Tout sélectionner", @@ -1082,8 +1079,6 @@ "tag_name_is_already_used": "L’étiquette \"__tagName__\" existe déjà", "tags": "Étiquettes", "take_me_home": "Retour à la maison !", - "tc_everyone": "Tout le monde", - "tc_guests": "Invités", "template_description": "Description des modèles", "template_gallery": "Galerie de modèles", "template_not_found_description": "Cette méthode de création de projets à partir de modèles n’est plus disponible. Merci de vous rendre sur notre galerie des modèles pour trouver d’autres modèles.", @@ -1140,10 +1135,7 @@ "tooltip_show_pdf": "Cliquez pour afficher le PDF", "total_words": "Total des mots", "tr": "Turque", - "track_any_change_in_real_time": "Suivez toute modification, en temps réel", "track_changes": "Suivre les modifications", - "track_changes_is_off": "Le suivi des modifs. est off", - "track_changes_is_on": "Le suivi des modifs. est on", "tracked_change_added": "Ajout de", "tracked_change_deleted": "Suppression de", "trash": "Corbeille", @@ -1188,7 +1180,6 @@ "upgrade_cc_btn": "Mettez à niveau maintenant, payez dans 7 jours", "upgrade_now": "Mettre à niveau maintenant", "upgrade_to_get_feature": "Mettre à niveau pour profiter de __feature__, plus :", - "upgrade_to_track_changes": "Mettez à niveau pour suivre les modifications", "upload": "Importer", "upload_failed": "Échec du téléversement", "upload_file": "Importer un fichier", diff --git a/services/web/locales/ko.json b/services/web/locales/ko.json index 4f4e26af03..cb320bd996 100644 --- a/services/web/locales/ko.json +++ b/services/web/locales/ko.json @@ -12,7 +12,6 @@ "about_to_delete_projects": "다음과 같은 프로젝트를 삭제하려 합니다:", "about_to_leave_projects": "다음과 같은 프로젝트를 나가려고합니다:", "accept": "승락", - "accept_or_reject_each_changes_individually": "각각의 변경 사항 승락 또는 거절", "accepting_invite_as": "다음 이메일로 온 초대를 승락합니다.", "account": "계정", "account_not_linked_to_dropbox": "계정이 Dropbox에 연결되지 않았습니다", @@ -421,7 +420,6 @@ "restricted_no_permission": "이 페이지를 불러올 권한이 없습니다.", "return_to_login_page": "로그인 페이지로 이동", "review": "검토", - "review_your_peers_work": "동료의 작업 검토", "revoke_invite": "초대 취소", "ro": "로마니아어", "role": "역할", @@ -434,7 +432,6 @@ "search_projects": "프로젝트 검색", "search_references": "이 프로젝트에서 .bib 파일 검색", "security": "보안", - "see_changes_in_your_documents_live": "문서 변경사항 실시간으로 보기", "select_all_projects": "전체 선택", "select_github_repository": "__appName__에 불러올 GitHub 저장소를 선택합니다.", "send": "발신", @@ -483,8 +480,6 @@ "sync_to_github": "GitHub 동기화", "syntax_validation": "코드 확인", "take_me_home": "홈으로!", - "tc_everyone": "모든 사람", - "tc_guests": "게스트", "template_description": "템플릿 설명", "templates": "템플릿", "terminated": "컴파일 취소됨", @@ -513,10 +508,7 @@ "tooltip_show_pdf": "PDF를 보려면 클릭", "total_words": "총 단어 수", "tr": "Türkçe", - "track_any_change_in_real_time": "실시간으로 모든 변경 사항 추적", "track_changes": "변경 내용 추적", - "track_changes_is_off": "변경 내용 추적 꺼짐", - "track_changes_is_on": "변경 내용 추적 사용", "tracked_change_added": "추가됨", "tracked_change_deleted": "삭제됨", "try_it_for_free": "무료로 사용해보세요", @@ -547,7 +539,6 @@ "upgrade_cc_btn": "지금 업그레이드하고 7일 후 지불", "upgrade_now": "지금 업그레이드", "upgrade_to_get_feature": "__feature__와 다음 기능 사용을 위해 업그레이드:", - "upgrade_to_track_changes": "변경 내용 추적을 위해 업그레이드", "upload": "업로드", "upload_file": "파일 업로드", "upload_project": "프로젝트 업로드", diff --git a/services/web/locales/nl.json b/services/web/locales/nl.json index 39ab174b4d..14e0d7b703 100644 --- a/services/web/locales/nl.json +++ b/services/web/locales/nl.json @@ -14,7 +14,6 @@ "about_to_leave_projects": "Je staat op het punt de volgende projecten te verlaten:", "accept": "Accepteer", "accept_invitation": "Accepteer de uitnodiging", - "accept_or_reject_each_changes_individually": "Accepteer of verwerp iedere wijziging individueel", "accepted_invite": "Uitnodiging geaccepteerd", "accepting_invite_as": "U accepteert deze uitnodiging als", "account": "Account", @@ -428,7 +427,6 @@ "restricted_no_permission": "Beperkt, sorry. Je hebt geen toegang tot deze pagina.", "return_to_login_page": "Keer terug naar inlogpagina", "review": "Review", - "review_your_peers_work": "Review werk van uw collega’s", "revoke_invite": "Herroep uitnodiging", "ro": "Roemeens", "role": "Functie", @@ -444,7 +442,6 @@ "search_projects": "Projecten zoeken", "search_references": "Doorzoek het .bib bestand in dit project", "security": "Veiligheid", - "see_changes_in_your_documents_live": "Zie verandering in uw documenten, live", "select_github_repository": "Selecteer een GitHub repository om naar __appName__ te importeren.", "send": "Verstuur", "send_first_message": "Verzend je eerste bericht", @@ -517,9 +514,6 @@ "too_recently_compiled": "Dit project is recentelijk nog gecompileerd, daarom is het nu overgeslagen.", "total_words": "Aantal woorden", "tr": "Turks", - "track_any_change_in_real_time": "Hou alle veranderingen bij, in realtime", - "track_changes_is_off": "Wijzigingen bijhouden staat uit", - "track_changes_is_on": "Wijzigingen bijhouden staat aan", "tracked_change_added": "Toegevoegd", "tracked_change_deleted": "Verwijderd", "try_it_for_free": "Probeer het gratis", @@ -547,7 +541,6 @@ "upgrade_cc_btn": "Upgrade nu, betaal na 7 dagen", "upgrade_now": "Upgrade Nu", "upgrade_to_get_feature": "Upgrade om __feature__ te krijgen, plus:", - "upgrade_to_track_changes": "Upgrade naar Wijzigingen Bijhouden", "upload": "Uploaden", "upload_file": "Bestand Uploaden", "upload_project": "Project Uploaden", diff --git a/services/web/locales/pt.json b/services/web/locales/pt.json index 55272084c5..8d2455c369 100644 --- a/services/web/locales/pt.json +++ b/services/web/locales/pt.json @@ -14,7 +14,6 @@ "about_to_leave_projects": "Você está prestes à deixar de seguir os projetos:", "accept": "Aceitar", "accept_invitation": "Aceitar convite", - "accept_or_reject_each_changes_individually": "Aceitar ou rejeitar cada alteração individualmente", "accepted_invite": "Convite aceito", "accepting_invite_as": "Você está aceitando esse convite como", "account": "Conta", @@ -510,7 +509,6 @@ "restricted_no_permission": "Restrito, desculpe você não tem permissão para carregar essa página.", "return_to_login_page": "Retornar à página de Login", "review": "Revisar", - "review_your_peers_work": "Revisar o trabalho de seus colegas", "revoke_invite": "Revogar Convite", "ro": "Romeno", "role": "Papel", @@ -526,7 +524,6 @@ "search_projects": "Buscar projetos", "search_references": "Buscar os arquivos .bib no projeto", "security": "Segurança", - "see_changes_in_your_documents_live": "Ver alterações nos seus documentos, ao vivo", "select_all_projects": "Selecionar todos", "select_github_repository": "Selecione um repositório no GitHub para importar para o __appName__.", "send": "Enviar", @@ -579,8 +576,6 @@ "sync_to_github": "Sincronizar com GitHub", "syntax_validation": "Checar código", "take_me_home": "Ir para o início!", - "tc_everyone": "Todos", - "tc_guests": "Convidados", "template_description": "Descrição do Modelo", "templates": "Modelos", "terminated": "Compilação cancelada", @@ -615,10 +610,7 @@ "tooltip_show_pdf": "Clique para mostrar o PDF", "total_words": "Total de Palavras", "tr": "Turco", - "track_any_change_in_real_time": "Acompanhar qualquer alteração, em tempo real", "track_changes": "Acompanhe as mudanças", - "track_changes_is_off": "Controle de alterações está desligado", - "track_changes_is_on": "Controle de alterações está ligado", "tracked_change_added": "Adicionado", "tracked_change_deleted": "Deletado", "try_again": "Por favor, tente novamente", @@ -652,7 +644,6 @@ "upgrade_cc_btn": "Aprimorar agora, pague depois de 7 dias", "upgrade_now": "Aprimorar Agora", "upgrade_to_get_feature": "Aprimore para ter __feature__, mais:", - "upgrade_to_track_changes": "Atualizar para acompanhar alterações", "upload": "Carregar", "upload_file": "Atualizar Arquivo", "upload_project": "Carregar Projeto", diff --git a/services/web/locales/sv.json b/services/web/locales/sv.json index 70e35fdc8f..da03604b4b 100644 --- a/services/web/locales/sv.json +++ b/services/web/locales/sv.json @@ -16,7 +16,6 @@ "abstract": "Sammanfattning", "accept": "Acceptera", "accept_invitation": "Acceptera inbjudan", - "accept_or_reject_each_changes_individually": "Acceptera eller neka varje förändring för sig", "accepted_invite": "Accepterat inbjudan", "accepting_invite_as": "Du accepterar inbjudan som", "account": "Konto", @@ -717,7 +716,6 @@ "return_to_login_page": "Tillbaka till inloggningssidan", "reverse_x_sort_order": "Omvänd __x__-sortering", "review": "Granska", - "review_your_peers_work": "Granska dina medarbetares bidrag", "revoke": "Återkalla", "revoke_invite": "Återkalla inbjudan", "ro": "Rumänska", @@ -736,7 +734,6 @@ "search_references": "Sök i .bib filerna för det här projektet", "secondary_email_password_reset": "Denna e-postadress är registrerad som sekundär e-postadress. Vänligen ange primär e-postadress för ditt konto.", "security": "Säkerhet", - "see_changes_in_your_documents_live": "Se ändringar i dina dokument, i realtid", "select_a_project": "Välj ett projekt", "select_all_projects": "Välj alla", "select_an_output_file": "Välj en utdatafil", @@ -812,8 +809,6 @@ "tag_name_cannot_exceed_characters": "Taggnamnet får inte överstiga __maxLength__ tecken.", "take_me_home": "Ta mig härifrån!", "take_short_survey": "Gör en kort enkät", - "tc_everyone": "Alla", - "tc_guests": "Gäster", "template_approved_by_publisher": "Denna mall har godkänts av utgivaren", "template_description": "Mallbeskrivning", "template_gallery": "Mallgalleri", @@ -869,10 +864,7 @@ "total_per_year": "Totalt per år", "total_words": "Totalt antal ord", "tr": "Turkiska", - "track_any_change_in_real_time": "Spåra alla ändringar, i realtid", "track_changes": "Spåra ändringar", - "track_changes_is_off": "Spåra ändringar är av", - "track_changes_is_on": "Spåra ändringar är ", "tracked_change_added": "Tillagd", "tracked_change_deleted": "Raderad", "trash": "Papperskorg", @@ -914,7 +906,6 @@ "upgrade_cc_btn": "Uppgradera nu, betala efter 7 dagar", "upgrade_now": "Uppgradera Nu", "upgrade_to_get_feature": "Uppgradera för att få __feature__, plus:", - "upgrade_to_track_changes": "Uppgradera för att spåra ändringar", "upload": "Ladda upp", "upload_failed": "Uppladdning misslyckades", "upload_file": "Ladda upp fil", diff --git a/services/web/locales/zh-CN.json b/services/web/locales/zh-CN.json index aeb10a6143..96f04dfdc3 100644 --- a/services/web/locales/zh-CN.json +++ b/services/web/locales/zh-CN.json @@ -34,7 +34,6 @@ "about_to_leave_project": "您即将离开此项目", "about_to_leave_projects": "您将离开下面的项目", "about_to_trash_projects": "您将要把以下项目移至回收站:", - "about_writefull": "关于 Writefull", "abstract": "摘要", "accept": "采纳", "accept_and_continue": "接受并继续", @@ -42,7 +41,6 @@ "accept_change_error_description": "接受跟踪更改时出现错误,请稍后重试。", "accept_change_error_title": "接受错误修改", "accept_invitation": "接受邀请", - "accept_or_reject_each_changes_individually": "接受或拒绝修改意见", "accept_or_reject_individual_edits": "接受或拒绝个别修改", "accept_selected_changes": "接受选定修改", "accept_terms_and_conditions": "接受条款和条件", @@ -145,7 +143,6 @@ "already_have_a_papers_account": "现在可以更轻松的在 Overleaf 中管理你的引文和参考书目!已有 Papers 帐户了吗?<0>在此关联你的帐户。", "already_have_an_account": "已经有一个账户啦?", "already_have_sl_account": "已经拥有 __appName__ 账户了吗?", - "already_subscribed_try_refreshing_the_page": "已经订阅啦?请刷新界面哦。", "also": "也", "alternatively_create_new_institution_account": "或者,您可以通过单击__clickText__来使用机构电子邮件(__email__)创建一个新帐户。", "an_email_has_already_been_sent_to": "一封电子邮件已经被发送给<0>__email__。请稍后再尝试。", @@ -1178,10 +1175,6 @@ "limited_document_history": "有限的文档历史记录", "limited_to_n_collaborators_per_project": "每个项目仅限 __count__ 位合作者", "limited_to_n_collaborators_per_project_plural": "每个项目仅限 __count__ 位合作者", - "limited_to_n_editors": "仅限 __count__ 个编辑", - "limited_to_n_editors_per_project": "每个项目仅限 __count__ 个编辑者", - "limited_to_n_editors_per_project_plural": "每个项目最多可有 __count__ 名编辑者", - "limited_to_n_editors_plural": "仅限 __count__ 名编辑者", "line_height": "行高 (编辑器)", "line_width_is_the_width_of_the_line_in_the_current_environment": "行宽是当前环境下行的宽度。例如:单列布局中的全页宽度或两列布局中的半页宽度。", "link": "链接", @@ -1842,7 +1835,6 @@ "revert_pending_plan_change": "撤销计划的套餐更改", "review": "审阅", "review_panel": "审阅面板", - "review_your_peers_work": "同行评议", "reviewer": "审阅者", "reviewer_dropbox_sync_message": "作为审阅者,您可以将当前项目版本同步到 Dropbox,但在 Dropbox 中所做的更改<0>不会同步回 Overleaf。", "reviewing": "审阅", @@ -1907,7 +1899,6 @@ "searched_path_for_lines_containing": "在 __path__ 中搜索包含“__query__”的行", "secondary_email_password_reset": "该电子邮件已注册为辅助电子邮件。请输入您帐户的主要电子邮件。", "security": "安全性", - "see_changes_in_your_documents_live": "实时查看文档修改情况", "see_suggestions_from_collaborators": "查看合作者的建议", "select_a_column_or_a_merged_cell_to_align": "选择要对齐的列或合并的单元格", "select_a_column_to_adjust_column_width": "选择一列来调整列宽", @@ -2165,8 +2156,6 @@ "take_me_home": "我要返回!", "take_short_survey": "做一个简短的调查", "take_survey": "参加调查", - "tc_everyone": "所有人", - "tc_guests": "受邀用户", "tell_the_project_owner_and_ask_them_to_upgrade": "如果您需要更多编译时间,<0>告诉项目所有者并要求他们升级其 Overleaf 计划。", "template": "模版", "template_approved_by_publisher": "该模板已获得发布者批准", @@ -2350,12 +2339,7 @@ "total_with_subtotal_and_tax": "总计:每年 <0> __total__ (__subtotal__ + __tax__税)", "total_words": "总字数", "tr": "土耳其语", - "track_any_change_in_real_time": "实时记录文档的任何修改情况", "track_changes": "修订", - "track_changes_for_everyone": "跟踪每个人的更改", - "track_changes_for_x": "跟踪 __name__ 的更改", - "track_changes_is_off": "修改追踪功能 关闭", - "track_changes_is_on": "修改追踪功能 开启", "tracked_change_added": "已添加", "tracked_change_deleted": "已删除", "transfer_management_of_your_account": "Overleaf 账户的转移管理", @@ -2454,7 +2438,6 @@ "upgrade_to_add_more_collaborators_and_access_collaboration_features": "升级以添加更多合作者并访问协作功能,如跟踪更改和完整的项目历史记录。", "upgrade_to_get_feature": "升级以获得__feature__,以及:", "upgrade_to_review": "升级以获取评论", - "upgrade_to_track_changes": "升级以记录文档修改历史", "upgrade_to_unlock_more_time": "立即升级即可在我们最快的服务器上解锁 12 倍以上的编译时间。", "upgrade_your_subscription": "升级您的订阅", "upload": "上传", From 957462b61c2cce71d89222ac4f94d184f6cf6a67 Mon Sep 17 00:00:00 2001 From: Rebeka Dekany <50901361+rebekadekany@users.noreply.github.com> Date: Mon, 19 May 2025 10:31:34 +0200 Subject: [PATCH 141/194] Update selector to target input[type='search'] (#25712) GitOrigin-RevId: 492bda0eb70dd821dbfa3dbf818cafc1ef8975eb --- services/web/frontend/js/features/faq-search/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/web/frontend/js/features/faq-search/index.js b/services/web/frontend/js/features/faq-search/index.js index bb0b11b7a4..8774ee235a 100644 --- a/services/web/frontend/js/features/faq-search/index.js +++ b/services/web/frontend/js/features/faq-search/index.js @@ -2,7 +2,7 @@ import _ from 'lodash' import { formatWikiHit, searchWiki } from '../algolia-search/search-wiki' function setupSearch(formEl) { - const inputEl = formEl.querySelector('input[type="text"]') + const inputEl = formEl.querySelector('input[type="search"]') const resultsEl = formEl.querySelector('[data-ol-search-results]') const wrapperEl = formEl.querySelector('[data-ol-search-results-wrapper]') const noResultsEl = formEl.querySelector('[data-ol-search-no-results]') From cc3b020d88fa92193c41ec28ca2d789fa579b683 Mon Sep 17 00:00:00 2001 From: Miguel Serrano Date: Mon, 19 May 2025 11:14:50 +0200 Subject: [PATCH 142/194] [CE/SP] `cron` for daily runs of `scripts/flush_all.js` (#25575) * [CE/SP] `cron` for daily runs of `scripts/flush_all.js` GitOrigin-RevId: 9616e99c01491e2a410601f4e33917ed47990b11 --- server-ce/config/crontab-history | 1 + server-ce/cron/project-history-flush-all.sh | 14 ++++++++++++++ 2 files changed, 15 insertions(+) create mode 100755 server-ce/cron/project-history-flush-all.sh diff --git a/server-ce/config/crontab-history b/server-ce/config/crontab-history index cfa12f9fc8..ac6dcc1c7b 100644 --- a/server-ce/config/crontab-history +++ b/server-ce/config/crontab-history @@ -1,3 +1,4 @@ */20 * * * * root /overleaf/cron/project-history-periodic-flush.sh >> /var/log/overleaf/cron-project-history-periodic-flush.log 2>&1 30 * * * * root /overleaf/cron/project-history-retry-soft.sh >> /var/log/overleaf/project-history-retry-soft.log 2>&1 45 * * * * root /overleaf/cron/project-history-retry-hard.sh >> /var/log/overleaf/project-history-retry-hard.log 2>&1 +0 3 * * * root /overleaf/cron/project-history-flush-all.sh >> /var/log/overleaf/project-history-flush-all.log 2>&1 diff --git a/server-ce/cron/project-history-flush-all.sh b/server-ce/cron/project-history-flush-all.sh new file mode 100755 index 0000000000..d8bbb184aa --- /dev/null +++ b/server-ce/cron/project-history-flush-all.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +set -eux + +echo "---------------------------------" +echo "Flush all project-history changes" +echo "---------------------------------" +date + +source /etc/container_environment.sh +source /etc/overleaf/env.sh +cd /overleaf/services/project-history && node scripts/flush_all.js + +echo "Done flushing all project-history changes" From 50a5c7984dd660e3e25a2877aa86319f5c6193fb Mon Sep 17 00:00:00 2001 From: Domagoj Kriskovic Date: Mon, 19 May 2025 11:43:51 +0200 Subject: [PATCH 143/194] Reinitialise Writefull toolbar after buying AI assist (#25596) * Reinit Writefull toolbar after buying AI assist * use refreshSession() * add a timeout * add a second refresh GitOrigin-RevId: a2572d62bce0e344d92696e42d137a0b36574b3b --- .../web/frontend/js/shared/context/types/writefull-instance.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/services/web/frontend/js/shared/context/types/writefull-instance.ts b/services/web/frontend/js/shared/context/types/writefull-instance.ts index 120590e668..18b6d08616 100644 --- a/services/web/frontend/js/shared/context/types/writefull-instance.ts +++ b/services/web/frontend/js/shared/context/types/writefull-instance.ts @@ -42,4 +42,5 @@ export interface WritefullAPI { ): void openTableGenerator(): void openEquationGenerator(): void + refreshSession(): void } From d7ef7f039962201aba98f63fd17ac62b8539eddb Mon Sep 17 00:00:00 2001 From: Domagoj Kriskovic Date: Mon, 19 May 2025 11:43:59 +0200 Subject: [PATCH 144/194] Update AI Assist wording on checkout page if not in rollout (#25689) * Update AI Assist wording on checkout page if not in rollout * update wording when buyin addon GitOrigin-RevId: 20a90b14e97b1a8837e8be697c1a9666ed15a1c3 --- .../Subscription/SubscriptionController.js | 6 +++ .../web/frontend/extracted-translations.json | 2 + .../preview-subscription-change/root.tsx | 47 +++++++++++++------ .../user/subscription/preview-change.tsx | 7 ++- services/web/locales/en.json | 2 + 5 files changed, 49 insertions(+), 15 deletions(-) diff --git a/services/web/app/src/Features/Subscription/SubscriptionController.js b/services/web/app/src/Features/Subscription/SubscriptionController.js index fe23512901..b48088bb45 100644 --- a/services/web/app/src/Features/Subscription/SubscriptionController.js +++ b/services/web/app/src/Features/Subscription/SubscriptionController.js @@ -379,6 +379,12 @@ async function previewAddonPurchase(req, res) { paymentMethod ) + await SplitTestHandler.promises.getAssignment( + req, + res, + 'overleaf-assist-bundle' + ) + res.render('subscriptions/preview-change', { changePreview, purchaseReferrer, diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json index f3448a572d..501490b179 100644 --- a/services/web/frontend/extracted-translations.json +++ b/services/web/frontend/extracted-translations.json @@ -74,6 +74,8 @@ "add_company_details": "", "add_email_address": "", "add_email_to_claim_features": "", + "add_error_assist_annual_to_your_projects": "", + "add_error_assist_to_your_projects": "", "add_files": "", "add_more_collaborators": "", "add_more_licenses_to_my_plan": "", diff --git a/services/web/frontend/js/features/subscription/components/preview-subscription-change/root.tsx b/services/web/frontend/js/features/subscription/components/preview-subscription-change/root.tsx index 705af73e27..367a5e35a9 100644 --- a/services/web/frontend/js/features/subscription/components/preview-subscription-change/root.tsx +++ b/services/web/frontend/js/features/subscription/components/preview-subscription-change/root.tsx @@ -20,6 +20,7 @@ import OLButton from '@/features/ui/components/ol/ol-button' import { subscriptionUpdateUrl } from '@/features/subscription/data/subscription-url' import * as eventTracking from '@/infrastructure/event-tracking' import sparkleText from '@/shared/svgs/ai-sparkle-text.svg' +import { useFeatureFlag } from '@/shared/context/split-test-context' function PreviewSubscriptionChange() { const preview = getMeta( @@ -29,6 +30,7 @@ function PreviewSubscriptionChange() { const { t } = useTranslation() const payNowTask = useAsync() const location = useLocation() + const aiAssistEnabled = useFeatureFlag('overleaf-assist-bundle') useEffect(() => { if (preview.change.type === 'add-on-purchase') { @@ -107,20 +109,37 @@ function PreviewSubscriptionChange() { {aiAddOnChange && (
    -
    )} diff --git a/services/web/frontend/js/pages/user/subscription/preview-change.tsx b/services/web/frontend/js/pages/user/subscription/preview-change.tsx index 7b4b4c68b0..9ff5f14d45 100644 --- a/services/web/frontend/js/pages/user/subscription/preview-change.tsx +++ b/services/web/frontend/js/pages/user/subscription/preview-change.tsx @@ -1,9 +1,14 @@ import '@/marketing' import { createRoot } from 'react-dom/client' import PreviewSubscriptionChange from '@/features/subscription/components/preview-subscription-change/root' +import { SplitTestProvider } from '@/shared/context/split-test-context' const element = document.getElementById('subscription-preview-change') if (element) { const root = createRoot(element) - root.render() + root.render( + + + + ) } diff --git a/services/web/locales/en.json b/services/web/locales/en.json index fc9b8da439..279fa8d96d 100644 --- a/services/web/locales/en.json +++ b/services/web/locales/en.json @@ -88,6 +88,8 @@ "add_email": "Add Email", "add_email_address": "Add email address", "add_email_to_claim_features": "Add an institutional email address to claim your features.", + "add_error_assist_annual_to_your_projects": "Add Error Assist Annual to your projects and get unlimited AI help to fix LaTeX errors faster.", + "add_error_assist_to_your_projects": "Add Error Assist to your projects and get unlimited AI help to fix LaTeX errors faster.", "add_files": "Add Files", "add_more_collaborators": "Add more collaborators", "add_more_licenses_to_my_plan": "Add more licenses to my plan", From 85533a36e99e1d3362eac9956861e4c7e35c10cc Mon Sep 17 00:00:00 2001 From: Miguel Serrano Date: Mon, 19 May 2025 11:54:39 +0200 Subject: [PATCH 145/194] [history-v1] Disable backups on CE/SP (#25591) Disables backup when `backupStore` is not present, as it's the case for CE/SP GitOrigin-RevId: a920f041c639e599084fa97d2ef2643a01da70e3 --- services/history-v1/api/controllers/projects.js | 12 +++++++----- services/history-v1/storage/lib/chunk_store/mongo.js | 4 ++++ .../test/acceptance/js/storage/support/cleanup.js | 4 ++++ 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/services/history-v1/api/controllers/projects.js b/services/history-v1/api/controllers/projects.js index 5ae74cb6da..b5c3b629a9 100644 --- a/services/history-v1/api/controllers/projects.js +++ b/services/history-v1/api/controllers/projects.js @@ -255,11 +255,13 @@ async function createProjectBlob(req, res, next) { const blobStore = new BlobStore(projectId) const newBlob = await blobStore.putFile(tmpPath) - try { - const { backupBlob } = await import('../../storage/lib/backupBlob.mjs') - await backupBlob(projectId, newBlob, tmpPath) - } catch (error) { - logger.warn({ error, projectId, hash }, 'Failed to backup blob') + if (config.has('backupStore')) { + try { + const { backupBlob } = await import('../../storage/lib/backupBlob.mjs') + await backupBlob(projectId, newBlob, tmpPath) + } catch (error) { + logger.warn({ error, projectId, hash }, 'Failed to backup blob') + } } res.status(HTTPStatus.CREATED).end() }) diff --git a/services/history-v1/storage/lib/chunk_store/mongo.js b/services/history-v1/storage/lib/chunk_store/mongo.js index d84e7a8327..26c1bc48ec 100644 --- a/services/history-v1/storage/lib/chunk_store/mongo.js +++ b/services/history-v1/storage/lib/chunk_store/mongo.js @@ -3,6 +3,7 @@ const { ObjectId, ReadPreference, MongoError } = require('mongodb') const { Chunk } = require('overleaf-editor-core') const OError = require('@overleaf/o-error') +const config = require('config') const assert = require('../assert') const mongodb = require('../mongodb') const { ChunkVersionConflictError } = require('./errors') @@ -259,6 +260,9 @@ async function updateProjectRecord( earliestChangeTimestamp, mongoOpts = {} ) { + if (!config.has('backupStore')) { + return + } // record the end version against the project await mongodb.projects.updateOne( { diff --git a/services/history-v1/test/acceptance/js/storage/support/cleanup.js b/services/history-v1/test/acceptance/js/storage/support/cleanup.js index 55829bef13..632cc96c04 100644 --- a/services/history-v1/test/acceptance/js/storage/support/cleanup.js +++ b/services/history-v1/test/acceptance/js/storage/support/cleanup.js @@ -64,6 +64,10 @@ async function clearBucket(name) { let s3PersistorForBackupCleanup async function cleanupBackup() { + if (!config.has('backupStore')) { + return + } + // The backupPersistor refuses to delete short prefixes. Use a low-level S3 persistor. if (!s3PersistorForBackupCleanup) { const { backupPersistor } = await import( From 996c4073932d944cf81ae1fc805f209e91df6825 Mon Sep 17 00:00:00 2001 From: Domagoj Kriskovic Date: Mon, 19 May 2025 12:19:55 +0200 Subject: [PATCH 146/194] Revert "Update AI Assist wording on checkout page if not in rollout (#25689)" (#25726) This reverts commit 20a90b14e97b1a8837e8be697c1a9666ed15a1c3. GitOrigin-RevId: 26d4ad8f1b3a7dbe884dfbe4f4be5ee632abed1e --- .../Subscription/SubscriptionController.js | 6 --- .../web/frontend/extracted-translations.json | 2 - .../preview-subscription-change/root.tsx | 47 ++++++------------- .../user/subscription/preview-change.tsx | 7 +-- services/web/locales/en.json | 2 - 5 files changed, 15 insertions(+), 49 deletions(-) diff --git a/services/web/app/src/Features/Subscription/SubscriptionController.js b/services/web/app/src/Features/Subscription/SubscriptionController.js index b48088bb45..fe23512901 100644 --- a/services/web/app/src/Features/Subscription/SubscriptionController.js +++ b/services/web/app/src/Features/Subscription/SubscriptionController.js @@ -379,12 +379,6 @@ async function previewAddonPurchase(req, res) { paymentMethod ) - await SplitTestHandler.promises.getAssignment( - req, - res, - 'overleaf-assist-bundle' - ) - res.render('subscriptions/preview-change', { changePreview, purchaseReferrer, diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json index 501490b179..f3448a572d 100644 --- a/services/web/frontend/extracted-translations.json +++ b/services/web/frontend/extracted-translations.json @@ -74,8 +74,6 @@ "add_company_details": "", "add_email_address": "", "add_email_to_claim_features": "", - "add_error_assist_annual_to_your_projects": "", - "add_error_assist_to_your_projects": "", "add_files": "", "add_more_collaborators": "", "add_more_licenses_to_my_plan": "", diff --git a/services/web/frontend/js/features/subscription/components/preview-subscription-change/root.tsx b/services/web/frontend/js/features/subscription/components/preview-subscription-change/root.tsx index 367a5e35a9..705af73e27 100644 --- a/services/web/frontend/js/features/subscription/components/preview-subscription-change/root.tsx +++ b/services/web/frontend/js/features/subscription/components/preview-subscription-change/root.tsx @@ -20,7 +20,6 @@ import OLButton from '@/features/ui/components/ol/ol-button' import { subscriptionUpdateUrl } from '@/features/subscription/data/subscription-url' import * as eventTracking from '@/infrastructure/event-tracking' import sparkleText from '@/shared/svgs/ai-sparkle-text.svg' -import { useFeatureFlag } from '@/shared/context/split-test-context' function PreviewSubscriptionChange() { const preview = getMeta( @@ -30,7 +29,6 @@ function PreviewSubscriptionChange() { const { t } = useTranslation() const payNowTask = useAsync() const location = useLocation() - const aiAssistEnabled = useFeatureFlag('overleaf-assist-bundle') useEffect(() => { if (preview.change.type === 'add-on-purchase') { @@ -109,37 +107,20 @@ function PreviewSubscriptionChange() { {aiAddOnChange && (
    - {aiAssistEnabled ? ( -
    )} diff --git a/services/web/frontend/js/pages/user/subscription/preview-change.tsx b/services/web/frontend/js/pages/user/subscription/preview-change.tsx index 9ff5f14d45..7b4b4c68b0 100644 --- a/services/web/frontend/js/pages/user/subscription/preview-change.tsx +++ b/services/web/frontend/js/pages/user/subscription/preview-change.tsx @@ -1,14 +1,9 @@ import '@/marketing' import { createRoot } from 'react-dom/client' import PreviewSubscriptionChange from '@/features/subscription/components/preview-subscription-change/root' -import { SplitTestProvider } from '@/shared/context/split-test-context' const element = document.getElementById('subscription-preview-change') if (element) { const root = createRoot(element) - root.render( - - - - ) + root.render() } diff --git a/services/web/locales/en.json b/services/web/locales/en.json index 279fa8d96d..fc9b8da439 100644 --- a/services/web/locales/en.json +++ b/services/web/locales/en.json @@ -88,8 +88,6 @@ "add_email": "Add Email", "add_email_address": "Add email address", "add_email_to_claim_features": "Add an institutional email address to claim your features.", - "add_error_assist_annual_to_your_projects": "Add Error Assist Annual to your projects and get unlimited AI help to fix LaTeX errors faster.", - "add_error_assist_to_your_projects": "Add Error Assist to your projects and get unlimited AI help to fix LaTeX errors faster.", "add_files": "Add Files", "add_more_collaborators": "Add more collaborators", "add_more_licenses_to_my_plan": "Add more licenses to my plan", From a8b443fe5f698c12184845776a1b883937dc50ab Mon Sep 17 00:00:00 2001 From: Miguel Serrano Date: Mon, 19 May 2025 12:20:19 +0200 Subject: [PATCH 147/194] [web] Update `base-x` dependency to 4.0.1 (#25581) GitOrigin-RevId: 746f06a3abe75dbc7deb6ea181a15dfc24cd9d22 --- package-lock.json | 13 +++++++------ services/web/package.json | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5b6cb72cc7..09498173c8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16110,11 +16110,6 @@ "node": ">=0.10.0" } }, - "node_modules/base-x": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/base-x/-/base-x-4.0.0.tgz", - "integrity": "sha512-FuwxlW4H5kh37X/oW59pwTzzTKRzfrrQwhmyspRM7swOEZcHtDZSCt45U6oKgtuFE+WYPblePMVIPR4RZrh/hw==" - }, "node_modules/base/node_modules/define-property": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", @@ -44731,7 +44726,7 @@ "ajv": "^8.12.0", "archiver": "^5.3.0", "async": "^3.2.5", - "base-x": "^4.0.0", + "base-x": "^4.0.1", "basic-auth": "^2.0.1", "bcrypt": "^5.0.0", "body-parser": "^1.20.3", @@ -45405,6 +45400,12 @@ "ajv": "^8.8.2" } }, + "services/web/node_modules/base-x": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-4.0.1.tgz", + "integrity": "sha512-uAZ8x6r6S3aUM9rbHGVOIsR15U/ZSc82b3ymnCPsT45Gk1DDvhDPdIgB5MrhirZWt+5K0EEPQH985kNqZgNPFw==", + "license": "MIT" + }, "services/web/node_modules/brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", diff --git a/services/web/package.json b/services/web/package.json index 5a0e1c3959..29434d95e7 100644 --- a/services/web/package.json +++ b/services/web/package.json @@ -94,7 +94,7 @@ "ajv": "^8.12.0", "archiver": "^5.3.0", "async": "^3.2.5", - "base-x": "^4.0.0", + "base-x": "^4.0.1", "basic-auth": "^2.0.1", "bcrypt": "^5.0.0", "body-parser": "^1.20.3", From 5ee59a4f4a999a1c2a91215e8319716860b704ba Mon Sep 17 00:00:00 2001 From: Mathias Jakobsen Date: Mon, 19 May 2025 11:46:52 +0100 Subject: [PATCH 148/194] Merge pull request #25548 from overleaf/mj-add-booktabs [web] Add support for booktabs table style GitOrigin-RevId: e3f7e1a867474a86e4b5f8c701d845d55592bb68 --- .../web/frontend/extracted-translations.json | 2 +- .../components/table-generator/tabular.tsx | 31 +- .../table-generator/toolbar/commands.ts | 336 ++++++++++++------ .../table-generator/toolbar/toolbar.tsx | 25 +- services/web/locales/en.json | 2 +- ...codemirror-editor-table-generator.spec.tsx | 35 ++ 6 files changed, 311 insertions(+), 120 deletions(-) diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json index f3448a572d..ccda244ffb 100644 --- a/services/web/frontend/extracted-translations.json +++ b/services/web/frontend/extracted-translations.json @@ -183,6 +183,7 @@ "blocked_filename": "", "blog": "", "bold": "", + "booktabs": "", "browser": "", "bullet_list": "", "buy_licenses": "", @@ -1035,7 +1036,6 @@ "more_editor_toolbar_item": "", "more_info": "", "more_options": "", - "more_options_for_border_settings_coming_soon": "", "my_library": "", "n_items": "", "n_items_plural": "", diff --git a/services/web/frontend/js/features/source-editor/components/table-generator/tabular.tsx b/services/web/frontend/js/features/source-editor/components/table-generator/tabular.tsx index 0de999b214..50f15d71b8 100644 --- a/services/web/frontend/js/features/source-editor/components/table-generator/tabular.tsx +++ b/services/web/frontend/js/features/source-editor/components/table-generator/tabular.tsx @@ -139,6 +139,33 @@ export class TableData { if (this.rows.length === 0 || this.columns.length === 0) { return null } + const hasTopRule = this.rows[0].borderTop === 1 + const hasMidRule = + this.rows[1]?.borderTop === 1 && + (this.rows.length === 2 || this.rows[1]?.borderBottom === 0) + const hasBottomRule = + this.rows[this.rows.length - 1].borderBottom === 1 && + (this.rows.length === 2 || + this.rows[this.rows.length - 1].borderTop === 0) + + if (hasTopRule && hasMidRule && hasBottomRule) { + let isBooktabs = true + // Check for no other borders + for (const row of this.rows.slice(2, -1)) { + if (row.borderTop > 0 || row.borderBottom > 0) { + isBooktabs = false + } + } + for (const column of this.columns) { + if (column.borderLeft > 0 || column.borderRight > 0) { + isBooktabs = false + } + } + if (isBooktabs) { + return BorderTheme.BOOKTABS + } + } + const lastRow = this.rows[this.rows.length - 1] const hasBottomBorder = lastRow.borderBottom > 0 const firstColumn = this.columns[0] @@ -180,8 +207,10 @@ export class TableData { if (hasAllRowBorders && hasAllColumnBorders) { return BorderTheme.FULLY_BORDERED - } else { + } else if (!hasAllRowBorders && !hasAllColumnBorders) { return BorderTheme.NO_BORDERS + } else { + return null } } } diff --git a/services/web/frontend/js/features/source-editor/components/table-generator/toolbar/commands.ts b/services/web/frontend/js/features/source-editor/components/table-generator/toolbar/commands.ts index 0d8798f275..2645e853bd 100644 --- a/services/web/frontend/js/features/source-editor/components/table-generator/toolbar/commands.ts +++ b/services/web/frontend/js/features/source-editor/components/table-generator/toolbar/commands.ts @@ -20,8 +20,224 @@ import { WidthSelection } from './column-width-modal/column-width' export enum BorderTheme { NO_BORDERS = 0, FULLY_BORDERED = 1, + BOOKTABS = 2, } /* eslint-enable no-unused-vars */ + +type ThemeGenerator = { + column: ( + number: number, + numColumns: number + ) => { left: boolean; right: boolean } + row: (number: number, numRows: number) => string | false + lastRow?: () => string | false + multicolumn: () => { left: boolean; right: boolean } +} + +const themeGenerators: Record = { + [BorderTheme.NO_BORDERS]: { + column: () => ({ left: false, right: false }), + row: () => false, + multicolumn: () => ({ left: false, right: false }), + }, + [BorderTheme.FULLY_BORDERED]: { + column: (number: number, numColumns: number) => ({ + left: true, + right: number === numColumns - 1, + }), + row: (number: number, numRows: number) => '\\hline', + multicolumn: () => ({ left: true, right: true }), + lastRow: () => '\\hline', + }, + [BorderTheme.BOOKTABS]: { + column: (number: number, numColumns: number) => ({ + left: false, + right: false, + }), + row: (number: number, numRows: number) => { + if (number === 0) { + return '\\toprule' + } + if (number === 1) { + return '\\midrule' + } + return false + }, + lastRow: () => '\\bottomrule', + multicolumn: () => ({ left: false, right: false }), + }, +} + +function applyBorderTheme( + generator: ThemeGenerator, + view: EditorView, + table: TableData, + positions: Positions, + rowSeparators: RowSeparator[] +): ChangeSpec[] { + const changes: ChangeSpec[] = [] + + // Update specification + const spec = view.state.sliceDoc( + positions.columnDeclarations.from, + positions.columnDeclarations.to + ) + const columnSpecification = parseColumnSpecifications(spec) + columnSpecification.forEach((column, index) => { + const { left, right } = generator.column(index, columnSpecification.length) + column.borderLeft = left ? 1 : 0 + column.borderRight = right ? 1 : 0 + }) + const newSpec = generateColumnSpecification(columnSpecification) + if (newSpec !== spec) { + changes.push({ + from: positions.columnDeclarations.from, + to: positions.columnDeclarations.to, + insert: newSpec, + }) + } + + for (let i = 0; i < positions.rowPositions.length; i++) { + const row = positions.rowPositions[i] + const topBorder = generator.row(i, positions.rowPositions.length) + if (topBorder) { + let borderPresent = false + for (const hline of row.hlines) { + if (hline.from > rowSeparators[i]?.to) { + continue + } + if (borderPresent) { + // Remove extra border + changes.push({ + from: hline.from, + to: hline.to, + insert: '', + }) + } else { + const type = view.state.sliceDoc(hline.from, hline.to) + if (type.trim() !== topBorder) { + // Replace with the correct border + changes.push({ + from: hline.from, + to: hline.to, + insert: topBorder, + }) + } + borderPresent = true + } + } + if (!borderPresent) { + // Add the border + changes.push({ + from: row.from, + to: row.from, + insert: topBorder, + }) + } + } else { + for (const hline of row.hlines) { + if (hline.from > rowSeparators[i]?.to) { + continue + } + changes.push({ + from: hline.from, + to: hline.to, + insert: '', + }) + } + } + } + + const lastRow = positions.rowPositions[positions.rowPositions.length - 1] + const lastRowBorder = generator.lastRow?.() + const hasLastRowSeparator = + positions.rowPositions.length === rowSeparators.length + if (hasLastRowSeparator) { + if (lastRowBorder) { + let borderPresent = false + for (const hline of lastRow.hlines) { + if (hline.from < rowSeparators[positions.rowPositions.length - 1].to) { + continue + } + if (borderPresent) { + // Remove extra border + changes.push({ + from: hline.from, + to: hline.to, + insert: '', + }) + } else { + const type = view.state.sliceDoc(hline.from, hline.to) + if (type.trim() !== lastRowBorder) { + // Replace with the correct border + changes.push({ + from: hline.from, + to: hline.to, + insert: lastRowBorder, + }) + } + borderPresent = true + } + } + if (!borderPresent) { + const rowSeparator = rowSeparators[positions.rowPositions.length - 1] + + // Add the border + changes.push({ + from: rowSeparator.to, + to: rowSeparator.to, + insert: ` ${lastRowBorder}`, + }) + } + } else { + for (const hline of lastRow.hlines) { + if (hline.from < rowSeparators[positions.rowPositions.length - 1].to) { + continue + } + changes.push({ + from: hline.from, + to: hline.to, + insert: '', + }) + } + } + } else if (lastRowBorder) { + changes.push({ + from: lastRow.to, + to: lastRow.to, + insert: `\\\\ ${lastRowBorder}`, + }) + } + + // Update multicolumn + for (const row of table.rows) { + for (const cell of row.cells) { + if (cell.multiColumn) { + const { left, right } = generator.multicolumn() + const spec = view.state.sliceDoc( + cell.multiColumn.columns.from, + cell.multiColumn.columns.to + ) + const columnSpecification = parseColumnSpecifications(spec) + columnSpecification.forEach(column => { + column.borderLeft = left ? 1 : 0 + column.borderRight = right ? 1 : 0 + }) + const newSpec = generateColumnSpecification(columnSpecification) + if (newSpec !== spec) { + changes.push({ + from: cell.multiColumn.columns.from, + to: cell.multiColumn.columns.to, + insert: newSpec, + }) + } + } + } + } + + return changes +} + export const setBorders = ( view: EditorView, theme: BorderTheme, @@ -29,118 +245,18 @@ export const setBorders = ( rowSeparators: RowSeparator[], table: TableData ) => { - const specification = view.state.sliceDoc( - positions.columnDeclarations.from, - positions.columnDeclarations.to + const generator = themeGenerators[theme] + const changes = applyBorderTheme( + generator, + view, + table, + positions, + rowSeparators ) - if (theme === BorderTheme.NO_BORDERS) { - const removeColumnBorders = view.state.changes({ - from: positions.columnDeclarations.from, - to: positions.columnDeclarations.to, - insert: specification.replace(/\|/g, ''), - }) - const removeHlines: ChangeSpec[] = [] - for (const row of positions.rowPositions) { - for (const hline of row.hlines) { - removeHlines.push({ - from: hline.from, - to: hline.to, - insert: '', - }) - } - } - const removeMulticolumnBorders: ChangeSpec[] = [] - for (const row of table.rows) { - for (const cell of row.cells) { - if (cell.multiColumn) { - const specification = view.state.sliceDoc( - cell.multiColumn.columns.from, - cell.multiColumn.columns.to - ) - removeMulticolumnBorders.push({ - from: cell.multiColumn.columns.from, - to: cell.multiColumn.columns.to, - insert: specification.replace(/\|/g, ''), - }) - } - } - } - view.dispatch({ - changes: [ - removeColumnBorders, - ...removeHlines, - ...removeMulticolumnBorders, - ], - }) - } else if (theme === BorderTheme.FULLY_BORDERED) { - const newSpec = generateColumnSpecification( - addColumnBordersToSpecification(table.columns) - ) - const insertColumns = view.state.changes({ - from: positions.columnDeclarations.from, - to: positions.columnDeclarations.to, - insert: newSpec, - }) - - const insertHlines: ChangeSpec[] = [] - for (const row of positions.rowPositions) { - if (row.hlines.length === 0) { - insertHlines.push( - view.state.changes({ - from: row.from, - to: row.from, - insert: ' \\hline ', - }) - ) - } - } - const lastRow = positions.rowPositions[positions.rowPositions.length - 1] - if (lastRow.hlines.length < 2) { - let toInsert = ' \\hline' - if (rowSeparators.length < positions.rowPositions.length) { - // We need a trailing \\ - toInsert = ` \\\\${toInsert}` - } - insertHlines.push( - view.state.changes({ - from: lastRow.to, - to: lastRow.to, - insert: toInsert, - }) - ) - } - const addMulticolumnBorders: ChangeSpec[] = [] - for (const row of table.rows) { - for (const cell of row.cells) { - if (cell.multiColumn) { - addMulticolumnBorders.push({ - from: cell.multiColumn.columns.from, - to: cell.multiColumn.columns.to, - insert: generateColumnSpecification( - addColumnBordersToSpecification( - cell.multiColumn.columns.specification - ) - ), - }) - } - } - } - - view.dispatch({ - changes: [insertColumns, ...insertHlines, ...addMulticolumnBorders], - }) - } -} - -const addColumnBordersToSpecification = (specification: ColumnDefinition[]) => { - const newSpec = specification.map(column => ({ - ...column, - borderLeft: 1, - borderRight: 0, - })) - newSpec[newSpec.length - 1].borderRight = 1 - return newSpec + view.dispatch({ + changes, + }) } export const setAlignment = ( diff --git a/services/web/frontend/js/features/source-editor/components/table-generator/toolbar/toolbar.tsx b/services/web/frontend/js/features/source-editor/components/table-generator/toolbar/toolbar.tsx index 1e92398f6f..637764fd76 100644 --- a/services/web/frontend/js/features/source-editor/components/table-generator/toolbar/toolbar.tsx +++ b/services/web/frontend/js/features/source-editor/components/table-generator/toolbar/toolbar.tsx @@ -3,7 +3,6 @@ import { useSelectionContext } from '../contexts/selection-context' import { ToolbarButton } from './toolbar-button' import { ToolbarButtonMenu } from './toolbar-button-menu' import { ToolbarDropdown, ToolbarDropdownItem } from './toolbar-dropdown' -import MaterialIcon from '../../../../../shared/components/material-icon' import { BorderTheme, insertColumn, @@ -47,6 +46,8 @@ export const Toolbar = memo(function Toolbar() { return t('all_borders') case BorderTheme.NO_BORDERS: return t('no_borders') + case BorderTheme.BOOKTABS: + return t('booktabs') default: return t('custom_borders') } @@ -203,12 +204,22 @@ export const Toolbar = memo(function Toolbar() { > {t('no_borders')} -
    -
    - -
    - {t('more_options_for_border_settings_coming_soon')} -
    + { + setBorders( + view, + BorderTheme.BOOKTABS, + positions, + rowSeparators, + table + ) + }} + active={table.getBorderTheme() === BorderTheme.BOOKTABS} + icon="border_top" + > + {t('booktabs')} +
    diff --git a/services/web/locales/en.json b/services/web/locales/en.json index fc9b8da439..cafab27447 100644 --- a/services/web/locales/en.json +++ b/services/web/locales/en.json @@ -237,6 +237,7 @@ "blocked_filename": "This file name is blocked.", "blog": "Blog", "bold": "Bold", + "booktabs": "Booktabs", "brl_discount_offer_plans_page_banner": "__flag__ Great news! We’ve 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", @@ -1353,7 +1354,6 @@ "more_editor_toolbar_item": "More editor toolbar items", "more_info": "More Info", "more_options": "More options", - "more_options_for_border_settings_coming_soon": "More options for border settings coming soon.", "more_project_collaborators": "<0>More project <0>collaborators", "more_than_one_kind_of_snippet_was_requested": "The link to open this content on Overleaf included some invalid parameters. If this keeps happening for links on a particular site, please report this to them.", "most_popular_uppercase": "Most popular", diff --git a/services/web/test/frontend/features/source-editor/components/codemirror-editor-table-generator.spec.tsx b/services/web/test/frontend/features/source-editor/components/codemirror-editor-table-generator.spec.tsx index ea7414ddab..34f26f02cb 100644 --- a/services/web/test/frontend/features/source-editor/components/codemirror-editor-table-generator.spec.tsx +++ b/services/web/test/frontend/features/source-editor/components/codemirror-editor-table-generator.spec.tsx @@ -217,6 +217,41 @@ cell 3 & cell 4 \\\\ ) }) + it('Correctly sets borders for booktabs', function () { + // Add a blank line above the table to allow room for the table toolbar + mountEditor(` + +\\begin{tabular}{c|c} + cell 1 & cell 2 \\\\ + cell 3 & cell 4 \\\\ + cell 5 & cell 6 \\\\ +\\end{tabular} +`) + checkBordersWithNoMultiColumn( + [false, false, false, false], + [false, true, false] + ) + cy.get('.table-generator-floating-toolbar').should('not.exist') + cy.get('.table-generator-cell').first().click() + cy.get('.table-generator-floating-toolbar').as('toolbar').should('exist') + cy.get('@toolbar').findByText('Custom borders').click({ force: true }) + cy.get('.table-generator').findByText('Booktabs').click() + // The element is partially covered, but we can still click it + cy.get('.cm-line').first().click({ force: true }) + // Table should be unchanged + checkTable([ + ['cell 1', 'cell 2'], + ['cell 3', 'cell 4'], + ['cell 5', 'cell 6'], + ]) + checkBordersWithNoMultiColumn( + [true, true, false, true], + [false, false, false] + ) + cy.get('.table-generator-cell').first().click() + cy.get('@toolbar').findByText('Booktabs').should('exist') + }) + it('Changes the column alignment with dropdown buttons', function () { // Add a blank line above the table to allow room for the table toolbar mountEditor(` From 60e588440f18caec7e6e082060da0486190608db Mon Sep 17 00:00:00 2001 From: Mathias Jakobsen Date: Mon, 19 May 2025 11:47:10 +0100 Subject: [PATCH 149/194] Merge pull request #25631 from overleaf/mj-reactdom-render [web] Remove ReactDOM.render usage GitOrigin-RevId: 42f62fa79a784cf3cc5c420357880154562d7dc7 --- .../group-management/subtotal-limit-exceeded.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/services/web/frontend/js/pages/user/subscription/group-management/subtotal-limit-exceeded.tsx b/services/web/frontend/js/pages/user/subscription/group-management/subtotal-limit-exceeded.tsx index 242c04f5cf..ecc38222c2 100644 --- a/services/web/frontend/js/pages/user/subscription/group-management/subtotal-limit-exceeded.tsx +++ b/services/web/frontend/js/pages/user/subscription/group-management/subtotal-limit-exceeded.tsx @@ -1,8 +1,9 @@ import '../base' -import ReactDOM from 'react-dom' +import { createRoot } from 'react-dom/client' import SubtotalLimitExceeded from '@/features/group-management/components/subtotal-limit-exceeded' const element = document.getElementById('subtotal-limit-exceeded-root') if (element) { - ReactDOM.render(, element) + const root = createRoot(element) + root.render() } From b375b139504bbba818603d8926a07249d40e1c8a Mon Sep 17 00:00:00 2001 From: Domagoj Kriskovic Date: Mon, 19 May 2025 13:21:37 +0200 Subject: [PATCH 150/194] Revert "Reinitialise Writefull toolbar after buying AI assist" (#25731) * Revert "Increase a timeout for second refresh of writefull session (#25725)" This reverts commit 0a34bdde656ade863aead22f003253e13af37829. * Revert "Reinitialise Writefull toolbar after buying AI assist (#25596)" This reverts commit a2572d62bce0e344d92696e42d137a0b36574b3b. GitOrigin-RevId: 3d51a4375059ab9f4494a7e18b132cc5db34e4cd --- .../web/frontend/js/shared/context/types/writefull-instance.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/services/web/frontend/js/shared/context/types/writefull-instance.ts b/services/web/frontend/js/shared/context/types/writefull-instance.ts index 18b6d08616..120590e668 100644 --- a/services/web/frontend/js/shared/context/types/writefull-instance.ts +++ b/services/web/frontend/js/shared/context/types/writefull-instance.ts @@ -42,5 +42,4 @@ export interface WritefullAPI { ): void openTableGenerator(): void openEquationGenerator(): void - refreshSession(): void } From d280d4ecad0a2f098cdecedf1acab2964bd22e65 Mon Sep 17 00:00:00 2001 From: Alf Eaton Date: Mon, 19 May 2025 12:40:52 +0100 Subject: [PATCH 151/194] Merge pull request #25691 from overleaf/ae-project-list-nav Move isReady out of DefaultNavbar component GitOrigin-RevId: 66f19620399e405c9ef4d95f7aef3ab918da5aa1 --- .../js/features/header-footer-react/index.tsx | 4 ++-- .../bootstrap-5/navbar/default-navbar.tsx | 15 ++++++++++----- .../emails/emails-section-add-new-email.test.tsx | 2 +- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/services/web/frontend/js/features/header-footer-react/index.tsx b/services/web/frontend/js/features/header-footer-react/index.tsx index ebbf4ddecf..94a43b65fa 100644 --- a/services/web/frontend/js/features/header-footer-react/index.tsx +++ b/services/web/frontend/js/features/header-footer-react/index.tsx @@ -1,6 +1,6 @@ import { createRoot } from 'react-dom/client' import getMeta from '@/utils/meta' -import DefaultNavbar from '@/features/ui/components/bootstrap-5/navbar/default-navbar' +import { DefaultNavbarRoot } from '@/features/ui/components/bootstrap-5/navbar/default-navbar' import Footer from '@/features/ui/components/bootstrap-5/footer/footer' import { SplitTestProvider } from '@/shared/context/split-test-context' @@ -10,7 +10,7 @@ if (navbarElement) { const root = createRoot(navbarElement) root.render( - + ) } 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 a5fb5afd10..84ed9b8da8 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 @@ -34,7 +34,6 @@ function DefaultNavbar(props: DefaultNavbarMetadata) { items, } = props const { t } = useTranslation() - const { isReady } = useWaitForI18n() const [expanded, setExpanded] = useState(false) // The Contact Us modal is rendered at this level rather than inside the nav @@ -44,10 +43,6 @@ function DefaultNavbar(props: DefaultNavbarMetadata) { autofillProjectUrl: false, }) - if (!isReady) { - return null - } - return ( <> { + const { isReady } = useWaitForI18n() + + if (!isReady) { + return null + } + + return +} + export default DefaultNavbar diff --git a/services/web/test/frontend/features/settings/components/emails/emails-section-add-new-email.test.tsx b/services/web/test/frontend/features/settings/components/emails/emails-section-add-new-email.test.tsx index 105e52f0aa..b2df8b40eb 100644 --- a/services/web/test/frontend/features/settings/components/emails/emails-section-add-new-email.test.tsx +++ b/services/web/test/frontend/features/settings/components/emails/emails-section-add-new-email.test.tsx @@ -384,7 +384,7 @@ describe('', function () { department: customDepartment, }) - screen.getByText( + await screen.findByText( `Enter the 6-digit confirmation code sent to ${userEmailData.email}.` ) From bb3f1aa998c73c4a8e04045627ad64d26da268be Mon Sep 17 00:00:00 2001 From: Alf Eaton Date: Mon, 19 May 2025 12:41:17 +0100 Subject: [PATCH 152/194] Merge pull request #25666 from overleaf/ae-reviewing-button Prevent gap between button and menu GitOrigin-RevId: 42f6a80ea2cda55c3b321a8d9ca9710ecf06c8a3 --- .../bootstrap-5/pages/editor/review-panel-new.scss | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/services/web/frontend/stylesheets/bootstrap-5/pages/editor/review-panel-new.scss b/services/web/frontend/stylesheets/bootstrap-5/pages/editor/review-panel-new.scss index 5e9f53b7f7..7f1c32b139 100644 --- a/services/web/frontend/stylesheets/bootstrap-5/pages/editor/review-panel-new.scss +++ b/services/web/frontend/stylesheets/bootstrap-5/pages/editor/review-panel-new.scss @@ -721,6 +721,11 @@ del.review-panel-content-highlight { display: block; } } + + // prevent gap between button and menu + > .dropdown-menu { + margin-top: -2px; + } } .review-mode-switcher-toggle-button { From ed006b707c33d8b0333ef21bf9fb99639a6ec1f1 Mon Sep 17 00:00:00 2001 From: Alf Eaton Date: Mon, 19 May 2025 12:41:40 +0100 Subject: [PATCH 153/194] Update "Editing" role description (#25641) GitOrigin-RevId: dba2e4a21ca645ba34d9e41b76f09a287b5c3b33 --- services/web/frontend/extracted-translations.json | 2 +- .../review-panel-new/components/review-mode-switcher.tsx | 2 +- services/web/locales/en.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json index ccda244ffb..bb5d208f12 100644 --- a/services/web/frontend/extracted-translations.json +++ b/services/web/frontend/extracted-translations.json @@ -189,7 +189,6 @@ "buy_licenses": "", "buy_more_licenses": "", "by_subscribing_you_agree_to_our_terms_of_service": "", - "can_edit_content": "", "can_link_institution_email_acct_to_institution_acct": "", "can_link_your_institution_acct_2": "", "can_now_relink_dropbox": "", @@ -467,6 +466,7 @@ "edit": "", "edit_comment_error_message": "", "edit_comment_error_title": "", + "edit_content_directly": "", "edit_dictionary": "", "edit_dictionary_empty": "", "edit_dictionary_remove": "", diff --git a/services/web/frontend/js/features/review-panel-new/components/review-mode-switcher.tsx b/services/web/frontend/js/features/review-panel-new/components/review-mode-switcher.tsx index de1a5faaf0..879b539aeb 100644 --- a/services/web/frontend/js/features/review-panel-new/components/review-mode-switcher.tsx +++ b/services/web/frontend/js/features/review-panel-new/components/review-mode-switcher.tsx @@ -73,7 +73,7 @@ function ReviewModeSwitcher() { }) saveTrackChangesForCurrentUser(false) }} - description={t('can_edit_content')} + description={t('edit_content_directly')} leadingIcon="edit" active={write && mode === 'edit'} > diff --git a/services/web/locales/en.json b/services/web/locales/en.json index cafab27447..ddf96c30bc 100644 --- a/services/web/locales/en.json +++ b/services/web/locales/en.json @@ -249,7 +249,6 @@ "by_joining_labs": "By joining Labs, you agree to receive occasional emails and updates from Overleaf—for example, to request your feedback. You also agree to our <0>terms of service and <1>privacy notice.", "by_registering_you_agree_to_our_terms_of_service": "By registering, you agree to our <0>terms of service and <1>privacy notice.", "by_subscribing_you_agree_to_our_terms_of_service": "By subscribing, you agree to our <0>terms of service.", - "can_edit_content": "Can edit content", "can_link_institution_email_acct_to_institution_acct": "You can now link your __email__ __appName__ account to your __institutionName__ institutional account.", "can_link_institution_email_by_clicking": "You can link your __email__ __appName__ account to your __institutionName__ account by clicking __clickText__.", "can_link_institution_email_to_login": "You can link your __email__ __appName__ account to your __institutionName__ account, which will allow you to log in to __appName__ through your institution and will reconfirm your institutional email address.", @@ -605,6 +604,7 @@ "edit": "Edit", "edit_comment_error_message": "There was an error editing the comment. Please try again in a few moments.", "edit_comment_error_title": "Edit Comment Error", + "edit_content_directly": "Edit content directly", "edit_dictionary": "Edit Dictionary", "edit_dictionary_empty": "Your custom dictionary is empty.", "edit_dictionary_remove": "Remove from dictionary", From f7d37a49d67204e479265490bbe2babb20e19e42 Mon Sep 17 00:00:00 2001 From: Alf Eaton Date: Mon, 19 May 2025 12:42:06 +0100 Subject: [PATCH 154/194] Upgrade CodeMirror dependencies (#25620) * Upgrade CodeMirror dependencies * Upgrade Lezer dependencies GitOrigin-RevId: fcbf9b4bbf2577d85f44b48d3b745a56e49e24c9 --- package-lock.json | 113 +++++++++++++++++++++----------------- services/web/package.json | 12 ++-- 2 files changed, 69 insertions(+), 56 deletions(-) diff --git a/package-lock.json b/package-lock.json index 09498173c8..168d968293 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3271,9 +3271,9 @@ } }, "node_modules/@codemirror/commands": { - "version": "6.8.0", - "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.8.0.tgz", - "integrity": "sha512-q8VPEFaEP4ikSlt6ZxjB3zW72+7osfAYW9i8Zu943uqbKuz6utc1+F170hyLUCUltXORjQXRyYQNfkckzA/bPQ==", + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.8.1.tgz", + "integrity": "sha512-KlGVYufHMQzxbdQONiLyGQDUW0itrLZwq3CcY7xpv9ZLRHqzkBSoteocBHtMCoY7/Ci4xhzSrToIeLg7FxHuaw==", "dev": true, "license": "MIT", "dependencies": { @@ -3284,43 +3284,49 @@ } }, "node_modules/@codemirror/lang-css": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@codemirror/lang-css/-/lang-css-6.0.0.tgz", - "integrity": "sha512-jBqc+BTuwhNOTlrimFghLlSrN6iFuE44HULKWoR4qKYObhOIl9Lci1iYj6zMIte1XTQmZguNvjXMyr43LUKwSw==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/@codemirror/lang-css/-/lang-css-6.3.1.tgz", + "integrity": "sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==", "dev": true, + "license": "MIT", "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/language": "^6.0.0", "@codemirror/state": "^6.0.0", - "@lezer/css": "^1.0.0" + "@lezer/common": "^1.0.2", + "@lezer/css": "^1.1.7" } }, "node_modules/@codemirror/lang-html": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.1.0.tgz", - "integrity": "sha512-gA7NmJxqvnhwza05CvR7W/39Ap9r/4Vs9uiC0IeFYo1hSlJzc/8N6Evviz6vTW1x8SpHcRYyqKOf6rpl6LfWtg==", + "version": "6.4.9", + "resolved": "https://registry.npmjs.org/@codemirror/lang-html/-/lang-html-6.4.9.tgz", + "integrity": "sha512-aQv37pIMSlueybId/2PVSP6NPnmurFDVmZwzc7jszd2KAF8qd4VBbvNYPXWQq90WIARjsdVkPbw29pszmHws3Q==", "dev": true, + "license": "MIT", "dependencies": { "@codemirror/autocomplete": "^6.0.0", "@codemirror/lang-css": "^6.0.0", "@codemirror/lang-javascript": "^6.0.0", - "@codemirror/language": "^6.0.0", + "@codemirror/language": "^6.4.0", "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", "@lezer/common": "^1.0.0", - "@lezer/html": "^1.0.0" + "@lezer/css": "^1.1.0", + "@lezer/html": "^1.3.0" } }, "node_modules/@codemirror/lang-javascript": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.0.1.tgz", - "integrity": "sha512-kjGbBEosl+ozDU5ruDV48w4v3H6KECTFiDjqMLT0KhVwESPfv3wOvnDrTT0uaMOg3YRGnBWsyiIoKHl/tNWWDg==", + "version": "6.2.4", + "resolved": "https://registry.npmjs.org/@codemirror/lang-javascript/-/lang-javascript-6.2.4.tgz", + "integrity": "sha512-0WVmhp1QOqZ4Rt6GlVGwKJN3KW7Xh4H2q8ZZNGZaP6lRdxXJzmjm4FqvmOojVj6khWJHIb9sp7U/72W7xQgqAA==", "dev": true, + "license": "MIT", "dependencies": { "@codemirror/autocomplete": "^6.0.0", - "@codemirror/language": "^6.0.0", + "@codemirror/language": "^6.6.0", "@codemirror/lint": "^6.0.0", "@codemirror/state": "^6.0.0", - "@codemirror/view": "^6.0.0", + "@codemirror/view": "^6.17.0", "@lezer/common": "^1.0.0", "@lezer/javascript": "^1.0.0" } @@ -3342,9 +3348,9 @@ } }, "node_modules/@codemirror/language": { - "version": "6.10.8", - "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.10.8.tgz", - "integrity": "sha512-wcP8XPPhDH2vTqf181U8MbZnW+tDyPYy0UzVOa+oHORjyT+mhhom9vBd7dApJwoDz9Nb/a8kHjJIsuA/t8vNFw==", + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.11.0.tgz", + "integrity": "sha512-A7+f++LodNNc1wGgoRDTt78cOwWm9KVezApgjOMp1W4hM0898nsqBXwF+sbePE7ZRcjN7Sa1Z5m2oN27XkmEjQ==", "dev": true, "license": "MIT", "dependencies": { @@ -3357,9 +3363,9 @@ } }, "node_modules/@codemirror/lint": { - "version": "6.8.4", - "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.8.4.tgz", - "integrity": "sha512-u4q7PnZlJUojeRe8FJa/njJcMctISGgPQ4PnWsd9268R4ZTtU+tfFYmwkBvgcrK2+QQ8tYFVALVb5fVJykKc5A==", + "version": "6.8.5", + "resolved": "https://registry.npmjs.org/@codemirror/lint/-/lint-6.8.5.tgz", + "integrity": "sha512-s3n3KisH7dx3vsoeGMxsbRAgKe4O1vbrnKBClm99PU0fWxmxsx5rR2PfqQgIt+2MMJBHbiJ5rfIdLYfB9NNvsA==", "dev": true, "license": "MIT", "dependencies": { @@ -3391,9 +3397,9 @@ } }, "node_modules/@codemirror/view": { - "version": "6.36.3", - "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.36.3.tgz", - "integrity": "sha512-N2bilM47QWC8Hnx0rMdDxO2x2ImJ1FvZWXubwKgjeoOrWwEiFrtpA7SFHcuZ+o2Ze2VzbkgbzWVj4+V18LVkeg==", + "version": "6.36.8", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.36.8.tgz", + "integrity": "sha512-yoRo4f+FdnD01fFt4XpfpMCcCAo9QvZOtbrXExn4SqzH32YC6LgzqxfLZw/r6Ge65xyY03mK/UfUqrVw1gFiFg==", "dev": true, "license": "MIT", "dependencies": { @@ -6492,20 +6498,23 @@ "license": "MIT" }, "node_modules/@lezer/css": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.0.0.tgz", - "integrity": "sha512-616VqgDKumHmYIuxs3tnX1irEQmoDHgF/TlP4O5ICWwyHwLMErq+8iKVuzTkOdBqvYAVmObqThcDEAaaMJjAdg==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@lezer/css/-/css-1.2.0.tgz", + "integrity": "sha512-8FLXsWpwKWMqQ6XjDP0DWbMP4YdeqhIcwN8IulcBinGpu30PG74zz0c6w+Yi2DeQD9/4FXfeLp+XP90NflIkGA==", "dev": true, + "license": "MIT", "dependencies": { + "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", - "@lezer/lr": "^1.0.0" + "@lezer/lr": "^1.3.0" } }, "node_modules/@lezer/generator": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@lezer/generator/-/generator-1.7.1.tgz", - "integrity": "sha512-MgPJN9Si+ccxzXl3OAmCeZuUKw4XiPl4y664FX/hnnyG9CTqUPq65N3/VGPA2jD23D7QgMTtNqflta+cPN+5mQ==", + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/@lezer/generator/-/generator-1.7.3.tgz", + "integrity": "sha512-vAI2O1tPF8QMMgp+bdUeeJCneJNkOZvqsrtyb4ohnFVFdboSqPwBEacnt0HH4E+5h+qsIwTHUSAhffU4hzKl1A==", "dev": true, + "license": "MIT", "dependencies": { "@lezer/common": "^1.1.0", "@lezer/lr": "^1.3.0" @@ -6524,23 +6533,27 @@ } }, "node_modules/@lezer/html": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.0.0.tgz", - "integrity": "sha512-wZHBcieArLTxEi198hqRBBHMySzDKo5suWaESdUw0t44IXp01vkSRwX2brG1qBbKdwJ+C6U0iMl00vWNiyAROg==", + "version": "1.3.10", + "resolved": "https://registry.npmjs.org/@lezer/html/-/html-1.3.10.tgz", + "integrity": "sha512-dqpT8nISx/p9Do3AchvYGV3qYc4/rKr3IBZxlHmpIKam56P47RSHkSF5f13Vu9hebS1jM0HmtJIwLbWz1VIY6w==", "dev": true, + "license": "MIT", "dependencies": { + "@lezer/common": "^1.2.0", "@lezer/highlight": "^1.0.0", "@lezer/lr": "^1.0.0" } }, "node_modules/@lezer/javascript": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.0.1.tgz", - "integrity": "sha512-t7fpf3+gi/jiAtW+Gv734TbKdpPg6b8qATH01/jprW9H2oR++Tb688IHwJvZbk9F4GjpCEv86beuHMpUyC1b5g==", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@lezer/javascript/-/javascript-1.5.1.tgz", + "integrity": "sha512-ATOImjeVJuvgm3JQ/bpo2Tmv55HSScE2MTPnKRMRIPx2cLhHGyX2VnqpHhtIV1tVzIjZDbcWQm+NCTF40ggZVw==", "dev": true, + "license": "MIT", "dependencies": { - "@lezer/highlight": "^1.0.0", - "@lezer/lr": "^1.0.0" + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.1.3", + "@lezer/lr": "^1.3.0" } }, "node_modules/@lezer/lr": { @@ -6553,9 +6566,9 @@ } }, "node_modules/@lezer/markdown": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@lezer/markdown/-/markdown-1.3.2.tgz", - "integrity": "sha512-Wu7B6VnrKTbBEohqa63h5vxXjiC4pO5ZQJ/TDbhJxPQaaIoRD/6UVDhSDtVsCwVZV12vvN9KxuLL3ATMnlG0oQ==", + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@lezer/markdown/-/markdown-1.4.3.tgz", + "integrity": "sha512-kfw+2uMrQ/wy/+ONfrH83OkdFNM0ye5Xq96cLlaCy7h5UT9FO54DU4oRoIc0CSBh5NWmWuiIJA7NGLMJbQ+Oxg==", "dev": true, "license": "MIT", "dependencies": { @@ -44817,19 +44830,19 @@ "@babel/preset-typescript": "^7.27.0", "@babel/register": "^7.25.9", "@codemirror/autocomplete": "github:overleaf/codemirror-autocomplete#6445cd056671c98d12d1c597ba705e11327ec4c5", - "@codemirror/commands": "^6.8.0", + "@codemirror/commands": "^6.8.1", "@codemirror/lang-markdown": "^6.3.2", - "@codemirror/language": "^6.10.8", - "@codemirror/lint": "^6.8.4", + "@codemirror/language": "^6.11.0", + "@codemirror/lint": "^6.8.5", "@codemirror/search": "github:overleaf/codemirror-search#04380a528c339cd4b78fb10b3ef017f657ec17bd", "@codemirror/state": "^6.5.2", - "@codemirror/view": "^6.36.3", + "@codemirror/view": "^6.36.8", "@juggle/resize-observer": "^3.3.1", "@lezer/common": "^1.2.3", - "@lezer/generator": "^1.7.1", + "@lezer/generator": "^1.7.3", "@lezer/highlight": "^1.2.1", "@lezer/lr": "^1.4.2", - "@lezer/markdown": "^1.3.2", + "@lezer/markdown": "^1.4.3", "@overleaf/codemirror-tree-view": "^0.1.3", "@overleaf/dictionaries": "https://github.com/overleaf/dictionaries/archive/refs/tags/v0.0.3.tar.gz", "@overleaf/ranges-tracker": "*", diff --git a/services/web/package.json b/services/web/package.json index 29434d95e7..f5f1ec3e96 100644 --- a/services/web/package.json +++ b/services/web/package.json @@ -185,19 +185,19 @@ "@babel/preset-typescript": "^7.27.0", "@babel/register": "^7.25.9", "@codemirror/autocomplete": "github:overleaf/codemirror-autocomplete#6445cd056671c98d12d1c597ba705e11327ec4c5", - "@codemirror/commands": "^6.8.0", + "@codemirror/commands": "^6.8.1", "@codemirror/lang-markdown": "^6.3.2", - "@codemirror/language": "^6.10.8", - "@codemirror/lint": "^6.8.4", + "@codemirror/language": "^6.11.0", + "@codemirror/lint": "^6.8.5", "@codemirror/search": "github:overleaf/codemirror-search#04380a528c339cd4b78fb10b3ef017f657ec17bd", "@codemirror/state": "^6.5.2", - "@codemirror/view": "^6.36.3", + "@codemirror/view": "^6.36.8", "@juggle/resize-observer": "^3.3.1", "@lezer/common": "^1.2.3", - "@lezer/generator": "^1.7.1", + "@lezer/generator": "^1.7.3", "@lezer/highlight": "^1.2.1", "@lezer/lr": "^1.4.2", - "@lezer/markdown": "^1.3.2", + "@lezer/markdown": "^1.4.3", "@overleaf/codemirror-tree-view": "^0.1.3", "@overleaf/dictionaries": "https://github.com/overleaf/dictionaries/archive/refs/tags/v0.0.3.tar.gz", "@overleaf/ranges-tracker": "*", From ecdd0c54bdfdfab8b5dac4465f6004327f16c26f Mon Sep 17 00:00:00 2001 From: Miguel Serrano Date: Mon, 19 May 2025 14:20:08 +0200 Subject: [PATCH 155/194] [CE/SP] Update base image to `noble-1.0.2` and `node@22` (#25716) * [CE/SP] Update base image to node:22 Also triggers a rebuild of the image to ensure all dependencies are up to date * Bump phusion image to noble-1.0.2 GitOrigin-RevId: 8dce9d3cc6e8df28fce7a15f2727e7bc4aa453fd --- server-ce/Dockerfile-base | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server-ce/Dockerfile-base b/server-ce/Dockerfile-base index ca3c45cc1d..e130d7a414 100644 --- a/server-ce/Dockerfile-base +++ b/server-ce/Dockerfile-base @@ -2,7 +2,7 @@ # Overleaf Base Image (sharelatex/sharelatex-base) # -------------------------------------------------- -FROM phusion/baseimage:noble-1.0.0 +FROM phusion/baseimage:noble-1.0.2 # Makes sure LuaTex cache is writable # ----------------------------------- @@ -10,7 +10,7 @@ ENV TEXMFVAR=/var/lib/overleaf/tmp/texmf-var # Update to ensure dependencies are updated # ------------------------------------------ -ENV REBUILT_AFTER="2025-03-27" +ENV REBUILT_AFTER="2025-05-19" # Install dependencies # -------------------- @@ -30,7 +30,7 @@ RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \ # install Node.js https://github.com/nodesource/distributions#nodejs && mkdir -p /etc/apt/keyrings \ && curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \ -&& echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_20.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list \ +&& echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_22.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list \ && apt-get update \ && apt-get install -y nodejs \ \ From dbb528762e8b283b66ed64a302510240396341a5 Mon Sep 17 00:00:00 2001 From: Kristina <7614497+khjrtbrg@users.noreply.github.com> Date: Mon, 19 May 2025 15:05:55 +0200 Subject: [PATCH 156/194] [web] add support for previewing base plan changes for Stripe (#25619) GitOrigin-RevId: 458eeac52bc5fc010b9749f6fcd48350aa792582 --- .../Features/Subscription/SubscriptionController.js | 8 ++++++-- .../src/Features/Subscription/SubscriptionHandler.js | 11 ++++++----- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/services/web/app/src/Features/Subscription/SubscriptionController.js b/services/web/app/src/Features/Subscription/SubscriptionController.js index fe23512901..be2f409b86 100644 --- a/services/web/app/src/Features/Subscription/SubscriptionController.js +++ b/services/web/app/src/Features/Subscription/SubscriptionController.js @@ -468,6 +468,7 @@ async function previewSubscription(req, res, next) { if (!planCode) { return HttpErrorHandler.notFound(req, res, 'Missing plan code') } + // TODO: use PaymentService to fetch plan information const plan = await RecurlyClient.promises.getPlan(planCode) const userId = SessionManager.getLoggedInUserId(req.session) const subscriptionChange = @@ -475,14 +476,17 @@ async function previewSubscription(req, res, next) { userId, planCode ) - const paymentMethod = await RecurlyClient.promises.getPaymentMethod(userId) + const paymentMethod = await Modules.promises.hooks.fire( + 'getPaymentMethod', + userId + ) const changePreview = makeChangePreview( { type: 'premium-subscription', plan: { code: plan.code, name: plan.name }, }, subscriptionChange, - paymentMethod + paymentMethod[0] ) res.render('subscriptions/preview-change', { changePreview }) diff --git a/services/web/app/src/Features/Subscription/SubscriptionHandler.js b/services/web/app/src/Features/Subscription/SubscriptionHandler.js index ecf05695da..332aa0d8ca 100644 --- a/services/web/app/src/Features/Subscription/SubscriptionHandler.js +++ b/services/web/app/src/Features/Subscription/SubscriptionHandler.js @@ -73,11 +73,12 @@ async function createSubscription(user, subscriptionDetails, recurlyTokenIds) { * @return {Promise} */ async function previewSubscriptionChange(userId, planCode) { - const subscription = await getSubscriptionForUser(userId) - const changeRequest = subscription?.getRequestForPlanChange(planCode) - const change = - await RecurlyClient.promises.previewSubscriptionChange(changeRequest) - return change + const change = await Modules.promises.hooks.fire( + 'previewSubscriptionChange', + userId, + planCode + ) + return change[0] } /** From ab37e18bc33d09be17e9abeb8706797ee4aff714 Mon Sep 17 00:00:00 2001 From: Domagoj Kriskovic Date: Mon, 19 May 2025 15:06:25 +0200 Subject: [PATCH 157/194] Update AI Assist wording on checkout page if not in rollout (#25689) (#25733) * Update AI Assist wording on checkout page if not in rollout * update wording when buyin addon GitOrigin-RevId: 7133b4fd3efac8e8a7361dcc15d54367f809f16d --- .../Subscription/SubscriptionController.js | 6 +++ .../web/frontend/extracted-translations.json | 2 + .../preview-subscription-change/root.tsx | 47 +++++++++++++------ .../user/subscription/preview-change.tsx | 7 ++- services/web/locales/en.json | 2 + 5 files changed, 49 insertions(+), 15 deletions(-) diff --git a/services/web/app/src/Features/Subscription/SubscriptionController.js b/services/web/app/src/Features/Subscription/SubscriptionController.js index be2f409b86..d0025c460b 100644 --- a/services/web/app/src/Features/Subscription/SubscriptionController.js +++ b/services/web/app/src/Features/Subscription/SubscriptionController.js @@ -379,6 +379,12 @@ async function previewAddonPurchase(req, res) { paymentMethod ) + await SplitTestHandler.promises.getAssignment( + req, + res, + 'overleaf-assist-bundle' + ) + res.render('subscriptions/preview-change', { changePreview, purchaseReferrer, diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json index bb5d208f12..6c5af98c38 100644 --- a/services/web/frontend/extracted-translations.json +++ b/services/web/frontend/extracted-translations.json @@ -74,6 +74,8 @@ "add_company_details": "", "add_email_address": "", "add_email_to_claim_features": "", + "add_error_assist_annual_to_your_projects": "", + "add_error_assist_to_your_projects": "", "add_files": "", "add_more_collaborators": "", "add_more_licenses_to_my_plan": "", diff --git a/services/web/frontend/js/features/subscription/components/preview-subscription-change/root.tsx b/services/web/frontend/js/features/subscription/components/preview-subscription-change/root.tsx index 705af73e27..367a5e35a9 100644 --- a/services/web/frontend/js/features/subscription/components/preview-subscription-change/root.tsx +++ b/services/web/frontend/js/features/subscription/components/preview-subscription-change/root.tsx @@ -20,6 +20,7 @@ import OLButton from '@/features/ui/components/ol/ol-button' import { subscriptionUpdateUrl } from '@/features/subscription/data/subscription-url' import * as eventTracking from '@/infrastructure/event-tracking' import sparkleText from '@/shared/svgs/ai-sparkle-text.svg' +import { useFeatureFlag } from '@/shared/context/split-test-context' function PreviewSubscriptionChange() { const preview = getMeta( @@ -29,6 +30,7 @@ function PreviewSubscriptionChange() { const { t } = useTranslation() const payNowTask = useAsync() const location = useLocation() + const aiAssistEnabled = useFeatureFlag('overleaf-assist-bundle') useEffect(() => { if (preview.change.type === 'add-on-purchase') { @@ -107,20 +109,37 @@ function PreviewSubscriptionChange() { {aiAddOnChange && (
    -
    )} diff --git a/services/web/frontend/js/pages/user/subscription/preview-change.tsx b/services/web/frontend/js/pages/user/subscription/preview-change.tsx index 7b4b4c68b0..9ff5f14d45 100644 --- a/services/web/frontend/js/pages/user/subscription/preview-change.tsx +++ b/services/web/frontend/js/pages/user/subscription/preview-change.tsx @@ -1,9 +1,14 @@ import '@/marketing' import { createRoot } from 'react-dom/client' import PreviewSubscriptionChange from '@/features/subscription/components/preview-subscription-change/root' +import { SplitTestProvider } from '@/shared/context/split-test-context' const element = document.getElementById('subscription-preview-change') if (element) { const root = createRoot(element) - root.render() + root.render( + + + + ) } diff --git a/services/web/locales/en.json b/services/web/locales/en.json index ddf96c30bc..6495a24fbd 100644 --- a/services/web/locales/en.json +++ b/services/web/locales/en.json @@ -88,6 +88,8 @@ "add_email": "Add Email", "add_email_address": "Add email address", "add_email_to_claim_features": "Add an institutional email address to claim your features.", + "add_error_assist_annual_to_your_projects": "Add Error Assist Annual to your projects and get unlimited AI help to fix LaTeX errors faster.", + "add_error_assist_to_your_projects": "Add Error Assist to your projects and get unlimited AI help to fix LaTeX errors faster.", "add_files": "Add Files", "add_more_collaborators": "Add more collaborators", "add_more_licenses_to_my_plan": "Add more licenses to my plan", From d680544b690b5832d5d158a06b87c55cba23f0a9 Mon Sep 17 00:00:00 2001 From: roo hutton Date: Mon, 19 May 2025 15:07:08 +0100 Subject: [PATCH 158/194] Merge pull request #24787 from overleaf/rh-sort-account-emails Sort user emails by primary>confirmed>alphabetical GitOrigin-RevId: 1d166e424e3848b83110c1a87adfff7790c1a01f --- .../settings/components/emails-section.tsx | 16 +++++- .../settings/components/emails/row.tsx | 2 +- .../components/emails/emails-section.test.tsx | 52 +++++++++++++++++++ 3 files changed, 68 insertions(+), 2 deletions(-) diff --git a/services/web/frontend/js/features/settings/components/emails-section.tsx b/services/web/frontend/js/features/settings/components/emails-section.tsx index a8bc54b017..e58dcd213a 100644 --- a/services/web/frontend/js/features/settings/components/emails-section.tsx +++ b/services/web/frontend/js/features/settings/components/emails-section.tsx @@ -26,6 +26,20 @@ function EmailsSectionContent() { // Only show the "add email" button if the user has permission to add a secondary email const hideAddSecondaryEmail = getMeta('ol-cannot-add-secondary-email') + // Sort emails: primary first, then confirmed secondary emails, then unconfirmed secondary emails + const sortedUserEmails = [...userEmails].sort((a, b) => { + // Primary email comes first + if (a.default) return -1 + if (b.default) return 1 + + // Then sort by confirmation status + if (a.confirmedAt && !b.confirmedAt) return -1 + if (!a.confirmedAt && b.confirmedAt) return 1 + + // If both have the same status, sort by email string + return a.email.localeCompare(b.email) + }) + return ( <>

    {t('emails_and_affiliations_title')}

    @@ -54,7 +68,7 @@ function EmailsSectionContent() {
    ) : ( <> - {userEmails?.map(userEmail => ( + {sortedUserEmails.map(userEmail => (
    diff --git a/services/web/frontend/js/features/settings/components/emails/row.tsx b/services/web/frontend/js/features/settings/components/emails/row.tsx index fd74281ad8..3cbb84ef9b 100644 --- a/services/web/frontend/js/features/settings/components/emails/row.tsx +++ b/services/web/frontend/js/features/settings/components/emails/row.tsx @@ -28,7 +28,7 @@ function EmailsRow({ userEmailData, primary }: EmailsRowProps) { return ( <> - + diff --git a/services/web/test/frontend/features/settings/components/emails/emails-section.test.tsx b/services/web/test/frontend/features/settings/components/emails/emails-section.test.tsx index 243d85533a..e784f6aaac 100644 --- a/services/web/test/frontend/features/settings/components/emails/emails-section.test.tsx +++ b/services/web/test/frontend/features/settings/components/emails/emails-section.test.tsx @@ -163,4 +163,56 @@ describe('', function () { screen.getByText(/sorry, something went wrong/i) screen.getByRole('button', { name: /resend confirmation code/i }) }) + + it('sorts emails with primary first, then confirmed, then unconfirmed', async function () { + const unconfirmedEmail = { ...unconfirmedUserData, email: 'b@example.com' } + const unconfirmedEmailTwo = { + ...unconfirmedUserData, + email: 'd@example.com', + } + const confirmedEmail = { + ...confirmedUserData, + email: 'a@example.com', + confirmedAt: new Date().toISOString(), + } + const confirmedEmailTwo = { + ...confirmedUserData, + email: 'e@example.com', + confirmedAt: new Date().toISOString(), + } + const primaryEmail = { + ...professionalUserData, + email: 'c@example.com', + default: true, + } + + const emails = [ + confirmedEmailTwo, + unconfirmedEmailTwo, + unconfirmedEmail, + confirmedEmail, + primaryEmail, + ] + + fetchMock.get('/user/emails?ensureAffiliation=true', emails) + render() + + await waitForElementToBeRemoved(() => screen.getByText(/loading/i)) + + const emailElements = screen.getAllByTestId(/email-row/i) + + // Primary should be first regardless of alphabetical order + expect(within(emailElements[0]).getByText('c@example.com')).to.exist + expect(within(emailElements[0]).getByText('Primary')).to.exist + + // Confirmed should be second in alphabetical order + expect(within(emailElements[1]).getByText('a@example.com')).to.exist + expect(within(emailElements[2]).getByText('e@example.com')).to.exist + + // Unconfirmed should be last in alphabetical order + expect(within(emailElements[3]).getByText('b@example.com')).to.exist + expect(within(emailElements[3]).getByText(/unconfirmed/i)).to.exist + expect(within(emailElements[4]).getByText('d@example.com')).to.exist + expect(within(emailElements[4]).getByText(/unconfirmed/i)).to.exist + }) }) From d8c5b74e09def3049d421ba9e805421d8ec1b061 Mon Sep 17 00:00:00 2001 From: roo hutton Date: Mon, 19 May 2025 15:12:34 +0100 Subject: [PATCH 159/194] Merge pull request #24855 from overleaf/rh-pug-contact-ui Pug BS5 contact form UI updates GitOrigin-RevId: 2a2428c89f799913ad5c0f7a607d59735334aeda --- services/web/frontend/js/features/contact-form/search.js | 3 ++- services/web/frontend/js/features/form-helpers/hydrate-form.js | 2 +- services/web/frontend/stylesheets/app/contact-us.less | 3 ++- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/services/web/frontend/js/features/contact-form/search.js b/services/web/frontend/js/features/contact-form/search.js index dddba6781f..10e2ab2f63 100644 --- a/services/web/frontend/js/features/contact-form/search.js +++ b/services/web/frontend/js/features/contact-form/search.js @@ -46,7 +46,8 @@ export function setupSearch(formEl) { linkEl.append(contentEl) const iconEl = document.createElement('i') - iconEl.className = 'fa fa-angle-right' + iconEl.className = 'material-symbols dropdown-item-trailing-icon' + iconEl.innerText = 'open_in_new' iconEl.setAttribute('aria-hidden', 'true') linkEl.append(iconEl) diff --git a/services/web/frontend/js/features/form-helpers/hydrate-form.js b/services/web/frontend/js/features/form-helpers/hydrate-form.js index 3febf861b7..ed7b9fc26e 100644 --- a/services/web/frontend/js/features/form-helpers/hydrate-form.js +++ b/services/web/frontend/js/features/form-helpers/hydrate-form.js @@ -375,7 +375,7 @@ function formSentHelper(el) { } function formValidationHelper(el) { - el.querySelectorAll('input').forEach(inputEl => { + el.querySelectorAll('input, textarea').forEach(inputEl => { if ( inputEl.willValidate && !inputEl.hasAttribute('data-ol-no-custom-form-validation-messages') diff --git a/services/web/frontend/stylesheets/app/contact-us.less b/services/web/frontend/stylesheets/app/contact-us.less index 48400dee10..e72c620676 100644 --- a/services/web/frontend/stylesheets/app/contact-us.less +++ b/services/web/frontend/stylesheets/app/contact-us.less @@ -59,7 +59,8 @@ } } - .fa { + .fa, + .material-symbols { display: table-cell; text-align: right; color: @gray-lighter; From 14d6600fb5736b80aed6c5d1ba5eb922492029ce Mon Sep 17 00:00:00 2001 From: M Fahru Date: Mon, 19 May 2025 10:52:31 -0700 Subject: [PATCH 160/194] Merge pull request #22908 from overleaf/mf-fix-disable-element-bs5-anchor-tag [web] Fix `disableElement` won't properly disable the element if using bs5 and applied on anchor tag GitOrigin-RevId: 49ce8514be3e44e5e3a45f41751c94c77f34399b --- .../frontend/js/features/utils/disableElement.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/services/web/frontend/js/features/utils/disableElement.js b/services/web/frontend/js/features/utils/disableElement.js index a60f1e9b98..7d423a26a3 100644 --- a/services/web/frontend/js/features/utils/disableElement.js +++ b/services/web/frontend/js/features/utils/disableElement.js @@ -1,9 +1,19 @@ +import { isBootstrap5 } from './bootstrap-5' + export function disableElement(el) { - el.setAttribute('disabled', '') + if (isBootstrap5() && el.tagName.toLowerCase() === 'a') { + el.classList.add('disabled') + } else { + el.disabled = true + } el.setAttribute('aria-disabled', 'true') } export function enableElement(el) { - el.removeAttribute('disabled') + if (isBootstrap5() && el.tagName.toLowerCase() === 'a') { + el.classList.remove('disabled') + } else { + el.disabled = false + } el.removeAttribute('aria-disabled') } From 26f27d32a14a66f1fb56ff5c75ff5b8848933986 Mon Sep 17 00:00:00 2001 From: CloudBuild Date: Tue, 20 May 2025 01:53:11 +0000 Subject: [PATCH 161/194] auto update translation GitOrigin-RevId: 8d2e4b1682fe0735260ca5f48cf5617966497fa9 --- services/web/locales/da.json | 1 - services/web/locales/zh-CN.json | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/services/web/locales/da.json b/services/web/locales/da.json index cc7ba02521..86911ff083 100644 --- a/services/web/locales/da.json +++ b/services/web/locales/da.json @@ -1114,7 +1114,6 @@ "more_comments": "Flere kommentarer", "more_info": "Mere info", "more_options": "Flere muligheder", - "more_options_for_border_settings_coming_soon": "Flere muligheder for kantindstillinger kommer snart.", "more_project_collaborators": "<0>Flere <0>samarbejdspartnere i projekter", "more_than_one_kind_of_snippet_was_requested": "Linket til at åbne dette indhold i Overleaf havde nogle ugyldige parametre. Hvis du bliver ved med at opleve det her med links fra en bestemt side, bliver du næsten nødt til at fortælle dem om det.", "most_popular_uppercase": "Mest populære", diff --git a/services/web/locales/zh-CN.json b/services/web/locales/zh-CN.json index 96f04dfdc3..6611d0f31d 100644 --- a/services/web/locales/zh-CN.json +++ b/services/web/locales/zh-CN.json @@ -83,6 +83,8 @@ "add_email": "添加电子邮件", "add_email_address": "添加邮件地址", "add_email_to_claim_features": "添加一个机构电子邮件地址来声明您的功能。", + "add_error_assist_annual_to_your_projects": "将 Error Assist Annual 添加到您的项目中并获得无限的 AI 修复帮助,以更快地修复 LaTeX 错误。", + "add_error_assist_to_your_projects": "将错误辅助 添加到您的项目中并获得无限的 AI 帮助,以更快地修复 LaTeX 错误。", "add_files": "添加文件", "add_more_collaborators": "添加更多协作者", "add_more_licenses_to_my_plan": "为我的计划添加更多许可证", @@ -239,7 +241,6 @@ "by_joining_labs": "加入实验室即表示您同意接收 Overleaf 不定期发送的电子邮件和更新信息(例如,征求您的反馈)。您还同意我们的<0>服务条款和<1>隐私声明。", "by_registering_you_agree_to_our_terms_of_service": "注册即表示您同意我们的 <0>服务条款 和 <1>隐私条款。", "by_subscribing_you_agree_to_our_terms_of_service": "订阅即表示您同意我们的<0>服务条款。", - "can_edit_content": "允许编辑", "can_link_institution_email_acct_to_institution_acct": "您现在可以将您的 __appName__ 账户 __email__ 与您的 __institutionName__ 机构账户关联。", "can_link_institution_email_by_clicking": "您可以通过单击 __clickText__ 将您的 __email__ __appName__ 账户链接到您的 __institutionName__ 帐户。", "can_link_institution_email_to_login": "您可以将您的 __email__ __appName__ 账户链接到你的 __institutionName__ 账户,这将允许您通过机构门户登录到__appName__ 。", @@ -1322,7 +1323,6 @@ "more_compile_time": "更长的编译时间", "more_info": "更多信息", "more_options": "更多选择", - "more_options_for_border_settings_coming_soon": "更多的边框设置选项即将推出。", "more_project_collaborators": "<0>更多项目<0>合作者", "more_than_one_kind_of_snippet_was_requested": "在Overleaf打开此内容的链接包含一些无效参数。如果某个网站的链接经常出现这种情况,请向他们报告。", "most_popular_uppercase": "最受欢迎的", From efc42481da79ba5a8a7985e5552c3d199b839d5a Mon Sep 17 00:00:00 2001 From: Jakob Ackermann Date: Tue, 20 May 2025 08:20:00 +0100 Subject: [PATCH 162/194] [clsi] avoid creating legacy db folder (#25760) GitOrigin-RevId: eaa97813f0e20d14d7d6dafefe7b8b4d002fa41c --- services/clsi/entrypoint.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/services/clsi/entrypoint.sh b/services/clsi/entrypoint.sh index bb551c91eb..b45899ab17 100755 --- a/services/clsi/entrypoint.sh +++ b/services/clsi/entrypoint.sh @@ -8,7 +8,6 @@ usermod -aG dockeronhost node # compatibility: initial volume setup mkdir -p /overleaf/services/clsi/cache && chown node:node /overleaf/services/clsi/cache mkdir -p /overleaf/services/clsi/compiles && chown node:node /overleaf/services/clsi/compiles -mkdir -p /overleaf/services/clsi/db && chown node:node /overleaf/services/clsi/db mkdir -p /overleaf/services/clsi/output && chown node:node /overleaf/services/clsi/output exec runuser -u node -- "$@" From f56645ecfec8841a7bfe664bd5778fa7e594b6aa Mon Sep 17 00:00:00 2001 From: Jakob Ackermann Date: Tue, 20 May 2025 08:20:31 +0100 Subject: [PATCH 163/194] [server-ce] develop: fix mismatching bind-mount and env for clsi output (#25761) GitOrigin-RevId: e907bc183360d253f446ed304108974d1f0b034d --- develop/docker-compose.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/develop/docker-compose.yml b/develop/docker-compose.yml index e37999f71d..750e11ac87 100644 --- a/develop/docker-compose.yml +++ b/develop/docker-compose.yml @@ -1,6 +1,5 @@ volumes: clsi-cache: - clsi-output: filestore-public-files: filestore-template-files: filestore-uploads: @@ -33,9 +32,9 @@ services: user: root volumes: - ${PWD}/compiles:/overleaf/services/clsi/compiles + - ${PWD}/output:/overleaf/services/clsi/output - ${DOCKER_SOCKET_PATH:-/var/run/docker.sock}:/var/run/docker.sock - clsi-cache:/overleaf/services/clsi/cache - - clsi-output:/overleaf/services/clsi/output contacts: build: From 1e6b13f9d5a59c282d0add87912a34867ab3ef82 Mon Sep 17 00:00:00 2001 From: Jakob Ackermann Date: Tue, 20 May 2025 08:22:57 +0100 Subject: [PATCH 164/194] [history-ot] rename remaining history-v1-ot references to history-ot (#25428) * [history-ot] rename remaining history-v1-ot references to history-ot * [web] rename History-v1 OT -> History OT in admin panel * [web] rename OT Migration -> History OT Migration in admin panel GitOrigin-RevId: 103ce816d5320d6379d51009cdc08b8a71aa48e6 --- .../document-updater/app/js/DocumentManager.js | 4 ++-- ...TUpdateManager.js => HistoryOTUpdateManager.js} | 8 ++++---- services/document-updater/app/js/HttpController.js | 4 ++-- services/document-updater/app/js/UpdateManager.js | 6 +++--- services/document-updater/app/js/types.ts | 2 +- .../acceptance/js/ApplyingUpdatesToADocTests.js | 14 +++++++------- .../test/acceptance/js/SettingADocumentTests.js | 4 ++-- .../js/DocumentManager/DocumentManagerTests.js | 4 ++-- .../project-history/app/js/UpdateCompressor.js | 10 ++++------ .../project-history/app/js/UpdateTranslator.js | 4 ++-- services/project-history/app/js/types.ts | 2 +- .../real-time/app/js/DocumentUpdaterManager.js | 2 +- services/real-time/app/js/WebsocketController.js | 4 ++-- .../real-time/test/acceptance/js/JoinDocTests.js | 2 +- .../test/unit/js/DocumentUpdaterManagerTests.js | 2 +- .../ide-react/editor/document-container.ts | 4 ++-- 16 files changed, 37 insertions(+), 39 deletions(-) rename services/document-updater/app/js/{HistoryV1OTUpdateManager.js => HistoryOTUpdateManager.js} (94%) diff --git a/services/document-updater/app/js/DocumentManager.js b/services/document-updater/app/js/DocumentManager.js index 2ff31b8c41..4803056423 100644 --- a/services/document-updater/app/js/DocumentManager.js +++ b/services/document-updater/app/js/DocumentManager.js @@ -176,7 +176,7 @@ const DocumentManager = { } // Circular dependencies. Import at runtime. - const HistoryV1OTUpdateManager = require('./HistoryV1OTUpdateManager') + const HistoryOTUpdateManager = require('./HistoryOTUpdateManager') const UpdateManager = require('./UpdateManager') const { @@ -246,7 +246,7 @@ const DocumentManager = { // removed from redis. if (op.length > 0) { if (type === 'history-ot') { - await HistoryV1OTUpdateManager.applyUpdate(projectId, docId, update) + await HistoryOTUpdateManager.applyUpdate(projectId, docId, update) } else { await UpdateManager.promises.applyUpdate(projectId, docId, update) } diff --git a/services/document-updater/app/js/HistoryV1OTUpdateManager.js b/services/document-updater/app/js/HistoryOTUpdateManager.js similarity index 94% rename from services/document-updater/app/js/HistoryV1OTUpdateManager.js rename to services/document-updater/app/js/HistoryOTUpdateManager.js index ca3183ec62..5a8b92099e 100644 --- a/services/document-updater/app/js/HistoryV1OTUpdateManager.js +++ b/services/document-updater/app/js/HistoryOTUpdateManager.js @@ -16,12 +16,12 @@ const RealTimeRedisManager = require('./RealTimeRedisManager') /** * @typedef {import("./types").Update} Update - * @typedef {import("./types").HistoryV1OTEditOperationUpdate} HistoryV1OTEditOperationUpdate + * @typedef {import("./types").HistoryOTEditOperationUpdate} HistoryOTEditOperationUpdate */ /** * @param {Update} update - * @return {update is HistoryV1OTEditOperationUpdate} + * @return {update is HistoryOTEditOperationUpdate} */ function isHistoryOTEditOperationUpdate(update) { return ( @@ -39,7 +39,7 @@ function isHistoryOTEditOperationUpdate(update) { * * @param {string} projectId * @param {string} docId - * @param {HistoryV1OTEditOperationUpdate} update + * @param {HistoryOTEditOperationUpdate} update * @param {Profiler} profiler */ async function tryApplyUpdate(projectId, docId, update, profiler) { @@ -131,7 +131,7 @@ async function tryApplyUpdate(projectId, docId, update, profiler) { * * @param {string} projectId * @param {string} docId - * @param {HistoryV1OTEditOperationUpdate} update + * @param {HistoryOTEditOperationUpdate} update */ async function applyUpdate(projectId, docId, update) { const profiler = new Profiler('applyUpdate', { diff --git a/services/document-updater/app/js/HttpController.js b/services/document-updater/app/js/HttpController.js index 2a7a40d8b5..0a6ae3b2b4 100644 --- a/services/document-updater/app/js/HttpController.js +++ b/services/document-updater/app/js/HttpController.js @@ -37,7 +37,7 @@ function getDoc(req, res, next) { if (lines == null || version == null) { return next(new Errors.NotFoundError('document not found')) } - if (!Array.isArray(lines) && req.query.historyV1OTSupport !== 'true') { + if (!Array.isArray(lines) && req.query.historyOTSupport !== 'true') { const file = StringFileData.fromRaw(lines) // TODO(24596): tc support for history-ot lines = file.getLines() @@ -91,7 +91,7 @@ function peekDoc(req, res, next) { if (lines == null || version == null) { return next(new Errors.NotFoundError('document not found')) } - if (!Array.isArray(lines) && req.query.historyV1OTSupport !== 'true') { + if (!Array.isArray(lines) && req.query.historyOTSupport !== 'true') { const file = StringFileData.fromRaw(lines) // TODO(24596): tc support for history-ot lines = file.getLines() diff --git a/services/document-updater/app/js/UpdateManager.js b/services/document-updater/app/js/UpdateManager.js index ba37018a14..e5df48575e 100644 --- a/services/document-updater/app/js/UpdateManager.js +++ b/services/document-updater/app/js/UpdateManager.js @@ -15,7 +15,7 @@ const RangesManager = require('./RangesManager') const SnapshotManager = require('./SnapshotManager') const Profiler = require('./Profiler') const { isInsert, isDelete, getDocLength, computeDocHash } = require('./Utils') -const HistoryV1OTUpdateManager = require('./HistoryV1OTUpdateManager') +const HistoryOTUpdateManager = require('./HistoryOTUpdateManager') /** * @import { Ranges, Update, HistoryUpdate } from "./types" @@ -81,8 +81,8 @@ const UpdateManager = { profile.log('getPendingUpdatesForDoc') for (const update of updates) { - if (HistoryV1OTUpdateManager.isHistoryOTEditOperationUpdate(update)) { - await HistoryV1OTUpdateManager.applyUpdate(projectId, docId, update) + if (HistoryOTUpdateManager.isHistoryOTEditOperationUpdate(update)) { + await HistoryOTUpdateManager.applyUpdate(projectId, docId, update) } else { await UpdateManager.applyUpdate(projectId, docId, update) } diff --git a/services/document-updater/app/js/types.ts b/services/document-updater/app/js/types.ts index d630e7fc82..851e62d8c8 100644 --- a/services/document-updater/app/js/types.ts +++ b/services/document-updater/app/js/types.ts @@ -23,7 +23,7 @@ export type Update = { projectHistoryId?: string } -export type HistoryV1OTEditOperationUpdate = Omit & { +export type HistoryOTEditOperationUpdate = Omit & { op: RawEditOperation[] meta: Update['meta'] & { source: string } } diff --git a/services/document-updater/test/acceptance/js/ApplyingUpdatesToADocTests.js b/services/document-updater/test/acceptance/js/ApplyingUpdatesToADocTests.js index 32a94f37cb..39ec6c2ac7 100644 --- a/services/document-updater/test/acceptance/js/ApplyingUpdatesToADocTests.js +++ b/services/document-updater/test/acceptance/js/ApplyingUpdatesToADocTests.js @@ -31,7 +31,7 @@ describe('Applying updates to a doc', function () { op: [this.op], v: this.version, } - this.historyV1OTUpdate = { + this.historyOTUpdate = { doc: this.doc_id, op: [{ textOperation: [4, 'one and a half\n', 9] }], v: this.version, @@ -301,7 +301,7 @@ describe('Applying updates to a doc', function () { DocUpdaterClient.sendUpdate( this.project_id, this.doc_id, - this.historyV1OTUpdate, + this.historyOTUpdate, error => { if (error != null) { throw error @@ -352,7 +352,7 @@ describe('Applying updates to a doc', function () { if (error != null) { throw error } - JSON.parse(updates[0]).op.should.deep.equal(this.historyV1OTUpdate.op) + JSON.parse(updates[0]).op.should.deep.equal(this.historyOTUpdate.op) JSON.parse(updates[0]).meta.pathname.should.equal('/a/b/c.tex') done() @@ -395,7 +395,7 @@ describe('Applying updates to a doc', function () { describe('when sending another update', function () { beforeEach(function (done) { this.timeout(10000) - this.second_update = Object.assign({}, this.historyV1OTUpdate) + this.second_update = Object.assign({}, this.historyOTUpdate) this.second_update.op = [ { textOperation: [4, 'one and a half\n', 24], @@ -664,7 +664,7 @@ describe('Applying updates to a doc', function () { DocUpdaterClient.sendUpdate( this.project_id, this.doc_id, - this.historyV1OTUpdate, + this.historyOTUpdate, error => { if (error != null) { throw error @@ -694,7 +694,7 @@ describe('Applying updates to a doc', function () { -1, (error, updates) => { if (error) return done(error) - JSON.parse(updates[0]).op.should.deep.equal(this.historyV1OTUpdate.op) + JSON.parse(updates[0]).op.should.deep.equal(this.historyOTUpdate.op) JSON.parse(updates[0]).meta.pathname.should.equal('/a/b/c.tex') done() } @@ -979,7 +979,7 @@ describe('Applying updates to a doc', function () { DocUpdaterClient.sendUpdate( this.project_id, this.doc_id, - this.historyV1OTUpdate, + this.historyOTUpdate, error => { if (error != null) { throw error diff --git a/services/document-updater/test/acceptance/js/SettingADocumentTests.js b/services/document-updater/test/acceptance/js/SettingADocumentTests.js index 83e662185a..fd1851a221 100644 --- a/services/document-updater/test/acceptance/js/SettingADocumentTests.js +++ b/services/document-updater/test/acceptance/js/SettingADocumentTests.js @@ -201,7 +201,7 @@ describe('Setting a document', function () { numberOfReceivedUpdates = 0 this.project_id = DocUpdaterClient.randomId() this.doc_id = DocUpdaterClient.randomId() - this.historyV1OTUpdate = { + this.historyOTUpdate = { doc: this.doc_id, op: [{ textOperation: [4, 'one and a half\n', 9] }], v: this.version, @@ -219,7 +219,7 @@ describe('Setting a document', function () { DocUpdaterClient.sendUpdate( this.project_id, this.doc_id, - this.historyV1OTUpdate, + this.historyOTUpdate, error => { if (error) { throw error diff --git a/services/document-updater/test/unit/js/DocumentManager/DocumentManagerTests.js b/services/document-updater/test/unit/js/DocumentManager/DocumentManagerTests.js index f0effb4c24..1816579103 100644 --- a/services/document-updater/test/unit/js/DocumentManager/DocumentManagerTests.js +++ b/services/document-updater/test/unit/js/DocumentManager/DocumentManagerTests.js @@ -49,7 +49,7 @@ describe('DocumentManager', function () { applyUpdate: sinon.stub().resolves(), }, } - this.HistoryV1OTUpdateManager = { + this.HistoryOTUpdateManager = { applyUpdate: sinon.stub().resolves(), } this.RangesManager = { @@ -69,7 +69,7 @@ describe('DocumentManager', function () { './Metrics': this.Metrics, './DiffCodec': this.DiffCodec, './UpdateManager': this.UpdateManager, - './HistoryV1OTUpdateManager': this.HistoryV1OTUpdateManager, + './HistoryOTUpdateManager': this.HistoryOTUpdateManager, './RangesManager': this.RangesManager, './Errors': Errors, '@overleaf/settings': this.Settings, diff --git a/services/project-history/app/js/UpdateCompressor.js b/services/project-history/app/js/UpdateCompressor.js index 726cb5fff9..471fc791ab 100644 --- a/services/project-history/app/js/UpdateCompressor.js +++ b/services/project-history/app/js/UpdateCompressor.js @@ -231,11 +231,9 @@ function _concatTwoUpdates(firstUpdate, secondUpdate) { return [firstUpdate, secondUpdate] } - const firstUpdateIsHistoryV1OT = EditOperationBuilder.isValid(firstUpdate.op) - const secondUpdateIsHistoryV1OT = EditOperationBuilder.isValid( - secondUpdate.op - ) - if (firstUpdateIsHistoryV1OT !== secondUpdateIsHistoryV1OT) { + const firstUpdateIsHistoryOT = EditOperationBuilder.isValid(firstUpdate.op) + const secondUpdateIsHistoryOT = EditOperationBuilder.isValid(secondUpdate.op) + if (firstUpdateIsHistoryOT !== secondUpdateIsHistoryOT) { // cannot merge mix of sharejs-text-op and history-ot, should not happen. return [firstUpdate, secondUpdate] } @@ -286,7 +284,7 @@ function _concatTwoUpdates(firstUpdate, secondUpdate) { return [firstUpdate, secondUpdate] } - if (firstUpdateIsHistoryV1OT && secondUpdateIsHistoryV1OT) { + if (firstUpdateIsHistoryOT && secondUpdateIsHistoryOT) { const op1 = EditOperationBuilder.fromJSON(firstUpdate.op) const op2 = EditOperationBuilder.fromJSON(secondUpdate.op) if (!op1.canBeComposedWith(op2)) return [firstUpdate, secondUpdate] diff --git a/services/project-history/app/js/UpdateTranslator.js b/services/project-history/app/js/UpdateTranslator.js index c1443ab1cd..43b9f48270 100644 --- a/services/project-history/app/js/UpdateTranslator.js +++ b/services/project-history/app/js/UpdateTranslator.js @@ -7,7 +7,7 @@ import * as OperationsCompressor from './OperationsCompressor.js' import { isInsert, isRetain, isDelete, isComment } from './Utils.js' /** - * @import { AddDocUpdate, AddFileUpdate, DeleteCommentUpdate, HistoryV1OTEditOperationUpdate, Op, RawScanOp } from './types' + * @import { AddDocUpdate, AddFileUpdate, DeleteCommentUpdate, HistoryOTEditOperationUpdate, Op, RawScanOp } from './types' * @import { RenameUpdate, TextUpdate, TrackingDirective, TrackingProps } from './types' * @import { SetCommentStateUpdate, SetFileMetadataOperation, Update, UpdateWithBlob } from './types' */ @@ -206,7 +206,7 @@ export function isTextUpdate(update) { /** * @param {Update} update - * @returns {update is HistoryV1OTEditOperationUpdate} + * @returns {update is HistoryOTEditOperationUpdate} */ export function isHistoryOTEditOperationUpdate(update) { return ( diff --git a/services/project-history/app/js/types.ts b/services/project-history/app/js/types.ts index 23911b0086..96701e587f 100644 --- a/services/project-history/app/js/types.ts +++ b/services/project-history/app/js/types.ts @@ -44,7 +44,7 @@ export type TextUpdate = { } } -export type HistoryV1OTEditOperationUpdate = { +export type HistoryOTEditOperationUpdate = { doc: string op: RawEditOperation[] v: number diff --git a/services/real-time/app/js/DocumentUpdaterManager.js b/services/real-time/app/js/DocumentUpdaterManager.js index 72a37d0f1d..51b71e8ec0 100644 --- a/services/real-time/app/js/DocumentUpdaterManager.js +++ b/services/real-time/app/js/DocumentUpdaterManager.js @@ -19,7 +19,7 @@ const Keys = settings.redis.documentupdater.key_schema const DocumentUpdaterManager = { getDocument(projectId, docId, fromVersion, callback) { const timer = new metrics.Timer('get-document') - const url = `${settings.apis.documentupdater.url}/project/${projectId}/doc/${docId}?fromVersion=${fromVersion}&historyV1OTSupport=true` + const url = `${settings.apis.documentupdater.url}/project/${projectId}/doc/${docId}?fromVersion=${fromVersion}&historyOTSupport=true` logger.debug( { projectId, docId, fromVersion }, 'getting doc from document updater' diff --git a/services/real-time/app/js/WebsocketController.js b/services/real-time/app/js/WebsocketController.js index 17186eb92e..c0f465a490 100644 --- a/services/real-time/app/js/WebsocketController.js +++ b/services/real-time/app/js/WebsocketController.js @@ -309,11 +309,11 @@ module.exports = WebsocketController = { const encodeForWebsockets = text => unescape(encodeURIComponent(text)) metrics.inc('client_supports_history_v1_ot', 1, { - status: options.supportsHistoryV1OT ? 'success' : 'failure', + status: options.supportsHistoryOT ? 'success' : 'failure', }) let escapedLines if (type === 'history-ot') { - if (!options.supportsHistoryV1OT) { + if (!options.supportsHistoryOT) { RoomManager.leaveDoc(client, docId) // TODO(24596): ask the user to reload the editor page (via out-of-sync modal when there are pending ops). return callback( diff --git a/services/real-time/test/acceptance/js/JoinDocTests.js b/services/real-time/test/acceptance/js/JoinDocTests.js index cdb5489ec9..3381526c59 100644 --- a/services/real-time/test/acceptance/js/JoinDocTests.js +++ b/services/real-time/test/acceptance/js/JoinDocTests.js @@ -641,7 +641,7 @@ describe('joinDoc', function () { this.client.emit( 'joinDoc', this.doc_id, - { supportsHistoryV1OT: true }, + { supportsHistoryOT: true }, (error, ...rest) => { ;[...this.returnedArgs] = Array.from(rest) cb(error) diff --git a/services/real-time/test/unit/js/DocumentUpdaterManagerTests.js b/services/real-time/test/unit/js/DocumentUpdaterManagerTests.js index d9755fef57..ecf45cd452 100644 --- a/services/real-time/test/unit/js/DocumentUpdaterManagerTests.js +++ b/services/real-time/test/unit/js/DocumentUpdaterManagerTests.js @@ -79,7 +79,7 @@ describe('DocumentUpdaterManager', function () { }) it('should get the document from the document updater', function () { - const url = `${this.settings.apis.documentupdater.url}/project/${this.project_id}/doc/${this.doc_id}?fromVersion=${this.fromVersion}&historyV1OTSupport=true` + const url = `${this.settings.apis.documentupdater.url}/project/${this.project_id}/doc/${this.doc_id}?fromVersion=${this.fromVersion}&historyOTSupport=true` return this.request.get.calledWith(url).should.equal(true) }) diff --git a/services/web/frontend/js/features/ide-react/editor/document-container.ts b/services/web/frontend/js/features/ide-react/editor/document-container.ts index 1770894584..fee359f146 100644 --- a/services/web/frontend/js/features/ide-react/editor/document-container.ts +++ b/services/web/frontend/js/features/ide-react/editor/document-container.ts @@ -451,7 +451,7 @@ export class DocumentContainer extends EventEmitter { { encodeRanges: true, age: this.doc.getTimeSinceLastServerActivity(), - supportsHistoryV1OT: true, + supportsHistoryOT: true, }, ( error, @@ -487,7 +487,7 @@ export class DocumentContainer extends EventEmitter { this.doc_id, { encodeRanges: true, - supportsHistoryV1OT: true, + supportsHistoryOT: true, }, ( error, From 13544655628701cf92c86a8e367a0770c7d14243 Mon Sep 17 00:00:00 2001 From: David <33458145+davidmcpowell@users.noreply.github.com> Date: Tue, 20 May 2025 09:39:14 +0100 Subject: [PATCH 165/194] Merge pull request #25727 from overleaf/dp-fix-track-changes-e2e-tests Fix broken E2E tests after reviewer role feature flag cleanup GitOrigin-RevId: 9860d546d8a6385554f223bf15c10875089ea130 --- server-ce/test/editor.spec.ts | 136 +----------------------------- server-ce/test/helpers/project.ts | 34 -------- 2 files changed, 1 insertion(+), 169 deletions(-) diff --git a/server-ce/test/editor.spec.ts b/server-ce/test/editor.spec.ts index 648c55a907..62eeeda26b 100644 --- a/server-ce/test/editor.spec.ts +++ b/server-ce/test/editor.spec.ts @@ -1,11 +1,7 @@ import { createNewFile, createProject, - enableLinkSharing, - openFile, openProjectById, - openProjectViaLinkSharingAsUser, - toggleTrackChanges, } from './helpers/project' import { isExcludedBySharding, startWith } from './helpers/config' import { ensureUserExists, login } from './helpers/login' @@ -71,7 +67,7 @@ describe('editor', () => { cy.log('remove word from dictionary') cy.get('button').contains('Menu').click() - cy.get('button').contains('Edit').click() + cy.get('button#dictionary-settings').contains('Edit').click() cy.get('[id="dictionary-modal"]').within(() => { cy.findByText(word) .parent() @@ -92,136 +88,6 @@ describe('editor', () => { }) }) - describe('collaboration', () => { - beforeWithReRunOnTestRetry(function () { - enableLinkSharing().then(({ linkSharingReadAndWrite }) => { - const email = 'collaborator@example.com' - login(email) - openProjectViaLinkSharingAsUser( - linkSharingReadAndWrite, - projectName, - email - ) - }) - - login('user@example.com') - waitForCompileRateLimitCoolOff(() => { - openProjectById(projectId) - }) - }) - - it('track-changes', () => { - cy.log('disable track-changes before populating doc') - toggleTrackChanges(false) - - const fileName = createNewFile() - const oldContent = 'oldContent' - cy.get('.cm-line').type(`${oldContent}\n\nstatic`) - - cy.log('recompile to force flush') - recompile() - - cy.log('enable track-changes for everyone') - toggleTrackChanges(true) - - login('collaborator@example.com') - waitForCompileRateLimitCoolOff(() => { - openProjectById(projectId) - }) - openFile(fileName, 'static') - - cy.log('make changes in main file') - // cy.type() "clicks" in the center of the selected element before typing. This "click" discards the text as selected by the dblclick. - // Go down to the lower level event based typing, the frontend tests in web use similar events. - cy.get('.cm-editor').as('editor') - cy.get('@editor').findByText(oldContent).dblclick() - cy.get('@editor').trigger('keydown', { key: 'Delete' }) - cy.get('@editor').trigger('keydown', { key: 'Enter' }) - cy.get('@editor').trigger('keydown', { key: 'Enter' }) - - cy.log('recompile to force flush') - recompile() - - login('user@example.com') - waitForCompileRateLimitCoolOff(() => { - openProjectById(projectId) - }) - openFile(fileName, 'static') - - cy.log('reject changes') - cy.contains('.toolbar-item', 'Review').click() - cy.get('.cm-content').should('not.contain.text', oldContent) - cy.findByText('Reject change').click({ force: true }) - cy.contains('.toolbar-item', 'Review').click() - - cy.log('recompile to force flush') - recompile() - - cy.log('verify the changes are applied') - cy.get('.cm-content').should('contain.text', oldContent) - - cy.log('disable track-changes for everyone again') - toggleTrackChanges(false) - }) - - it('track-changes rich text', () => { - cy.log('disable track-changes before populating doc') - toggleTrackChanges(false) - - const fileName = createNewFile() - const oldContent = 'oldContent' - cy.get('.cm-line').type(`\\section{{}${oldContent}}\n\nstatic`) - - cy.log('recompile to force flush') - recompile() - - cy.log('enable track-changes for everyone') - toggleTrackChanges(true) - - login('collaborator@example.com') - waitForCompileRateLimitCoolOff(() => { - openProjectById(projectId) - }) - cy.log('enable visual editor and make changes in main file') - cy.findByText('Visual Editor').click() - - openFile(fileName, 'static') - - // cy.type() "clicks" in the center of the selected element before typing. This "click" discards the text as selected by the dblclick. - // Go down to the lower level event based typing, the frontend tests in web use similar events. - cy.get('.cm-editor').as('editor') - cy.get('@editor').findByText(oldContent).dblclick() - cy.get('@editor').trigger('keydown', { key: 'Delete' }) - cy.get('@editor').trigger('keydown', { key: 'Enter' }) - cy.get('@editor').trigger('keydown', { key: 'Enter' }) - - cy.log('recompile to force flush') - recompile() - - login('user@example.com') - waitForCompileRateLimitCoolOff(() => { - openProjectById(projectId) - }) - openFile(fileName, 'static') - - cy.log('reject changes') - cy.contains('.toolbar-item', 'Review').click() - cy.get('.cm-content').should('not.contain.text', oldContent) - cy.findAllByText('Reject change').first().click({ force: true }) - cy.contains('.toolbar-item', 'Review').click() - - cy.log('recompile to force flush') - recompile() - - cy.log('verify the changes are applied in the visual editor') - cy.findByText('Visual Editor').click() - cy.get('.cm-content').should('contain.text', oldContent) - - cy.log('disable track-changes for everyone again') - toggleTrackChanges(false) - }) - }) - describe('editor', () => { it('renders jpg', () => { cy.findByTestId('file-tree').findByText('frog.jpg').click() diff --git a/server-ce/test/helpers/project.ts b/server-ce/test/helpers/project.ts index 662327d6f2..6a2cd74e4a 100644 --- a/server-ce/test/helpers/project.ts +++ b/server-ce/test/helpers/project.ts @@ -215,37 +215,3 @@ export function createNewFile() { return fileName } - -export function toggleTrackChanges(state: boolean) { - cy.findByText('Review').click() - cy.get('.track-changes-menu-button').then(el => { - // when the menu is expanded renders the `expand_more` icon, - // and the `chevron_right` icon when it's collapsed - if (!el.text().includes('expand_more')) { - el.click() - } - }) - - cy.findByText('Everyone') - .parent() - .within(() => { - cy.get('.form-check-input').then(el => { - if (el.prop('checked') === state) return - - const id = uuid() - const alias = `@${id}` - cy.intercept({ - method: 'POST', - url: '**/track_changes', - times: 1, - }).as(id) - if (state) { - cy.get('.form-check-input').check() - } else { - cy.get('.form-check-input').uncheck() - } - cy.wait(alias) - }) - }) - cy.contains('.toolbar-item', 'Review').click() -} From efd55ffe97e7be46af87361acc34b4aaa6e8706d Mon Sep 17 00:00:00 2001 From: Brian Gough Date: Tue, 20 May 2025 09:47:19 +0100 Subject: [PATCH 166/194] Merge pull request #25743 from overleaf/bg-deactivate-projects-script add deactivate projects script GitOrigin-RevId: 5acf4b980d8980457930ee488571362da2a8014c --- .../InactiveData/InactiveProjectManager.js | 54 +++-- .../src/infrastructure/GracefulShutdown.js | 28 +-- services/web/scripts/deactivate_projects.mjs | 214 ++++++++++++++++++ 3 files changed, 261 insertions(+), 35 deletions(-) create mode 100755 services/web/scripts/deactivate_projects.mjs diff --git a/services/web/app/src/Features/InactiveData/InactiveProjectManager.js b/services/web/app/src/Features/InactiveData/InactiveProjectManager.js index 818fe70c08..54bd81a500 100644 --- a/services/web/app/src/Features/InactiveData/InactiveProjectManager.js +++ b/services/web/app/src/Features/InactiveData/InactiveProjectManager.js @@ -11,6 +11,27 @@ const { callbackifyAll } = require('@overleaf/promise-utils') const Metrics = require('@overleaf/metrics') const MILISECONDS_IN_DAY = 86400000 + +function findInactiveProjects(limit, daysOld) { + const oldProjectDate = new Date() - MILISECONDS_IN_DAY * daysOld + try { + // use $not $gt to catch non-opened projects where lastOpened is null + // return a cursor instead of executing the query + return Project.find({ + lastOpened: { $not: { $gt: oldProjectDate } }, + }) + .where('active') + .equals(true) + .select(['_id', 'lastOpened']) + .limit(limit) + .read(READ_PREFERENCE_SECONDARY) + .cursor() + } catch (err) { + logger.err({ err }, 'could not get projects for deactivating') + throw err // Re-throw the error to be handled by the caller + } +} + const InactiveProjectManager = { async reactivateProjectIfRequired(projectId) { let project @@ -53,30 +74,13 @@ const InactiveProjectManager = { if (daysOld == null) { daysOld = 360 } - const oldProjectDate = new Date() - MILISECONDS_IN_DAY * daysOld - let projects - try { - // use $not $gt to catch non-opened projects where lastOpened is null - projects = await Project.find({ - lastOpened: { $not: { $gt: oldProjectDate } }, - }) - .where('active') - .equals(true) - .select('_id') - .limit(limit) - .read(READ_PREFERENCE_SECONDARY) - .exec() - } catch (err) { - logger.err({ err }, 'could not get projects for deactivating') - } + logger.debug('deactivating projects') - logger.debug( - { numberOfProjects: projects && projects.length }, - 'deactivating projects' - ) + const processedProjects = [] - for (const project of projects) { + for await (const project of findInactiveProjects(limit, daysOld)) { + processedProjects.push(project) try { await InactiveProjectManager.deactivateProject(project._id) } catch (err) { @@ -87,7 +91,12 @@ const InactiveProjectManager = { } } - return projects + logger.debug( + { numberOfProjects: processedProjects.length }, + 'finished deactivating projects' + ) + + return processedProjects }, async deactivateProject(projectId) { @@ -126,4 +135,5 @@ const InactiveProjectManager = { module.exports = { ...callbackifyAll(InactiveProjectManager), promises: InactiveProjectManager, + findInactiveProjects, } diff --git a/services/web/app/src/infrastructure/GracefulShutdown.js b/services/web/app/src/infrastructure/GracefulShutdown.js index 2446397b1d..b4b345fb95 100644 --- a/services/web/app/src/infrastructure/GracefulShutdown.js +++ b/services/web/app/src/infrastructure/GracefulShutdown.js @@ -65,20 +65,22 @@ async function gracefulShutdown(server, signal) { true ) - await sleep(Settings.gracefulShutdownDelayInMs) - try { - await new Promise((resolve, reject) => { - logger.warn({}, 'graceful shutdown: closing http server') - server.close(err => { - if (err) { - reject(OError.tag(err, 'http.Server.close failed')) - } else { - resolve() - } + if (server) { + await sleep(Settings.gracefulShutdownDelayInMs) + try { + await new Promise((resolve, reject) => { + logger.warn({}, 'graceful shutdown: closing http server') + server.close(err => { + if (err) { + reject(OError.tag(err, 'http.Server.close failed')) + } else { + resolve() + } + }) }) - }) - } catch (err) { - throw OError.tag(err, 'stop traffic') + } catch (err) { + throw OError.tag(err, 'stop traffic') + } } await runHandlers( diff --git a/services/web/scripts/deactivate_projects.mjs b/services/web/scripts/deactivate_projects.mjs new file mode 100755 index 0000000000..b229af649d --- /dev/null +++ b/services/web/scripts/deactivate_projects.mjs @@ -0,0 +1,214 @@ +#!/usr/bin/env node +import minimist from 'minimist' +import PQueue from 'p-queue' +import InactiveProjectManager from '../app/src/Features/InactiveData/InactiveProjectManager.js' +import { gracefulShutdown } from '../app/src/infrastructure/GracefulShutdown.js' +import logger from '@overleaf/logger' + +// Global variables for tracking job and error counts +let jobCount = 0 +let succeededCount = 0 +let skippedCount = 0 +let failedCount = 0 +let currentAgeInDays = null +let currentLastOpened = null +let DRY_RUN = false +let gracefulShutdownInitiated = false +const SCRIPT_START_TIME = Date.now() +const MAX_RUNTIME_DEFAULT = null +let MAX_RUNTIME = MAX_RUNTIME_DEFAULT // in milliseconds + +// Configure signal handling +process.on('SIGINT', handleSignal) +process.on('SIGTERM', handleSignal) +function handleSignal() { + if (gracefulShutdownInitiated) return + gracefulShutdownInitiated = true + logger.warn( + { gracefulShutdownInitiated }, + 'graceful shutdown initiated, draining queue' + ) +} + +// Check if max runtime has been exceeded +function hasMaxRuntimeExceeded() { + if (MAX_RUNTIME === null) return false + const elapsedTime = Date.now() - SCRIPT_START_TIME + const hasExceeded = elapsedTime >= MAX_RUNTIME + if (hasExceeded && !gracefulShutdownInitiated) { + gracefulShutdownInitiated = true + logger.warn( + { elapsedTimeMs: elapsedTime, maxRuntimeMs: MAX_RUNTIME }, + 'maximum runtime exceeded, initiating graceful shutdown' + ) + } + return hasExceeded +} + +// Calculates the age in days since the provided lastOpened date. +function getAgeFromLastOpened(lastOpened) { + const lastOpenedDate = new Date(lastOpened) + const now = new Date() + return Number(((now - lastOpenedDate) / (1000 * 60 * 60 * 24)).toFixed(2)) +} + +// Deactivates a single project and handles errors +async function deactivateSingleProject(project) { + const { _id: projectId, lastOpened } = project + jobCount++ + + if (lastOpened) { + currentLastOpened = lastOpened + currentAgeInDays = getAgeFromLastOpened(lastOpened) + } + + // Periodic progress logging + if (jobCount % 1000 === 0) { + logger.info( + { jobCount, failedCount, currentAgeInDays }, + 'project deactivation in progress' + ) + } + + // Debug level detail logging + logger.debug( + { projectId, jobCount, failedCount, dryRun: DRY_RUN }, + 'attempting to deactivate project' + ) + + // Dry run handling + if (DRY_RUN) { + logger.info({ projectId }, '[DRY RUN] would deactivate project') + succeededCount++ + } + + // Actual deactivation with error handling + try { + await InactiveProjectManager.promises.deactivateProject(projectId) + logger.debug({ projectId }, 'successfully deactivated project') + succeededCount++ + } catch (error) { + failedCount++ + logger.error({ projectId, err: error }, 'failed to deactivate project') + } +} + +// Centralized project processing function +async function processProjects(projectCursor, concurrency) { + const queue = new PQueue({ concurrency }) + for await (const project of projectCursor) { + if (gracefulShutdownInitiated || hasMaxRuntimeExceeded()) { + skippedCount++ + break + } + await queue.onEmpty() + logger.debug( + { queueSize: queue.size, queuePending: queue.pending }, + 'queue size before adding new job' + ) + queue.add(async () => { + await deactivateSingleProject(project) + }) + } + await queue.onIdle() +} + +const usage = ` +Usage: scripts/deactivate_projects.mjs [options] + +Options: + --limit Max number of projects to process (default: 10) + --daysOld Min age in days for a project to be considered inactive (default: 7) + --concurrency Number of deactivations to run in parallel (default: 1) + --max-time Maximum runtime in seconds before graceful shutdown (default: no limit) + --dry-run, -n Simulate deactivation without making changes (default: false) + --help Display this usage message +` + +async function main() { + const argv = minimist(process.argv.slice(2), { + string: ['limit', 'daysOld', 'concurrency', 'maxTime'], + boolean: ['dryRun', 'help'], + alias: { + dryRun: ['dry-run', 'n'], + maxTime: 'max-time', + help: 'h', + }, + default: { + limit: '10', + daysOld: '7', + concurrency: '1', + maxTime: '', + dryRun: false, + }, + }) + + if (argv.help || process.argv.length <= 2) { + console.log(usage) + process.exit(0) + } + + const limit = parseInt(argv.limit, 10) + const daysOld = parseInt(argv.daysOld, 10) + const concurrency = parseInt(argv.concurrency, 10) + const maxRuntimeInSeconds = parseInt(argv.maxTime, 10) + DRY_RUN = argv.dryRun + MAX_RUNTIME = maxRuntimeInSeconds * 1000 // Convert seconds to milliseconds + + if (DRY_RUN) { + logger.info( + {}, + 'DRY RUN MODE ENABLED: No actual deactivations will be performed' + ) + } + + logger.info( + { + limit, + daysOld, + concurrency, + dryRun: DRY_RUN, + maxRuntimeSeconds: maxRuntimeInSeconds || 'unlimited', + }, + 'finding inactive projects' + ) + + try { + // Find projects to deactivate + const projectCursor = await InactiveProjectManager.findInactiveProjects( + limit, + daysOld + ) + + // Process the projects + await processProjects(projectCursor, concurrency) + } catch (error) { + logger.error({ err: error }, 'critical error during script execution') + process.exitCode = 1 + } finally { + logger.info( + { + jobCount, + succeededCount, + failedCount, + skippedCount, + currentAgeInDays, + currentLastOpened, + elapsedTimeInSeconds: Math.floor( + (Date.now() - SCRIPT_START_TIME) / 1000 + ), + maxRuntimeInSeconds: maxRuntimeInSeconds || 'unlimited', + }, + 'project deactivation process completed' + ) + } +} + +main() + .then(async () => { + await gracefulShutdown() + }) + .catch(err => { + logger.fatal({ err }, 'unhandled error in main execution') + process.exit(1) + }) From bb24aa46d1a43c6cafb7f807432e91e6470725a4 Mon Sep 17 00:00:00 2001 From: Davinder Singh Date: Tue, 20 May 2025 10:51:24 +0100 Subject: [PATCH 167/194] [B2C] Bootstrap 5 migration of `why-latex` page (#25133) * adding temporary rendering of the BS5 version of why-latex page * adding first section on the page with new styling, that is compatible with BS5 * adding next section * adding cards and copy pasting existing styling * using variables instead of direct values * fixing the styling of h3 in info-card * adding next section and its styling * adding variables * adding features card section * adding the next features card * adding the next features card section * adding another card section * adding last feature card section * adding next section * adding next section * adding next section * adding begin now card * running npm run lint:styles:fix * making some style changes to match BS3 version for smaller screen under lg in BS5 * adding a width fix to image * changing breakpoints to bring consistency in stylesheet * adding vars * adding split test * removing the temporary rendering solution for the BS5 page * adding splitTestHandler Stub GitOrigin-RevId: 1257dff09e5371d68e102972e3544559800ca339 --- .../bootstrap-5/pages/website-redesign.scss | 161 ++++++++++++++++++ 1 file changed, 161 insertions(+) diff --git a/services/web/frontend/stylesheets/bootstrap-5/pages/website-redesign.scss b/services/web/frontend/stylesheets/bootstrap-5/pages/website-redesign.scss index bdd168a519..2e069d0599 100644 --- a/services/web/frontend/stylesheets/bootstrap-5/pages/website-redesign.scss +++ b/services/web/frontend/stylesheets/bootstrap-5/pages/website-redesign.scss @@ -262,12 +262,173 @@ height: 20px; } + .centered-block { + @include media-breakpoint-up(lg) { + text-align: center; + } + } + + .header-description { + p { + font-size: var(--font-size-05); + line-height: var(--line-height-03); + margin-bottom: 0; + + @include media-breakpoint-down(lg) { + font-size: var(--font-size-04); + line-height: var(--line-height-02); + } + } + } + + .resources { + @include media-breakpoint-up(lg) { + display: flex; + + /* equal heights */ + flex-wrap: wrap; + } + + .resources-card { + display: flex; + flex-flow: column wrap; + margin-bottom: 48px; + align-content: flex-start; + + @include media-breakpoint-down(lg) { + margin-bottom: 16px; + } + + img { + width: 56px; + } + + h3 { + width: 100%; + font-size: var(--font-size-05); + } + + a { + margin-top: auto; + + @include heading-xs; + } + + p { + margin-bottom: var(--spacing-05); + } + } + } + .green-round-background { @extend .round-background; background: var(--green-30); } + .why-latex { + h1 { + margin-top: var(--spacing-08); + } + + .sub-heading { + font-size: var(--font-size-04); + } + } + + .info-cards { + padding: 0; + + @include media-breakpoint-up(lg) { + display: flex; + + /* equal heights */ + flex-wrap: wrap; + } + + .info-card-container { + margin-bottom: var(--spacing-06); + padding-left: var(--spacing-06); + padding-right: var(--spacing-06); + + h3 { + font-size: var(--font-size-05); + line-height: var(--line-height-04); + } + + .info-card { + border-radius: 8px; + height: 100%; + box-shadow: + 0 2px 4px 0 #1e253014, + 0 4px 12px 0 #1e25301f; + border-top: 8px solid var(--sapphire-blue); + padding: var(--spacing-09) var(--spacing-10); + + &.info-card-big-text { + h3 { + font-size: var(--font-size-06); + line-height: var(--line-height-02); + } + + p { + font-size: var(--font-size-04); + line-height: var(--line-height-02); + } + } + + i.material-symbols { + color: var(--sapphire-blue); + } + } + } + } + + .heading-section-md-align-left { + @include media-breakpoint-down(lg) { + display: flex; + flex-direction: column; + align-items: baseline; + + h2 { + text-align: left; + } + + p { + text-align: left; + } + } + } + + .responsive-button-container { + display: flex; + margin-top: var(--spacing-08); + gap: var(--spacing-06); + + &.centered-buttons { + justify-content: center; + } + + &.align-left-button-sm { + @include media-breakpoint-down(md) { + justify-content: start; + } + } + + @include media-breakpoint-down(md) { + width: 100%; + flex-direction: column; + } + } + + .overleaf-sticker { + width: unset; + + @include media-breakpoint-down(lg) { + width: 74px; // 70% of 106px + } + } + .features-card { display: flex; /* equal heights */ flex-wrap: wrap; From 26a7a7d7b820bd9af2167c0d97ccef2081783d9c Mon Sep 17 00:00:00 2001 From: Jakob Ackermann Date: Tue, 20 May 2025 11:26:00 +0100 Subject: [PATCH 168/194] [clsi] mark VM as unhealthy when detecting of-of-disk condition (#25721) * [clsi] shed load when detecting out-of-disk condition * [clsi] mark VM as unhealthy when detecting of-of-disk condition GitOrigin-RevId: 25cda6785c0d973f50ec6206bee389804f35917e --- services/clsi/app.js | 14 +++++++--- .../clsi/app/js/ProjectPersistenceManager.js | 26 +++++++++++++++++-- 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/services/clsi/app.js b/services/clsi/app.js index 8de9d89b9b..872f612d9c 100644 --- a/services/clsi/app.js +++ b/services/clsi/app.js @@ -249,6 +249,9 @@ app.get('/health_check', function (req, res) { if (Settings.processTooOld) { return res.status(500).json({ processTooOld: true }) } + if (ProjectPersistenceManager.isAnyDiskCriticalLow()) { + return res.status(500).json({ diskCritical: true }) + } smokeTest.sendLastResult(res) }) @@ -296,9 +299,14 @@ const loadTcpServer = net.createServer(function (socket) { } const freeLoad = availableWorkingCpus - currentLoad - const freeLoadPercentage = Math.round( - (freeLoad / availableWorkingCpus) * 100 - ) + let freeLoadPercentage = Math.round((freeLoad / availableWorkingCpus) * 100) + if (ProjectPersistenceManager.isAnyDiskCriticalLow()) { + freeLoadPercentage = 0 + } + if (ProjectPersistenceManager.isAnyDiskLow()) { + freeLoadPercentage = freeLoadPercentage / 2 + } + if ( Settings.internal.load_balancer_agent.allow_maintenance && freeLoadPercentage <= 0 diff --git a/services/clsi/app/js/ProjectPersistenceManager.js b/services/clsi/app/js/ProjectPersistenceManager.js index e96a4591c3..41cdd07f4d 100644 --- a/services/clsi/app/js/ProjectPersistenceManager.js +++ b/services/clsi/app/js/ProjectPersistenceManager.js @@ -22,6 +22,9 @@ const fs = require('node:fs') // projectId -> timestamp mapping. const LAST_ACCESS = new Map() +let ANY_DISK_LOW = false +let ANY_DISK_CRITICAL_LOW = false + async function collectDiskStats() { const paths = [ Settings.path.compilesDir, @@ -30,6 +33,8 @@ async function collectDiskStats() { ] const diskStats = {} + let anyDiskLow = false + let anyDiskCriticalLow = false for (const path of paths) { try { const { blocks, bavail, bsize } = await fs.promises.statfs(path) @@ -45,10 +50,16 @@ async function collectDiskStats() { }) const lowDisk = diskAvailablePercent < 10 diskStats[path] = { stats, lowDisk } + + const criticalLowDisk = diskAvailablePercent < 3 + anyDiskLow = anyDiskLow || lowDisk + anyDiskCriticalLow = anyDiskCriticalLow || criticalLowDisk } catch (err) { logger.err({ err, path }, 'error getting disk usage') } } + ANY_DISK_LOW = anyDiskLow + ANY_DISK_CRITICAL_LOW = anyDiskCriticalLow return diskStats } @@ -70,11 +81,22 @@ async function refreshExpiryTimeout() { break } } + Metrics.gauge( + 'project_persistence_expiry_timeout', + ProjectPersistenceManager.EXPIRY_TIMEOUT + ) } module.exports = ProjectPersistenceManager = { EXPIRY_TIMEOUT: Settings.project_cache_length_ms || oneDay * 2.5, + isAnyDiskLow() { + return ANY_DISK_LOW + }, + isAnyDiskCriticalLow() { + return ANY_DISK_CRITICAL_LOW + }, + promises: { refreshExpiryTimeout, }, @@ -125,12 +147,12 @@ module.exports = ProjectPersistenceManager = { ) }) - // Collect disk stats frequently to have them ready the next time /metrics is scraped (60s +- jitter). + // Collect disk stats frequently to have them ready the next time /metrics is scraped (60s +- jitter) or every 5th scrape of the load agent (3s +- jitter). setInterval(() => { collectDiskStats().catch(err => { logger.err({ err }, 'low level error collecting disk stats') }) - }, 50_000) + }, 15_000) }, markProjectAsJustAccessed(projectId, callback) { From 6bc4e40773c6f79ca8d13b56fb66d983f5a2eb77 Mon Sep 17 00:00:00 2001 From: Jessica Lawshe <5312836+lawshe@users.noreply.github.com> Date: Tue, 20 May 2025 09:00:09 -0500 Subject: [PATCH 169/194] Merge pull request #24624 from overleaf/jel-latexqc-updates [LaTeXQC] Footer and other minor style updates GitOrigin-RevId: 67a43f715857a3bd1e97f24278d8e4586815688c --- package-lock.json | 46 ---------------------------------------------- 1 file changed, 46 deletions(-) diff --git a/package-lock.json b/package-lock.json index 168d968293..2a42f5e110 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18690,12 +18690,6 @@ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", "dev": true }, - "node_modules/css-mediaquery": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/css-mediaquery/-/css-mediaquery-0.1.2.tgz", - "integrity": "sha512-COtn4EROW5dBGlE/4PiKnh6rZpAPxDeFLaEEwt4i10jpDMFt2EhQGS79QmmrO+iKCHv0PU/HrOWEhijFd1x99Q==", - "license": "BSD" - }, "node_modules/css-minimizer-webpack-plugin": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-5.0.1.tgz", @@ -25519,12 +25513,6 @@ "node": ">=10.18" } }, - "node_modules/hyphenate-style-name": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/hyphenate-style-name/-/hyphenate-style-name-1.1.0.tgz", - "integrity": "sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==", - "license": "BSD-3-Clause" - }, "node_modules/i18next": { "version": "23.10.0", "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.10.0.tgz", @@ -28840,15 +28828,6 @@ "remove-accents": "0.4.2" } }, - "node_modules/matchmediaquery": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/matchmediaquery/-/matchmediaquery-0.4.2.tgz", - "integrity": "sha512-wrZpoT50ehYOudhDjt/YvUJc6eUzcdFPdmbizfgvswCKNHD1/OBOHYJpHie+HXpu6bSkEGieFMYk6VuutaiRfA==", - "license": "MIT", - "dependencies": { - "css-mediaquery": "^0.1.2" - } - }, "node_modules/material-colors": { "version": "1.2.6", "resolved": "https://registry.npmjs.org/material-colors/-/material-colors-1.2.6.tgz", @@ -34695,24 +34674,6 @@ "react-dom": "^16.14.0 || ^17.0.0 || ^18.0.0" } }, - "node_modules/react-responsive": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/react-responsive/-/react-responsive-10.0.1.tgz", - "integrity": "sha512-OM5/cRvbtUWEX8le8RCT8scA8y2OPtb0Q/IViEyCEM5FBN8lRrkUOZnu87I88A6njxDldvxG+rLBxWiA7/UM9g==", - "license": "MIT", - "dependencies": { - "hyphenate-style-name": "^1.0.0", - "matchmediaquery": "^0.4.2", - "prop-types": "^15.6.1", - "shallow-equal": "^3.1.0" - }, - "engines": { - "node": ">=14" - }, - "peerDependencies": { - "react": ">=16.8.0" - } - }, "node_modules/react-router": { "version": "6.30.0", "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.30.0.tgz", @@ -36547,12 +36508,6 @@ "node": ">=8" } }, - "node_modules/shallow-equal": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/shallow-equal/-/shallow-equal-3.1.0.tgz", - "integrity": "sha512-pfVOw8QZIXpMbhBWvzBISicvToTiM5WBF1EeAUZDDSb5Dt29yl4AYbyywbJFSEsRUMr7gJaxqCdr4L3tQf9wVg==", - "license": "MIT" - }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -43075,7 +43030,6 @@ "react-dropzone": "^14.2.3", "react-helmet": "^6.1.0", "react-redux": "^7.2.2", - "react-responsive": "^10.0.0", "react-router-dom": "^6.26.1", "redux": "^4.2.1", "redux-logger": "^3.0.1", From 47b76a49d8435370afca8bc49607bce38499b2b1 Mon Sep 17 00:00:00 2001 From: Jakob Ackermann Date: Tue, 20 May 2025 15:23:52 +0100 Subject: [PATCH 170/194] [web] start fetching load global blobs on module import (#25757) GitOrigin-RevId: 7c1b6ed717142ad07d6ba5464aab2ecc6ebe9736 --- services/web/app.mjs | 2 +- .../src/Features/History/HistoryManager.js | 4 +- .../unit/src/History/HistoryManagerTests.js | 54 ++++++++++++++++++- 3 files changed, 57 insertions(+), 3 deletions(-) diff --git a/services/web/app.mjs b/services/web/app.mjs index 1538e60149..b7c723da3d 100644 --- a/services/web/app.mjs +++ b/services/web/app.mjs @@ -75,7 +75,7 @@ try { await Promise.all([ mongodb.connectionPromise, mongoose.connectionPromise, - HistoryManager.promises.loadGlobalBlobs(), + HistoryManager.loadGlobalBlobsPromise, ]) } catch (err) { logger.fatal({ err }, 'Cannot connect to mongo. Exiting.') diff --git a/services/web/app/src/Features/History/HistoryManager.js b/services/web/app/src/Features/History/HistoryManager.js index 6e40907d1b..fe9e6e86a7 100644 --- a/services/web/app/src/Features/History/HistoryManager.js +++ b/services/web/app/src/Features/History/HistoryManager.js @@ -417,8 +417,11 @@ function _userView(user) { return { first_name: firstName, last_name: lastName, email, id: _id } } +const loadGlobalBlobsPromise = loadGlobalBlobs() + module.exports = { getBlobLocation, + loadGlobalBlobsPromise, initializeProject: callbackify(initializeProject), flushProject: callbackify(flushProject), resyncProject: callbackify(resyncProject), @@ -432,7 +435,6 @@ module.exports = { getLatestHistory: callbackify(getLatestHistory), getChanges: callbackify(getChanges), promises: { - loadGlobalBlobs, initializeProject, flushProject, resyncProject, diff --git a/services/web/test/unit/src/History/HistoryManagerTests.js b/services/web/test/unit/src/History/HistoryManagerTests.js index bfb13feaf2..6b83d15ed0 100644 --- a/services/web/test/unit/src/History/HistoryManagerTests.js +++ b/services/web/test/unit/src/History/HistoryManagerTests.js @@ -2,10 +2,36 @@ const { expect } = require('chai') const sinon = require('sinon') const SandboxedModule = require('sandboxed-module') const { ObjectId } = require('mongodb-legacy') +const { + connectionPromise, + cleanupTestDatabase, + db, +} = require('../../../../app/src/infrastructure/mongodb') const MODULE_PATH = '../../../../app/src/Features/History/HistoryManager' +const GLOBAL_BLOBS = { + e69de29bb2d1d6434b8b29ae775ad8c2e48c5391: + 'e6/9d/e29bb2d1d6434b8b29ae775ad8c2e48c5391', + '02426c2b3a484003ca42ed52b374b7907b757d12': + '02/42/6c2b3a484003ca42ed52b374b7907b757d12', +} + describe('HistoryManager', function () { + before(async function () { + await connectionPromise + }) + before(cleanupTestDatabase) + before(async function () { + await db.projectHistoryGlobalBlobs.insertMany( + Object.keys(GLOBAL_BLOBS).map(sha => ({ + _id: sha, + byteLength: 0, + stringLength: 0, + })) + ) + }) + beforeEach(function () { this.user_id = 'user-id-123' this.historyId = new ObjectId().toString() @@ -29,6 +55,10 @@ describe('HistoryManager', function () { url: this.v1HistoryUrl, user: this.v1HistoryUser, pass: this.v1HistoryPassword, + buckets: { + globalBlobs: 'globalBlobs', + projectBlobs: 'projectBlobs', + }, }, }, } @@ -60,7 +90,7 @@ describe('HistoryManager', function () { this.HistoryManager = SandboxedModule.require(MODULE_PATH, { requires: { - '../../infrastructure/mongodb': { ObjectId }, + '../../infrastructure/mongodb': { ObjectId, db }, '@overleaf/fetch-utils': this.FetchUtils, '@overleaf/settings': this.settings, '../User/UserGetter': this.UserGetter, @@ -70,6 +100,28 @@ describe('HistoryManager', function () { }) }) + describe('getBlobLocation', function () { + beforeEach(async function () { + await this.HistoryManager.loadGlobalBlobsPromise + }) + it('should return a global blob location', function () { + for (const [sha, key] of Object.entries(GLOBAL_BLOBS)) { + expect(this.HistoryManager.getBlobLocation('42', sha)).to.deep.equal({ + bucket: this.settings.apis.v1_history.buckets.globalBlobs, + key, + }) + } + }) + it('should return a project blob location', function () { + const sha = '6ddfa0578a67fe5ad6623a8665ec9aafce1eb5ca' + const key = '240/000/000/6d/dfa0578a67fe5ad6623a8665ec9aafce1eb5ca' + expect(this.HistoryManager.getBlobLocation('42', sha)).to.deep.equal({ + bucket: this.settings.apis.v1_history.buckets.projectBlobs, + key, + }) + }) + }) + describe('initializeProject', function () { beforeEach(function () { this.settings.apis.project_history.initializeHistoryForNewProjects = true From 6a73d3f8f367bd11718ad0d2c7052d9e762f04e7 Mon Sep 17 00:00:00 2001 From: M Fahru Date: Tue, 20 May 2025 07:39:54 -0700 Subject: [PATCH 171/194] Merge pull request #25705 from overleaf/mf-add-journal-grey-sticker [web] Add `journal-grey.svg` sticker in Contentful GitOrigin-RevId: 713ba7ef1e2589eecb9b93081d31a79464172850 --- .../website-redesign/stickers/journal-grey.svg | 15 +++++++++++++++ services/web/types/cms.ts | 1 + 2 files changed, 16 insertions(+) create mode 100644 services/web/public/img/website-redesign/stickers/journal-grey.svg diff --git a/services/web/public/img/website-redesign/stickers/journal-grey.svg b/services/web/public/img/website-redesign/stickers/journal-grey.svg new file mode 100644 index 0000000000..12dfc2de3c --- /dev/null +++ b/services/web/public/img/website-redesign/stickers/journal-grey.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/services/web/types/cms.ts b/services/web/types/cms.ts index 48e94d2a81..1f9e4beebc 100644 --- a/services/web/types/cms.ts +++ b/services/web/types/cms.ts @@ -12,6 +12,7 @@ export type IconElementSticker = | 'sticker | globe | green' | 'sticker | house-tree | grey | large' | 'sticker | hub | tangerine' + | 'sticker | journal | grey' | 'sticker | lock | grey | medium' | 'sticker | lightning | yellow' | 'sticker | overleaf | green | medium' From 0e54e650e30e646c0bfd6afd963a418a182cc509 Mon Sep 17 00:00:00 2001 From: M Fahru Date: Tue, 20 May 2025 07:40:39 -0700 Subject: [PATCH 172/194] Merge pull request #25706 from overleaf/mf-tear-down-nudge-annual-checkout-page [web] Tear down `nudge-annual-checkout-page` split test and keep the default version GitOrigin-RevId: 5714810b2a8abedca60855b37b059cd7f900407c --- services/web/frontend/extracted-translations.json | 3 --- .../bootstrap-5/pages/subscription.scss | 14 -------------- services/web/locales/en.json | 3 --- .../types/subscription/payment-context-value.tsx | 1 - 4 files changed, 21 deletions(-) diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json index 6c5af98c38..36e66feed7 100644 --- a/services/web/frontend/extracted-translations.json +++ b/services/web/frontend/extracted-translations.json @@ -1447,7 +1447,6 @@ "saml_missing_signature_error": "", "saml_response": "", "save": "", - "save_20_percent_when_you_switch_to_annual": "", "save_or_cancel-cancel": "", "save_or_cancel-or": "", "save_or_cancel-save": "", @@ -1681,7 +1680,6 @@ "sure_you_want_to_change_plan": "", "sure_you_want_to_delete": "", "sure_you_want_to_leave_group": "", - "switch_back_to_monthly_pay_20_more": "", "switch_compile_mode_for_faster_draft_compilation": "", "switch_to_editor": "", "switch_to_new_editor": "", @@ -2091,7 +2089,6 @@ "you_are_a_manager_of_publisher_x": "", "you_are_a_manager_of_x_plan_as_member_of_group_subscription_y_administered_by_z": "", "you_are_a_manager_of_x_plan_as_member_of_group_subscription_y_administered_by_z_you": "", - "you_are_now_saving_20_percent": "", "you_are_on_a_paid_plan_contact_support_to_find_out_more": "", "you_are_on_x_plan_as_a_confirmed_member_of_institution_y": "", "you_are_on_x_plan_as_member_of_group_subscription_y_administered_by_z": "", diff --git a/services/web/frontend/stylesheets/bootstrap-5/pages/subscription.scss b/services/web/frontend/stylesheets/bootstrap-5/pages/subscription.scss index ed2acdf5b5..cc01e1abd2 100644 --- a/services/web/frontend/stylesheets/bootstrap-5/pages/subscription.scss +++ b/services/web/frontend/stylesheets/bootstrap-5/pages/subscription.scss @@ -182,10 +182,6 @@ h4 { margin-bottom: var(--spacing-06); - - &:has(+ .payment-nudge-annual-button) { - margin-bottom: 0; - } } .features-list { @@ -452,16 +448,6 @@ } } -.payment-nudge-annual-button { - margin: var(--spacing-02) 0 var(--spacing-06) 0; - - @include body-sm; - - button { - padding: 0; - } -} - .add-on-card { display: flex; align-items: center; diff --git a/services/web/locales/en.json b/services/web/locales/en.json index 6495a24fbd..21ee0fd735 100644 --- a/services/web/locales/en.json +++ b/services/web/locales/en.json @@ -1910,7 +1910,6 @@ "saml_response": "SAML Response", "save": "Save", "save_20_percent": "save 20%", - "save_20_percent_when_you_switch_to_annual": "Save 20% when you switch to annual", "save_or_cancel-cancel": "Cancel", "save_or_cancel-or": "or", "save_or_cancel-save": "Save", @@ -2178,7 +2177,6 @@ "sure_you_want_to_delete": "Are you sure you want to permanently delete the following files?", "sure_you_want_to_leave_group": "Are you sure you want to leave this group?", "sv": "Swedish", - "switch_back_to_monthly_pay_20_more": "Switch back to monthly (20% more)", "switch_compile_mode_for_faster_draft_compilation": "Switch compile mode for faster draft compilation", "switch_to_editor": "Switch to editor", "switch_to_new_editor": "Switch to new editor", @@ -2645,7 +2643,6 @@ "you_are_a_manager_of_x_plan_as_member_of_group_subscription_y_administered_by_z": "You are a <1>manager of the <0>__planName__ group subscription <1>__groupName__ administered by <1>__adminEmail__.", "you_are_a_manager_of_x_plan_as_member_of_group_subscription_y_administered_by_z_you": "You are a <1>manager of the <0>__planName__ group subscription <1>__groupName__ administered by <1>you (__adminEmail__).", "you_are_currently_logged_in_as": "You are currently logged in as __email__.", - "you_are_now_saving_20_percent": "You are now saving 20%", "you_are_on_a_paid_plan_contact_support_to_find_out_more": "You’re on an __appName__ Paid plan. <0>Contact support to find out more.", "you_are_on_x_plan_as_a_confirmed_member_of_institution_y": "You are on our <0>__planName__ plan as a <1>confirmed member of <1>__institutionName__", "you_are_on_x_plan_as_member_of_group_subscription_y_administered_by_z": "You are on our <0>__planName__ plan as a <1>member of the group subscription <1>__groupName__ administered by <1>__adminEmail__", diff --git a/services/web/types/subscription/payment-context-value.tsx b/services/web/types/subscription/payment-context-value.tsx index 496f7e027b..2df1265270 100644 --- a/services/web/types/subscription/payment-context-value.tsx +++ b/services/web/types/subscription/payment-context-value.tsx @@ -73,5 +73,4 @@ export type PaymentContextValue = { React.SetStateAction > updatePlan: (newPlanCode: string) => void - showNudgeToAnnualText: boolean } From 5a0f53654f501013c68f4e975e81453096912985 Mon Sep 17 00:00:00 2001 From: Miguel Serrano Date: Tue, 20 May 2025 17:04:13 +0200 Subject: [PATCH 173/194] Merge pull request #25021 from overleaf/msm-hotfix-5-4-1 CE/SP Hotfix `5.4.1` GitOrigin-RevId: f88fb2bef6d096cb46eb0b39652e751056d114ef --- server-ce/config/settings.js | 1 + server-ce/hotfix/5.4.1/Dockerfile | 14 + server-ce/hotfix/5.4.1/issue_24996.patch | 10 + server-ce/hotfix/5.4.1/package-lock.json.diff | 2024 +++++++++++++++++ 4 files changed, 2049 insertions(+) create mode 100644 server-ce/hotfix/5.4.1/Dockerfile create mode 100644 server-ce/hotfix/5.4.1/issue_24996.patch create mode 100644 server-ce/hotfix/5.4.1/package-lock.json.diff diff --git a/server-ce/config/settings.js b/server-ce/config/settings.js index 0cf2c5a7ec..164d8b0196 100644 --- a/server-ce/config/settings.js +++ b/server-ce/config/settings.js @@ -79,6 +79,7 @@ const settings = { host: process.env.OVERLEAF_REDIS_HOST || 'dockerhost', port: process.env.OVERLEAF_REDIS_PORT || '6379', password: process.env.OVERLEAF_REDIS_PASS || undefined, + tls: process.env.OVERLEAF_REDIS_TLS === 'true' ? {} : undefined, key_schema: { // document-updater blockingKey({ doc_id }) { diff --git a/server-ce/hotfix/5.4.1/Dockerfile b/server-ce/hotfix/5.4.1/Dockerfile new file mode 100644 index 0000000000..46bde4013c --- /dev/null +++ b/server-ce/hotfix/5.4.1/Dockerfile @@ -0,0 +1,14 @@ +FROM sharelatex/sharelatex:5.4.0 + +RUN apt update && apt install -y linux-libc-dev \ + && unattended-upgrade --verbose --no-minimal-upgrade-steps \ + && rm -rf /var/lib/apt/lists/* +COPY package-lock.json.diff . +RUN patch package-lock.json < package-lock.json.diff +RUN npm install --omit=dev + + +# fix tls configuration in redis +COPY issue_24996.patch . +RUN patch -p0 /etc/overleaf/settings.js < issue_24996.patch \ + && rm issue_24996.patch diff --git a/server-ce/hotfix/5.4.1/issue_24996.patch b/server-ce/hotfix/5.4.1/issue_24996.patch new file mode 100644 index 0000000000..3067aab968 --- /dev/null +++ b/server-ce/hotfix/5.4.1/issue_24996.patch @@ -0,0 +1,10 @@ +--- settings.js ++++ settings.js +@@ -79,6 +79,7 @@ const settings = { + host: process.env.OVERLEAF_REDIS_HOST || 'dockerhost', + port: process.env.OVERLEAF_REDIS_PORT || '6379', + password: process.env.OVERLEAF_REDIS_PASS || undefined, ++ tls: process.env.OVERLEAF_REDIS_TLS === 'true' ? {} : undefined, + key_schema: { + // document-updater + blockingKey({ doc_id }) { diff --git a/server-ce/hotfix/5.4.1/package-lock.json.diff b/server-ce/hotfix/5.4.1/package-lock.json.diff new file mode 100644 index 0000000000..f4f7ae4788 --- /dev/null +++ b/server-ce/hotfix/5.4.1/package-lock.json.diff @@ -0,0 +1,2024 @@ +151c151 +< "@google-cloud/profiler": "^6.0.0", +--- +> "@google-cloud/profiler": "^6.0.3", +173a174,507 +> "libraries/metrics/node_modules/@google-cloud/logging-min": { +> "version": "11.2.0", +> "resolved": "https://registry.npmjs.org/@google-cloud/logging-min/-/logging-min-11.2.0.tgz", +> "integrity": "sha512-o1mwzi1+9NMEjwYZJ0X3tK64obf9PzPVBAhzEJv65L0h7jVl3Fw7GswtsMUkdUvZexf96vAvlZZMvXB9jAIW2Q==", +> "license": "Apache-2.0", +> "dependencies": { +> "@google-cloud/common": "^5.0.0", +> "@google-cloud/paginator": "^5.0.0", +> "@google-cloud/projectify": "^4.0.0", +> "@google-cloud/promisify": "^4.0.0", +> "@opentelemetry/api": "^1.7.0", +> "arrify": "^2.0.1", +> "dot-prop": "^6.0.0", +> "eventid": "^2.0.0", +> "extend": "^3.0.2", +> "gcp-metadata": "^6.0.0", +> "google-auth-library": "^9.0.0", +> "google-gax": "^4.0.3", +> "on-finished": "^2.3.0", +> "pumpify": "^2.0.1", +> "stream-events": "^1.0.5", +> "uuid": "^9.0.0" +> }, +> "engines": { +> "node": ">=14.0.0" +> } +> }, +> "libraries/metrics/node_modules/@google-cloud/paginator": { +> "version": "5.0.2", +> "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-5.0.2.tgz", +> "integrity": "sha512-DJS3s0OVH4zFDB1PzjxAsHqJT6sKVbRwwML0ZBP9PbU7Yebtu/7SWMRzvO2J3nUi9pRNITCfu4LJeooM2w4pjg==", +> "license": "Apache-2.0", +> "dependencies": { +> "arrify": "^2.0.0", +> "extend": "^3.0.2" +> }, +> "engines": { +> "node": ">=14.0.0" +> } +> }, +> "libraries/metrics/node_modules/@google-cloud/profiler": { +> "version": "6.0.3", +> "resolved": "https://registry.npmjs.org/@google-cloud/profiler/-/profiler-6.0.3.tgz", +> "integrity": "sha512-Ey8li6Vc2CbfEzOTSZaqKolxPMGacxVUQuhChNT0Wi55a3nfImMiiuDgqYw1In/a9Q3Z62O7jUg2L8f1XwMN7Q==", +> "license": "Apache-2.0", +> "dependencies": { +> "@google-cloud/common": "^5.0.0", +> "@google-cloud/logging-min": "^11.0.0", +> "@google-cloud/promisify": "~4.0.0", +> "@types/console-log-level": "^1.4.0", +> "@types/semver": "^7.0.0", +> "console-log-level": "^1.4.0", +> "delay": "^5.0.0", +> "extend": "^3.0.2", +> "gcp-metadata": "^6.0.0", +> "ms": "^2.1.3", +> "pprof": "4.0.0", +> "pretty-ms": "^7.0.0", +> "protobufjs": "~7.4.0", +> "semver": "^7.0.0", +> "teeny-request": "^9.0.0" +> }, +> "engines": { +> "node": ">=14.0.0" +> } +> }, +> "libraries/metrics/node_modules/@google-cloud/profiler/node_modules/protobufjs": { +> "version": "7.4.0", +> "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz", +> "integrity": "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==", +> "hasInstallScript": true, +> "license": "BSD-3-Clause", +> "dependencies": { +> "@protobufjs/aspromise": "^1.1.2", +> "@protobufjs/base64": "^1.1.2", +> "@protobufjs/codegen": "^2.0.4", +> "@protobufjs/eventemitter": "^1.1.0", +> "@protobufjs/fetch": "^1.1.0", +> "@protobufjs/float": "^1.0.2", +> "@protobufjs/inquire": "^1.1.0", +> "@protobufjs/path": "^1.1.2", +> "@protobufjs/pool": "^1.1.0", +> "@protobufjs/utf8": "^1.1.0", +> "@types/node": ">=13.7.0", +> "long": "^5.0.0" +> }, +> "engines": { +> "node": ">=12.0.0" +> } +> }, +> "libraries/metrics/node_modules/@mapbox/node-pre-gyp": { +> "version": "1.0.11", +> "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", +> "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", +> "license": "BSD-3-Clause", +> "dependencies": { +> "detect-libc": "^2.0.0", +> "https-proxy-agent": "^5.0.0", +> "make-dir": "^3.1.0", +> "node-fetch": "^2.6.7", +> "nopt": "^5.0.0", +> "npmlog": "^5.0.1", +> "rimraf": "^3.0.2", +> "semver": "^7.3.5", +> "tar": "^6.1.11" +> }, +> "bin": { +> "node-pre-gyp": "bin/node-pre-gyp" +> } +> }, +> "libraries/metrics/node_modules/@mapbox/node-pre-gyp/node_modules/agent-base": { +> "version": "6.0.2", +> "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", +> "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", +> "license": "MIT", +> "dependencies": { +> "debug": "4" +> }, +> "engines": { +> "node": ">= 6.0.0" +> } +> }, +> "libraries/metrics/node_modules/@mapbox/node-pre-gyp/node_modules/https-proxy-agent": { +> "version": "5.0.1", +> "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", +> "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", +> "license": "MIT", +> "dependencies": { +> "agent-base": "6", +> "debug": "4" +> }, +> "engines": { +> "node": ">= 6" +> } +> }, +> "libraries/metrics/node_modules/@opentelemetry/api": { +> "version": "1.9.0", +> "resolved": "https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz", +> "integrity": "sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==", +> "license": "Apache-2.0", +> "engines": { +> "node": ">=8.0.0" +> } +> }, +> "libraries/metrics/node_modules/agent-base": { +> "version": "7.1.3", +> "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", +> "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", +> "license": "MIT", +> "engines": { +> "node": ">= 14" +> } +> }, +> "libraries/metrics/node_modules/detect-libc": { +> "version": "2.0.4", +> "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", +> "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", +> "license": "Apache-2.0", +> "engines": { +> "node": ">=8" +> } +> }, +> "libraries/metrics/node_modules/gaxios": { +> "version": "6.7.1", +> "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", +> "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==", +> "license": "Apache-2.0", +> "dependencies": { +> "extend": "^3.0.2", +> "https-proxy-agent": "^7.0.1", +> "is-stream": "^2.0.0", +> "node-fetch": "^2.6.9", +> "uuid": "^9.0.1" +> }, +> "engines": { +> "node": ">=14" +> } +> }, +> "libraries/metrics/node_modules/gcp-metadata": { +> "version": "6.1.1", +> "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.1.tgz", +> "integrity": "sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A==", +> "license": "Apache-2.0", +> "dependencies": { +> "gaxios": "^6.1.1", +> "google-logging-utils": "^0.0.2", +> "json-bigint": "^1.0.0" +> }, +> "engines": { +> "node": ">=14" +> } +> }, +> "libraries/metrics/node_modules/google-auth-library": { +> "version": "9.15.1", +> "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.15.1.tgz", +> "integrity": "sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==", +> "license": "Apache-2.0", +> "dependencies": { +> "base64-js": "^1.3.0", +> "ecdsa-sig-formatter": "^1.0.11", +> "gaxios": "^6.1.1", +> "gcp-metadata": "^6.1.0", +> "gtoken": "^7.0.0", +> "jws": "^4.0.0" +> }, +> "engines": { +> "node": ">=14" +> } +> }, +> "libraries/metrics/node_modules/gtoken": { +> "version": "7.1.0", +> "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz", +> "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==", +> "license": "MIT", +> "dependencies": { +> "gaxios": "^6.0.0", +> "jws": "^4.0.0" +> }, +> "engines": { +> "node": ">=14.0.0" +> } +> }, +> "libraries/metrics/node_modules/https-proxy-agent": { +> "version": "7.0.6", +> "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", +> "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", +> "license": "MIT", +> "dependencies": { +> "agent-base": "^7.1.2", +> "debug": "4" +> }, +> "engines": { +> "node": ">= 14" +> } +> }, +> "libraries/metrics/node_modules/make-dir": { +> "version": "3.1.0", +> "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", +> "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", +> "license": "MIT", +> "dependencies": { +> "semver": "^6.0.0" +> }, +> "engines": { +> "node": ">=8" +> }, +> "funding": { +> "url": "https://github.com/sponsors/sindresorhus" +> } +> }, +> "libraries/metrics/node_modules/make-dir/node_modules/semver": { +> "version": "6.3.1", +> "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", +> "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", +> "license": "ISC", +> "bin": { +> "semver": "bin/semver.js" +> } +> }, +> "libraries/metrics/node_modules/pprof": { +> "version": "4.0.0", +> "resolved": "https://registry.npmjs.org/pprof/-/pprof-4.0.0.tgz", +> "integrity": "sha512-Yhfk7Y0G1MYsy97oXxmSG5nvbM1sCz9EALiNhW/isAv5Xf7svzP+1RfGeBlS6mLSgRJvgSLh6Mi5DaisQuPttw==", +> "hasInstallScript": true, +> "license": "Apache-2.0", +> "dependencies": { +> "@mapbox/node-pre-gyp": "^1.0.9", +> "bindings": "^1.2.1", +> "delay": "^5.0.0", +> "findit2": "^2.2.3", +> "nan": "^2.17.0", +> "p-limit": "^3.0.0", +> "protobufjs": "~7.2.4", +> "source-map": "~0.8.0-beta.0", +> "split": "^1.0.1" +> }, +> "engines": { +> "node": ">=14.0.0" +> } +> }, +> "libraries/metrics/node_modules/semver": { +> "version": "7.7.1", +> "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", +> "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", +> "license": "ISC", +> "bin": { +> "semver": "bin/semver.js" +> }, +> "engines": { +> "node": ">=10" +> } +> }, +> "libraries/metrics/node_modules/source-map": { +> "version": "0.8.0-beta.0", +> "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", +> "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", +> "license": "BSD-3-Clause", +> "dependencies": { +> "whatwg-url": "^7.0.0" +> }, +> "engines": { +> "node": ">= 8" +> } +> }, +> "libraries/metrics/node_modules/uuid": { +> "version": "9.0.1", +> "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", +> "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", +> "funding": [ +> "https://github.com/sponsors/broofa", +> "https://github.com/sponsors/ctavan" +> ], +> "license": "MIT", +> "bin": { +> "uuid": "dist/bin/uuid" +> } +> }, +> "libraries/metrics/node_modules/webidl-conversions": { +> "version": "4.0.2", +> "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", +> "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", +> "license": "BSD-2-Clause" +> }, +> "libraries/metrics/node_modules/whatwg-url": { +> "version": "7.1.0", +> "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", +> "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", +> "license": "MIT", +> "dependencies": { +> "lodash.sortby": "^4.7.0", +> "tr46": "^1.0.1", +> "webidl-conversions": "^4.0.2" +> } +> }, +3205a3540,3545 +> "node_modules/@balena/dockerignore": { +> "version": "1.0.2", +> "resolved": "https://registry.npmjs.org/@balena/dockerignore/-/dockerignore-1.0.2.tgz", +> "integrity": "sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q==", +> "license": "Apache-2.0" +> }, +4095a4436,4550 +> "node_modules/@google-cloud/common": { +> "version": "5.0.2", +> "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-5.0.2.tgz", +> "integrity": "sha512-V7bmBKYQyu0eVG2BFejuUjlBt+zrya6vtsKdY+JxMM/dNntPF41vZ9+LhOshEUH01zOHEqBSvI7Dad7ZS6aUeA==", +> "license": "Apache-2.0", +> "dependencies": { +> "@google-cloud/projectify": "^4.0.0", +> "@google-cloud/promisify": "^4.0.0", +> "arrify": "^2.0.1", +> "duplexify": "^4.1.1", +> "extend": "^3.0.2", +> "google-auth-library": "^9.0.0", +> "html-entities": "^2.5.2", +> "retry-request": "^7.0.0", +> "teeny-request": "^9.0.0" +> }, +> "engines": { +> "node": ">=14.0.0" +> } +> }, +> "node_modules/@google-cloud/common/node_modules/agent-base": { +> "version": "7.1.3", +> "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", +> "integrity": "sha512-jRR5wdylq8CkOe6hei19GGZnxM6rBGwFl3Bg0YItGDimvjGtAvdZk4Pu6Cl4u4Igsws4a1fd1Vq3ezrhn4KmFw==", +> "license": "MIT", +> "engines": { +> "node": ">= 14" +> } +> }, +> "node_modules/@google-cloud/common/node_modules/gaxios": { +> "version": "6.7.1", +> "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz", +> "integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==", +> "license": "Apache-2.0", +> "dependencies": { +> "extend": "^3.0.2", +> "https-proxy-agent": "^7.0.1", +> "is-stream": "^2.0.0", +> "node-fetch": "^2.6.9", +> "uuid": "^9.0.1" +> }, +> "engines": { +> "node": ">=14" +> } +> }, +> "node_modules/@google-cloud/common/node_modules/gcp-metadata": { +> "version": "6.1.1", +> "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.1.tgz", +> "integrity": "sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A==", +> "license": "Apache-2.0", +> "dependencies": { +> "gaxios": "^6.1.1", +> "google-logging-utils": "^0.0.2", +> "json-bigint": "^1.0.0" +> }, +> "engines": { +> "node": ">=14" +> } +> }, +> "node_modules/@google-cloud/common/node_modules/google-auth-library": { +> "version": "9.15.1", +> "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.15.1.tgz", +> "integrity": "sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==", +> "license": "Apache-2.0", +> "dependencies": { +> "base64-js": "^1.3.0", +> "ecdsa-sig-formatter": "^1.0.11", +> "gaxios": "^6.1.1", +> "gcp-metadata": "^6.1.0", +> "gtoken": "^7.0.0", +> "jws": "^4.0.0" +> }, +> "engines": { +> "node": ">=14" +> } +> }, +> "node_modules/@google-cloud/common/node_modules/gtoken": { +> "version": "7.1.0", +> "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz", +> "integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==", +> "license": "MIT", +> "dependencies": { +> "gaxios": "^6.0.0", +> "jws": "^4.0.0" +> }, +> "engines": { +> "node": ">=14.0.0" +> } +> }, +> "node_modules/@google-cloud/common/node_modules/https-proxy-agent": { +> "version": "7.0.6", +> "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", +> "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", +> "license": "MIT", +> "dependencies": { +> "agent-base": "^7.1.2", +> "debug": "4" +> }, +> "engines": { +> "node": ">= 14" +> } +> }, +> "node_modules/@google-cloud/common/node_modules/uuid": { +> "version": "9.0.1", +> "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", +> "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", +> "funding": [ +> "https://github.com/sponsors/broofa", +> "https://github.com/sponsors/ctavan" +> ], +> "license": "MIT", +> "bin": { +> "uuid": "dist/bin/uuid" +> } +> }, +4251,4529d4705 +< "node_modules/@google-cloud/logging-min": { +< "version": "10.4.0", +< "resolved": "https://registry.npmjs.org/@google-cloud/logging-min/-/logging-min-10.4.0.tgz", +< "integrity": "sha512-TcblDYAATO9hHcDcWYFh+vqt3pAV7Qddaih1JK3cpkzLa+BWjD5gAVAWww8W9Wr5yxOX+8CkssanH/xSS4n76Q==", +< "dependencies": { +< "@google-cloud/common": "^4.0.0", +< "@google-cloud/paginator": "^4.0.0", +< "@google-cloud/projectify": "^3.0.0", +< "@google-cloud/promisify": "^3.0.0", +< "arrify": "^2.0.1", +< "dot-prop": "^6.0.0", +< "eventid": "^2.0.0", +< "extend": "^3.0.2", +< "gcp-metadata": "^4.0.0", +< "google-auth-library": "^8.0.2", +< "google-gax": "^3.5.2", +< "on-finished": "^2.3.0", +< "pumpify": "^2.0.1", +< "stream-events": "^1.0.5", +< "uuid": "^9.0.0" +< }, +< "engines": { +< "node": ">=12.0.0" +< } +< }, +< "node_modules/@google-cloud/logging-min/node_modules/@google-cloud/common": { +< "version": "4.0.3", +< "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-4.0.3.tgz", +< "integrity": "sha512-fUoMo5b8iAKbrYpneIRV3z95AlxVJPrjpevxs4SKoclngWZvTXBSGpNisF5+x5m+oNGve7jfB1e6vNBZBUs7Fw==", +< "dependencies": { +< "@google-cloud/projectify": "^3.0.0", +< "@google-cloud/promisify": "^3.0.0", +< "arrify": "^2.0.1", +< "duplexify": "^4.1.1", +< "ent": "^2.2.0", +< "extend": "^3.0.2", +< "google-auth-library": "^8.0.2", +< "retry-request": "^5.0.0", +< "teeny-request": "^8.0.0" +< }, +< "engines": { +< "node": ">=12.0.0" +< } +< }, +< "node_modules/@google-cloud/logging-min/node_modules/@google-cloud/paginator": { +< "version": "4.0.1", +< "resolved": "https://registry.npmjs.org/@google-cloud/paginator/-/paginator-4.0.1.tgz", +< "integrity": "sha512-6G1ui6bWhNyHjmbYwavdN7mpVPRBtyDg/bfqBTAlwr413On2TnFNfDxc9UhTJctkgoCDgQXEKiRPLPR9USlkbQ==", +< "dependencies": { +< "arrify": "^2.0.0", +< "extend": "^3.0.2" +< }, +< "engines": { +< "node": ">=12.0.0" +< } +< }, +< "node_modules/@google-cloud/logging-min/node_modules/@google-cloud/projectify": { +< "version": "3.0.0", +< "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-3.0.0.tgz", +< "integrity": "sha512-HRkZsNmjScY6Li8/kb70wjGlDDyLkVk3KvoEo9uIoxSjYLJasGiCch9+PqRVDOCGUFvEIqyogl+BeqILL4OJHA==", +< "engines": { +< "node": ">=12.0.0" +< } +< }, +< "node_modules/@google-cloud/logging-min/node_modules/@google-cloud/promisify": { +< "version": "3.0.1", +< "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-3.0.1.tgz", +< "integrity": "sha512-z1CjRjtQyBOYL+5Qr9DdYIfrdLBe746jRTYfaYU6MeXkqp7UfYs/jX16lFFVzZ7PGEJvqZNqYUEtb1mvDww4pA==", +< "engines": { +< "node": ">=12" +< } +< }, +< "node_modules/@google-cloud/logging-min/node_modules/duplexify": { +< "version": "4.1.2", +< "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.2.tgz", +< "integrity": "sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw==", +< "dependencies": { +< "end-of-stream": "^1.4.1", +< "inherits": "^2.0.3", +< "readable-stream": "^3.1.1", +< "stream-shift": "^1.0.0" +< } +< }, +< "node_modules/@google-cloud/logging-min/node_modules/gcp-metadata": { +< "version": "4.3.1", +< "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-4.3.1.tgz", +< "integrity": "sha512-x850LS5N7V1F3UcV7PoupzGsyD6iVwTVvsh3tbXfkctZnBnjW5yu5z1/3k3SehF7TyoTIe78rJs02GMMy+LF+A==", +< "dependencies": { +< "gaxios": "^4.0.0", +< "json-bigint": "^1.0.0" +< }, +< "engines": { +< "node": ">=10" +< } +< }, +< "node_modules/@google-cloud/logging-min/node_modules/gcp-metadata/node_modules/gaxios": { +< "version": "4.3.3", +< "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-4.3.3.tgz", +< "integrity": "sha512-gSaYYIO1Y3wUtdfHmjDUZ8LWaxJQpiavzbF5Kq53akSzvmVg0RfyOcFDbO1KJ/KCGRFz2qG+lS81F0nkr7cRJA==", +< "dependencies": { +< "abort-controller": "^3.0.0", +< "extend": "^3.0.2", +< "https-proxy-agent": "^5.0.0", +< "is-stream": "^2.0.0", +< "node-fetch": "^2.6.7" +< }, +< "engines": { +< "node": ">=10" +< } +< }, +< "node_modules/@google-cloud/logging-min/node_modules/google-auth-library": { +< "version": "8.9.0", +< "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-8.9.0.tgz", +< "integrity": "sha512-f7aQCJODJFmYWN6PeNKzgvy9LI2tYmXnzpNDHEjG5sDNPgGb2FXQyTBnXeSH+PAtpKESFD+LmHw3Ox3mN7e1Fg==", +< "dependencies": { +< "arrify": "^2.0.0", +< "base64-js": "^1.3.0", +< "ecdsa-sig-formatter": "^1.0.11", +< "fast-text-encoding": "^1.0.0", +< "gaxios": "^5.0.0", +< "gcp-metadata": "^5.3.0", +< "gtoken": "^6.1.0", +< "jws": "^4.0.0", +< "lru-cache": "^6.0.0" +< }, +< "engines": { +< "node": ">=12" +< } +< }, +< "node_modules/@google-cloud/logging-min/node_modules/google-auth-library/node_modules/gcp-metadata": { +< "version": "5.3.0", +< "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.3.0.tgz", +< "integrity": "sha512-FNTkdNEnBdlqF2oatizolQqNANMrcqJt6AAYt99B3y1aLLC8Hc5IOBb+ZnnzllodEEf6xMBp6wRcBbc16fa65w==", +< "dependencies": { +< "gaxios": "^5.0.0", +< "json-bigint": "^1.0.0" +< }, +< "engines": { +< "node": ">=12" +< } +< }, +< "node_modules/@google-cloud/logging-min/node_modules/google-gax": { +< "version": "3.6.1", +< "resolved": "https://registry.npmjs.org/google-gax/-/google-gax-3.6.1.tgz", +< "integrity": "sha512-g/lcUjGcB6DSw2HxgEmCDOrI/CByOwqRvsuUvNalHUK2iPPPlmAIpbMbl62u0YufGMr8zgE3JL7th6dCb1Ry+w==", +< "dependencies": { +< "@grpc/grpc-js": "~1.8.0", +< "@grpc/proto-loader": "^0.7.0", +< "@types/long": "^4.0.0", +< "@types/rimraf": "^3.0.2", +< "abort-controller": "^3.0.0", +< "duplexify": "^4.0.0", +< "fast-text-encoding": "^1.0.3", +< "google-auth-library": "^8.0.2", +< "is-stream-ended": "^0.1.4", +< "node-fetch": "^2.6.1", +< "object-hash": "^3.0.0", +< "proto3-json-serializer": "^1.0.0", +< "protobufjs": "7.2.4", +< "protobufjs-cli": "1.1.1", +< "retry-request": "^5.0.0" +< }, +< "bin": { +< "compileProtos": "build/tools/compileProtos.js", +< "minifyProtoJson": "build/tools/minify.js" +< }, +< "engines": { +< "node": ">=12" +< } +< }, +< "node_modules/@google-cloud/logging-min/node_modules/google-p12-pem": { +< "version": "4.0.1", +< "resolved": "https://registry.npmjs.org/google-p12-pem/-/google-p12-pem-4.0.1.tgz", +< "integrity": "sha512-WPkN4yGtz05WZ5EhtlxNDWPhC4JIic6G8ePitwUWy4l+XPVYec+a0j0Ts47PDtW59y3RwAhUd9/h9ZZ63px6RQ==", +< "dependencies": { +< "node-forge": "^1.3.1" +< }, +< "bin": { +< "gp12-pem": "build/src/bin/gp12-pem.js" +< }, +< "engines": { +< "node": ">=12.0.0" +< } +< }, +< "node_modules/@google-cloud/logging-min/node_modules/gtoken": { +< "version": "6.1.2", +< "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-6.1.2.tgz", +< "integrity": "sha512-4ccGpzz7YAr7lxrT2neugmXQ3hP9ho2gcaityLVkiUecAiwiy60Ii8gRbZeOsXV19fYaRjgBSshs8kXw+NKCPQ==", +< "dependencies": { +< "gaxios": "^5.0.1", +< "google-p12-pem": "^4.0.0", +< "jws": "^4.0.0" +< }, +< "engines": { +< "node": ">=12.0.0" +< } +< }, +< "node_modules/@google-cloud/logging-min/node_modules/lru-cache": { +< "version": "6.0.0", +< "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", +< "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", +< "dependencies": { +< "yallist": "^4.0.0" +< }, +< "engines": { +< "node": ">=10" +< } +< }, +< "node_modules/@google-cloud/logging-min/node_modules/object-hash": { +< "version": "3.0.0", +< "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", +< "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", +< "engines": { +< "node": ">= 6" +< } +< }, +< "node_modules/@google-cloud/logging-min/node_modules/retry-request": { +< "version": "5.0.2", +< "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-5.0.2.tgz", +< "integrity": "sha512-wfI3pk7EE80lCIXprqh7ym48IHYdwmAAzESdbU8Q9l7pnRCk9LEhpbOTNKjz6FARLm/Bl5m+4F0ABxOkYUujSQ==", +< "dependencies": { +< "debug": "^4.1.1", +< "extend": "^3.0.2" +< }, +< "engines": { +< "node": ">=12" +< } +< }, +< "node_modules/@google-cloud/logging-min/node_modules/teeny-request": { +< "version": "8.0.3", +< "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-8.0.3.tgz", +< "integrity": "sha512-jJZpA5He2y52yUhA7pyAGZlgQpcB+xLjcN0eUFxr9c8hP/H7uOXbBNVo/O0C/xVfJLJs680jvkFgVJEEvk9+ww==", +< "dependencies": { +< "http-proxy-agent": "^5.0.0", +< "https-proxy-agent": "^5.0.0", +< "node-fetch": "^2.6.1", +< "stream-events": "^1.0.5", +< "uuid": "^9.0.0" +< }, +< "engines": { +< "node": ">=12" +< } +< }, +< "node_modules/@google-cloud/logging-min/node_modules/uuid": { +< "version": "9.0.1", +< "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", +< "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", +< "funding": [ +< "https://github.com/sponsors/broofa", +< "https://github.com/sponsors/ctavan" +< ], +< "bin": { +< "uuid": "dist/bin/uuid" +< } +< }, +< "node_modules/@google-cloud/logging-min/node_modules/yallist": { +< "version": "4.0.0", +< "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", +< "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" +< }, +< "node_modules/@google-cloud/logging/node_modules/@google-cloud/common": { +< "version": "5.0.2", +< "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-5.0.2.tgz", +< "integrity": "sha512-V7bmBKYQyu0eVG2BFejuUjlBt+zrya6vtsKdY+JxMM/dNntPF41vZ9+LhOshEUH01zOHEqBSvI7Dad7ZS6aUeA==", +< "dependencies": { +< "@google-cloud/projectify": "^4.0.0", +< "@google-cloud/promisify": "^4.0.0", +< "arrify": "^2.0.1", +< "duplexify": "^4.1.1", +< "extend": "^3.0.2", +< "google-auth-library": "^9.0.0", +< "html-entities": "^2.5.2", +< "retry-request": "^7.0.0", +< "teeny-request": "^9.0.0" +< }, +< "engines": { +< "node": ">=14.0.0" +< } +< }, +4542,4557d4717 +< "node_modules/@google-cloud/logging/node_modules/@google-cloud/projectify": { +< "version": "4.0.0", +< "resolved": "https://registry.npmjs.org/@google-cloud/projectify/-/projectify-4.0.0.tgz", +< "integrity": "sha512-MmaX6HeSvyPbWGwFq7mXdo0uQZLGBYCwziiLIGq5JVX+/bdI3SAq6bP98trV5eTWfLuvsMcIC1YJOF2vfteLFA==", +< "engines": { +< "node": ">=14.0.0" +< } +< }, +< "node_modules/@google-cloud/logging/node_modules/@google-cloud/promisify": { +< "version": "4.0.0", +< "resolved": "https://registry.npmjs.org/@google-cloud/promisify/-/promisify-4.0.0.tgz", +< "integrity": "sha512-Orxzlfb9c67A15cq2JQEyVc7wEsmFBmHjZWZYQMUyJ1qivXyMwdyNOs9odi79hze+2zqdTtu1E19IM/FtqZ10g==", +< "engines": { +< "node": ">=14" +< } +< }, +4585,4595d4744 +< "node_modules/@google-cloud/logging/node_modules/duplexify": { +< "version": "4.1.3", +< "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.3.tgz", +< "integrity": "sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==", +< "dependencies": { +< "end-of-stream": "^1.4.1", +< "inherits": "^2.0.3", +< "readable-stream": "^3.1.1", +< "stream-shift": "^1.0.2" +< } +< }, +4668,4695d4816 +< "node_modules/@google-cloud/logging/node_modules/retry-request": { +< "version": "7.0.2", +< "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-7.0.2.tgz", +< "integrity": "sha512-dUOvLMJ0/JJYEn8NrpOaGNE7X3vpI5XlZS/u0ANjqtcZVKnIxP7IgCFwrKTxENw29emmwug53awKtaMm4i9g5w==", +< "dependencies": { +< "@types/request": "^2.48.8", +< "extend": "^3.0.2", +< "teeny-request": "^9.0.0" +< }, +< "engines": { +< "node": ">=14" +< } +< }, +< "node_modules/@google-cloud/logging/node_modules/teeny-request": { +< "version": "9.0.0", +< "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-9.0.0.tgz", +< "integrity": "sha512-resvxdc6Mgb7YEThw6G6bExlXKkv6+YbuzGg9xuXxSgxJF7Ozs+o8Y9+2R3sArdWdW8nOokoQb1yrpFB0pQK2g==", +< "dependencies": { +< "http-proxy-agent": "^5.0.0", +< "https-proxy-agent": "^5.0.0", +< "node-fetch": "^2.6.9", +< "stream-events": "^1.0.5", +< "uuid": "^9.0.0" +< }, +< "engines": { +< "node": ">=14" +< } +< }, +4756,4799c4877 +< "node_modules/@google-cloud/profiler": { +< "version": "6.0.0", +< "resolved": "https://registry.npmjs.org/@google-cloud/profiler/-/profiler-6.0.0.tgz", +< "integrity": "sha512-EAxPbDiNRidAKOEnlUK3M+CcOlqG+REkUEZKirLtxFwzI/m7LmGqDzQvrVWTOSFSEYJ9qQRRnO+Q1osNGk3NUg==", +< "dependencies": { +< "@google-cloud/common": "^5.0.0", +< "@google-cloud/logging-min": "^10.0.0", +< "@types/console-log-level": "^1.4.0", +< "@types/semver": "^7.0.0", +< "console-log-level": "^1.4.0", +< "delay": "^5.0.0", +< "extend": "^3.0.2", +< "gcp-metadata": "^6.0.0", +< "parse-duration": "^1.0.0", +< "pprof": "3.2.1", +< "pretty-ms": "^7.0.0", +< "protobufjs": "~7.2.4", +< "semver": "^7.0.0", +< "teeny-request": "^9.0.0" +< }, +< "engines": { +< "node": ">=14.0.0" +< } +< }, +< "node_modules/@google-cloud/profiler/node_modules/@google-cloud/common": { +< "version": "5.0.0", +< "resolved": "https://registry.npmjs.org/@google-cloud/common/-/common-5.0.0.tgz", +< "integrity": "sha512-IsbTVr7Ag+04GMT87X738vDs85QU1rMvaesm2wEQrtTbZAR92tGmUQ8/D/kdnYgAi98Q4zmfhF+T8Xs/Lw4zAA==", +< "dependencies": { +< "@google-cloud/projectify": "^4.0.0", +< "@google-cloud/promisify": "^4.0.0", +< "arrify": "^2.0.1", +< "duplexify": "^4.1.1", +< "ent": "^2.2.0", +< "extend": "^3.0.2", +< "google-auth-library": "^9.0.0", +< "retry-request": "^6.0.0", +< "teeny-request": "^9.0.0" +< }, +< "engines": { +< "node": ">=14.0.0" +< } +< }, +< "node_modules/@google-cloud/profiler/node_modules/@google-cloud/projectify": { +--- +> "node_modules/@google-cloud/projectify": { +4802a4881 +> "license": "Apache-2.0", +4807c4886 +< "node_modules/@google-cloud/profiler/node_modules/@google-cloud/promisify": { +--- +> "node_modules/@google-cloud/promisify": { +4810a4890 +> "license": "Apache-2.0", +4815,4993d4894 +< "node_modules/@google-cloud/profiler/node_modules/agent-base": { +< "version": "7.1.0", +< "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.0.tgz", +< "integrity": "sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==", +< "dependencies": { +< "debug": "^4.3.4" +< }, +< "engines": { +< "node": ">= 14" +< } +< }, +< "node_modules/@google-cloud/profiler/node_modules/debug": { +< "version": "4.3.4", +< "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", +< "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", +< "dependencies": { +< "ms": "2.1.2" +< }, +< "engines": { +< "node": ">=6.0" +< }, +< "peerDependenciesMeta": { +< "supports-color": { +< "optional": true +< } +< } +< }, +< "node_modules/@google-cloud/profiler/node_modules/duplexify": { +< "version": "4.1.2", +< "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.2.tgz", +< "integrity": "sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw==", +< "dependencies": { +< "end-of-stream": "^1.4.1", +< "inherits": "^2.0.3", +< "readable-stream": "^3.1.1", +< "stream-shift": "^1.0.0" +< } +< }, +< "node_modules/@google-cloud/profiler/node_modules/gaxios": { +< "version": "6.1.1", +< "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.1.1.tgz", +< "integrity": "sha512-bw8smrX+XlAoo9o1JAksBwX+hi/RG15J+NTSxmNPIclKC3ZVK6C2afwY8OSdRvOK0+ZLecUJYtj2MmjOt3Dm0w==", +< "dependencies": { +< "extend": "^3.0.2", +< "https-proxy-agent": "^7.0.1", +< "is-stream": "^2.0.0", +< "node-fetch": "^2.6.9" +< }, +< "engines": { +< "node": ">=14" +< } +< }, +< "node_modules/@google-cloud/profiler/node_modules/gaxios/node_modules/https-proxy-agent": { +< "version": "7.0.2", +< "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.2.tgz", +< "integrity": "sha512-NmLNjm6ucYwtcUmL7JQC1ZQ57LmHP4lT15FQ8D61nak1rO6DH+fz5qNK2Ap5UN4ZapYICE3/0KodcLYSPsPbaA==", +< "dependencies": { +< "agent-base": "^7.0.2", +< "debug": "4" +< }, +< "engines": { +< "node": ">= 14" +< } +< }, +< "node_modules/@google-cloud/profiler/node_modules/gcp-metadata": { +< "version": "6.0.0", +< "resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.0.0.tgz", +< "integrity": "sha512-Ozxyi23/1Ar51wjUT2RDklK+3HxqDr8TLBNK8rBBFQ7T85iIGnXnVusauj06QyqCXRFZig8LZC+TUddWbndlpQ==", +< "dependencies": { +< "gaxios": "^6.0.0", +< "json-bigint": "^1.0.0" +< }, +< "engines": { +< "node": ">=14" +< } +< }, +< "node_modules/@google-cloud/profiler/node_modules/google-auth-library": { +< "version": "9.1.0", +< "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.1.0.tgz", +< "integrity": "sha512-1M9HdOcQNPV5BwSXqwwT238MTKodJFBxZ/V2JP397ieOLv4FjQdfYb9SooR7Mb+oUT2IJ92mLJQf804dyx0MJA==", +< "dependencies": { +< "base64-js": "^1.3.0", +< "ecdsa-sig-formatter": "^1.0.11", +< "gaxios": "^6.0.0", +< "gcp-metadata": "^6.0.0", +< "gtoken": "^7.0.0", +< "jws": "^4.0.0", +< "lru-cache": "^6.0.0" +< }, +< "engines": { +< "node": ">=14" +< } +< }, +< "node_modules/@google-cloud/profiler/node_modules/gtoken": { +< "version": "7.0.1", +< "resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.0.1.tgz", +< "integrity": "sha512-KcFVtoP1CVFtQu0aSk3AyAt2og66PFhZAlkUOuWKwzMLoulHXG5W5wE5xAnHb+yl3/wEFoqGW7/cDGMU8igDZQ==", +< "dependencies": { +< "gaxios": "^6.0.0", +< "jws": "^4.0.0" +< }, +< "engines": { +< "node": ">=14.0.0" +< } +< }, +< "node_modules/@google-cloud/profiler/node_modules/lru-cache": { +< "version": "6.0.0", +< "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", +< "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", +< "dependencies": { +< "yallist": "^4.0.0" +< }, +< "engines": { +< "node": ">=10" +< } +< }, +< "node_modules/@google-cloud/profiler/node_modules/ms": { +< "version": "2.1.2", +< "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", +< "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" +< }, +< "node_modules/@google-cloud/profiler/node_modules/retry-request": { +< "version": "6.0.0", +< "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-6.0.0.tgz", +< "integrity": "sha512-24kaFMd3wCnT3n4uPnsQh90ZSV8OISpfTFXJ00Wi+/oD2OPrp63EQ8hznk6rhxdlpwx2QBhQSDz2Fg46ki852g==", +< "dependencies": { +< "debug": "^4.1.1", +< "extend": "^3.0.2" +< }, +< "engines": { +< "node": ">=14" +< } +< }, +< "node_modules/@google-cloud/profiler/node_modules/semver": { +< "version": "7.5.4", +< "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", +< "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", +< "dependencies": { +< "lru-cache": "^6.0.0" +< }, +< "bin": { +< "semver": "bin/semver.js" +< }, +< "engines": { +< "node": ">=10" +< } +< }, +< "node_modules/@google-cloud/profiler/node_modules/teeny-request": { +< "version": "9.0.0", +< "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-9.0.0.tgz", +< "integrity": "sha512-resvxdc6Mgb7YEThw6G6bExlXKkv6+YbuzGg9xuXxSgxJF7Ozs+o8Y9+2R3sArdWdW8nOokoQb1yrpFB0pQK2g==", +< "dependencies": { +< "http-proxy-agent": "^5.0.0", +< "https-proxy-agent": "^5.0.0", +< "node-fetch": "^2.6.9", +< "stream-events": "^1.0.5", +< "uuid": "^9.0.0" +< }, +< "engines": { +< "node": ">=14" +< } +< }, +< "node_modules/@google-cloud/profiler/node_modules/uuid": { +< "version": "9.0.1", +< "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", +< "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", +< "funding": [ +< "https://github.com/sponsors/broofa", +< "https://github.com/sponsors/ctavan" +< ], +< "bin": { +< "uuid": "dist/bin/uuid" +< } +< }, +< "node_modules/@google-cloud/profiler/node_modules/yallist": { +< "version": "4.0.0", +< "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", +< "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" +< }, +5048,5058d4948 +< "node_modules/@google-cloud/storage/node_modules/duplexify": { +< "version": "4.1.2", +< "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.2.tgz", +< "integrity": "sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw==", +< "dependencies": { +< "end-of-stream": "^1.4.1", +< "inherits": "^2.0.3", +< "readable-stream": "^3.1.1", +< "stream-shift": "^1.0.0" +< } +< }, +5598,5608d5487 +< "node_modules/@jsdoc/salty": { +< "version": "0.2.8", +< "resolved": "https://registry.npmjs.org/@jsdoc/salty/-/salty-0.2.8.tgz", +< "integrity": "sha512-5e+SFVavj1ORKlKaKr2BmTOekmXbelU7dC0cDkQLqag7xfuTPuGMUFx7KWJuv4bYZrTsoL2Z18VVCOKYxzoHcg==", +< "dependencies": { +< "lodash": "^4.17.21" +< }, +< "engines": { +< "node": ">=v12.0.0" +< } +< }, +5809,5822d5687 +< "node_modules/@mapbox/node-pre-gyp/node_modules/rimraf": { +< "version": "3.0.2", +< "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", +< "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", +< "dependencies": { +< "glob": "^7.1.3" +< }, +< "bin": { +< "rimraf": "bin.js" +< }, +< "funding": { +< "url": "https://github.com/sponsors/isaacs" +< } +< }, +11095,11103d10959 +< "node_modules/@types/glob": { +< "version": "8.1.0", +< "resolved": "https://registry.npmjs.org/@types/glob/-/glob-8.1.0.tgz", +< "integrity": "sha512-IO+MJPVhoqz+28h1qLAcBEH2+xHMK6MTyHJc7MTnnYb6wsoLR29POVGJ7LycmVXIqyy/4/2ShP5sUwTXuOwb/w==", +< "dependencies": { +< "@types/minimatch": "^5.1.2", +< "@types/node": "*" +< } +< }, +11266,11270d11121 +< "node_modules/@types/linkify-it": { +< "version": "5.0.0", +< "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", +< "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==" +< }, +11282,11295d11132 +< "node_modules/@types/markdown-it": { +< "version": "14.1.1", +< "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.1.tgz", +< "integrity": "sha512-4NpsnpYl2Gt1ljyBGrKMxFYAYvpqbnnkgP/i/g+NLpjEUa3obn1XJCur9YbEXKDAkaXqsR1LbDnGEJ0MmKFxfg==", +< "dependencies": { +< "@types/linkify-it": "^5", +< "@types/mdurl": "^2" +< } +< }, +< "node_modules/@types/mdurl": { +< "version": "2.0.0", +< "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", +< "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==" +< }, +11321,11325d11157 +< "node_modules/@types/minimatch": { +< "version": "5.1.2", +< "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", +< "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==" +< }, +11574,11582d11405 +< "node_modules/@types/rimraf": { +< "version": "3.0.2", +< "resolved": "https://registry.npmjs.org/@types/rimraf/-/rimraf-3.0.2.tgz", +< "integrity": "sha512-F3OznnSLAUxFrCEu/L5PY8+ny8DtcFRjx7fZZ9bycvXRi3KPTRS9HOitGZwvPg0juRhXFWIeKX58cnX5YqLohQ==", +< "dependencies": { +< "@types/glob": "*", +< "@types/node": "*" +< } +< }, +13204a13028 +> "dev": true, +13842a13667,13675 +> "node_modules/asn1": { +> "version": "0.2.6", +> "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", +> "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", +> "license": "MIT", +> "dependencies": { +> "safer-buffer": "~2.1.0" +> } +> }, +14965a14799,14807 +> "node_modules/buildcheck": { +> "version": "0.0.6", +> "resolved": "https://registry.npmjs.org/buildcheck/-/buildcheck-0.0.6.tgz", +> "integrity": "sha512-8f9ZJCUXyT1M35Jx7MkBgmBMo3oHTTBIPLiY9xyL0pl3T5RwcPEY8cUHr5LBNfu/fk6c2T4DJZuVM/8ZZT2D2A==", +> "optional": true, +> "engines": { +> "node": ">=10.0.0" +> } +> }, +15160,15174d15001 +< "node_modules/c8/node_modules/rimraf": { +< "version": "3.0.2", +< "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", +< "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", +< "dev": true, +< "dependencies": { +< "glob": "^7.1.3" +< }, +< "bin": { +< "rimraf": "bin.js" +< }, +< "funding": { +< "url": "https://github.com/sponsors/isaacs" +< } +< }, +15367,15377d15193 +< "node_modules/catharsis": { +< "version": "0.9.0", +< "resolved": "https://registry.npmjs.org/catharsis/-/catharsis-0.9.0.tgz", +< "integrity": "sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==", +< "dependencies": { +< "lodash": "^4.17.15" +< }, +< "engines": { +< "node": ">= 10" +< } +< }, +16502,16514d16317 +< "node_modules/cpu-features": { +< "version": "0.0.2", +< "resolved": "https://registry.npmjs.org/cpu-features/-/cpu-features-0.0.2.tgz", +< "integrity": "sha512-/2yieBqvMcRj8McNzkycjW2v3OIUOibBfd2dLEJ0nWts8NobAxwiyw9phVNS6oDL8x8tz9F7uNVFEVpJncQpeA==", +< "hasInstallScript": true, +< "optional": true, +< "dependencies": { +< "nan": "^2.14.1" +< }, +< "engines": { +< "node": ">=8.0.0" +< } +< }, +17652c17455,17456 +< "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" +--- +> "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", +> "dev": true +17910,17935d17713 +< "node_modules/docker-modem": { +< "version": "3.0.3", +< "resolved": "https://registry.npmjs.org/docker-modem/-/docker-modem-3.0.3.tgz", +< "integrity": "sha512-Tgkn2a+yiNP9FoZgMa/D9Wk+D2Db///0KOyKSYZRJa8w4+DzKyzQMkczKSdR/adQ0x46BOpeNkoyEOKjPhCzjw==", +< "dependencies": { +< "debug": "^4.1.1", +< "readable-stream": "^3.5.0", +< "split-ca": "^1.0.1", +< "ssh2": "^1.4.0" +< }, +< "engines": { +< "node": ">= 8.0" +< } +< }, +< "node_modules/dockerode": { +< "version": "3.3.1", +< "resolved": "https://registry.npmjs.org/dockerode/-/dockerode-3.3.1.tgz", +< "integrity": "sha512-AS2mr8Lp122aa5n6d99HkuTNdRV1wkkhHwBdcnY6V0+28D3DSYwhxAk85/mM9XwD3RMliTxyr63iuvn5ZblFYQ==", +< "dependencies": { +< "docker-modem": "^3.0.0", +< "tar-fs": "~2.0.1" +< }, +< "engines": { +< "node": ">= 8.0" +< } +< }, +18119a17898,17909 +> "node_modules/duplexify": { +> "version": "4.1.3", +> "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.3.tgz", +> "integrity": "sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==", +> "license": "MIT", +> "dependencies": { +> "end-of-stream": "^1.4.1", +> "inherits": "^2.0.3", +> "readable-stream": "^3.1.1", +> "stream-shift": "^1.0.2" +> } +> }, +19831a19622 +> "dev": true, +19847a19639 +> "dev": true, +19858a19651 +> "dev": true, +19918a19712 +> "dev": true, +19936a19731 +> "dev": true, +20628c20423,20424 +< "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" +--- +> "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", +> "dev": true +21080,21094d20875 +< "node_modules/flat-cache/node_modules/rimraf": { +< "version": "3.0.2", +< "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", +< "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", +< "dev": true, +< "dependencies": { +< "glob": "^7.1.3" +< }, +< "bin": { +< "rimraf": "bin.js" +< }, +< "funding": { +< "url": "https://github.com/sponsors/isaacs" +< } +< }, +22169,22219d21949 +< "node_modules/google-gax/node_modules/retry-request": { +< "version": "7.0.2", +< "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-7.0.2.tgz", +< "integrity": "sha512-dUOvLMJ0/JJYEn8NrpOaGNE7X3vpI5XlZS/u0ANjqtcZVKnIxP7IgCFwrKTxENw29emmwug53awKtaMm4i9g5w==", +< "dependencies": { +< "@types/request": "^2.48.8", +< "extend": "^3.0.2", +< "teeny-request": "^9.0.0" +< }, +< "engines": { +< "node": ">=14" +< } +< }, +< "node_modules/google-gax/node_modules/teeny-request": { +< "version": "9.0.0", +< "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-9.0.0.tgz", +< "integrity": "sha512-resvxdc6Mgb7YEThw6G6bExlXKkv6+YbuzGg9xuXxSgxJF7Ozs+o8Y9+2R3sArdWdW8nOokoQb1yrpFB0pQK2g==", +< "dependencies": { +< "http-proxy-agent": "^5.0.0", +< "https-proxy-agent": "^5.0.0", +< "node-fetch": "^2.6.9", +< "stream-events": "^1.0.5", +< "uuid": "^9.0.0" +< }, +< "engines": { +< "node": ">=14" +< } +< }, +< "node_modules/google-gax/node_modules/teeny-request/node_modules/agent-base": { +< "version": "6.0.2", +< "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", +< "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", +< "dependencies": { +< "debug": "4" +< }, +< "engines": { +< "node": ">= 6.0.0" +< } +< }, +< "node_modules/google-gax/node_modules/teeny-request/node_modules/https-proxy-agent": { +< "version": "5.0.1", +< "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", +< "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", +< "dependencies": { +< "agent-base": "6", +< "debug": "4" +< }, +< "engines": { +< "node": ">= 6" +< } +< }, +22231a21962,21970 +> "node_modules/google-logging-utils": { +> "version": "0.0.2", +> "resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-0.0.2.tgz", +> "integrity": "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ==", +> "license": "Apache-2.0", +> "engines": { +> "node": ">=14" +> } +> }, +24058,24062d23796 +< "node_modules/is-stream-ended": { +< "version": "0.1.4", +< "resolved": "https://registry.npmjs.org/is-stream-ended/-/is-stream-ended-0.1.4.tgz", +< "integrity": "sha512-xj0XPvmr7bQFTvirqnFr50o0hQIh6ZItDqloxt5aJrR4NQsYeSsyFQERYGCAzfindAcnKjINnwEEgLx4IqVzQw==" +< }, +24518,24525d24251 +< "node_modules/js2xmlparser": { +< "version": "4.0.2", +< "resolved": "https://registry.npmjs.org/js2xmlparser/-/js2xmlparser-4.0.2.tgz", +< "integrity": "sha512-6n4D8gLlLf1n5mNLQPRfViYzu9RATblzPEtm1SthMX1Pjao0r9YI9nw7ZIfRxQMERS87mcswrg+r/OYrPRX6jA==", +< "dependencies": { +< "xmlcreate": "^2.0.4" +< } +< }, +24597,24624d24322 +< "node_modules/jsdoc": { +< "version": "4.0.3", +< "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.3.tgz", +< "integrity": "sha512-Nu7Sf35kXJ1MWDZIMAuATRQTg1iIPdzh7tqJ6jjvaU/GfDf+qi5UV8zJR3Mo+/pYFvm8mzay4+6O5EWigaQBQw==", +< "dependencies": { +< "@babel/parser": "^7.20.15", +< "@jsdoc/salty": "^0.2.1", +< "@types/markdown-it": "^14.1.1", +< "bluebird": "^3.7.2", +< "catharsis": "^0.9.0", +< "escape-string-regexp": "^2.0.0", +< "js2xmlparser": "^4.0.2", +< "klaw": "^3.0.0", +< "markdown-it": "^14.1.0", +< "markdown-it-anchor": "^8.6.7", +< "marked": "^4.0.10", +< "mkdirp": "^1.0.4", +< "requizzle": "^0.2.3", +< "strip-json-comments": "^3.1.0", +< "underscore": "~1.13.2" +< }, +< "bin": { +< "jsdoc": "jsdoc.js" +< }, +< "engines": { +< "node": ">=12.0.0" +< } +< }, +24635,24653d24332 +< "node_modules/jsdoc/node_modules/escape-string-regexp": { +< "version": "2.0.0", +< "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", +< "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", +< "engines": { +< "node": ">=8" +< } +< }, +< "node_modules/jsdoc/node_modules/mkdirp": { +< "version": "1.0.4", +< "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", +< "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", +< "bin": { +< "mkdirp": "bin/cmd.js" +< }, +< "engines": { +< "node": ">=10" +< } +< }, +25108,25115d24786 +< "node_modules/klaw": { +< "version": "3.0.0", +< "resolved": "https://registry.npmjs.org/klaw/-/klaw-3.0.0.tgz", +< "integrity": "sha512-0Fo5oir+O9jnXu5EefYbVK+mHMBeEVEy2cmctR1O1NECcCkPRreJKrS6Qt/j3KC2C148Dfo9i3pCmCMsdqGr0g==", +< "dependencies": { +< "graceful-fs": "^4.1.9" +< } +< }, +25497,25509d25167 +< "node_modules/linkify-it": { +< "version": "5.0.0", +< "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", +< "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", +< "dependencies": { +< "uc.micro": "^2.0.0" +< } +< }, +< "node_modules/linkify-it/node_modules/uc.micro": { +< "version": "2.1.0", +< "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", +< "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==" +< }, +25922,25923c25580 +< "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", +< "dev": true +--- +> "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=" +26248,26288d25904 +< "node_modules/markdown-it": { +< "version": "14.1.0", +< "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", +< "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", +< "dependencies": { +< "argparse": "^2.0.1", +< "entities": "^4.4.0", +< "linkify-it": "^5.0.0", +< "mdurl": "^2.0.0", +< "punycode.js": "^2.3.1", +< "uc.micro": "^2.1.0" +< }, +< "bin": { +< "markdown-it": "bin/markdown-it.mjs" +< } +< }, +< "node_modules/markdown-it-anchor": { +< "version": "8.6.7", +< "resolved": "https://registry.npmjs.org/markdown-it-anchor/-/markdown-it-anchor-8.6.7.tgz", +< "integrity": "sha512-FlCHFwNnutLgVTflOYHPW2pPcl2AACqVzExlkGQNsi4CJgqOHN7YTgDd4LuhgN1BFO3TS0vLAruV1Td6dwWPJA==", +< "peerDependencies": { +< "@types/markdown-it": "*", +< "markdown-it": "*" +< } +< }, +< "node_modules/markdown-it/node_modules/entities": { +< "version": "4.5.0", +< "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", +< "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", +< "engines": { +< "node": ">=0.12" +< }, +< "funding": { +< "url": "https://github.com/fb55/entities?sponsor=1" +< } +< }, +< "node_modules/markdown-it/node_modules/uc.micro": { +< "version": "2.1.0", +< "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", +< "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==" +< }, +26339,26343d25954 +< "node_modules/mdurl": { +< "version": "2.0.0", +< "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", +< "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==" +< }, +28390,28436d28000 +< "node_modules/optionator": { +< "version": "0.8.3", +< "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", +< "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", +< "dependencies": { +< "deep-is": "~0.1.3", +< "fast-levenshtein": "~2.0.6", +< "levn": "~0.3.0", +< "prelude-ls": "~1.1.2", +< "type-check": "~0.3.2", +< "word-wrap": "~1.2.3" +< }, +< "engines": { +< "node": ">= 0.8.0" +< } +< }, +< "node_modules/optionator/node_modules/levn": { +< "version": "0.3.0", +< "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", +< "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", +< "dependencies": { +< "prelude-ls": "~1.1.2", +< "type-check": "~0.3.2" +< }, +< "engines": { +< "node": ">= 0.8.0" +< } +< }, +< "node_modules/optionator/node_modules/prelude-ls": { +< "version": "1.1.2", +< "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", +< "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", +< "engines": { +< "node": ">= 0.8.0" +< } +< }, +< "node_modules/optionator/node_modules/type-check": { +< "version": "0.3.2", +< "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", +< "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", +< "dependencies": { +< "prelude-ls": "~1.1.2" +< }, +< "engines": { +< "node": ">= 0.8.0" +< } +< }, +28682,28686d28245 +< "node_modules/parse-duration": { +< "version": "1.1.0", +< "resolved": "https://registry.npmjs.org/parse-duration/-/parse-duration-1.1.0.tgz", +< "integrity": "sha512-z6t9dvSJYaPoQq7quMzdEagSFtpGu+utzHqqxmpVWNNZRIXnvqyCvn9XsTdh7c/w0Bqmdz3RB3YnRaKtpRtEXQ==" +< }, +30167,30206d29725 +< "node_modules/pprof": { +< "version": "3.2.1", +< "resolved": "https://registry.npmjs.org/pprof/-/pprof-3.2.1.tgz", +< "integrity": "sha512-KnextTM3EHQ2zqN8fUjB0VpE+njcVR7cOfo7DjJSLKzIbKTPelDtokI04ScR/Vd8CLDj+M99tsaKV+K6FHzpzA==", +< "hasInstallScript": true, +< "dependencies": { +< "@mapbox/node-pre-gyp": "^1.0.0", +< "bindings": "^1.2.1", +< "delay": "^5.0.0", +< "findit2": "^2.2.3", +< "nan": "^2.14.0", +< "p-limit": "^3.0.0", +< "pify": "^5.0.0", +< "protobufjs": "~7.2.4", +< "source-map": "^0.7.3", +< "split": "^1.0.1" +< }, +< "engines": { +< "node": ">=10.4.1" +< } +< }, +< "node_modules/pprof/node_modules/pify": { +< "version": "5.0.0", +< "resolved": "https://registry.npmjs.org/pify/-/pify-5.0.0.tgz", +< "integrity": "sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==", +< "engines": { +< "node": ">=10" +< }, +< "funding": { +< "url": "https://github.com/sponsors/sindresorhus" +< } +< }, +< "node_modules/pprof/node_modules/source-map": { +< "version": "0.7.4", +< "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", +< "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==", +< "engines": { +< "node": ">= 8" +< } +< }, +30457,30467d29975 +< "node_modules/proto3-json-serializer": { +< "version": "1.1.1", +< "resolved": "https://registry.npmjs.org/proto3-json-serializer/-/proto3-json-serializer-1.1.1.tgz", +< "integrity": "sha512-AwAuY4g9nxx0u52DnSMkqqgyLHaW/XaPLtaAo3y/ZCfeaQB/g4YDH4kb8Wc/mWzWvu0YjOznVnfn373MVZZrgw==", +< "dependencies": { +< "protobufjs": "^7.0.0" +< }, +< "engines": { +< "node": ">=12.0.0" +< } +< }, +30491,30635d29998 +< "node_modules/protobufjs-cli": { +< "version": "1.1.1", +< "resolved": "https://registry.npmjs.org/protobufjs-cli/-/protobufjs-cli-1.1.1.tgz", +< "integrity": "sha512-VPWMgIcRNyQwWUv8OLPyGQ/0lQY/QTQAVN5fh+XzfDwsVw1FZ2L3DM/bcBf8WPiRz2tNpaov9lPZfNcmNo6LXA==", +< "dependencies": { +< "chalk": "^4.0.0", +< "escodegen": "^1.13.0", +< "espree": "^9.0.0", +< "estraverse": "^5.1.0", +< "glob": "^8.0.0", +< "jsdoc": "^4.0.0", +< "minimist": "^1.2.0", +< "semver": "^7.1.2", +< "tmp": "^0.2.1", +< "uglify-js": "^3.7.7" +< }, +< "bin": { +< "pbjs": "bin/pbjs", +< "pbts": "bin/pbts" +< }, +< "engines": { +< "node": ">=12.0.0" +< }, +< "peerDependencies": { +< "protobufjs": "^7.0.0" +< } +< }, +< "node_modules/protobufjs-cli/node_modules/ansi-styles": { +< "version": "4.3.0", +< "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", +< "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", +< "dependencies": { +< "color-convert": "^2.0.1" +< }, +< "engines": { +< "node": ">=8" +< }, +< "funding": { +< "url": "https://github.com/chalk/ansi-styles?sponsor=1" +< } +< }, +< "node_modules/protobufjs-cli/node_modules/brace-expansion": { +< "version": "2.0.1", +< "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", +< "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", +< "dependencies": { +< "balanced-match": "^1.0.0" +< } +< }, +< "node_modules/protobufjs-cli/node_modules/chalk": { +< "version": "4.1.2", +< "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", +< "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", +< "dependencies": { +< "ansi-styles": "^4.1.0", +< "supports-color": "^7.1.0" +< }, +< "engines": { +< "node": ">=10" +< }, +< "funding": { +< "url": "https://github.com/chalk/chalk?sponsor=1" +< } +< }, +< "node_modules/protobufjs-cli/node_modules/escodegen": { +< "version": "1.14.3", +< "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", +< "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", +< "dependencies": { +< "esprima": "^4.0.1", +< "estraverse": "^4.2.0", +< "esutils": "^2.0.2", +< "optionator": "^0.8.1" +< }, +< "bin": { +< "escodegen": "bin/escodegen.js", +< "esgenerate": "bin/esgenerate.js" +< }, +< "engines": { +< "node": ">=4.0" +< }, +< "optionalDependencies": { +< "source-map": "~0.6.1" +< } +< }, +< "node_modules/protobufjs-cli/node_modules/escodegen/node_modules/estraverse": { +< "version": "4.3.0", +< "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", +< "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", +< "engines": { +< "node": ">=4.0" +< } +< }, +< "node_modules/protobufjs-cli/node_modules/glob": { +< "version": "8.1.0", +< "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", +< "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", +< "deprecated": "Glob versions prior to v9 are no longer supported", +< "dependencies": { +< "fs.realpath": "^1.0.0", +< "inflight": "^1.0.4", +< "inherits": "2", +< "minimatch": "^5.0.1", +< "once": "^1.3.0" +< }, +< "engines": { +< "node": ">=12" +< }, +< "funding": { +< "url": "https://github.com/sponsors/isaacs" +< } +< }, +< "node_modules/protobufjs-cli/node_modules/minimatch": { +< "version": "5.1.6", +< "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", +< "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", +< "dependencies": { +< "brace-expansion": "^2.0.1" +< }, +< "engines": { +< "node": ">=10" +< } +< }, +< "node_modules/protobufjs-cli/node_modules/semver": { +< "version": "7.6.2", +< "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", +< "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", +< "bin": { +< "semver": "bin/semver.js" +< }, +< "engines": { +< "node": ">=10" +< } +< }, +< "node_modules/protobufjs-cli/node_modules/supports-color": { +< "version": "7.2.0", +< "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", +< "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", +< "dependencies": { +< "has-flag": "^4.0.0" +< }, +< "engines": { +< "node": ">=8" +< } +< }, +30797,30807d30159 +< "node_modules/pumpify/node_modules/duplexify": { +< "version": "4.1.2", +< "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.2.tgz", +< "integrity": "sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw==", +< "dependencies": { +< "end-of-stream": "^1.4.1", +< "inherits": "^2.0.3", +< "readable-stream": "^3.1.1", +< "stream-shift": "^1.0.0" +< } +< }, +30816,30823d30167 +< "node_modules/punycode.js": { +< "version": "2.3.1", +< "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", +< "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", +< "engines": { +< "node": ">=6" +< } +< }, +31992,31999d31335 +< "node_modules/requizzle": { +< "version": "0.2.4", +< "resolved": "https://registry.npmjs.org/requizzle/-/requizzle-0.2.4.tgz", +< "integrity": "sha512-JRrFk1D4OQ4SqovXOgdav+K8EAhSB/LJZqCz8tbX0KObcdeM15Ss59ozWMBWmmINMagCwmqn4ZNryUGpBsl6Jw==", +< "dependencies": { +< "lodash": "^4.17.21" +< } +< }, +32106a31443,31456 +> "node_modules/retry-request": { +> "version": "7.0.2", +> "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-7.0.2.tgz", +> "integrity": "sha512-dUOvLMJ0/JJYEn8NrpOaGNE7X3vpI5XlZS/u0ANjqtcZVKnIxP7IgCFwrKTxENw29emmwug53awKtaMm4i9g5w==", +> "license": "MIT", +> "dependencies": { +> "@types/request": "^2.48.8", +> "extend": "^3.0.2", +> "teeny-request": "^9.0.0" +> }, +> "engines": { +> "node": ">=14" +> } +> }, +32122a31473,31488 +> "node_modules/rimraf": { +> "version": "3.0.2", +> "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", +> "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", +> "deprecated": "Rimraf versions prior to v4 are no longer supported", +> "license": "ISC", +> "dependencies": { +> "glob": "^7.1.3" +> }, +> "bin": { +> "rimraf": "bin.js" +> }, +> "funding": { +> "url": "https://github.com/sponsors/isaacs" +> } +> }, +33320c32686 +< "devOptional": true, +--- +> "dev": true, +33488,33512d32853 +< "node_modules/ssh2": { +< "version": "1.6.0", +< "resolved": "https://registry.npmjs.org/ssh2/-/ssh2-1.6.0.tgz", +< "integrity": "sha512-lxc+uvXqOxyQ99N2M7k5o4pkYDO5GptOTYduWw7hIM41icxvoBcCNHcj+LTKrjkL0vFcAl+qfZekthoSFRJn2Q==", +< "hasInstallScript": true, +< "dependencies": { +< "asn1": "^0.2.4", +< "bcrypt-pbkdf": "^1.0.2" +< }, +< "engines": { +< "node": ">=10.16.0" +< }, +< "optionalDependencies": { +< "cpu-features": "0.0.2", +< "nan": "^2.15.0" +< } +< }, +< "node_modules/ssh2/node_modules/asn1": { +< "version": "0.2.6", +< "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", +< "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", +< "dependencies": { +< "safer-buffer": "~2.1.0" +< } +< }, +33537,33544d32877 +< "node_modules/sshpk/node_modules/asn1": { +< "version": "0.2.6", +< "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", +< "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", +< "dependencies": { +< "safer-buffer": "~2.1.0" +< } +< }, +33896a33230 +> "dev": true, +34789,34799d34122 +< "node_modules/tar-fs": { +< "version": "2.0.1", +< "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.0.1.tgz", +< "integrity": "sha512-6tzWDMeroL87uF/+lin46k+Q+46rAJ0SyPGz7OW7wTgblI273hsBqk2C1j0/xNadNLKDTUL9BukSjB7cwgmlPA==", +< "dependencies": { +< "chownr": "^1.1.1", +< "mkdirp-classic": "^0.5.2", +< "pump": "^3.0.0", +< "tar-stream": "^2.0.0" +< } +< }, +34862a34186,34214 +> "node_modules/teeny-request": { +> "version": "9.0.0", +> "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-9.0.0.tgz", +> "integrity": "sha512-resvxdc6Mgb7YEThw6G6bExlXKkv6+YbuzGg9xuXxSgxJF7Ozs+o8Y9+2R3sArdWdW8nOokoQb1yrpFB0pQK2g==", +> "license": "Apache-2.0", +> "dependencies": { +> "http-proxy-agent": "^5.0.0", +> "https-proxy-agent": "^5.0.0", +> "node-fetch": "^2.6.9", +> "stream-events": "^1.0.5", +> "uuid": "^9.0.0" +> }, +> "engines": { +> "node": ">=14" +> } +> }, +> "node_modules/teeny-request/node_modules/uuid": { +> "version": "9.0.1", +> "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", +> "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", +> "funding": [ +> "https://github.com/sponsors/broofa", +> "https://github.com/sponsors/ctavan" +> ], +> "license": "MIT", +> "bin": { +> "uuid": "dist/bin/uuid" +> } +> }, +35286a34639 +> "dev": true, +35442d34794 +< "dev": true, +35745a35098,35099 +> "dev": true, +> "optional": true, +37308,37315d36661 +< "node_modules/word-wrap": { +< "version": "1.2.5", +< "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", +< "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", +< "engines": { +< "node": ">=0.10.0" +< } +< }, +37546,37550d36891 +< "node_modules/xmlcreate": { +< "version": "2.0.4", +< "resolved": "https://registry.npmjs.org/xmlcreate/-/xmlcreate-2.0.4.tgz", +< "integrity": "sha512-nquOebG4sngPmGPICTS5EnxqhKbCmz5Ox5hsszI2T6U5qdrJizBc+0ilYSEjTSzU0yZcmvppztXe/5Al5fUwdg==" +< }, +37861c37202 +< "dockerode": "^3.1.0", +--- +> "dockerode": "^4.0.6", +37904a37246,37272 +> "services/clsi/node_modules/@grpc/grpc-js": { +> "version": "1.13.3", +> "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.13.3.tgz", +> "integrity": "sha512-FTXHdOoPbZrBjlVLHuKbDZnsTxXv2BlHF57xw6LuThXacXvtkahEPED0CKMk6obZDf65Hv4k3z62eyPNpvinIg==", +> "license": "Apache-2.0", +> "dependencies": { +> "@grpc/proto-loader": "^0.7.13", +> "@js-sdsl/ordered-map": "^4.4.2" +> }, +> "engines": { +> "node": ">=12.10.0" +> } +> }, +> "services/clsi/node_modules/cpu-features": { +> "version": "0.0.10", +> "resolved": "https://registry.npmjs.org/cpu-features/-/cpu-features-0.0.10.tgz", +> "integrity": "sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA==", +> "hasInstallScript": true, +> "optional": true, +> "dependencies": { +> "buildcheck": "~0.0.6", +> "nan": "^2.19.0" +> }, +> "engines": { +> "node": ">=10.0.0" +> } +> }, +37913a37282,37345 +> "services/clsi/node_modules/docker-modem": { +> "version": "5.0.6", +> "resolved": "https://registry.npmjs.org/docker-modem/-/docker-modem-5.0.6.tgz", +> "integrity": "sha512-ens7BiayssQz/uAxGzH8zGXCtiV24rRWXdjNha5V4zSOcxmAZsfGVm/PPFbwQdqEkDnhG+SyR9E3zSHUbOKXBQ==", +> "license": "Apache-2.0", +> "dependencies": { +> "debug": "^4.1.1", +> "readable-stream": "^3.5.0", +> "split-ca": "^1.0.1", +> "ssh2": "^1.15.0" +> }, +> "engines": { +> "node": ">= 8.0" +> } +> }, +> "services/clsi/node_modules/dockerode": { +> "version": "4.0.6", +> "resolved": "https://registry.npmjs.org/dockerode/-/dockerode-4.0.6.tgz", +> "integrity": "sha512-FbVf3Z8fY/kALB9s+P9epCpWhfi/r0N2DgYYcYpsAUlaTxPjdsitsFobnltb+lyCgAIvf9C+4PSWlTnHlJMf1w==", +> "license": "Apache-2.0", +> "dependencies": { +> "@balena/dockerignore": "^1.0.2", +> "@grpc/grpc-js": "^1.11.1", +> "@grpc/proto-loader": "^0.7.13", +> "docker-modem": "^5.0.6", +> "protobufjs": "^7.3.2", +> "tar-fs": "~2.1.2", +> "uuid": "^10.0.0" +> }, +> "engines": { +> "node": ">= 8.0" +> } +> }, +> "services/clsi/node_modules/nan": { +> "version": "2.22.2", +> "resolved": "https://registry.npmjs.org/nan/-/nan-2.22.2.tgz", +> "integrity": "sha512-DANghxFkS1plDdRsX0X9pm0Z6SJNN6gBdtXfanwoZ8hooC5gosGFSBGRYHUVPz1asKA/kMRqDRdHrluZ61SpBQ==", +> "license": "MIT", +> "optional": true +> }, +> "services/clsi/node_modules/protobufjs": { +> "version": "7.5.0", +> "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.0.tgz", +> "integrity": "sha512-Z2E/kOY1QjoMlCytmexzYfDm/w5fKAiRwpSzGtdnXW1zC88Z2yXazHHrOtwCzn+7wSxyE8PYM4rvVcMphF9sOA==", +> "hasInstallScript": true, +> "license": "BSD-3-Clause", +> "dependencies": { +> "@protobufjs/aspromise": "^1.1.2", +> "@protobufjs/base64": "^1.1.2", +> "@protobufjs/codegen": "^2.0.4", +> "@protobufjs/eventemitter": "^1.1.0", +> "@protobufjs/fetch": "^1.1.0", +> "@protobufjs/float": "^1.0.2", +> "@protobufjs/inquire": "^1.1.0", +> "@protobufjs/path": "^1.1.2", +> "@protobufjs/pool": "^1.1.0", +> "@protobufjs/utf8": "^1.1.0", +> "@types/node": ">=13.7.0", +> "long": "^5.0.0" +> }, +> "engines": { +> "node": ">=12.0.0" +> } +> }, +37932a37365,37381 +> "services/clsi/node_modules/ssh2": { +> "version": "1.16.0", +> "resolved": "https://registry.npmjs.org/ssh2/-/ssh2-1.16.0.tgz", +> "integrity": "sha512-r1X4KsBGedJqo7h8F5c4Ybpcr5RjyP+aWIG007uBPRjmdQWfEiVLzSK71Zji1B9sKxwaCvD8y8cwSkYrlLiRRg==", +> "hasInstallScript": true, +> "dependencies": { +> "asn1": "^0.2.6", +> "bcrypt-pbkdf": "^1.0.2" +> }, +> "engines": { +> "node": ">=10.16.0" +> }, +> "optionalDependencies": { +> "cpu-features": "~0.0.10", +> "nan": "^2.20.0" +> } +> }, +37942a37392,37416 +> } +> }, +> "services/clsi/node_modules/tar-fs": { +> "version": "2.1.2", +> "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.2.tgz", +> "integrity": "sha512-EsaAXwxmx8UB7FRKqeozqEPop69DXcmYwTQwXvyAPF352HJsPdkVhvTaDPYqfNgruveJIJy3TA2l+2zj8LJIJA==", +> "license": "MIT", +> "dependencies": { +> "chownr": "^1.1.1", +> "mkdirp-classic": "^0.5.2", +> "pump": "^3.0.0", +> "tar-stream": "^2.1.4" +> } +> }, +> "services/clsi/node_modules/uuid": { +> "version": "10.0.0", +> "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", +> "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", +> "funding": [ +> "https://github.com/sponsors/broofa", +> "https://github.com/sponsors/ctavan" +> ], +> "license": "MIT", +> "bin": { +> "uuid": "dist/bin/uuid" From 17d1b0b8d67bfe5376598248cf1576654b71f41c Mon Sep 17 00:00:00 2001 From: Eric Mc Sween <5454374+emcsween@users.noreply.github.com> Date: Tue, 20 May 2025 12:09:51 -0400 Subject: [PATCH 174/194] Merge pull request #25646 from overleaf/em-ds-mobile-app-compile Compile endpoint for the DS mobile app GitOrigin-RevId: 2fd9f4a6e8c2ed4ee868b0c1293f6760b9d113c8 --- .../app/src/Features/Compile/CompileController.js | 1 + .../web/test/acceptance/src/LinkedFilesTests.mjs | 8 ++++---- .../web/test/acceptance/src/mocks/MockClsiApi.mjs | 12 ++++++------ .../test/unit/src/Compile/CompileControllerTests.js | 2 +- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/services/web/app/src/Features/Compile/CompileController.js b/services/web/app/src/Features/Compile/CompileController.js index 8b190402ff..9d5cbf63b9 100644 --- a/services/web/app/src/Features/Compile/CompileController.js +++ b/services/web/app/src/Features/Compile/CompileController.js @@ -715,6 +715,7 @@ async function _getPersistenceOptions( } const CompileController = { + COMPILE_TIMEOUT_MS, compile: expressify(_CompileController.compile), stopCompile: expressify(_CompileController.stopCompile), compileSubmission: expressify(_CompileController.compileSubmission), diff --git a/services/web/test/acceptance/src/LinkedFilesTests.mjs b/services/web/test/acceptance/src/LinkedFilesTests.mjs index 2edcada015..8010aac407 100644 --- a/services/web/test/acceptance/src/LinkedFilesTests.mjs +++ b/services/web/test/acceptance/src/LinkedFilesTests.mjs @@ -443,7 +443,7 @@ describe('LinkedFiles', function () { projectTwoRootFolderId = projectTwo.rootFolder[0]._id.toString() }) - it('should import the project.pdf file from the source project and refresh it', async function () { + it('should import the output.pdf file from the source project and refresh it', async function () { // import the file let { response, body } = await owner.doRequest('post', { url: `/project/${projectOneId}/linked_file`, @@ -453,7 +453,7 @@ describe('LinkedFiles', function () { provider: 'project_output_file', data: { source_project_id: projectTwoId, - source_output_file_path: 'project.pdf', + source_output_file_path: 'output.pdf', build_id: '1234-abcd', }, }, @@ -468,7 +468,7 @@ describe('LinkedFiles', function () { expect(firstFile.linkedFileData).to.deep.equal({ provider: 'project_output_file', source_project_id: projectTwoId, - source_output_file_path: 'project.pdf', + source_output_file_path: 'output.pdf', build_id: '1234-abcd', importedAt: new Date().toISOString(), }) @@ -503,7 +503,7 @@ describe('LinkedFiles', function () { linkedFileData: { provider: 'project_output_file', v1_source_doc_id: 9999999, // We won't find this id in the database - source_output_file_path: 'project.pdf', + source_output_file_path: 'output.pdf', }, _id: 'abcdef', rev: 0, diff --git a/services/web/test/acceptance/src/mocks/MockClsiApi.mjs b/services/web/test/acceptance/src/mocks/MockClsiApi.mjs index 102b75b0d3..de2fc488c0 100644 --- a/services/web/test/acceptance/src/mocks/MockClsiApi.mjs +++ b/services/web/test/acceptance/src/mocks/MockClsiApi.mjs @@ -9,14 +9,14 @@ class MockClsiApi extends AbstractMockApi { error: null, outputFiles: [ { - url: `http://clsi:3013/project/${req.params.project_id}/build/1234/output/project.pdf`, - path: 'project.pdf', + url: `http://clsi:3013/project/${req.params.project_id}/build/1234/output/output.pdf`, + path: 'output.pdf', type: 'pdf', build: 1234, }, { - url: `http://clsi:3013/project/${req.params.project_id}/build/1234/output/project.log`, - path: 'project.log', + url: `http://clsi:3013/project/${req.params.project_id}/build/1234/output/output.log`, + path: 'output.log', type: 'log', build: 1234, }, @@ -36,9 +36,9 @@ class MockClsiApi extends AbstractMockApi { '/project/:project_id/build/:build_id/output/*', (req, res) => { const filename = req.params[0] - if (filename === 'project.pdf') { + if (filename === 'output.pdf') { plainTextResponse(res, 'mock-pdf') - } else if (filename === 'project.log') { + } else if (filename === 'output.log') { plainTextResponse(res, 'mock-log') } else { res.sendStatus(404) diff --git a/services/web/test/unit/src/Compile/CompileControllerTests.js b/services/web/test/unit/src/Compile/CompileControllerTests.js index 25af7926b1..92bd21d176 100644 --- a/services/web/test/unit/src/Compile/CompileControllerTests.js +++ b/services/web/test/unit/src/Compile/CompileControllerTests.js @@ -538,7 +538,7 @@ describe('CompileController', function () { describe('getFileFromClsiWithoutUser', function () { beforeEach(function () { this.submission_id = 'sub-1234' - this.file = 'project.pdf' + this.file = 'output.pdf' this.req.params = { submission_id: this.submission_id, build_id: this.build_id, From c45bca6ce9559b3914c336c0d7ca43c3cbff055d Mon Sep 17 00:00:00 2001 From: David <33458145+davidmcpowell@users.noreply.github.com> Date: Wed, 21 May 2025 09:47:18 +0100 Subject: [PATCH 175/194] Merge pull request #25780 from overleaf/dp-equation-preview-copy Update copy in equation preview disable modal for new editor GitOrigin-RevId: 51f1c00764ed3cb8f48448846e575ca738f71238 --- .../web/frontend/extracted-translations.json | 1 + .../components/math-preview-tooltip.tsx | 18 ++++++++++++++---- services/web/locales/en.json | 1 + 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json index 36e66feed7..d8f23bd45f 100644 --- a/services/web/frontend/extracted-translations.json +++ b/services/web/frontend/extracted-translations.json @@ -399,6 +399,7 @@ "disable_equation_preview": "", "disable_equation_preview_confirm": "", "disable_equation_preview_enable": "", + "disable_equation_preview_enable_in_settings": "", "disable_single_sign_on": "", "disable_sso": "", "disable_stop_on_first_error": "", diff --git a/services/web/frontend/js/features/source-editor/components/math-preview-tooltip.tsx b/services/web/frontend/js/features/source-editor/components/math-preview-tooltip.tsx index 4e2a200f44..60d9c430e2 100644 --- a/services/web/frontend/js/features/source-editor/components/math-preview-tooltip.tsx +++ b/services/web/frontend/js/features/source-editor/components/math-preview-tooltip.tsx @@ -23,6 +23,7 @@ import { mathPreviewStateField } from '../extensions/math-preview' import { getTooltip } from '@codemirror/view' import ReactDOM from 'react-dom' import OLDropdownMenuItem from '@/features/ui/components/ol/ol-dropdown-menu-item' +import { useIsNewEditorEnabled } from '@/features/ide-redesign/utils/new-editor-utils' const MathPreviewTooltipContainer: FC = () => { const state = useCodeMirrorStateContext() @@ -57,6 +58,8 @@ const MathPreviewTooltip: FC<{ mathContent: HTMLDivElement }> = ({ }) => { const { t } = useTranslation() + const newEditor = useIsNewEditorEnabled() + const [showDisableModal, setShowDisableModal] = useState(false) const { setMathPreview } = useProjectSettingsContext() const openDisableModal = useCallback(() => setShowDisableModal(true), []) @@ -133,10 +136,17 @@ const MathPreviewTooltip: FC<{ mathContent: HTMLDivElement }> = ({ {t('disable_equation_preview_confirm')}
    - }} - /> + {newEditor ? ( + }} + /> + ) : ( + }} + /> + )}
    diff --git a/services/web/locales/en.json b/services/web/locales/en.json index 21ee0fd735..08f22483a7 100644 --- a/services/web/locales/en.json +++ b/services/web/locales/en.json @@ -521,6 +521,7 @@ "disable_equation_preview": "Disable equation preview", "disable_equation_preview_confirm": "This will disable equation preview for you in all projects.", "disable_equation_preview_enable": "You can enable it again from the Menu.", + "disable_equation_preview_enable_in_settings": "You can enable it again in Settings.", "disable_single_sign_on": "Disable single sign-on", "disable_sso": "Disable SSO", "disable_stop_on_first_error": "Disable “Stop on first error”", From 061f10a059aca9a445e79bcaeb33a4a52d580d70 Mon Sep 17 00:00:00 2001 From: David <33458145+davidmcpowell@users.noreply.github.com> Date: Wed, 21 May 2025 09:47:25 +0100 Subject: [PATCH 176/194] Merge pull request #25752 from overleaf/dp-delete-multiple-files Add bulk delete button to file tree toolbar GitOrigin-RevId: c857d8f5027eddb29b1ca324efe1a0e94ef4c28b --- ...alSymbolsRoundedUnfilledPartialSlice.woff2 | Bin 4392 -> 4384 bytes .../material-symbols/unfilled-symbols.mjs | 1 + .../file-tree/components/file-tree-root.tsx | 2 +- .../contexts/file-tree-actionable.tsx | 5 +- .../components/file-tree-toolbar.tsx | 149 ------------------ .../file-tree/file-tree-action-button.tsx | 33 ++++ .../file-tree/file-tree-action-buttons.tsx | 107 +++++++++++++ .../file-tree-outline-panel.tsx | 2 +- .../file-tree/file-tree-toolbar.tsx | 32 ++++ .../features/ide-redesign/components/rail.tsx | 2 +- 10 files changed, 180 insertions(+), 153 deletions(-) delete mode 100644 services/web/frontend/js/features/ide-redesign/components/file-tree-toolbar.tsx create mode 100644 services/web/frontend/js/features/ide-redesign/components/file-tree/file-tree-action-button.tsx create mode 100644 services/web/frontend/js/features/ide-redesign/components/file-tree/file-tree-action-buttons.tsx rename services/web/frontend/js/features/ide-redesign/components/{ => file-tree}/file-tree-outline-panel.tsx (95%) create mode 100644 services/web/frontend/js/features/ide-redesign/components/file-tree/file-tree-toolbar.tsx diff --git a/services/web/frontend/fonts/material-symbols/MaterialSymbolsRoundedUnfilledPartialSlice.woff2 b/services/web/frontend/fonts/material-symbols/MaterialSymbolsRoundedUnfilledPartialSlice.woff2 index 14a5ef1e2bb15e116072c39297e63a01e822b47c..df942df17688781db3c188df5a20253919ec02fc 100644 GIT binary patch literal 4384 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 awAp~2GYtG2#iPx+myzd%{|nE^D**u2R#deB literal 4392 zcmV+@5!dc_Pew8T0RR9101+qv4gdfE03pl(01(dr0RR9100000000000000000000 z0000ShFS(-KT}jeRAK;vI1va6uMDSb3t#{NHUcCAU<4oqg$@TG3TKh4K_Gek`2%6h{j)Ew{U?7HKnu3VnlA)zxNj4!K z=iYlMegIC=c1RjfxOrq_r>8U;Jtby&yZ<+nq+~)8WKBp^P1KSpNhL*&?f1g>9lVKK zwh)yMa}Rl|_bM_^;>}W97LB6+H&@P2CVSc8>rIjZkOUTn0|i2b^IhM~t6L}$XaeIa zO#lCJ?;PLypTXTQ=^JGahsddxnh^1AMw@E@wGVv!o6w(-2XA>2-mzY=qr;9J4 zQb|hx*M9BIu%`(X-Nnr!*WflGB8lWyZ)iU;G0sM&#Z@^Ok#5j~QFeY;@w(W4V4RLg zQWFqQr^odpZf=)CddNh*dEozWf1fr7cA{y+)}W}3fo9XvDD6biH~41XK0Nqi>tUK1 ziBZLpm^HC=O@v0$kSU3genkB4x}|W_-_J#XB<=0ID)2`xQ+E@O(%A$C)aSG0|NpOj zt22LbZMxbvG$0g0rqQT7_r00V%>D0+k72exvf}d*lMHRQV`6a|oNf}DSf(XnBn6JK z`R^I9h(gEkh%rQr_x|tOL4Y)E32zT+VXxPEvbYSSUcvHs?1ed!`O#bgK*=xCS62@q zA|He@7ft>#IhA)(!SZ%t{QQtm23evSmY{XzRqZ$1eAnmKlHe z=2%chQN+iC#vMiskLEk-T^u92Q0$w^CQP7^2 zEjh9JE%|8VnInpGQ29C1tJjUprb%vSG2w@hR5?Tw5qt!jYhAv^Xr@6 z57QrxuI8@}u3oP-ZjxSdUusz4Tj^UZYhk@?fo-zC``?GK_mCgI|8)RM0AXTSO>#-e z9p?prcld%6Scf%O<&XiF2FxoQ@At0X>3*~GGxCG;_51wOo$uA|bu7D-wX(xm`?CtN zlCqX(#i=%r^<#D`p5t2oVjN5&GR-)c@gF#9(X0vl3hro^*!N^zSusW~WKqoO?XC#q zfMR^46aahyKES|RbSa%#!1l7PjD}$t8W!!K#u%f76+)hGUJ$zVI8`*W@vCB9M$g)(pbJTvEeO5_>OD;hEVr7r|xC99&znUYkeYVUd!6IjC+QQa6Bz-Hvpc>DmTHozmgQD zix79*=6aM#7D*#OkW{)zY-O*ifQ{@mHMwsC04K#GdBQ-ca>CexAU(9;zm8S-7D9Z- zrT>Nm?lqhAywpKp?$v9!#r+LJ5kdPK#9e2Lw1D`J3y-|9(_z}`wz)1#3M;P@)2dCo zr0KKDm8Yd`unm{23y>!K)K|Ixtix+c$D|G_uvJ4LHC=Pr;|^fJTIM`4^Iowi=@0DsoGUp`$4X3BWrADmFodKna;3R8D6%z z4y)_9v6cqXoJQ8v2vtL8(87Cpju6M&-Go;Y;`tD$2iZqHu=aek|j3j zv~4ykIp-Ra>BiYzJ(V!5vlge*@^E{&Dr;$bvW2$0=g}6}?%t;e&U~~pLb(~v{f(qJ z*czr>y`0}^=b5e!NtkxxYD7=*iCMx4ofu@jAIZ0C=`{RVz-3rv!0{tYvk4 zqnEfdz497$f<08{sYH@Ez)(DR1}!g-zLjOe;=EEx;Y=vsQgbJVJ3}_~Cftca*LsF_5es20Awg}IIK-PaIk?uWrvC<%B_h!m|Lpeuy(eq^K+ z;s?Z|B%|7C_yI^viO{#vYy^idu$r(0;vq9yD@x080t!Q&knX3)Lvr{w1UV$H4E=uB zAxdUbCmI3;UkDD@_i(#qI-pPtp;jmt*Fdfj>}v=2l7nhY$WR64P=Y*a;uld16rfN{ z#sd^kAHBcR`12~UEq_TRAHbBa*C+q|p*nuIwu`oN3!h)p!mEDqcD7RyZ zJ>zw{gMV5jxYBApysnfr>56CnkPb%imNT3z>G1p&i+CkvTGi;!!}!1#@J`yX|x<=@WpXG;@-yG>mUiO zk!@K-W=1DQli@z-4zVPpFIlHBGn;Ie9E=KB&WQp=!GN4rD5UhzQlQm|KhBW+tS9*G zcmx1ACZ9Q!2z7=Z<;9cPJOahi?8vnb2ZkBLEHrtQHBde-)_35>@UfzCBcXRZLdi;p z!(?)duU(obTu>LqbzI_XA{izAqz{8&Z!~|sbfKeldELu1Rp|*O4Z0z*fRxS8e^d*H z5}p5SX!8x+=Apn<2Zb;Ppbr^GxV&Xo=ul{!WTz!7SP zAnvrjyp5mV3`e-^}2?+0~*BY<=p9aMNCWLw2p_UHx1_yz8>ylMrc$;7b z%nFYn6<5Lokf}$of&NT901W`n`5XO&VfwpNhlCWd-5JNlcpIn|7Z-5I8)GHW{d~L$ zZOXbz#)Qlvx;zNVc1j1ApxbKYE_af9W`{E(nB*RuU9td@XBACyrD&f|w$r@CFPup^ z+g^KtB0pe80U%=O5q)?7vfTHo008fPIfF9z^ZGbv4E5$X=SaDV*{>4V&wRb@9JQ_* zx3``MI5H9d^m;?fBc|`*IDj|l-r{h!IQ;d`od2Kx)Iu+>C)E@AUHJrG zfyEu3XNfI6E?Tl;3}Ze$7svxUvkpcQ0S!x+jIJ`*@3;3SFQ##&hV#|e69b<7-dchs zq(YLfv?H4@3;V?sP-vx?a6fK5f6iB$L`uqORTAJ4AMdf=P3|rj{HL|ES6&}!G*vZM z*>|bW8*J+CQ#w@4!8j?1V#H-%tT~u1U3WX{TiEn3<4FN= zit&yHbMl9(sq3V`x-`*u``Mht=R6z+x=iV$qY95m zZ=bLr*rO(_A-c$5Jf}OJ8RRzPWCID9jY2-L=x(R@DGsw$2X-sf@3&x|6oMk2WD>YW zsz-=2XGQk4=o`-K13;v#`H}hq7+za=M1xlm^U<;* zFAvb=0U82yY@h|+hu$G8BO}6Lc!sMbWUM`)tz#-gJwVx&^-O>)o!3Tb#AgFak^ = ({ const value = useMemo( () => ({ canDelete: write && selectedEntityIds.size > 0 && !isRootFolderSelected, + canBulkDelete: + write && selectedEntityIds.size > 1 && !isRootFolderSelected, canRename: write && selectedEntityIds.size === 1 && !isRootFolderSelected, canCreate: write && selectedEntityIds.size < 2, ...state, @@ -545,8 +548,8 @@ export const FileTreeActionableProvider: FC = ({ isDuplicate, isRootFolderSelected, parentFolderId, - selectedEntityIds.size, selectedFileName, + selectedEntityIds.size, startCreatingDocOrFile, startCreatingFile, startCreatingFolder, diff --git a/services/web/frontend/js/features/ide-redesign/components/file-tree-toolbar.tsx b/services/web/frontend/js/features/ide-redesign/components/file-tree-toolbar.tsx deleted file mode 100644 index f1d72941f5..0000000000 --- a/services/web/frontend/js/features/ide-redesign/components/file-tree-toolbar.tsx +++ /dev/null @@ -1,149 +0,0 @@ -import { useTranslation } from 'react-i18next' -import * as eventTracking from '../../../infrastructure/event-tracking' -import { useFileTreeActionable } from '@/features/file-tree/contexts/file-tree-actionable' -import { useFileTreeData } from '@/shared/context/file-tree-data-context' -import OLTooltip from '@/features/ui/components/ol/ol-tooltip' -import MaterialIcon, { - AvailableUnfilledIcon, -} from '@/shared/components/material-icon' -import React from 'react' -import useCollapsibleFileTree from '../hooks/use-collapsible-file-tree' -import { useCommandProvider } from '@/features/ide-react/hooks/use-command-provider' -import { usePermissionsContext } from '@/features/ide-react/context/permissions-context' - -function FileTreeToolbar() { - const { t } = useTranslation() - const { fileTreeExpanded, toggleFileTreeExpanded } = useCollapsibleFileTree() - - return ( -
    - - -
    - ) -} - -function FileTreeActionButtons() { - const { t } = useTranslation() - const { fileTreeReadOnly } = useFileTreeData() - const { write } = usePermissionsContext() - - const { - canCreate, - startCreatingFolder, - startCreatingDocOrFile, - startUploadingDocOrFile, - } = useFileTreeActionable() - useCommandProvider(() => { - if (!canCreate || fileTreeReadOnly || !write) return - return [ - { - label: t('new_file'), - id: 'new_file', - handler: ({ location }) => { - eventTracking.sendMB('new-file-click', { location }) - startCreatingDocOrFile() - }, - }, - { - label: t('new_folder'), - id: 'new_folder', - handler: startCreatingFolder, - }, - { - label: t('upload_file'), - id: 'upload_file', - handler: ({ location }) => { - eventTracking.sendMB('upload-click', { location }) - startUploadingDocOrFile() - }, - }, - ] - }, [ - canCreate, - fileTreeReadOnly, - startCreatingDocOrFile, - t, - startCreatingFolder, - startUploadingDocOrFile, - write, - ]) - - if (!canCreate || fileTreeReadOnly) return null - - const createWithAnalytics = () => { - eventTracking.sendMB('new-file-click', { location: 'toolbar' }) - startCreatingDocOrFile() - } - - const uploadWithAnalytics = () => { - eventTracking.sendMB('upload-click', { location: 'toolbar' }) - startUploadingDocOrFile() - } - - return ( -
    - - - -
    - ) -} - -function FileTreeActionButton({ - id, - description, - onClick, - iconType, -}: { - id: string - description: string - onClick: () => void - iconType: AvailableUnfilledIcon -}) { - return ( - - - - ) -} - -export default FileTreeToolbar diff --git a/services/web/frontend/js/features/ide-redesign/components/file-tree/file-tree-action-button.tsx b/services/web/frontend/js/features/ide-redesign/components/file-tree/file-tree-action-button.tsx new file mode 100644 index 0000000000..5678db58c8 --- /dev/null +++ b/services/web/frontend/js/features/ide-redesign/components/file-tree/file-tree-action-button.tsx @@ -0,0 +1,33 @@ +import OLTooltip from '@/features/ui/components/ol/ol-tooltip' +import MaterialIcon, { + AvailableUnfilledIcon, +} from '@/shared/components/material-icon' +import React from 'react' + +export default function FileTreeActionButton({ + id, + description, + onClick, + iconType, +}: { + id: string + description: string + onClick: () => void + iconType: AvailableUnfilledIcon +}) { + return ( + + + + ) +} diff --git a/services/web/frontend/js/features/ide-redesign/components/file-tree/file-tree-action-buttons.tsx b/services/web/frontend/js/features/ide-redesign/components/file-tree/file-tree-action-buttons.tsx new file mode 100644 index 0000000000..93760b97ab --- /dev/null +++ b/services/web/frontend/js/features/ide-redesign/components/file-tree/file-tree-action-buttons.tsx @@ -0,0 +1,107 @@ +import { useTranslation } from 'react-i18next' +import * as eventTracking from '../../../../infrastructure/event-tracking' +import { useFileTreeActionable } from '@/features/file-tree/contexts/file-tree-actionable' +import { useFileTreeData } from '@/shared/context/file-tree-data-context' +import React from 'react' +import { useCommandProvider } from '@/features/ide-react/hooks/use-command-provider' +import { usePermissionsContext } from '@/features/ide-react/context/permissions-context' +import FileTreeActionButton from './file-tree-action-button' + +export default function FileTreeActionButtons() { + const { t } = useTranslation() + const { fileTreeReadOnly } = useFileTreeData() + const { write } = usePermissionsContext() + + const { + canCreate, + canBulkDelete, + startDeleting, + startCreatingFolder, + startCreatingDocOrFile, + startUploadingDocOrFile, + } = useFileTreeActionable() + + useCommandProvider(() => { + if (!canCreate || fileTreeReadOnly || !write) return + return [ + { + label: t('new_file'), + id: 'new_file', + handler: ({ location }) => { + eventTracking.sendMB('new-file-click', { location }) + startCreatingDocOrFile() + }, + }, + { + label: t('new_folder'), + id: 'new_folder', + handler: startCreatingFolder, + }, + { + label: t('upload_file'), + id: 'upload_file', + handler: ({ location }) => { + eventTracking.sendMB('upload-click', { location }) + startUploadingDocOrFile() + }, + }, + ] + }, [ + canCreate, + fileTreeReadOnly, + startCreatingDocOrFile, + t, + startCreatingFolder, + startUploadingDocOrFile, + write, + ]) + + if (fileTreeReadOnly) return null + + const createWithAnalytics = () => { + eventTracking.sendMB('new-file-click', { location: 'toolbar' }) + startCreatingDocOrFile() + } + + const uploadWithAnalytics = () => { + eventTracking.sendMB('upload-click', { location: 'toolbar' }) + startUploadingDocOrFile() + } + + return ( +
    + {canCreate && ( + + )} + {canCreate && ( + + )} + {canCreate && ( + + )} + {canBulkDelete && ( + + )} +
    + ) +} diff --git a/services/web/frontend/js/features/ide-redesign/components/file-tree-outline-panel.tsx b/services/web/frontend/js/features/ide-redesign/components/file-tree/file-tree-outline-panel.tsx similarity index 95% rename from services/web/frontend/js/features/ide-redesign/components/file-tree-outline-panel.tsx rename to services/web/frontend/js/features/ide-redesign/components/file-tree/file-tree-outline-panel.tsx index bf69d40e59..62c228973e 100644 --- a/services/web/frontend/js/features/ide-redesign/components/file-tree-outline-panel.tsx +++ b/services/web/frontend/js/features/ide-redesign/components/file-tree/file-tree-outline-panel.tsx @@ -3,7 +3,7 @@ import { FileTree } from '@/features/ide-react/components/file-tree' import { OutlineContainer } from '@/features/outline/components/outline-container' import { VerticalResizeHandle } from '@/features/ide-react/components/resize/vertical-resize-handle' import { useOutlinePane } from '@/features/ide-react/hooks/use-outline-pane' -import useCollapsibleFileTree from '../hooks/use-collapsible-file-tree' +import useCollapsibleFileTree from '../../hooks/use-collapsible-file-tree' import classNames from 'classnames' function FileTreeOutlinePanel() { diff --git a/services/web/frontend/js/features/ide-redesign/components/file-tree/file-tree-toolbar.tsx b/services/web/frontend/js/features/ide-redesign/components/file-tree/file-tree-toolbar.tsx new file mode 100644 index 0000000000..231f98e7d7 --- /dev/null +++ b/services/web/frontend/js/features/ide-redesign/components/file-tree/file-tree-toolbar.tsx @@ -0,0 +1,32 @@ +import { useTranslation } from 'react-i18next' +import MaterialIcon from '@/shared/components/material-icon' +import React from 'react' +import useCollapsibleFileTree from '../../hooks/use-collapsible-file-tree' +import FileTreeActionButtons from './file-tree-action-buttons' + +function FileTreeToolbar() { + const { t } = useTranslation() + const { fileTreeExpanded, toggleFileTreeExpanded } = useCollapsibleFileTree() + + return ( +
    + + +
    + ) +} + +export default FileTreeToolbar diff --git a/services/web/frontend/js/features/ide-redesign/components/rail.tsx b/services/web/frontend/js/features/ide-redesign/components/rail.tsx index 8f54331630..249b2b1891 100644 --- a/services/web/frontend/js/features/ide-redesign/components/rail.tsx +++ b/services/web/frontend/js/features/ide-redesign/components/rail.tsx @@ -12,7 +12,7 @@ import { RailTabKey, useRailContext, } from '../contexts/rail-context' -import FileTreeOutlinePanel from './file-tree-outline-panel' +import FileTreeOutlinePanel from './file-tree/file-tree-outline-panel' import { ChatIndicator, ChatPane } from './chat/chat' import getMeta from '@/utils/meta' import { HorizontalResizeHandle } from '@/features/ide-react/components/resize/horizontal-resize-handle' From 0b9cb185faba35a005ab42a071c719e09b8442bc Mon Sep 17 00:00:00 2001 From: David <33458145+davidmcpowell@users.noreply.github.com> Date: Wed, 21 May 2025 09:47:33 +0100 Subject: [PATCH 177/194] Merge pull request #25740 from overleaf/dp-gutter-error-logs Correctly open logs when using AI assist gutter button in new editor GitOrigin-RevId: 2700832f9e18d10ca3e6ee52841d31613fee626c --- .../js/shared/context/local-compile-context.tsx | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/services/web/frontend/js/shared/context/local-compile-context.tsx b/services/web/frontend/js/shared/context/local-compile-context.tsx index e16fc53114..4ac3654a45 100644 --- a/services/web/frontend/js/shared/context/local-compile-context.tsx +++ b/services/web/frontend/js/shared/context/local-compile-context.tsx @@ -50,6 +50,8 @@ import { isSplitTestEnabled } from '@/utils/splitTestUtils' import { captureException } from '@/infrastructure/error-reporter' import OError from '@overleaf/o-error' import getMeta from '@/utils/meta' +import { useIsNewEditorEnabled } from '@/features/ide-redesign/utils/new-editor-utils' +import { useRailContext } from '@/features/ide-redesign/contexts/rail-context' type PdfFile = Record @@ -125,6 +127,8 @@ export const LocalCompileProvider: FC = ({ const { openDocWithId, openDocs, currentDocument } = useEditorManagerContext() const { role } = useDetachContext() + const newEditor = useIsNewEditorEnabled() + const { _id: projectId, rootDocId, @@ -135,6 +139,8 @@ export const LocalCompileProvider: FC = ({ const { pdfPreviewOpen } = useLayoutContext() + const { openTab: openRailTab } = useRailContext() + const { features, alphaProgram, labsProgram } = useUserContext() const { fileTreeData } = useFileTreeData() @@ -742,8 +748,12 @@ export const LocalCompileProvider: FC = ({ const lastCompileOptions = useMemo(() => data?.options || {}, [data]) useEffect(() => { - const listener = (event: Event) => { - setShowLogs((event as CustomEvent).detail as boolean) + const listener = () => { + if (newEditor) { + openRailTab('errors') + } else { + setShowLogs(true) + } } window.addEventListener('editor:show-logs', listener) @@ -751,7 +761,7 @@ export const LocalCompileProvider: FC = ({ return () => { window.removeEventListener('editor:show-logs', listener) } - }, []) + }, [newEditor, openRailTab]) const value = useMemo( () => ({ From e98addf33a8beb9c944bd1cc62bd9fdc4f092f48 Mon Sep 17 00:00:00 2001 From: Mathias Jakobsen Date: Wed, 21 May 2025 09:48:51 +0100 Subject: [PATCH 178/194] Merge pull request #24979 from overleaf/mj-editor-event-hook [web] Introduce React hook wrapper around sendMB and friends GitOrigin-RevId: 3c693ae609c6d4e5ba280c45096692aca47975ca --- .../features/ide-redesign/components/rail.tsx | 16 +++++-- .../components/switcher-modal/modal.tsx | 15 ++++++- .../toolbar/change-layout-options.tsx | 15 ++++--- .../components/toolbar/command-dropdown.tsx | 1 + .../components/toolbar/menu-bar.tsx | 20 ++++++++- .../toolbar/show-history-button.tsx | 11 ++--- .../js/infrastructure/event-tracking.ts | 2 +- .../components/menu-bar/menu-bar-option.tsx | 17 ++++++- .../js/shared/hooks/use-editor-analytics.ts | 44 +++++++++++++++++++ 9 files changed, 118 insertions(+), 23 deletions(-) create mode 100644 services/web/frontend/js/shared/hooks/use-editor-analytics.ts diff --git a/services/web/frontend/js/features/ide-redesign/components/rail.tsx b/services/web/frontend/js/features/ide-redesign/components/rail.tsx index 249b2b1891..d6e1112536 100644 --- a/services/web/frontend/js/features/ide-redesign/components/rail.tsx +++ b/services/web/frontend/js/features/ide-redesign/components/rail.tsx @@ -33,6 +33,7 @@ import DictionarySettingsModal from './settings/editor-settings/dictionary-setti import OLTooltip from '@/features/ui/components/ol/ol-tooltip' import OLIconButton from '@/features/ui/components/ol/ol-icon-button' import { useChatContext } from '@/features/chat/context/chat-context' +import { useEditorAnalytics } from '@/shared/hooks/use-editor-analytics' type RailElement = { icon: AvailableUnfilledIcon @@ -76,6 +77,7 @@ const RAIL_MODALS: { ] export const RailLayout = () => { + const { sendEvent } = useEditorAnalytics() const { t } = useTranslation() const { activeModal, @@ -147,16 +149,20 @@ export const RailLayout = () => { key: 'settings', icon: 'settings', title: t('settings'), - action: () => setLeftMenuShown(true), + action: () => { + sendEvent('rail-click', { tab: 'settings' }) + setLeftMenuShown(true) + }, }, ], - [setLeftMenuShown, t] + [setLeftMenuShown, t, sendEvent] ) const onTabSelect = useCallback( (key: string | null) => { if (key === selectedTab) { togglePane() + sendEvent('rail-click', { tab: key, type: 'toggle' }) } else { // HACK: Apparently the onSelect event is triggered with href attributes // from DropdownItems @@ -164,15 +170,17 @@ export const RailLayout = () => { // Attempting to open a non-existent tab return } + const keyOrDefault = key ?? 'file-tree' // Change the selected tab and make sure it's open - openTab((key ?? 'file-tree') as RailTabKey) + openTab(keyOrDefault as RailTabKey) + sendEvent('rail-click', { tab: keyOrDefault }) if (key === 'chat') { markMessagesAsRead() } } }, - [openTab, togglePane, selectedTab, railTabs, markMessagesAsRead] + [openTab, togglePane, selectedTab, railTabs, sendEvent, markMessagesAsRead] ) const isReviewPanelOpen = selectedTab === 'review-panel' diff --git a/services/web/frontend/js/features/ide-redesign/components/switcher-modal/modal.tsx b/services/web/frontend/js/features/ide-redesign/components/switcher-modal/modal.tsx index dd063fc115..6942674de5 100644 --- a/services/web/frontend/js/features/ide-redesign/components/switcher-modal/modal.tsx +++ b/services/web/frontend/js/features/ide-redesign/components/switcher-modal/modal.tsx @@ -14,6 +14,7 @@ import { import Notification from '@/shared/components/notification' import { useSwitchEnableNewEditorState } from '../../hooks/use-switch-enable-new-editor-state' import { Trans, useTranslation } from 'react-i18next' +import { useEditorAnalytics } from '@/shared/hooks/use-editor-analytics' export const IdeRedesignSwitcherModal = () => { const { t } = useTranslation() @@ -66,13 +67,18 @@ const SwitcherModalContentEnabled: FC = ({ loading, }) => { const { t } = useTranslation() + const { sendEvent } = useEditorAnalytics() const disable = useCallback(() => { + sendEvent('editor-redesign-toggle', { + action: 'disable', + location: 'modal', + }) setEditorRedesignStatus(false) .then(hide) .catch(() => { // do nothing, we're already showing the error }) - }, [setEditorRedesignStatus, hide]) + }, [setEditorRedesignStatus, hide, sendEvent]) return ( <> @@ -116,13 +122,18 @@ const SwitcherModalContentDisabled: FC = ({ loading, }) => { const { t } = useTranslation() + const { sendEvent } = useEditorAnalytics() const enable = useCallback(() => { + sendEvent('editor-redesign-toggle', { + action: 'enable', + location: 'modal', + }) setEditorRedesignStatus(true) .then(hide) .catch(() => { // do nothing, we're already showing the error }) - }, [setEditorRedesignStatus, hide]) + }, [setEditorRedesignStatus, hide, sendEvent]) return ( <> diff --git a/services/web/frontend/js/features/ide-redesign/components/toolbar/change-layout-options.tsx b/services/web/frontend/js/features/ide-redesign/components/toolbar/change-layout-options.tsx index 19d4905d22..0ca4531e2f 100644 --- a/services/web/frontend/js/features/ide-redesign/components/toolbar/change-layout-options.tsx +++ b/services/web/frontend/js/features/ide-redesign/components/toolbar/change-layout-options.tsx @@ -9,10 +9,10 @@ import { } from '@/shared/context/layout-context' import React, { useCallback } from 'react' import { useTranslation } from 'react-i18next' -import * as eventTracking from '../../../../infrastructure/event-tracking' import useEventListener from '@/shared/hooks/use-event-listener' import { DetachRole } from '@/shared/context/detach-context' import { Spinner } from 'react-bootstrap' +import { useEditorAnalytics } from '@/shared/hooks/use-editor-analytics' type LayoutOption = 'sideBySide' | 'editorOnly' | 'pdfOnly' | 'detachedPdf' @@ -87,6 +87,7 @@ const LayoutDropdownItem = ({ } export default function ChangeLayoutOptions() { + const { sendEvent } = useEditorAnalytics() const { reattach, detach, @@ -99,16 +100,16 @@ export default function ChangeLayoutOptions() { const handleDetach = useCallback(() => { detach() - eventTracking.sendMB('project-layout-detach') - }, [detach]) + sendEvent('project-layout-detach') + }, [detach, sendEvent]) const handleReattach = useCallback(() => { if (detachRole !== 'detacher') { return } reattach() - eventTracking.sendMB('project-layout-reattach') - }, [detachRole, reattach]) + sendEvent('project-layout-reattach') + }, [detachRole, reattach, sendEvent]) // reattach when the PDF pane opens useEventListener('ui:pdf-open', handleReattach) @@ -117,12 +118,12 @@ export default function ChangeLayoutOptions() { (newLayout: IdeLayout, newView?: IdeView) => { handleReattach() changeLayout(newLayout, newView) - eventTracking.sendMB('project-layout-change', { + sendEvent('project-layout-change', { layout: newLayout, view: newView, }) }, - [changeLayout, handleReattach] + [changeLayout, handleReattach, sendEvent] ) const { t } = useTranslation() diff --git a/services/web/frontend/js/features/ide-redesign/components/toolbar/command-dropdown.tsx b/services/web/frontend/js/features/ide-redesign/components/toolbar/command-dropdown.tsx index ca23de7fce..e08cf8873a 100644 --- a/services/web/frontend/js/features/ide-redesign/components/toolbar/command-dropdown.tsx +++ b/services/web/frontend/js/features/ide-redesign/components/toolbar/command-dropdown.tsx @@ -101,6 +101,7 @@ const CommandDropdownChild = ({ item }: { item: Entry }) => { if (isTaggedCommand(item)) { return ( { const { t } = useTranslation() @@ -210,6 +211,7 @@ export const ToolbarMenuBar = () => { Editor settings { className="ide-redesign-toolbar-dropdown-toggle-subdued ide-redesign-toolbar-button-subdued" > - + { @@ -263,14 +273,19 @@ export const ToolbarMenuBar = () => { const SwitchToOldEditorMenuBarOption = () => { const { loading, error, setEditorRedesignStatus } = useSwitchEnableNewEditorState() + const { sendEvent } = useEditorAnalytics() const disable: MouseEventHandler = useCallback( event => { // Don't close the dropdown event.stopPropagation() + sendEvent('editor-redesign-toggle', { + action: 'disable', + location: 'menu-bar', + }) setEditorRedesignStatus(false) }, - [setEditorRedesignStatus] + [setEditorRedesignStatus, sendEvent] ) let icon = null if (loading) { @@ -280,6 +295,7 @@ const SwitchToOldEditorMenuBarOption = () => { } return ( { const action = view === 'history' ? 'close' : 'open' - eventTracking.sendMB('navigation-clicked-history', { action }) + sendEvent('navigation-clicked-history', { action }) setView(view === 'history' ? 'editor' : 'history') - }, [view, setView]) + }, [view, setView, sendEvent]) return (
    diff --git a/services/web/frontend/js/infrastructure/event-tracking.ts b/services/web/frontend/js/infrastructure/event-tracking.ts index a2b65a1ce4..6fbe93a53a 100644 --- a/services/web/frontend/js/infrastructure/event-tracking.ts +++ b/services/web/frontend/js/infrastructure/event-tracking.ts @@ -1,7 +1,7 @@ import sessionStorage from './session-storage' import getMeta from '@/utils/meta' -type Segmentation = Record< +export type Segmentation = Record< string, string | number | boolean | undefined | unknown | any // TODO: RecurlyError > diff --git a/services/web/frontend/js/shared/components/menu-bar/menu-bar-option.tsx b/services/web/frontend/js/shared/components/menu-bar/menu-bar-option.tsx index 692f555ea7..9eaa9fb26a 100644 --- a/services/web/frontend/js/shared/components/menu-bar/menu-bar-option.tsx +++ b/services/web/frontend/js/shared/components/menu-bar/menu-bar-option.tsx @@ -1,7 +1,8 @@ import DropdownListItem from '@/features/ui/components/bootstrap-5/dropdown-list-item' import { DropdownItem } from '@/features/ui/components/bootstrap-5/dropdown-menu' +import { useEditorAnalytics } from '@/shared/hooks/use-editor-analytics' import { useNestableDropdown } from '@/shared/hooks/use-nestable-dropdown' -import { MouseEventHandler, ReactNode } from 'react' +import { MouseEventHandler, ReactNode, useCallback } from 'react' type MenuBarOptionProps = { title: string @@ -11,18 +12,30 @@ type MenuBarOptionProps = { href?: string target?: string rel?: string + eventKey?: string } export const MenuBarOption = ({ title, - onClick, + onClick: clickHandler, href, disabled, trailingIcon, target, rel, + eventKey, }: MenuBarOptionProps) => { const { setSelected } = useNestableDropdown() + const { sendEvent } = useEditorAnalytics() + const onClick: MouseEventHandler = useCallback( + e => { + if (eventKey) { + sendEvent('menu-bar-option-click', { key: eventKey }) + } + return clickHandler?.(e) + }, + [clickHandler, eventKey, sendEvent] + ) return ( { + const editorRedesign = useIsNewEditorEnabled() + + const populateSegmentation = useCallback( + (segmentation: Segmentation | undefined = {}): Segmentation => { + return editorRedesign + ? { ...segmentation, 'editor-redesign': 'enabled' } + : segmentation + }, + [editorRedesign] + ) + + const sendEvent: typeof sendMB = useCallback( + (key, segmentation) => { + sendMB(key, populateSegmentation(segmentation)) + }, + [populateSegmentation] + ) + + const sendEventOnce: typeof sendMBOnce = useCallback( + (key, segmentation) => { + sendMBOnce(key, populateSegmentation(segmentation)) + }, + [populateSegmentation] + ) + + const sendEventSampled: typeof sendMBSampled = useCallback( + (key, segmentation, rate) => { + sendMBSampled(key, populateSegmentation(segmentation), rate) + }, + [populateSegmentation] + ) + + return { sendEvent, sendEventOnce, sendEventSampled } +} From 8a1cdab27e1ad0b5e15f59ed48f85ee98de3ef55 Mon Sep 17 00:00:00 2001 From: Mathias Jakobsen Date: Wed, 21 May 2025 09:49:04 +0100 Subject: [PATCH 179/194] Merge pull request #25755 from overleaf/mj-ide-collaborators-look [web] Align online user design to Figma GitOrigin-RevId: 89e09056558d98a57d3c1e5a8409476530784b26 --- .../web/frontend/extracted-translations.json | 2 + .../online-users/online-users-widget.tsx | 133 ++++++++++++++++++ .../components/toolbar/online-users.tsx | 2 +- .../frontend/stories/online-users.stories.tsx | 69 +++++++++ .../pages/editor/online-users.scss | 90 ++++++++++++ services/web/locales/en.json | 2 + 6 files changed, 297 insertions(+), 1 deletion(-) create mode 100644 services/web/frontend/js/features/ide-redesign/components/online-users/online-users-widget.tsx create mode 100644 services/web/frontend/stories/online-users.stories.tsx diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json index d8f23bd45f..6e2205aed4 100644 --- a/services/web/frontend/extracted-translations.json +++ b/services/web/frontend/extracted-translations.json @@ -1042,6 +1042,8 @@ "my_library": "", "n_items": "", "n_items_plural": "", + "n_more_collaborators": "", + "n_more_collaborators_plural": "", "n_more_updates_above": "", "n_more_updates_above_plural": "", "n_more_updates_below": "", diff --git a/services/web/frontend/js/features/ide-redesign/components/online-users/online-users-widget.tsx b/services/web/frontend/js/features/ide-redesign/components/online-users/online-users-widget.tsx new file mode 100644 index 0000000000..bac1787e10 --- /dev/null +++ b/services/web/frontend/js/features/ide-redesign/components/online-users/online-users-widget.tsx @@ -0,0 +1,133 @@ +import { OnlineUser } from '@/features/ide-react/context/online-users-context' +import { + Dropdown, + DropdownHeader, + DropdownItem, + DropdownMenu, + DropdownToggle, +} from '@/features/ui/components/bootstrap-5/dropdown-menu' +import OLTooltip from '@/features/ui/components/ol/ol-tooltip' +import { getBackgroundColorForUserId } from '@/shared/utils/colors' +import { useCallback, useMemo } from 'react' +import { useTranslation } from 'react-i18next' + +// Should be kept in sync with $max-user-circles-displayed CSS constant +const MAX_USER_CIRCLES_DISPLAYED = 5 + +// We don't want a +1 circle since we could just show the user instead +const MAX_USERS_WITH_OVERFLOW_VISIBLE = MAX_USER_CIRCLES_DISPLAYED - 1 + +export const OnlineUsersWidget = ({ + onlineUsers, + goToUser, +}: { + onlineUsers: OnlineUser[] + goToUser: (user: OnlineUser) => void +}) => { + const hasOverflow = onlineUsers.length > MAX_USER_CIRCLES_DISPLAYED + const usersBeforeOverflow = useMemo( + () => + hasOverflow + ? onlineUsers.slice(0, MAX_USERS_WITH_OVERFLOW_VISIBLE) + : onlineUsers, + [onlineUsers, hasOverflow] + ) + const usersInOverflow = useMemo( + () => + hasOverflow ? onlineUsers.slice(MAX_USERS_WITH_OVERFLOW_VISIBLE) : [], + [onlineUsers, hasOverflow] + ) + + return ( +
    + {usersBeforeOverflow.map((user, index) => ( + + ))} + {hasOverflow && ( + + )} +
    + ) +} + +const OnlineUserWidget = ({ + user, + goToUser, + id, +}: { + user: OnlineUser + goToUser: (user: OnlineUser) => void + id: string +}) => { + const onClick = useCallback(() => { + goToUser(user) + }, [goToUser, user]) + return ( + + + + ) +} + +const OnlineUserCircle = ({ user }: { user: OnlineUser }) => { + const backgroundColor = getBackgroundColorForUserId(user.user_id) + return ( + + {user.name.charAt(0)} + + ) +} + +const OnlineUserOverflow = ({ + goToUser, + users, +}: { + goToUser: (user: OnlineUser) => void + users: OnlineUser[] +}) => { + const { t } = useTranslation() + return ( + + + + +{users.length} + + + + + {users.map((user, index) => ( +
  • + goToUser(user)} + > + {user.name} + +
  • + ))} +
    +
    + ) +} diff --git a/services/web/frontend/js/features/ide-redesign/components/toolbar/online-users.tsx b/services/web/frontend/js/features/ide-redesign/components/toolbar/online-users.tsx index d8fb314907..4d937fa89c 100644 --- a/services/web/frontend/js/features/ide-redesign/components/toolbar/online-users.tsx +++ b/services/web/frontend/js/features/ide-redesign/components/toolbar/online-users.tsx @@ -1,10 +1,10 @@ -import OnlineUsersWidget from '@/features/editor-navigation-toolbar/components/online-users-widget' import { useEditorManagerContext } from '@/features/ide-react/context/editor-manager-context' import { OnlineUser, useOnlineUsersContext, } from '@/features/ide-react/context/online-users-context' import { useCallback } from 'react' +import { OnlineUsersWidget } from '../online-users/online-users-widget' export const OnlineUsers = () => { const { openDoc } = useEditorManagerContext() diff --git a/services/web/frontend/stories/online-users.stories.tsx b/services/web/frontend/stories/online-users.stories.tsx new file mode 100644 index 0000000000..7d2796cb2e --- /dev/null +++ b/services/web/frontend/stories/online-users.stories.tsx @@ -0,0 +1,69 @@ +import { Meta } from '@storybook/react' +import { OnlineUser } from '@/features/ide-react/context/online-users-context' +import OnlineUsersWidgetOld from '@/features/editor-navigation-toolbar/components/online-users-widget' +import { OnlineUsersWidget } from '@/features/ide-redesign/components/online-users/online-users-widget' + +const NAMES = [ + 'Alice', + 'Bob', + 'Charlie', + 'Dave', + 'Erin', + 'Frank', + 'Grace', + 'Heidi', + 'Ivan', + 'Judy', + 'Mallory', + 'Niaj', + 'Olivia', + 'Peggy', + 'Rupert', +] + +const generateUser = (_: any, index: number): OnlineUser => { + const name = NAMES[index % NAMES.length] + return { + user_id: `user_${'b'.repeat(index)}`, + name, + id: `user-${index}`, + email: `${name.toLowerCase()}@example.com`, + } +} + +export const OnlineUsersRedesign = ({ users }: { users: number }) => { + const generatedUsers = Array.from({ length: users }, generateUser) + return ( +
    + {}} /> +
    + ) +} + +export const OnlineUsersOld = ({ users }: { users: number }) => { + const generatedUsers = Array.from({ length: users }, generateUser) + return ( +
    + {}} /> +
    + ) +} + +const meta: Meta = { + title: 'Editor / Online Users Widget', + args: { + users: 6, + }, +} + +export default meta diff --git a/services/web/frontend/stylesheets/bootstrap-5/pages/editor/online-users.scss b/services/web/frontend/stylesheets/bootstrap-5/pages/editor/online-users.scss index 52265c6e97..2a03c22c19 100644 --- a/services/web/frontend/stylesheets/bootstrap-5/pages/editor/online-users.scss +++ b/services/web/frontend/stylesheets/bootstrap-5/pages/editor/online-users.scss @@ -1,9 +1,15 @@ :root { --toolbar-btn-color: var(--white); + --online-users-border-color: var(--bg-dark-primary); + --online-users-text-color: var(--content-primary); + --online-users-overflow-button-background-color: var(--bg-light-secondary); } @include theme('light') { --toolbar-btn-color: var(--neutral-70); + --online-users-border-color: var(--bg-light-primary); + --online-users-text-color: var(--content-primary); + --online-users-overflow-button-background-color: var(--bg-light-secondary); } .online-users { @@ -35,3 +41,87 @@ align-items: center; } } + +.online-users-row { + // Keep in sync with the MAX_USER_CIRCLES_DISPLAYED js constant + $max-user-circles-displayed: 5; + + --online-users-circle-size: 24px; + --online-users-border-size: 1px; + --online-users-overlap: 8px; + --online-users-circle-padding: var(--spacing-01); + + .online-user-overflow-dropdown { + --online-users-border-color: transparent; + } + + display: flex; + align-items: center; + color: var(--online-users-text-color); + + .online-users-row-button { + padding: 0; + margin: 0; + background: none; + border: none; + color: var(--online-users-text-color); + min-width: var(--online-users-circle-size); + height: var(--online-users-circle-size); + display: block; + font-weight: var(--font-weight-regular); + + &.dropdown-toggle::after { + display: none; + } + + &:not(:last-child, .online-user-overflow-toggle) { + margin-right: calc( + var(--online-users-overlap) * -1 + var(--online-users-border-size) + ); + } + + @for $i from 1 through $max-user-circles-displayed { + &:nth-child(#{$i}):not(:hover):not(.online-user-overflow-toggle) { + z-index: $i; + } + } + + &:hover { + z-index: $max-user-circles-displayed + 2; + } + } + + .online-user-overflow-toggle { + &:not(:hover) { + z-index: $max-user-circles-displayed + 1; + } + + &:hover, + &:active, + & { + background: none; + } + + .online-user-circle { + background-color: var(--online-users-overflow-button-background-color); + color: var(--online-users-text-color); + } + } + + .online-user-circle { + padding: var(--online-users-circle-padding); + border-radius: 50%; + min-width: var(--online-users-circle-size); + height: var(--online-users-circle-size); + line-height: calc( + var(--online-users-circle-size) - 2 * var(--online-users-border-size) - 2 * + var(--online-users-circle-padding) + ); + font-size: var(--font-size-01); + text-align: center; + border: var(--online-users-border-size) solid + var(--online-users-border-color); + box-sizing: border-box; + display: inline-block; + } +} diff --git a/services/web/locales/en.json b/services/web/locales/en.json index 08f22483a7..99f4f27473 100644 --- a/services/web/locales/en.json +++ b/services/web/locales/en.json @@ -1365,6 +1365,8 @@ "my_library": "My Library", "n_items": "__count__ item", "n_items_plural": "__count__ items", + "n_more_collaborators": "__count__ more collaborator", + "n_more_collaborators_plural": "__count__ more collaborators", "n_more_updates_above": "__count__ more update above", "n_more_updates_above_plural": "__count__ more updates above", "n_more_updates_below": "__count__ more update below", From 80897001a5c67411c49f46fa0c977080c7e1afc4 Mon Sep 17 00:00:00 2001 From: Alf Eaton Date: Wed, 21 May 2025 09:52:44 +0100 Subject: [PATCH 180/194] Add review mode tutorial popover (#25709) GitOrigin-RevId: bf2a365b21da780786f2736efb0770cea5f5b656 --- .../Features/Tutorial/TutorialController.mjs | 1 + .../components/review-mode-promo.tsx | 80 +++++++++++++++++ .../components/review-mode-switcher.tsx | 85 ++++++++++++++----- 3 files changed, 147 insertions(+), 19 deletions(-) create mode 100644 services/web/frontend/js/features/review-panel-new/components/review-mode-promo.tsx diff --git a/services/web/app/src/Features/Tutorial/TutorialController.mjs b/services/web/app/src/Features/Tutorial/TutorialController.mjs index e66a54f71b..e5fc940b34 100644 --- a/services/web/app/src/Features/Tutorial/TutorialController.mjs +++ b/services/web/app/src/Features/Tutorial/TutorialController.mjs @@ -14,6 +14,7 @@ const VALID_KEYS = [ 'full-project-search-promo', 'editor-popup-ux-survey', 'wf-features-moved', + 'review-mode', ] async function completeTutorial(req, res, next) { diff --git a/services/web/frontend/js/features/review-panel-new/components/review-mode-promo.tsx b/services/web/frontend/js/features/review-panel-new/components/review-mode-promo.tsx new file mode 100644 index 0000000000..0979cc58ac --- /dev/null +++ b/services/web/frontend/js/features/review-panel-new/components/review-mode-promo.tsx @@ -0,0 +1,80 @@ +import { FC, RefObject, useCallback, useEffect } from 'react' +import { Button, Overlay, Popover } from 'react-bootstrap' +import Close from '@/shared/components/close' + +export const ReviewModePromo: FC<{ + target: RefObject + showPopup: boolean + tryShowingPopup: () => void + hideUntilReload: () => void + completeTutorial: (props: { + action: 'complete' + event: 'promo-click' | 'promo-dismiss' + }) => void +}> = ({ + showPopup, + tryShowingPopup, + hideUntilReload, + completeTutorial, + target, +}) => { + useEffect(() => { + tryShowingPopup() + }, [tryShowingPopup]) + + const handleHide = useCallback(() => { + hideUntilReload() + }, [hideUntilReload]) + + const handleClose = useCallback(() => { + completeTutorial({ + action: 'complete', + event: 'promo-dismiss', + }) + }, [completeTutorial]) + + const handleAccept = useCallback(() => { + completeTutorial({ + action: 'complete', + event: 'promo-click', + }) + }, [completeTutorial]) + + if (!showPopup) { + return null + } + + return ( + + + + +

    Track changes have moved

    +

    + Choose Reviewing mode in the dropdown to turn on track + changes. +

    +
    + + +
    +
    +
    +
    + ) +} diff --git a/services/web/frontend/js/features/review-panel-new/components/review-mode-switcher.tsx b/services/web/frontend/js/features/review-panel-new/components/review-mode-switcher.tsx index 879b539aeb..f2bb2763bd 100644 --- a/services/web/frontend/js/features/review-panel-new/components/review-mode-switcher.tsx +++ b/services/web/frontend/js/features/review-panel-new/components/review-mode-switcher.tsx @@ -1,4 +1,4 @@ -import { forwardRef, memo, MouseEventHandler, useState } from 'react' +import { forwardRef, memo, MouseEventHandler, useRef, useState } from 'react' import { Dropdown, DropdownMenu, @@ -19,6 +19,9 @@ import { sendMB } from '@/infrastructure/event-tracking' import { useEditorContext } from '@/shared/context/editor-context' import { useProjectContext } from '@/shared/context/project-context' import UpgradeTrackChangesModal from './upgrade-track-changes-modal' +import { ReviewModePromo } from '@/features/review-panel-new/components/review-mode-promo' +import useTutorial from '@/shared/hooks/promotions/use-tutorial' +import { useLayoutContext } from '@/shared/context/layout-context' type Mode = 'view' | 'review' | 'edit' @@ -141,6 +144,7 @@ const ModeSwitcherToggleButton = forwardRef< iconType="edit" label={t('editing')} ariaExpanded={ariaExpanded} + currentMode={mode} /> ) } else if (mode === 'review') { @@ -152,6 +156,7 @@ const ModeSwitcherToggleButton = forwardRef< iconType="rate_review" label={t('reviewing')} ariaExpanded={ariaExpanded} + currentMode={mode} /> ) } @@ -164,6 +169,7 @@ const ModeSwitcherToggleButton = forwardRef< iconType="visibility" label={t('viewing')} ariaExpanded={ariaExpanded} + currentMode={mode} /> ) }) @@ -176,31 +182,72 @@ const ModeSwitcherToggleButtonContent = forwardRef< iconType: string label: string ariaExpanded: boolean + currentMode: string } ->(({ onClick, className, iconType, label, ariaExpanded }, ref) => { +>(({ onClick, className, iconType, label, ariaExpanded, currentMode }, ref) => { const [isFirstTimeUsed, setIsFirstTimeUsed] = usePersistedState( `modeSwitcherFirstTimeUsed`, true ) + const tutorialProps = useTutorial('review-mode', { + name: 'review-mode-notification', + }) + + const user = useUserContext() + const project = useProjectContext() + const { reviewPanelOpen } = useLayoutContext() + const { inactiveTutorials } = useEditorContext() + + const hasCompletedReviewModeTutorial = + inactiveTutorials.includes('review-mode') + + const canShowReviewModePromo = + reviewPanelOpen && + currentMode !== 'review' && + project.features.trackChanges && + user.signUpDate && + user.signUpDate < '2025-03-15' && + !hasCompletedReviewModeTutorial + + const containerRef = useRef(null) + return ( - + <> + + + + + {canShowReviewModePromo && ( + + )} + ) }) From 827fb19df7ab9917097d9a6ca16b81778890a845 Mon Sep 17 00:00:00 2001 From: Alf Eaton Date: Wed, 21 May 2025 09:52:53 +0100 Subject: [PATCH 181/194] Define Change.toRaw result as RawChange (#25676) GitOrigin-RevId: 0fa23806e9d7ee015b5a2a542d52382499edf9ab --- libraries/overleaf-editor-core/lib/change.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/overleaf-editor-core/lib/change.js b/libraries/overleaf-editor-core/lib/change.js index e36cda9ad3..ff4b973a3c 100644 --- a/libraries/overleaf-editor-core/lib/change.js +++ b/libraries/overleaf-editor-core/lib/change.js @@ -13,7 +13,7 @@ const V2DocVersions = require('./v2_doc_versions') /** * @import Author from "./author" - * @import { BlobStore } from "./types" + * @import { BlobStore, RawChange } from "./types" */ /** @@ -54,7 +54,7 @@ class Change { /** * For serialization. * - * @return {Object} + * @return {RawChange} */ toRaw() { function toRaw(object) { From b8e391c00536f9ed6befe192c1bde4895524d789 Mon Sep 17 00:00:00 2001 From: Domagoj Kriskovic Date: Tue, 20 May 2025 11:11:52 +0200 Subject: [PATCH 182/194] Handle undefined user features in UserFeaturesProvider GitOrigin-RevId: 70841809f691e9f20591bdf1ea05c510a44892af --- .../web/frontend/js/shared/context/user-features-context.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/services/web/frontend/js/shared/context/user-features-context.tsx b/services/web/frontend/js/shared/context/user-features-context.tsx index 2cc9ba932d..a162ae0540 100644 --- a/services/web/frontend/js/shared/context/user-features-context.tsx +++ b/services/web/frontend/js/shared/context/user-features-context.tsx @@ -19,7 +19,7 @@ export const UserFeaturesProvider: FC = ({ }) => { const user = useUserContext() const { writefullInstance } = useEditorContext() - const [features, setFeatures] = useState(user.features) + const [features, setFeatures] = useState(user.features || {}) useReceiveUser( useCallback(data => { @@ -61,7 +61,7 @@ export function useUserFeaturesContext() { if (!context) { throw new Error( - 'useUserFeaturesContext is only available inside UserFeaturesContext, or `ol-user` meta is not defined' + 'useUserFeaturesContext is only available inside UserFeaturesContext' ) } From 9b27ed4798fc3a8ece64abbee59c18c395e7b075 Mon Sep 17 00:00:00 2001 From: Domagoj Kriskovic Date: Wed, 21 May 2025 11:45:14 +0200 Subject: [PATCH 183/194] Reinitialise Writefull toolbar after buying AI assist (#25741) * Reinitialise Writefull toolbar after buying AI assist (#25596) * Reinit Writefull toolbar after buying AI assist * use refreshSession() * add a timeout * add a second refresh * Increase a timeout for second refresh of writefull session (#25725) GitOrigin-RevId: 7247ae45ca7de7f1f3778b1b22f49e2ff840a7ef --- .../web/frontend/js/shared/context/types/writefull-instance.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/services/web/frontend/js/shared/context/types/writefull-instance.ts b/services/web/frontend/js/shared/context/types/writefull-instance.ts index 120590e668..18b6d08616 100644 --- a/services/web/frontend/js/shared/context/types/writefull-instance.ts +++ b/services/web/frontend/js/shared/context/types/writefull-instance.ts @@ -42,4 +42,5 @@ export interface WritefullAPI { ): void openTableGenerator(): void openEquationGenerator(): void + refreshSession(): void } From 8e6d1d5f070b137f7df38b01c67926d6f438c2db Mon Sep 17 00:00:00 2001 From: Kristina <7614497+khjrtbrg@users.noreply.github.com> Date: Wed, 21 May 2025 11:45:29 +0200 Subject: [PATCH 184/194] Merge pull request #25732 from overleaf/kh-stripe-preview-addon-purchase [web] add support for previewing add-on changes for Stripe GitOrigin-RevId: 46e7d0b96bf0935a4a3afcaf03d7a6f3c26d2108 --- .../src/Features/Subscription/PlansLocator.js | 2 ++ .../Subscription/SubscriptionController.js | 9 +++++-- .../Subscription/SubscriptionHandler.js | 24 +++++-------------- 3 files changed, 15 insertions(+), 20 deletions(-) diff --git a/services/web/app/src/Features/Subscription/PlansLocator.js b/services/web/app/src/Features/Subscription/PlansLocator.js index a418e23b78..24343e1109 100644 --- a/services/web/app/src/Features/Subscription/PlansLocator.js +++ b/services/web/app/src/Features/Subscription/PlansLocator.js @@ -38,6 +38,8 @@ const recurlyPlanCodeToStripeLookupKey = { group_professional_educational: 'group_professional_educational', group_collaborator: 'group_standard_enterprise', group_collaborator_educational: 'group_standard_educational', + assistant_annual: 'error_assist_annual', + assistant: 'error_assist_monthly', } /** diff --git a/services/web/app/src/Features/Subscription/SubscriptionController.js b/services/web/app/src/Features/Subscription/SubscriptionController.js index d0025c460b..9b2cec1398 100644 --- a/services/web/app/src/Features/Subscription/SubscriptionController.js +++ b/services/web/app/src/Features/Subscription/SubscriptionController.js @@ -335,7 +335,11 @@ async function previewAddonPurchase(req, res) { return HttpErrorHandler.notFound(req, res, `Unknown add-on: ${addOnCode}`) } - const paymentMethod = await RecurlyClient.promises.getPaymentMethod(userId) + /** @type {PaymentMethod[]} */ + const paymentMethod = await Modules.promises.hooks.fire( + 'getPaymentMethod', + userId + ) let subscriptionChange try { @@ -376,7 +380,7 @@ async function previewAddonPurchase(req, res) { }, }, subscriptionChange, - paymentMethod + paymentMethod[0] ) await SplitTestHandler.promises.getAssignment( @@ -482,6 +486,7 @@ async function previewSubscription(req, res, next) { userId, planCode ) + /** @type {PaymentMethod[]} */ const paymentMethod = await Modules.promises.hooks.fire( 'getPaymentMethod', userId diff --git a/services/web/app/src/Features/Subscription/SubscriptionHandler.js b/services/web/app/src/Features/Subscription/SubscriptionHandler.js index 332aa0d8ca..39a44f305f 100644 --- a/services/web/app/src/Features/Subscription/SubscriptionHandler.js +++ b/services/web/app/src/Features/Subscription/SubscriptionHandler.js @@ -262,24 +262,12 @@ async function extendTrial(subscription, daysToExend) { * @return {Promise} */ async function previewAddonPurchase(userId, addOnCode) { - const subscription = await getSubscriptionForUser(userId) - - try { - await RecurlyClient.promises.getAddOn(subscription.planCode, addOnCode) - } catch (err) { - if (err instanceof recurly.errors.NotFoundError) { - throw new NotFoundError({ - message: 'Add-on not found', - info: { addOnCode }, - }) - } - throw err - } - - const changeRequest = subscription.getRequestForAddOnPurchase(addOnCode) - const change = - await RecurlyClient.promises.previewSubscriptionChange(changeRequest) - return change + const change = await Modules.promises.hooks.fire( + 'previewAddOnPurchase', + userId, + addOnCode + ) + return change[0] } /** From b667cef262dd0d5f7bb8ed3a7641c2c3f485816b Mon Sep 17 00:00:00 2001 From: Antoine Clausse Date: Wed, 21 May 2025 13:49:15 +0200 Subject: [PATCH 185/194] Revert "Update defaultHighWaterMark to 64KiB (Node 22's default) (#25522)" (#25789) This reverts commit 19d731abf683066654027de3a4f9ac0b8916f22c. GitOrigin-RevId: eb7c45ab45e02054601b607a4bfeb432424a1837 --- services/chat/config/settings.defaults.cjs | 3 --- services/clsi/config/settings.defaults.js | 4 ---- services/contacts/config/settings.defaults.cjs | 3 --- services/docstore/config/settings.defaults.js | 4 ---- services/document-updater/config/settings.defaults.js | 3 --- services/filestore/config/settings.defaults.js | 4 ---- services/notifications/config/settings.defaults.js | 5 ----- services/project-history/config/settings.defaults.cjs | 3 --- services/real-time/config/settings.defaults.js | 4 ---- services/web/config/settings.defaults.js | 4 ---- 10 files changed, 37 deletions(-) diff --git a/services/chat/config/settings.defaults.cjs b/services/chat/config/settings.defaults.cjs index 9cc27a6877..4b7dc293a9 100644 --- a/services/chat/config/settings.defaults.cjs +++ b/services/chat/config/settings.defaults.cjs @@ -1,9 +1,6 @@ const http = require('node:http') const https = require('node:https') -const stream = require('node:stream') -// TODO(24011): remove this after node 22 update -stream.setDefaultHighWaterMark(false, 64 * 1024) http.globalAgent.keepAlive = false https.globalAgent.keepAlive = false diff --git a/services/clsi/config/settings.defaults.js b/services/clsi/config/settings.defaults.js index 35783eb5b0..d187fe273e 100644 --- a/services/clsi/config/settings.defaults.js +++ b/services/clsi/config/settings.defaults.js @@ -1,9 +1,5 @@ const Path = require('node:path') const os = require('node:os') -const stream = require('node:stream') - -// TODO(24011): remove this after node 22 update -stream.setDefaultHighWaterMark(false, 64 * 1024) const isPreEmptible = process.env.PREEMPTIBLE === 'TRUE' const CLSI_SERVER_ID = os.hostname().replace('-ctr', '') diff --git a/services/contacts/config/settings.defaults.cjs b/services/contacts/config/settings.defaults.cjs index bcf014ee70..7ffdb83ce5 100644 --- a/services/contacts/config/settings.defaults.cjs +++ b/services/contacts/config/settings.defaults.cjs @@ -1,9 +1,6 @@ const http = require('node:http') const https = require('node:https') -const stream = require('node:stream') -// TODO(24011): remove this after node 22 update -stream.setDefaultHighWaterMark(false, 64 * 1024) http.globalAgent.maxSockets = 300 http.globalAgent.keepAlive = false https.globalAgent.keepAlive = false diff --git a/services/docstore/config/settings.defaults.js b/services/docstore/config/settings.defaults.js index 14da21e132..9ad506a9bd 100644 --- a/services/docstore/config/settings.defaults.js +++ b/services/docstore/config/settings.defaults.js @@ -1,9 +1,5 @@ const http = require('node:http') const https = require('node:https') -const stream = require('node:stream') - -// TODO(24011): remove this after node 22 update -stream.setDefaultHighWaterMark(false, 64 * 1024) http.globalAgent.maxSockets = 300 http.globalAgent.keepAlive = false diff --git a/services/document-updater/config/settings.defaults.js b/services/document-updater/config/settings.defaults.js index a6a1c397fa..0cd29d325b 100755 --- a/services/document-updater/config/settings.defaults.js +++ b/services/document-updater/config/settings.defaults.js @@ -1,9 +1,6 @@ const http = require('node:http') const https = require('node:https') -const stream = require('node:stream') -// TODO(24011): remove this after node 22 update -stream.setDefaultHighWaterMark(false, 64 * 1024) http.globalAgent.keepAlive = false https.globalAgent.keepAlive = false diff --git a/services/filestore/config/settings.defaults.js b/services/filestore/config/settings.defaults.js index 28d7190376..9a08bb197e 100644 --- a/services/filestore/config/settings.defaults.js +++ b/services/filestore/config/settings.defaults.js @@ -1,8 +1,4 @@ const Path = require('node:path') -const stream = require('node:stream') - -// TODO(24011): remove this after node 22 update -stream.setDefaultHighWaterMark(false, 64 * 1024) // environment variables renamed for consistency // use AWS_ACCESS_KEY_ID-style going forward diff --git a/services/notifications/config/settings.defaults.js b/services/notifications/config/settings.defaults.js index 26a3b79c6b..3453b88bd6 100644 --- a/services/notifications/config/settings.defaults.js +++ b/services/notifications/config/settings.defaults.js @@ -1,8 +1,3 @@ -const stream = require('node:stream') - -// TODO(24011): remove this after node 22 update -stream.setDefaultHighWaterMark(false, 64 * 1024) - module.exports = { internal: { notifications: { diff --git a/services/project-history/config/settings.defaults.cjs b/services/project-history/config/settings.defaults.cjs index ce92fc1cf2..9e5a39868a 100644 --- a/services/project-history/config/settings.defaults.cjs +++ b/services/project-history/config/settings.defaults.cjs @@ -1,9 +1,6 @@ const http = require('node:http') const https = require('node:https') -const stream = require('node:stream') -// TODO(24011): remove this after node 22 update -stream.setDefaultHighWaterMark(false, 64 * 1024) http.globalAgent.keepAlive = false https.globalAgent.keepAlive = false diff --git a/services/real-time/config/settings.defaults.js b/services/real-time/config/settings.defaults.js index 652fdae56a..57b0a50a42 100644 --- a/services/real-time/config/settings.defaults.js +++ b/services/real-time/config/settings.defaults.js @@ -1,10 +1,6 @@ /* eslint-disable camelcase */ const http = require('node:http') const https = require('node:https') -const stream = require('node:stream') - -// TODO(24011): remove this after node 22 update -stream.setDefaultHighWaterMark(false, 64 * 1024) http.globalAgent.keepAlive = false https.globalAgent.keepAlive = false diff --git a/services/web/config/settings.defaults.js b/services/web/config/settings.defaults.js index 9faeace655..a7ff970ef0 100644 --- a/services/web/config/settings.defaults.js +++ b/services/web/config/settings.defaults.js @@ -1,10 +1,6 @@ const Path = require('node:path') -const stream = require('node:stream') const { merge } = require('@overleaf/settings/merge') -// TODO(24011): remove this after node 22 update -stream.setDefaultHighWaterMark(false, 64 * 1024) - let defaultFeatures, siteUrl // Make time interval config easier. From 436dcc977f74a7561195436d70fe0927ac1f9714 Mon Sep 17 00:00:00 2001 From: Antoine Clausse Date: Wed, 21 May 2025 13:49:48 +0200 Subject: [PATCH 186/194] Update Node to 22.15.1 (#25785) GitOrigin-RevId: 52428d2d7e67c3135a1604fa487dd142aa08bf15 --- libraries/access-token-encryptor/.nvmrc | 2 +- libraries/access-token-encryptor/buildscript.txt | 2 +- libraries/fetch-utils/.nvmrc | 2 +- libraries/fetch-utils/buildscript.txt | 2 +- libraries/logger/.nvmrc | 2 +- libraries/logger/buildscript.txt | 2 +- libraries/metrics/.nvmrc | 2 +- libraries/metrics/buildscript.txt | 2 +- libraries/mongo-utils/.nvmrc | 2 +- libraries/mongo-utils/buildscript.txt | 2 +- libraries/o-error/.nvmrc | 2 +- libraries/o-error/buildscript.txt | 2 +- libraries/object-persistor/.nvmrc | 2 +- libraries/object-persistor/buildscript.txt | 2 +- libraries/overleaf-editor-core/.nvmrc | 2 +- libraries/overleaf-editor-core/buildscript.txt | 2 +- libraries/promise-utils/.nvmrc | 2 +- libraries/promise-utils/buildscript.txt | 2 +- libraries/ranges-tracker/.nvmrc | 2 +- libraries/ranges-tracker/buildscript.txt | 2 +- libraries/redis-wrapper/.nvmrc | 2 +- libraries/redis-wrapper/buildscript.txt | 2 +- libraries/settings/.nvmrc | 2 +- libraries/settings/buildscript.txt | 2 +- libraries/stream-utils/.nvmrc | 2 +- libraries/stream-utils/buildscript.txt | 2 +- server-ce/test/Dockerfile | 2 +- services/chat/.nvmrc | 2 +- services/chat/Dockerfile | 2 +- services/chat/Makefile | 4 ++-- services/chat/buildscript.txt | 2 +- services/chat/docker-compose.yml | 4 ++-- services/clsi/.nvmrc | 2 +- services/clsi/Dockerfile | 2 +- services/clsi/Makefile | 4 ++-- services/clsi/buildscript.txt | 2 +- services/contacts/.nvmrc | 2 +- services/contacts/Dockerfile | 2 +- services/contacts/Makefile | 4 ++-- services/contacts/buildscript.txt | 2 +- services/contacts/docker-compose.yml | 4 ++-- services/docstore/.nvmrc | 2 +- services/docstore/Dockerfile | 2 +- services/docstore/Makefile | 4 ++-- services/docstore/buildscript.txt | 2 +- services/docstore/docker-compose.yml | 4 ++-- services/document-updater/.nvmrc | 2 +- services/document-updater/Dockerfile | 2 +- services/document-updater/Makefile | 4 ++-- services/document-updater/buildscript.txt | 2 +- services/document-updater/docker-compose.yml | 4 ++-- services/filestore/.nvmrc | 2 +- services/filestore/Dockerfile | 2 +- services/filestore/Makefile | 4 ++-- services/filestore/buildscript.txt | 2 +- services/filestore/docker-compose.ci.yml | 2 +- services/filestore/docker-compose.yml | 2 +- services/history-v1/.nvmrc | 2 +- services/history-v1/Dockerfile | 2 +- services/history-v1/Makefile | 4 ++-- services/history-v1/buildscript.txt | 2 +- services/history-v1/docker-compose.ci.yml | 2 +- services/history-v1/docker-compose.yml | 2 +- services/notifications/.nvmrc | 2 +- services/notifications/Dockerfile | 2 +- services/notifications/Makefile | 4 ++-- services/notifications/buildscript.txt | 2 +- services/notifications/docker-compose.yml | 4 ++-- services/project-history/.nvmrc | 2 +- services/project-history/Dockerfile | 2 +- services/project-history/Makefile | 4 ++-- services/project-history/buildscript.txt | 2 +- services/project-history/docker-compose.yml | 4 ++-- services/real-time/.nvmrc | 2 +- services/real-time/Dockerfile | 2 +- services/real-time/Makefile | 4 ++-- services/real-time/buildscript.txt | 2 +- services/real-time/docker-compose.yml | 4 ++-- services/web/.nvmrc | 2 +- services/web/Dockerfile | 2 +- services/web/Dockerfile.frontend | 2 +- services/web/cloudbuild-storybook.yaml | 6 +++--- services/web/docker-compose.common.env | 2 +- services/web/docker-compose.yml | 4 ++-- services/web/scripts/translations/Dockerfile | 2 +- 85 files changed, 105 insertions(+), 105 deletions(-) diff --git a/libraries/access-token-encryptor/.nvmrc b/libraries/access-token-encryptor/.nvmrc index b8ffd70759..8320a6d299 100644 --- a/libraries/access-token-encryptor/.nvmrc +++ b/libraries/access-token-encryptor/.nvmrc @@ -1 +1 @@ -22.15.0 +22.15.1 diff --git a/libraries/access-token-encryptor/buildscript.txt b/libraries/access-token-encryptor/buildscript.txt index 6edd9f367b..74c3bbbd24 100644 --- a/libraries/access-token-encryptor/buildscript.txt +++ b/libraries/access-token-encryptor/buildscript.txt @@ -5,6 +5,6 @@ access-token-encryptor --env-pass-through= --esmock-loader=False --is-library=True ---node-version=22.15.0 +--node-version=22.15.1 --public-repo=False --script-version=4.7.0 diff --git a/libraries/fetch-utils/.nvmrc b/libraries/fetch-utils/.nvmrc index b8ffd70759..8320a6d299 100644 --- a/libraries/fetch-utils/.nvmrc +++ b/libraries/fetch-utils/.nvmrc @@ -1 +1 @@ -22.15.0 +22.15.1 diff --git a/libraries/fetch-utils/buildscript.txt b/libraries/fetch-utils/buildscript.txt index 8b214755c6..91548ff7c6 100644 --- a/libraries/fetch-utils/buildscript.txt +++ b/libraries/fetch-utils/buildscript.txt @@ -5,6 +5,6 @@ fetch-utils --env-pass-through= --esmock-loader=False --is-library=True ---node-version=22.15.0 +--node-version=22.15.1 --public-repo=False --script-version=4.7.0 diff --git a/libraries/logger/.nvmrc b/libraries/logger/.nvmrc index b8ffd70759..8320a6d299 100644 --- a/libraries/logger/.nvmrc +++ b/libraries/logger/.nvmrc @@ -1 +1 @@ -22.15.0 +22.15.1 diff --git a/libraries/logger/buildscript.txt b/libraries/logger/buildscript.txt index 7f3bba47dd..9008707b0e 100644 --- a/libraries/logger/buildscript.txt +++ b/libraries/logger/buildscript.txt @@ -5,6 +5,6 @@ logger --env-pass-through= --esmock-loader=False --is-library=True ---node-version=22.15.0 +--node-version=22.15.1 --public-repo=False --script-version=4.7.0 diff --git a/libraries/metrics/.nvmrc b/libraries/metrics/.nvmrc index b8ffd70759..8320a6d299 100644 --- a/libraries/metrics/.nvmrc +++ b/libraries/metrics/.nvmrc @@ -1 +1 @@ -22.15.0 +22.15.1 diff --git a/libraries/metrics/buildscript.txt b/libraries/metrics/buildscript.txt index 7b952a3920..2c2e5d7531 100644 --- a/libraries/metrics/buildscript.txt +++ b/libraries/metrics/buildscript.txt @@ -5,6 +5,6 @@ metrics --env-pass-through= --esmock-loader=False --is-library=True ---node-version=22.15.0 +--node-version=22.15.1 --public-repo=False --script-version=4.7.0 diff --git a/libraries/mongo-utils/.nvmrc b/libraries/mongo-utils/.nvmrc index b8ffd70759..8320a6d299 100644 --- a/libraries/mongo-utils/.nvmrc +++ b/libraries/mongo-utils/.nvmrc @@ -1 +1 @@ -22.15.0 +22.15.1 diff --git a/libraries/mongo-utils/buildscript.txt b/libraries/mongo-utils/buildscript.txt index 81fe685256..bda8d4f734 100644 --- a/libraries/mongo-utils/buildscript.txt +++ b/libraries/mongo-utils/buildscript.txt @@ -5,6 +5,6 @@ mongo-utils --env-pass-through= --esmock-loader=False --is-library=True ---node-version=22.15.0 +--node-version=22.15.1 --public-repo=False --script-version=4.7.0 diff --git a/libraries/o-error/.nvmrc b/libraries/o-error/.nvmrc index b8ffd70759..8320a6d299 100644 --- a/libraries/o-error/.nvmrc +++ b/libraries/o-error/.nvmrc @@ -1 +1 @@ -22.15.0 +22.15.1 diff --git a/libraries/o-error/buildscript.txt b/libraries/o-error/buildscript.txt index 79b5d38c73..a4134b4b60 100644 --- a/libraries/o-error/buildscript.txt +++ b/libraries/o-error/buildscript.txt @@ -5,6 +5,6 @@ o-error --env-pass-through= --esmock-loader=False --is-library=True ---node-version=22.15.0 +--node-version=22.15.1 --public-repo=False --script-version=4.7.0 diff --git a/libraries/object-persistor/.nvmrc b/libraries/object-persistor/.nvmrc index b8ffd70759..8320a6d299 100644 --- a/libraries/object-persistor/.nvmrc +++ b/libraries/object-persistor/.nvmrc @@ -1 +1 @@ -22.15.0 +22.15.1 diff --git a/libraries/object-persistor/buildscript.txt b/libraries/object-persistor/buildscript.txt index 2484951ddc..75d2e09382 100644 --- a/libraries/object-persistor/buildscript.txt +++ b/libraries/object-persistor/buildscript.txt @@ -5,6 +5,6 @@ object-persistor --env-pass-through= --esmock-loader=False --is-library=True ---node-version=22.15.0 +--node-version=22.15.1 --public-repo=False --script-version=4.7.0 diff --git a/libraries/overleaf-editor-core/.nvmrc b/libraries/overleaf-editor-core/.nvmrc index b8ffd70759..8320a6d299 100644 --- a/libraries/overleaf-editor-core/.nvmrc +++ b/libraries/overleaf-editor-core/.nvmrc @@ -1 +1 @@ -22.15.0 +22.15.1 diff --git a/libraries/overleaf-editor-core/buildscript.txt b/libraries/overleaf-editor-core/buildscript.txt index 0011456758..9b6508663b 100644 --- a/libraries/overleaf-editor-core/buildscript.txt +++ b/libraries/overleaf-editor-core/buildscript.txt @@ -5,6 +5,6 @@ overleaf-editor-core --env-pass-through= --esmock-loader=False --is-library=True ---node-version=22.15.0 +--node-version=22.15.1 --public-repo=False --script-version=4.7.0 diff --git a/libraries/promise-utils/.nvmrc b/libraries/promise-utils/.nvmrc index b8ffd70759..8320a6d299 100644 --- a/libraries/promise-utils/.nvmrc +++ b/libraries/promise-utils/.nvmrc @@ -1 +1 @@ -22.15.0 +22.15.1 diff --git a/libraries/promise-utils/buildscript.txt b/libraries/promise-utils/buildscript.txt index a961c96d43..73dec381c1 100644 --- a/libraries/promise-utils/buildscript.txt +++ b/libraries/promise-utils/buildscript.txt @@ -5,6 +5,6 @@ promise-utils --env-pass-through= --esmock-loader=False --is-library=True ---node-version=22.15.0 +--node-version=22.15.1 --public-repo=False --script-version=4.7.0 diff --git a/libraries/ranges-tracker/.nvmrc b/libraries/ranges-tracker/.nvmrc index b8ffd70759..8320a6d299 100644 --- a/libraries/ranges-tracker/.nvmrc +++ b/libraries/ranges-tracker/.nvmrc @@ -1 +1 @@ -22.15.0 +22.15.1 diff --git a/libraries/ranges-tracker/buildscript.txt b/libraries/ranges-tracker/buildscript.txt index d1803898ae..6276182679 100644 --- a/libraries/ranges-tracker/buildscript.txt +++ b/libraries/ranges-tracker/buildscript.txt @@ -5,6 +5,6 @@ ranges-tracker --env-pass-through= --esmock-loader=False --is-library=True ---node-version=22.15.0 +--node-version=22.15.1 --public-repo=False --script-version=4.7.0 diff --git a/libraries/redis-wrapper/.nvmrc b/libraries/redis-wrapper/.nvmrc index b8ffd70759..8320a6d299 100644 --- a/libraries/redis-wrapper/.nvmrc +++ b/libraries/redis-wrapper/.nvmrc @@ -1 +1 @@ -22.15.0 +22.15.1 diff --git a/libraries/redis-wrapper/buildscript.txt b/libraries/redis-wrapper/buildscript.txt index c5c4f6abd9..1e4489a655 100644 --- a/libraries/redis-wrapper/buildscript.txt +++ b/libraries/redis-wrapper/buildscript.txt @@ -5,6 +5,6 @@ redis-wrapper --env-pass-through= --esmock-loader=False --is-library=True ---node-version=22.15.0 +--node-version=22.15.1 --public-repo=False --script-version=4.7.0 diff --git a/libraries/settings/.nvmrc b/libraries/settings/.nvmrc index b8ffd70759..8320a6d299 100644 --- a/libraries/settings/.nvmrc +++ b/libraries/settings/.nvmrc @@ -1 +1 @@ -22.15.0 +22.15.1 diff --git a/libraries/settings/buildscript.txt b/libraries/settings/buildscript.txt index 844f3dd13c..925234f561 100644 --- a/libraries/settings/buildscript.txt +++ b/libraries/settings/buildscript.txt @@ -5,6 +5,6 @@ settings --env-pass-through= --esmock-loader=False --is-library=True ---node-version=22.15.0 +--node-version=22.15.1 --public-repo=False --script-version=4.7.0 diff --git a/libraries/stream-utils/.nvmrc b/libraries/stream-utils/.nvmrc index b8ffd70759..8320a6d299 100644 --- a/libraries/stream-utils/.nvmrc +++ b/libraries/stream-utils/.nvmrc @@ -1 +1 @@ -22.15.0 +22.15.1 diff --git a/libraries/stream-utils/buildscript.txt b/libraries/stream-utils/buildscript.txt index 709adeae9a..a04310e77f 100644 --- a/libraries/stream-utils/buildscript.txt +++ b/libraries/stream-utils/buildscript.txt @@ -5,6 +5,6 @@ stream-utils --env-pass-through= --esmock-loader=False --is-library=True ---node-version=22.15.0 +--node-version=22.15.1 --public-repo=False --script-version=4.7.0 diff --git a/server-ce/test/Dockerfile b/server-ce/test/Dockerfile index c8c2626a5b..ad89208541 100644 --- a/server-ce/test/Dockerfile +++ b/server-ce/test/Dockerfile @@ -1,4 +1,4 @@ -FROM node:22.15.0 +FROM node:22.15.1 RUN curl -fsSL https://download.docker.com/linux/debian/gpg | apt-key add - \ && echo \ "deb [arch=$(dpkg --print-architecture)] https://download.docker.com/linux/debian $(. /etc/os-release && echo "$VERSION_CODENAME") stable" \ diff --git a/services/chat/.nvmrc b/services/chat/.nvmrc index b8ffd70759..8320a6d299 100644 --- a/services/chat/.nvmrc +++ b/services/chat/.nvmrc @@ -1 +1 @@ -22.15.0 +22.15.1 diff --git a/services/chat/Dockerfile b/services/chat/Dockerfile index 9f9efaa915..2fa7112407 100644 --- a/services/chat/Dockerfile +++ b/services/chat/Dockerfile @@ -2,7 +2,7 @@ # Instead run bin/update_build_scripts from # https://github.com/overleaf/internal/ -FROM node:22.15.0 AS base +FROM node:22.15.1 AS base WORKDIR /overleaf/services/chat diff --git a/services/chat/Makefile b/services/chat/Makefile index 40cb2b6af3..d3f6641f44 100644 --- a/services/chat/Makefile +++ b/services/chat/Makefile @@ -32,12 +32,12 @@ HERE=$(shell pwd) MONOREPO=$(shell cd ../../ && pwd) # Run the linting commands in the scope of the monorepo. # Eslint and prettier (plus some configs) are on the root. -RUN_LINTING = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(HERE) node:22.15.0 npm run --silent +RUN_LINTING = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(HERE) node:22.15.1 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 -RUN_LINTING_MONOREPO = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(MONOREPO) node:22.15.0 npm run --silent +RUN_LINTING_MONOREPO = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(MONOREPO) node:22.15.1 npm run --silent SHELLCHECK_OPTS = \ --shell=bash \ diff --git a/services/chat/buildscript.txt b/services/chat/buildscript.txt index a90eb1a5e9..287aa49cb7 100644 --- a/services/chat/buildscript.txt +++ b/services/chat/buildscript.txt @@ -4,6 +4,6 @@ chat --env-add= --env-pass-through= --esmock-loader=False ---node-version=22.15.0 +--node-version=22.15.1 --public-repo=False --script-version=4.7.0 diff --git a/services/chat/docker-compose.yml b/services/chat/docker-compose.yml index 40a6e561f6..14b739a55e 100644 --- a/services/chat/docker-compose.yml +++ b/services/chat/docker-compose.yml @@ -6,7 +6,7 @@ version: "2.3" services: test_unit: - image: node:22.15.0 + image: node:22.15.1 volumes: - .:/overleaf/services/chat - ../../node_modules:/overleaf/node_modules @@ -21,7 +21,7 @@ services: user: node test_acceptance: - image: node:22.15.0 + image: node:22.15.1 volumes: - .:/overleaf/services/chat - ../../node_modules:/overleaf/node_modules diff --git a/services/clsi/.nvmrc b/services/clsi/.nvmrc index b8ffd70759..8320a6d299 100644 --- a/services/clsi/.nvmrc +++ b/services/clsi/.nvmrc @@ -1 +1 @@ -22.15.0 +22.15.1 diff --git a/services/clsi/Dockerfile b/services/clsi/Dockerfile index 1f37b3dd01..581e95b832 100644 --- a/services/clsi/Dockerfile +++ b/services/clsi/Dockerfile @@ -2,7 +2,7 @@ # Instead run bin/update_build_scripts from # https://github.com/overleaf/internal/ -FROM node:22.15.0 AS base +FROM node:22.15.1 AS base WORKDIR /overleaf/services/clsi COPY services/clsi/install_deps.sh /overleaf/services/clsi/ diff --git a/services/clsi/Makefile b/services/clsi/Makefile index 1d93a411be..f18e7201c1 100644 --- a/services/clsi/Makefile +++ b/services/clsi/Makefile @@ -32,12 +32,12 @@ HERE=$(shell pwd) MONOREPO=$(shell cd ../../ && pwd) # Run the linting commands in the scope of the monorepo. # Eslint and prettier (plus some configs) are on the root. -RUN_LINTING = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(HERE) node:22.15.0 npm run --silent +RUN_LINTING = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(HERE) node:22.15.1 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 -RUN_LINTING_MONOREPO = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(MONOREPO) node:22.15.0 npm run --silent +RUN_LINTING_MONOREPO = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(MONOREPO) node:22.15.1 npm run --silent SHELLCHECK_OPTS = \ --shell=bash \ diff --git a/services/clsi/buildscript.txt b/services/clsi/buildscript.txt index 82084539ee..709ade18c3 100644 --- a/services/clsi/buildscript.txt +++ b/services/clsi/buildscript.txt @@ -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",DOCKER_RUNNER="true",COMPILES_HOST_DIR=$PWD/compiles,OUTPUT_HOST_DIR=$PWD/output --env-pass-through= --esmock-loader=False ---node-version=22.15.0 +--node-version=22.15.1 --public-repo=True --script-version=4.7.0 --use-large-ci-runner=True diff --git a/services/contacts/.nvmrc b/services/contacts/.nvmrc index b8ffd70759..8320a6d299 100644 --- a/services/contacts/.nvmrc +++ b/services/contacts/.nvmrc @@ -1 +1 @@ -22.15.0 +22.15.1 diff --git a/services/contacts/Dockerfile b/services/contacts/Dockerfile index 19094d7d44..d3db63bb25 100644 --- a/services/contacts/Dockerfile +++ b/services/contacts/Dockerfile @@ -2,7 +2,7 @@ # Instead run bin/update_build_scripts from # https://github.com/overleaf/internal/ -FROM node:22.15.0 AS base +FROM node:22.15.1 AS base WORKDIR /overleaf/services/contacts diff --git a/services/contacts/Makefile b/services/contacts/Makefile index 963a19c590..035a8ea5ac 100644 --- a/services/contacts/Makefile +++ b/services/contacts/Makefile @@ -32,12 +32,12 @@ HERE=$(shell pwd) MONOREPO=$(shell cd ../../ && pwd) # Run the linting commands in the scope of the monorepo. # Eslint and prettier (plus some configs) are on the root. -RUN_LINTING = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(HERE) node:22.15.0 npm run --silent +RUN_LINTING = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(HERE) node:22.15.1 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 -RUN_LINTING_MONOREPO = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(MONOREPO) node:22.15.0 npm run --silent +RUN_LINTING_MONOREPO = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(MONOREPO) node:22.15.1 npm run --silent SHELLCHECK_OPTS = \ --shell=bash \ diff --git a/services/contacts/buildscript.txt b/services/contacts/buildscript.txt index d498d72ecc..f03bcebd64 100644 --- a/services/contacts/buildscript.txt +++ b/services/contacts/buildscript.txt @@ -4,6 +4,6 @@ contacts --env-add= --env-pass-through= --esmock-loader=True ---node-version=22.15.0 +--node-version=22.15.1 --public-repo=False --script-version=4.7.0 diff --git a/services/contacts/docker-compose.yml b/services/contacts/docker-compose.yml index 7646495f6f..2177ad797e 100644 --- a/services/contacts/docker-compose.yml +++ b/services/contacts/docker-compose.yml @@ -6,7 +6,7 @@ version: "2.3" services: test_unit: - image: node:22.15.0 + image: node:22.15.1 volumes: - .:/overleaf/services/contacts - ../../node_modules:/overleaf/node_modules @@ -21,7 +21,7 @@ services: user: node test_acceptance: - image: node:22.15.0 + image: node:22.15.1 volumes: - .:/overleaf/services/contacts - ../../node_modules:/overleaf/node_modules diff --git a/services/docstore/.nvmrc b/services/docstore/.nvmrc index b8ffd70759..8320a6d299 100644 --- a/services/docstore/.nvmrc +++ b/services/docstore/.nvmrc @@ -1 +1 @@ -22.15.0 +22.15.1 diff --git a/services/docstore/Dockerfile b/services/docstore/Dockerfile index 98ad4b88e5..811e7c2553 100644 --- a/services/docstore/Dockerfile +++ b/services/docstore/Dockerfile @@ -2,7 +2,7 @@ # Instead run bin/update_build_scripts from # https://github.com/overleaf/internal/ -FROM node:22.15.0 AS base +FROM node:22.15.1 AS base WORKDIR /overleaf/services/docstore diff --git a/services/docstore/Makefile b/services/docstore/Makefile index e44b964044..715d5cf351 100644 --- a/services/docstore/Makefile +++ b/services/docstore/Makefile @@ -32,12 +32,12 @@ HERE=$(shell pwd) MONOREPO=$(shell cd ../../ && pwd) # Run the linting commands in the scope of the monorepo. # Eslint and prettier (plus some configs) are on the root. -RUN_LINTING = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(HERE) node:22.15.0 npm run --silent +RUN_LINTING = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(HERE) node:22.15.1 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 -RUN_LINTING_MONOREPO = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(MONOREPO) node:22.15.0 npm run --silent +RUN_LINTING_MONOREPO = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(MONOREPO) node:22.15.1 npm run --silent SHELLCHECK_OPTS = \ --shell=bash \ diff --git a/services/docstore/buildscript.txt b/services/docstore/buildscript.txt index acf043e4e2..fda91d775e 100644 --- a/services/docstore/buildscript.txt +++ b/services/docstore/buildscript.txt @@ -4,6 +4,6 @@ docstore --env-add= --env-pass-through= --esmock-loader=False ---node-version=22.15.0 +--node-version=22.15.1 --public-repo=True --script-version=4.7.0 diff --git a/services/docstore/docker-compose.yml b/services/docstore/docker-compose.yml index 9d4a7bfc6b..8211e2a9a3 100644 --- a/services/docstore/docker-compose.yml +++ b/services/docstore/docker-compose.yml @@ -6,7 +6,7 @@ version: "2.3" services: test_unit: - image: node:22.15.0 + image: node:22.15.1 volumes: - .:/overleaf/services/docstore - ../../node_modules:/overleaf/node_modules @@ -21,7 +21,7 @@ services: user: node test_acceptance: - image: node:22.15.0 + image: node:22.15.1 volumes: - .:/overleaf/services/docstore - ../../node_modules:/overleaf/node_modules diff --git a/services/document-updater/.nvmrc b/services/document-updater/.nvmrc index b8ffd70759..8320a6d299 100644 --- a/services/document-updater/.nvmrc +++ b/services/document-updater/.nvmrc @@ -1 +1 @@ -22.15.0 +22.15.1 diff --git a/services/document-updater/Dockerfile b/services/document-updater/Dockerfile index e23e5a9052..77f18e8f28 100644 --- a/services/document-updater/Dockerfile +++ b/services/document-updater/Dockerfile @@ -2,7 +2,7 @@ # Instead run bin/update_build_scripts from # https://github.com/overleaf/internal/ -FROM node:22.15.0 AS base +FROM node:22.15.1 AS base WORKDIR /overleaf/services/document-updater diff --git a/services/document-updater/Makefile b/services/document-updater/Makefile index 963a56a0d0..4f6bae1816 100644 --- a/services/document-updater/Makefile +++ b/services/document-updater/Makefile @@ -32,12 +32,12 @@ HERE=$(shell pwd) MONOREPO=$(shell cd ../../ && pwd) # Run the linting commands in the scope of the monorepo. # Eslint and prettier (plus some configs) are on the root. -RUN_LINTING = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(HERE) node:22.15.0 npm run --silent +RUN_LINTING = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(HERE) node:22.15.1 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 -RUN_LINTING_MONOREPO = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(MONOREPO) node:22.15.0 npm run --silent +RUN_LINTING_MONOREPO = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(MONOREPO) node:22.15.1 npm run --silent SHELLCHECK_OPTS = \ --shell=bash \ diff --git a/services/document-updater/buildscript.txt b/services/document-updater/buildscript.txt index 83e9299109..8252032dd4 100644 --- a/services/document-updater/buildscript.txt +++ b/services/document-updater/buildscript.txt @@ -4,6 +4,6 @@ document-updater --env-add= --env-pass-through= --esmock-loader=False ---node-version=22.15.0 +--node-version=22.15.1 --public-repo=True --script-version=4.7.0 diff --git a/services/document-updater/docker-compose.yml b/services/document-updater/docker-compose.yml index d211fbe307..cf9e837c7a 100644 --- a/services/document-updater/docker-compose.yml +++ b/services/document-updater/docker-compose.yml @@ -6,7 +6,7 @@ version: "2.3" services: test_unit: - image: node:22.15.0 + image: node:22.15.1 volumes: - .:/overleaf/services/document-updater - ../../node_modules:/overleaf/node_modules @@ -21,7 +21,7 @@ services: user: node test_acceptance: - image: node:22.15.0 + image: node:22.15.1 volumes: - .:/overleaf/services/document-updater - ../../node_modules:/overleaf/node_modules diff --git a/services/filestore/.nvmrc b/services/filestore/.nvmrc index b8ffd70759..8320a6d299 100644 --- a/services/filestore/.nvmrc +++ b/services/filestore/.nvmrc @@ -1 +1 @@ -22.15.0 +22.15.1 diff --git a/services/filestore/Dockerfile b/services/filestore/Dockerfile index 1de1be5e22..43477aa349 100644 --- a/services/filestore/Dockerfile +++ b/services/filestore/Dockerfile @@ -2,7 +2,7 @@ # Instead run bin/update_build_scripts from # https://github.com/overleaf/internal/ -FROM node:22.15.0 AS base +FROM node:22.15.1 AS base WORKDIR /overleaf/services/filestore COPY services/filestore/install_deps.sh /overleaf/services/filestore/ diff --git a/services/filestore/Makefile b/services/filestore/Makefile index 53827955fc..9b32f44f1b 100644 --- a/services/filestore/Makefile +++ b/services/filestore/Makefile @@ -32,12 +32,12 @@ HERE=$(shell pwd) MONOREPO=$(shell cd ../../ && pwd) # Run the linting commands in the scope of the monorepo. # Eslint and prettier (plus some configs) are on the root. -RUN_LINTING = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(HERE) node:22.15.0 npm run --silent +RUN_LINTING = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(HERE) node:22.15.1 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 -RUN_LINTING_MONOREPO = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(MONOREPO) node:22.15.0 npm run --silent +RUN_LINTING_MONOREPO = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(MONOREPO) node:22.15.1 npm run --silent SHELLCHECK_OPTS = \ --shell=bash \ diff --git a/services/filestore/buildscript.txt b/services/filestore/buildscript.txt index fd80733689..8ffa1e058f 100644 --- a/services/filestore/buildscript.txt +++ b/services/filestore/buildscript.txt @@ -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-pass-through= --esmock-loader=False ---node-version=22.15.0 +--node-version=22.15.1 --public-repo=True --script-version=4.7.0 --test-acceptance-shards=SHARD_01_,SHARD_02_,SHARD_03_ diff --git a/services/filestore/docker-compose.ci.yml b/services/filestore/docker-compose.ci.yml index e1454fa809..3656e1d6b1 100644 --- a/services/filestore/docker-compose.ci.yml +++ b/services/filestore/docker-compose.ci.yml @@ -64,7 +64,7 @@ services: command: tar -czf /tmp/build/build.tar.gz --exclude=build.tar.gz --exclude-vcs . user: root certs: - image: node:22.15.0 + image: node:22.15.1 volumes: - ./test/acceptance/certs:/certs working_dir: /certs diff --git a/services/filestore/docker-compose.yml b/services/filestore/docker-compose.yml index e6e9c659db..a15323c722 100644 --- a/services/filestore/docker-compose.yml +++ b/services/filestore/docker-compose.yml @@ -72,7 +72,7 @@ services: command: npm run --silent test:acceptance certs: - image: node:22.15.0 + image: node:22.15.1 volumes: - ./test/acceptance/certs:/certs working_dir: /certs diff --git a/services/history-v1/.nvmrc b/services/history-v1/.nvmrc index b8ffd70759..8320a6d299 100644 --- a/services/history-v1/.nvmrc +++ b/services/history-v1/.nvmrc @@ -1 +1 @@ -22.15.0 +22.15.1 diff --git a/services/history-v1/Dockerfile b/services/history-v1/Dockerfile index 4231373781..7eb6f00832 100644 --- a/services/history-v1/Dockerfile +++ b/services/history-v1/Dockerfile @@ -2,7 +2,7 @@ # Instead run bin/update_build_scripts from # https://github.com/overleaf/internal/ -FROM node:22.15.0 AS base +FROM node:22.15.1 AS base WORKDIR /overleaf/services/history-v1 COPY services/history-v1/install_deps.sh /overleaf/services/history-v1/ diff --git a/services/history-v1/Makefile b/services/history-v1/Makefile index 18f6bcffd5..53fa3f17e3 100644 --- a/services/history-v1/Makefile +++ b/services/history-v1/Makefile @@ -32,12 +32,12 @@ HERE=$(shell pwd) MONOREPO=$(shell cd ../../ && pwd) # Run the linting commands in the scope of the monorepo. # Eslint and prettier (plus some configs) are on the root. -RUN_LINTING = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(HERE) node:22.15.0 npm run --silent +RUN_LINTING = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(HERE) node:22.15.1 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 -RUN_LINTING_MONOREPO = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(MONOREPO) node:22.15.0 npm run --silent +RUN_LINTING_MONOREPO = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(MONOREPO) node:22.15.1 npm run --silent SHELLCHECK_OPTS = \ --shell=bash \ diff --git a/services/history-v1/buildscript.txt b/services/history-v1/buildscript.txt index a1988ab2ca..7f6f209f1f 100644 --- a/services/history-v1/buildscript.txt +++ b/services/history-v1/buildscript.txt @@ -4,7 +4,7 @@ history-v1 --env-add= --env-pass-through= --esmock-loader=False ---node-version=22.15.0 +--node-version=22.15.1 --public-repo=False --script-version=4.7.0 --tsconfig-extra-includes=backup-deletion-app.mjs,backup-verifier-app.mjs,backup-worker-app.mjs,api/**/*,migrations/**/*,storage/**/* diff --git a/services/history-v1/docker-compose.ci.yml b/services/history-v1/docker-compose.ci.yml index ebf15679eb..60a2ec7bda 100644 --- a/services/history-v1/docker-compose.ci.yml +++ b/services/history-v1/docker-compose.ci.yml @@ -98,7 +98,7 @@ services: retries: 20 certs: - image: node:22.15.0 + image: node:22.15.1 volumes: - ./test/acceptance/certs:/certs working_dir: /certs diff --git a/services/history-v1/docker-compose.yml b/services/history-v1/docker-compose.yml index 941c5a6d6d..f1971b5780 100644 --- a/services/history-v1/docker-compose.yml +++ b/services/history-v1/docker-compose.yml @@ -107,7 +107,7 @@ services: retries: 20 certs: - image: node:22.15.0 + image: node:22.15.1 volumes: - ./test/acceptance/certs:/certs working_dir: /certs diff --git a/services/notifications/.nvmrc b/services/notifications/.nvmrc index b8ffd70759..8320a6d299 100644 --- a/services/notifications/.nvmrc +++ b/services/notifications/.nvmrc @@ -1 +1 @@ -22.15.0 +22.15.1 diff --git a/services/notifications/Dockerfile b/services/notifications/Dockerfile index 255a7ffeff..3733d6df6a 100644 --- a/services/notifications/Dockerfile +++ b/services/notifications/Dockerfile @@ -2,7 +2,7 @@ # Instead run bin/update_build_scripts from # https://github.com/overleaf/internal/ -FROM node:22.15.0 AS base +FROM node:22.15.1 AS base WORKDIR /overleaf/services/notifications diff --git a/services/notifications/Makefile b/services/notifications/Makefile index 1d5938cfcf..51d588bd86 100644 --- a/services/notifications/Makefile +++ b/services/notifications/Makefile @@ -32,12 +32,12 @@ HERE=$(shell pwd) MONOREPO=$(shell cd ../../ && pwd) # Run the linting commands in the scope of the monorepo. # Eslint and prettier (plus some configs) are on the root. -RUN_LINTING = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(HERE) node:22.15.0 npm run --silent +RUN_LINTING = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(HERE) node:22.15.1 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 -RUN_LINTING_MONOREPO = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(MONOREPO) node:22.15.0 npm run --silent +RUN_LINTING_MONOREPO = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(MONOREPO) node:22.15.1 npm run --silent SHELLCHECK_OPTS = \ --shell=bash \ diff --git a/services/notifications/buildscript.txt b/services/notifications/buildscript.txt index 1ff790603e..bb7e1ec953 100644 --- a/services/notifications/buildscript.txt +++ b/services/notifications/buildscript.txt @@ -4,6 +4,6 @@ notifications --env-add= --env-pass-through= --esmock-loader=False ---node-version=22.15.0 +--node-version=22.15.1 --public-repo=True --script-version=4.7.0 diff --git a/services/notifications/docker-compose.yml b/services/notifications/docker-compose.yml index b35375a399..217bcc8200 100644 --- a/services/notifications/docker-compose.yml +++ b/services/notifications/docker-compose.yml @@ -6,7 +6,7 @@ version: "2.3" services: test_unit: - image: node:22.15.0 + image: node:22.15.1 volumes: - .:/overleaf/services/notifications - ../../node_modules:/overleaf/node_modules @@ -21,7 +21,7 @@ services: user: node test_acceptance: - image: node:22.15.0 + image: node:22.15.1 volumes: - .:/overleaf/services/notifications - ../../node_modules:/overleaf/node_modules diff --git a/services/project-history/.nvmrc b/services/project-history/.nvmrc index b8ffd70759..8320a6d299 100644 --- a/services/project-history/.nvmrc +++ b/services/project-history/.nvmrc @@ -1 +1 @@ -22.15.0 +22.15.1 diff --git a/services/project-history/Dockerfile b/services/project-history/Dockerfile index 5a88d0382f..5216b7fcb9 100644 --- a/services/project-history/Dockerfile +++ b/services/project-history/Dockerfile @@ -2,7 +2,7 @@ # Instead run bin/update_build_scripts from # https://github.com/overleaf/internal/ -FROM node:22.15.0 AS base +FROM node:22.15.1 AS base WORKDIR /overleaf/services/project-history diff --git a/services/project-history/Makefile b/services/project-history/Makefile index 1d63b7a8f0..27b339a97a 100644 --- a/services/project-history/Makefile +++ b/services/project-history/Makefile @@ -32,12 +32,12 @@ HERE=$(shell pwd) MONOREPO=$(shell cd ../../ && pwd) # Run the linting commands in the scope of the monorepo. # Eslint and prettier (plus some configs) are on the root. -RUN_LINTING = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(HERE) node:22.15.0 npm run --silent +RUN_LINTING = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(HERE) node:22.15.1 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 --volume $(MONOREPO)/services/document-updater/app/js/types.ts:/overleaf/services/document-updater/app/js/types.ts ci/$(PROJECT_NAME):$(BRANCH_NAME)-$(BUILD_NUMBER) npm run --silent # Same but from the top of the monorepo -RUN_LINTING_MONOREPO = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(MONOREPO) node:22.15.0 npm run --silent +RUN_LINTING_MONOREPO = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(MONOREPO) node:22.15.1 npm run --silent SHELLCHECK_OPTS = \ --shell=bash \ diff --git a/services/project-history/buildscript.txt b/services/project-history/buildscript.txt index 0e4591c5fc..bcb29f215b 100644 --- a/services/project-history/buildscript.txt +++ b/services/project-history/buildscript.txt @@ -4,6 +4,6 @@ project-history --env-add= --env-pass-through= --esmock-loader=True ---node-version=22.15.0 +--node-version=22.15.1 --public-repo=False --script-version=4.7.0 diff --git a/services/project-history/docker-compose.yml b/services/project-history/docker-compose.yml index a7db044a27..8190d2074c 100644 --- a/services/project-history/docker-compose.yml +++ b/services/project-history/docker-compose.yml @@ -6,7 +6,7 @@ version: "2.3" services: test_unit: - image: node:22.15.0 + image: node:22.15.1 volumes: - .:/overleaf/services/project-history - ../../node_modules:/overleaf/node_modules @@ -21,7 +21,7 @@ services: user: node test_acceptance: - image: node:22.15.0 + image: node:22.15.1 volumes: - .:/overleaf/services/project-history - ../../node_modules:/overleaf/node_modules diff --git a/services/real-time/.nvmrc b/services/real-time/.nvmrc index b8ffd70759..8320a6d299 100644 --- a/services/real-time/.nvmrc +++ b/services/real-time/.nvmrc @@ -1 +1 @@ -22.15.0 +22.15.1 diff --git a/services/real-time/Dockerfile b/services/real-time/Dockerfile index a14a51b224..f178233af9 100644 --- a/services/real-time/Dockerfile +++ b/services/real-time/Dockerfile @@ -2,7 +2,7 @@ # Instead run bin/update_build_scripts from # https://github.com/overleaf/internal/ -FROM node:22.15.0 AS base +FROM node:22.15.1 AS base WORKDIR /overleaf/services/real-time diff --git a/services/real-time/Makefile b/services/real-time/Makefile index 746f5e8e74..5604e9f5cc 100644 --- a/services/real-time/Makefile +++ b/services/real-time/Makefile @@ -32,12 +32,12 @@ HERE=$(shell pwd) MONOREPO=$(shell cd ../../ && pwd) # Run the linting commands in the scope of the monorepo. # Eslint and prettier (plus some configs) are on the root. -RUN_LINTING = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(HERE) node:22.15.0 npm run --silent +RUN_LINTING = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(HERE) node:22.15.1 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 -RUN_LINTING_MONOREPO = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(MONOREPO) node:22.15.0 npm run --silent +RUN_LINTING_MONOREPO = docker run --rm -v $(MONOREPO):$(MONOREPO) -w $(MONOREPO) node:22.15.1 npm run --silent SHELLCHECK_OPTS = \ --shell=bash \ diff --git a/services/real-time/buildscript.txt b/services/real-time/buildscript.txt index 1b5cfce301..fdfd32e135 100644 --- a/services/real-time/buildscript.txt +++ b/services/real-time/buildscript.txt @@ -4,6 +4,6 @@ real-time --env-add= --env-pass-through= --esmock-loader=False ---node-version=22.15.0 +--node-version=22.15.1 --public-repo=False --script-version=4.7.0 diff --git a/services/real-time/docker-compose.yml b/services/real-time/docker-compose.yml index b122dd0f39..9333271dcf 100644 --- a/services/real-time/docker-compose.yml +++ b/services/real-time/docker-compose.yml @@ -6,7 +6,7 @@ version: "2.3" services: test_unit: - image: node:22.15.0 + image: node:22.15.1 volumes: - .:/overleaf/services/real-time - ../../node_modules:/overleaf/node_modules @@ -21,7 +21,7 @@ services: user: node test_acceptance: - image: node:22.15.0 + image: node:22.15.1 volumes: - .:/overleaf/services/real-time - ../../node_modules:/overleaf/node_modules diff --git a/services/web/.nvmrc b/services/web/.nvmrc index b8ffd70759..8320a6d299 100644 --- a/services/web/.nvmrc +++ b/services/web/.nvmrc @@ -1 +1 @@ -22.15.0 +22.15.1 diff --git a/services/web/Dockerfile b/services/web/Dockerfile index ba8a91fe6d..469a7af2d7 100644 --- a/services/web/Dockerfile +++ b/services/web/Dockerfile @@ -1,6 +1,6 @@ # the base image is suitable for running web with /overleaf/services/web bind # mounted -FROM node:22.15.0 AS base +FROM node:22.15.1 AS base WORKDIR /overleaf/services/web diff --git a/services/web/Dockerfile.frontend b/services/web/Dockerfile.frontend index ee66f12559..b22e376391 100644 --- a/services/web/Dockerfile.frontend +++ b/services/web/Dockerfile.frontend @@ -1,4 +1,4 @@ -FROM node:22.15.0 +FROM node:22.15.1 # Install Google Chrome RUN wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - diff --git a/services/web/cloudbuild-storybook.yaml b/services/web/cloudbuild-storybook.yaml index 20a523ceaa..c50513ff7b 100644 --- a/services/web/cloudbuild-storybook.yaml +++ b/services/web/cloudbuild-storybook.yaml @@ -1,13 +1,13 @@ steps: - id: npm_ci - name: "node:22.15.0" + name: "node:22.15.1" entrypoint: /bin/bash args: - '-c' - 'bin/npm_install_subset . libraries/* services/web' - id: build-storybook - name: 'node:22.15.0' + name: 'node:22.15.1' env: - 'BRANCH_NAME=$BRANCH_NAME' - 'BUILD_ID=$BUILD_ID' @@ -49,7 +49,7 @@ steps: - deploy-storybook - id: create-storybook-index - name: 'node:22.15.0' + name: 'node:22.15.1' dir: services/web env: - 'BRANCH_NAME=$BRANCH_NAME' diff --git a/services/web/docker-compose.common.env b/services/web/docker-compose.common.env index 10ee8b4a89..0642e1ee98 100644 --- a/services/web/docker-compose.common.env +++ b/services/web/docker-compose.common.env @@ -41,6 +41,6 @@ OVERLEAF_SAML_UPDATE_USER_DETAILS_ON_LOGIN=true OVERLEAF_SAML_CERT=MIIDXTCCAkWgAwIBAgIJAOvOeQ4xFTzsMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNVBAYTAkdCMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMTYxMTE1MTQxMjU5WhcNMjYxMTE1MTQxMjU5WjBFMQswCQYDVQQGEwJHQjETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxCT6MBe5G9VoLU8MfztOEbUhnwLp17ak8eFUqxqeXkkqtWB0b/cmIBU3xoQoO3dIF8PBzfqehqfYVhrNt/TFgcmDfmJnPJRL1RJWMW3VmiP5odJ3LwlkKbZpkeT3wZ8HEJIR1+zbpxiBNkbd2GbdR1iumcsHzMYX1A2CBj+ZMV5VijC+K4P0e9c05VsDEUtLmfeAasJAiumQoVVgAe/BpiXjICGGewa6EPFI7mKkifIRKOGxdRESwZZjxP30bI31oDN0cgKqIgSJtJ9nfCn9jgBMBkQHu42WMuaWD4jrGd7+vYdX+oIfArs9aKgAH5kUGhGdew2R9SpBefrhbNxG8QIDAQABo1AwTjAdBgNVHQ4EFgQU+aSojSyyLChP/IpZcafvSdhj7KkwHwYDVR0jBBgwFoAU+aSojSyyLChP/IpZcafvSdhj7KkwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEABl3+OOVLBWMKs6PjA8lPuloWDNzSr3v76oUcHqAb+cfbucjXrOVsS9RJ0X9yxvCQyfM9FfY43DbspnN3izYhdvbJD8kKLNf0LA5st+ZxLfy0ACyL2iyAwICaqndqxAjQYplFAHmpUiu1DiHckyBPekokDJd+ze95urHMOsaGS5RWPoKJVE0bkaAeZCmEu0NNpXRSBiuxXSTeSAJfv6kyE/rkdhzUKyUl/cGQFrsVYfAFQVA+W6CKOh74ErSEzSHQQYndl7nD33snD/YqdU1ROxV6aJzLKCg+sdj+wRXSP2u/UHnM4jW9TGJfhO42jzL6WVuEvr9q4l7zWzUQKKKhtQ== # DEVICE_HISTORY_SECRET has been generated using: # NOTE: crypto.generateKeySync was added in v15, v16 is the next LTS release. -# $ docker run --rm node:22.15.0 --print 'require("crypto").generateKeySync("aes", { length: 256 }).export().toString("hex")' +# $ docker run --rm node:22.15.1 --print 'require("crypto").generateKeySync("aes", { length: 256 }).export().toString("hex")' DEVICE_HISTORY_SECRET=1b46e6cdf72db02845da06c9517c9cfbbfa0d87357479f4e1df3ce160bd54807 QUEUE_PROCESSING_ENABLED=true diff --git a/services/web/docker-compose.yml b/services/web/docker-compose.yml index a423c9687f..2bba008eb8 100644 --- a/services/web/docker-compose.yml +++ b/services/web/docker-compose.yml @@ -6,7 +6,7 @@ volumes: services: test_unit: - image: node:22.15.0 + image: node:22.15.1 volumes: - .:/overleaf/services/web - ../../node_modules:/overleaf/node_modules @@ -26,7 +26,7 @@ services: - mongo test_acceptance: - image: node:22.15.0 + image: node:22.15.1 volumes: - .:/overleaf/services/web - ../../node_modules:/overleaf/node_modules diff --git a/services/web/scripts/translations/Dockerfile b/services/web/scripts/translations/Dockerfile index f1b4d09492..ada45efb8f 100644 --- a/services/web/scripts/translations/Dockerfile +++ b/services/web/scripts/translations/Dockerfile @@ -1,4 +1,4 @@ -FROM node:22.15.0 +FROM node:22.15.1 WORKDIR /app/scripts/translations From e0f3bea9ad141acbe6e3ff193a1196b921a14691 Mon Sep 17 00:00:00 2001 From: Antoine Clausse Date: Wed, 21 May 2025 14:00:44 +0200 Subject: [PATCH 187/194] [web] De-capitalize english translations (#24123) * Create decapitalize.sh script * Remove `text-capitalize` classes, rely on translations instead * `Account Linking` -> `Account linking` * `Account Settings` -> `Account settings` * `Add Affiliation` -> `Add affiliation` * `Add Email` -> `Add email` * `Add Files` -> `Add files` * `Add to Dictionary` -> `Add to dictionary` * `All Projects` -> `All projects` * `All Templates` -> `All templates` * `Archive Projects` -> `Archive projects` * `Archived Projects` -> `Archived projects` * `Auto Compile` -> `Auto compile` * `Back to Subscription` -> `Back to subscription` * `Blank Project` -> `Blank project` * `Change Password` -> `Change password` * `Change Project Owner` -> `Change project owner` * `Clear Sessions` -> `Clear sessions` * `Company Name` -> `Company name` * `Compile Error Handling` -> `Compile error handling` * `Compile Mode` -> `Compile mode` * `Compromised Password` -> `Compromised password` * `Confirm Affiliation` -> `Confirm affiliation` * `Confirm Email` -> `Confirm email` * `Connected Users` -> `Connected users` * `Contact Sales` -> `Contact sales` * `Contact Support` -> `Contact support` * `Contact Us` -> `Contact us` * `Copy Project` -> `Copy project` * `Delete Account` -> `Delete account` * `Emails and Affiliations` -> `Emails and affiliations` * `Git Integration` -> `Git integration` * `Group Settings` -> `Group settings` * `Link Accounts` -> `Link accounts` * `Make Primary` -> `Make primary` * `Mendeley Integration` -> `Mendeley integration` * `Papers Integration` -> `Papers integration` * `Project Synchronisation` -> `Project synchronisation` * `Sessions Cleared` -> `Sessions cleared` * `Stop Compilation` -> `Stop compilation` * `Update Account Info` -> `Update account info` * `the Sales team` -> `the sales team` * `your Group settings` -> `your group settings` * `Zotero Integration` -> `Zotero integration` * Update decapitalize.sh * Decapitalize some translations * `Example Project` -> `Example project` * `New Project` -> `New project` * `New Tag` -> `New tag` * `Trashed Projects` -> `Trashed projects` * `Upload Project` -> `Upload project` * `Your Projects` -> `Your projects` * Revert "Create decapitalize.sh script" This reverts commit 8c79f367096c206c704c7c01e3572a18f3961d5e. * Revert changes to stories * Fix tests * `Contact us of` -> `Contact us if` * Make `Contact us` bold in tex files * `sales team` -> `Sales team` * `Link accounts and Add email` -> `Link accounts and add email` * `Make Private` -> `Make private` * `contact support` -> `contact Support` * Make `Make primary` tests case sensitive * Use `add_email` translation string * Revert changes to non-english locales * Remove redundant `Account settings` translation * `New project Name` -> `New project name` GitOrigin-RevId: 675c46f96ddbf3d259a8d723fed62aa4a7ed40b7 --- .../app/src/Features/Email/EmailBuilder.js | 10 +- .../Subscription/SubscriptionController.js | 2 +- .../TokenAccess/TokenAccessController.mjs | 2 +- .../project_files/example-project/main.tex | 10 +- .../web/app/views/_mixins/back_to_btns.pug | 4 +- .../layout/navbar-marketing-bootstrap-5.pug | 2 +- .../web/app/views/layout/navbar-marketing.pug | 2 +- .../views/layout/navbar-website-redesign.pug | 2 +- services/web/app/views/user/reconfirm.pug | 2 +- services/web/app/views/user/sessions.pug | 4 +- .../web/app/views/user_membership/new.pug | 2 +- .../web/frontend/extracted-translations.json | 1 - .../settings/components/linking-section.tsx | 16 +- .../components/linking/enable-widget.tsx | 2 +- .../components/linking/integration-widget.tsx | 7 +- .../components/linking/sso-widget.tsx | 1 - .../bootstrap-5/navbar/account-menu-items.tsx | 2 +- .../bootstrap-5/navbar/default-navbar.tsx | 2 +- .../web/frontend/stories/menu-bar.stories.tsx | 2 +- .../new-project-button.stories.tsx | 2 +- .../web/frontend/stylesheets/core/type.less | 3 - services/web/locales/en.json | 140 +++++++++--------- .../editor-left-menu.spec.tsx | 18 +-- .../components/clone-project-modal.test.jsx | 2 +- .../components/actions-copy-project.test.jsx | 6 +- .../components/actions-menu.test.jsx | 4 +- .../components/help-contact-us.test.jsx | 2 +- .../components/help-menu.test.jsx | 4 +- .../components/new-project-button.test.tsx | 28 ++-- .../components/notifications.test.tsx | 12 +- .../components/project-list-root.test.tsx | 10 +- .../components/sidebar/tags-list.test.tsx | 14 +- .../archive-project-button.test.tsx | 4 +- .../copy-project-button.test.tsx | 2 +- .../buttons/archive-projects.button.test.tsx | 2 +- .../components/welcome-message.test.tsx | 24 +-- .../components/emails/emails-row.test.tsx | 6 +- .../emails-section-add-new-email.test.tsx | 2 +- .../emails/reconfirmation-info.test.tsx | 4 +- .../components/leave-section.test.tsx | 4 +- .../components/linking-section.test.tsx | 4 +- .../components/password-section.test.tsx | 2 +- .../settings/components/root.test.tsx | 16 +- .../components/share-project-modal.test.jsx | 4 +- .../dashboard/personal-subscription.test.tsx | 4 +- .../dashboard/states/active/active.test.tsx | 6 +- .../dashboard/subscription-dashboard.test.tsx | 6 +- ...s-individual-recurly-subscription.test.tsx | 2 +- .../src/steps/100_loadProjectDashboard.js | 2 +- .../test/unit/src/Email/EmailBuilderTests.js | 4 +- 50 files changed, 201 insertions(+), 217 deletions(-) diff --git a/services/web/app/src/Features/Email/EmailBuilder.js b/services/web/app/src/Features/Email/EmailBuilder.js index 0e46b1b8ea..01565201ac 100644 --- a/services/web/app/src/Features/Email/EmailBuilder.js +++ b/services/web/app/src/Features/Email/EmailBuilder.js @@ -222,10 +222,10 @@ templates.passwordResetRequested = ctaTemplate({ templates.confirmEmail = ctaTemplate({ subject() { - return `Confirm Email - ${settings.appName}` + return `Confirm email - ${settings.appName}` }, title() { - return 'Confirm Email' + return 'Confirm email' }, message(opts) { return [ @@ -239,7 +239,7 @@ templates.confirmEmail = ctaTemplate({ ] }, ctaText() { - return 'Confirm Email' + return 'Confirm email' }, ctaURL(opts) { return opts.confirmEmailUrl @@ -861,7 +861,7 @@ templates.SAMLDataCleared = ctaTemplate({ ] }, ctaText(opts) { - return 'Update my Emails and Affiliations' + return 'Update my Emails and affiliations' }, ctaURL(opts) { return `${settings.siteUrl}/user/settings` @@ -907,7 +907,7 @@ templates.welcome = ctaTemplate({ ] }, ctaText() { - return 'Confirm Email' + return 'Confirm email' }, ctaURL(opts) { return opts.confirmEmailUrl diff --git a/services/web/app/src/Features/Subscription/SubscriptionController.js b/services/web/app/src/Features/Subscription/SubscriptionController.js index 9b2cec1398..6116a71174 100644 --- a/services/web/app/src/Features/Subscription/SubscriptionController.js +++ b/services/web/app/src/Features/Subscription/SubscriptionController.js @@ -86,7 +86,7 @@ async function userSubscriptionPage(req, res) { const groupPlansDataForDash = formatGroupPlansDataForDash() - // display the Group Settings button only to admins of group subscriptions with either/or the Managed Users or Group SSO feature available + // display the Group settings button only to admins of group subscriptions with either/or the Managed Users or Group SSO feature available let groupSettingsEnabledFor try { const managedGroups = await async.filter( diff --git a/services/web/app/src/Features/TokenAccess/TokenAccessController.mjs b/services/web/app/src/Features/TokenAccess/TokenAccessController.mjs index ff4b93e88c..276524dd02 100644 --- a/services/web/app/src/Features/TokenAccess/TokenAccessController.mjs +++ b/services/web/app/src/Features/TokenAccess/TokenAccessController.mjs @@ -66,7 +66,7 @@ async function _handleV1Project(token, userId) { userId ) // This should not happen anymore, but it does show - // a nice "contact support" message, so it can stay + // a nice "contact Support" message, so it can stay if (!docInfo) { return { v1Import: { status: 'cannotImport' } } } diff --git a/services/web/app/templates/project_files/example-project/main.tex b/services/web/app/templates/project_files/example-project/main.tex index 5199b66d9d..45d982a2f6 100644 --- a/services/web/app/templates/project_files/example-project/main.tex +++ b/services/web/app/templates/project_files/example-project/main.tex @@ -49,7 +49,7 @@ Note that your figure will automatically be placed in the most appropriate place \subsection{How to add Tables} -Use the table and tabular environments for basic tables --- see Table~\ref{tab:widgets}, for example. For more information, please see this help article on \href{https://www.overleaf.com/learn/latex/tables}{tables}. +Use the table and tabular environments for basic tables --- see Table~\ref{tab:widgets}, for example. For more information, please see this help article on \href{https://www.overleaf.com/learn/latex/tables}{tables}. \begin{table} \centering @@ -65,7 +65,7 @@ Gadgets & 13 Comments can be added to your project by highlighting some text and clicking ``Add comment'' in the top right of the editor pane. To view existing comments, click on the Review menu in the toolbar above. To reply to a comment, click on the Reply button in the lower right corner of the comment. You can close the Review pane by clicking its name on the toolbar when you're done reviewing for the time being. -Track changes are available on all our \href{https://www.overleaf.com/user/subscription/plans}{premium plans}, and can be toggled on or off using the option at the top of the Review pane. Track changes allow you to keep track of every change made to the document, along with the person making the change. +Track changes are available on all our \href{https://www.overleaf.com/user/subscription/plans}{premium plans}, and can be toggled on or off using the option at the top of the Review pane. Track changes allow you to keep track of every change made to the document, along with the person making the change. \subsection{How to add Lists} @@ -97,7 +97,7 @@ If however you're using a more general template, such as this one, and would lik \subsection{How to change the document language and spell check settings} -Overleaf supports many different languages, including multiple different languages within one document. +Overleaf supports many different languages, including multiple different languages within one document. To configure the document language, simply edit the option provided to the babel package in the preamble at the top of this example project. To learn more about the different options, please visit this help article on \href{https://www.overleaf.com/learn/latex/International_language_support}{international language support}. @@ -111,9 +111,9 @@ If you have an \href{https://www.overleaf.com/user/subscription/plans}{upgraded \subsection{Good luck!} -We hope you find Overleaf useful, and do take a look at our \href{https://www.overleaf.com/learn}{help library} for more tutorials and user guides! Please also let us know if you have any feedback using the Contact Us link at the bottom of the Overleaf menu --- or use the contact form at \url{https://www.overleaf.com/contact}. +We hope you find Overleaf useful, and do take a look at our \href{https://www.overleaf.com/learn}{help library} for more tutorials and user guides! Please also let us know if you have any feedback using the \textbf{Contact us} link at the bottom of the Overleaf menu --- or use the contact form at \url{https://www.overleaf.com/contact}. \bibliographystyle{alpha} \bibliography{sample} -\end{document} \ No newline at end of file +\end{document} diff --git a/services/web/app/views/_mixins/back_to_btns.pug b/services/web/app/views/_mixins/back_to_btns.pug index da1c9c09db..570237b5bc 100644 --- a/services/web/app/views/_mixins/back_to_btns.pug +++ b/services/web/app/views/_mixins/back_to_btns.pug @@ -1,4 +1,4 @@ mixin back-to-btns(settingsAnchor) - a.btn.btn-secondary.text-capitalize(href=`/user/settings${settingsAnchor ? '#' + settingsAnchor : '' }`) #{translate('back_to_account_settings')} + a.btn.btn-secondary(href=`/user/settings${settingsAnchor ? '#' + settingsAnchor : '' }`) #{translate('back_to_account_settings')} | - a.btn.btn-secondary.text-capitalize(href='/project') #{translate('back_to_your_projects')} \ No newline at end of file + a.btn.btn-secondary(href='/project') #{translate('back_to_your_projects')} diff --git a/services/web/app/views/layout/navbar-marketing-bootstrap-5.pug b/services/web/app/views/layout/navbar-marketing-bootstrap-5.pug index ee94394bc4..92e2d4301d 100644 --- a/services/web/app/views/layout/navbar-marketing-bootstrap-5.pug +++ b/services/web/app/views/layout/navbar-marketing-bootstrap-5.pug @@ -178,7 +178,7 @@ nav.navbar.navbar-default.navbar-main.navbar-expand-lg(class={ +dropdown-menu-item div.disabled.dropdown-item #{getSessionUser().email} +dropdown-menu-divider - +dropdown-menu-link-item()(href="/user/settings") #{translate('Account Settings')} + +dropdown-menu-link-item()(href="/user/settings") #{translate('account_settings')} if nav.showSubscriptionLink +dropdown-menu-link-item()(href="/user/subscription") #{translate('subscription')} +dropdown-menu-divider diff --git a/services/web/app/views/layout/navbar-marketing.pug b/services/web/app/views/layout/navbar-marketing.pug index e0f36004b8..c5e9f2e0bf 100644 --- a/services/web/app/views/layout/navbar-marketing.pug +++ b/services/web/app/views/layout/navbar-marketing.pug @@ -186,7 +186,7 @@ nav.navbar.navbar-default.navbar-main(class={ div.subdued #{getSessionUser().email} li.divider.hidden-xs.hidden-sm li - a(href="/user/settings") #{translate('Account Settings')} + a(href="/user/settings") #{translate('account_settings')} if nav.showSubscriptionLink li a(href="/user/subscription") #{translate('subscription')} diff --git a/services/web/app/views/layout/navbar-website-redesign.pug b/services/web/app/views/layout/navbar-website-redesign.pug index c4b712e955..8ea71861c0 100644 --- a/services/web/app/views/layout/navbar-website-redesign.pug +++ b/services/web/app/views/layout/navbar-website-redesign.pug @@ -184,7 +184,7 @@ nav.navbar.navbar-default.navbar-main.website-redesign-navbar div.subdued #{getSessionUser().email} li.divider.hidden-xs.hidden-sm li - a(href="/user/settings") #{translate('Account Settings')} + a(href="/user/settings") #{translate('account_settings')} if nav.showSubscriptionLink li a(href="/user/subscription") #{translate('subscription')} diff --git a/services/web/app/views/user/reconfirm.pug b/services/web/app/views/user/reconfirm.pug index 5db1d811c7..7c17423d5a 100644 --- a/services/web/app/views/user/reconfirm.pug +++ b/services/web/app/views/user/reconfirm.pug @@ -23,7 +23,7 @@ block content .row .col-sm-12.col-md-6.col-md-offset-3 .card - h1.card-header.text-capitalize #{translate("reconfirm")} #{translate("Account")} + h1.card-header #{translate("reconfirm")} #{translate("Account")} p #{translate('reconfirm_explained')}  a(href=`mailto:${settings.adminEmail}`) #{settings.adminEmail} | . diff --git a/services/web/app/views/user/sessions.pug b/services/web/app/views/user/sessions.pug index 99905a960d..187c1dae75 100644 --- a/services/web/app/views/user/sessions.pug +++ b/services/web/app/views/user/sessions.pug @@ -67,6 +67,6 @@ block content p.text-success.text-center | #{translate('clear_sessions_success')} .page-separator - a.btn.btn-secondary.text-capitalize(href='/user/settings') #{translate('back_to_account_settings')} + a.btn.btn-secondary(href='/user/settings') #{translate('back_to_account_settings')} | - a.btn.btn-secondary.text-capitalize(href='/project') #{translate('back_to_your_projects')} + a.btn.btn-secondary(href='/project') #{translate('back_to_your_projects')} diff --git a/services/web/app/views/user_membership/new.pug b/services/web/app/views/user_membership/new.pug index 6a88249ca6..c59837b107 100644 --- a/services/web/app/views/user_membership/new.pug +++ b/services/web/app/views/user_membership/new.pug @@ -15,7 +15,7 @@ block content action='' ) input(name="_csrf", type="hidden", value=csrfToken) - button.btn.btn-primary.text-capitalize( + button.btn.btn-primary( type="submit", data-ol-disabled-inflight ) diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json index 6e2205aed4..a8fd6b8169 100644 --- a/services/web/frontend/extracted-translations.json +++ b/services/web/frontend/extracted-translations.json @@ -5,7 +5,6 @@ "3_4_width": "", "About": "", "Account": "", - "Account Settings": "", "Documentation": "", "Get Involved": "", "Help": "", diff --git a/services/web/frontend/js/features/settings/components/linking-section.tsx b/services/web/frontend/js/features/settings/components/linking-section.tsx index 69d2dc268e..0b9001927e 100644 --- a/services/web/frontend/js/features/settings/components/linking-section.tsx +++ b/services/web/frontend/js/features/settings/components/linking-section.tsx @@ -90,9 +90,7 @@ function LinkingSection() {

    {t('linked_accounts_explained')}

    {haslangFeedbackLinkingWidgets ? ( <> -

    - {t('ai_features')} -

    +

    {t('ai_features')}

    {langFeedbackLinkingWidgets.map( ({ import: { default: widget }, path }, widgetIndex) => ( @@ -108,9 +106,7 @@ function LinkingSection() { ) : null} {hasIntegrationLinkingSection ? ( <> -

    - {t('project_synchronisation')} -

    +

    {t('project_synchronisation')}

    {projectSyncSuccessMessage ? ( -

    - {t('reference_managers')} -

    +

    {t('reference_managers')}

    {referenceLinkingWidgets.map( ({ import: importObject, path }, widgetIndex) => ( @@ -152,9 +146,7 @@ function LinkingSection() { ) : null} {hasSSOLinkingSection ? ( <> -

    - {t('linked_accounts')} -

    +

    {t('linked_accounts')}

    {ssoErrorMessage ? ( - {t('upgrade')} + {t('upgrade')} ) } else if (linked) { diff --git a/services/web/frontend/js/features/settings/components/linking/integration-widget.tsx b/services/web/frontend/js/features/settings/components/linking/integration-widget.tsx index 8d18e3e515..62c569067a 100644 --- a/services/web/frontend/js/features/settings/components/linking/integration-widget.tsx +++ b/services/web/frontend/js/features/settings/components/linking/integration-widget.tsx @@ -131,9 +131,7 @@ function ActionButton({ onClick={() => trackUpgradeClick(integration)} aria-labelledby={`${titleId} ${linkTextId}`} > - - {t('upgrade')} - + {t('upgrade')} ) } else if (linked) { @@ -150,14 +148,13 @@ function ActionButton({ return ( <> {disabled ? ( - + {t('link')} ) : ( trackLinkingClick(integration)} > {t('link')} diff --git a/services/web/frontend/js/features/settings/components/linking/sso-widget.tsx b/services/web/frontend/js/features/settings/components/linking/sso-widget.tsx index dd64f7d489..800a7540ae 100644 --- a/services/web/frontend/js/features/settings/components/linking/sso-widget.tsx +++ b/services/web/frontend/js/features/settings/components/linking/sso-widget.tsx @@ -142,7 +142,6 @@ function ActionButton({ diff --git a/services/web/frontend/js/features/ui/components/bootstrap-5/navbar/account-menu-items.tsx b/services/web/frontend/js/features/ui/components/bootstrap-5/navbar/account-menu-items.tsx index 2b6ed6a64b..d00e2f964b 100644 --- a/services/web/frontend/js/features/ui/components/bootstrap-5/navbar/account-menu-items.tsx +++ b/services/web/frontend/js/features/ui/components/bootstrap-5/navbar/account-menu-items.tsx @@ -25,7 +25,7 @@ export function AccountMenuItems({ - {t('Account Settings')} + {t('account_settings')} {showSubscriptionLink ? ( 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 84ed9b8da8..2480b7f061 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 @@ -36,7 +36,7 @@ function DefaultNavbar(props: DefaultNavbarMetadata) { const { t } = useTranslation() const [expanded, setExpanded] = useState(false) - // The Contact Us modal is rendered at this level rather than inside the nav + // The Contact us modal is rendered at this level rather than inside the nav // bar because otherwise the help wiki search results dropdown doesn't show up const { modal: contactUsModal, showModal: showContactUsModal } = useContactUsModal({ diff --git a/services/web/frontend/stories/menu-bar.stories.tsx b/services/web/frontend/stories/menu-bar.stories.tsx index 1fe5628bec..fe8a7dc926 100644 --- a/services/web/frontend/stories/menu-bar.stories.tsx +++ b/services/web/frontend/stories/menu-bar.stories.tsx @@ -9,7 +9,7 @@ export const Default = () => { - + diff --git a/services/web/frontend/stories/project-list/new-project-button.stories.tsx b/services/web/frontend/stories/project-list/new-project-button.stories.tsx index 9d2e03ad2d..fcb19deb4c 100644 --- a/services/web/frontend/stories/project-list/new-project-button.stories.tsx +++ b/services/web/frontend/stories/project-list/new-project-button.stories.tsx @@ -102,6 +102,6 @@ export const Error = () => { } export default { - title: 'Project List / New Project Button', + title: 'Project List / New project Button', component: NewProjectButton, } diff --git a/services/web/frontend/stylesheets/core/type.less b/services/web/frontend/stylesheets/core/type.less index 3d51fac4eb..be3347b6eb 100755 --- a/services/web/frontend/stylesheets/core/type.less +++ b/services/web/frontend/stylesheets/core/type.less @@ -190,9 +190,6 @@ cite { } // Transformations -.text-capitalize { - text-transform: capitalize; -} .text-lowercase { text-transform: lowercase; } diff --git a/services/web/locales/en.json b/services/web/locales/en.json index 99f4f27473..5e747b59f8 100644 --- a/services/web/locales/en.json +++ b/services/web/locales/en.json @@ -6,7 +6,6 @@ "3_4_width": "¾ width", "About": "About", "Account": "Account", - "Account Settings": "Account Settings", "Documentation": "Documentation", "Projects": "Projects", "Security": "Security", @@ -56,10 +55,10 @@ "account_billed_manually": "Account billed manually", "account_has_been_link_to_institution_account": "Your __appName__ account on __email__ has been linked to your __institutionName__ institutional account.", "account_has_past_due_invoice_change_plan_warning": "Your account currently has a past due invoice. You will not be able to change your plan until this is resolved.", - "account_linking": "Account Linking", + "account_linking": "Account linking", "account_managed_by_group_administrator": "Your account is managed by your group administrator (__admin__)", "account_not_linked_to_dropbox": "Your account is not linked to Dropbox", - "account_settings": "Account Settings", + "account_settings": "Account settings", "account_with_email_exists": "It looks like an __appName__ account with the email __email__ already exists.", "acct_linked_to_institution_acct_2": "You can <0>log in to Overleaf through your <0>__institutionName__ institutional login.", "actions": "Actions", @@ -72,7 +71,7 @@ "add_a_recovery_email_address": "Add a recovery email address", "add_add_on_to_your_plan": "Add __addOnName__ to your plan", "add_additional_certificate": "Add another certificate", - "add_affiliation": "Add Affiliation", + "add_affiliation": "Add affiliation", "add_ai_assist": "Add AI Assist", "add_ai_assist_annual_and_get_unlimited_access": "Add AI Assist Annual and get unlimited* access to Overleaf and Writefull AI features.", "add_ai_assist_monthly_and_get_unlimited_access": "Add AI Assist Monthly and get unlimited* access to Overleaf and Writefull AI features.", @@ -85,12 +84,12 @@ "add_comment_error_message": "There was an error adding your comment. Please try again in a few moments.", "add_comment_error_title": "Add Comment Error", "add_company_details": "Add company details", - "add_email": "Add Email", + "add_email": "Add email", "add_email_address": "Add email address", "add_email_to_claim_features": "Add an institutional email address to claim your features.", "add_error_assist_annual_to_your_projects": "Add Error Assist Annual to your projects and get unlimited AI help to fix LaTeX errors faster.", "add_error_assist_to_your_projects": "Add Error Assist to your projects and get unlimited AI help to fix LaTeX errors faster.", - "add_files": "Add Files", + "add_files": "Add files", "add_more_collaborators": "Add more collaborators", "add_more_licenses_to_my_plan": "Add more licenses to my plan", "add_more_managers": "Add more managers", @@ -100,7 +99,7 @@ "add_or_remove_project_from_tag": "Add or remove project from tag __tagName__", "add_people": "Add people", "add_role_and_department": "Add role and department", - "add_to_dictionary": "Add to Dictionary", + "add_to_dictionary": "Add to dictionary", "add_to_tag": "Add to tag", "add_unlimited_ai_to_overleaf": "Add unlimited AI* to Overleaf", "add_unlimited_ai_to_your_overleaf_plan": "Add unlimited AI* to your Overleaf __planName__ plan", @@ -145,9 +144,9 @@ "all_premium_features": "All premium features", "all_premium_features_including": "All premium features, including:", "all_prices_displayed_are_in_currency": "All prices displayed are in __recommendedCurrency__.", - "all_projects": "All Projects", + "all_projects": "All projects", "all_projects_will_be_transferred_immediately": "All projects will be transferred to the new owner immediately.", - "all_templates": "All Templates", + "all_templates": "All templates", "all_the_pros_of_our_standard_plan_plus_unlimited_collab": "All the pros of our standard plan, plus unlimited collaborators per project.", "all_these_experiments_are_available_exclusively": "All these experiments are available exclusively to members of the Labs program. If you sign up, you can choose which experiments you want to try.", "allows_to_search_by_author_title_etc_possible_to_pull_results_directly_from_your_reference_manager_if_connected": "Allows to search by author, title, etc. Possible to pull results directly from your reference manager (if connected).", @@ -173,9 +172,9 @@ "apply_suggestion": "Apply suggestion", "april": "April", "archive": "Archive", - "archive_projects": "Archive Projects", + "archive_projects": "Archive projects", "archived": "Archived", - "archived_projects": "Archived Projects", + "archived_projects": "Archived projects", "archiving_projects_wont_affect_collaborators": "Archiving projects won’t affect your collaborators.", "are_you_affiliated_with_an_institution": "Are you affiliated with an institution?", "are_you_getting_an_undefined_control_sequence_error": "Are you getting an Undefined Control Sequence error? If you are, make sure you’ve loaded the graphicx package—<0>\\usepackage{graphicx}—in the preamble (first section of code) in your document. <1>Learn more", @@ -195,7 +194,7 @@ "august": "August", "author": "Author", "auto_close_brackets": "Auto-close brackets", - "auto_compile": "Auto Compile", + "auto_compile": "Auto compile", "auto_complete": "Auto-complete", "autocompile": "Autocompile", "autocompile_disabled": "Autocompile disabled", @@ -212,7 +211,7 @@ "back_to_configuration": "Back to configuration", "back_to_editor": "Back to editor", "back_to_log_in": "Back to log in", - "back_to_subscription": "Back to Subscription", + "back_to_subscription": "Back to subscription", "back_to_your_projects": "Back to your projects", "basic": "Basic", "basic_compile_time": "Basic compile time", @@ -235,7 +234,7 @@ "billing": "Billing", "billing_period_sentence_case": "Billing period", "binary_history_error": "Preview not available for this file type", - "blank_project": "Blank Project", + "blank_project": "Blank project", "blocked_filename": "This file name is blocked.", "blog": "Blog", "bold": "Bold", @@ -295,12 +294,12 @@ "change_or_cancel-change": "Change", "change_or_cancel-or": "or", "change_owner": "Change owner", - "change_password": "Change Password", - "change_password_in_account_settings": "Change password in Account Settings", + "change_password": "Change password", + "change_password_in_account_settings": "Change password in Account settings", "change_plan": "Change plan", "change_primary_email": "Change primary email", - "change_primary_email_address_instructions": "To change your primary email, please add your new primary email address first (by clicking <0>Add another email) and confirm it. Then click the <0>Make Primary button. <1>Learn more about managing your __appName__ emails.", - "change_project_owner": "Change Project Owner", + "change_primary_email_address_instructions": "To change your primary email, please add your new primary email address first (by clicking <0>Add another email) and confirm it. Then click the <0>Make primary button. <1>Learn more about managing your __appName__ emails.", + "change_project_owner": "Change project owner", "change_the_ownership_of_your_personal_projects": "Change the ownership of your personal projects to the new account. <0>Find out how to change project owner.", "change_to_group_plan": "Change to a group plan", "change_to_this_plan": "Change to this plan", @@ -322,15 +321,15 @@ "city": "City", "clear_cached_files": "Clear cached files", "clear_search": "clear search", - "clear_sessions": "Clear Sessions", - "clear_sessions_description": "This is a list of other sessions (logins) which are active on your account, not including your current session. Click the \"Clear Sessions\" button below to log them out.", - "clear_sessions_success": "Sessions Cleared", + "clear_sessions": "Clear sessions", + "clear_sessions_description": "This is a list of other sessions (logins) which are active on your account, not including your current session. Click the \"Clear sessions\" button below to log them out.", + "clear_sessions_success": "Sessions cleared", "clearing": "Clearing", "click_here_to_view_sl_in_lng": "Click here to use __appName__ in <0>__lngName__", "click_link_to_proceed": "Click __clickText__ below to proceed.", "click_to_give_feedback": "Click to give feedback.", "click_to_unpause": "Click to unpause and reactivate your Overleaf premium features.", - "clicking_delete_will_remove_sso_config_and_clear_saml_data": "Clicking <0>Delete will remove your SSO configuration and unlink all users. You can only do this when SSO is disabled in your Group settings.", + "clicking_delete_will_remove_sso_config_and_clear_saml_data": "Clicking <0>Delete will remove your SSO configuration and unlink all users. You can only do this when SSO is disabled in your group settings.", "clone_with_git": "Clone with Git", "close": "Close", "clsi_maintenance": "The compile servers are down for maintenance, and will be back shortly.", @@ -361,34 +360,34 @@ "community_articles": "Community articles", "community_articles_lowercase": "community articles", "compact": "Compact", - "company_name": "Company Name", + "company_name": "Company name", "compare": "Compare", "compare_all_plans": "Compare all plans on our <0>pricing page", "compare_features": "Compare features", "comparing_from_x_to_y": "Comparing from <0>__startTime__ to <0>__endTime__", "compile_error_entry_description": "An error which prevented this project from compiling", - "compile_error_handling": "Compile Error Handling", + "compile_error_handling": "Compile error handling", "compile_larger_projects": "Compile larger projects", - "compile_mode": "Compile Mode", + "compile_mode": "Compile mode", "compile_servers": "Compile servers", "compile_servers_info_new": "The servers used to compile your project. Compiles for users on paid plans always run on the fastest available servers.", - "compile_terminated_by_user": "The compile was cancelled using the ‘Stop Compilation’ button. You can download the raw logs to see where the compile stopped.", + "compile_terminated_by_user": "The compile was cancelled using the ‘Stop compilation’ button. You can download the raw logs to see where the compile stopped.", "compile_timeout_short": "Compile timeout", "compile_timeout_short_info_new": "This is how much time you get to compile your project on Overleaf. You may need additional time for longer or more complex projects.", "compiler": "Compiler", "compiling": "Compiling", "complete": "Complete", "compliance": "Compliance", - "compromised_password": "Compromised Password", + "compromised_password": "Compromised password", "configure_sso": "Configure SSO", "configured": "Configured", "confirm": "Confirm", "confirm_accept_selected_changes": "Are you sure you want to accept the selected change?", "confirm_accept_selected_changes_plural": "Are you sure you want to accept the selected __count__ changes?", - "confirm_affiliation": "Confirm Affiliation", + "confirm_affiliation": "Confirm affiliation", "confirm_affiliation_to_relink_dropbox": "Please confirm you are still at the institution and on their license, or upgrade your account in order to relink your Dropbox account.", "confirm_delete_user_type_email_address": "To confirm you want to delete __userName__ please type the email address associated with their account", - "confirm_email": "Confirm Email", + "confirm_email": "Confirm email", "confirm_new_password": "Confirm new password", "confirm_primary_email_change": "Confirm primary email change", "confirm_reject_selected_changes": "Are you sure you want to reject the selected change?", @@ -402,16 +401,16 @@ "conflicting_paths_found": "Conflicting Paths Found", "congratulations_youve_successfully_join_group": "Congratulations! You‘ve successfully joined the group subscription.", "connect_overleaf_with_github": "Connect __appName__ with GitHub for easy project syncing and real-time version control.", - "connected_users": "Connected Users", + "connected_users": "Connected users", "connecting": "Connecting", "connection_lost_with_unsaved_changes": "Connection lost with unsaved changes.", "contact": "Contact", "contact_group_admin": "Please contact your group administrator.", "contact_message_label": "Message", - "contact_sales": "Contact Sales", + "contact_sales": "Contact sales", "contact_support": "Contact Support", - "contact_support_to_change_group_subscription": "Please <0>contact support if you wish to change your group subscription.", - "contact_us": "Contact Us", + "contact_support_to_change_group_subscription": "Please <0>contact Support if you wish to change your group subscription.", + "contact_us": "Contact us", "contact_us_lowercase": "Contact us", "contacting_the_sales_team": "Contacting the Sales team", "continue": "Continue", @@ -423,7 +422,7 @@ "copied": "Copied", "copy": "Copy", "copy_code": "Copy code", - "copy_project": "Copy Project", + "copy_project": "Copy project", "copy_response": "Copy response", "copying": "Copying", "cost_summary": "Cost summary", @@ -445,6 +444,7 @@ "create_new_subscription": "Create new subscription", "create_new_tag": "Create new tag", "create_project_in_github": "Create a GitHub repository", + "created": "Created", "created_at": "Created at", "creating": "Creating", "credit_card": "Credit Card", @@ -477,7 +477,7 @@ "dedicated_account_manager": "Dedicated account manager", "default": "Default", "delete": "Delete", - "delete_account": "Delete Account", + "delete_account": "Delete account", "delete_account_confirmation_label": "I understand this will delete all projects in my __appName__ account with email address <0>__userDefaultEmail__", "delete_account_warning_message_3": "You are about to permanently delete all of your account data, including your projects and settings. Please type your account email address and password in the boxes below to proceed.", "delete_acct_no_existing_pw": "Please use the password reset form to set a password before deleting your account", @@ -653,7 +653,7 @@ "email_sent": "Email Sent", "emails": "Emails", "emails_and_affiliations_explanation": "Add additional email addresses to your account to access any upgrades your university or institution has, to make it easier for collaborators to find you, and to make sure you can recover your account.", - "emails_and_affiliations_title": "Emails and Affiliations", + "emails_and_affiliations_title": "Emails and affiliations", "empty": "Empty", "empty_zip_file": "Zip doesn’t contain any file", "en": "English", @@ -694,7 +694,7 @@ "everything_in_group_standard_plus": "Everything in Group Standard, plus…", "everything_in_standard_plus": "Everything in Standard, plus…", "example": "Example", - "example_project": "Example Project", + "example_project": "Example project", "examples": "Examples", "examples_lowercase": "examples", "examples_to_help_you_learn": "Examples to help you learn how to use powerful LaTeX packages and techniques.", @@ -839,16 +839,16 @@ "git_authentication_token": "Git authentication token", "git_authentication_token_create_modal_info_1": "This is your Git authentication token. You should enter this when prompted for a password.", "git_authentication_token_create_modal_info_2": "<0>You will only see this authentication token once so please copy it and keep it safe. For full instructions on using authentication tokens, visit our <1>help page.", - "git_bridge_modal_click_generate": "Click Generate token to generate your authentication token now. Or do this later in your Account Settings.", + "git_bridge_modal_click_generate": "Click Generate token to generate your authentication token now. Or do this later in your Account settings.", "git_bridge_modal_enter_authentication_token": "When prompted for a password, enter your new authentication token:", "git_bridge_modal_git_clone_your_project": "Git clone your project by using the link below and a Git authentication token", "git_bridge_modal_learn_more_about_authentication_tokens": "Learn more about Git integration authentication tokens.", "git_bridge_modal_read_only": "You have read-only access to this project. This means you can pull from __appName__ but you can’t push any changes you make back to this project.", "git_bridge_modal_review_access": "<0>You have review access to this project. This means you can pull from __appName__ but you can’t push any changes you make back to this project.", - "git_bridge_modal_see_once": "You’ll only see this token once. To delete it or generate a new one, visit Account Settings. For detailed instructions and troubleshooting, read our <0>help page.", - "git_bridge_modal_use_previous_token": "If you’re prompted for a password, you can use a previously generated Git authentication token. Or you can generate a new one in Account Settings. For more support, read our <0>help page.", + "git_bridge_modal_see_once": "You’ll only see this token once. To delete it or generate a new one, visit Account settings. For detailed instructions and troubleshooting, read our <0>help page.", + "git_bridge_modal_use_previous_token": "If you’re prompted for a password, you can use a previously generated Git authentication token. Or you can generate a new one in Account settings. For more support, read our <0>help page.", "git_gitHub_dropbox_mendeley_papers_and_zotero_integrations": "Git, GitHub, Dropbox, Papers, Zotero, and Mendeley integrations", - "git_integration": "Git Integration", + "git_integration": "Git integration", "git_integration_info": "With Git integration, you can clone your Overleaf projects with Git. For full instructions on how to do this, <0>read our Git Integration help page.", "github": "GitHub", "github_commit_message_placeholder": "Commit message for changes made in __appName__...", @@ -887,7 +887,7 @@ "go_next_page": "Go to Next Page", "go_page": "Go to page __page__", "go_prev_page": "Go to Previous Page", - "go_to_account_settings": "Go to Account Settings", + "go_to_account_settings": "Go to Account settings", "go_to_code_location_in_pdf": "Go to code location in PDF", "go_to_first_page": "Go to first page", "go_to_last_page": "Go to last page", @@ -1153,13 +1153,13 @@ "last_active_description": "Last time a project was opened.", "last_edit": "Last edit", "last_logged_in": "Last logged in", - "last_modified": "Last Modified", + "last_modified": "Last modified", "last_name": "Last name", "last_resort_trouble_shooting_guide": "If that doesn’t help, follow our <0>troubleshooting guide.", "last_suggested_fix": "Last suggested fix", "last_updated": "Last Updated", "last_updated_date_by_x": "__lastUpdatedDate__ by __person__", - "last_used": "last used", + "last_used": "Last used", "latam_discount_modal_info": "Unlock the full potential of Overleaf with a __discount__% discount on premium subscriptions paid in __currencyName__. Get a longer compile timeout, full document history, track changes, additional collaborators, and more.", "latam_discount_modal_title": "Premium subscription discount", "latam_discount_offer_plans_page_banner": "__flag__ We’ve applied a __discount__ discount to premium plans on this page for our users in __country__. Check out the new lower prices (in __currency__).", @@ -1212,8 +1212,8 @@ "line_width_is_the_width_of_the_line_in_the_current_environment": "Line width is the width of the line in the current environment. e.g. a full page width in single-column layout or half a page width in a two-column layout.", "link": "Link", "link_account": "Link Account", - "link_accounts": "Link Accounts", - "link_accounts_and_add_email": "Link Accounts and Add Email", + "link_accounts": "Link accounts", + "link_accounts_and_add_email": "Link accounts and add email", "link_institutional_email_get_started": "Link an institutional email address to your account to get started.", "link_overleaf_with_git": "Link __appName__ with Git for seamless project syncing and version control across your repositories.", "link_sharing": "Link sharing", @@ -1225,7 +1225,7 @@ "link_to_papers": "Link to Papers", "link_to_zotero": "Link to Zotero", "link_your_accounts": "Link your accounts", - "linked_accounts": "linked accounts", + "linked_accounts": "Linked accounts", "linked_accounts_explained": "You can link your __appName__ account with other services to enable the features described below.", "linked_collabratec_description": "Use Collabratec to manage your __appName__ projects.", "linked_file": "Imported file", @@ -1287,8 +1287,8 @@ "make_a_copy": "Make a copy", "make_email_primary_description": "Make this the primary email, used to log in", "make_owner": "Make owner", - "make_primary": "Make Primary", - "make_private": "Make Private", + "make_primary": "Make primary", + "make_private": "Make private", "manage_beta_program_membership": "Manage Beta Program Membership", "manage_files_from_your_dropbox_folder": "Manage files from your Dropbox folder", "manage_group_members_subtext": "Add or remove members from your group subscription", @@ -1329,7 +1329,7 @@ "mendeley_dynamic_sync_description": "With the Mendeley integration, you can import your references into __appName__. You can either import all your references at once or dynamically search your Mendeley library directly from __appName__.", "mendeley_groups_loading_error": "There was an error loading groups from Mendeley", "mendeley_groups_relink": "There was an error accessing your Mendeley data. This was likely caused by lack of permissions. Please re-link your account and try again.", - "mendeley_integration": "Mendeley Integration", + "mendeley_integration": "Mendeley integration", "mendeley_is_premium": "Mendeley integration is a premium feature", "mendeley_reference_loading_error": "Error, could not load references from Mendeley", "mendeley_reference_loading_error_expired": "Mendeley token expired, please re-link your account", @@ -1378,7 +1378,7 @@ "navigate_log_source": "Navigate to log position in source code: __location__", "navigation": "Navigation", "nearly_activated": "You’re one step away from activating your __appName__ account!", - "need_20_plus_users_discount": "20+ users? <0>Contact Sales to get the best discounts.", + "need_20_plus_users_discount": "20+ users? <0>Contact sales to get the best discounts.", "need_anything_contact_us_at": "If there is anything you ever need please feel free to contact us directly at", "need_contact_group_admin_to_make_changes": "You’ll need to contact your group admin if you want to make certain changes to your account. <0>Read more about managed users.", "need_make_changes": "You need to make some changes", @@ -1397,11 +1397,11 @@ "new_navigation_introducing_left_hand_side_rail_and_top_menus": "New navigation - introducing left-hand side rail and top menus", "new_overleaf_editor": "New Overleaf editor", "new_password": "New password", - "new_project": "New Project", + "new_project": "New project", "new_project_name": "New project name", "new_snippet_project": "Untitled", "new_subscription_will_be_billed_immediately": "Your new subscription will be billed immediately to your current payment method.", - "new_tag": "New Tag", + "new_tag": "New tag", "new_tag_name": "New tag name", "newsletter": "Newsletter", "newsletter_info_note": "Please note: you will still receive important emails, such as project invites and security notifications (password resets, account linking, etc).", @@ -1546,7 +1546,7 @@ "papers_dynamic_sync_description": "With the Papers integration, you can import your references into __appName__. You can either import all your references at once or dynamically search your Papers library directly from __appName__.", "papers_groups_loading_error": "There was an error loading libraries from Papers", "papers_groups_relink": "There was an error accessing your Papers data. This was likely caused by lack of permissions. Please re-link your account and try again.", - "papers_integration": "Papers Integration", + "papers_integration": "Papers integration", "papers_is_premium": "Papers integration is a premium feature", "papers_presentations_reports_and_more": "Papers, presentations, reports and more, written in LaTeX and published by our community.", "papers_reference_loading_error": "Error, could not load references from Papers", @@ -1629,7 +1629,7 @@ "please_confirm_primary_email": "Please confirm your primary email address __emailAddress__ by clicking on the link in the confirmation email.", "please_confirm_secondary_email": "Please confirm your secondary email address __emailAddress__ by clicking on the link in the confirmation email.", "please_confirm_your_email_before_making_it_default": "Please confirm your email before making it the primary.", - "please_contact_support_to_makes_change_to_your_plan": "Please <0>contact support to make changes to your plan", + "please_contact_support_to_makes_change_to_your_plan": "Please <0>contact Support to make changes to your plan", "please_contact_us_if_you_think_this_is_in_error": "Please <0>contact us if you think this is in error.", "please_enter_confirmation_code": "Please enter your confirmation code", "please_enter_email": "Please enter your email address", @@ -1711,7 +1711,7 @@ "project_search_file_count_plural": "in __count__ files", "project_search_result_count": "__count__ result", "project_search_result_count_plural": "__count__ results", - "project_synchronisation": "Project Synchronisation", + "project_synchronisation": "Project synchronisation", "project_timed_out_enable_stop_on_first_error": "<0>Enable “Stop on first error” to help you find and fix errors right away.", "project_timed_out_fatal_error": "A <0>fatal compile error may be completely blocking compilation.", "project_timed_out_intro": "Sorry, your compile took too long to run and timed out. The most common causes of timeouts are:", @@ -1792,7 +1792,7 @@ "regards": "Regards", "register": "Register", "register_error": "Registration error", - "register_intercept_sso": "You can link your __authProviderName__ account from the Account Settings page after logging in.", + "register_intercept_sso": "You can link your __authProviderName__ account from the Account settings page after logging in.", "register_to_accept_invitation": "Register to accept invitation", "register_to_edit_template": "Please register to edit the __templateName__ template", "register_with_another_email": "Register with __appName__ using another email.", @@ -2047,7 +2047,7 @@ "skip_to_content": "Skip to content", "something_not_right": "Something’s not right", "something_went_wrong": "Something went wrong", - "something_went_wrong_canceling_your_subscription": "Something went wrong canceling your subscription. Please contact support.", + "something_went_wrong_canceling_your_subscription": "Something went wrong canceling your subscription. Please contact Support.", "something_went_wrong_loading_pdf_viewer": "Something went wrong loading the PDF viewer. This might be caused by issues like <0>temporary network problems or an <0>outdated web browser. Please follow the <1>troubleshooting steps for access, loading and display problems. If the issue persists, please <2>let us know.", "something_went_wrong_processing_the_request": "Something went wrong processing the request", "something_went_wrong_rendering_pdf": "Something went wrong while rendering this PDF.", @@ -2274,7 +2274,7 @@ "then_x_price_per_year": "Then __price__ per year", "there_are_lots_of_options_to_edit_and_customize_your_figures": "There are lots of options to edit and customize your figures, such as wrapping text around the figure, rotating the image, or including multiple images in a single figure. You’ll need to edit the LaTeX code to do this. <0>Find out how", "there_is_an_unrecoverable_latex_error": "There is an unrecoverable LaTeX error. If there are LaTeX errors shown below or in the raw logs, please try to fix them and compile again.", - "there_was_a_problem_restoring_the_project_please_try_again_in_a_few_moments_or_contact_us": "There was a problem restoring the project. Please try again in a few moments. Contact us of the problem persists.", + "there_was_a_problem_restoring_the_project_please_try_again_in_a_few_moments_or_contact_us": "There was a problem restoring the project. Please try again in a few moments. Contact us if the problem persists.", "there_was_an_error_opening_your_content": "There was an error creating your project", "thesis": "Thesis", "they_lose_access_to_account": "They lose all access to this Overleaf account immediately", @@ -2326,7 +2326,7 @@ "to_use_text_wrapping_in_your_table_make_sure_you_include_the_array_package": "<0>Please note: To use text wrapping in your table, make sure you include the <1>array package in your document preamble:", "toggle_compile_options_menu": "Toggle compile options menu", "toggle_unknown_group": "Toggle unknown group", - "token": "token", + "token": "Token", "token_access_failure": "Cannot grant access; contact the project owner for help", "token_limit_reached": "You’ve reached the 10 token limit. To generate a new authentication token, please delete an existing one.", "token_read_only": "token read-only", @@ -2406,7 +2406,7 @@ "trash": "Trash", "trash_projects": "Trash Projects", "trashed": "Trashed", - "trashed_projects": "Trashed Projects", + "trashed_projects": "Trashed projects", "trashing_projects_wont_affect_collaborators": "Trashing projects won’t affect your collaborators.", "trial_last_day": "This is the last day of your Overleaf Premium trial", "trial_remaining_days": "__days__ more days on your Overleaf Premium trial", @@ -2456,8 +2456,8 @@ "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_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).", - "unlink_linked_google_account": "Unlink your Google account. <0>Remove it in Account Settings (under Linked Accounts).", + "unlink_linked_accounts": "Unlink any linked accounts (such as ORCID ID, IEEE). <0>Remove them in Account settings (under Linked Accounts).", + "unlink_linked_google_account": "Unlink your Google account. <0>Remove it in Account settings (under Linked Accounts).", "unlink_provider_account_title": "Unlink __provider__ Account", "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", @@ -2477,7 +2477,7 @@ "until_then_you_can_still": "Until then you can still:", "untrash": "Restore", "update": "Update", - "update_account_info": "Update Account Info", + "update_account_info": "Update account info", "update_dropbox_settings": "Update Dropbox Settings", "update_your_billing_details": "Update your billing details", "updates_to_project_sharing": "Updates to project sharing", @@ -2499,7 +2499,7 @@ "upload_failed": "Upload failed", "upload_file": "Upload file", "upload_from_computer": "Upload from computer", - "upload_project": "Upload Project", + "upload_project": "Upload project", "upload_zipped_project": "Upload Zipped Project", "url_to_fetch_the_file_from": "URL to fetch the file from", "us_gov_banner_fedramp": "<0>Now FedRAMP® authorized for LI-SaaS: Overleaf’s Group Professional subscription. Need an air-gapped deployment? We offer an on-premises solution too. Talk to our US federal government team.", @@ -2540,7 +2540,7 @@ "vat_number": "VAT Number", "verify_email_address_before_enabling_managed_users": "You need to verify your email address before enabling managed users.", "view": "View", - "view_all": "View All", + "view_all": "View all", "view_billing_details": "View billing details", "view_code": "View code", "view_configuration": "View configuration", @@ -2646,14 +2646,14 @@ "you_are_a_manager_of_x_plan_as_member_of_group_subscription_y_administered_by_z": "You are a <1>manager of the <0>__planName__ group subscription <1>__groupName__ administered by <1>__adminEmail__.", "you_are_a_manager_of_x_plan_as_member_of_group_subscription_y_administered_by_z_you": "You are a <1>manager of the <0>__planName__ group subscription <1>__groupName__ administered by <1>you (__adminEmail__).", "you_are_currently_logged_in_as": "You are currently logged in as __email__.", - "you_are_on_a_paid_plan_contact_support_to_find_out_more": "You’re on an __appName__ Paid plan. <0>Contact support to find out more.", + "you_are_on_a_paid_plan_contact_support_to_find_out_more": "You’re on an __appName__ Paid plan. <0>Contact Support to find out more.", "you_are_on_x_plan_as_a_confirmed_member_of_institution_y": "You are on our <0>__planName__ plan as a <1>confirmed member of <1>__institutionName__", "you_are_on_x_plan_as_member_of_group_subscription_y_administered_by_z": "You are on our <0>__planName__ plan as a <1>member of the group subscription <1>__groupName__ administered by <1>__adminEmail__", "you_can_also_choose_to_view_anonymously_or_leave_the_project": "You can also choose to <0>view anonymously (you will lose edit access) or <1>leave the project.", "you_can_buy_this_plan_but_not_as_a_trial": "You can buy this plan but not as a trial, as you’ve completed a trial recently.", "you_can_leave_the_experiment_from_your_account_settings_at_any_time": "You can leave the experiment from your <0>account settings at any time.", "you_can_manage_your_reference_manager_integrations_from_your_account_settings_page": "You can manage your reference manager integrations from your <0>account settings page.", - "you_can_now_enable_sso": "You can now enable SSO on your Group settings page.", + "you_can_now_enable_sso": "You can now enable SSO on your group settings page.", "you_can_now_log_in_sso": "You can now log in through your institution and if eligible you will receive <0>__appName__ Professional features.", "you_can_now_sync_your_papers_library_directly_with_your_overleaf_projects": "You can now sync your Papers library directly with your Overleaf projects", "you_can_opt_in_and_out_of_the_program_at_any_time_on_this_page": "You can <0>opt in and out of the program at any time on this page", @@ -2716,7 +2716,7 @@ "your_project_exceeded_compile_timeout_limit_on_free_plan": "Your project exceeded the compile timeout limit on our free plan.", "your_project_near_compile_timeout_limit": "Your project is near the compile timeout limit for our free plan.", "your_project_need_more_time_to_compile": "It looks like your project may need more time to compile than our free plan allows.", - "your_projects": "Your Projects", + "your_projects": "Your projects", "your_questions_answered": "Your questions answered", "your_role": "Your role", "your_sessions": "Your Sessions", @@ -2751,7 +2751,7 @@ "zotero_dynamic_sync_description": "With the Zotero integration, you can import your references into __appName__. You can either import all your references at once or dynamically search your Zotero library directly from __appName__.", "zotero_groups_loading_error": "There was an error loading groups from Zotero", "zotero_groups_relink": "There was an error accessing your Zotero data. This was likely caused by lack of permissions. Please re-link your account and try again.", - "zotero_integration": "Zotero Integration", + "zotero_integration": "Zotero integration", "zotero_is_premium": "Zotero integration is a premium feature", "zotero_reference_loading_error": "Error, could not load references from Zotero", "zotero_reference_loading_error_expired": "Zotero token expired, please re-link your account", diff --git a/services/web/test/frontend/components/editor-left-menu/editor-left-menu.spec.tsx b/services/web/test/frontend/components/editor-left-menu/editor-left-menu.spec.tsx index b5e49765a5..808f97bd4b 100644 --- a/services/web/test/frontend/components/editor-left-menu/editor-left-menu.spec.tsx +++ b/services/web/test/frontend/components/editor-left-menu/editor-left-menu.spec.tsx @@ -75,7 +75,7 @@ describe('', function () { // Actions Menu cy.findByRole('heading', { name: 'Actions' }) - cy.findByRole('button', { name: 'Copy Project' }) + cy.findByRole('button', { name: 'Copy project' }) cy.findByRole('button', { name: 'Word Count' }) // Sync Menu @@ -105,7 +105,7 @@ describe('', function () { cy.findByRole('heading', { name: 'Help' }) cy.findByRole('button', { name: 'Show Hotkeys' }) cy.findByRole('link', { name: 'Documentation' }) - cy.findByRole('button', { name: 'Contact Us' }) + cy.findByRole('button', { name: 'Contact us' }) }) describe('download menu', function () { @@ -154,14 +154,14 @@ describe('', function () { ) - cy.findByRole('button', { name: 'Copy Project' }).click() - cy.findByRole('heading', { name: 'Copy Project' }) + cy.findByRole('button', { name: 'Copy project' }).click() + cy.findByRole('heading', { name: 'Copy project' }) // try closing & re-opening the modal with different methods cy.findByRole('button', { name: 'Close' }).click() - cy.findByRole('button', { name: 'Copy Project' }).click() + cy.findByRole('button', { name: 'Copy project' }).click() cy.findByRole('button', { name: 'Cancel' }).click() - cy.findByRole('button', { name: 'Copy Project' }).click() + cy.findByRole('button', { name: 'Copy project' }).click() cy.findByLabelText('New Name').focus() cy.findByLabelText('New Name').clear() @@ -840,7 +840,7 @@ describe('', function () { ) - cy.findByRole('button', { name: 'Contact Us' }).click() + cy.findByRole('button', { name: 'Contact us' }).click() cy.findByText('Affected project URL (Optional)') }) }) @@ -869,7 +869,7 @@ describe('', function () { // Actions Menu cy.findByRole('heading', { name: 'Actions' }).should('not.exist') - cy.findByRole('button', { name: 'Copy Project' }).should('not.exist') + cy.findByRole('button', { name: 'Copy project' }).should('not.exist') cy.findByRole('button', { name: 'Word Count' }).should('not.exist') // Sync Menu @@ -899,7 +899,7 @@ describe('', function () { cy.findByRole('heading', { name: 'Help' }) cy.findByRole('button', { name: 'Show Hotkeys' }) cy.findByRole('button', { name: 'Documentation' }).should('not.exist') - cy.findByRole('link', { name: 'Contact Us' }).should('not.exist') + cy.findByRole('link', { name: 'Contact us' }).should('not.exist') }) }) }) diff --git a/services/web/test/frontend/features/clone-project-modal/components/clone-project-modal.test.jsx b/services/web/test/frontend/features/clone-project-modal/components/clone-project-modal.test.jsx index e22a2c1791..6a837ed81f 100644 --- a/services/web/test/frontend/features/clone-project-modal/components/clone-project-modal.test.jsx +++ b/services/web/test/frontend/features/clone-project-modal/components/clone-project-modal.test.jsx @@ -33,7 +33,7 @@ describe('', function () { { scope: { project } } ) - await screen.findByText('Copy Project') + await screen.findByText('Copy project') }) it('posts the generated project name', async function () { diff --git a/services/web/test/frontend/features/editor-left-menu/components/actions-copy-project.test.jsx b/services/web/test/frontend/features/editor-left-menu/components/actions-copy-project.test.jsx index 066858f1ba..050e11f653 100644 --- a/services/web/test/frontend/features/editor-left-menu/components/actions-copy-project.test.jsx +++ b/services/web/test/frontend/features/editor-left-menu/components/actions-copy-project.test.jsx @@ -21,7 +21,7 @@ describe('', function () { it('shows correct modal when clicked', async function () { renderWithEditorContext() - fireEvent.click(screen.getByRole('button', { name: 'Copy Project' })) + fireEvent.click(screen.getByRole('button', { name: 'Copy project' })) screen.getByPlaceholderText('New project name') }) @@ -36,10 +36,10 @@ describe('', function () { renderWithEditorContext() - fireEvent.click(screen.getByRole('button', { name: 'Copy Project' })) + fireEvent.click(screen.getByRole('button', { name: 'Copy project' })) const input = screen.getByPlaceholderText('New project name') - fireEvent.change(input, { target: { value: 'New Project' } }) + fireEvent.change(input, { target: { value: 'New project' } }) const button = screen.getByRole('button', { name: 'Copy' }) button.click() diff --git a/services/web/test/frontend/features/editor-left-menu/components/actions-menu.test.jsx b/services/web/test/frontend/features/editor-left-menu/components/actions-menu.test.jsx index d48e172c49..54f0048afa 100644 --- a/services/web/test/frontend/features/editor-left-menu/components/actions-menu.test.jsx +++ b/services/web/test/frontend/features/editor-left-menu/components/actions-menu.test.jsx @@ -41,7 +41,7 @@ describe('', function () { screen.getByText('Actions') screen.getByRole('button', { - name: 'Copy Project', + name: 'Copy project', }) await waitFor(() => { @@ -69,7 +69,7 @@ describe('', function () { expect(screen.queryByText('Actions')).to.equal(null) expect( screen.queryByRole('button', { - name: 'Copy Project', + name: 'Copy project', }) ).to.equal(null) diff --git a/services/web/test/frontend/features/editor-left-menu/components/help-contact-us.test.jsx b/services/web/test/frontend/features/editor-left-menu/components/help-contact-us.test.jsx index 9b37107fad..d75a798214 100644 --- a/services/web/test/frontend/features/editor-left-menu/components/help-contact-us.test.jsx +++ b/services/web/test/frontend/features/editor-left-menu/components/help-contact-us.test.jsx @@ -21,7 +21,7 @@ describe('', function () { renderWithEditorContext() expect(screen.queryByRole('dialog')).to.equal(null) - fireEvent.click(screen.getByRole('button', { name: 'Contact Us' })) + fireEvent.click(screen.getByRole('button', { name: 'Contact us' })) const modal = screen.getAllByRole('dialog')[0] within(modal).getAllByText('Get in touch') within(modal).getByText('Subject') diff --git a/services/web/test/frontend/features/editor-left-menu/components/help-menu.test.jsx b/services/web/test/frontend/features/editor-left-menu/components/help-menu.test.jsx index eec6fdce31..3eb3fe6d04 100644 --- a/services/web/test/frontend/features/editor-left-menu/components/help-menu.test.jsx +++ b/services/web/test/frontend/features/editor-left-menu/components/help-menu.test.jsx @@ -23,7 +23,7 @@ describe('', function () { renderWithEditorContext() screen.getByRole('button', { name: 'Show Hotkeys' }) - screen.getByRole('button', { name: 'Contact Us' }) + screen.getByRole('button', { name: 'Contact us' }) screen.getByRole('link', { name: 'Documentation' }) }) @@ -33,7 +33,7 @@ describe('', function () { renderWithEditorContext() screen.getByRole('button', { name: 'Show Hotkeys' }) - expect(screen.queryByRole('button', { name: 'Contact Us' })).to.equal(null) + expect(screen.queryByRole('button', { name: 'Contact us' })).to.equal(null) expect(screen.queryByRole('link', { name: 'Documentation' })).to.equal(null) }) }) diff --git a/services/web/test/frontend/features/project-list/components/new-project-button.test.tsx b/services/web/test/frontend/features/project-list/components/new-project-button.test.tsx index 1c86b861a3..3f3dc89575 100644 --- a/services/web/test/frontend/features/project-list/components/new-project-button.test.tsx +++ b/services/web/test/frontend/features/project-list/components/new-project-button.test.tsx @@ -28,16 +28,16 @@ describe('', function () { renderWithProjectListContext() const newProjectButton = screen.getByRole('button', { - name: 'New Project', + name: 'New project', }) fireEvent.click(newProjectButton) }) it('shows the correct dropdown menu', function () { // static menu - screen.getByText('Blank Project') - screen.getByText('Example Project') - screen.getByText('Upload Project') + screen.getByText('Blank project') + screen.getByText('Example project') + screen.getByText('Upload project') screen.getByText('Import from GitHub') // static text @@ -48,27 +48,27 @@ describe('', function () { screen.getByText('View All') }) - it('open new project modal when clicking at Blank Project', function () { - fireEvent.click(screen.getByRole('menuitem', { name: 'Blank Project' })) + it('open new project modal when clicking at Blank project', function () { + fireEvent.click(screen.getByRole('menuitem', { name: 'Blank project' })) screen.getByPlaceholderText('Project Name') }) - it('open new project modal when clicking at Example Project', function () { - fireEvent.click(screen.getByRole('menuitem', { name: 'Example Project' })) + it('open new project modal when clicking at Example project', function () { + fireEvent.click(screen.getByRole('menuitem', { name: 'Example project' })) screen.getByPlaceholderText('Project Name') }) it('close the new project modal when clicking at the top right "x" button', function () { - fireEvent.click(screen.getByRole('menuitem', { name: 'Blank Project' })) + fireEvent.click(screen.getByRole('menuitem', { name: 'Blank project' })) fireEvent.click(screen.getByRole('button', { name: 'Close' })) expect(screen.queryByRole('dialog')).to.be.null }) it('close the new project modal when clicking at the Cancel button', function () { - fireEvent.click(screen.getByRole('menuitem', { name: 'Blank Project' })) + fireEvent.click(screen.getByRole('menuitem', { name: 'Blank project' })) fireEvent.click(screen.getByRole('button', { name: 'Cancel' })) expect(screen.queryByRole('dialog')).to.be.null @@ -102,14 +102,14 @@ describe('', function () { renderWithProjectListContext() const newProjectButton = screen.getByRole('button', { - name: 'New Project', + name: 'New project', }) fireEvent.click(newProjectButton) // static menu - screen.getByText('Blank Project') - screen.getByText('Example Project') - screen.getByText('Upload Project') + screen.getByText('Blank project') + screen.getByText('Example project') + screen.getByText('Upload project') screen.getByText('Import from GitHub') // static text for institution templates diff --git a/services/web/test/frontend/features/project-list/components/notifications.test.tsx b/services/web/test/frontend/features/project-list/components/notifications.test.tsx index db844f0260..7197ddb365 100644 --- a/services/web/test/frontend/features/project-list/components/notifications.test.tsx +++ b/services/web/test/frontend/features/project-list/components/notifications.test.tsx @@ -931,7 +931,7 @@ describe('', function () { renderWithinProjectListProvider(GroupsAndEnterpriseBanner) await fetchMock.callHistory.flush(true) - expect(screen.queryByRole('link', { name: 'Contact Sales' })).to.be.null + expect(screen.queryByRole('link', { name: 'Contact sales' })).to.be.null }) it('shows the banner for users that have dismissed the previous banners', async function () { @@ -941,7 +941,7 @@ describe('', function () { renderWithinProjectListProvider(GroupsAndEnterpriseBanner) await fetchMock.callHistory.flush(true) - await screen.findByRole('link', { name: 'Contact Sales' }) + await screen.findByRole('link', { name: 'Contact sales' }) }) it('shows the banner for users that have dismissed the banner more than 30 days ago', async function () { @@ -956,7 +956,7 @@ describe('', function () { renderWithinProjectListProvider(GroupsAndEnterpriseBanner) await fetchMock.callHistory.flush(true) - await screen.findByRole('link', { name: 'Contact Sales' }) + await screen.findByRole('link', { name: 'Contact sales' }) }) it('does not show the banner for users that have dismissed the banner within the last 30 days', async function () { @@ -971,7 +971,7 @@ describe('', function () { renderWithinProjectListProvider(GroupsAndEnterpriseBanner) await fetchMock.callHistory.flush(true) - expect(screen.queryByRole('link', { name: 'Contact Sales' })).to.be.null + expect(screen.queryByRole('link', { name: 'Contact sales' })).to.be.null }) describe('users that are not in group and are not affiliated', function () { @@ -1012,7 +1012,7 @@ describe('', function () { await screen.findByText( 'Overleaf On-Premises: Does your company want to keep its data within its firewall? Overleaf offers Server Pro, an on-premises solution for companies. Get in touch to learn more.' ) - const link = screen.getByRole('link', { name: 'Contact Sales' }) + const link = screen.getByRole('link', { name: 'Contact sales' }) expect(link.getAttribute('href')).to.equal(`/for/contact-sales-2`) }) @@ -1029,7 +1029,7 @@ describe('', function () { await screen.findByText( 'Why do Fortune 500 companies and top research institutions trust Overleaf to streamline their collaboration? Get in touch to learn more.' ) - const link = screen.getByRole('link', { name: 'Contact Sales' }) + const link = screen.getByRole('link', { name: 'Contact sales' }) expect(link.getAttribute('href')).to.equal(`/for/contact-sales-4`) }) diff --git a/services/web/test/frontend/features/project-list/components/project-list-root.test.tsx b/services/web/test/frontend/features/project-list/components/project-list-root.test.tsx index dc5ecfe6cf..abc92eefd1 100644 --- a/services/web/test/frontend/features/project-list/components/project-list-root.test.tsx +++ b/services/web/test/frontend/features/project-list/components/project-list-root.test.tsx @@ -260,7 +260,7 @@ describe('', function () { describe('archived projects', function () { beforeEach(function () { - const filterButton = screen.getAllByText('Archived Projects')[0] + const filterButton = screen.getAllByText('Archived projects')[0] fireEvent.click(filterButton) allCheckboxes = screen.getAllByRole('checkbox') @@ -308,7 +308,7 @@ describe('', function () { describe('trashed projects', function () { beforeEach(function () { - const filterButton = screen.getAllByText('Trashed Projects')[0] + const filterButton = screen.getAllByText('Trashed projects')[0] fireEvent.click(filterButton) allCheckboxes = screen.getAllByRole('checkbox') @@ -333,7 +333,7 @@ describe('', function () { }) it('clears selected projects when filter changed', function () { - const filterButton = screen.getAllByText('All Projects')[0] + const filterButton = screen.getAllByText('All projects')[0] fireEvent.click(filterButton) const allCheckboxes = @@ -843,7 +843,7 @@ describe('', function () { describe('"More" dropdown', function () { beforeEach(async function () { - const filterButton = screen.getAllByText('All Projects')[0] + const filterButton = screen.getAllByText('All projects')[0] fireEvent.click(filterButton) allCheckboxes = screen.getAllByRole('checkbox') }) @@ -1193,7 +1193,7 @@ describe('', function () { expect(screen.queryByText(copiedProjectName)).to.be.null - const yourProjectFilter = screen.getAllByText('Your Projects')[0] + const yourProjectFilter = screen.getAllByText('Your projects')[0] fireEvent.click(yourProjectFilter) await screen.findByText(copiedProjectName) }) diff --git a/services/web/test/frontend/features/project-list/components/sidebar/tags-list.test.tsx b/services/web/test/frontend/features/project-list/components/sidebar/tags-list.test.tsx index 7d68a8f7fe..b066d45244 100644 --- a/services/web/test/frontend/features/project-list/components/sidebar/tags-list.test.tsx +++ b/services/web/test/frontend/features/project-list/components/sidebar/tags-list.test.tsx @@ -24,7 +24,7 @@ describe('', function () { fetchMock.post('/tag', { _id: 'eee888eee888', - name: 'New Tag', + name: 'New tag', project_ids: [], }) fetchMock.post('express:/tag/:tagId/projects', 200) @@ -46,7 +46,7 @@ describe('', function () { expect(header.textContent).to.equal('Organize Tags') await screen.findByRole('button', { - name: 'New Tag', + name: 'New tag', }) await screen.findByRole('button', { name: 'Tag 1 (1)', @@ -82,7 +82,7 @@ describe('', function () { describe('Create modal', function () { beforeEach(async function () { const newTagButton = screen.getByRole('button', { - name: 'New Tag', + name: 'New tag', }) fireEvent.click(newTagButton) @@ -139,7 +139,7 @@ describe('', function () { it('filling the input and clicking Create sends a request', async function () { const modal = screen.getAllByRole('dialog', { hidden: false })[0] const input = within(modal).getByRole('textbox') - fireEvent.change(input, { target: { value: 'New Tag' } }) + fireEvent.change(input, { target: { value: 'New tag' } }) const createButton = within(modal).getByRole('button', { name: 'Create' }) expect(createButton.hasAttribute('disabled')).to.be.false @@ -155,7 +155,7 @@ describe('', function () { ) screen.getByRole('button', { - name: 'New Tag (0)', + name: 'New tag (0)', }) }) }) @@ -234,7 +234,7 @@ describe('', function () { it('filling the input and clicking Save sends a request', async function () { const modal = screen.getAllByRole('dialog', { hidden: false })[0] const input = within(modal).getByRole('textbox') - fireEvent.change(input, { target: { value: 'New Tag Name' } }) + fireEvent.change(input, { target: { value: 'New tag Name' } }) const saveButton = within(modal).getByRole('button', { name: 'Save' }) expect(saveButton.hasAttribute('disabled')).to.be.false @@ -250,7 +250,7 @@ describe('', function () { ) screen.getByRole('button', { - name: 'New Tag Name (1)', + name: 'New tag Name (1)', }) }) }) diff --git a/services/web/test/frontend/features/project-list/components/table/cells/action-buttons/archive-project-button.test.tsx b/services/web/test/frontend/features/project-list/components/table/cells/action-buttons/archive-project-button.test.tsx index 216f08a89b..f5b55a71ba 100644 --- a/services/web/test/frontend/features/project-list/components/table/cells/action-buttons/archive-project-button.test.tsx +++ b/services/web/test/frontend/features/project-list/components/table/cells/action-buttons/archive-project-button.test.tsx @@ -31,7 +31,7 @@ describe('', function () { ) const btn = screen.getByRole('button', { name: 'Archive' }) fireEvent.click(btn) - screen.getByText('Archive Projects') + screen.getByText('Archive projects') screen.getByText(archiveableProject.name) }) @@ -56,7 +56,7 @@ describe('', function () { ) const btn = screen.getByRole('button', { name: 'Archive' }) fireEvent.click(btn) - screen.getByText('Archive Projects') + screen.getByText('Archive projects') screen.getByText('You are about to archive the following projects:') screen.getByText('Archiving projects won’t affect your collaborators.') const confirmBtn = screen.getByRole('button', { diff --git a/services/web/test/frontend/features/project-list/components/table/cells/action-buttons/copy-project-button.test.tsx b/services/web/test/frontend/features/project-list/components/table/cells/action-buttons/copy-project-button.test.tsx index 37c38d13d9..98dc43c683 100644 --- a/services/web/test/frontend/features/project-list/components/table/cells/action-buttons/copy-project-button.test.tsx +++ b/services/web/test/frontend/features/project-list/components/table/cells/action-buttons/copy-project-button.test.tsx @@ -55,7 +55,7 @@ describe('', function () { const btn = screen.getByRole('button', { name: 'Copy' }) fireEvent.click(btn) - screen.getByText('Copy Project') + screen.getByText('Copy project') screen.getByLabelText('New Name') screen.getByDisplayValue(`${copyableProject.name} (Copy)`) const copyBtn = screen.getAllByRole('button', { diff --git a/services/web/test/frontend/features/project-list/components/table/project-tools/buttons/archive-projects.button.test.tsx b/services/web/test/frontend/features/project-list/components/table/project-tools/buttons/archive-projects.button.test.tsx index 2963592164..4201c092ec 100644 --- a/services/web/test/frontend/features/project-list/components/table/project-tools/buttons/archive-projects.button.test.tsx +++ b/services/web/test/frontend/features/project-list/components/table/project-tools/buttons/archive-projects.button.test.tsx @@ -21,6 +21,6 @@ describe('', function () { renderWithProjectListContext() const btn = screen.getByRole('button', { name: 'Archive' }) fireEvent.click(btn) - screen.getByText('Archive Projects') + screen.getByText('Archive projects') }) }) diff --git a/services/web/test/frontend/features/project-list/components/welcome-message.test.tsx b/services/web/test/frontend/features/project-list/components/welcome-message.test.tsx index 064a488e3c..56c3693695 100644 --- a/services/web/test/frontend/features/project-list/components/welcome-message.test.tsx +++ b/services/web/test/frontend/features/project-list/components/welcome-message.test.tsx @@ -30,9 +30,9 @@ describe('', function () { fireEvent.click(button) - screen.getByText('Blank Project') - screen.getByText('Example Project') - screen.getByText('Upload Project') + screen.getByText('Blank project') + screen.getByText('Example project') + screen.getByText('Upload project') screen.getByText('Import from GitHub') }) @@ -52,9 +52,9 @@ describe('', function () { fireEvent.click(button) // static menu - screen.getByText('Blank Project') - screen.getByText('Example Project') - screen.getByText('Upload Project') + screen.getByText('Blank project') + screen.getByText('Example project') + screen.getByText('Upload project') screen.getByText('Import from GitHub') // static text for institution templates @@ -79,9 +79,9 @@ describe('', function () { fireEvent.click(button) - screen.getByText('Blank Project') - screen.getByText('Example Project') - screen.getByText('Upload Project') + screen.getByText('Blank project') + screen.getByText('Example project') + screen.getByText('Upload project') screen.getByText('Import from GitHub') }) @@ -130,9 +130,9 @@ describe('', function () { fireEvent.click(button) - screen.getByText('Blank Project') - screen.getByText('Example Project') - screen.getByText('Upload Project') + screen.getByText('Blank project') + screen.getByText('Example project') + screen.getByText('Upload project') expect(screen.queryByText('Import from GitHub')).to.not.exist }) diff --git a/services/web/test/frontend/features/settings/components/emails/emails-row.test.tsx b/services/web/test/frontend/features/settings/components/emails/emails-row.test.tsx index 990d905ba8..f0d105d3e9 100644 --- a/services/web/test/frontend/features/settings/components/emails/emails-row.test.tsx +++ b/services/web/test/frontend/features/settings/components/emails/emails-row.test.tsx @@ -48,7 +48,7 @@ describe('', function () { it('renders actions', function () { renderEmailsRow(unconfirmedUserData) - screen.getByRole('button', { name: 'Make Primary' }) + screen.getByRole('button', { name: 'Make primary' }) }) }) @@ -96,7 +96,7 @@ describe('', function () { getByTextContent( 'You can now link your Overleaf account to your Overleaf institutional account.' ) - screen.getByRole('button', { name: 'Link Accounts' }) + screen.getByRole('button', { name: 'Link accounts' }) }) }) @@ -113,7 +113,7 @@ describe('', function () { getByTextContent( 'You can log in to Overleaf through your Overleaf institutional login.' ) - expect(screen.queryByRole('button', { name: 'Link Accounts' })).to.be + expect(screen.queryByRole('button', { name: 'Link accounts' })).to.be .null }) }) diff --git a/services/web/test/frontend/features/settings/components/emails/emails-section-add-new-email.test.tsx b/services/web/test/frontend/features/settings/components/emails/emails-section-add-new-email.test.tsx index b2df8b40eb..63021ca26e 100644 --- a/services/web/test/frontend/features/settings/components/emails/emails-section-add-new-email.test.tsx +++ b/services/web/test/frontend/features/settings/components/emails/emails-section-add-new-email.test.tsx @@ -284,7 +284,7 @@ describe('', function () { target: { value: 'user@autocomplete.edu' }, }) - await screen.findByRole('button', { name: 'Link Accounts and Add Email' }) + await screen.findByRole('button', { name: 'Link accounts and add email' }) }) it('adds new email address with existing institution and custom departments', async function () { diff --git a/services/web/test/frontend/features/settings/components/emails/reconfirmation-info.test.tsx b/services/web/test/frontend/features/settings/components/emails/reconfirmation-info.test.tsx index d18039a280..801b864cd4 100644 --- a/services/web/test/frontend/features/settings/components/emails/reconfirmation-info.test.tsx +++ b/services/web/test/frontend/features/settings/components/emails/reconfirmation-info.test.tsx @@ -97,7 +97,7 @@ describe('', function () { it('redirects to SAML flow', async function () { renderReconfirmationInfo(inReconfirmUserData) const confirmButton = screen.getByRole('button', { - name: 'Confirm Affiliation', + name: 'Confirm affiliation', }) as HTMLButtonElement await waitFor(() => { @@ -127,7 +127,7 @@ describe('', function () { it('sends and resends confirmation email', async function () { renderReconfirmationInfo(inReconfirmUserData) const confirmButton = (await screen.findByRole('button', { - name: 'Confirm Affiliation', + name: 'Confirm affiliation', })) as HTMLButtonElement await waitFor(() => { diff --git a/services/web/test/frontend/features/settings/components/leave-section.test.tsx b/services/web/test/frontend/features/settings/components/leave-section.test.tsx index 80d068a619..ddf67ac11f 100644 --- a/services/web/test/frontend/features/settings/components/leave-section.test.tsx +++ b/services/web/test/frontend/features/settings/components/leave-section.test.tsx @@ -23,7 +23,7 @@ describe('', function () { }) fireEvent.click(button) - await screen.findByText('Delete Account') + await screen.findByText('Delete account') }) it('closes modal', async function () { @@ -40,6 +40,6 @@ describe('', function () { fireEvent.click(cancelButton) - await waitForElementToBeRemoved(() => screen.getByText('Delete Account')) + await waitForElementToBeRemoved(() => screen.getByText('Delete account')) }) }) diff --git a/services/web/test/frontend/features/settings/components/linking-section.test.tsx b/services/web/test/frontend/features/settings/components/linking-section.test.tsx index 056273ccc8..92958f7a07 100644 --- a/services/web/test/frontend/features/settings/components/linking-section.test.tsx +++ b/services/web/test/frontend/features/settings/components/linking-section.test.tsx @@ -66,7 +66,7 @@ describe('', function () { it('lists SSO providers', async function () { renderSectionWithProviders() - screen.getByText('linked accounts') + screen.getByText('Linked accounts') screen.getByText('Google') screen.getByText('Log in with Google.') @@ -94,6 +94,6 @@ describe('', function () { window.metaAttributesCache.delete('ol-oauthProviders') renderSectionWithProviders() - expect(screen.queryByText('linked accounts')).to.not.exist + expect(screen.queryByText('Linked accounts')).to.not.exist }) }) diff --git a/services/web/test/frontend/features/settings/components/password-section.test.tsx b/services/web/test/frontend/features/settings/components/password-section.test.tsx index 63f151a3df..5f4e0b01f5 100644 --- a/services/web/test/frontend/features/settings/components/password-section.test.tsx +++ b/services/web/test/frontend/features/settings/components/password-section.test.tsx @@ -186,7 +186,7 @@ describe('', function () { it('shows message when user cannot use password log in', async function () { window.metaAttributesCache.set('ol-cannot-change-password', true) render() - await screen.findByRole('heading', { name: 'Change Password' }) + await screen.findByRole('heading', { name: 'Change password' }) screen.getByText( 'You can’t add or change your password because your group or organization uses', { exact: false } diff --git a/services/web/test/frontend/features/settings/components/root.test.tsx b/services/web/test/frontend/features/settings/components/root.test.tsx index ea4de26aa1..f3f2f80639 100644 --- a/services/web/test/frontend/features/settings/components/root.test.tsx +++ b/services/web/test/frontend/features/settings/components/root.test.tsx @@ -36,11 +36,11 @@ describe('', function () { render() await waitFor(() => { - screen.getByText('Account Settings') + screen.getByText('Account settings') }) - screen.getByText('Emails and Affiliations') - screen.getByText('Update Account Info') - screen.getByText('Change Password') + screen.getByText('Emails and affiliations') + screen.getByText('Update account info') + screen.getByText('Change password') screen.getByText('Integrations') screen.getByText('Overleaf Beta Program') screen.getByText('Sessions') @@ -58,11 +58,11 @@ describe('', function () { render() await waitFor(() => { - screen.getByText('Account Settings') + screen.getByText('Account settings') }) - expect(screen.queryByText('Emails and Affiliations')).to.not.exist - screen.getByText('Update Account Info') - screen.getByText('Change Password') + expect(screen.queryByText('Emails and affiliations')).to.not.exist + screen.getByText('Update account info') + screen.getByText('Change password') screen.getByText('Integrations') expect(screen.queryByText('Overleaf Beta Program')).to.not.exist screen.getByText('Sessions') 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 91a64581da..88f3482c4b 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 @@ -181,7 +181,7 @@ describe('', function () { await screen.findByText( 'This project is public and can be edited by anyone with the URL.' ) - await screen.findByRole('button', { name: 'Make Private' }) + await screen.findByRole('button', { name: 'Make private' }) }) it('handles legacy access level "readOnly"', async function () { @@ -192,7 +192,7 @@ describe('', function () { await screen.findByText( 'This project is public and can be viewed but not edited by anyone with the URL' ) - await screen.findByRole('button', { name: 'Make Private' }) + await screen.findByRole('button', { name: 'Make private' }) }) it('displays actions for project-owners', async function () { diff --git a/services/web/test/frontend/features/subscription/components/dashboard/personal-subscription.test.tsx b/services/web/test/frontend/features/subscription/components/dashboard/personal-subscription.test.tsx index 6f09d66ab8..8edc881caa 100644 --- a/services/web/test/frontend/features/subscription/components/dashboard/personal-subscription.test.tsx +++ b/services/web/test/frontend/features/subscription/components/dashboard/personal-subscription.test.tsx @@ -37,13 +37,13 @@ describe('', function () { }) describe('custom subscription', function () { - it('displays contact support message', function () { + it('displays contact Support message', function () { renderWithSubscriptionDashContext(, { metaTags: [{ name: 'ol-subscription', value: customSubscription }], }) screen.getByText('Please', { exact: false }) - screen.getByText('contact support', { exact: false }) + screen.getByText('contact Support', { exact: false }) screen.getByText('to make changes to your plan', { exact: false }) }) }) diff --git a/services/web/test/frontend/features/subscription/components/dashboard/states/active/active.test.tsx b/services/web/test/frontend/features/subscription/components/dashboard/states/active/active.test.tsx index 4102760cd1..baada41976 100644 --- a/services/web/test/frontend/features/subscription/components/dashboard/states/active/active.test.tsx +++ b/services/web/test/frontend/features/subscription/components/dashboard/states/active/active.test.tsx @@ -102,7 +102,7 @@ describe('', function () { 'If you wish this change to apply before the end of your current billing period, please contact us.' ) - expect(screen.queryByRole('link', { name: 'contact support' })).to.be.null + expect(screen.queryByRole('link', { name: 'contact Support' })).to.be.null expect(screen.queryByText('if you wish to change your group subscription.')) .to.be.null }) @@ -524,9 +524,9 @@ describe('', function () { expect(changePlan).to.be.null }) - it('shows contact support message for group plan change requests', function () { + it('shows contact Support message for group plan change requests', function () { renderActiveSubscription(groupActiveSubscription) - screen.getByRole('link', { name: 'contact support' }) + screen.getByRole('link', { name: 'contact Support' }) screen.getByText('if you wish to change your group subscription.', { exact: false, }) diff --git a/services/web/test/frontend/features/subscription/components/dashboard/subscription-dashboard.test.tsx b/services/web/test/frontend/features/subscription/components/dashboard/subscription-dashboard.test.tsx index 364b09820e..2e44b4b6e4 100644 --- a/services/web/test/frontend/features/subscription/components/dashboard/subscription-dashboard.test.tsx +++ b/services/web/test/frontend/features/subscription/components/dashboard/subscription-dashboard.test.tsx @@ -48,7 +48,7 @@ describe('', function () { expect(text).to.be.null }) - it('does not render the contact support message', function () { + it('does not render the contact Support message', function () { const text = screen.queryByText( `You’re on an Overleaf Paid plan. Contact`, { @@ -60,7 +60,7 @@ describe('', function () { }) describe('Custom subscription', function () { - it('renders the contact support message', function () { + it('renders the contact Support message', function () { renderWithSubscriptionDashContext(, { metaTags: [ { @@ -77,7 +77,7 @@ describe('', function () { screen.getByText(`You’re on an Overleaf Paid plan.`, { exact: false, }) - screen.getByText(`Contact support`, { + screen.getByText(`Contact Support`, { exact: false, }) }) diff --git a/services/web/test/frontend/features/subscription/components/group-invite/has-individual-recurly-subscription.test.tsx b/services/web/test/frontend/features/subscription/components/group-invite/has-individual-recurly-subscription.test.tsx index b5bf4ec97d..fb89b19bf1 100644 --- a/services/web/test/frontend/features/subscription/components/group-invite/has-individual-recurly-subscription.test.tsx +++ b/services/web/test/frontend/features/subscription/components/group-invite/has-individual-recurly-subscription.test.tsx @@ -40,7 +40,7 @@ describe('group invite', function () { fireEvent.click(button) await waitFor(() => { screen.getByText( - 'Something went wrong canceling your subscription. Please contact support.' + 'Something went wrong canceling your subscription. Please contact Support.' ) }) }) diff --git a/services/web/test/smoke/src/steps/100_loadProjectDashboard.js b/services/web/test/smoke/src/steps/100_loadProjectDashboard.js index cea96bfc02..2a60f8d4b3 100644 --- a/services/web/test/smoke/src/steps/100_loadProjectDashboard.js +++ b/services/web/test/smoke/src/steps/100_loadProjectDashboard.js @@ -1,4 +1,4 @@ -const TITLE_REGEX = /Your Projects - .*, Online LaTeX Editor<\/title>/ +const TITLE_REGEX = /<title>Your projects - .*, Online LaTeX Editor<\/title>/ async function run({ request, assertHasStatusCode }) { const response = await request('/project') diff --git a/services/web/test/unit/src/Email/EmailBuilderTests.js b/services/web/test/unit/src/Email/EmailBuilderTests.js index 1f7628bc3f..a8a0dc1ad5 100644 --- a/services/web/test/unit/src/Email/EmailBuilderTests.js +++ b/services/web/test/unit/src/Email/EmailBuilderTests.js @@ -229,7 +229,7 @@ describe('EmailBuilder', function () { describe('HTML email', function () { it('should include a CTA button and a fallback CTA link', function () { const dom = cheerio.load(this.email.html) - const buttonLink = dom('a:contains("Confirm Email")') + const buttonLink = dom('a:contains("Confirm email")') expect(buttonLink.length).to.equal(1) expect(buttonLink.attr('href')).to.equal(this.opts.confirmEmailUrl) const fallback = dom('.force-overleaf-style').last() @@ -592,7 +592,7 @@ describe('EmailBuilder', function () { describe('HTML email', function () { it('should include a CTA button and a fallback CTA link', function () { - const buttonLink = this.dom('a:contains("Confirm Email")') + const buttonLink = this.dom('a:contains("Confirm email")') expect(buttonLink.length).to.equal(1) expect(buttonLink.attr('href')).to.equal(this.opts.confirmEmailUrl) const fallback = this.dom('.force-overleaf-style').last() From d6cd0417046ae4561906a2c83f0bb2fe072723c0 Mon Sep 17 00:00:00 2001 From: Jimmy Domagala-Tang <Jimmy.Domagala-Tang@overleaf.com> Date: Wed, 21 May 2025 09:39:15 -0400 Subject: [PATCH 188/194] Merge pull request #25505 from overleaf/jdt-redirect-to-wf-based-on-prem-src Manage on Writefull should provide the right instructions based on premiumSource GitOrigin-RevId: bc6dcc5962d18220c445315acbb3b4040ff23d5d --- .../Features/Subscription/FeaturesUpdater.js | 10 ------- .../Subscription/SubscriptionController.js | 13 ++++---- .../web/app/src/Features/User/UserGetter.js | 22 +++++++++++++- .../views/subscriptions/dashboard-react.pug | 1 + .../web/frontend/extracted-translations.json | 3 +- .../writefull-bundle-management-modal.tsx | 30 ++++++++++++------- services/web/frontend/js/utils/meta.ts | 2 ++ services/web/locales/en.json | 3 +- .../SubscriptionControllerTests.js | 4 ++- 9 files changed, 59 insertions(+), 29 deletions(-) diff --git a/services/web/app/src/Features/Subscription/FeaturesUpdater.js b/services/web/app/src/Features/Subscription/FeaturesUpdater.js index eff88d45fb..a8c27f705f 100644 --- a/services/web/app/src/Features/Subscription/FeaturesUpdater.js +++ b/services/web/app/src/Features/Subscription/FeaturesUpdater.js @@ -197,14 +197,6 @@ async function doSyncFromV1(v1UserId) { return refreshFeatures(user._id, 'sync-v1') } -async function hasFeaturesViaWritefull(userId) { - const user = await UserGetter.promises.getUser(userId, { - _id: 1, - writefull: 1, - }) - return Boolean(user?.writefull?.isPremium) -} - module.exports = { featuresEpochIsCurrent, computeFeatures: callbackify(computeFeatures), @@ -217,12 +209,10 @@ module.exports = { 'featuresChanged', ]), scheduleRefreshFeatures: callbackify(scheduleRefreshFeatures), - hasFeaturesViaWritefull: callbackify(hasFeaturesViaWritefull), promises: { computeFeatures, refreshFeatures, scheduleRefreshFeatures, doSyncFromV1, - hasFeaturesViaWritefull, }, } diff --git a/services/web/app/src/Features/Subscription/SubscriptionController.js b/services/web/app/src/Features/Subscription/SubscriptionController.js index 6116a71174..db278b23c0 100644 --- a/services/web/app/src/Features/Subscription/SubscriptionController.js +++ b/services/web/app/src/Features/Subscription/SubscriptionController.js @@ -26,6 +26,7 @@ const { AI_ADD_ON_CODE } = require('./PaymentProviderEntities') const PlansLocator = require('./PlansLocator') const PaymentProviderEntities = require('./PaymentProviderEntities') const { User } = require('../../models/User') +const UserGetter = require('../User/UserGetter') /** * @import { SubscriptionChangeDescription } from '../../../../types/subscription/subscription-change-preview' @@ -154,9 +155,10 @@ async function userSubscriptionPage(req, res) { 'Failed to list groups with group settings enabled for advertising' ) } - - const hasAiAssistViaWritefull = - await FeaturesUpdater.promises.hasFeaturesViaWritefull(user._id) + const { + isPremium: hasAiAssistViaWritefull, + premiumSource: aiAssistViaWritefullSource, + } = await UserGetter.promises.getWritefullData(user._id) const data = { title: 'your_subscription', @@ -181,6 +183,7 @@ async function userSubscriptionPage(req, res) { isManagedAccount: !!req.managedBy, userRestrictions: Array.from(req.userRestrictions || []), hasAiAssistViaWritefull, + aiAssistViaWritefullSource, } res.render('subscriptions/dashboard-react', data) } @@ -346,8 +349,8 @@ async function previewAddonPurchase(req, res) { subscriptionChange = await SubscriptionHandler.promises.previewAddonPurchase(userId, addOnCode) - const hasAiAssistViaWritefull = - await FeaturesUpdater.promises.hasFeaturesViaWritefull(userId) + const { isPremium: hasAiAssistViaWritefull } = + await UserGetter.promises.getWritefullData(userId) const isAiUpgrade = PaymentProviderEntities.subscriptionChangeIsAiAssistUpgrade( subscriptionChange diff --git a/services/web/app/src/Features/User/UserGetter.js b/services/web/app/src/Features/User/UserGetter.js index 9be0c64b9a..fa46492e04 100644 --- a/services/web/app/src/Features/User/UserGetter.js +++ b/services/web/app/src/Features/User/UserGetter.js @@ -137,6 +137,19 @@ async function getSsoUsersAtInstitution(institutionId, projection) { ).exec() } +async function getWritefullData(userId) { + const user = await UserGetter.promises.getUser(userId, { + writefull: 1, + }) + if (!user) { + throw new Error('user not found') + } + return { + isPremium: Boolean(user?.writefull?.isPremium), + premiumSource: user.writefull.premiumSource || null, + } +} + const UserGetter = { getSsoUsersAtInstitution: callbackify(getSsoUsersAtInstitution), @@ -271,6 +284,7 @@ const UserGetter = { callback(error) }) }, + getWritefullData: callbackify(getWritefullData), } const decorateFullEmails = ( @@ -346,10 +360,16 @@ const decorateFullEmails = ( } UserGetter.promises = promisifyAll(UserGetter, { - without: ['getSsoUsersAtInstitution', 'getUserFullEmails', 'getUserFeatures'], + without: [ + 'getSsoUsersAtInstitution', + 'getUserFullEmails', + 'getUserFeatures', + 'getWritefullData', + ], }) UserGetter.promises.getUserFullEmails = getUserFullEmails UserGetter.promises.getSsoUsersAtInstitution = getSsoUsersAtInstitution UserGetter.promises.getUserFeatures = getUserFeatures +UserGetter.promises.getWritefullData = getWritefullData module.exports = UserGetter diff --git a/services/web/app/views/subscriptions/dashboard-react.pug b/services/web/app/views/subscriptions/dashboard-react.pug index dab505e4e5..d6a1bff49c 100644 --- a/services/web/app/views/subscriptions/dashboard-react.pug +++ b/services/web/app/views/subscriptions/dashboard-react.pug @@ -23,6 +23,7 @@ block append meta meta(name="ol-showGroupDiscount" data-type="boolean", content=showGroupDiscount) meta(name="ol-groupSettingsEnabledFor" data-type="json" content=groupSettingsEnabledFor) meta(name="ol-hasAiAssistViaWritefull" data-type="boolean", content=hasAiAssistViaWritefull) + meta(name="ol-aiAssistViaWritefullSource" data-type="string", content=aiAssistViaWritefullSource) meta(name="ol-user" data-type="json" content=user) if (personalSubscription && personalSubscription.payment) meta(name="ol-recurlyApiKey" content=settings.apis.recurly.publicKey) diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json index a8fd6b8169..c64817b94c 100644 --- a/services/web/frontend/extracted-translations.json +++ b/services/web/frontend/extracted-translations.json @@ -104,7 +104,8 @@ "aggregate_to": "", "agree": "", "agree_with_the_terms": "", - "ai_assist_in_overleaf_is_included_via_writefull": "", + "ai_assist_in_overleaf_is_included_via_writefull_groups": "", + "ai_assist_in_overleaf_is_included_via_writefull_individual": "", "ai_assistance_to_help_you": "", "ai_based_language_tools": "", "ai_can_make_mistakes": "", diff --git a/services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/modals/writefull-bundle-management-modal.tsx b/services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/modals/writefull-bundle-management-modal.tsx index 7d7c971197..d5a615821b 100644 --- a/services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/modals/writefull-bundle-management-modal.tsx +++ b/services/web/frontend/js/features/subscription/components/dashboard/states/active/change-plan/modals/writefull-bundle-management-modal.tsx @@ -13,14 +13,18 @@ import { Dropdown, DropdownMenu, DropdownToggle } from 'react-bootstrap' import OLDropdownMenuItem from '@/features/ui/components/ol/ol-dropdown-menu-item' import MaterialIcon from '@/shared/components/material-icon' import { ADD_ON_NAME } from '@/features/subscription/data/add-on-codes' +import getMeta from '@/utils/meta' function WritefullBundleManagementModal() { const modalId: SubscriptionDashModalIds = 'manage-on-writefull' const { t } = useTranslation() const { handleCloseModal, modalIdShown } = useSubscriptionDashboardContext() + const aiAssistViaWritefullSource = getMeta('ol-aiAssistViaWritefullSource') if (modalIdShown !== modalId) return null + const individualWFSubscription = aiAssistViaWritefullSource === 'individual' + return ( <OLModal id={modalId} @@ -34,22 +38,28 @@ function WritefullBundleManagementModal() { </OLModalHeader> <OLModalBody> - <p>{t('ai_assist_in_overleaf_is_included_via_writefull')}</p> + <p> + {individualWFSubscription + ? t('ai_assist_in_overleaf_is_included_via_writefull_individual') + : t('ai_assist_in_overleaf_is_included_via_writefull_groups')} + </p> </OLModalBody> <OLModalFooter> <OLButton variant="secondary" onClick={handleCloseModal}> {t('back')} </OLButton> - <OLButton - variant="primary" - onClick={handleCloseModal} - href="https://my.writefull.com/account" - target="_blank" - rel="noreferrer" - > - {t('go_to_writefull')} - </OLButton> + {individualWFSubscription && ( + <OLButton + variant="primary" + onClick={handleCloseModal} + href="https://my.writefull.com/account" + target="_blank" + rel="noreferrer" + > + {t('go_to_writefull')} + </OLButton> + )} </OLModalFooter> </OLModal> ) diff --git a/services/web/frontend/js/utils/meta.ts b/services/web/frontend/js/utils/meta.ts index 2e0e40c49d..9461635625 100644 --- a/services/web/frontend/js/utils/meta.ts +++ b/services/web/frontend/js/utils/meta.ts @@ -59,6 +59,7 @@ export interface Meta { string, { annual: string; monthly: string; annualDividedByTwelve: string } > + 'ol-aiAssistViaWritefullSource': string 'ol-allInReconfirmNotificationPeriods': UserEmailData[] 'ol-allowedExperiments': string[] 'ol-allowedImageNames': AllowedImageName[] @@ -81,6 +82,7 @@ export interface Meta { 'ol-cannot-reactivate-subscription': boolean 'ol-cannot-use-ai': boolean 'ol-chatEnabled': boolean + 'ol-compilesUserContentDomain': string 'ol-countryCode': PricingFormState['country'] 'ol-couponCode': PricingFormState['coupon'] diff --git a/services/web/locales/en.json b/services/web/locales/en.json index 5e747b59f8..4729f54756 100644 --- a/services/web/locales/en.json +++ b/services/web/locales/en.json @@ -125,7 +125,8 @@ "aggregate_to": "to", "agree": "Agree", "agree_with_the_terms": "I agree with the Overleaf terms", - "ai_assist_in_overleaf_is_included_via_writefull": "AI Assist in Overleaf is included as part of your Writefull subscription. You can cancel or manage your access to AI Assist in your Writefull subscription settings.", + "ai_assist_in_overleaf_is_included_via_writefull_groups": "AI Assist in Overleaf is included as part of your group or organization’s Writefull subscription. To make changes you’ll need to speak to your subscription admin", + "ai_assist_in_overleaf_is_included_via_writefull_individual": "AI Assist in Overleaf is included as part of your Writefull subscription. You can cancel or manage your access to AI Assist in your Writefull subscription settings.", "ai_assistance_to_help_you": "AI assistance to help you fix LaTeX errors", "ai_based_language_tools": "AI-based language tools tailored to research writing", "ai_can_make_mistakes": "AI can make mistakes. Review fixes before you apply them.", diff --git a/services/web/test/unit/src/Subscription/SubscriptionControllerTests.js b/services/web/test/unit/src/Subscription/SubscriptionControllerTests.js index 5b6e86663e..b3ae6610e1 100644 --- a/services/web/test/unit/src/Subscription/SubscriptionControllerTests.js +++ b/services/web/test/unit/src/Subscription/SubscriptionControllerTests.js @@ -127,6 +127,9 @@ describe('SubscriptionController', function () { getUser: sinon.stub().callsArgWith(2, null, this.user), promises: { getUser: sinon.stub().resolves(this.user), + getWritefullData: sinon + .stub() + .resolves({ isPremium: false, premiumSource: null }), }, } this.SplitTestV2Hander = { @@ -157,7 +160,6 @@ describe('SubscriptionController', function () { }, './FeaturesUpdater': (this.FeaturesUpdater = { promises: { - hasFeaturesViaWritefull: sinon.stub().resolves(false), refreshFeatures: sinon.stub().resolves({ features: {} }), }, }), From 23e24627d56eb99a0897889287743178b6f0f2ff Mon Sep 17 00:00:00 2001 From: Jimmy Domagala-Tang <Jimmy.Domagala-Tang@overleaf.com> Date: Wed, 21 May 2025 10:35:13 -0400 Subject: [PATCH 189/194] Merge pull request #25821 from overleaf/jdt-handle-no-prem-src Handle cases where Writefull is not defined on a user on the subscription page GitOrigin-RevId: ef43da630b5194f6021ebfc52ca4cb473e674b23 --- services/web/app/src/Features/User/UserGetter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/services/web/app/src/Features/User/UserGetter.js b/services/web/app/src/Features/User/UserGetter.js index fa46492e04..bce4568880 100644 --- a/services/web/app/src/Features/User/UserGetter.js +++ b/services/web/app/src/Features/User/UserGetter.js @@ -146,7 +146,7 @@ async function getWritefullData(userId) { } return { isPremium: Boolean(user?.writefull?.isPremium), - premiumSource: user.writefull.premiumSource || null, + premiumSource: user?.writefull?.premiumSource || null, } } From c18b3f95b227c8dce355a8204ee13e8b9b7b528f Mon Sep 17 00:00:00 2001 From: Eric Mc Sween <5454374+emcsween@users.noreply.github.com> Date: Wed, 21 May 2025 11:34:01 -0400 Subject: [PATCH 190/194] Merge pull request #25492 from overleaf/em-paginate-changes-1 Paginate history changes endpoint GitOrigin-RevId: 2b48044d64244404efcd2e090b682c1f571a5567 --- .../history-v1/api/controllers/projects.js | 32 ++--- .../storage/lib/chunk_store/index.js | 6 +- .../test/acceptance/js/api/projects.test.js | 132 ++++++++++-------- .../src/Features/History/HistoryController.js | 34 ++++- .../js/infrastructure/project-snapshot.ts | 49 ++++++- .../infrastructure/project-snapshot.test.ts | 12 +- 6 files changed, 176 insertions(+), 89 deletions(-) diff --git a/services/history-v1/api/controllers/projects.js b/services/history-v1/api/controllers/projects.js index b5c3b629a9..47a1d959ad 100644 --- a/services/history-v1/api/controllers/projects.js +++ b/services/history-v1/api/controllers/projects.js @@ -152,29 +152,27 @@ async function getChanges(req, res, next) { }) } - const changes = [] - let chunk = await chunkStore.loadLatest(projectId) - - if (since > chunk.getEndVersion()) { - return res.status(400).json({ - error: `Version out of bounds: ${since}`, + let chunk + try { + chunk = await chunkStore.loadAtVersion(projectId, since, { + preferNewer: true, }) + } catch (err) { + if (err instanceof Chunk.VersionNotFoundError) { + return res.status(400).json({ + error: `Version out of bounds: ${since}`, + }) + } + throw err } - // Fetch all chunks that come after the chunk that contains the start version - while (chunk.getStartVersion() > since) { - const changesInChunk = chunk.getChanges() - changes.unshift(...changesInChunk) - chunk = await chunkStore.loadAtVersion(projectId, chunk.getStartVersion()) - } + const latestChunkMetadata = await chunkStore.getLatestChunkMetadata(projectId) // Extract the relevant changes from the chunk that contains the start version - const changesInChunk = chunk - .getChanges() - .slice(since - chunk.getStartVersion()) - changes.unshift(...changesInChunk) + const changes = chunk.getChanges().slice(since - chunk.getStartVersion()) + const hasMore = latestChunkMetadata.endVersion > chunk.getEndVersion() - res.json(changes.map(change => change.toRaw())) + res.json({ changes: changes.map(change => change.toRaw()), hasMore }) } async function getZip(req, res, next) { diff --git a/services/history-v1/storage/lib/chunk_store/index.js b/services/history-v1/storage/lib/chunk_store/index.js index b026a073d4..6dab84f929 100644 --- a/services/history-v1/storage/lib/chunk_store/index.js +++ b/services/history-v1/storage/lib/chunk_store/index.js @@ -141,6 +141,8 @@ async function loadLatest(projectId, opts = {}) { * @param {number} version * @param {object} [opts] * @param {boolean} [opts.persistedOnly] - only include persisted changes + * @param {boolean} [opts.preferNewer] - If the version is at the boundary of + * two chunks, return the newer chunk. */ async function loadAtVersion(projectId, version, opts = {}) { assert.projectId(projectId, 'bad projectId') @@ -150,7 +152,9 @@ async function loadAtVersion(projectId, version, opts = {}) { const blobStore = new BlobStore(projectId) const batchBlobStore = new BatchBlobStore(blobStore) - const chunkRecord = await backend.getChunkForVersion(projectId, version) + const chunkRecord = await backend.getChunkForVersion(projectId, version, { + preferNewer: opts.preferNewer, + }) const rawHistory = await historyStore.loadRaw(projectId, chunkRecord.id) const history = History.fromRaw(rawHistory) diff --git a/services/history-v1/test/acceptance/js/api/projects.test.js b/services/history-v1/test/acceptance/js/api/projects.test.js index 3c333d8698..7654829516 100644 --- a/services/history-v1/test/acceptance/js/api/projects.test.js +++ b/services/history-v1/test/acceptance/js/api/projects.test.js @@ -10,7 +10,7 @@ const cleanup = require('../storage/support/cleanup') const fixtures = require('../storage/support/fixtures') const testFiles = require('../storage/support/test_files') -const { zipStore, persistChanges } = require('../../../../storage') +const { zipStore, BlobStore, persistChanges } = require('../../../../storage') const { expectHttpError } = require('./support/expect_response') const testServer = require('./support/test_server') @@ -155,12 +155,13 @@ describe('project controller', function () { project_id: projectId, }) expect(response.status).to.equal(HTTPStatus.OK) - const changes = response.obj + const { changes, hasMore } = response.obj expect(changes.length).to.equal(2) const filenames = changes .flatMap(change => change.operations) .map(operation => operation.pathname) expect(filenames).to.deep.equal(['test.tex', 'other.tex']) + expect(hasMore).to.be.false }) it('returns only requested changes', async function () { @@ -170,12 +171,13 @@ describe('project controller', function () { since: 1, }) expect(response.status).to.equal(HTTPStatus.OK) - const changes = response.obj + const { changes, hasMore } = response.obj expect(changes.length).to.equal(1) const filenames = changes .flatMap(change => change.operations) .map(operation => operation.pathname) expect(filenames).to.deep.equal(['other.tex']) + expect(hasMore).to.be.false }) it('rejects negative versions', async function () { @@ -196,68 +198,84 @@ describe('project controller', function () { ).to.be.rejectedWith('Bad Request') }) }) - }) - describe('project with many chunks', function () { - let projectId + describe('project with many chunks', function () { + let projectId, changes - beforeEach(async function () { - // used to provide a limit which forces us to persist all of the changes. - const farFuture = new Date() - farFuture.setTime(farFuture.getTime() + 7 * 24 * 3600 * 1000) - const limits = { - minChangeTimestamp: farFuture, - maxChangeTimestamp: farFuture, - maxChunkChanges: 5, - } - const changes = [ - new Change( - [new AddFileOperation('test.tex', File.fromString(''))], - new Date(), - [] - ), - ] - - for (let i = 0; i < 20; i++) { - const textOperation = new TextOperation() - textOperation.retain(i) - textOperation.insert('x') - changes.push( + beforeEach(async function () { + // used to provide a limit which forces us to persist all of the changes. + const farFuture = new Date() + farFuture.setTime(farFuture.getTime() + 7 * 24 * 3600 * 1000) + const limits = { + minChangeTimestamp: farFuture, + maxChangeTimestamp: farFuture, + maxChunkChanges: 5, + } + projectId = await createEmptyProject() + const blobStore = new BlobStore(projectId) + const blob = await blobStore.putString('') + changes = [ new Change( - [new EditFileOperation('test.tex', textOperation)], + [new AddFileOperation('test.tex', File.createLazyFromBlobs(blob))], new Date(), [] + ), + ] + + for (let i = 0; i < 20; i++) { + const textOperation = new TextOperation() + textOperation.retain(i) + textOperation.insert('x') + changes.push( + new Change( + [new EditFileOperation('test.tex', textOperation)], + new Date(), + [] + ) ) - ) - } - - projectId = await createEmptyProject() - await persistChanges(projectId, changes, limits, 0) - }) - - it('returns all changes when not given a limit', async function () { - const response = await testServer.basicAuthClient.apis.Project.getChanges( - { - project_id: projectId, } - ) - expect(response.status).to.equal(HTTPStatus.OK) - const changes = response.obj - expect(changes.length).to.equal(21) - expect(changes[10].operations[0].textOperation).to.deep.equal([9, 'x']) - }) - it('returns only requested changes', async function () { - const response = await testServer.basicAuthClient.apis.Project.getChanges( - { - project_id: projectId, - since: 10, - } - ) - expect(response.status).to.equal(HTTPStatus.OK) - const changes = response.obj - expect(changes.length).to.equal(11) - expect(changes[2].operations[0].textOperation).to.deep.equal([11, 'x']) + await persistChanges(projectId, changes, limits, 0) + }) + + it('returns the first chunk when not given a limit', async function () { + const response = + await testServer.basicAuthClient.apis.Project.getChanges({ + project_id: projectId, + }) + + expect(response.status).to.equal(HTTPStatus.OK) + expect(response.obj).to.deep.equal({ + changes: changes.slice(0, 5).map(c => c.toRaw()), + hasMore: true, + }) + }) + + it('returns only requested changes', async function () { + const response = + await testServer.basicAuthClient.apis.Project.getChanges({ + project_id: projectId, + since: 12, + }) + expect(response.status).to.equal(HTTPStatus.OK) + expect(response.obj).to.deep.equal({ + changes: changes.slice(12, 15).map(c => c.toRaw()), + hasMore: true, + }) + }) + + it('returns changes in the latest chunk', async function () { + const response = + await testServer.basicAuthClient.apis.Project.getChanges({ + project_id: projectId, + since: 20, + }) + expect(response.status).to.equal(HTTPStatus.OK) + expect(response.obj).to.deep.equal({ + changes: changes.slice(20).map(c => c.toRaw()), + hasMore: false, + }) + }) }) }) diff --git a/services/web/app/src/Features/History/HistoryController.js b/services/web/app/src/Features/History/HistoryController.js index a0f0183f44..be2a44c39e 100644 --- a/services/web/app/src/Features/History/HistoryController.js +++ b/services/web/app/src/Features/History/HistoryController.js @@ -465,9 +465,37 @@ async function getLatestHistory(req, res, next) { async function getChanges(req, res, next) { const projectId = req.params.project_id - const since = req.query.since - const changes = await HistoryManager.promises.getChanges(projectId, { since }) - res.json(changes) + let since = req.query.since + // TODO: Transition flag; remove after a while + const paginated = req.query.paginated === 'true' + + if (paginated) { + const changes = await HistoryManager.promises.getChanges(projectId, { + since, + }) + return res.json(changes) + } else { + // TODO: Remove this code path after a while + let hasMore = true + const allChanges = [] + while (hasMore) { + const response = await HistoryManager.promises.getChanges(projectId, { + since, + }) + + let changes + if (Array.isArray(response)) { + changes = response + hasMore = false + } else { + changes = response.changes + hasMore = response.hasMore + since += changes.length + } + allChanges.push(...changes) + } + return res.json(allChanges) + } } function isPrematureClose(err) { diff --git a/services/web/frontend/js/infrastructure/project-snapshot.ts b/services/web/frontend/js/infrastructure/project-snapshot.ts index eb7c768adf..976eb9d63f 100644 --- a/services/web/frontend/js/infrastructure/project-snapshot.ts +++ b/services/web/frontend/js/infrastructure/project-snapshot.ts @@ -124,9 +124,15 @@ export class ProjectSnapshot { */ private async loadChanges() { await flushHistory(this.projectId) - const changes = await fetchLatestChanges(this.projectId, this.version) - this.snapshot.applyAll(changes) - this.version += changes.length + let hasMore = true + while (hasMore) { + const response = await fetchLatestChanges(this.projectId, this.version) + const changes = response.changes + this.snapshot.applyAll(changes) + this.version += changes.length + hasMore = response.hasMore + } + await this.loadDocs() } @@ -181,14 +187,43 @@ async function fetchLatestChunk(projectId: string): Promise<Chunk> { return Chunk.fromRaw(response.chunk) } +type FetchLatestChangesResponse = { + changes: Change[] + hasMore: boolean +} + +type FetchLatestChangesApiResponse = + | RawChange[] + | { + changes: RawChange[] + hasMore: boolean + } + async function fetchLatestChanges( projectId: string, version: number -): Promise<Change[]> { - const response = await getJSON<RawChange[]>( - `/project/${projectId}/changes?since=${version}` +): Promise<FetchLatestChangesResponse> { + // TODO: The paginated flag is a transition flag. It can be removed after this + // code has been deployed for a few weeks. + const response = await getJSON<FetchLatestChangesApiResponse>( + `/project/${projectId}/changes?since=${version}&paginated=true` ) - return response.map(Change.fromRaw).filter(change => change != null) + + let changes, hasMore + if (Array.isArray(response)) { + // deprecated response format is a simple array of changes + // TODO: Remove this branch after the transition + changes = response + hasMore = false + } else { + changes = response.changes + hasMore = response.hasMore + } + + return { + changes: changes.map(Change.fromRaw).filter(change => change != null), + hasMore, + } } async function fetchBlob(projectId: string, hash: string): Promise<string> { diff --git a/services/web/test/frontend/infrastructure/project-snapshot.test.ts b/services/web/test/frontend/infrastructure/project-snapshot.test.ts index 313436a3f5..b3a78efed7 100644 --- a/services/web/test/frontend/infrastructure/project-snapshot.test.ts +++ b/services/web/test/frontend/infrastructure/project-snapshot.test.ts @@ -119,10 +119,14 @@ describe('ProjectSnapshot', function () { } function mockChanges() { - fetchMock.getOnce(`/project/${projectId}/changes?since=1`, changes, { - name: 'changes-1', - }) - fetchMock.get(`/project/${projectId}/changes?since=2`, [], { + fetchMock.getOnce( + `/project/${projectId}/changes?since=1&paginated=true`, + changes, + { + name: 'changes-1', + } + ) + fetchMock.get(`/project/${projectId}/changes?since=2&paginated=true`, [], { name: 'changes-2', }) } From f86eb6208f576b2b75223f0781438e37c58aa49a Mon Sep 17 00:00:00 2001 From: Rebeka Dekany <50901361+rebekadekany@users.noreply.github.com> Date: Wed, 21 May 2025 18:20:28 +0200 Subject: [PATCH 191/194] Filter out link-sharing token sent to Sentry (#25787) GitOrigin-RevId: 647f1a2e20e7883f7ab9c862bb0d90cf96360c24 --- .../frontend/js/infrastructure/error-reporter.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/services/web/frontend/js/infrastructure/error-reporter.ts b/services/web/frontend/js/infrastructure/error-reporter.ts index a21a376594..5d5734535a 100644 --- a/services/web/frontend/js/infrastructure/error-reporter.ts +++ b/services/web/frontend/js/infrastructure/error-reporter.ts @@ -73,6 +73,19 @@ function sentryReporter() { return null // Block the event from sending } + // Do not send link-sharing token to Sentry + if (event.request?.headers?.Referer) { + const refererUrl = new URL(event.request.headers.Referer) + + if ( + refererUrl.hostname === location.hostname && + refererUrl.pathname.startsWith('/read/') + ) { + refererUrl.pathname = '/read/' + event.request.headers.Referer = refererUrl.toString() + } + } + return event }, }) From 88d3186dc1993babb891912face4bbaf98bfaaa5 Mon Sep 17 00:00:00 2001 From: Miguel Serrano <mserranom@users.noreply.github.com> Date: Thu, 22 May 2025 10:01:12 +0200 Subject: [PATCH 192/194] Merge pull request #25792 from overleaf/msm-bump-multer Bump `multer` to v2.0.0 GitOrigin-RevId: 688bfd3e6546b18f22ebaef82e4d9dd57b6d40bd --- package-lock.json | 21 +++++++++++---------- package.json | 2 +- services/web/package.json | 2 +- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2a42f5e110..4a14efb544 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30181,9 +30181,9 @@ } }, "node_modules/multer": { - "version": "1.4.5-lts.1", - "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz", - "integrity": "sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/multer/-/multer-2.0.0.tgz", + "integrity": "sha512-bS8rPZurbAuHGAnApbM9d4h1wSoYqrOqkE+6a64KLMK9yWU7gJXBDDVklKQ3TPi9DRb85cRs6yXaC0+cjxRtRg==", "license": "MIT", "dependencies": { "append-field": "^1.0.0", @@ -30195,7 +30195,7 @@ "xtend": "^4.0.0" }, "engines": { - "node": ">= 6.0.0" + "node": ">= 10.16.0" } }, "node_modules/multicast-dns": { @@ -38457,7 +38457,8 @@ "node_modules/swagger-tools/node_modules/path-to-regexp": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.3.0.tgz", - "integrity": "sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==" + "integrity": "sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==", + "license": "MIT" }, "node_modules/symbol-tree": { "version": "3.2.4", @@ -44739,7 +44740,7 @@ "moment": "^2.29.4", "mongodb-legacy": "6.1.3", "mongoose": "8.9.5", - "multer": "overleaf/multer#e1df247fbf8e7590520d20ae3601eaef9f3d2e9e", + "multer": "overleaf/multer#199c5ff05bd375c508f4074498237baead7f5148", "nocache": "^2.1.0", "node-fetch": "^2.7.0", "nodemailer": "^6.7.0", @@ -45658,9 +45659,9 @@ } }, "services/web/node_modules/multer": { - "version": "1.4.5-lts.1", - "resolved": "git+ssh://git@github.com/overleaf/multer.git#e1df247fbf8e7590520d20ae3601eaef9f3d2e9e", - "integrity": "sha512-3fJSnWF3iBZJ6Z9y8AjFVY+O4DUKspxSnzXidb3zCKqBYyEKRrpGp7OXjT9th2gWPd+9u64ZyRWUf+YRYn1GCw==", + "version": "2.0.0", + "resolved": "git+ssh://git@github.com/overleaf/multer.git#199c5ff05bd375c508f4074498237baead7f5148", + "integrity": "sha512-S5MlIoOgrDr+a2jLS8z7jQlbzvZ0m30U2tRwdyLrxhnnMUQZYEzkVysEv10Dw41RTpM5bQQDs563Vzl1LLhxhQ==", "license": "MIT", "dependencies": { "append-field": "^1.0.0", @@ -45672,7 +45673,7 @@ "xtend": "^4.0.0" }, "engines": { - "node": ">= 6.0.0" + "node": ">= 10.16.0" } }, "services/web/node_modules/nise": { diff --git a/package.json b/package.json index 7dc9a63e29..64fbd258ed 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ }, "swagger-tools": { "body-parser": "1.20.3", - "multer": "1.4.5-lts.1", + "multer": "2.0.0", "path-to-regexp": "3.3.0", "qs": "6.13.0" } diff --git a/services/web/package.json b/services/web/package.json index f5f1ec3e96..5080813d55 100644 --- a/services/web/package.json +++ b/services/web/package.json @@ -140,7 +140,7 @@ "moment": "^2.29.4", "mongodb-legacy": "6.1.3", "mongoose": "8.9.5", - "multer": "overleaf/multer#e1df247fbf8e7590520d20ae3601eaef9f3d2e9e", + "multer": "overleaf/multer#199c5ff05bd375c508f4074498237baead7f5148", "nocache": "^2.1.0", "node-fetch": "^2.7.0", "nodemailer": "^6.7.0", From 841e32bb646f4602ec1eebcdd4dcebd8435a00ae Mon Sep 17 00:00:00 2001 From: Christopher Hoskin <mans0954@users.noreply.github.com> Date: Thu, 22 May 2025 09:25:20 +0100 Subject: [PATCH 193/194] Merge pull request #25809 from overleaf/csh-issue-22789-ci-mongo-7 Upgrade the dev environment and CI to mongo 7 GitOrigin-RevId: da02881d142d21be47dac7bd2af74520ba8664cd --- services/chat/docker-compose.ci.yml | 2 +- services/chat/docker-compose.yml | 2 +- services/contacts/docker-compose.ci.yml | 2 +- services/contacts/docker-compose.yml | 2 +- services/docstore/docker-compose.ci.yml | 2 +- services/docstore/docker-compose.yml | 2 +- services/document-updater/docker-compose.ci.yml | 2 +- services/document-updater/docker-compose.yml | 2 +- services/history-v1/docker-compose.ci.yml | 2 +- services/history-v1/docker-compose.yml | 2 +- services/notifications/docker-compose.ci.yml | 2 +- services/notifications/docker-compose.yml | 2 +- services/project-history/docker-compose.ci.yml | 2 +- services/project-history/docker-compose.yml | 2 +- services/web/docker-compose.ci.yml | 2 +- services/web/docker-compose.yml | 2 +- 16 files changed, 16 insertions(+), 16 deletions(-) diff --git a/services/chat/docker-compose.ci.yml b/services/chat/docker-compose.ci.yml index 51eb64d126..8fd86c1fbb 100644 --- a/services/chat/docker-compose.ci.yml +++ b/services/chat/docker-compose.ci.yml @@ -39,7 +39,7 @@ services: command: tar -czf /tmp/build/build.tar.gz --exclude=build.tar.gz --exclude-vcs . user: root mongo: - image: mongo:6.0.13 + image: mongo:7.0.20 command: --replSet overleaf volumes: - ../../bin/shared/mongodb-init-replica-set.js:/docker-entrypoint-initdb.d/mongodb-init-replica-set.js diff --git a/services/chat/docker-compose.yml b/services/chat/docker-compose.yml index 14b739a55e..89a48339bd 100644 --- a/services/chat/docker-compose.yml +++ b/services/chat/docker-compose.yml @@ -42,7 +42,7 @@ services: command: npm run --silent test:acceptance mongo: - image: mongo:6.0.13 + image: mongo:7.0.20 command: --replSet overleaf volumes: - ../../bin/shared/mongodb-init-replica-set.js:/docker-entrypoint-initdb.d/mongodb-init-replica-set.js diff --git a/services/contacts/docker-compose.ci.yml b/services/contacts/docker-compose.ci.yml index 51eb64d126..8fd86c1fbb 100644 --- a/services/contacts/docker-compose.ci.yml +++ b/services/contacts/docker-compose.ci.yml @@ -39,7 +39,7 @@ services: command: tar -czf /tmp/build/build.tar.gz --exclude=build.tar.gz --exclude-vcs . user: root mongo: - image: mongo:6.0.13 + image: mongo:7.0.20 command: --replSet overleaf volumes: - ../../bin/shared/mongodb-init-replica-set.js:/docker-entrypoint-initdb.d/mongodb-init-replica-set.js diff --git a/services/contacts/docker-compose.yml b/services/contacts/docker-compose.yml index 2177ad797e..65e1a578cd 100644 --- a/services/contacts/docker-compose.yml +++ b/services/contacts/docker-compose.yml @@ -42,7 +42,7 @@ services: command: npm run --silent test:acceptance mongo: - image: mongo:6.0.13 + image: mongo:7.0.20 command: --replSet overleaf volumes: - ../../bin/shared/mongodb-init-replica-set.js:/docker-entrypoint-initdb.d/mongodb-init-replica-set.js diff --git a/services/docstore/docker-compose.ci.yml b/services/docstore/docker-compose.ci.yml index a1a9995f60..ff222f6514 100644 --- a/services/docstore/docker-compose.ci.yml +++ b/services/docstore/docker-compose.ci.yml @@ -44,7 +44,7 @@ services: command: tar -czf /tmp/build/build.tar.gz --exclude=build.tar.gz --exclude-vcs . user: root mongo: - image: mongo:6.0.13 + image: mongo:7.0.20 command: --replSet overleaf volumes: - ../../bin/shared/mongodb-init-replica-set.js:/docker-entrypoint-initdb.d/mongodb-init-replica-set.js diff --git a/services/docstore/docker-compose.yml b/services/docstore/docker-compose.yml index 8211e2a9a3..4a4fa2f10c 100644 --- a/services/docstore/docker-compose.yml +++ b/services/docstore/docker-compose.yml @@ -47,7 +47,7 @@ services: command: npm run --silent test:acceptance mongo: - image: mongo:6.0.13 + image: mongo:7.0.20 command: --replSet overleaf volumes: - ../../bin/shared/mongodb-init-replica-set.js:/docker-entrypoint-initdb.d/mongodb-init-replica-set.js diff --git a/services/document-updater/docker-compose.ci.yml b/services/document-updater/docker-compose.ci.yml index 6deaad433d..2fe97bd9b3 100644 --- a/services/document-updater/docker-compose.ci.yml +++ b/services/document-updater/docker-compose.ci.yml @@ -52,7 +52,7 @@ services: retries: 20 mongo: - image: mongo:6.0.13 + image: mongo:7.0.20 command: --replSet overleaf volumes: - ../../bin/shared/mongodb-init-replica-set.js:/docker-entrypoint-initdb.d/mongodb-init-replica-set.js diff --git a/services/document-updater/docker-compose.yml b/services/document-updater/docker-compose.yml index cf9e837c7a..8a94d1a24c 100644 --- a/services/document-updater/docker-compose.yml +++ b/services/document-updater/docker-compose.yml @@ -55,7 +55,7 @@ services: retries: 20 mongo: - image: mongo:6.0.13 + image: mongo:7.0.20 command: --replSet overleaf volumes: - ../../bin/shared/mongodb-init-replica-set.js:/docker-entrypoint-initdb.d/mongodb-init-replica-set.js diff --git a/services/history-v1/docker-compose.ci.yml b/services/history-v1/docker-compose.ci.yml index 60a2ec7bda..0dfe8b99d3 100644 --- a/services/history-v1/docker-compose.ci.yml +++ b/services/history-v1/docker-compose.ci.yml @@ -73,7 +73,7 @@ services: retries: 20 mongo: - image: mongo:6.0.13 + image: mongo:7.0.20 command: --replSet overleaf volumes: - ../../bin/shared/mongodb-init-replica-set.js:/docker-entrypoint-initdb.d/mongodb-init-replica-set.js diff --git a/services/history-v1/docker-compose.yml b/services/history-v1/docker-compose.yml index f1971b5780..b87d859e1e 100644 --- a/services/history-v1/docker-compose.yml +++ b/services/history-v1/docker-compose.yml @@ -81,7 +81,7 @@ services: retries: 20 mongo: - image: mongo:6.0.13 + image: mongo:7.0.20 command: --replSet overleaf volumes: - ../../bin/shared/mongodb-init-replica-set.js:/docker-entrypoint-initdb.d/mongodb-init-replica-set.js diff --git a/services/notifications/docker-compose.ci.yml b/services/notifications/docker-compose.ci.yml index 51eb64d126..8fd86c1fbb 100644 --- a/services/notifications/docker-compose.ci.yml +++ b/services/notifications/docker-compose.ci.yml @@ -39,7 +39,7 @@ services: command: tar -czf /tmp/build/build.tar.gz --exclude=build.tar.gz --exclude-vcs . user: root mongo: - image: mongo:6.0.13 + image: mongo:7.0.20 command: --replSet overleaf volumes: - ../../bin/shared/mongodb-init-replica-set.js:/docker-entrypoint-initdb.d/mongodb-init-replica-set.js diff --git a/services/notifications/docker-compose.yml b/services/notifications/docker-compose.yml index 217bcc8200..090742ff6d 100644 --- a/services/notifications/docker-compose.yml +++ b/services/notifications/docker-compose.yml @@ -42,7 +42,7 @@ services: command: npm run --silent test:acceptance mongo: - image: mongo:6.0.13 + image: mongo:7.0.20 command: --replSet overleaf volumes: - ../../bin/shared/mongodb-init-replica-set.js:/docker-entrypoint-initdb.d/mongodb-init-replica-set.js diff --git a/services/project-history/docker-compose.ci.yml b/services/project-history/docker-compose.ci.yml index 6deaad433d..2fe97bd9b3 100644 --- a/services/project-history/docker-compose.ci.yml +++ b/services/project-history/docker-compose.ci.yml @@ -52,7 +52,7 @@ services: retries: 20 mongo: - image: mongo:6.0.13 + image: mongo:7.0.20 command: --replSet overleaf volumes: - ../../bin/shared/mongodb-init-replica-set.js:/docker-entrypoint-initdb.d/mongodb-init-replica-set.js diff --git a/services/project-history/docker-compose.yml b/services/project-history/docker-compose.yml index 8190d2074c..68360baf44 100644 --- a/services/project-history/docker-compose.yml +++ b/services/project-history/docker-compose.yml @@ -55,7 +55,7 @@ services: retries: 20 mongo: - image: mongo:6.0.13 + image: mongo:7.0.20 command: --replSet overleaf volumes: - ../../bin/shared/mongodb-init-replica-set.js:/docker-entrypoint-initdb.d/mongodb-init-replica-set.js diff --git a/services/web/docker-compose.ci.yml b/services/web/docker-compose.ci.yml index c277cc0e97..164cc22c5a 100644 --- a/services/web/docker-compose.ci.yml +++ b/services/web/docker-compose.ci.yml @@ -88,7 +88,7 @@ services: image: redis mongo: - image: mongo:6.0.13 + image: mongo:7.0.20 logging: driver: none command: --replSet overleaf diff --git a/services/web/docker-compose.yml b/services/web/docker-compose.yml index 2bba008eb8..5314e94ed3 100644 --- a/services/web/docker-compose.yml +++ b/services/web/docker-compose.yml @@ -87,7 +87,7 @@ services: image: redis mongo: - image: mongo:6.0.13 + image: mongo:7.0.20 command: --replSet overleaf volumes: - ../../bin/shared/mongodb-init-replica-set.js:/docker-entrypoint-initdb.d/mongodb-init-replica-set.js From 52f1e463431b6f8f484f920bf7c1129b14687a2b Mon Sep 17 00:00:00 2001 From: Antoine Clausse <antoine.clausse@overleaf.com> Date: Thu, 22 May 2025 10:38:37 +0200 Subject: [PATCH 194/194] Fix casing in E2E tests (Example project, Blank project, ...) (#25812) * Fix casing in Tests (Example project, Blank project, Trashed projects, ...) * Check "Blank Project" case insentively * Fix git-bridge tests GitOrigin-RevId: 52339258016c3a923c6207a65d058cb0d6e9d3a2 --- server-ce/test/admin.spec.ts | 4 ++-- server-ce/test/create-and-compile-project.spec.ts | 4 ++-- server-ce/test/editor.spec.ts | 4 ++-- server-ce/test/git-bridge.spec.ts | 8 ++++---- server-ce/test/helpers/project.ts | 6 +++--- server-ce/test/project-list.spec.ts | 2 +- 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/server-ce/test/admin.spec.ts b/server-ce/test/admin.spec.ts index 8020deeb4b..9031e21b68 100644 --- a/server-ce/test/admin.spec.ts +++ b/server-ce/test/admin.spec.ts @@ -294,7 +294,7 @@ describe('admin panel', function () { cy.log('navigate to thrashed projects and delete the project') cy.get('.project-list-sidebar-scroll').within(() => { - cy.findByText('Trashed Projects').click() + cy.findByText('Trashed projects').click() }) findProjectRow(deletedProjectName).within(() => cy.findByRole('button', { name: 'Delete' }).click() @@ -319,7 +319,7 @@ describe('admin panel', function () { login(user1) cy.visit('/project') cy.get('.project-list-sidebar-scroll').within(() => { - cy.findByText('Trashed Projects').click() + cy.findByText('Trashed projects').click() }) cy.findByText(`${deletedProjectName} (Restored)`) }) diff --git a/server-ce/test/create-and-compile-project.spec.ts b/server-ce/test/create-and-compile-project.spec.ts index 2be4f208e2..959a3be107 100644 --- a/server-ce/test/create-and-compile-project.spec.ts +++ b/server-ce/test/create-and-compile-project.spec.ts @@ -51,7 +51,7 @@ describe('Project creation and compilation', function () { login('user@example.com') createProject(sourceProjectName, { - type: 'Example Project', + type: 'Example project', open: false, }).as('sourceProjectId') createProject(targetProjectName) @@ -79,7 +79,7 @@ describe('Project creation and compilation', function () { const targetProjectName = `${sourceProjectName}-target` login('user@example.com') createProject(sourceProjectName, { - type: 'Example Project', + type: 'Example project', open: false, }).as('sourceProjectId') createProject(targetProjectName).as('targetProjectId') diff --git a/server-ce/test/editor.spec.ts b/server-ce/test/editor.spec.ts index 62eeeda26b..7a34f26573 100644 --- a/server-ce/test/editor.spec.ts +++ b/server-ce/test/editor.spec.ts @@ -22,7 +22,7 @@ describe('editor', () => { beforeWithReRunOnTestRetry(function () { projectName = `project-${uuid()}` login('user@example.com') - createProject(projectName, { type: 'Example Project', open: false }).then( + createProject(projectName, { type: 'Example project', open: false }).then( id => (projectId = id) ) ;({ recompile, waitForCompileRateLimitCoolOff } = @@ -62,7 +62,7 @@ describe('editor', () => { cy.log('add word to dictionary') cy.get('.ol-cm-spelling-error').contains(word).rightclick() - cy.findByText('Add to Dictionary').click() + cy.findByText('Add to dictionary').click() cy.get('.ol-cm-spelling-error').should('not.exist') cy.log('remove word from dictionary') diff --git a/server-ce/test/git-bridge.spec.ts b/server-ce/test/git-bridge.spec.ts index 67a09b3409..447f28bfd2 100644 --- a/server-ce/test/git-bridge.spec.ts +++ b/server-ce/test/git-bridge.spec.ts @@ -46,7 +46,7 @@ describe('git-bridge', function () { function maybeClearAllTokens() { cy.visit('/user/settings') - cy.findByText('Git Integration') + cy.findByText('Git integration') cy.get('button') .contains(/Generate token|Add another token/) .then(btn => { @@ -63,7 +63,7 @@ describe('git-bridge', function () { it('should render the git-bridge UI in the settings', () => { maybeClearAllTokens() cy.visit('/user/settings') - cy.findByText('Git Integration') + cy.findByText('Git integration') cy.get('button').contains('Generate token').click() cy.get('code') .contains(/olp_[a-zA-Z0-9]{16}/) @@ -107,7 +107,7 @@ describe('git-bridge', function () { cy.get('code').contains(`git clone ${gitURL(id.toString())}`) }) cy.findByText('Generate token').should('not.exist') - cy.findByText(/generate a new one in Account Settings/) + cy.findByText(/generate a new one in Account settings/i) cy.findByText('Go to settings') .should('have.attr', 'target', '_blank') .and('have.attr', 'href', '/user/settings') @@ -365,7 +365,7 @@ Hello world it('should not render the git-bridge UI in the settings', () => { login('user@example.com') cy.visit('/user/settings') - cy.findByText('Git Integration').should('not.exist') + cy.findByText('Git integration').should('not.exist') }) it('should not render the git-bridge UI in the editor', function () { login('user@example.com') diff --git a/server-ce/test/helpers/project.ts b/server-ce/test/helpers/project.ts index 6a2cd74e4a..8fb6aa2404 100644 --- a/server-ce/test/helpers/project.ts +++ b/server-ce/test/helpers/project.ts @@ -5,11 +5,11 @@ import { v4 as uuid } from 'uuid' export function createProject( name: string, { - type = 'Blank Project', + type = 'Blank project', newProjectButtonMatcher = /new project/i, open = true, }: { - type?: 'Blank Project' | 'Example Project' + type?: 'Blank project' | 'Example project' newProjectButtonMatcher?: RegExp open?: boolean } = {} @@ -37,7 +37,7 @@ export function createProject( } cy.findAllByRole('button').contains(newProjectButtonMatcher).click() // FIXME: This should only look in the left menu - cy.findAllByText(type).first().click() + cy.findAllByText(new RegExp(type, 'i')).first().click() cy.findByRole('dialog').within(() => { cy.get('input').type(name) cy.findByText('Create').click() diff --git a/server-ce/test/project-list.spec.ts b/server-ce/test/project-list.spec.ts index 3056242a0f..6b038f320c 100644 --- a/server-ce/test/project-list.spec.ts +++ b/server-ce/test/project-list.spec.ts @@ -32,7 +32,7 @@ describe('Project List', () => { before(() => { login(REGULAR_USER) - createProject(projectName, { type: 'Example Project', open: false }) + createProject(projectName, { type: 'Example project', open: false }) }) beforeEach(function () { login(REGULAR_USER)