Author SHA1 Message Date
Miguel SerranoandCopybot f07212337f [web] Replace pro with commons wording on institutional subscript… (#34078)
* [web] Replace `pro` with `commons` wording on institutional subscriptions

Replaces the wording in several places:
- subscription settings
- email tags
- features tooltip
- institution portal

GitOrigin-RevId: 1b9a0e51245ed8a41865300d9e9d555bc05e6c17
2026-05-29 08:06:32 +00:00
Miguel SerranoandCopybot 63852c5934 [web] bump js-yaml in reference-parser (#33953)
* [web] bump `js-yaml` in `reference-parser`

`.yarn/patches/referer-parser-npm-0.0.3.patch` bumps the `js-yaml` dependency,
but yarn patches don't take that into account the patched package.json for dependency resolution.

* Add RequestHelper test

GitOrigin-RevId: 8246f8ab54956897cc361d7c02b65e5363ad43ec
2026-05-29 08:06:27 +00:00
Andrew RumbleandCopybot 05895dc0dc Merge pull request #34073 from overleaf/mj-remove-error-log
[web] Stop logging handled errors

GitOrigin-RevId: 7a60a576032a0dd389ef200e22c860b2ea9e8ed8
2026-05-29 08:06:02 +00:00
Andrew RumbleandCopybot bb7643f697 Merge pull request #33482 from overleaf/ar-mixpanel-labs-project
[web/analytics] Send labs user's events to separate mixpanel project

GitOrigin-RevId: 42612b71d2d7a082ffbe1ff614499a0b94553b90
2026-05-29 08:05:57 +00:00
KristinaandCopybot 5f7e81aafc [web] add stats to process_notifications cron (#34049)
GitOrigin-RevId: ea6890f2726cba268f1e5eead0643d03757b8dff
2026-05-29 08:05:42 +00:00
Mathias JakobsenandCopybot 5f1a71580b Merge pull request #34072 from overleaf/mj-toolbar-borders
[web] Add borders to dropdowns in editor toolbar

GitOrigin-RevId: e6199736559f755bde79341d78e6d8cd2d4c1ca1
2026-05-29 08:05:38 +00:00
Tom WellsandCopybot fa2b70ffc6 Shared CSS cleanup, empty state polish, fix translation (#33998)
GitOrigin-RevId: eadaec851774b51912d45d18e7efca9981122628
2026-05-29 08:05:33 +00:00
Kate CrichtonandCopybot 54385384b9 Merge pull request #34053 from overleaf/kc-domain-capture-active-labels
[web] replace Capture column icon with Active/Inactive badges

GitOrigin-RevId: 1986a703b24f1d648bd054ce7def04bccb0007ea
2026-05-29 08:05:21 +00:00
Kate CrichtonandCopybot 0630245bb6 Merge pull request #34029 from overleaf/kc-domain-capture-status-note
[web] Add domain capture status note to group settings

GitOrigin-RevId: dbe6a67d088c3e19207ed7bee127d2b33ff9fdcc
2026-05-28 08:08:22 +00:00
Kate CrichtonandCopybot 3677e5d08f Merge pull request #33673 from overleaf/kc-pending-verification-notification
[web] verification notification updates

GitOrigin-RevId: 29c4284b4de4e150c021a25e5f485312b1b37dc7
2026-05-28 08:08:18 +00:00
Mathias JakobsenandCopybot 51ca5c0156 Merge pull request #33972 from overleaf/mj-web-show-pandoc-error
[web] Expose conversion errors during project exports

GitOrigin-RevId: 2e808bd65f03e81405db4727f2f5773d3b14cbe7
2026-05-28 08:08:14 +00:00
Jakob AckermannandCopybot e7a202a0bf [clsi] fix duplicate import of Errors module (#34068)
GitOrigin-RevId: e1dec5e5d439e5d178f9f7400c0873e0be6e90f8
2026-05-28 08:08:10 +00:00
Jakob AckermannandCopybot 7e4820f0b0 [clsi] migrate convert project to document to compile from history (#33985)
* [clsi] add request flag for isCompileFromHistory

* [clsi] derive cacheKey for history snapshot from compile dir

* [clsi] migrate convert project to document to compile from history

* [clsi] address review feedback

* [web] determine root doc at the time of converting the project

* [web] wait for flush before starting document conversion

* [saas-e2e] add tests for root doc override when converting project

GitOrigin-RevId: 71c578030949b89f3a74e7f7ab882dfa9c98c17a
2026-05-28 08:08:06 +00:00
Jakob AckermannandCopybot 666788be70 [clsi] nginx: use a fixed project id for the content domain access check (#33291)
GitOrigin-RevId: 7801fa001e42b1b96d851f74efff396bc6471980
2026-05-28 08:08:02 +00:00
Mathias JakobsenandCopybot 68e9572fbb [clsi] Forward pandoc errors to web (#33971)
* [clsi] Forward pandoc errors to web

* [clsi] Remove unused import

* [clsi] Align warning logs

* [clsi] Update HTTP response for errors

* [clsi] Update acceptance test with 422

* [clsi] Always return json body on 422

* [clsi] Include stderr in logs for non user facing errors

GitOrigin-RevId: 4284c8d4e8b7b45eac4997cd9e52ca4894b20412
2026-05-28 08:07:58 +00:00
5e47353ad4 [Security upgrade] Pin @babel/plugin-transform-modules-systemjs to 7.29.4 via resolutions (GHSA-fv7c-fp4j-7gwp) (#33650)
* Pin @babel/plugin-transform-modules-systemjs to 7.29.4 via resolutions (GHSA-fv7c-fp4j-7gwp)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Fix missing comma in package.json resolutions

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Jakob Ackermann <jakob.ackermann@overleaf.com>
GitOrigin-RevId: d6f3e72234d64fd0afb8676b8652cc03b0cddbe0
2026-05-28 08:07:54 +00:00
a4e7d90cf1 [Security upgrade] pin js-cookie to 3.0.7 (#33960)
* pin js-cookie to 3.0.7 in root yarn.lock

* drop unnecessary js-cookie resolution; ^3.0.5 already permits 3.0.7

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
GitOrigin-RevId: e7803a04aa76daea574e6b1e67b3d6c42134945e
2026-05-28 08:07:49 +00:00
CopilotandCopybot 016296cc07 web: add info/cause support to BackwardCompatibleError (OError-compatible) (#33766)
GitOrigin-RevId: 736ca3245f85f14df5a01e5c4a342b99742118e8
2026-05-28 08:07:29 +00:00
Andrew RumbleandCopybot 9daa56becc Merge pull request #33079 from overleaf/ar-remove-request-from-clsi
[clsi] remove request library

GitOrigin-RevId: 4690c476157fc2829e516d91f688f9424f8c162f
2026-05-28 08:07:11 +00:00
Andrew RumbleandCopybot 26b9d029f3 Merge pull request #33708 from overleaf/jlm-stripe-stage5-fixes
Stripe migration improvements from stage 5

GitOrigin-RevId: 897941bf1a51f8f1639489fcd3f542af671b7ac7
2026-05-28 08:07:04 +00:00
Andrew RumbleandCopybot dddefc9e30 Merge pull request #33962 from overleaf/lg-uuid-security-upgrade
[Security upgrade] pin uuid to 11.1.1 in root yarn.lock

GitOrigin-RevId: 873da40311c0e67fc3eb7eb0c484475d1e515148
2026-05-28 08:06:59 +00:00
Andrew RumbleandCopybot 08975f4ba2 Merge pull request #33997 from overleaf/ar-require-cookie-consent-for-mixpanel-autocapture
[web] require cookie consent for mixpanel autocapture

GitOrigin-RevId: 6898b72079cc6c286ce15a276979381a23c32ebe
2026-05-28 08:06:55 +00:00
Miguel SerranoandCopybot 21902e7a55 [CE/SP] Remove non-production dependencies (#33949)
Some non-production dependencies were bundled in the CE and SP images:
- `lint` was pulled as production dependency by `eslint-plugin`. Moving to peer-dependency, which is the usual strategy, addresses the issue.
- Yarn cache wasn't purged. By adding `/usr/local/share/.cache/yarn` to the mounts we ensure it's also cleaned.

GitOrigin-RevId: f328592c8f8de7193295839578e239a975fe30aa
2026-05-28 08:06:51 +00:00
Jakob AckermannandCopybot d610f404e7 [history-v1] increase timeout for downloading the latest content as zip (#34045)
Remove the default timeout as it's too low and a big footgun.

GitOrigin-RevId: 42e26a2a288ad3e38252bc98b909a4bc8b10f70c
2026-05-28 08:06:47 +00:00
roo huttonandCopybot 4f192564f2 Merge pull request #33345 from overleaf/rh-split-group-role
Update group_role in customer.io when changed

GitOrigin-RevId: d21866a9fe324a0468de74a45b6932dda27de8a1
2026-05-28 08:06:43 +00:00
roo huttonandCopybot 5d0becf76b Merge pull request #33572 from overleaf/rh-cio-past-due
Expose past_due status to customer.io

GitOrigin-RevId: 5b1b03db0e1068f1ae444585e4a9e732470f0ffa
2026-05-28 08:06:39 +00:00
MoxAmberandCopybot ad193d81c5 Merge pull request #33840 from overleaf/as-link-sharing-schema
[web] Set ProjectInvite privileges type to Union

GitOrigin-RevId: a68e732bec5f6a7752b1110075736cb33454e6eb
2026-05-28 08:06:35 +00:00
DavidandCopybot b6b03a2091 Merge pull request #33471 from overleaf/copilot/fix-git-integration-prompts
Fix incorrect auth error messages shown to git bridge users

GitOrigin-RevId: 52888e991cf1ec7f3ae28c67fcd70fa2a1a9fad9
2026-05-28 08:06:31 +00:00
Maria Florencia Besteiro GonzalezandCopybot bc8008737c Merge pull request #33847 from overleaf/lg-async-http-client-security-upgrade
[Security upgrade] bump async-http-client to 3.0.10 in git-bridge

GitOrigin-RevId: c2283a78055f1dce0644f2278167399e65a12937
2026-05-28 08:06:23 +00:00
Malik GlossopandCopybot 94894779cf Merge pull request #32575 from overleaf/mg-worktree-symlink
Add worktree-setup and worktree-teardown scripts for dev environment

GitOrigin-RevId: 8cce26ef4f6f45bd9e39b2c46f314366d6414cab
2026-05-28 08:06:19 +00:00
Malik GlossopandCopybot a47f6443f8 Merge pull request #33932 from overleaf/mg-select-style
Replace text label with icon in "Select style" toolbar button

GitOrigin-RevId: 52b93a29db47e99609a90294e53abe1057a6c71d
2026-05-28 08:06:15 +00:00
Jakob AckermannandCopybot 984b8e3f4a [web] add a hook for discovering the current root doc (#34027)
GitOrigin-RevId: d1930e6b13ca18dbae927dc15a5c6507351f71c8
2026-05-28 08:06:08 +00:00
Miguel SerranoandCopybot 14b04ad4b8 [project-history] Removed request dependency (#32686)
* [project-history] Removed `request` dependency

GitOrigin-RevId: 086bbbf2efeea6026127653a1f68ca6bf0476de6
2026-05-28 08:06:04 +00:00
KristinaandCopybot 78dd0a8681 [web] update email base templates (#33791)
* update email template design
* gate email template on split test

GitOrigin-RevId: 2e0a1d9abf11a0c3f16e103511191d43d542b8a4
2026-05-28 08:05:59 +00:00
Malik GlossopandCopybot fa26367aa9 Merge pull request #33805 from overleaf/mg-ai-paywall-analytics
Add paywall-prompt and paywall-click events to ai paywalls

GitOrigin-RevId: aa7de15a990ad1833e3dda65d5fb50f60bb7c9e3
2026-05-28 08:05:52 +00:00
Malik GlossopandCopybot 8af5c2c346 Merge pull request #33600 from overleaf/worktree-mg-bullet-list-compact
Combine toolbar list controls and render indentation controls conditionally

GitOrigin-RevId: 48d7c52983449566bfa21b5572915d79e595c704
2026-05-28 08:05:48 +00:00
Miguel SerranoandCopybot 4cfaea4621 [clsi] Fix compile stop detection in local runner (#33024)
* [clsi] Fix compile stop detection in local runner

* Tighten compilation cancelled message

* Cleanup process PID on `error`

GitOrigin-RevId: 61495466b98c127402b256d629ea58e7b9be6df7
2026-05-28 08:05:44 +00:00
Miguel SerranoandCopybot 8ce9d184cb [migrations] pin underscore version to 1.13.8 (#33951)
`east` is the only workspace pointing to an old version of `underscore`, which is now pinned to `1.13.8` the same way it's done for `argparse`.

GitOrigin-RevId: a938067ba62aca7b73e15f030d9c341f9337c26d
2026-05-28 08:05:30 +00:00
Tim DownandCopybot 287e619967 Merge pull request #33663 from overleaf/td-en-json-sort-hook
Sort staged locale files in the pre-commit hook

GitOrigin-RevId: fb3a155be52495305372c5c5cc54c2f50e88c417
2026-05-28 08:05:22 +00:00
Eric Mc SweenandCopybot 52a8a447aa Merge pull request #33909 from overleaf/em-remove-tpdsworker
Remove tpdsworker service

GitOrigin-RevId: 9014d7d5bdc4e3cc7a7168d93b90ef8aa72d0c1a
2026-05-27 08:08:00 +00:00
Tom WellsandCopybot 0565a778d8 Library: Design Changes (#33933)
GitOrigin-RevId: b45ea92adc424e2864e952cd7f157509e10ffb7d
2026-05-27 08:07:47 +00:00
577ff63eca [document-updater] batch collaborator lookups in project_notifications.mts (#33974)
* Refactor project collaborator check to handle multiple project IDs and optimize Redis caching
* Batch mongo queries and use promiseMapWithLimit

---------

Co-authored-by: Domagoj Kriskovic <dom.kriskovic@overleaf.com>
GitOrigin-RevId: 7d568b4b05894465c930595ebd3f214ebc5c72c0
2026-05-27 08:07:35 +00:00
Jakob AckermannandCopybot c7b56ff295 [monorepo] remove contacts service (#33550)
GitOrigin-RevId: 15478243e4d6a56b81eee28f76f9ef7dc54a45d7
2026-05-27 08:07:19 +00:00
Kate CrichtonandCopybot 01624f1387 Merge pull request #33311 from overleaf/kc-remove-domain-verification-ui
[web] remove domain verification UI

GitOrigin-RevId: 71e4869180cd6573870d4ba48b7a66bade99eab3
2026-05-27 08:07:02 +00:00
Daniel KontšekandCopybot f4565a677b Merge pull request #33922 from overleaf/dn0-gke02-rt-23-staging
[gke-02] Migrate staging real-time pool to n4d-highcpu-2

GitOrigin-RevId: 5dcdc8d5074cc49e2027ba06e86a0751eab71bf4
2026-05-27 08:06:18 +00:00
KristinaandCopybot 3bd908d963 [document-updater] update logging in project_notifications script (#33924)
* reduce logs for project_notifications script
* include cache hit/miss stats

GitOrigin-RevId: 6aac74a18f2ad352b0e8609f0ad2b6c831bd9da5
2026-05-25 08:05:57 +00:00
Eric Mc SweenandCopybot 3d71f51435 Merge pull request #33856 from overleaf/em-library-write-and-cite
[web] Add account-level library as a source for write & cite autocomplete

GitOrigin-RevId: 3182d516c1fab68e5bbf5d77d60e4c431d54b73e
2026-05-25 08:05:53 +00:00
09f03381fd [web] Fix preview next-invoice date for cadence-change upgrades (#33697)
* [web] Fix preview next-invoice date for cadence-change upgrades

When upgrading from a monthly plan to an annual plan (or vice versa) the
user pays for a full new-cadence term today, so the next payment is one
new-term-length from now — not the current cycle's period end. Previously
we always echoed subscription.periodEnd in the preview, which surfaced
the stale current-cycle date and misled the user into thinking they'd
be charged again ~25 days later.

makeChangePreview now compares the current and next plans' annual flag:
on a cadence flip it returns now + 1 year or now + 1 month; otherwise it
keeps the existing behaviour.

Closes #33283.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Format

* Fix next invoice date using priceincents

* Apply suggestions from code review

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
GitOrigin-RevId: 05b660ecb518c04b60e88f2ddc7531733245bdde
2026-05-25 08:05:49 +00:00
Tim DownandCopybot ab4f23ab86 Merge pull request #33788 from overleaf/td-payment-preview-ai
Add AI to features list on upgrade checkout page

GitOrigin-RevId: 0b5b94fc4961ac2e8e2d2812bb80c1041f4c8c24
2026-05-25 08:05:41 +00:00
Tim DownandCopybot b8e689b3fe Merge pull request #33902 from overleaf/revert-33567-td-briefly-onboarding-screen
Remove Briefly ad from onboarding flow

GitOrigin-RevId: e762d550a802b7bf01b5e22d5025ebc9f304df42
2026-05-25 08:05:37 +00:00
Malik GlossopandCopybot 9ae5663423 Merge pull request #33160 from overleaf/copilot/fix-typeerror-out-of-memory
fix: normalize string errors at pdf-caching call sites before passing to OError.tag()
GitOrigin-RevId: 0259de81cca72e3b9c304f68b087a627db8f1980
2026-05-25 08:05:33 +00:00
Malik GlossopandCopybot eb9d586bdb Merge pull request #32968 from overleaf/worktree-labs-feature-preview
Add labs preview modal to editor

GitOrigin-RevId: 0df33135febc8e94129bcdfdfb5c4981326dfab0
2026-05-25 08:05:28 +00:00
Alf EatonandCopybot 24ba0b86b1 Set npmMinimalAgeGate in yarnrc (#33639)
GitOrigin-RevId: 69745c2c5606ff90d7a2a8b904f850007082b84a
2026-05-25 08:05:24 +00:00
ae00bcbeca [Security Upgrade]: pin @xmldom/xmldom to 0.8.13 (#33373)
Adds a resolution in root package.json to force all consumers to
@xmldom/xmldom@0.8.13, fixing GHSA-wh4c-j3r5-mjhp, GHSA-j759-j44w-7fr8,
GHSA-x6wf-f3px-wcqx, GHSA-f6ww-3ggp-fr8h, and GHSA-2v35-w6hq-6mfw.

The vulnerable 0.7.13 entry in yarn.lock is replaced by 0.8.13
(minimum safe version across all five advisories).

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
GitOrigin-RevId: e1a301e3a1d637894284f35238ca0e8c23534276
2026-05-25 08:05:19 +00:00
Lucie GermainandCopybot 6fa708982b Pin argparse/underscore to 1.13.8 via yarn resolution (#33364)
Fixes GHSA-cf4h-3jhx-xvhq (critical, arbitrary code execution) and
GHSA-qpx9-hpmf-5gmw (high, DoS via _.flatten/_.isEqual).

Vulnerable underscore@1.7.0 came from js-yaml@2.1.3 → argparse@0.1.16.
All other instances were already ≥1.13.8.

GitOrigin-RevId: b2ab4bc2682e19709694b7dd686134a439ade90c
2026-05-22 08:06:48 +00:00
Kate CrichtonandCopybot afaef6a1ef Merge pull request #33499 from overleaf/kc-update-add-domain-modal-ux
[web] update domain verification modal ux

GitOrigin-RevId: e7abcb569e8c956e8f22f09c90dd8e9998fc6255
2026-05-22 08:06:30 +00:00
Domagoj KriskovicandCopybot bf1dd6986f Record project notification timestamp in Redis on applyUpdate (reverted) (#30814)
* Revert "Revert "Record project notification timestamp in Redis on applyUpdate (#29509)""

This reverts commit 31c88ee836fb5e1ab3950da590c28e24b1397edb.

GitOrigin-RevId: fc012324f6035156585fab468aca72b900a9710b
2026-05-22 08:06:26 +00:00
Miguel SerranoandCopybot 496f110465 [CE] Update phusion image version to 1.0.3 (#33885)
GitOrigin-RevId: cc707258e145849f3bc1ddb6b44f7eca8c904d74
2026-05-22 08:06:15 +00:00
Mathias JakobsenandCopybot fc3dbc1aad Merge pull request #33883 from overleaf/mj-command-palette-border
[web] Tweak border color of command palette

GitOrigin-RevId: 7b37b9c8503402da62e2fd2b334f28dc14760b75
2026-05-22 08:06:11 +00:00
KristinaandCopybot 88eb599d4e [document-updater] increase scanStream COUNT for project notifications (#33833)
* increase scanStream COUNT for project notifications
* fix Bull queue.add delay option being ignored
* parse timestamp to number before adding to notification queue
* fix outdated comments in project_notifications script

GitOrigin-RevId: 98bb638228550b2f6f2de90280a06c47e022cf96
2026-05-22 08:06:03 +00:00
ilkin-overleafandCopybot 192fd7c28c Merge pull request #33542 from overleaf/ii-dropdown-flicker
Fix dropdown menu flicker on open

GitOrigin-RevId: ddf826b30fcac3322d86067e5950731e7dc1a2d4
2026-05-22 08:05:59 +00:00
Domagoj KriskovicandCopybot 803ba735ca Show toast when Python script saves output files to project
GitOrigin-RevId: 9ca5201645953f86c3ac8e83f545dfbcdac2b35c
2026-05-22 08:05:55 +00:00
Olzhas AskarandCopybot 014ac37704 Merge pull request #33814 from overleaf/oa-adjust-tooltip
[web] Adjust tooltip

GitOrigin-RevId: a180fb9872c9fc85b5ea7e3821e8e8c8393bab9d
2026-05-22 08:05:50 +00:00
Olzhas AskarandCopybot 793d5c79fb Merge pull request #33775 from overleaf/oa-remove-addon-section
[web] Remove Add-ons section

GitOrigin-RevId: 82009dd6aeb3588f46cabacdb7313c01f3afc27e
2026-05-22 08:05:46 +00:00
Mathias JakobsenandCopybot 9d79cc89ec Merge pull request #33855 from overleaf/mj-command-palette-tweaks
[web] command palette tweaks

GitOrigin-RevId: 454d6916043d3317e60302379bcf9707fb8d4dcb
2026-05-21 08:07:12 +00:00
Mathias JakobsenandCopybot eddec90cb1 Merge pull request #33649 from overleaf/mj-command-palette
[web] Add command palette

GitOrigin-RevId: 5bf1903836810ca5f0e2bc7f6c00a4b1da797ea2
2026-05-21 08:07:04 +00:00
Simon GardnerandCopybot 5cfd7b6c6a [migration] re-use paypal methods if billing agreement ids match (#33720)
GitOrigin-RevId: 4a324c1cdde84dabf620a2616a0aa27242cf041e
2026-05-21 08:06:38 +00:00
Olzhas AskarandCopybot bb0dc07d22 Merge pull request #33741 from overleaf/lg-sanitize-html-upgrade
[Security upgrade] Upgrade sanitize-html to 2.17.4 (GHSA-rpr9-rxv7-x643)

GitOrigin-RevId: 40a11361eac35d44a6fd7069e0d0d7c02a6628ec
2026-05-21 08:06:33 +00:00
Miguel SerranoandCopybot ad651a22fa Revert "[web] Add SVG support to file-view panel (#32155)" (#33832)
This reverts commit e80c491a10db6f5757c568430e17d9cbb613c5b4.

GitOrigin-RevId: dbe0de698bc7349e5b8f9712d1d13998e41ab528
2026-05-21 08:06:28 +00:00
Miguel SerranoandCopybot 35681dd3b2 [web] Add SVG support to file-view panel (#32155)
* [web] Add SVG support to file-view panel

Adds support by reading the content of the downloaded SVG, then creating a blob and rendering it as native HTML.

GitOrigin-RevId: e80c491a10db6f5757c568430e17d9cbb613c5b4
2026-05-21 08:06:16 +00:00
Miguel SerranoandCopybot f9c53fe147 [web] Added DEFAULT_LATEX_COMPILER env (#32455)
This is mainly intended to be used in CE/Server Pro

GitOrigin-RevId: 277f9afca389a1e7b00db2d987129432fb1707b5
2026-05-21 08:06:12 +00:00
Miguel SerranoandCopybot 107189cd5f [web] Clear hardcoded password in external SP auth (#33597)
registerExternalAuthAdmin() now generates a random password on admin registration.

A migration clears the password for existing installs only in CE/SP

GitOrigin-RevId: 94a82d35dc8cd46915c31fb24f477c19367025eb
2026-05-21 08:06:07 +00:00
Simon GardnerandCopybot 2233ac9b1d Harden shell quoting in CI pipeline definitions (#33789)
GitOrigin-RevId: a21c02f632b1e357ba18a86378d796a0d93fa484
2026-05-20 08:07:46 +00:00
Mathias JakobsenandCopybot 5d4f38e57a Merge pull request #33629 from overleaf/lg-fast-uri-resolution
[Security upgrade] Pin fast-uri to 3.1.2 via resolutions (GHSA-q3j6-qgpj-74h6, GHSA-v39h-62p7-jpjc)

GitOrigin-RevId: 154e742e12cb68e8b1c8d5b88e1a188160746784
2026-05-20 08:07:34 +00:00
Liam O'BrienandCopybot b4a76fee6d [web] Implementing library search (#33604)
* Initial working version of library search

draft fetch allowing optional search param

draft debounce search

draft search bar

draft using  for search

draft search params

draft data index creation

draft prefix-regex search

draft add fields only on search

draft index setup

draft search tests

draft search tests for extra params

draft using correct display value from bib entry for tokenization

* Library search handles diacritics

* Library styling and refreshing table data without
reloading table

* Updating mongo search query and creating migration
scripts for existing data

* Using Mongo query for sorting results

* Moving copied files into shared directory

* Addressing review comments

* Pulling changes from bibtex-search-token for consistency with migration

* Fixing lint

* Using mongo collation for handling case and diacritics in search queries

* Boosting citation keys with  check for tokens

* Removing double foldLatinDigraphs call

* Matching figma designs for Library search component

* Adding cursor for paginated Library search results

* Re-fixing flash after searching library

* Unit test for cursor search

* Using same cursor object for search and get all results

* Data migration moved to manual script

GitOrigin-RevId: b7e6a1f07f775c8450dd97e7269cab3b68ca0eb3
2026-05-20 08:07:29 +00:00
Jessica LawsheandCopybot 9acf3b8e7f Merge pull request #33610 from overleaf/jel-hostname-capturedByGroup
[web] Check `capturedByGroup` when adding new email

GitOrigin-RevId: f9ef3d4cc7387dc0139a70aecd6cfcb20170abc6
2026-05-20 08:07:18 +00:00
Eric Mc SweenandCopybot 2f32b9d61e Merge pull request #32706 from overleaf/em-dropbox-queue
[third-party-datastore] Apply Dropbox→Overleaf updates inline, removing queue hop

GitOrigin-RevId: 1ea17eefe57aaf32634ce3395682f7eac2e53dc5
2026-05-20 08:07:10 +00:00
2f08f6f6eb Bump mongoose to 8.22.1 (GHSA-wpg9-53fq-2r8h) (#33648)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GitOrigin-RevId: f092e8d914ea5825e285fe4741bb42dd2c5d5fa3
2026-05-20 08:07:05 +00:00
Alf EatonandCopybot 34d272afa9 Reapply "Wrap PDF setDocument in startViewTransition (#33346)" (#33633)
GitOrigin-RevId: 11dc65d8a8195c8cd6e6e2b58905a0f8b7b218f4
2026-05-20 08:06:53 +00:00
Alf EatonandCopybot 1d959af16e Process zip import entries in parallel (#33176)
GitOrigin-RevId: f77c2b08d4c085b51a8608d2621dd5bbe1134258
2026-05-20 08:06:48 +00:00
Alf EatonandCopybot 7eed283b11 Ignore entries in __MACOSX folder when importing zip archive (#33147)
GitOrigin-RevId: e990d593d96085e13a209d4155823097b0814276
2026-05-20 08:06:40 +00:00
Alf EatonandCopybot efab968153 Treat qmd and rmd extensions as Markdown (#33786)
GitOrigin-RevId: 89d79e958ea08f3388bde8dc561b04f87a1b6549
2026-05-20 08:06:36 +00:00
Alf EatonandCopybot 7c8e89923d Add .qmd to editable file extensions (#33785)
GitOrigin-RevId: 868a96e66e8d408ceb2f5b29e59f0d1ee12992dd
2026-05-20 08:06:31 +00:00
d8c33cc34c Allow multiple concurrent reference searches (#33739)
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GitOrigin-RevId: 403d4f5900a8c4ccdc64032d365adb285a191b71
2026-05-20 08:06:23 +00:00
Alf EatonandCopybot c0acddbfaf Use sharejs doc type in Storybook stories (#33565)
GitOrigin-RevId: a2bf64d3ce376dd05e740796e8f1ea74913ed8f0
2026-05-20 08:06:19 +00:00
Alf EatonandCopybot 3dbbf95fbb Disable Yarn telemetry (#33561)
GitOrigin-RevId: f835277a689e142b7d336ee38af3f142e37c6f5c
2026-05-20 08:06:14 +00:00
30edd837e1 Use resolve.tsconfig (#31639)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
GitOrigin-RevId: 1c7da49e14af5935f85f1927186a825b116bb4e9
2026-05-20 08:06:05 +00:00
EvelynandCopybot b5654c5a01 fix: chown /var/lib/overleaf mount point to www-data (#33764)
The init script chowns all subdirectories but not the mount point
itself. When the host volume is owned by a non-www-data user with
restrictive permissions (e.g. 770), the web process cannot traverse
the directory and crashes with EACCES, causing a 502.

Fixes #1325 and #1465

COPYBARA_INTEGRATE_REVIEW=https://github.com/overleaf/overleaf/pull/1475 from ev-not-eve:patch-1 269a80500f

Co-authored-by: Evelyn <evansvevelyn@gmail.com>
GitOrigin-RevId: 959051861246c9f3958e56861821b92d84167926
2026-05-19 08:04:55 +00:00
Mathias JakobsenandCopybot ce6f9b8e8c Merge pull request #33705 from overleaf/mj-clsi-cwd-for-conversions
[clsi] Add cwd argument to CommandRunner and use to simplify conversions

GitOrigin-RevId: 5333e3262a99e602ab5470ae1e23facb5b28a170
2026-05-19 08:04:51 +00:00
Jakob AckermannandCopybot c0111fec29 [monorepo] run format_fix and trigger prettier on .agents changes (#33759)
* [monorepo] run format_fix and trigger prettier on .agents changes

* [monorepo] cleanup stale prettier ignore rule

* [monorepo] tweak format:monorepo-check:fix

GitOrigin-RevId: e6c29a0c601fbf388a048eb42706f9bd0a18344f
2026-05-19 08:04:48 +00:00
Jakob AckermannandCopybot 1f8371e0a3 [document-updater] flush_all: log progress after every 1k projects (#33757)
GitOrigin-RevId: b5b68f6f53bece51234799fb626d0d6a2a5b590c
2026-05-19 08:04:41 +00:00
Jakob AckermannandCopybot 293d89a4cb [web] inline contacts service into web (#33546)
GitOrigin-RevId: d5e84d4f80f5ad4e951934d6dcdc332b0d26f3d0
2026-05-19 08:04:34 +00:00
Miguel SerranoandCopybot b79d432deb [web] Conditionally show items in insert figure toolbar (#33721)
Removes the options for inserting an image from another project or by downloading an external URL if the features are disabled.

GitOrigin-RevId: ffa64e5929e254d8a236c8e9aca4eb8210f444c9
2026-05-19 08:04:21 +00:00
Jakob AckermannandCopybot 99148d5956 [web] silence customer.io integration when not configured in dev/CI (#33731)
GitOrigin-RevId: c9498f57f0dacb3d18cd7617388df11d5cf029de
2026-05-19 08:04:17 +00:00
Brian GoughandCopybot 60860aa202 Merge pull request #33576 from overleaf/bg-jpa-convert-document-to-file
Modify convertDocToFile to bypass docstore

GitOrigin-RevId: 3ec789034a369d39d223450462394c8f303caa07
2026-05-19 08:04:13 +00:00
b1a0bb16db [migrations] delete expired oauth access tokens after 24h (#33575)
Co-authored-by: Brian Gough <brian.gough@overleaf.com>
GitOrigin-RevId: 7f67a7e6949472c66f5f75a6053161d8e359f5df
2026-05-19 08:04:09 +00:00
KristinaandCopybot 2f5d838e0f Merge pull request #33704 from overleaf/kh-add-reject-change-preference-check
[web] add reject tracked change preference check

GitOrigin-RevId: b55dba21b3d4f42e68528d2b5906862c57794cd1
2026-05-18 08:06:57 +00:00
Andrew RumbleandCopybot e9aedce4ab Merge pull request #33625 from overleaf/ar-update-vitest
[monorepo] bump vitest to 4.1.5

GitOrigin-RevId: 22ba2249ae384fd59347c9aa45c70f51ccdf8890
2026-05-18 08:06:49 +00:00
Andrew RumbleandCopybot 19ad00c329 Merge pull request #33743 from overleaf/lg-systeminformation-upgrade
[Security Upgrade] Upgrade systeminformation to 5.31.6 (GHSA-hvx9-hwr7-wjj9)

GitOrigin-RevId: bd75d2bc59e183d23972e367f40f753c08ca6967
2026-05-18 08:06:41 +00:00
Eric Mc SweenandCopybot 2913e462ec Merge pull request #33665 from overleaf/copilot/fix-error-logging-in-git-bridge
git-bridge: Log WrongBranchException and ForcedPushException at WARN instead of ERROR
GitOrigin-RevId: 7aaa934a0df614e336ce3c20b892af1af0cd070f
2026-05-18 08:06:29 +00:00
Eric Mc SweenandCopybot a3682af6e4 Merge pull request #33710 from overleaf/em-tpds-config
Clean up tpdsworker config from web and third-party-datastore

GitOrigin-RevId: 3856126d9dc856fea4bc4133b11402c35b10630b
2026-05-18 08:06:25 +00:00
9e42d3a530 [web] Address design QA items on pricing page (#33682)
- pricing table: integration icons gap uses --spacing-06 (horizontal),
  integrations content gap uses --spacing-04 (vertical)
- Student card no longer renders with the green stroke highlight
- Interstitial H1 wrapped in .main-heading-section so its spacing
  matches the pricing page

Part of #33619.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GitOrigin-RevId: 12ddd223f68c776c06a3d5dc5faa841819baae90
2026-05-18 08:06:17 +00:00
0089c0af08 [web] Space skip link from disclaimer on interstitial (#33464)
The "continue with free plan" skip link sat directly under the
disclaimer with no separation. Add spacing-08 margin-top to match
the disclaimer's own padding-top above the cards.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GitOrigin-RevId: ba7334785757a39ca0bdff309ded224e6cb8e3bf
2026-05-18 08:06:09 +00:00
Olzhas AskarandCopybot 868da835b6 Merge pull request #32434 from overleaf/oa-babelfish
[web] Project Babelfish

GitOrigin-RevId: da8c47c0831eaab3e2c74a9507892ae9571919e8
2026-05-18 08:05:57 +00:00
Olzhas AskarandCopybot d388e48a99 Merge pull request #33679 from overleaf/oa-plan-names
[web] Get plan names from the settings

GitOrigin-RevId: 1e61975c3306c025f33e05686f9d2b57964b4f65
2026-05-18 08:05:52 +00:00
Olzhas AskarandCopybot 6c267e68d3 Merge pull request #33707 from overleaf/oa-learn-links
[web] Learn Overleaf links

GitOrigin-RevId: af9f72da008ad8b8c86e4c355268123eb6c40bcd
2026-05-18 08:05:48 +00:00
Andrew RumbleandCopybot 25dfaab2a1 Merge pull request #33641 from overleaf/lg-fast-xml-builder-resolution
[Security upgrade] Pin fast-xml-builder to 1.1.7 via resolutions (GHSA-5wm8-gmm8-39j9, GHSA-45c6-75p6-83cc)

GitOrigin-RevId: ab13841bd8c20da98a136567cf7436ebb9f73722
2026-05-15 08:08:40 +00:00
Noel SchenkandCopybot ba016d798e Upgrade MongoDB image from 6.0 to 8.0 (#33579)
sharelatex  | The MongoDB server has version 6.0.27, but Overleaf requires at least version 8.0. Aborting.

COPYBARA_INTEGRATE_REVIEW=https://github.com/overleaf/overleaf/pull/1480 from noel-schenk:patch-1 4a13e4fbcd

Co-authored-by: Noel Schenk <schenknoel@gmail.com>
GitOrigin-RevId: 9035d16f2c34edcb39c0da99e9d02b9ed8a9f6fa
2026-05-15 08:08:35 +00:00
Mathias JakobsenandCopybot ac961f1d40 Merge pull request #33687 from overleaf/mj-temporary-tabs-fix
[web] Only consider real key presses to make tab permanent

GitOrigin-RevId: 50ab453445e111de2b317f50470f9f4eec39a66f
2026-05-15 08:08:28 +00:00
Mathias JakobsenandCopybot 6538c00742 Merge pull request #33690 from overleaf/mj-prune-deleted-tabs
[web] Prune non-existent tabs when file tree changes

GitOrigin-RevId: 97e68a88a201acc2d1e582911ca64e1f72f9bfe1
2026-05-15 08:08:19 +00:00
CopilotCopybotbriangoughCopilotCopilotcopilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>Brian GoughBrian Gough
3980b9e580 Fix IDOR in exports by adding token verification (Issue #31637) (#32883)
* Fix IDOR in exports by adding token verification

Implement jdleesmiller's suggested fix for Issue #31637:
- V1: Return export token in create response
- V1: Verify token in get_export using secure_compare
- Web: Pass token through fetchExport and fetchDownload
- Web: Return token from exportProject to frontend
- Frontend: Pass token as query param on status/download requests
- Add tests for both services

Agent-Logs-Url: https://github.com/overleaf/internal/sessions/7ba5f535-fba2-49a8-91d4-c87bd332d3a0

Co-authored-by: briangough <7457354+briangough@users.noreply.github.com>

Fix window.location.pathname to .href to preserve query params

Code review correctly identified that window.location.pathname strips
query parameters. Switch to window.location.href so the token query
parameter is preserved in download URLs.

Agent-Logs-Url: https://github.com/overleaf/internal/sessions/7ba5f535-fba2-49a8-91d4-c87bd332d3a0

Co-authored-by: briangough <7457354+briangough@users.noreply.github.com>

Fix test mocks to include token in POST responses

Agent-Logs-Url: https://github.com/overleaf/internal/sessions/0350c6ef-0fff-4e98-8464-812cd92c523f

Co-authored-by: briangough <7457354+briangough@users.noreply.github.com>

fix formatting

Fix token assignment in initiateExport to use pollResponse token if available

Add requireExportToken config setting and tests for invalid/missing token cases

Agent-Logs-Url: https://github.com/overleaf/internal/sessions/059bdba2-4f7a-4407-a5a5-cfcffd888739

Co-authored-by: briangough <7457354+briangough@users.noreply.github.com>

fix formatting

Add tests for export status and token validation in ExportsController and MockV1Api

Co-authored-by: Copilot <copilot@github.com>

* Update services/v1/main/app/controllers/api/v1/overleaf/exports_controller.rb

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* fix linting

* fix fetchString response handling in ExportsHandler tests

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Brian Gough <briangough@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Brian Gough <brian.gough@overleaf.com>
GitOrigin-RevId: 399aef8eaa15ab3655f0905482f3a31fe94e2251
2026-05-15 08:08:04 +00:00
Miguel SerranoandCopybot 5a886aa9fb [web] Add extra details to flexible license sales email (#32929)
* [web] Add extra details to flexible license sales email

GitOrigin-RevId: fbd41adae21c55c5e97f9531565100e1ae911808
2026-05-15 08:07:56 +00:00
Tim DownandCopybot 248e149701 Default interstitial to monthly plans (#33706)
* Default interstitial to monthly plans except for upgrade, which defaults to user's existing subscription period

* Add tests for interstitial page period toggle defaults

GitOrigin-RevId: fa0ac41e7d8a7bf858b53e0940287b28ef21253d
2026-05-15 08:07:49 +00:00
Eric Mc SweenandCopybot 529c332159 Merge pull request #33658 from overleaf/em-fix-docker-tag-length
build: truncate branch names to 96 chars for Docker image tags
GitOrigin-RevId: 9db313244e78a6d4e0aa5d8c08d25f1aac83318b
2026-05-14 08:06:45 +00:00
Jakob AckermannandCopybot 0c8e93bb33 [server-pro] fix tag name for branches with slash (#33685)
GitOrigin-RevId: 25ee2d340b17ce7c758ec8c7e156a67928ab6c73
2026-05-14 08:06:38 +00:00
Davinder SinghandCopybot a3a508d193 [WEB] Add analytics events for importing and exporting to different file types (#33614)
* adding events for success and failure for import and export from latex

* adding the operation property to capture the import/export keyword

GitOrigin-RevId: 2e5482b3c7517b402fc151966975ca8718729683
2026-05-14 08:06:30 +00:00
Jakob AckermannandCopybot 75a12dda17 [web] resync_projects: use the secondaries for all reads (#33684)
* [docstore] add useSecondary flag to projectHasRanges

The rev-check for unarchiving always consults with the primary.

Two extra changes:
- Add a projection argument to peekDoc in order to skip lines download
   from projectHasRanges.
- Add one retry to peekDoc to reduce chances of surfacing a rev-check
   violation.

* [web] resync_projects: use the secondaries for all reads

* [web] add default value for useSecondary

* [docstore] add default value for useSecondary

* [k8s] docstore: set MONGO_HAS_SECONDARIES=true

GitOrigin-RevId: f15ec4fdc1cabe74c1eab87bec85f28d6f7a587d
2026-05-14 08:06:26 +00:00
Domagoj KriskovicandCopybot ff53705bfa Refactor Python output pane toolbar for improved layout and styling
GitOrigin-RevId: b6d838e5c9bd8023bf12df976dad0c50564a0b2f
2026-05-14 08:06:22 +00:00
renovate[bot]Copybotrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
fc66bbfb26 [CoreI] Update dependency axios to v1.15.2 from 1.15.0 [SECURITY] (#33398)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
GitOrigin-RevId: 567d0e7463084e872187a72085714f68d84dc5b6
2026-05-14 08:06:04 +00:00
Alf EatonandCopybot d203a62834 Fix (un)fold all shortcuts on macOS (#33630)
GitOrigin-RevId: db0911cdfdeb19c90bd601e6173973d884859b09
2026-05-14 08:06:00 +00:00
Alf EatonandCopybot 6776f82952 Wait for parsing and syntax highlighting in autocomplete Cypress tests (#33667)
* Wait for parsing and syntax highlighting in autocomplete Cypress tests

* Wait for syntax highlighting in write-and-cite Cypress tests

GitOrigin-RevId: d48f10c864b0a170b4a02e95e3a989fdc4137dbb
2026-05-14 08:05:55 +00:00
Alf EatonandCopybot 00ddd8185c Upgrade webpack and related dependencies (#31638)
GitOrigin-RevId: e188a6ab9f7a024c1769a85e1d4e40ccb5d02213
2026-05-14 08:05:51 +00:00
Jakob AckermannandCopybot b62d4814c3 [monorepo] turn throw statements in callback code into callback calls (#33524)
* [eslint-plugin] add rule for throw inside callback code

* [monorepo] enable our custom eslint plugins globally

* [monorepo] fix running make lint from root

* [monorepo] turn throw statements in callback code into callback calls

* [monorepo] add eslint-plugin libraries to all the Dockerfiles

* [monorepo] install eslint-plugin library at the root level

* [linked-url-proxy] add eslint-plugin library into Dockerfile

* [latexqc] add our eslint-plugin to eslint config

GitOrigin-RevId: b05e3ebbefb62370f2422e83880dd3913815270d
2026-05-14 08:05:47 +00:00
KristinaandCopybot d8df893593 [web] rm unnecessary webpack configuration (#33587)
GitOrigin-RevId: d9f305e59af2585db096a83c4cbd41ba5f785184
2026-05-14 08:05:39 +00:00
bc2f5ae746 Reject tracked changes notifications (#32917)
* [web] Reject tracked changes notifications

feat: adding new tests

feat: adding rejected changes notifications

feat: adding tests for rejectchanges

feat: updating tests for rejecting notifications;

feat: adding in rejecting user, and improving subject and activity line

fix: moving to a params object instead of positionals for email building

feat: updating to use events triggered from applyUpdate in document-updater

feat: updating to send rejected author ids with rejected change notification instead of change ids

feat: moving rejected author notification determination to updateManager instead of RangesManager, which is used by other paths

feat: only map to author if changes were made

* fix: gate by user status not project status

* fix: unit tests post-rebase

---------

Co-authored-by: Kristina Hjertberg <kristina.hjertberg@overleaf.com>
GitOrigin-RevId: f992e1885c47d1a6cf776740769d6d4763f3cb7c
2026-05-14 08:05:35 +00:00
Andrew RumbleandCopybot 5e3561aedc Merge pull request #33655 from overleaf/lg-ip-address-resolution
Pin ip-address to 10.1.1 via resolutions (GHSA-v2v4-37r5-5v8g)

GitOrigin-RevId: c0233698549fee7f32c8a95a17b793b8535922c1
2026-05-14 08:05:30 +00:00
Jakob AckermannandCopybot 7c50dc9990 [history-v1] add endpoint for downloading latest zip (#33181)
* [history-v1] add endpoint for downloading latest zip

* [web] address review feedback

* [web] tests: do not overwrite db.projects.overleaf, extend it

* [web] set includeReferer flag from downloading zip

GitOrigin-RevId: e63e549f004230086f82eccf03b43fd62bde6071
2026-05-13 08:06:53 +00:00
b1931d0b3b [web] cleanup archived split-test assignments from user record on login (#33365)
* [web] cleanup archived split-test assignments from user record on login

Co-authored-by: Anna Claire Fields <anna.fields@overleaf.com>

* [migrations] purge archived split tests from all users

Co-authored-by: Anna Claire Fields <anna.fields@overleaf.com>

* [web] add missing mock and update snapshot test

* [web] gracefully access db.users.splitTests

---------

Co-authored-by: Anna Claire Fields <anna.fields@overleaf.com>
GitOrigin-RevId: bd185074a402556d7b7c812208cf834dd52b27a5
2026-05-13 08:06:49 +00:00
13e426b14c [web] Share third-party integration icons + add Papers icon (#33537)
* [web] Add Papers/ReadCube icon to plans page integrations

Closes #33493

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Delete old 200kB zotero logo

* Allow png use in logos

* Allow wrap

* [web] Share third-party integration icon list across plans and onboarding pages

Extract the icons array to services/web/app/src/util/third-party-icons.mjs
so the plans-2026 feature table and the try_premium onboarding page render
from a single source. The try_premium page now also includes Papers and
follows the plans page ordering.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* [web] Allow ciam try-premium logo row to wrap

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GitOrigin-RevId: f5a52418cbe01d9e343092b552183dffa3ae78bd
2026-05-13 08:06:44 +00:00
e04be692e2 [web] Use content-secondary for plan card body text (#33466)
Plan cards inherited neutral-60 (=neutral-60) for description and
include-list text, which can fail WCAG contrast on the light card
background. Switch to content-secondary (=neutral-70) per Vee's
short-term recommendation; affects the free plan description, card
include items (e.g. "Basic AI allowance") and the in-card group
picker labels.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
GitOrigin-RevId: 42aff473a779b4b4f36b6c648d86097a79f820c8
2026-05-13 08:06:40 +00:00
Eric Mc SweenandCopybot d48af6dc1b Merge pull request #33213 from overleaf/em-git-bridge-structured-logging
git-bridge: structured JSON logging for GCP Cloud Logging
GitOrigin-RevId: 7ff06202cab6fe0e35c4a4f757d0b9ad04e5431a
2026-05-13 08:06:31 +00:00
Tim DownandCopybot 85e55ebf5a Implement Briefly ad in onboarding flow (#33567)
GitOrigin-RevId: 78c7c38878024aaaf79def6ac7ca164d92a59a57
2026-05-13 08:06:02 +00:00
Tim DownandCopybot 2f25793609 Merge pull request #33353 from overleaf/td-pricing-tooltip-esc
Dismiss Bootstrap tooltips via Esc key

GitOrigin-RevId: 2368a0691fd811180f908309f99b1f9a02c225ee
2026-05-13 08:05:58 +00:00
Alf EatonandCopybot 5e94f8abce Remove -- from test scripts (#33622)
GitOrigin-RevId: a33f09fb9924e4d02de6db0550c22585b2d331b6
2026-05-13 08:05:50 +00:00
jmescuderowritefullandCopybot c383674cd8 Fix suggestions blocked modal (#33571)
GitOrigin-RevId: fde20822d884678c729ed93b672b5ad131901938
2026-05-12 08:07:03 +00:00
Eric Mc SweenandCopybot 943961ca18 Merge pull request #33539 from overleaf/em-doi-import
Library: accept DOIs in the paste references dialog
GitOrigin-RevId: bcef3cd654a1ac34f7d372930ec21116d460cd74
2026-05-12 08:06:59 +00:00
Eric Mc SweenandCopybot 569f36d01b Merge pull request #33315 from overleaf/em-library-api-pagination
Add cursor-based pagination to GET /library/references

GitOrigin-RevId: 1acec69031b0ca82ef6e1e05eddb165acaf05003
2026-05-12 08:06:55 +00:00
Eric Mc SweenandCopybot aca60c02c0 Merge pull request #33391 from overleaf/em-bibtex-projection-32449
Use a projected state field for BibTeX entries in the editor

GitOrigin-RevId: 5034be8bdc0cb4b9d854135ac117046c1b3750e7
2026-05-12 08:06:47 +00:00
Jessica LawsheandCopybot 0f3ae5ac5b Merge pull request #33335 from overleaf/jel-add-email-captured-by-group
[web] Group with domain capture takes priority over Commons when adding new email

GitOrigin-RevId: 40eb561018f4be0badf9f3885d24553c5f8bbde7
2026-05-12 08:06:37 +00:00
Jessica LawsheandCopybot ba13ccdb11 Merge pull request #33202 from overleaf/jel-domain-captured-by-group-settings-page
[Domain capture] Check `domainCapturedByGroup` for existing emails on user settings

GitOrigin-RevId: 5ac86b89969b186cce0cac410c2957e5aa1b9703
2026-05-12 08:06:33 +00:00
Jakob AckermannandCopybot 6a911e4ec3 [web] do not send a second response from api error handler (#33526)
GitOrigin-RevId: 6974f5d5f7042d5170eb2a755715b2d139f06130
2026-05-12 08:06:25 +00:00
Jakob AckermannandCopybot 1df98c028d [web] add includeReferer flag to SplitTestHandler.getAssignment (#33235)
* [web] add includeReferer flag to SplitTestHandler.getAssignment

* [web] tests: migrate User.getSplitTestAssignment to async/await

I don't want to fight with callbacks and optional arguments. Just move
it to async/await. New tests should use async/await, so there is no
point in making this work in callback-hell.

* [web] remove unused URL import

GitOrigin-RevId: 6251001e6ba7354f704fa663be8ef365ca0b9d23
2026-05-12 08:06:21 +00:00
Mathias JakobsenandCopybot 6b28a4ee5a Merge pull request #33560 from overleaf/mj-conversion-cleanup
[clsi+web] Small cleanups and improvements to conversions / exports

GitOrigin-RevId: 300adfbb91e89f754ee7f835db792ccb50b27613
2026-05-12 08:06:17 +00:00
Mathias JakobsenandCopybot 62d92b70dd Merge pull request #33341 from overleaf/mj-two-step-export-web
[web] Add two-step pandoc conversion download

GitOrigin-RevId: 093f435a497a7583d2b4d23558415cc442f84553
2026-05-12 08:06:13 +00:00
Alf EatonandCopybot 64d706f114 Revert "Wrap PDF setDocument in startViewTransition (#33346)" (#33580)
This reverts commit 353ab865de3c7872363a61592d86390dfc34dacc.

GitOrigin-RevId: dd103eb413a51861b31cd77542ca541e10df0c6c
2026-05-12 08:06:05 +00:00
renovate[bot]Copybotrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
5c5a80923a [Platform] Update dependency dompurify to v3.4.0 from 3.3.3 [SECURITY] (#33227)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
GitOrigin-RevId: da9d98ac0b4d3690bd2db18f7c4f61cf45fb379a
2026-05-12 08:05:58 +00:00
17dd108ce1 Wrap PDF setDocument in startViewTransition (#33346)
* Set scale synchronously on pagesinit to prevent 1.333 DPI flash

PDF.js resets its internal scale to 1.0 when setDocument() is called,
causing pages to momentarily render at the default 96/72 DPI scale
(1.333) before the React restore effect can apply the correct value.
Setting currentScaleValue directly in the pagesinit handler eliminates
this one-frame wrong-scale flash.

* Override .page display to block to prevent horizontal jump on recompile

Overleaf's global .loading class sets display:inline-flex, which
collides with PDF.js's transient 'loading' class on .page elements.
When the loading class is applied, inline-flex breaks margin:auto
centering, causing the page to jump horizontally. Forcing display:block
at higher specificity prevents the global rule from taking effect.

* Fix scrollToPosition offset using marginTop instead of borderWidth

scrollPageIntoView aligns the page content edge with the container top,
leaving scrollTop equal to the page's top margin (12px) rather than 0.
The previous correction used borderWidth (effectively 0) so the margin
offset was never compensated. Using marginTop scrolls back the correct
amount so the margin above the first page is visible.

* Prevent PDF viewer collapsing during recompile by preserving height

When setDocument() is called with a new PDF, _resetView() synchronously
clears all page elements, briefly collapsing the .pdfViewer div to the
viewport height. This produces a visible flicker before pagesinit fires
and pages are re-added.

Fix: record the current height and pin it as min-height on the .pdfViewer
element before calling setDocument(). A one-shot pagesinit listener
removes the constraint once the new pages are initialised at the correct
scale, by which point the element is already at its correct final height.

* Suppress PDF.js page-level loading spinner in Overleaf viewer

The PDF.js loadingIcon/loading classes briefly add a ::after pseudo-element
with display:block and contain:strict to each page div. Overleaf has its
own loading state UI so the spinner is redundant, and its activation was
the root cause of the shifts 4-5 height oscillation (the display change
broke CSS margin collapse on .pdfViewer, adding 2x page margins to its
computed height).

The display:block rule already added to .page prevents the direct cause
(Overleaf's .loading{display:inline-flex} colliding with the PDF.js class).
This rule makes the intent explicit by zeroing the ::after entirely.

* Wrap PDF setDocument in startViewTransition

---------

Co-authored-by: Brian Gough <brian.gough@overleaf.com>
GitOrigin-RevId: 353ab865de3c7872363a61592d86390dfc34dacc
2026-05-12 08:05:53 +00:00
Alf EatonandCopybot b906de86db Add info to NotFoundError (#33440)
* Add info to errors in ProjectLocator

* Update ProjectLocator.test.mjs

* Add info to errors in SSOConfigManager

* Update SSOConfigManager.test.mjs

GitOrigin-RevId: 5a13350af1808f3a16a4bc8a9946cbe8f15e6b3a
2026-05-12 08:05:49 +00:00
Alf EatonandCopybot 8be321fd73 Add @modules alias to frontend test module resolver (#33491)
GitOrigin-RevId: 929180b0887695b0d04456cfa66ccf87b4cd51c0
2026-05-12 08:05:45 +00:00
Alf EatonandCopybot c9d4edbb5b Use CSSProperties for button style prop (#33492)
GitOrigin-RevId: 0f531c58d8ec82930bd603c19af840338f0b512e
2026-05-12 08:05:41 +00:00
KristinaandCopybot b3beca0e8d [web] isolate cypress webpack caches (#33516)
GitOrigin-RevId: 65f8bf162df4fa3b67c8ac19f36fd578251e88f4
2026-05-12 08:05:24 +00:00
Tom WellsandCopybot ea94771624 Add SplitTestBadge to file tree for .bib files (#33460)
GitOrigin-RevId: 7ae109844d5d0b984eeec25ea22572b304375c47
2026-05-12 08:05:19 +00:00
renovate[bot]andCopybot 931ce5f590 [CoreII] Update bouncycastle.crypto.version to v1.84 from 1.83 [SECURITY]
GitOrigin-RevId: ce579e2bc46bb23075754ddfd0760269d11c00b0
2026-05-12 08:05:15 +00:00
Simon GardnerandCopybot 8869dd5f32 Consistently import Stripe prices with interval: 12 monthly rather than yearly. (#33536)
* ensure imported annual prices are 12-monthly
* script to convert annual prices to 12-monthly

GitOrigin-RevId: b7af88156bde510ecdf080fc97384463fa77db13
2026-05-11 08:06:24 +00:00
Andrew RumbleandCopybot 45005d2783 Merge pull request #33483 from overleaf/ar-remove-unused-sandboxed-module-deps
[monorepo] remove sandboxed-module from services that don't use it

GitOrigin-RevId: dbb9c3b11f4b5436a447942713ce02ff3efb0b50
2026-05-11 08:06:20 +00:00
Malik GlossopandCopybot 7bf7102b98 Merge pull request #32712 from overleaf/mg-recreate-corrupted-blob
Recover from corrupted blobs during hard resync

GitOrigin-RevId: 7cc764e3bcc8557689c040c8f042991d97f897bc
2026-05-11 08:06:15 +00:00
Eric Mc SweenandCopybot e0488a8d3b Merge pull request #33534 from overleaf/em-fix-autocomplete-32913
Fix autocomplete dropdown closing on blur and input re-click

GitOrigin-RevId: 82f45f0f1ae9e2b3846906d962a3f16e5b2963e4
2026-05-11 08:06:11 +00:00
Malik GlossopandCopybot 893005cb88 Merge pull request #33281 from overleaf/jd-back-to-pdf-button
Change the "close logs" icon button to a "Back to PDF" button with a label

GitOrigin-RevId: 30b8d3e314fb9b8c901b90055832b88687827e62
2026-05-11 08:05:55 +00:00
Brian GoughandCopybot 3940f8c2a7 Merge pull request #33504 from overleaf/bg-upgrade-yauzl
Upgrade yauzl library in web to version 3.3.0

GitOrigin-RevId: 82b4158db7a432f4257bd48402840f07801c6d07
2026-05-11 08:05:47 +00:00
Domagoj KriskovicandCopybot 11d35bd065 Test that pyodide package.json version matches fetch script
GitOrigin-RevId: e04ae191d2b1e5a08ba2e27518e61899d0e2d490
2026-05-11 08:05:39 +00:00
Domagoj KriskovicandCopybot 5d171066c2 Add analytics events for Python script runner
GitOrigin-RevId: 53f0fec09fc2a4ccdf1a6f77345741bed29d8a8b
2026-05-11 08:05:34 +00:00
Jimmy Domagala-TangandCopybot c37e46e1ad Add audit log entries when users max out their AI usage (#32886)
* feat: adding audit log entries when users max out their AI usage

* feat: also log when user hits quota exactly, since support wants to know that

* feat: moving audit logging to the rate limiters themselves

* feat: moving to single quota breach event with tool in info

* feat: adding audit log for ai quota tests

GitOrigin-RevId: 64056632f142a9ea22a703b7621234f93e9f6ec7
2026-05-08 08:10:23 +00:00
renovate[bot]Copybotrenovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
47f80317e4 [CoreI] Update dependency nodemailer to v8.0.5 [SECURITY] (#32703)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
GitOrigin-RevId: 3ae15cc3adad3d0212c46b5c478210dc9f20ef08
2026-05-08 08:10:18 +00:00
Eric Mc SweenandCopybot 2bb35fafb8 Merge pull request #33490 from overleaf/em-parse-req-errors-2
Reintroduce custom error types in request validation

GitOrigin-RevId: 1985ca04c8fe693fb836b042517d94700343bc46
2026-05-08 08:10:13 +00:00
jmescuderowritefullandCopybot 8c0589df7f Remove onboarding guide (#33474)
GitOrigin-RevId: a6a71cca0634ebba43f4ba18a5b40f79e3a81fb0
2026-05-08 08:09:31 +00:00
Mathias JakobsenandCopybot 32da6548c8 Merge pull request #33277 from overleaf/mj-pandoc-clsi-two-step-download
[clsi] Use clsi-nginx for downloading pandoc exports

GitOrigin-RevId: b6013fae6f53c7af714634d700ceed491d724653
2026-05-08 08:09:18 +00:00
Mathias JakobsenandCopybot ae31ad218c Merge pull request #33104 from overleaf/mj-pandoc-arguments
[clsi] Add pandoc arguments for better conversions

GitOrigin-RevId: 76cddc5959237d6d2801c56471d8d3f63d111200
2026-05-08 08:09:12 +00:00
Mathias JakobsenandCopybot 5dc67db403 Merge pull request #33089 from overleaf/ds-export-md-files-pandoc
[WEB + CLSI] Download as markdown

GitOrigin-RevId: 181eddf2513e9c5edacbab37e93f9cac2191ee1a
2026-05-08 08:09:07 +00:00
Mathias JakobsenandCopybot eddcc5a42e Merge pull request #32857 from overleaf/ds-pandoc-import-md
[WEB + CLSI] Import markdown files using pandoc

GitOrigin-RevId: adad7831ddb13a8fcb8063871166bde13cbbf1b6
2026-05-08 08:09:02 +00:00
Jakob AckermannandCopybot 44efc9d745 [monorepo] avoid corepack network requests (#33502)
* [monorepo] avoid corepack network requests

- Download yarn via corepack as the first step in all the docker files
- Turn off networking in corepack
- Do not run things in the upstream node image
  Instead, use the monorepo image, or base layer in all the services.
- Always build the base layer when running tests (uses cache)

* [monorepo] install corepack in shared place

* [clsi-lb] remove unrelated changes

* [web] add missing DC_RUN_FLAGS

* [monorepo] only rebuild test images locally

Also remove spurious build config in docker-compose.ci.yml.

* [server-ce] test: make yarn files available to host-admin and e2e

* [monorepo] put the corepack install snippet in a few more places

GitOrigin-RevId: 38005016ae5a708e12295e246269d6c18fece937
2026-05-08 08:08:57 +00:00
Chris DrydenandCopybot 95f1e711da Merge pull request #32789 from overleaf/renovate-async.http.client.version
[CoreII] Update dependency org.asynchttpclient:async-http-client to v3.0.9 from 3.0.6 [SECURITY]

GitOrigin-RevId: 0490d7653fd08d8b7f26c94a036edae69911b7fb
2026-05-08 08:08:52 +00:00
Mathias JakobsenandCopybot c2c8b1d1f1 Merge pull request #32688 from overleaf/mj-chai-messages
[overleaf-editor-core] Use chai messages instead of try-catch in fuzzing tests

GitOrigin-RevId: b6db81e2fdbaac730ddca2bfb555983685396b43
2026-05-08 08:08:39 +00:00
KristinaandCopybot bd604063e6 [web] add preferences to control all implemented notifications (#33320)
* feat: add granular controls for other features
* feat: add filtering to notifications that were missing them
* refactor: rm duplicate thread fetches
* fix: reduce permissions to fit spec (all === new comments/tracked changes, replies === only if also a participant)
* fix: include mentions in types

GitOrigin-RevId: b4a09ef59e5cf4de2e07d5b9a13c31fc1cf81a31
2026-05-08 08:08:30 +00:00
KristinaandCopybot 40954ae2dc Merge pull request #32949 from overleaf/kh-default-invitees-to-replies-only
[web] default invitees to replies only

GitOrigin-RevId: e3198403917e2679e49e27aaa87ae111675dc974
2026-05-08 08:08:17 +00:00
roo huttonandCopybot 498af9b07b Merge pull request #33467 from overleaf/rh-cio-prev-plan-type-fix
Only set previous_plan_type when normalised plan type changes

GitOrigin-RevId: 43133fc248bfb32b921da68bee91b445ca44eb1f
2026-05-08 08:08:11 +00:00
Mathias JakobsenandCopybot 0d40b7aca0 [web] Add dark mode variants to AI paywall notifications (#33469)
GitOrigin-RevId: 4c7b8fc0493b448fd565ac8b8521ee1777e60202
2026-05-07 08:08:41 +00:00
Mathias JakobsenandCopybot 76fbb56107 [web] Delay suggest fix paywall until suggest button has been clicked (#33458)
GitOrigin-RevId: 11d2ec0c9c33aea3fedff57d5f1a74d6ce774017
2026-05-07 08:08:36 +00:00
l-obrien-overleafandCopybot ff6ad4b41e Correct styling for visual bib elements (#32953)
* Correct styling for visual bib elements

* reverting outlined icon

* re-applying padding to optional fields

* citation key not shown on empty form

GitOrigin-RevId: 77c670e6687c6c60acf9f691e4c1d77e3390ac46
2026-05-07 08:08:24 +00:00
Jessica LawsheandCopybot fc4e17d30f Merge pull request #32816 from overleaf/jel-domain-captured-by-group
[web] Check `domainCapturedByGroup` on domain instead of `group.domainCaptureEnabled` only for project/dash redirect

GitOrigin-RevId: a6389da9c943327e5941eaa24eb274106526f80b
2026-05-07 08:08:07 +00:00
Miguel SerranoandCopybot d3f5738158 [web] Update pro -> premium wording (#33445)
* [web] Update `pro` -> `premium` wording

Updated translations to reflect change on
plan denominations

GitOrigin-RevId: 39e9c8257f29540d33769e960b5b81fb08c47c62
2026-05-07 08:07:59 +00:00
ilkin-overleafandCopybot faec27d7b0 Merge pull request #33163 from overleaf/ii-share-modal-give-feedback
[web] Share modal give feedback link

GitOrigin-RevId: 5e83dec6c6b97c172b7600d8ded285db49178a64
2026-05-07 08:07:54 +00:00
de9b07f0b9 [Platform] Update dependency lodash to v4.18.1 from 4.17.23 [SECURITY] (#33229)
* Upgrade lodash resolution to 4.18.1

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* update lodash in rest of packages

---------

Co-authored-by: Eric Mc Sween <5454374+emcsween@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Anna Fields <acfields11@gmail.com>
GitOrigin-RevId: 66ce1610993a592899c25155757ca3267ebcd5c1
2026-05-07 08:07:41 +00:00
roo huttonandCopybot 5c348078c2 Merge pull request #33340 from overleaf/rh-pause-block
Prevent calls to pause endpoint if pause-subscription not enabled

GitOrigin-RevId: 6efd00391576441b3104e34def2e5ad110dcc853
2026-05-07 08:07:36 +00:00
Brian GoughandCopybot a6c8ce32c3 Merge pull request #33312 from overleaf/copilot/send-clear-site-data-header
Send `Clear-Site-Data` header on account deletion

GitOrigin-RevId: c3f0b5f27cc80a1927518c56436c3a808b144fb7
2026-05-07 08:07:20 +00:00
Brian GoughandCopybot 61f480ca4e Merge pull request #33436 from overleaf/bg-fix-lint-in-codespaces
fix web linting commands to run in codespaces

GitOrigin-RevId: 106572bab1b2a4e13f1da298253c35e4af0b1316
2026-05-07 08:07:11 +00:00
Chris DrydenandCopybot df01c7e9e1 Merge pull request #33162 from overleaf/cd-upload-generated-files-to-filetree
Cd upload generated files to filetree

GitOrigin-RevId: 2d2774e57c42452fba3a2582fde7153ffcde59bf
2026-05-07 08:07:06 +00:00
Domagoj KriskovicandCopybot 8f0979d6a2 Trigger reject tracked changes notification from document-updater
GitOrigin-RevId: 9ac47490d052b3058931ca250f4090e6576f56b2
2026-05-07 08:06:57 +00:00
Domagoj KriskovicandCopybot ad58ec2c79 Consolidate duplicate Pyodide output limit tests
GitOrigin-RevId: 8ec631b740736158d6e1e75ccab90136813ffa15
2026-05-07 08:06:52 +00:00
Domagoj KriskovicandCopybot 672e01c703 Enforce output file count and size limits in the Pyodide worker
GitOrigin-RevId: 2cc61613381243d810a8cb9e1c2c32fa9e751da7
2026-05-07 08:06:46 +00:00
Jakob AckermannandCopybot d97a659f92 [web] make double compile test parameters configurable via env vars (#33406)
* [web] make double compile test parameters configurable via env vars

* [k8s] web: enable double compile test for free compiles on n4 instances

GitOrigin-RevId: 3a5cb8ed6d044fcf3f4c0d2b9d252326bac48511
2026-05-06 08:07:28 +00:00
Tim DownandCopybot c63d048dd3 Merge pull request #33422 from overleaf/td-pricing-copy-fixes
New pricing page copy fixes

GitOrigin-RevId: cef71065ce7228594c2fb58c77273a607e3ba414
2026-05-06 08:07:23 +00:00
Alf EatonandCopybot 37a68a9c5e Reapply "Add Vertex as an AI provider (#32450)" (#33339)
GitOrigin-RevId: d506c99cf32fae97b6721923256bd980120fbeed
2026-05-06 08:07:19 +00:00
CopilotandCopybot 32f5ac48c7 Use a Firefox-allowlisted Material Symbols family for unfilled editor icons (#32945)
GitOrigin-RevId: 61b7e4044f9a57a2083c5467fa0ec6eaa0f9ae1e
2026-05-06 08:07:14 +00:00
Brian GoughandCopybot d658dba53c Merge pull request #33421 from overleaf/bg-use-plain-test-output-in-ci
add NO_COLOR environment variable to web CI for cleaner output

GitOrigin-RevId: 1c089333e2002e63b62d55d0e6b4def29e844aa6
2026-05-06 08:07:10 +00:00
Eric Mc SweenandCopybot 3da2d53acb Merge pull request #33413 from overleaf/revert-33040-em-parse-req-errors
Revert "Replace isZodErrorLike with custom error types in request validation"

GitOrigin-RevId: 1f51fe9e14ffabf283f1229552d3887136420f8f
2026-05-06 08:06:41 +00:00
Eric Mc SweenandCopybot 98def949ec Merge pull request #33040 from overleaf/em-parse-req-errors
Replace isZodErrorLike with custom error types in request validation

GitOrigin-RevId: 9cb453a2cde595a00f5049e4829ea9e3dbe17b28
2026-05-06 08:06:37 +00:00
Jakob AckermannandCopybot 1aa6f03b3c [web] fix make test_unit_watch (#33370)
* [web] check for missing module tsconfig in CI

* [web] add missing tsconfig into test_unit container

GitOrigin-RevId: 7b861f8e68f961e3455b72b5660cf3825389c656
2026-05-06 08:06:26 +00:00
jmescuderowritefullandCopybot 637ff3aa88 Feedback for new quota messages in new plans (#33357)
GitOrigin-RevId: 41f06b31cd9b60844b2136bffecf966ef378c1c9
2026-05-06 08:06:21 +00:00
Davinder SinghandCopybot 8b47dedbea [WEB] Update copy for docx import (#33239)
* removing link from translation weve_converted_your_content_to_latex

* adding the translations on choose document modal

* adding beta icon

GitOrigin-RevId: b734447474e41e57efacb589aadf67e4124d4924
2026-05-06 08:06:10 +00:00
Tim DownandCopybot 77d25eed14 Merge pull request #33342 from overleaf/td-pricing-features-corrections
Fix errors in 2026 pricing page features list

GitOrigin-RevId: 2b62f3dbf0e88676f63818a6f243916178ac9373
2026-05-06 08:06:05 +00:00
Tim DownandCopybot 479e302027 Merge pull request #33372 from overleaf/td-pricing-geo-banner-margin
Add margin to geo banner in new pricing and interstitial pages

GitOrigin-RevId: 39fafa1c8a5447e20776fcee34dabc54c5ca33bc
2026-05-06 08:05:57 +00:00
Antoine ClausseandCopybot 590c2ab2e2 [web] Fix AI quota paywall upgrade CTA to use interstitial page (#33376)
* Update hrefs

* Revert link on expired subscriptions

* Revert hrefs of other buttons

* Use `plans-2026-phase-1` feature flag

GitOrigin-RevId: 3fe489c6ec192adc2fb836b07429dc2a11f9a57f
2026-05-06 08:05:53 +00:00
cff35c743f [web] Fix wrong price shown in future payments preview when upgrading over a pending downgrade (#33305)
* fix(web): show correct plan in future payments preview when upgrading over a pending downgrade

When a user had a scheduled plan downgrade and then immediately upgraded
to a higher plan, makeChangePreview() always used the pending (stale)
plan code/name/price for the future payments display rather than the
newly selected plan.

Check whether the current change is a plan change (premium-subscription
or group-plan-upgrade type) and if so use subscriptionChange's plan
details instead of pendingChange's, since the immediate upgrade overrides
the scheduled downgrade.

Closes #33299

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* test(web): add unit tests for makeChangePreview pending-change plan override

Covers the four cases: premium-subscription and group-plan-upgrade types
use subscriptionChange plan (not pendingChange), add-on-purchase type
defers to pendingChange plan, and no-pending-change falls back to
subscriptionChange as before.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
GitOrigin-RevId: cc2f9c88e5dfdfb89370798e857ea98caf8fcf85
2026-05-06 08:05:49 +00:00
Andrew RumbleandCopybot f434b1fc28 Merge pull request #33149 from overleaf/ar-ja-remove-i18next-additional-packages
[web] remove i18next additional libraries

GitOrigin-RevId: 98fc17b409090db32b02bb66953f1c2e6efee608
2026-05-06 08:05:41 +00:00
Jimmy Domagala-TangandCopybot 6a6bb625db feat: removing duplicated error messaging when user hits paywall, now leave any limit messaging to the paywall instead of also handling it in the error assist components (#33337)
GitOrigin-RevId: d3a915e75e5ba4f10c109e8e971b00a84177109a
2026-05-05 08:06:37 +00:00
Jimmy Domagala-TangandCopybot cf0f4cb339 feat: updating content and adding new variant for groups (#33289)
GitOrigin-RevId: afecbd92c6a9f224226f3918d94396e2927f104a
2026-05-05 08:06:30 +00:00
Jakob AckermannandCopybot d61e3f5521 [web] fix caching of session.analyticsId (#33300)
* [web] fix caching of session.analyticsId

* [web] disable analyticsIdMiddleware tests for Server Pro/CE

GitOrigin-RevId: 2acf76f937adabd62b1e9f876a858211ef7a13c6
2026-05-05 08:06:09 +00:00
Jakob AckermannandCopybot 37cc65ec7e [web] consolidate clsi downloads and add zod validation (#33069)
* [web] consolidate clsi downloads and add zod validation

* [validation-tools] make prettier happy

* [web] make clsiServerId optional

* [web] fix type of buildId

* [web] gracefully handle ObjectId

* [web] fix type of buildId

* [monorepo] address review feedback

- cjs export
- update module path in comments
- skip adding ?clsiserverid if not set
- allow nested output file download for submissions and add tests

* [web] address review feedback

* [web] cache one more zod schema

* [web] fix unit tests

GitOrigin-RevId: 0a1e618955983e035defd6d3c0528b81e0e85c95
2026-05-05 08:06:05 +00:00
Malik GlossopandCopybot e2de08ca86 Merge pull request #33146 from overleaf/copilot/fix-code-folding-bug
Fix code folding when a comment or blank line precedes an indented sectioning command

GitOrigin-RevId: 2a955311c1ce073b2eb80fdfbf45d00705e22d69
2026-05-05 08:05:57 +00:00
Malik GlossopandCopybot 47473bc5f4 Merge pull request #33044 from overleaf/worktree-mg-writefull-setting
Add writefull "AI Assistance" section

GitOrigin-RevId: c6d4cb60601c0b808cde96f29f6b79b26f631906
2026-05-05 08:05:53 +00:00
Maria Florencia Besteiro GonzalezandCopybot ed3f9517fd Merge pull request #33367 from overleaf/mfb-revert-revert-bibtex-loading-spinner
Reapply "BibTeX visual editor: show loading spinner when switching bib files"

GitOrigin-RevId: e71c44948b17cfa9aa9d38aa87842dba05697c38
2026-05-05 08:05:49 +00:00
ilkin-overleafandCopybot 5727643852 Merge pull request #33065 from overleaf/ii-share-modal-send-invites
[web] Add send invites input and role selection to share modal

GitOrigin-RevId: f43654e1ca0d8000b2327f1f398fd062ef1b74e4
2026-05-05 08:05:45 +00:00
KristinaandCopybot fdc939fe0a [web] emit project:membership:changed when removing self from project (#33143)
GitOrigin-RevId: 5a101add69e0077f667f98f5b95c2476ad3085d4
2026-05-05 08:05:34 +00:00
CopilotandCopybot 799dcf618b [web] Add text-align: left to .notification-content (#33142)
GitOrigin-RevId: 6470f6453f3c31e335863a67d3738972c84fc515
2026-05-05 08:05:30 +00:00
KristinaandCopybot a6ac7bdd41 [web] catch failed requests to project/doc metadata endpoints (#33139)
Unhandled promise rejections from these fire-and-forget calls were
surfacing in Sentry. Add .catch(debugConsole.error) to suppress them.

GitOrigin-RevId: a14cd0a3956a2b551210723ad56e7ec5e354a7a7
2026-05-05 08:05:26 +00:00
KristinaandCopybot d73e2b063a [web] add email notification surveys (#33063)
* feat: update survey link in settings modal
* feat: add survey link to email footer

GitOrigin-RevId: acd22281931bb98eebafa7072dca1c54d48cd972
2026-05-05 08:05:21 +00:00
Maria Florencia Besteiro GonzalezandCopybot f977a813db Merge pull request #33359 from overleaf/revert-33145-mfb-bug-switching-files-editor-not-immediately-refresh
Revert "BibTeX visual editor: show loading spinner when switching bib files"

GitOrigin-RevId: 509ba1f07e7ff418ad2a30683980100dacb77cc6
2026-05-05 08:05:17 +00:00
Maria Florencia Besteiro GonzalezandCopybot 04b6a9762f Merge pull request #33145 from overleaf/mfb-bug-switching-files-editor-not-immediately-refresh
BibTeX visual editor: show loading spinner when switching bib files

GitOrigin-RevId: 0047e6a75273e322490dde0b0fc7889b46f6d469
2026-05-04 08:06:02 +00:00
Simon GardnerandCopybot feb4f49859 [fix] use Stripe productId rather planCode when adding prices (#33344)
GitOrigin-RevId: 6e24317a0086332145c88a9be3b700b96d7a1187
2026-05-04 08:05:50 +00:00
KristinaandCopybot 9a670805c4 [scripts] handle creating custom prices (#32607)
GitOrigin-RevId: 3950698ca21de5d8ee87d0531e1e9f562aeb76f8
2026-05-04 08:05:46 +00:00
Andrew RumbleandCopybot 4954c738da Merge pull request #33328 from overleaf/ar-handle-more-than-40-saas-modules
[web] make sure acceptance tests run when there are more than 40 modules

GitOrigin-RevId: 6aad027448f2dcdc5c0a8e0bbd4120c514b9a0ca
2026-05-04 08:05:41 +00:00
Mathias JakobsenandCopybot c67885919b Merge pull request #33141 from overleaf/mj-tutorials-show-one
[web] Ensure only one tutorial shows at once

GitOrigin-RevId: 797c677a3d45635451485d79ed1c0705819ed5ad
2026-05-01 08:07:29 +00:00
Tom WellsandCopybot ffafccdba3 Don't preselect entry type when adding a BibTeX reference (#33193)
GitOrigin-RevId: a809b277fb0db8962ea0eb0e8c22af6775b2c832
2026-05-01 08:07:24 +00:00
Andrew RumbleandCopybot 30bedf3913 Merge pull request #33278 from overleaf/ar-mixpanel-autocapture
[web] mixpanel autocapture

GitOrigin-RevId: 81f6a11ae968da4c13a28e202dd3ed1113f365d4
2026-05-01 08:07:20 +00:00
Mathias JakobsenandCopybot 42f7bca37e Merge pull request #33317 from overleaf/mj-menu-bar-role
[web] Fix aria roles for nested menu bar dropdowns

GitOrigin-RevId: 1c285c2ef8ed0c589bd6b0df6112c054c8662ca4
2026-05-01 08:07:09 +00:00
Olzhas AskarandCopybot 823f11426b Merge pull request #33109 from overleaf/oa-upgrade-path
[web] Upgrade path

GitOrigin-RevId: 532993e613bdc42cf92a7b10e629aa94596d854e
2026-05-01 08:07:01 +00:00
Davinder SinghandCopybot 30e0e6adaf adding side menu for download option (#33307)
* adding side menu for download option

* fixing the E2E tests to adapt the new behaviour

GitOrigin-RevId: d96df4906a40006d36ac0ea525d74a1644ec4085
2026-05-01 08:06:57 +00:00
Alex VizcainoandCopybot 2ebe0bd513 fix: add unique key to GetPremium component in ReplacementsCard (#33303)
* fix: add unique key to GetPremium component in ReplacementsCard

* fix: update paywall messages for clarity in English and Spanish

GitOrigin-RevId: 3422ef2fbf049fe1c2cc20f6f8d224b4d67374ca
2026-05-01 08:06:49 +00:00
Antoine ClausseandCopybot 8da6222a89 [web] Fix plans CTA plain-link and edu discount clickable area overflow (#33243)
* Update .plans-cta-plain-link styles so the clickable area doesn't overflow

* Update .plans-educational-discount-label styles so the clickable area doesn't overflow

* Fix lint

GitOrigin-RevId: cedbaa78a079fd4f7cefe2be9b39252d30ba6355
2026-05-01 08:06:45 +00:00
dd44f4e2e8 [web] Remove stale "You already have a subscription" notification (#33187)
* Remove stale "You already have a subscription" notification after cancel/plan change

The notification was derived from a server-rendered meta tag set at page load,
so it persisted through cancel and plan-change flows. Now derived directly from
the URL param on the client; the param is stripped on cancel button click
(replaceState) and before plan-change reloads (location.replace via
reloadWithoutHasSubscription helper).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Fix format

* Update services/web/test/frontend/features/subscription/components/dashboard/subscription-dashboard.test.tsx

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Fix change-plan tests after location.reload → location.replace migration

reloadWithoutHasSubscription calls location.replace() not location.reload(),
so update assertions accordingly. Also stub toString() to return the jsdom
origin so FlashMessage's replaceState call doesn't throw a SecurityError.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Guard reloadWithoutHasSubscription against empty URL

When called after component unmount, useLocation's toString() returns '',
causing new URL('') to throw. No-op early to avoid the exception.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Guard against empty URL in history state replacement for subscription cancellation

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
GitOrigin-RevId: 8408ee971adf038e2d819eae5df060ace62a7e14
2026-05-01 08:06:41 +00:00
353c681d51 [web] Disable AI Assist add-on purchase for plans-2026-phase-1 users (#33178)
Users in the plans-2026-phase-1=enabled split test can no longer
purchase the AI Assist add-on via crafted HTTP requests. The preview
and purchase endpoints return 404/redirect for these users.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
GitOrigin-RevId: 2c75eb622cf44dc91019a692290ac646b51fd72c
2026-05-01 08:06:37 +00:00
Alf EatonandCopybot e3f88791da Revert "Add Vertex as an AI provider (#32450)" (#33309)
This reverts commit 20d895350ee13a7683f178bc83b87f0e765c7af6.

GitOrigin-RevId: 6be06b0fee0b038c42db45fce2377efd5d5a47dc
2026-05-01 08:06:32 +00:00
Alf EatonandCopybot f00dab5cc0 Add Vertex as an AI provider (#32450)
GitOrigin-RevId: 20d895350ee13a7683f178bc83b87f0e765c7af6
2026-05-01 08:06:25 +00:00
Alf EatonandCopybot 94018faafc Ensure tsconfig.json in all modules/*/app/src folders (#33060)
GitOrigin-RevId: 998d9c32bd11d77ee371e8bfd96fa201cf6950b2
2026-05-01 08:06:17 +00:00
Alf EatonandCopybot 664cc65ab0 Use CSF Next (#32998)
GitOrigin-RevId: 498e15e3ae44f0a7b85e889e199d71607c420e12
2026-05-01 08:06:13 +00:00
Brian GoughandCopybot c19c25d113 Merge pull request #32874 from overleaf/bg-promisify-archive-manager
Promisify ArchiveManager

GitOrigin-RevId: 7a3ee3dfb2f07dc06ee894cfce0a9196622c878e
2026-05-01 08:06:05 +00:00
Brian GoughandCopybot 8a8679eb78 Merge pull request #33297 from overleaf/bg-fix-acceptance-tests-in-dev-env
fix: update migration commands to use yarn instead of east
GitOrigin-RevId: 9cfcc393cfc1855e1edba6ba90ebb7b3a76ecb6b
2026-05-01 08:06:01 +00:00
Brian GoughandCopybot 7f7556cf6a Merge pull request #33219 from overleaf/bg-fix-collapsible-panel-flicker
Fix flicker when switching between editor and PDF views

GitOrigin-RevId: 1f6543c0046dc458fa174aa9b54985934a7437fa
2026-05-01 08:05:56 +00:00
Brian GoughandCopybot eec3be362b Merge pull request #33172 from overleaf/bg-fix-project-upload-unlinks
fix: use fsPromises ins ProjectUploadController async functions for consistency
GitOrigin-RevId: beb858d9b6cf50431fb14626dfd7cddfaf093882
2026-05-01 08:05:52 +00:00
roo huttonandCopybot 970bc85b78 Merge pull request #33247 from overleaf/rh-cio-fix-ai-group-enabled
Base group ai enabled cio attribute on group policy

GitOrigin-RevId: 2b2411aec3ffc694d2570e6031e9a876a1575e2c
2026-05-01 08:05:44 +00:00
a478c1a829 [web+project-history] Add lastResyncedAt field to projects and set after resync (#33240)
Co-authored-by: Jakob Ackermann <jakob.ackermann@overleaf.com>
GitOrigin-RevId: f679a190b88694a46f7816d51eff96446f338dec
2026-04-30 08:06:43 +00:00
50abfe8f0c [migrations] add migration for back filling db.users.analyticsId (#33115)
* [migrations] add migration for back filling db.users.analyticsId

Co-authored-by: Davinder Singh <davinder.singh@overleaf.com>

* [web] add acceptance test for backfilling db.users.analyticsId

---------

Co-authored-by: Davinder Singh <davinder.singh@overleaf.com>
GitOrigin-RevId: a0840969ac0c4c84e874c6f00cf0a78857a4bb06
2026-04-30 08:06:38 +00:00
Jakob AckermannandCopybot 4e138e6f99 [web] make labs releases available to E2E tests/Codespaces (#33242)
GitOrigin-RevId: a4e6a6bd6ffb7f3e9331aa81daf19e22b894d771
2026-04-30 08:06:34 +00:00
Jimmy Domagala-TangandCopybot 41b96ec8d6 feat: ai quotas should reset when a new plan purchase is made or upgraded (#33095)
GitOrigin-RevId: 9034800e067426fc22f8f86f9d7309699797d02e
2026-04-30 08:06:30 +00:00
Jimmy Domagala-TangandCopybot 730ff8f0ea feat: add a utilitty script for setting a users AI usage, useful for testing in staging (#33216)
GitOrigin-RevId: 71d4948235ccf37971142a4ed917cff2ef50cea9
2026-04-30 08:06:26 +00:00
Jimmy Domagala-TangandCopybot 994932b8e3 [Web + Doc-Updater] Add track changes accepted notification (#32752)
* feat: update doc manager to return a list of contributors to the accepted change

* feat: add new notification type for accepting a tracked change

* update email with tracked changes accepted

* feat: update tests

* fix: feedback on consistent api and returns

* feat: adding new tests

* feat: self accepted changes shouldnt trigger notification, and using existing changesAccepted hook

* Add better subject and activity list for track change accepted (#33094)

* feat: add better activity list entry and subject header for accepted changes, to match other notifications

* feat: updating tests

* feat: updating accepting_user_id to just user_id

* fix: adding users in emailBuilder test to userCache

GitOrigin-RevId: 6114f77916b5f503b7bbbb5ca8fed99e58edc31b
2026-04-30 08:06:19 +00:00
Olzhas AskarandCopybot fe59b9cdb5 Merge pull request #33245 from overleaf/oa-faq
[web] 2026 FAQ fixes

GitOrigin-RevId: e70a6080341ea63d3668794f50baf7538f09f55b
2026-04-30 08:06:02 +00:00
jmescuderowritefullandCopybot 35dc3ab790 Enhance AI usage messaging (#33105)
GitOrigin-RevId: 0bd325d806d945366abb6e7d8cc3cd177cb66ef2
2026-04-30 08:05:57 +00:00
Kate CrichtonandCopybot 20f08ab8c1 Merge pull request #32855 from overleaf/kc-move-token-rendering
[web] move domain verification token rendering to collapsible section

GitOrigin-RevId: c34b0f66f56c4e9942a53c2a2383d7a8cbdfdf7c
2026-04-30 08:05:48 +00:00
Kate CrichtonandCopybot e7cfcbdc66 Merge pull request #32503 from overleaf/kc-domain-verification-error-messages
[web] domain verification error messages

GitOrigin-RevId: a5a04934a8675741c4dc6a4779c2c90cbf54c04d
2026-04-30 08:05:37 +00:00
Domagoj KriskovicandCopybot 117a783f21 [web] Add .py to defaultTextExtensions
GitOrigin-RevId: 0880c01f6e3b4267f6cb790a44c6094738ab2229
2026-04-30 08:05:30 +00:00
Domagoj KriskovicandCopybot 9e677a2c1e Use overleaf CDN for loading pyodide packages
GitOrigin-RevId: e17ff3387166421a546a9519786d77ba12cdffc4
2026-04-30 08:05:23 +00:00
KristinaandCopybot a46ca0705f [web] update refund policy in zh-CN from 30 days to 14 days (#33236)
GitOrigin-RevId: 27f6b116b2cc344449417e7e32f7b93784919977
2026-04-30 08:05:18 +00:00
Chris DrydenandCopybot 6c9560cd4e Merge pull request #32943 from overleaf/cd-auto-install-python-packages
Auto-install python packages from the executing python script

GitOrigin-RevId: e343312d61e1804d927688bf4e0de00b2bdb5382
2026-04-30 08:05:14 +00:00
Olzhas AskarandCopybot 7c0595f9a9 Merge pull request #32063 from overleaf/oa-group-plans
[web] Rename group plans

GitOrigin-RevId: 8a3097dc1724709b5d7b163e0f9d968c21d63831
2026-04-30 08:05:10 +00:00
Mathias JakobsenandCopybot 92bbba5e50 Merge pull request #33046 from overleaf/mj-batch-history-resync-script
[web] Add script for batch history resyncs

GitOrigin-RevId: 2409475fa1ba12dadfaae9641a5fafdaa6c88e47
2026-04-29 08:06:12 +00:00
CopilotandCopybot e59cbc61cf Merge pull request #33134 from overleaf/copilot/fix-autocomplete-subparagraph
GitOrigin-RevId: 2ca0b9ab5de393b9b0a256c81450f57d933376fc
2026-04-29 08:06:08 +00:00
Andrew RumbleandCopybot 2f51ad5180 Merge pull request #32782 from overleaf/ar-allow-types-to-pass-through-for-parseReq
[web] fix typing of parseReq

GitOrigin-RevId: 6f10ed8682af7c999497e3a9fbd77a9d25bd7c28
2026-04-29 08:06:04 +00:00
Anna Claire FieldsandCopybot 9f8e5582d5 Fix metrics pages broken by Yarn 4 migration (#33190)
GitOrigin-RevId: 9bb3462248a16bfab425f6ebc884e9210d872b9c
2026-04-29 08:05:49 +00:00
Jakob AckermannandCopybot da11cf2669 [monorepo] fix running tests locally (#33186)
* [monorepo] fix running tests locally

* [web] fix east invocation

* [web] move corepack into entrypoint

* [web] fix running module acceptance tests locally

* [web] fix merged module targets

* [web] remove spurious change

GitOrigin-RevId: 84cf7f8d768d3429c8a98c789acf76f6cecd6e3e
2026-04-29 08:05:42 +00:00
Olzhas AskarandCopybot 396e158ecd Merge pull request #31991 from overleaf/oa-rename-student-plans
[web] Rename Individual plan names

GitOrigin-RevId: f0133fc4e06542d7c68e0a0211a2ecc32afa733d
2026-04-29 08:05:37 +00:00
Malik GlossopandCopybot da027f46cf Merge pull request #32530 from overleaf/mg-stuck-sync-repro
Detect and auto-clear stuck sync states

GitOrigin-RevId: 680861a33e42432dab7d40ad421980b707eb6089
2026-04-29 08:05:33 +00:00
Malik GlossopandCopybot ba182f8275 Merge pull request #32710 from overleaf/mg-project-history-metrics
Add diagnostic annotations to LazyStringFileData toEager errors

GitOrigin-RevId: 47575586bb869d65e4eb443cc9f1215b6f245255
2026-04-29 08:05:28 +00:00
Tim DownandCopybot 6296e7911c Merge pull request #33169 from overleaf/td-pricing-interstitial-fixes
Interstitial 2026: wire up upgrade CTAs and fix card highlights

GitOrigin-RevId: 2feea397846452ff79c7cd01c931627097a03954
2026-04-29 08:05:20 +00:00
Jakob AckermannandCopybot 68c41e9b66 [history-v1] fix re-running test_acceptance_run (#33183)
* [history-v1] fix re-running test_acceptance_run

Preserve the contents of the migrations collection. Deleting the entries
does not "undo" the migrations. On re-run east would try applying all
migrations and fail as things were already applied.

* [history-v1] remove guard migration before running tests

GitOrigin-RevId: e6eeafd58215e5148dd70c37c7a87d84e0a12bf3
2026-04-29 08:05:16 +00:00
Jakob AckermannandCopybot f3ec774e6b [github] code spaces: add extra-split-tests.json (#33015)
* [github] code spaces: add extra-split-tests.json

* [github] address review feedback

* [github] add missing bind mount

* [monorepo] mount split test configs with :ro

* [monorepo] only mount backup/split-tests into web

GitOrigin-RevId: 6334a44599a2c8bb79bf1ad698e656c8bee3992b
2026-04-29 08:05:08 +00:00
Anna Claire FieldsandCopybot 0d64a88a46 Yarn 4 Migration (#32253)
Migrates the Overleaf monorepo package manager from npm (v11) to Yarn 4 (v4.9.1) using node-modules linker mode.

GitOrigin-RevId: 50d32ab01955c15e29679eff9e9e9cfb897fab2d
2026-04-28 08:52:37 +00:00
Lucie GermainandCopybot ed0fb0110a Merge pull request #33083 from overleaf/lg-npm-audit-fix-quay-logging
Run npm audit fix --force in quay-logging

GitOrigin-RevId: 6dbca8519ce0e3077f29bec17127c00086545500
2026-04-28 08:52:27 +00:00
Eric Mc SweenandCopybot 557da351c4 Merge pull request #32755 from overleaf/em-tpds-merge-worker
Add tpdsworker functionality to third-party-datastore and redirect web traffic

GitOrigin-RevId: 94a514a6a1b10ce6126feb27ce604bcd4f42cda1
2026-04-28 08:07:40 +00:00
Brian GoughandCopybot 50648d6ed9 Merge pull request #33138 from overleaf/bg-acf-web-graceful-shutdown
[web] add fast shutdown for development environment on SIGTERM

GitOrigin-RevId: eb82171144bfe4d4f6bafa5e20773a008eeb13af
2026-04-28 08:07:35 +00:00
Brian GoughandCopybot 18b2308887 Merge pull request #32835 from overleaf/bg-fix-potential-race-condition-in-archive-manager
fix potential race condition in extractZipArchive

GitOrigin-RevId: 6dc77443e8a58172825d2b03645da05a9887e468
2026-04-28 08:07:26 +00:00
l-obrien-overleafandCopybot 6b78b42469 visual updates to optional fields in manual bib entry (#33102)
* visual updates to optional fields in manual bib entry

* omitting onClose from button auto complete props

GitOrigin-RevId: 922695e8eaec83702b482123dc4b4483fe957b78
2026-04-28 08:07:21 +00:00
Domagoj KriskovicandCopybot 7eee5809bb [pyodide] collect output files from worker and include in RunCodeResult
GitOrigin-RevId: fa9d501933ee32729e3d083183cd2a14ff969e95
2026-04-28 08:07:16 +00:00
l-obrien-overleafandCopybot 966a2f9bfe BibTeX editor: add preview screen to "Paste references" dialog (#32928)
* Adding preview panel for bibtex entry

* Removing bibtex-editor module directory and moving tests to library module

* Format fix

* Adding accessibility headers and hardening tests

* Simplfying preview logic for bibtex titles

* Only show author placeholder if no author present

* Fixing build failures

* Fixing broken test

GitOrigin-RevId: 23d2d4ff48fe8135687578cd3efdf8ba9395e411
2026-04-28 08:07:11 +00:00
Domagoj KriskovicandCopybot 4e51eee4f8 scss: use content-info variable
GitOrigin-RevId: 925d1ea2dc3496194388b6a347d1c0ab41ced52b
2026-04-28 08:07:02 +00:00
Domagoj KriskovicandCopybot e3a1bce113 Add tests for stderr output handling and execution interruption in PythonOutputPane
GitOrigin-RevId: d383e90dffbabbb597a9cf793a6fabeeff9d6b1a
2026-04-28 08:06:57 +00:00
Domagoj KriskovicandCopybot 3a232c44eb Extract OutputStream type for python runner streams
GitOrigin-RevId: 9a26c847ec7daa4cd446e9fec0407e64e6f40916
2026-04-28 08:06:52 +00:00
Domagoj KriskovicandCopybot 09af91a936 info stream
GitOrigin-RevId: 5d4cb01c250768ca00e15368b9c616b467e4f9ba
2026-04-28 08:06:47 +00:00
ilkin-overleafandCopybot 6ccbefc3f8 Merge pull request #32941 from overleaf/ii-share-modal-manage-access
[web] Share modal redesign: invited people screen

GitOrigin-RevId: bf7f83dccc245e41eca8087fc27c1b411f025b0d
2026-04-28 08:06:42 +00:00
Brian GoughandCopybot e6861ab6fa Merge pull request #33023 from overleaf/copilot/add-warning-for-unprocessed-projects
Clarify clear_deleted.js completion output for force mode and partial processing

GitOrigin-RevId: 11d9595e5b43bb8df8e1c7f664f4c08c3fbd4509
2026-04-28 08:06:36 +00:00
Simon GardnerandCopybot 8a331bc943 fix: [web] managed group admins unable to re-subscribe (#29634)
GitOrigin-RevId: 5e3d46c39f4657fcc737403a80093b870bc42240
2026-04-28 08:06:27 +00:00
Tom WellsandCopybot 73cc1b571b Add DS nav page switcher behind overleaf-library flag (#33112)
* Add DS nav page switcher behind overleaf-library flag

- Add shared DsNavPageSwitcher component (Library/Projects nav links + logo)
- Show page switcher in projects sidebar when overleaf-library flag enabled
- Hide 'All projects' filter and sidebar New Project button behind flag
- Move New Project button to content area header when flag enabled
- Prevent full page reload when clicking active nav item
- Change Upgrade button to premium variant when flag enabled
- Add overleaf-library split test to ProjectListController
- Add library-page class to remove rounded corner on /library
- Add Cypress component tests for DsNavPageSwitcher

Closes #33092

GitOrigin-RevId: 2e348da8307bf944d481b54b3a2bcc2eb319e18e
2026-04-28 08:06:22 +00:00
ilkin-overleafandCopybot 816f8c45eb Merge pull request #32924 from overleaf/ii-share-modal-basic-layout
[web] Share modal redesign base layout

GitOrigin-RevId: 0f011d03762c6669a0fd3b1fc2af32c9026c7ea4
2026-04-28 08:06:13 +00:00
ilkin-overleafandCopybot 5d3d58e8f4 Merge pull request #32801 from overleaf/ii-fix-mobile-trash-delete
[web] Fix Delete/Leave dropdown items not working on mobile trashed projects list

GitOrigin-RevId: ce7c79f0c77bb1f0df023159ee6c463c577e26e1
2026-04-28 08:06:07 +00:00
KristinaandCopybot b556fd40b5 [web] support email sender customization for email notifications (#33035)
GitOrigin-RevId: 9aa298c233c1f314ef3bdb381c20692bd0d4e212
2026-04-27 08:06:35 +00:00
KristinaandCopybot 4db3982c08 [web] rename BaseWithHeaderEmailLayout -> BaseEmailLayout (#33026)
GitOrigin-RevId: 16967d34d5128a34ec9ddf382eb664e5a8e45065
2026-04-27 08:06:31 +00:00
Eric Mc SweenandCopybot a52c47ebee Merge pull request #33090 from overleaf/em-fix-library-entry-list
Show library entry list on /library page

GitOrigin-RevId: 4336ec6803cca656092c190c3bce9f92d6923a47
2026-04-27 08:06:17 +00:00
Antoine ClausseandCopybot e8ea298ee4 [web] Update paywall copy for plans-2026 AI tiers (#33070)
* [web] Update paywall copy for plans-2026 AI tiers

Replace AI-related strings in upgrade/paywall UI when the
plans-2026-phase-1 flag is enabled:

- Add `higher_ai_allowance` ("Higher AI allowance") for paywall bullet
  lists and body text; keep `higher_ai_limits` ("Higher AI limits") for
  the subscriptions dashboard, which uses different wording per spec
- Update three locale strings to use "allowance" instead of "limits":
  `access_all_premium_features_ai`, `plus_additional_collaborators_and_more`,
  `upgrade_to_add_more_collaborators_and_more`
- Fix casing: `upgrade_to_review` "Upgrade to Review" → "Upgrade to review"
- Switch upgrade-benefits and onboarding prompt from `higher_ai_limits`
  to `higher_ai_allowance`
- Fix subscriptions free-plan to show `higher_ai_limits` instead of
  `get_unlimited_ai` under the plans2026 flag
- Fix compile-timeout button to show "Start free trial" under plans2026
  instead of "Start a free trial"
- Update two Cypress assertions for the new track-changes modal title

* Update extracted translations for higher AI allowance

* Remove "start a free trial" with exclamation

* Remove "get unlimited ai" translation entries

GitOrigin-RevId: 12300d94dc81c5407a21d4682d5714d7284c31b0
2026-04-27 08:06:13 +00:00
8fcf007a73 [web] plans-2026: responsive card layout (mobile 1-col, md 2-col, lg 4-col) (#32905)
* plans-2026: wrap cards to 2-col at md, 4-col at lg

Replaces horizontal scroll at md/lg with a wrapping CSS grid so cards
stack in rows instead of requiring horizontal scrolling. The 4-column
layout now kicks in at lg (~992px) rather than xl (~1200px).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* plans-2026: replace mobile horizontal scroll with 1-col stack

Removes the overflow-x scroll behaviour at xs/sm (and the
scrollbar-overlap margin/padding hack that came with it). Cards now
stack in a single column on small screens, consistent with the
2-col/4-col grid above.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Fix lint

* plans-2026: fix student mode 2-col at lg, remove mobile min-width

Student-mode 2-col override was only kicking in at xl; move it to lg so
the two visible cards (free + student) fill the row correctly. Also drop
the 280px min-width on .plans-card-col — now that mobile uses a 1-col
grid instead of horizontal scroll, it causes unnecessary overflow on
narrow viewports.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* Update breakpoint for plans cards layout to lg for improved responsiveness

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
GitOrigin-RevId: 5ef923dd3795353bd946c1857e78f4b5f7063ab0
2026-04-27 08:06:09 +00:00
Tim DownandCopybot fb6e2f17cb Initial version of 2026 interstitial plans page with upgrade mode (#33036)
GitOrigin-RevId: e9da7c1464c6e05e9c70a11d9ad7a3632f189de2
2026-04-27 08:05:55 +00:00
Jakob AckermannandCopybot 633705e604 [server-ce] tests: extend debug logs (#33098)
GitOrigin-RevId: 5283eb8ab39f94ae8389cc4ccc3f288d401d3e52
2026-04-27 08:05:51 +00:00
Tim DownandCopybot 901ec15bc1 Merge pull request #32916 from overleaf/td-pricing-features-follow-up
2026 pricing page feature comparison table polish

GitOrigin-RevId: 5ebad4f1bd7c296dff2277a7f2ddd6324597b244
2026-04-27 08:05:46 +00:00
Mathias JakobsenandCopybot c46fba951e [web] Re-introduce orphan detection in detached PDF (#32994)
GitOrigin-RevId: 07a58d6f7e3c6db8465c62b390e34270c2b4fd67
2026-04-27 08:05:42 +00:00
Mathias JakobsenandCopybot 5e675664c6 [web] Cleanup PDFjs instances in visual editor (#33022)
GitOrigin-RevId: 2aa9ab01f88196fb56dc41749977ca33295c964f
2026-04-27 08:05:38 +00:00
jmescuderowritefullandCopybot 5c7db11d9b fix: fixes for new plans (#33051)
GitOrigin-RevId: 07ff096da2bc72483eab9a5c57417ced950f56c4
2026-04-27 08:05:34 +00:00
CopilotCopybotdas7padcopilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
713a0c15ef Remove prefetch of 5.0.1-RC1 image in Server Pro E2E tests (#32991)
* Initial plan

* Remove prefetch of 5.0.1-RC1 image in Server Pro E2E tests

Agent-Logs-Url: https://github.com/overleaf/internal/sessions/db75e124-b3a1-463a-a41b-bd570af482ef

Co-authored-by: das7pad <17931887+das7pad@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: das7pad <17931887+das7pad@users.noreply.github.com>
GitOrigin-RevId: 9afbba3495706ed11a9392979138c06386b66c88
2026-04-24 08:07:18 +00:00
Eric Mc SweenandCopybot 60cc551c4d Merge pull request #33085 from overleaf/revert-32956-em-library-entry-list
Revert "Show library entry list on /library page"

GitOrigin-RevId: c2bb4d240f5e07c5a3ddfca62fc5749a3e5c56ab
2026-04-24 08:07:13 +00:00
Eric Mc SweenandCopybot 2fdecb5d19 Merge pull request #32956 from overleaf/em-library-entry-list
Show library entry list on /library page

GitOrigin-RevId: 6c1c4e3ef66ea002525e5d5adb7943123f5d2587
2026-04-24 08:07:08 +00:00
Andrew RumbleandCopybot 54d953cfff Merge pull request #32743 from overleaf/ar-new-v1-api
[web] new v1 api client

GitOrigin-RevId: 7ba0deef0d10526198a2a6ba997c2dcff7b7e5a5
2026-04-24 08:07:03 +00:00
Olzhas AskarandCopybot d3b60bcb46 Merge pull request #31750 from overleaf/oa-update-student-features
[web] Update student collaborators

GitOrigin-RevId: fa4631c4d6e95eaa4823d1d5db125231c6cd0c00
2026-04-24 08:06:58 +00:00
Maria Florencia Besteiro GonzalezandCopybot 879c082587 Merge pull request #32923 from overleaf/mfb-replace-placeholders-with-helper-text-bibtex-form
[web] Replace placeholders with helper text in BibTeX entry form

GitOrigin-RevId: b7d4fef0414bc79abda6fba2c4c9594edd9f05fc
2026-04-24 08:06:53 +00:00
Jessica LawsheandCopybot 7ff114bbef Merge pull request #32396 from overleaf/jel-unlink-sso
[web] Add button so user can unlink Commons SSO

GitOrigin-RevId: 46e0607549341a98beca3873ea63bf091a883e85
2026-04-24 08:06:48 +00:00
Miguel SerranoandCopybot 08701ca5aa [web] Add update_group_policy script (#31715)
* [web]  Add `update_group_policy` script

The script allows to update a policy for a subscription, or update
multiple policies via a file containing a list of subscription ids

GitOrigin-RevId: f3b3a6666eb62bb93d13c4c75d60b99f21f23db4
2026-04-24 08:06:35 +00:00
Davinder SinghandCopybot be5a7b56c8 [WEB + CLSI] Download as docx file feature (#32851)
* using CLSI logic for fetching the project contents and skip the .zip export

* Use unique conversion directory for project-to-docx export to avoid corrupting the shared compile
  directory when a compile runs concurrently

* Remove X-Accel-Buffering header — not needed as CLSI does not run behind nginx

* moving log before sending the data

* Return CLSI stream directly instead of buffering to disk on web

  Previously convertProjectToDocx wrote the CLSI response to a temp file
  on disk, then the controller read it back to stream to the client.
  Now the stream is returned directly and piped to the response,
  avoiding unnecessary disk I/O on the web server.

* Use href redirect for docx export instead of fetching blob into memory

* making functions and files more generic so they can be used in future for other documents exports as well

* adding export-docx split test

* adding unit tests

* adding cypress E2E test

* format:fix

* renaming the route to download from convert

* adding new icon for export docx button

* format:fix

* remove unused showExportDocumentErrorToast export and adding guard against invalid Content-Length header from CLSI

* format:fix

* refactor(clsi): move promisify(parse) into RequestParser

* refactor: generic conversion endpoint with type as route
  param

* refactor: use type→extension map for validated conversion types

* refactor(clsi): remove --standalone flag and fix rejection test

* fixing the href in cypress test

* renaming function

* adding type to Metrics.inc

* fix: rename exportProjectDocument, add WithLock wrapper and metrics type label

* format:fix

* fix: hide docx export from anonymous users and add WithLock wrapper

* format fix

* remove redundant Content-Length validation from DocumentConversionManager

* format:fix

* removing trailing icon

GitOrigin-RevId: e9764fefac2c4b625d23be9e942ea4a8b283c70d
2026-04-24 08:06:10 +00:00
KristinaandCopybot b6ec7945f4 [web] update copy in email notifications (#32912)
* add footerMessage to base email template
* add customized subject line and CTA
* add _getBundledActivityList

GitOrigin-RevId: e70c0955485b0892f31e20daa0430faef80b0d64
2026-04-23 08:07:01 +00:00
KristinaandCopybot a64d1bbb6a [web] display global off treatment in settings modal (#32942)
* display global disabled state
* show loading indicator while project notification preferences load

GitOrigin-RevId: d7e905aaa3fc7b15b54bf99caeabf60c1e5d8050
2026-04-23 08:06:56 +00:00
KristinaandCopybot 9a129e7cab Merge pull request #32939 from overleaf/kh-update-settings-modal-language
[web] update copy & layout for notifications settings

GitOrigin-RevId: 13b6b78b0b2bddd9d0137362a14bfdbdba9d7d20
2026-04-23 08:06:47 +00:00
Eric Mc SweenandCopybot 6ad758830a Merge pull request #32555 from overleaf/em-bibtex-warnings
Fix duplicate errors in BibTeX entry list tooltip

GitOrigin-RevId: de7214a7f5d403552505f960f140768bb4f2c1ca
2026-04-23 08:06:31 +00:00
Jakob AckermannandCopybot 5c317644bd [server-ce] test: Add debugging in Jenkins (#33019)
GitOrigin-RevId: dd38d580109e870a966d1442a7c4133cd05ca3a3
2026-04-23 08:06:22 +00:00
Brian GoughandCopybot faf2fd2287 Merge pull request #32996 from overleaf/copilot/fix-race-condition-in-stopcompile
Stabilize stopCompile Cypress helper by waiting for enabled “Stop compilation” action

GitOrigin-RevId: 16997aaccd8d65d5ff0a0a1af73a0bf5d6803832
2026-04-23 08:06:17 +00:00
roo huttonandCopybot dece22ba92 Merge pull request #32871 from overleaf/rh-cio-comms-attributes
Expose remaining marketing properties to customer.io

GitOrigin-RevId: 6956e1faf90ecc650108404fe13b2f6de2eb4d0c
2026-04-23 08:06:04 +00:00
Mathias JakobsenandCopybot 2e5d7675ba Merge pull request #32983 from overleaf/mj-change-layout-button-tests
[web] Add tests for ChangeLayoutButton

GitOrigin-RevId: 30299625fc7f3977eedcb86a99937e3a9321bd2c
2026-04-22 08:06:54 +00:00
Mathias JakobsenandCopybot 1d37483e73 Merge pull request #32984 from overleaf/mj-test-runner-cypress-env
[skills] Describe cypress environment variables for running specific cypress tests

GitOrigin-RevId: db64ed3f0a4d9ecbe6b7d63c9275314da0df7421
2026-04-22 08:06:49 +00:00
Anna Claire FieldsandCopybot 36a8447bb3 update handlebars: package json and package lock (#32963)
GitOrigin-RevId: b39d2189f08b76ed61b14e77f2af20f36c9a2968
2026-04-22 08:06:44 +00:00
Alf EatonandCopybot b378f2b094 Remove flaky "doc version recovery" SaaS E2E Cypress test (#32986)
GitOrigin-RevId: b5fe75ee45539fabac2a05177a9e6fce36087f70
2026-04-22 08:06:39 +00:00
Alf EatonandCopybot fdadfaab45 Remove non-LaTeX file extensions (#32339)
GitOrigin-RevId: 370a27838ddc017b5b0926e47d550729d54d73aa
2026-04-22 08:06:35 +00:00
Alf EatonandCopybot ce4ca192ee Upgrade Storybook to v10 (#30442)
GitOrigin-RevId: 9f51624bc2b34b6746d1854969173b44c9c9cf9a
2026-04-22 08:06:26 +00:00
Alf EatonandCopybot f45eec25f4 Upgrade Cypress (#28858)
GitOrigin-RevId: a5bb02d4f13e5d1aa4426debd4861205d51597f3
2026-04-22 08:06:21 +00:00
Brian GoughandCopybot f07aaf1979 Merge pull request #32954 from overleaf/fix/security-oauth2-server-web
[web] upgrade @node-oauth/oauth2-server to 5.3.0

GitOrigin-RevId: e6b25a6fb3dfaef1e8690fc1dd434daab35b798d
2026-04-22 08:06:12 +00:00
Alexandre BourdinandCopybot 77b9d2fd1b Merge pull request #32959 from overleaf/ab-fix-32861-spam-safe-project-name
[saas-e2e] Fix project name generation still producing + and / characters

GitOrigin-RevId: 2367c8d45ee9ec4441e9ee2a1d74c2fd281f9e90
2026-04-22 08:06:04 +00:00
Jakob AckermannandCopybot 926d6bccd7 [clsi] reduce write traffic to clsi-cache from free users (#32958)
GitOrigin-RevId: c01cc21c3c82f361c7d82843e65bb087b21434ed
2026-04-22 08:05:59 +00:00
Andrew RumbleandCopybot 98dbb48c59 Merge pull request #32797 from overleaf/upgrade/path-to-regexp-0.1.13-8.4.0
[monorepo] upgrade path-to-regexp

GitOrigin-RevId: 27a6e32594957407807e88c5d9eb62e9399ccaf5
2026-04-22 08:05:50 +00:00
Andrew RumbleandCopybot bddc1d3fab Merge pull request #32906 from overleaf/ar-upgrade-protobufjs-7.5.5
[monorepo] upgrade protobufjs 7.5.5

GitOrigin-RevId: 7a8038737f8160e55031fa0365242f22c2dd97f5
2026-04-22 08:05:45 +00:00
Andrew RumbleandCopybot b98688b190 Merge pull request #32795 from overleaf/upgrade/defu-6.1.5
[monorepo] upgrade defu to 6.1.5

GitOrigin-RevId: db1fb6f0150b67785760726d9c681703061d729d
2026-04-22 08:05:40 +00:00
851 changed files with 66772 additions and 63756 deletions
@@ -0,0 +1,16 @@
diff --git a/dist/index.mjs b/dist/index.mjs
index b781835b37a1262510bdd66d8d3b399a076e9d68..312c314186d85bb3bc2ab2620e99bca259ef7167 100644
--- a/dist/index.mjs
+++ b/dist/index.mjs
@@ -44,10 +44,9 @@ import { z as z2 } from "zod/v4";
// src/tool/types.ts
import { z } from "zod/v4";
-var LATEST_PROTOCOL_VERSION = "2025-11-25";
+var LATEST_PROTOCOL_VERSION = "2025-06-18";
var SUPPORTED_PROTOCOL_VERSIONS = [
LATEST_PROTOCOL_VERSION,
- "2025-06-18",
"2025-03-26",
"2024-11-05"
];
@@ -0,0 +1,831 @@
diff --git a/dist/index.cjs b/dist/index.cjs
index 39215ae..b44cb76 100644
--- a/dist/index.cjs
+++ b/dist/index.cjs
@@ -187,16 +187,23 @@ Helper function that returns a transaction spec which inserts a
completion's text in the main selection range, and any other
selection range that has the same text in front of it.
*/
-function insertCompletionText(state$1, text, from, to) {
+function insertCompletionText(state$1, text, from, to, extend) {
let { main } = state$1.selection, fromOff = from - main.from, toOff = to - main.from;
return Object.assign(Object.assign({}, state$1.changeByRange(range => {
if (range != main && from != to &&
state$1.sliceDoc(range.from + fromOff, range.from + toOff) != state$1.sliceDoc(from, to))
return { range };
- let lines = state$1.toText(text);
+ let change = {
+ from: range.from + fromOff,
+ to: to == main.from ? range.to : range.from + toOff,
+ insert: text instanceof state.Text ? text : state$1.toText(text),
+ };
+ if (extend) {
+ extend(state$1, change);
+ }
return {
- changes: { from: range.from + fromOff, to: to == main.from ? range.to : range.from + toOff, insert: lines },
- range: state.EditorSelection.cursor(range.from + fromOff + lines.length)
+ changes: change,
+ range: state.EditorSelection.cursor(change.from + change.insert.length)
};
})), { scrollIntoView: true, userEvent: "input.complete" });
}
@@ -389,7 +396,9 @@ const completionConfig = state.Facet.define({
filterStrict: false,
compareCompletions: (a, b) => a.label.localeCompare(b.label),
interactionDelay: 75,
- updateSyncTime: 100
+ updateSyncTime: 100,
+ // overleaf: default to at top which is default CM6 behaviour
+ unfilteredResultsAtEnd: false
}, {
defaultKeymap: (a, b) => a && b,
closeOnBlur: (a, b) => a && b,
@@ -744,6 +753,7 @@ function score(option) {
(option.type ? 1 : 0);
}
function sortOptions(active, state) {
+ var _a;
let options = [];
let sections = null;
let addOption = (option) => {
@@ -763,7 +773,8 @@ function sortOptions(active, state) {
let getMatch = a.result.getMatch;
if (a.result.filter === false) {
for (let option of a.result.options) {
- addOption(new Option(option, a.source, getMatch ? getMatch(option) : [], 1e9 - options.length));
+ let defaultScore = conf.unfilteredResultsAtEnd ? -1e9 : 1e9;
+ addOption(new Option(option, a.source, getMatch ? getMatch(option) : [], defaultScore - options.length));
}
}
else {
@@ -790,15 +801,42 @@ function sortOptions(active, state) {
}
}
let result = [], prev = null;
+ const priorityIndices = new Map();
let compare = conf.compareCompletions;
for (let opt of options.sort((a, b) => (b.score - a.score) || compare(a.completion, b.completion))) {
+ // overleaf: Deduplicate results with dedup options
+ // The goal is to keep only the highest priority option, in the
+ // highest scoring position.
+ const key = (_a = opt.completion.deduplicate) === null || _a === void 0 ? void 0 : _a.key;
+ if (key) {
+ // Handle merging specifically for deduplicated items item
+ const currentOptionIndex = priorityIndices.get(key);
+ if (currentOptionIndex === undefined) {
+ priorityIndices.set(key, result.length);
+ result.push(opt);
+ prev = opt.completion;
+ }
+ else {
+ if (result[currentOptionIndex].completion.deduplicate.priority < opt.completion.deduplicate.priority) {
+ result[currentOptionIndex] = opt;
+ if (currentOptionIndex === result.length - 1) {
+ prev = opt.completion;
+ }
+ }
+ }
+ continue;
+ }
+ // overleaf: end
let cur = opt.completion;
- if (!prev || prev.label != cur.label || prev.detail != cur.detail ||
- (prev.type != null && cur.type != null && prev.type != cur.type) ||
- prev.apply != cur.apply || prev.boost != cur.boost)
+ if (!prev || prev.label != cur.label)
+ result.push(opt);
+ // overleaf: we're already handling deduplication, so skip extra merges
+ else if (prev.deduplicate)
result.push(opt);
else if (score(opt.completion) > score(prev))
result[result.length - 1] = opt;
+ else if (opt.completion.info)
+ result[result.length - 1] = opt;
prev = opt.completion;
}
return result;
@@ -817,8 +855,9 @@ class CompletionDialog {
: new CompletionDialog(this.options, makeAttrs(id, selected), this.tooltip, this.timestamp, selected, this.disabled);
}
static build(active, state, id, prev, conf, didSetActive) {
- if (prev && !didSetActive && active.some(s => s.isPending))
- return prev.setDisabled();
+ // Overleaf: avoid setting the previous completion state to disabled while completion sources are pending
+ // if (prev && !didSetActive && active.some(s => s.isPending))
+ // return prev.setDisabled()
let options = sortOptions(active, state);
if (!options.length)
return prev && active.some(a => a.isPending) ? prev.setDisabled() : null;
@@ -1017,13 +1056,14 @@ const completionState = state.StateField.define({
view.EditorView.contentAttributes.from(f, state => state.attrs)
]
});
+const getCompletionTooltip = (state) => { var _a; return (_a = state.field(completionState, false)) === null || _a === void 0 ? void 0 : _a.tooltip; };
function applyCompletion(view, option) {
const apply = option.completion.apply || option.completion.label;
let result = view.state.field(completionState).active.find(a => a.source == option.source);
if (!(result instanceof ActiveResult))
return false;
if (typeof apply == "string")
- view.dispatch(Object.assign(Object.assign({}, insertCompletionText(view.state, apply, result.from, result.to)), { annotations: pickedCompletion.of(option.completion) }));
+ view.dispatch(Object.assign(Object.assign({}, insertCompletionText(view.state, apply, result.from, result.to, option.completion.extend)), { annotations: pickedCompletion.of(option.completion) }));
else
apply(view, option.completion, result.from, result.to);
return true;
@@ -1559,20 +1599,42 @@ interpreted as indicating a placeholder.
function snippet(template) {
let snippet = Snippet.parse(template);
return (editor, completion, from, to) => {
- let { text, ranges } = snippet.instantiate(editor.state, from);
- let { main } = editor.state.selection;
- let spec = {
- changes: { from, to: to == main.from ? main.to : to, insert: state.Text.of(text) },
- scrollIntoView: true,
- annotations: completion ? [pickedCompletion.of(completion), state.Transaction.userEvent.of("input.complete")] : undefined
- };
+ let { main } = editor.state.selection, fromOff = from - main.from, toOff = to - main.from;
+ let ranges = [];
+ let totalOffset = 0;
+ let spec = Object.assign(Object.assign({}, editor.state.changeByRange(range => {
+ if (range != main && from != to &&
+ editor.state.sliceDoc(range.from + fromOff, range.from + toOff) != editor.state.sliceDoc(from, to))
+ return { range };
+ let { text, ranges: fieldRanges } = snippet.instantiate(editor.state, range.from + fromOff);
+ let change = {
+ from: range.from + fromOff,
+ to: range.from + toOff,
+ insert: state.Text.of(text)
+ };
+ let originalTo = change.to;
+ let offset = change.insert.length + fromOff;
+ if (completion.extend) {
+ completion.extend(editor.state, change);
+ offset += originalTo - change.to;
+ }
+ for (const fieldRange of fieldRanges) {
+ ranges.push(new FieldRange(fieldRange.field, fieldRange.from + totalOffset, fieldRange.to + totalOffset));
+ }
+ totalOffset += offset;
+ return {
+ changes: change,
+ range: state.EditorSelection.cursor(change.from + change.insert.length)
+ };
+ })), { scrollIntoView: true, annotations: completion ? [pickedCompletion.of(completion), state.Transaction.userEvent.of("input.complete")] : undefined, effects: [] });
if (ranges.length)
spec.selection = fieldSelection(ranges, 0);
if (ranges.some(r => r.field > 0)) {
let active = new ActiveSnippet(ranges, 0);
- let effects = spec.effects = [setActive.of(active)];
- if (editor.state.field(snippetState, false) === undefined)
- effects.push(state.StateEffect.appendConfig.of([snippetState, addSnippetKeymap, snippetPointerHandler, baseTheme]));
+ spec.effects.push(setActive.of(active));
+ if (editor.state.field(snippetState, false) === undefined) {
+ spec.effects.push(state.StateEffect.appendConfig.of([snippetState, addSnippetKeymap, snippetPointerHandler, baseTheme]));
+ }
}
editor.dispatch(editor.state.update(spec));
};
@@ -1746,7 +1808,8 @@ const completeAnyWord = context => {
const defaults = {
brackets: ["(", "[", "{", "'", '"'],
before: ")]}:;>",
- stringPrefixes: []
+ stringPrefixes: [],
+ buildInsert: (state, range, open, close) => open + close,
};
const closeBracketEffect = state.StateEffect.define({
map(value, mapping) {
@@ -1854,8 +1917,8 @@ function insertBracket(state$1, bracket) {
for (let tok of tokens) {
let closed = closing(state.codePointAt(tok, 0));
if (bracket == tok)
- return closed == tok ? handleSame(state$1, tok, tokens.indexOf(tok + tok + tok) > -1, conf)
- : handleOpen(state$1, tok, closed, conf.before || defaults.before);
+ return closed == tok ? handleSame(state$1, tok, tokens.indexOf(tok + tok) > -1, tokens.indexOf(tok + tok + tok) > -1, conf)
+ : handleOpen(state$1, tok, closed, conf.before || defaults.before, conf);
if (bracket == closed && closedBracketAt(state$1, state$1.selection.main.from))
return handleClose(state$1, tok, closed);
}
@@ -1877,17 +1940,21 @@ function prevChar(doc, pos) {
let prev = doc.sliceString(pos - 2, pos);
return state.codePointSize(state.codePointAt(prev, 0)) == prev.length ? prev : prev.slice(1);
}
-function handleOpen(state$1, open, close, closeBefore) {
+function handleOpen(state$1, open, close, closeBefore, config) {
+ let buildInsert = config.buildInsert || defaults.buildInsert;
let dont = null, changes = state$1.changeByRange(range => {
+ var _a;
if (!range.empty)
return { changes: [{ insert: open, from: range.from }, { insert: close, from: range.to }],
effects: closeBracketEffect.of(range.to + open.length),
range: state.EditorSelection.range(range.anchor + open.length, range.head + open.length) };
let next = nextChar(state$1.doc, range.head);
- if (!next || /\s/.test(next) || closeBefore.indexOf(next) > -1)
- return { changes: { insert: open + close, from: range.head },
- effects: closeBracketEffect.of(range.head + open.length),
+ if (!next || /\s/.test(next) || closeBefore.indexOf(next) > -1) {
+ const insert = (_a = buildInsert(state$1, range, open, close)) !== null && _a !== void 0 ? _a : open + close;
+ return { changes: { insert, from: range.head },
+ effects: insert === open ? [] : closeBracketEffect.of(range.head + open.length),
range: state.EditorSelection.cursor(range.head + open.length) };
+ }
return { range: dont = range };
});
return dont ? null : state$1.update(changes, {
@@ -1909,18 +1976,36 @@ function handleClose(state$1, _open, close) {
}
// Handles cases where the open and close token are the same, and
// possibly triple quotes (as in `"""abc"""`-style quoting).
-function handleSame(state$1, token, allowTriple, config) {
+function handleSame(state$1, token, allowDouble, allowTriple, config) {
let stringPrefixes = config.stringPrefixes || defaults.stringPrefixes;
+ let buildInsert = config.buildInsert || defaults.buildInsert;
let dont = null, changes = state$1.changeByRange(range => {
+ var _a, _b, _c;
if (!range.empty)
return { changes: [{ insert: token, from: range.from }, { insert: token, from: range.to }],
effects: closeBracketEffect.of(range.to + token.length),
range: state.EditorSelection.range(range.anchor + token.length, range.head + token.length) };
let pos = range.head, next = nextChar(state$1.doc, pos), start;
- if (next == token) {
+ if (allowTriple && state$1.sliceDoc(pos - 2 * token.length, pos) == token + token &&
+ (start = canStartStringAt(state$1, pos - 2 * token.length, stringPrefixes)) > -1 &&
+ nodeStart(state$1, start)) {
+ return { changes: { insert: token + token + token + token, from: pos },
+ effects: closeBracketEffect.of(pos + token.length),
+ range: state.EditorSelection.cursor(pos + token.length) };
+ }
+ else if (allowDouble && state$1.sliceDoc(pos - token.length, pos) == token &&
+ (start = canStartStringAt(state$1, pos - token.length, stringPrefixes)) > -1 &&
+ nodeStart(state$1, start)) {
+ let insert = (_a = buildInsert(state$1, range, token, token)) !== null && _a !== void 0 ? _a : token + token;
+ return { changes: { insert, from: pos },
+ effects: insert === token ? [] : closeBracketEffect.of(pos + token.length),
+ range: state.EditorSelection.cursor(pos + token.length) };
+ }
+ else if (next == token) {
if (nodeStart(state$1, pos)) {
- return { changes: { insert: token + token, from: pos },
- effects: closeBracketEffect.of(pos + token.length),
+ let insert = (_b = buildInsert(state$1, range, token, token)) !== null && _b !== void 0 ? _b : token + token;
+ return { changes: { insert, from: pos },
+ effects: insert === token ? [] : closeBracketEffect.of(pos + token.length),
range: state.EditorSelection.cursor(pos + token.length) };
}
else if (closedBracketAt(state$1, pos)) {
@@ -1930,18 +2015,13 @@ function handleSame(state$1, token, allowTriple, config) {
range: state.EditorSelection.cursor(pos + content.length) };
}
}
- else if (allowTriple && state$1.sliceDoc(pos - 2 * token.length, pos) == token + token &&
- (start = canStartStringAt(state$1, pos - 2 * token.length, stringPrefixes)) > -1 &&
- nodeStart(state$1, start)) {
- return { changes: { insert: token + token + token + token, from: pos },
- effects: closeBracketEffect.of(pos + token.length),
- range: state.EditorSelection.cursor(pos + token.length) };
- }
else if (state$1.charCategorizer(pos)(next) != state.CharCategory.Word) {
- if (canStartStringAt(state$1, pos, stringPrefixes) > -1 && !probablyInString(state$1, pos, token, stringPrefixes))
- return { changes: { insert: token + token, from: pos },
- effects: closeBracketEffect.of(pos + token.length),
+ if (canStartStringAt(state$1, pos, stringPrefixes) > -1 && !probablyInString(state$1, pos, token, stringPrefixes)) {
+ const insert = (_c = buildInsert(state$1, range, token, token)) !== null && _c !== void 0 ? _c : token + token;
+ return { changes: { insert, from: pos },
+ effects: insert === token ? [] : closeBracketEffect.of(pos + token.length),
range: state.EditorSelection.cursor(pos + token.length) };
+ }
}
return { range: dont = range };
});
@@ -2086,6 +2166,7 @@ exports.completionKeymap = completionKeymap;
exports.completionStatus = completionStatus;
exports.currentCompletions = currentCompletions;
exports.deleteBracketPair = deleteBracketPair;
+exports.getCompletionTooltip = getCompletionTooltip;
exports.hasNextSnippetField = hasNextSnippetField;
exports.hasPrevSnippetField = hasPrevSnippetField;
exports.ifIn = ifIn;
@@ -2093,8 +2174,10 @@ exports.ifNotIn = ifNotIn;
exports.insertBracket = insertBracket;
exports.insertCompletionText = insertCompletionText;
exports.moveCompletionSelection = moveCompletionSelection;
+exports.nextChar = nextChar;
exports.nextSnippetField = nextSnippetField;
exports.pickedCompletion = pickedCompletion;
+exports.prevChar = prevChar;
exports.prevSnippetField = prevSnippetField;
exports.selectedCompletion = selectedCompletion;
exports.selectedCompletionIndex = selectedCompletionIndex;
diff --git a/dist/index.d.cts b/dist/index.d.cts
index b57b8f6..fce47ab 100644
--- a/dist/index.d.cts
+++ b/dist/index.d.cts
@@ -1,6 +1,6 @@
import * as _codemirror_state from '@codemirror/state';
-import { EditorState, ChangeDesc, TransactionSpec, Transaction, StateCommand, Facet, Extension, StateEffect } from '@codemirror/state';
-import { EditorView, Rect, KeyBinding, Command } from '@codemirror/view';
+import { EditorState, Text, ChangeDesc, TransactionSpec, StateCommand, Transaction, Facet, SelectionRange, Extension, StateEffect } from '@codemirror/state';
+import { EditorView, Rect, KeyBinding, Tooltip, Command } from '@codemirror/view';
import * as _lezer_common from '@lezer/common';
/**
@@ -73,6 +73,19 @@ interface Completion {
a `{name}` object.
*/
section?: string | CompletionSection;
+ /**
+ Can be used to alter the change created when the completion is applied
+ */
+ extend?: ExtendCompletion;
+ /**
+ If multiple sources return the same result, use this field to specifiy a
+ deduplication key as well as a priority. For each unique key, only the
+ completion with the highest priority will be shown.
+ */
+ deduplicate?: {
+ key: string;
+ priority: number;
+ };
}
/**
The type returned from
@@ -306,12 +319,17 @@ This annotation is added to transactions that are produced by
picking a completion.
*/
declare const pickedCompletion: _codemirror_state.AnnotationType<Completion>;
+type ExtendCompletion = (state: EditorState, change: {
+ from: number;
+ to: number;
+ insert: string | Text;
+}) => void;
/**
Helper function that returns a transaction spec which inserts a
completion's text in the main selection range, and any other
selection range that has the same text in front of it.
*/
-declare function insertCompletionText(state: EditorState, text: string, from: number, to: number): TransactionSpec;
+declare function insertCompletionText(state: EditorState, text: string | Text, from: number, to: number, extend?: ExtendCompletion): TransactionSpec;
interface CompletionConfig {
/**
@@ -441,6 +459,10 @@ interface CompletionConfig {
milliseconds.
*/
updateSyncTime?: number;
+ /**
+ overleaf: Move unfiltered results after the filtered ones
+ */
+ unfilteredResultsAtEnd?: boolean;
}
/**
@@ -514,6 +536,8 @@ applies the snippet.
*/
declare function snippetCompletion(template: string, completion: Completion): Completion;
+declare const getCompletionTooltip: (state: EditorState) => Tooltip | undefined | null;
+
/**
Returns a command that moves the completion selection forward or
backward by the given amount.
@@ -562,6 +586,11 @@ interface CloseBracketConfig {
these prefixes before the opening quote.
*/
stringPrefixes?: string[];
+ /**
+ An optional callback for overriding the content that's inserted
+ based on surrounding characters
+ */
+ buildInsert?: (state: EditorState, range: SelectionRange, open: string, close: string) => string;
}
/**
Extension to enable bracket-closing behavior. When a closeable
@@ -593,6 +622,8 @@ to programmatically insert brackets—the
take care of running this for user input.)
*/
declare function insertBracket(state: EditorState, bracket: string): Transaction | null;
+declare function nextChar(doc: Text, pos: number): string;
+declare function prevChar(doc: Text, pos: number): string;
/**
Returns an extension that enables autocompletion.
@@ -636,4 +667,5 @@ the currently selected completion.
*/
declare function setSelectedCompletion(index: number): StateEffect<unknown>;
-export { type CloseBracketConfig, type Completion, CompletionContext, type CompletionInfo, type CompletionResult, type CompletionSection, type CompletionSource, acceptCompletion, autocompletion, clearSnippet, closeBrackets, closeBracketsKeymap, closeCompletion, completeAnyWord, completeFromList, completionKeymap, completionStatus, currentCompletions, deleteBracketPair, hasNextSnippetField, hasPrevSnippetField, ifIn, ifNotIn, insertBracket, insertCompletionText, moveCompletionSelection, nextSnippetField, pickedCompletion, prevSnippetField, selectedCompletion, selectedCompletionIndex, setSelectedCompletion, snippet, snippetCompletion, snippetKeymap, startCompletion };
+export { CompletionContext, acceptCompletion, autocompletion, clearSnippet, closeBrackets, closeBracketsKeymap, closeCompletion, completeAnyWord, completeFromList, completionKeymap, completionStatus, currentCompletions, deleteBracketPair, getCompletionTooltip, hasNextSnippetField, hasPrevSnippetField, ifIn, ifNotIn, insertBracket, insertCompletionText, moveCompletionSelection, nextChar, nextSnippetField, pickedCompletion, prevChar, prevSnippetField, selectedCompletion, selectedCompletionIndex, setSelectedCompletion, snippet, snippetCompletion, snippetKeymap, startCompletion };
+export type { CloseBracketConfig, Completion, CompletionInfo, CompletionResult, CompletionSection, CompletionSource };
diff --git a/dist/index.d.ts b/dist/index.d.ts
index b57b8f6..fce47ab 100644
--- a/dist/index.d.ts
+++ b/dist/index.d.ts
@@ -1,6 +1,6 @@
import * as _codemirror_state from '@codemirror/state';
-import { EditorState, ChangeDesc, TransactionSpec, Transaction, StateCommand, Facet, Extension, StateEffect } from '@codemirror/state';
-import { EditorView, Rect, KeyBinding, Command } from '@codemirror/view';
+import { EditorState, Text, ChangeDesc, TransactionSpec, StateCommand, Transaction, Facet, SelectionRange, Extension, StateEffect } from '@codemirror/state';
+import { EditorView, Rect, KeyBinding, Tooltip, Command } from '@codemirror/view';
import * as _lezer_common from '@lezer/common';
/**
@@ -73,6 +73,19 @@ interface Completion {
a `{name}` object.
*/
section?: string | CompletionSection;
+ /**
+ Can be used to alter the change created when the completion is applied
+ */
+ extend?: ExtendCompletion;
+ /**
+ If multiple sources return the same result, use this field to specifiy a
+ deduplication key as well as a priority. For each unique key, only the
+ completion with the highest priority will be shown.
+ */
+ deduplicate?: {
+ key: string;
+ priority: number;
+ };
}
/**
The type returned from
@@ -306,12 +319,17 @@ This annotation is added to transactions that are produced by
picking a completion.
*/
declare const pickedCompletion: _codemirror_state.AnnotationType<Completion>;
+type ExtendCompletion = (state: EditorState, change: {
+ from: number;
+ to: number;
+ insert: string | Text;
+}) => void;
/**
Helper function that returns a transaction spec which inserts a
completion's text in the main selection range, and any other
selection range that has the same text in front of it.
*/
-declare function insertCompletionText(state: EditorState, text: string, from: number, to: number): TransactionSpec;
+declare function insertCompletionText(state: EditorState, text: string | Text, from: number, to: number, extend?: ExtendCompletion): TransactionSpec;
interface CompletionConfig {
/**
@@ -441,6 +459,10 @@ interface CompletionConfig {
milliseconds.
*/
updateSyncTime?: number;
+ /**
+ overleaf: Move unfiltered results after the filtered ones
+ */
+ unfilteredResultsAtEnd?: boolean;
}
/**
@@ -514,6 +536,8 @@ applies the snippet.
*/
declare function snippetCompletion(template: string, completion: Completion): Completion;
+declare const getCompletionTooltip: (state: EditorState) => Tooltip | undefined | null;
+
/**
Returns a command that moves the completion selection forward or
backward by the given amount.
@@ -562,6 +586,11 @@ interface CloseBracketConfig {
these prefixes before the opening quote.
*/
stringPrefixes?: string[];
+ /**
+ An optional callback for overriding the content that's inserted
+ based on surrounding characters
+ */
+ buildInsert?: (state: EditorState, range: SelectionRange, open: string, close: string) => string;
}
/**
Extension to enable bracket-closing behavior. When a closeable
@@ -593,6 +622,8 @@ to programmatically insert brackets—the
take care of running this for user input.)
*/
declare function insertBracket(state: EditorState, bracket: string): Transaction | null;
+declare function nextChar(doc: Text, pos: number): string;
+declare function prevChar(doc: Text, pos: number): string;
/**
Returns an extension that enables autocompletion.
@@ -636,4 +667,5 @@ the currently selected completion.
*/
declare function setSelectedCompletion(index: number): StateEffect<unknown>;
-export { type CloseBracketConfig, type Completion, CompletionContext, type CompletionInfo, type CompletionResult, type CompletionSection, type CompletionSource, acceptCompletion, autocompletion, clearSnippet, closeBrackets, closeBracketsKeymap, closeCompletion, completeAnyWord, completeFromList, completionKeymap, completionStatus, currentCompletions, deleteBracketPair, hasNextSnippetField, hasPrevSnippetField, ifIn, ifNotIn, insertBracket, insertCompletionText, moveCompletionSelection, nextSnippetField, pickedCompletion, prevSnippetField, selectedCompletion, selectedCompletionIndex, setSelectedCompletion, snippet, snippetCompletion, snippetKeymap, startCompletion };
+export { CompletionContext, acceptCompletion, autocompletion, clearSnippet, closeBrackets, closeBracketsKeymap, closeCompletion, completeAnyWord, completeFromList, completionKeymap, completionStatus, currentCompletions, deleteBracketPair, getCompletionTooltip, hasNextSnippetField, hasPrevSnippetField, ifIn, ifNotIn, insertBracket, insertCompletionText, moveCompletionSelection, nextChar, nextSnippetField, pickedCompletion, prevChar, prevSnippetField, selectedCompletion, selectedCompletionIndex, setSelectedCompletion, snippet, snippetCompletion, snippetKeymap, startCompletion };
+export type { CloseBracketConfig, Completion, CompletionInfo, CompletionResult, CompletionSection, CompletionSource };
diff --git a/dist/index.js b/dist/index.js
index 4729223..9361a53 100644
--- a/dist/index.js
+++ b/dist/index.js
@@ -1,4 +1,4 @@
-import { Annotation, StateEffect, EditorSelection, codePointAt, codePointSize, fromCodePoint, Facet, combineConfig, StateField, Prec, Text, Transaction, MapMode, RangeValue, RangeSet, CharCategory } from '@codemirror/state';
+import { Annotation, StateEffect, Text, EditorSelection, codePointAt, codePointSize, fromCodePoint, Facet, combineConfig, StateField, Prec, Transaction, MapMode, RangeValue, RangeSet, CharCategory } from '@codemirror/state';
import { Direction, logException, showTooltip, EditorView, ViewPlugin, getTooltip, Decoration, WidgetType, keymap } from '@codemirror/view';
import { syntaxTree, indentUnit } from '@codemirror/language';
@@ -185,16 +185,23 @@ Helper function that returns a transaction spec which inserts a
completion's text in the main selection range, and any other
selection range that has the same text in front of it.
*/
-function insertCompletionText(state, text, from, to) {
+function insertCompletionText(state, text, from, to, extend) {
let { main } = state.selection, fromOff = from - main.from, toOff = to - main.from;
return Object.assign(Object.assign({}, state.changeByRange(range => {
if (range != main && from != to &&
state.sliceDoc(range.from + fromOff, range.from + toOff) != state.sliceDoc(from, to))
return { range };
- let lines = state.toText(text);
+ let change = {
+ from: range.from + fromOff,
+ to: to == main.from ? range.to : range.from + toOff,
+ insert: text instanceof Text ? text : state.toText(text),
+ };
+ if (extend) {
+ extend(state, change);
+ }
return {
- changes: { from: range.from + fromOff, to: to == main.from ? range.to : range.from + toOff, insert: lines },
- range: EditorSelection.cursor(range.from + fromOff + lines.length)
+ changes: change,
+ range: EditorSelection.cursor(change.from + change.insert.length)
};
})), { scrollIntoView: true, userEvent: "input.complete" });
}
@@ -387,7 +394,9 @@ const completionConfig = /*@__PURE__*/Facet.define({
filterStrict: false,
compareCompletions: (a, b) => a.label.localeCompare(b.label),
interactionDelay: 75,
- updateSyncTime: 100
+ updateSyncTime: 100,
+ // overleaf: default to at top which is default CM6 behaviour
+ unfilteredResultsAtEnd: false
}, {
defaultKeymap: (a, b) => a && b,
closeOnBlur: (a, b) => a && b,
@@ -742,6 +751,7 @@ function score(option) {
(option.type ? 1 : 0);
}
function sortOptions(active, state) {
+ var _a;
let options = [];
let sections = null;
let addOption = (option) => {
@@ -761,7 +771,8 @@ function sortOptions(active, state) {
let getMatch = a.result.getMatch;
if (a.result.filter === false) {
for (let option of a.result.options) {
- addOption(new Option(option, a.source, getMatch ? getMatch(option) : [], 1e9 - options.length));
+ let defaultScore = conf.unfilteredResultsAtEnd ? -1e9 : 1e9;
+ addOption(new Option(option, a.source, getMatch ? getMatch(option) : [], defaultScore - options.length));
}
}
else {
@@ -788,15 +799,42 @@ function sortOptions(active, state) {
}
}
let result = [], prev = null;
+ const priorityIndices = new Map();
let compare = conf.compareCompletions;
for (let opt of options.sort((a, b) => (b.score - a.score) || compare(a.completion, b.completion))) {
+ // overleaf: Deduplicate results with dedup options
+ // The goal is to keep only the highest priority option, in the
+ // highest scoring position.
+ const key = (_a = opt.completion.deduplicate) === null || _a === void 0 ? void 0 : _a.key;
+ if (key) {
+ // Handle merging specifically for deduplicated items item
+ const currentOptionIndex = priorityIndices.get(key);
+ if (currentOptionIndex === undefined) {
+ priorityIndices.set(key, result.length);
+ result.push(opt);
+ prev = opt.completion;
+ }
+ else {
+ if (result[currentOptionIndex].completion.deduplicate.priority < opt.completion.deduplicate.priority) {
+ result[currentOptionIndex] = opt;
+ if (currentOptionIndex === result.length - 1) {
+ prev = opt.completion;
+ }
+ }
+ }
+ continue;
+ }
+ // overleaf: end
let cur = opt.completion;
- if (!prev || prev.label != cur.label || prev.detail != cur.detail ||
- (prev.type != null && cur.type != null && prev.type != cur.type) ||
- prev.apply != cur.apply || prev.boost != cur.boost)
+ if (!prev || prev.label != cur.label)
+ result.push(opt);
+ // overleaf: we're already handling deduplication, so skip extra merges
+ else if (prev.deduplicate)
result.push(opt);
else if (score(opt.completion) > score(prev))
result[result.length - 1] = opt;
+ else if (opt.completion.info)
+ result[result.length - 1] = opt;
prev = opt.completion;
}
return result;
@@ -815,8 +853,9 @@ class CompletionDialog {
: new CompletionDialog(this.options, makeAttrs(id, selected), this.tooltip, this.timestamp, selected, this.disabled);
}
static build(active, state, id, prev, conf, didSetActive) {
- if (prev && !didSetActive && active.some(s => s.isPending))
- return prev.setDisabled();
+ // Overleaf: avoid setting the previous completion state to disabled while completion sources are pending
+ // if (prev && !didSetActive && active.some(s => s.isPending))
+ // return prev.setDisabled()
let options = sortOptions(active, state);
if (!options.length)
return prev && active.some(a => a.isPending) ? prev.setDisabled() : null;
@@ -1015,13 +1054,14 @@ const completionState = /*@__PURE__*/StateField.define({
EditorView.contentAttributes.from(f, state => state.attrs)
]
});
+const getCompletionTooltip = (state) => { var _a; return (_a = state.field(completionState, false)) === null || _a === void 0 ? void 0 : _a.tooltip; };
function applyCompletion(view, option) {
const apply = option.completion.apply || option.completion.label;
let result = view.state.field(completionState).active.find(a => a.source == option.source);
if (!(result instanceof ActiveResult))
return false;
if (typeof apply == "string")
- view.dispatch(Object.assign(Object.assign({}, insertCompletionText(view.state, apply, result.from, result.to)), { annotations: pickedCompletion.of(option.completion) }));
+ view.dispatch(Object.assign(Object.assign({}, insertCompletionText(view.state, apply, result.from, result.to, option.completion.extend)), { annotations: pickedCompletion.of(option.completion) }));
else
apply(view, option.completion, result.from, result.to);
return true;
@@ -1557,20 +1597,42 @@ interpreted as indicating a placeholder.
function snippet(template) {
let snippet = Snippet.parse(template);
return (editor, completion, from, to) => {
- let { text, ranges } = snippet.instantiate(editor.state, from);
- let { main } = editor.state.selection;
- let spec = {
- changes: { from, to: to == main.from ? main.to : to, insert: Text.of(text) },
- scrollIntoView: true,
- annotations: completion ? [pickedCompletion.of(completion), Transaction.userEvent.of("input.complete")] : undefined
- };
+ let { main } = editor.state.selection, fromOff = from - main.from, toOff = to - main.from;
+ let ranges = [];
+ let totalOffset = 0;
+ let spec = Object.assign(Object.assign({}, editor.state.changeByRange(range => {
+ if (range != main && from != to &&
+ editor.state.sliceDoc(range.from + fromOff, range.from + toOff) != editor.state.sliceDoc(from, to))
+ return { range };
+ let { text, ranges: fieldRanges } = snippet.instantiate(editor.state, range.from + fromOff);
+ let change = {
+ from: range.from + fromOff,
+ to: range.from + toOff,
+ insert: Text.of(text)
+ };
+ let originalTo = change.to;
+ let offset = change.insert.length + fromOff;
+ if (completion.extend) {
+ completion.extend(editor.state, change);
+ offset += originalTo - change.to;
+ }
+ for (const fieldRange of fieldRanges) {
+ ranges.push(new FieldRange(fieldRange.field, fieldRange.from + totalOffset, fieldRange.to + totalOffset));
+ }
+ totalOffset += offset;
+ return {
+ changes: change,
+ range: EditorSelection.cursor(change.from + change.insert.length)
+ };
+ })), { scrollIntoView: true, annotations: completion ? [pickedCompletion.of(completion), Transaction.userEvent.of("input.complete")] : undefined, effects: [] });
if (ranges.length)
spec.selection = fieldSelection(ranges, 0);
if (ranges.some(r => r.field > 0)) {
let active = new ActiveSnippet(ranges, 0);
- let effects = spec.effects = [setActive.of(active)];
- if (editor.state.field(snippetState, false) === undefined)
- effects.push(StateEffect.appendConfig.of([snippetState, addSnippetKeymap, snippetPointerHandler, baseTheme]));
+ spec.effects.push(setActive.of(active));
+ if (editor.state.field(snippetState, false) === undefined) {
+ spec.effects.push(StateEffect.appendConfig.of([snippetState, addSnippetKeymap, snippetPointerHandler, baseTheme]));
+ }
}
editor.dispatch(editor.state.update(spec));
};
@@ -1744,7 +1806,8 @@ const completeAnyWord = context => {
const defaults = {
brackets: ["(", "[", "{", "'", '"'],
before: ")]}:;>",
- stringPrefixes: []
+ stringPrefixes: [],
+ buildInsert: (state, range, open, close) => open + close,
};
const closeBracketEffect = /*@__PURE__*/StateEffect.define({
map(value, mapping) {
@@ -1852,8 +1915,8 @@ function insertBracket(state, bracket) {
for (let tok of tokens) {
let closed = closing(codePointAt(tok, 0));
if (bracket == tok)
- return closed == tok ? handleSame(state, tok, tokens.indexOf(tok + tok + tok) > -1, conf)
- : handleOpen(state, tok, closed, conf.before || defaults.before);
+ return closed == tok ? handleSame(state, tok, tokens.indexOf(tok + tok) > -1, tokens.indexOf(tok + tok + tok) > -1, conf)
+ : handleOpen(state, tok, closed, conf.before || defaults.before, conf);
if (bracket == closed && closedBracketAt(state, state.selection.main.from))
return handleClose(state, tok, closed);
}
@@ -1875,17 +1938,21 @@ function prevChar(doc, pos) {
let prev = doc.sliceString(pos - 2, pos);
return codePointSize(codePointAt(prev, 0)) == prev.length ? prev : prev.slice(1);
}
-function handleOpen(state, open, close, closeBefore) {
+function handleOpen(state, open, close, closeBefore, config) {
+ let buildInsert = config.buildInsert || defaults.buildInsert;
let dont = null, changes = state.changeByRange(range => {
+ var _a;
if (!range.empty)
return { changes: [{ insert: open, from: range.from }, { insert: close, from: range.to }],
effects: closeBracketEffect.of(range.to + open.length),
range: EditorSelection.range(range.anchor + open.length, range.head + open.length) };
let next = nextChar(state.doc, range.head);
- if (!next || /\s/.test(next) || closeBefore.indexOf(next) > -1)
- return { changes: { insert: open + close, from: range.head },
- effects: closeBracketEffect.of(range.head + open.length),
+ if (!next || /\s/.test(next) || closeBefore.indexOf(next) > -1) {
+ const insert = (_a = buildInsert(state, range, open, close)) !== null && _a !== void 0 ? _a : open + close;
+ return { changes: { insert, from: range.head },
+ effects: insert === open ? [] : closeBracketEffect.of(range.head + open.length),
range: EditorSelection.cursor(range.head + open.length) };
+ }
return { range: dont = range };
});
return dont ? null : state.update(changes, {
@@ -1907,18 +1974,36 @@ function handleClose(state, _open, close) {
}
// Handles cases where the open and close token are the same, and
// possibly triple quotes (as in `"""abc"""`-style quoting).
-function handleSame(state, token, allowTriple, config) {
+function handleSame(state, token, allowDouble, allowTriple, config) {
let stringPrefixes = config.stringPrefixes || defaults.stringPrefixes;
+ let buildInsert = config.buildInsert || defaults.buildInsert;
let dont = null, changes = state.changeByRange(range => {
+ var _a, _b, _c;
if (!range.empty)
return { changes: [{ insert: token, from: range.from }, { insert: token, from: range.to }],
effects: closeBracketEffect.of(range.to + token.length),
range: EditorSelection.range(range.anchor + token.length, range.head + token.length) };
let pos = range.head, next = nextChar(state.doc, pos), start;
- if (next == token) {
+ if (allowTriple && state.sliceDoc(pos - 2 * token.length, pos) == token + token &&
+ (start = canStartStringAt(state, pos - 2 * token.length, stringPrefixes)) > -1 &&
+ nodeStart(state, start)) {
+ return { changes: { insert: token + token + token + token, from: pos },
+ effects: closeBracketEffect.of(pos + token.length),
+ range: EditorSelection.cursor(pos + token.length) };
+ }
+ else if (allowDouble && state.sliceDoc(pos - token.length, pos) == token &&
+ (start = canStartStringAt(state, pos - token.length, stringPrefixes)) > -1 &&
+ nodeStart(state, start)) {
+ let insert = (_a = buildInsert(state, range, token, token)) !== null && _a !== void 0 ? _a : token + token;
+ return { changes: { insert, from: pos },
+ effects: insert === token ? [] : closeBracketEffect.of(pos + token.length),
+ range: EditorSelection.cursor(pos + token.length) };
+ }
+ else if (next == token) {
if (nodeStart(state, pos)) {
- return { changes: { insert: token + token, from: pos },
- effects: closeBracketEffect.of(pos + token.length),
+ let insert = (_b = buildInsert(state, range, token, token)) !== null && _b !== void 0 ? _b : token + token;
+ return { changes: { insert, from: pos },
+ effects: insert === token ? [] : closeBracketEffect.of(pos + token.length),
range: EditorSelection.cursor(pos + token.length) };
}
else if (closedBracketAt(state, pos)) {
@@ -1928,18 +2013,13 @@ function handleSame(state, token, allowTriple, config) {
range: EditorSelection.cursor(pos + content.length) };
}
}
- else if (allowTriple && state.sliceDoc(pos - 2 * token.length, pos) == token + token &&
- (start = canStartStringAt(state, pos - 2 * token.length, stringPrefixes)) > -1 &&
- nodeStart(state, start)) {
- return { changes: { insert: token + token + token + token, from: pos },
- effects: closeBracketEffect.of(pos + token.length),
- range: EditorSelection.cursor(pos + token.length) };
- }
else if (state.charCategorizer(pos)(next) != CharCategory.Word) {
- if (canStartStringAt(state, pos, stringPrefixes) > -1 && !probablyInString(state, pos, token, stringPrefixes))
- return { changes: { insert: token + token, from: pos },
- effects: closeBracketEffect.of(pos + token.length),
+ if (canStartStringAt(state, pos, stringPrefixes) > -1 && !probablyInString(state, pos, token, stringPrefixes)) {
+ const insert = (_c = buildInsert(state, range, token, token)) !== null && _c !== void 0 ? _c : token + token;
+ return { changes: { insert, from: pos },
+ effects: insert === token ? [] : closeBracketEffect.of(pos + token.length),
range: EditorSelection.cursor(pos + token.length) };
+ }
}
return { range: dont = range };
});
@@ -2071,4 +2151,4 @@ function setSelectedCompletion(index) {
return setSelectedEffect.of(index);
}
-export { CompletionContext, acceptCompletion, autocompletion, clearSnippet, closeBrackets, closeBracketsKeymap, closeCompletion, completeAnyWord, completeFromList, completionKeymap, completionStatus, currentCompletions, deleteBracketPair, hasNextSnippetField, hasPrevSnippetField, ifIn, ifNotIn, insertBracket, insertCompletionText, moveCompletionSelection, nextSnippetField, pickedCompletion, prevSnippetField, selectedCompletion, selectedCompletionIndex, setSelectedCompletion, snippet, snippetCompletion, snippetKeymap, startCompletion };
+export { CompletionContext, acceptCompletion, autocompletion, clearSnippet, closeBrackets, closeBracketsKeymap, closeCompletion, completeAnyWord, completeFromList, completionKeymap, completionStatus, currentCompletions, deleteBracketPair, getCompletionTooltip, hasNextSnippetField, hasPrevSnippetField, ifIn, ifNotIn, insertBracket, insertCompletionText, moveCompletionSelection, nextChar, nextSnippetField, pickedCompletion, prevChar, prevSnippetField, selectedCompletion, selectedCompletionIndex, setSelectedCompletion, snippet, snippetCompletion, snippetKeymap, startCompletion };
@@ -0,0 +1,381 @@
diff --git a/dist/index.cjs b/dist/index.cjs
index 46231ae..fb0f9aa 100644
--- a/dist/index.cjs
+++ b/dist/index.cjs
@@ -592,6 +592,7 @@ class SearchQuery {
this.valid = !!this.search && (!this.regexp || validRegExp(this.search));
this.unquoted = this.unquote(this.search);
this.wholeWord = !!config.wholeWord;
+ this.scope = config.scope;
}
/**
@internal
@@ -606,7 +607,7 @@ class SearchQuery {
eq(other) {
return this.search == other.search && this.replace == other.replace &&
this.caseSensitive == other.caseSensitive && this.regexp == other.regexp &&
- this.wholeWord == other.wholeWord;
+ this.wholeWord == other.wholeWord && this.scope == other.scope;
}
/**
@internal
@@ -631,7 +632,12 @@ class QueryType {
}
}
function stringCursor(spec, state, from, to) {
- return new SearchCursor(state.doc, spec.unquoted, from, to, spec.caseSensitive ? undefined : x => x.toLowerCase(), spec.wholeWord ? stringWordTest(state.doc, state.charCategorizer(state.selection.main.head)) : undefined);
+ const test = spec.wholeWord ? stringWordTest(state.doc, state.charCategorizer(state.selection.main.head)) : undefined;
+ const testWithinScope = (from, to, buffer, bufferPos) => {
+ return (!test || test(from, to, buffer, bufferPos))
+ && (!spec.scope || spec.scope.some(range => from >= range.from && from <= range.to && to >= range.from && to <= range.to));
+ };
+ return new SearchCursor(state.doc, spec.unquoted, from, to, spec.caseSensitive ? undefined : x => x.toLowerCase(), testWithinScope);
}
function stringWordTest(doc, categorizer) {
return (from, to, buf, bufPos) => {
@@ -695,9 +701,14 @@ class StringQuery extends QueryType {
}
}
function regexpCursor(spec, state, from, to) {
+ const test = spec.wholeWord ? regexpWordTest(state.charCategorizer(state.selection.main.head)) : undefined;
+ const testWithinScope = (from, to, match) => {
+ return (!test || test(from, to, match))
+ && (!spec.scope || spec.scope.some(range => from >= range.from && from <= range.to && to >= range.from && to <= range.to));
+ };
return new RegExpCursor(state.doc, spec.search, {
ignoreCase: !spec.caseSensitive,
- test: spec.wholeWord ? regexpWordTest(state.charCategorizer(state.selection.main.head)) : undefined
+ test: testWithinScope,
}, from, to);
}
function charBefore(str, index) {
@@ -737,10 +748,18 @@ class RegExpQuery extends QueryType {
this.prevMatchInRange(state, curTo, state.doc.length);
}
getReplacement(result) {
- return this.spec.unquote(this.spec.replace).replace(/\$([$&\d+])/g, (m, i) => i == "$" ? "$"
- : i == "&" ? result.match[0]
- : i != "0" && +i < result.match.length ? result.match[i]
- : m);
+ return this.spec.unquote(this.spec.replace).replace(/\$([$&]|\d+)/g, (m, i) => {
+ if (i == "&")
+ return result.match[0];
+ if (i == "$")
+ return "$";
+ for (let l = i.length; l > 0; l--) {
+ let n = +i.slice(0, l);
+ if (n > 0 && n < result.match.length)
+ return result.match[n] + i.slice(l);
+ }
+ return m;
+ });
}
matchAll(state, limit) {
let cursor = regexpCursor(this.spec, state, 0, state.doc.length), ranges = [];
@@ -1227,7 +1246,9 @@ const searchExtensions = [
exports.RegExpCursor = RegExpCursor;
exports.SearchCursor = SearchCursor;
exports.SearchQuery = SearchQuery;
+exports.StringQuery = StringQuery;
exports.closeSearchPanel = closeSearchPanel;
+exports.createSearchPanel = createSearchPanel;
exports.findNext = findNext;
exports.findPrevious = findPrevious;
exports.getSearchQuery = getSearchQuery;
@@ -1242,4 +1263,6 @@ exports.searchPanelOpen = searchPanelOpen;
exports.selectMatches = selectMatches;
exports.selectNextOccurrence = selectNextOccurrence;
exports.selectSelectionMatches = selectSelectionMatches;
+exports.selectWord = selectWord;
exports.setSearchQuery = setSearchQuery;
+exports.togglePanel = togglePanel;
diff --git a/dist/index.d.cts b/dist/index.d.cts
index 08f5696..663d192 100644
--- a/dist/index.d.cts
+++ b/dist/index.d.cts
@@ -1,6 +1,6 @@
import * as _codemirror_state from '@codemirror/state';
import { Text, Extension, StateCommand, EditorState, SelectionRange, StateEffect } from '@codemirror/state';
-import { Command, KeyBinding, EditorView, Panel } from '@codemirror/view';
+import { Command, EditorView, Panel, KeyBinding } from '@codemirror/view';
/**
A search cursor provides an iterator over text matches in a
@@ -161,6 +161,7 @@ the `"cm-selectionMatch"` class for the highlighting. When
itself will be highlighted with `"cm-selectionMatch-main"`.
*/
declare function highlightSelectionMatches(options?: HighlightOptions): Extension;
+declare const selectWord: StateCommand;
/**
Select next occurrence of the current selection. Expand selection
to the surrounding word when the selection is empty.
@@ -264,6 +265,13 @@ declare class SearchQuery {
*/
readonly wholeWord: boolean;
/**
+ When set, only include search matches within these ranges
+ */
+ readonly scope?: Readonly<{
+ from: number;
+ to: number;
+ }[]>;
+ /**
Create a query object.
*/
constructor(config: {
@@ -293,6 +301,13 @@ declare class SearchQuery {
Enable whole-word matching.
*/
wholeWord?: boolean;
+ /**
+ The ranges to match within
+ */
+ scope?: Readonly<{
+ from: number;
+ to: number;
+ }[]>;
});
/**
Compare this query to another query.
@@ -307,6 +322,34 @@ declare class SearchQuery {
to: number;
}>;
}
+type SearchResult = typeof SearchCursor.prototype.value;
+declare abstract class QueryType<Result extends SearchResult = SearchResult> {
+ readonly spec: SearchQuery;
+ constructor(spec: SearchQuery);
+ abstract nextMatch(state: EditorState, curFrom: number, curTo: number): Result | null;
+ abstract prevMatch(state: EditorState, curFrom: number, curTo: number): Result | null;
+ abstract getReplacement(result: Result): string;
+ abstract matchAll(state: EditorState, limit: number): readonly Result[] | null;
+ abstract highlight(state: EditorState, from: number, to: number, add: (from: number, to: number) => void): void;
+}
+declare class StringQuery extends QueryType<SearchResult> {
+ constructor(spec: SearchQuery);
+ nextMatch(state: EditorState, curFrom: number, curTo: number): {
+ from: number;
+ to: number;
+ } | null;
+ private prevMatchInRange;
+ prevMatch(state: EditorState, curFrom: number, curTo: number): {
+ from: number;
+ to: number;
+ } | null;
+ getReplacement(_result: SearchResult): string;
+ matchAll(state: EditorState, limit: number): {
+ from: number;
+ to: number;
+ }[] | null;
+ highlight(state: EditorState, from: number, to: number, add: (from: number, to: number) => void): void;
+}
/**
A state effect that updates the current search query. Note that
this only has an effect if the search state has been initialized
@@ -315,6 +358,7 @@ by running [`openSearchPanel`](https://codemirror.net/6/docs/ref/#search.openSea
once).
*/
declare const setSearchQuery: _codemirror_state.StateEffectType<SearchQuery>;
+declare const togglePanel: _codemirror_state.StateEffectType<boolean>;
/**
Get the current search query from an editor state.
*/
@@ -353,6 +397,7 @@ Replace all instances of the search query with the given
replacement.
*/
declare const replaceAll: Command;
+declare function createSearchPanel(view: EditorView): Panel;
/**
Make sure the search panel is open and focused.
*/
@@ -372,4 +417,4 @@ Default search-related key bindings.
*/
declare const searchKeymap: readonly KeyBinding[];
-export { RegExpCursor, SearchCursor, SearchQuery, closeSearchPanel, findNext, findPrevious, getSearchQuery, gotoLine, highlightSelectionMatches, openSearchPanel, replaceAll, replaceNext, search, searchKeymap, searchPanelOpen, selectMatches, selectNextOccurrence, selectSelectionMatches, setSearchQuery };
+export { RegExpCursor, SearchCursor, SearchQuery, StringQuery, closeSearchPanel, createSearchPanel, findNext, findPrevious, getSearchQuery, gotoLine, highlightSelectionMatches, openSearchPanel, replaceAll, replaceNext, search, searchKeymap, searchPanelOpen, selectMatches, selectNextOccurrence, selectSelectionMatches, selectWord, setSearchQuery, togglePanel };
diff --git a/dist/index.d.ts b/dist/index.d.ts
index 08f5696..663d192 100644
--- a/dist/index.d.ts
+++ b/dist/index.d.ts
@@ -1,6 +1,6 @@
import * as _codemirror_state from '@codemirror/state';
import { Text, Extension, StateCommand, EditorState, SelectionRange, StateEffect } from '@codemirror/state';
-import { Command, KeyBinding, EditorView, Panel } from '@codemirror/view';
+import { Command, EditorView, Panel, KeyBinding } from '@codemirror/view';
/**
A search cursor provides an iterator over text matches in a
@@ -161,6 +161,7 @@ the `"cm-selectionMatch"` class for the highlighting. When
itself will be highlighted with `"cm-selectionMatch-main"`.
*/
declare function highlightSelectionMatches(options?: HighlightOptions): Extension;
+declare const selectWord: StateCommand;
/**
Select next occurrence of the current selection. Expand selection
to the surrounding word when the selection is empty.
@@ -264,6 +265,13 @@ declare class SearchQuery {
*/
readonly wholeWord: boolean;
/**
+ When set, only include search matches within these ranges
+ */
+ readonly scope?: Readonly<{
+ from: number;
+ to: number;
+ }[]>;
+ /**
Create a query object.
*/
constructor(config: {
@@ -293,6 +301,13 @@ declare class SearchQuery {
Enable whole-word matching.
*/
wholeWord?: boolean;
+ /**
+ The ranges to match within
+ */
+ scope?: Readonly<{
+ from: number;
+ to: number;
+ }[]>;
});
/**
Compare this query to another query.
@@ -307,6 +322,34 @@ declare class SearchQuery {
to: number;
}>;
}
+type SearchResult = typeof SearchCursor.prototype.value;
+declare abstract class QueryType<Result extends SearchResult = SearchResult> {
+ readonly spec: SearchQuery;
+ constructor(spec: SearchQuery);
+ abstract nextMatch(state: EditorState, curFrom: number, curTo: number): Result | null;
+ abstract prevMatch(state: EditorState, curFrom: number, curTo: number): Result | null;
+ abstract getReplacement(result: Result): string;
+ abstract matchAll(state: EditorState, limit: number): readonly Result[] | null;
+ abstract highlight(state: EditorState, from: number, to: number, add: (from: number, to: number) => void): void;
+}
+declare class StringQuery extends QueryType<SearchResult> {
+ constructor(spec: SearchQuery);
+ nextMatch(state: EditorState, curFrom: number, curTo: number): {
+ from: number;
+ to: number;
+ } | null;
+ private prevMatchInRange;
+ prevMatch(state: EditorState, curFrom: number, curTo: number): {
+ from: number;
+ to: number;
+ } | null;
+ getReplacement(_result: SearchResult): string;
+ matchAll(state: EditorState, limit: number): {
+ from: number;
+ to: number;
+ }[] | null;
+ highlight(state: EditorState, from: number, to: number, add: (from: number, to: number) => void): void;
+}
/**
A state effect that updates the current search query. Note that
this only has an effect if the search state has been initialized
@@ -315,6 +358,7 @@ by running [`openSearchPanel`](https://codemirror.net/6/docs/ref/#search.openSea
once).
*/
declare const setSearchQuery: _codemirror_state.StateEffectType<SearchQuery>;
+declare const togglePanel: _codemirror_state.StateEffectType<boolean>;
/**
Get the current search query from an editor state.
*/
@@ -353,6 +397,7 @@ Replace all instances of the search query with the given
replacement.
*/
declare const replaceAll: Command;
+declare function createSearchPanel(view: EditorView): Panel;
/**
Make sure the search panel is open and focused.
*/
@@ -372,4 +417,4 @@ Default search-related key bindings.
*/
declare const searchKeymap: readonly KeyBinding[];
-export { RegExpCursor, SearchCursor, SearchQuery, closeSearchPanel, findNext, findPrevious, getSearchQuery, gotoLine, highlightSelectionMatches, openSearchPanel, replaceAll, replaceNext, search, searchKeymap, searchPanelOpen, selectMatches, selectNextOccurrence, selectSelectionMatches, setSearchQuery };
+export { RegExpCursor, SearchCursor, SearchQuery, StringQuery, closeSearchPanel, createSearchPanel, findNext, findPrevious, getSearchQuery, gotoLine, highlightSelectionMatches, openSearchPanel, replaceAll, replaceNext, search, searchKeymap, searchPanelOpen, selectMatches, selectNextOccurrence, selectSelectionMatches, selectWord, setSearchQuery, togglePanel };
diff --git a/dist/index.js b/dist/index.js
index 22172ef..08a9974 100644
--- a/dist/index.js
+++ b/dist/index.js
@@ -590,6 +590,7 @@ class SearchQuery {
this.valid = !!this.search && (!this.regexp || validRegExp(this.search));
this.unquoted = this.unquote(this.search);
this.wholeWord = !!config.wholeWord;
+ this.scope = config.scope;
}
/**
@internal
@@ -604,7 +605,7 @@ class SearchQuery {
eq(other) {
return this.search == other.search && this.replace == other.replace &&
this.caseSensitive == other.caseSensitive && this.regexp == other.regexp &&
- this.wholeWord == other.wholeWord;
+ this.wholeWord == other.wholeWord && this.scope == other.scope;
}
/**
@internal
@@ -629,7 +630,12 @@ class QueryType {
}
}
function stringCursor(spec, state, from, to) {
- return new SearchCursor(state.doc, spec.unquoted, from, to, spec.caseSensitive ? undefined : x => x.toLowerCase(), spec.wholeWord ? stringWordTest(state.doc, state.charCategorizer(state.selection.main.head)) : undefined);
+ const test = spec.wholeWord ? stringWordTest(state.doc, state.charCategorizer(state.selection.main.head)) : undefined;
+ const testWithinScope = (from, to, buffer, bufferPos) => {
+ return (!test || test(from, to, buffer, bufferPos))
+ && (!spec.scope || spec.scope.some(range => from >= range.from && from <= range.to && to >= range.from && to <= range.to));
+ };
+ return new SearchCursor(state.doc, spec.unquoted, from, to, spec.caseSensitive ? undefined : x => x.toLowerCase(), testWithinScope);
}
function stringWordTest(doc, categorizer) {
return (from, to, buf, bufPos) => {
@@ -693,9 +699,14 @@ class StringQuery extends QueryType {
}
}
function regexpCursor(spec, state, from, to) {
+ const test = spec.wholeWord ? regexpWordTest(state.charCategorizer(state.selection.main.head)) : undefined;
+ const testWithinScope = (from, to, match) => {
+ return (!test || test(from, to, match))
+ && (!spec.scope || spec.scope.some(range => from >= range.from && from <= range.to && to >= range.from && to <= range.to));
+ };
return new RegExpCursor(state.doc, spec.search, {
ignoreCase: !spec.caseSensitive,
- test: spec.wholeWord ? regexpWordTest(state.charCategorizer(state.selection.main.head)) : undefined
+ test: testWithinScope,
}, from, to);
}
function charBefore(str, index) {
@@ -735,10 +746,18 @@ class RegExpQuery extends QueryType {
this.prevMatchInRange(state, curTo, state.doc.length);
}
getReplacement(result) {
- return this.spec.unquote(this.spec.replace).replace(/\$([$&\d+])/g, (m, i) => i == "$" ? "$"
- : i == "&" ? result.match[0]
- : i != "0" && +i < result.match.length ? result.match[i]
- : m);
+ return this.spec.unquote(this.spec.replace).replace(/\$([$&]|\d+)/g, (m, i) => {
+ if (i == "&")
+ return result.match[0];
+ if (i == "$")
+ return "$";
+ for (let l = i.length; l > 0; l--) {
+ let n = +i.slice(0, l);
+ if (n > 0 && n < result.match.length)
+ return result.match[n] + i.slice(l);
+ }
+ return m;
+ });
}
matchAll(state, limit) {
let cursor = regexpCursor(this.spec, state, 0, state.doc.length), ranges = [];
@@ -1222,4 +1241,4 @@ const searchExtensions = [
baseTheme
];
-export { RegExpCursor, SearchCursor, SearchQuery, closeSearchPanel, findNext, findPrevious, getSearchQuery, gotoLine, highlightSelectionMatches, openSearchPanel, replaceAll, replaceNext, search, searchKeymap, searchPanelOpen, selectMatches, selectNextOccurrence, selectSelectionMatches, setSearchQuery };
+export { RegExpCursor, SearchCursor, SearchQuery, StringQuery, closeSearchPanel, createSearchPanel, findNext, findPrevious, getSearchQuery, gotoLine, highlightSelectionMatches, openSearchPanel, replaceAll, replaceNext, search, searchKeymap, searchPanelOpen, selectMatches, selectNextOccurrence, selectSelectionMatches, selectWord, setSearchQuery, togglePanel };
@@ -1,7 +1,7 @@
diff --git a/node_modules/body-parser/lib/read.js b/node_modules/body-parser/lib/read.js
diff --git a/lib/read.js b/lib/read.js
index fce6283..6131c31 100644
--- a/node_modules/body-parser/lib/read.js
+++ b/node_modules/body-parser/lib/read.js
--- a/lib/read.js
+++ b/lib/read.js
@@ -18,7 +18,7 @@ var iconv = require('iconv-lite')
var onFinished = require('on-finished')
var unpipe = require('unpipe')
@@ -1,7 +1,7 @@
diff --git a/node_modules/cypress-multi-reporters/lib/MultiReporters.js b/node_modules/cypress-multi-reporters/lib/MultiReporters.js
diff --git a/lib/MultiReporters.js b/lib/MultiReporters.js
index 98dc4ef..b2a97bf 100644
--- a/node_modules/cypress-multi-reporters/lib/MultiReporters.js
+++ b/node_modules/cypress-multi-reporters/lib/MultiReporters.js
--- a/lib/MultiReporters.js
+++ b/lib/MultiReporters.js
@@ -160,7 +160,7 @@ MultiReporters.prototype.getCustomOptions = function (options) {
debug('options file (custom)', customOptionsFile);
@@ -1,7 +1,7 @@
diff --git a/node_modules/forwarded/index.js b/node_modules/forwarded/index.js
diff --git a/index.js b/index.js
index b2b6bdd..75e6254 100644
--- a/node_modules/forwarded/index.js
+++ b/node_modules/forwarded/index.js
--- a/index.js
+++ b/index.js
@@ -46,7 +46,7 @@ function forwarded (req) {
function getSocketAddr (req) {
return req.socket
@@ -1,7 +1,7 @@
diff --git a/node_modules/mocha-multi-reporters/lib/MultiReporters.js b/node_modules/mocha-multi-reporters/lib/MultiReporters.js
index d61e019..e7a9515 100644
--- a/node_modules/mocha-multi-reporters/lib/MultiReporters.js
+++ b/node_modules/mocha-multi-reporters/lib/MultiReporters.js
diff --git a/lib/MultiReporters.js b/lib/MultiReporters.js
index d61e019711d5ac7f82c0fb90548bb0eb41ebbb85..e7a9515e05287621301b831191884a84d72ad0a1 100644
--- a/lib/MultiReporters.js
+++ b/lib/MultiReporters.js
@@ -153,7 +153,7 @@ MultiReporters.prototype.getCustomOptions = function (options) {
debug('options file (custom)', customOptionsFile);
@@ -1,7 +1,7 @@
diff --git a/node_modules/multer/lib/make-middleware.js b/node_modules/multer/lib/make-middleware.js
diff --git a/lib/make-middleware.js b/lib/make-middleware.js
index ee50988..de77364 100644
--- a/node_modules/multer/lib/make-middleware.js
+++ b/node_modules/multer/lib/make-middleware.js
--- a/lib/make-middleware.js
+++ b/lib/make-middleware.js
@@ -164,7 +164,7 @@ function makeMiddleware (setup) {
if (fieldname == null) return abortWithCode('MISSING_FIELD_NAME')
@@ -1,7 +1,7 @@
diff --git a/node_modules/node-fetch/lib/index.js b/node_modules/node-fetch/lib/index.js
diff --git a/lib/index.js b/lib/index.js
index 567ff5d..8eb45f7 100644
--- a/node_modules/node-fetch/lib/index.js
+++ b/node_modules/node-fetch/lib/index.js
--- a/lib/index.js
+++ b/lib/index.js
@@ -545,8 +545,8 @@ function clone(instance) {
// tee instance body
p1 = new PassThrough();
@@ -1,7 +1,7 @@
diff --git a/node_modules/passport-oauth2/lib/utils.js b/node_modules/passport-oauth2/lib/utils.js
diff --git a/lib/utils.js b/lib/utils.js
index 486f9e1..4584507 100644
--- a/node_modules/passport-oauth2/lib/utils.js
+++ b/node_modules/passport-oauth2/lib/utils.js
--- a/lib/utils.js
+++ b/lib/utils.js
@@ -24,7 +24,7 @@ exports.originalURL = function(req, options) {
var trustProxy = options.proxy;
@@ -1,7 +1,7 @@
diff --git a/node_modules/pdfjs-dist/build/pdf.worker.mjs b/node_modules/pdfjs-dist/build/pdf.worker.mjs
diff --git a/build/pdf.worker.mjs b/build/pdf.worker.mjs
index 6c5c6f1..bb6b7d1 100644
--- a/node_modules/pdfjs-dist/build/pdf.worker.mjs
+++ b/node_modules/pdfjs-dist/build/pdf.worker.mjs
--- a/build/pdf.worker.mjs
+++ b/build/pdf.worker.mjs
@@ -1830,7 +1830,7 @@ async function __wbg_init(module_or_path) {
}
}
File diff suppressed because it is too large Load Diff
@@ -1,12 +1,12 @@
diff --git a/node_modules/google-gax/node_modules/retry-request/index.js b/node_modules/google-gax/node_modules/retry-request/index.js
index 2fae107..5721c54 100644
--- a/node_modules/google-gax/node_modules/retry-request/index.js
+++ b/node_modules/google-gax/node_modules/retry-request/index.js
diff --git a/index.js b/index.js
index 2fae107d03b30aff9320d135ec79c049c51f298a..32ec707ddbf8937e3e130c3deac1060c308bf439 100644
--- a/index.js
+++ b/index.js
@@ -1,6 +1,6 @@
'use strict';
-const {PassThrough} = require('stream');
+const { PassThrough, pipeline } = require('stream');
+const {PassThrough, pipeline} = require('stream');
const extend = require('extend');
let debug = () => {};
@@ -1,12 +1,12 @@
diff --git a/node_modules/@google-cloud/logging/node_modules/retry-request/index.js b/node_modules/@google-cloud/logging/node_modules/retry-request/index.js
index 2fae107..5721c54 100644
--- a/node_modules/@google-cloud/logging/node_modules/retry-request/index.js
+++ b/node_modules/@google-cloud/logging/node_modules/retry-request/index.js
diff --git a/index.js b/index.js
index 298a351097d70a7fb005b6961f4d58247e391d8f..6a809ace0349d40cb2b6732ad66fd8ad208698ca 100644
--- a/index.js
+++ b/index.js
@@ -1,6 +1,6 @@
'use strict';
-const {PassThrough} = require('stream');
+const { PassThrough, pipeline } = require('stream');
+const {PassThrough, pipeline} = require('stream');
const extend = require('extend');
let debug = () => {};
@@ -0,0 +1,13 @@
diff --git a/lib/sandboxed_module.js b/lib/sandboxed_module.js
index 1cd6743fe221cbe91ea92fea3707ed07a8a2ded3..46889217d96d5534a206549ae7bd97100e41c3e4 100644
--- a/lib/sandboxed_module.js
+++ b/lib/sandboxed_module.js
@@ -4,7 +4,7 @@ var Module = require('module');
var fs = require('fs');
var vm = require('vm');
var path = require('path');
-var builtinModules = require('./builtin_modules.json');
+var builtinModules = Module.builtinModules || require('./builtin_modules.json');
var parent = module.parent;
var globalOptions = {};
var registeredBuiltInSourceTransformers = ['coffee'];
@@ -1,7 +1,7 @@
diff --git a/node_modules/send/index.js b/node_modules/send/index.js
diff --git a/index.js b/index.js
index 768f8ca..a882f4d 100644
--- a/node_modules/send/index.js
+++ b/node_modules/send/index.js
--- a/index.js
+++ b/index.js
@@ -788,29 +788,29 @@ SendStream.prototype.stream = function stream (path, options) {
// pipe
var stream = fs.createReadStream(path, options)
@@ -1,7 +1,7 @@
diff --git a/node_modules/teeny-request/build/src/index.js b/node_modules/teeny-request/build/src/index.js
diff --git a/build/src/index.js b/build/src/index.js
index a101736..a87f6b9 100644
--- a/node_modules/teeny-request/build/src/index.js
+++ b/node_modules/teeny-request/build/src/index.js
--- a/build/src/index.js
+++ b/build/src/index.js
@@ -130,6 +130,9 @@ function createMultipartStream(boundary, multipart) {
}
else {
@@ -1,7 +1,7 @@
diff --git a/node_modules/google-gax/node_modules/teeny-request/build/src/index.js b/node_modules/google-gax/node_modules/teeny-request/build/src/index.js
index af5d15e..2b63d0c 100644
--- a/node_modules/google-gax/node_modules/teeny-request/build/src/index.js
+++ b/node_modules/google-gax/node_modules/teeny-request/build/src/index.js
diff --git a/build/src/index.js b/build/src/index.js
index af5d15e260e2a47588c7c536447fe84bd3f86136..2b63d0c0b1eb6595c7a0bb314c1df792454c1a72 100644
--- a/build/src/index.js
+++ b/build/src/index.js
@@ -115,6 +115,9 @@ function createMultipartStream(boundary, multipart) {
}
else {
@@ -1,7 +1,7 @@
diff --git a/node_modules/thread-loader/dist/WorkerPool.js b/node_modules/thread-loader/dist/WorkerPool.js
index 4145779..f0ff068 100644
--- a/node_modules/thread-loader/dist/WorkerPool.js
+++ b/node_modules/thread-loader/dist/WorkerPool.js
diff --git a/dist/WorkerPool.js b/dist/WorkerPool.js
index 4145779f08eefafd0c18394806b6409c595ac5bb..aa16dd6a0f463804455164493a1e75e80cdf656a 100644
--- a/dist/WorkerPool.js
+++ b/dist/WorkerPool.js
@@ -258,6 +258,19 @@ class PoolWorker {
finalCallback();
break;
@@ -22,10 +22,10 @@ index 4145779..f0ff068 100644
case 'emitWarning':
{
const {
diff --git a/node_modules/thread-loader/dist/index.js b/node_modules/thread-loader/dist/index.js
index 75cd30f..d834af6 100644
--- a/node_modules/thread-loader/dist/index.js
+++ b/node_modules/thread-loader/dist/index.js
diff --git a/dist/index.js b/dist/index.js
index 75cd30fb63dc864057c1afc866f43fc7cc0a8020..d834af6ce1e2cd4473c4ae1b325d63eb1190c17e 100644
--- a/dist/index.js
+++ b/dist/index.js
@@ -43,6 +43,7 @@ function pitch() {
sourceMap: this.sourceMap,
emitError: this.emitError,
@@ -34,10 +34,10 @@ index 75cd30f..d834af6 100644
loadModule: this.loadModule,
resolve: this.resolve,
getResolve: this.getResolve,
diff --git a/node_modules/thread-loader/dist/worker.js b/node_modules/thread-loader/dist/worker.js
index 8e67959..aca94f1 100644
--- a/node_modules/thread-loader/dist/worker.js
+++ b/node_modules/thread-loader/dist/worker.js
diff --git a/dist/worker.js b/dist/worker.js
index 8e67959e4b7c9fd0db116509b1459636ba13a097..aca94f1442906baf179ada8e6a56501bbf71c480 100644
--- a/dist/worker.js
+++ b/dist/worker.js
@@ -90,6 +90,22 @@ function writeJson(data) {
writePipeWrite(lengthBuffer);
writePipeWrite(messageBuffer);
+25
View File
@@ -0,0 +1,25 @@
approvedGitRepositories:
- "**"
enableGlobalCache: false
enableScripts: true
enableTelemetry: false
nodeLinker: node-modules
npmMinimalAgeGate: 3d
supportedArchitectures:
cpu:
- current
- arm64
- x64
libc:
- current
- glibc
os:
- current
- darwin
- linux
-1
View File
@@ -59,7 +59,6 @@ each service:
| `web` | 9229 |
| `clsi` | 9230 |
| `chat` | 9231 |
| `contacts` | 9232 |
| `docstore` | 9233 |
| `document-updater` | 9234 |
| `filestore` | 9235 |
-1
View File
@@ -1,7 +1,6 @@
CHAT_HOST=chat
CLSI_HOST=clsi
DOWNLOAD_HOST=clsi-nginx
CONTACTS_HOST=contacts
DOCSTORE_HOST=docstore
DOCUMENT_UPDATER_HOST=document-updater
FILESTORE_HOST=filestore
-11
View File
@@ -21,17 +21,6 @@ services:
- ../services/chat/app.js:/overleaf/services/chat/app.js
- ../services/chat/config:/overleaf/services/chat/config
contacts:
command: ["node", "--watch", "app.js"]
environment:
- NODE_OPTIONS=--inspect=0.0.0.0:9229
ports:
- "127.0.0.1:9232:9229"
volumes:
- ../services/contacts/app:/overleaf/services/contacts/app
- ../services/contacts/app.js:/overleaf/services/contacts/app.js
- ../services/contacts/config:/overleaf/services/contacts/config
docstore:
command: ["node", "--watch", "app.js"]
environment:
-8
View File
@@ -45,13 +45,6 @@ services:
- ${PWD}/output:/output:ro
- ../services/clsi/nginx.conf:/etc/nginx/conf.d/nginx.conf:ro
contacts:
build:
context: ..
dockerfile: services/contacts/Dockerfile
env_file:
- dev.env
docstore:
build:
context: ..
@@ -161,7 +154,6 @@ services:
- redis
- chat
- clsi
- contacts
- docstore
- document-updater
- filestore
+1 -1
View File
@@ -103,7 +103,7 @@ services:
mongo:
restart: always
image: mongo:6.0
image: mongo:8.0
container_name: mongo
command: "--replSet overleaf"
volumes:
+7 -1
View File
@@ -1,7 +1,13 @@
FROM cypress/included:13.13.2
FROM cypress/included:15.12.0
ARG USER_UID=1000
ARG USER_GID=1000
# Corepack setup, shared between all the images.
ENV PATH="/overleaf/node_modules/.bin:$PATH"
ENV COREPACK_HOME=/opt/corepack
RUN corepack enable && corepack install -g yarn@4.14.1
ENV COREPACK_ENABLE_NETWORK=0
WORKDIR /overleaf
RUN sed -i s/node:x:1000:/node:x:${USER_GID}:/ /etc/group \
@@ -5,5 +5,6 @@ access-token-encryptor
--esmock-loader=False
--is-library=True
--node-version=24.14.1
--package-name=@overleaf/access-token-encryptor
--pipeline-owner=32
--public-repo=False
@@ -4,17 +4,17 @@
"description": "",
"main": "index.js",
"scripts": {
"test": "npm run lint && npm run types:check && npm run test:unit",
"test": "yarn run lint && yarn run types:check && yarn run test:unit",
"lint": "eslint --cache --cache-location ../../node_modules/.cache/eslint/ --ext .cjs,.js,.jsx,.mjs,.ts --max-warnings 0 --format unix .",
"lint:fix": "eslint --cache --cache-location ../../node_modules/.cache/eslint/ --fix --ext .cjs,.js,.jsx,.mjs,.ts .",
"test:ci": "npm run test:unit",
"test:ci": "yarn run test:unit",
"test:unit": "mocha --exit test/**/*.{js,cjs}",
"types:check": "tsc --noEmit"
},
"author": "",
"license": "AGPL-3.0-only",
"dependencies": {
"lodash": "^4.17.21"
"lodash": "^4.18.1"
},
"devDependencies": {
"chai": "^4.3.6",
+1
View File
@@ -8,5 +8,6 @@ module.exports = {
'require-vi-doMock-valid-path': require('./require-vi-doMock-valid-path'),
'require-loading-label': require('./require-loading-label'),
'require-cio-snake-case-properties': require('./require-cio-snake-case-properties'),
'no-throw-in-callback': require('./no-throw-in-callback'),
},
}
@@ -0,0 +1,52 @@
const CALLBACK_PARAM_NAMES = new Set(['cb', 'callback', 'done', 'next'])
function isCallbackParam(param) {
return (
param && param.type === 'Identifier' && CALLBACK_PARAM_NAMES.has(param.name)
)
}
module.exports = {
meta: {
type: 'error',
docs: {
description: 'Disallow throw statements inside callback-based functions',
},
messages: {
noThrowInCallback:
'Pass the error to the callback instead of throwing in callback-based code.',
},
},
create(context) {
// Stack tracks whether each enclosing function is a callback-style function.
// A callback-style function is non-async and has a last param named cb/callback/done/next.
const stack = []
function enterFunction(node) {
const params = node.params
const isCallback =
!node.async &&
params.length > 0 &&
isCallbackParam(params[params.length - 1])
stack.push(isCallback)
}
function exitFunction() {
stack.pop()
}
return {
FunctionDeclaration: enterFunction,
'FunctionDeclaration:exit': exitFunction,
FunctionExpression: enterFunction,
'FunctionExpression:exit': exitFunction,
ArrowFunctionExpression: enterFunction,
'ArrowFunctionExpression:exit': exitFunction,
ThrowStatement(node) {
if (stack[stack.length - 1]) {
context.report({ node, messageId: 'noThrowInCallback' })
}
},
}
},
}
+4 -2
View File
@@ -5,12 +5,14 @@
"license": "AGPL-3.0-only",
"main": "index.js",
"dependencies": {
"eslint": "^8.51.0",
"lodash": "^4.17.21"
"lodash": "^4.18.1"
},
"devDependencies": {
"@typescript-eslint/parser": "^8.50.0"
},
"peerDependencies": {
"eslint": "^8.51.0"
},
"scripts": {
"test": "node rules.test.js"
}
+51
View File
@@ -1,4 +1,5 @@
const { RuleTester } = require('eslint')
const noThrowInCallback = require('./no-throw-in-callback')
const preferKebabUrl = require('./prefer-kebab-url')
const noUnnecessaryTrans = require('./no-unnecessary-trans')
const shouldUnescapeTrans = require('./should-unescape-trans')
@@ -267,3 +268,53 @@ ruleTester.run(
],
}
)
const noThrowInCallbackMessage =
'Pass the error to the callback instead of throwing in callback-based code.'
ruleTester.run('no-throw-in-callback', noThrowInCallback, {
valid: [
// Calling the callback with an error is fine
{ code: `function foo(cb) { cb(new Error()) }` },
// async functions may throw (they return a rejected promise)
{ code: `async function foo(cb) { throw new Error() }` },
// Last param not a callback name — not a callback-style function
{ code: `function foo(data) { throw new Error() }` },
// No params at all
{ code: `function foo() { throw new Error() }` },
// throw inside a nested non-callback function is fine
{ code: `function foo(cb) { [1].map(function() { throw new Error() }) }` },
// throw inside a nested async arrow is fine
{ code: `function foo(cb) { [1].map(async () => { throw new Error() }) }` },
],
invalid: [
{
code: `function foo(cb) { throw new Error() }`,
errors: [{ message: noThrowInCallbackMessage }],
},
{
code: `function foo(callback) { throw new Error() }`,
errors: [{ message: noThrowInCallbackMessage }],
},
{
code: `function foo(done) { throw new Error() }`,
errors: [{ message: noThrowInCallbackMessage }],
},
{
code: `function foo(next) { throw new Error() }`,
errors: [{ message: noThrowInCallbackMessage }],
},
{
code: `function foo(data, cb) { throw new Error() }`,
errors: [{ message: noThrowInCallbackMessage }],
},
{
code: `const foo = (cb) => { throw new Error() }`,
errors: [{ message: noThrowInCallbackMessage }],
},
// throw in a nested callback-style function inside another callback function
{
code: `function foo(cb) { bar(function(done) { throw new Error() }) }`,
errors: [{ message: noThrowInCallbackMessage }],
},
],
})
+1
View File
@@ -5,5 +5,6 @@ fetch-utils
--esmock-loader=False
--is-library=True
--node-version=24.14.1
--package-name=@overleaf/fetch-utils
--pipeline-owner=32
--public-repo=False
+4 -4
View File
@@ -4,10 +4,10 @@
"description": "utilities for node-fetch",
"main": "index.js",
"scripts": {
"test": "npm run lint && npm run types:check && npm run test:unit",
"test": "yarn run lint && yarn run types:check && yarn run test:unit",
"lint": "eslint --cache --cache-location ../../node_modules/.cache/eslint/ --ext .cjs,.js,.jsx,.mjs,.ts --max-warnings 0 --format unix .",
"lint:fix": "eslint --cache --cache-location ../../node_modules/.cache/eslint/ --fix --ext .cjs,.js,.jsx,.mjs,.ts .",
"test:ci": "npm run test:unit",
"test:ci": "yarn run test:unit",
"test:unit": "mocha --exit test/**/*.{js,cjs}",
"types:check": "tsc --noEmit"
},
@@ -26,8 +26,8 @@
"typescript": "^5.0.4"
},
"dependencies": {
"@overleaf/o-error": "*",
"lodash": "^4.17.21",
"@overleaf/o-error": "workspace:*",
"lodash": "^4.18.1",
"node-fetch": "^2.7.0"
}
}
+1
View File
@@ -5,5 +5,6 @@ logger
--esmock-loader=False
--is-library=True
--node-version=24.14.1
--package-name=@overleaf/logger
--pipeline-owner=32
--public-repo=False
+5 -5
View File
@@ -10,17 +10,17 @@
"license": "AGPL-3.0-only",
"version": "3.1.1",
"scripts": {
"test": "npm run lint && npm run types:check && npm run test:unit",
"test": "yarn run lint && yarn run types:check && yarn run test:unit",
"lint": "eslint --cache --cache-location ../../node_modules/.cache/eslint/ --ext .cjs,.js,.jsx,.mjs,.ts --max-warnings 0 --format unix .",
"lint:fix": "eslint --cache --cache-location ../../node_modules/.cache/eslint/ --fix --ext .cjs,.js,.jsx,.mjs,.ts .",
"test:ci": "npm run test:unit",
"test:ci": "yarn run test:unit",
"test:unit": "mocha --exit test/**/*.{js,cjs}",
"types:check": "tsc --noEmit"
},
"dependencies": {
"@google-cloud/logging-bunyan": "^5.1.0",
"@overleaf/fetch-utils": "*",
"@overleaf/o-error": "*",
"@overleaf/fetch-utils": "workspace:*",
"@overleaf/o-error": "workspace:*",
"bunyan": "^1.8.14"
},
"devDependencies": {
@@ -34,6 +34,6 @@
"typescript": "^5.0.4"
},
"peerDependencies": {
"@overleaf/metrics": "*"
"@overleaf/metrics": "workspace:*"
}
}
+1
View File
@@ -5,5 +5,6 @@ metrics
--esmock-loader=False
--is-library=True
--node-version=24.14.1
--package-name=@overleaf/metrics
--pipeline-owner=32
--public-repo=False
+10 -10
View File
@@ -8,13 +8,13 @@
},
"main": "index.js",
"dependencies": {
"@google-cloud/opentelemetry-cloud-trace-exporter": "^2.4.1",
"@google-cloud/profiler": "^6.0.3",
"@google-cloud/opentelemetry-cloud-trace-exporter": "^3.0.0",
"@google-cloud/profiler": "^6.0.4",
"@opentelemetry/api": "1.9.0",
"@opentelemetry/auto-instrumentations-node": "^0.70.0",
"@opentelemetry/exporter-trace-otlp-http": "^0.212.0",
"@opentelemetry/resources": "^1.30.1",
"@opentelemetry/sdk-node": "^0.212.0",
"@opentelemetry/auto-instrumentations-node": "^0.72.0",
"@opentelemetry/exporter-trace-otlp-http": "^0.214.0",
"@opentelemetry/resources": "^2.6.0",
"@opentelemetry/sdk-node": "^0.214.0",
"@opentelemetry/semantic-conventions": "^1.39.0",
"compression": "^1.7.4",
"prom-client": "^14.1.1",
@@ -34,12 +34,12 @@
"lint": "eslint --cache --cache-location ../../node_modules/.cache/eslint/ --ext .cjs,.js,.jsx,.mjs,.ts --max-warnings 0 --format unix .",
"lint:fix": "eslint --cache --cache-location ../../node_modules/.cache/eslint/ --fix --ext .cjs,.js,.jsx,.mjs,.ts .",
"test:unit": "mocha --exit test/**/*.{js,cjs}",
"test:acceptance": "mocha --recursive --exit --grep=$MOCHA_GREP test/acceptance",
"test": "npm run lint && npm run types:check && npm run test:unit",
"test:ci": "npm run test:unit",
"test:acceptance": "mocha --recursive --exit --grep=${MOCHA_GREP:-} test/acceptance",
"test": "yarn run lint && yarn run types:check && yarn run test:unit",
"test:ci": "yarn run test:unit",
"types:check": "tsc --noEmit"
},
"peerDependencies": {
"@overleaf/logger": "*"
"@overleaf/logger": "workspace:*"
}
}
+1
View File
@@ -5,6 +5,7 @@ mongo-utils
--esmock-loader=False
--is-library=True
--node-version=24.14.1
--package-name=@overleaf/mongo-utils
--pipeline-owner=32
--public-repo=False
--tsconfig-no-implicit-any=True
+2 -2
View File
@@ -4,11 +4,11 @@
"description": "utilities to help working with mongo",
"main": "index.js",
"scripts": {
"test": "npm run lint && npm run types:check && npm run test:unit",
"test": "yarn run lint && yarn run types:check && yarn run test:unit",
"test:unit": "mocha --exit test/**/*.{js,cjs}",
"lint": "eslint --cache --cache-location ../../node_modules/.cache/eslint/ --ext .cjs,.js,.jsx,.mjs,.ts --max-warnings 0 --format unix .",
"lint:fix": "eslint --cache --cache-location ../../node_modules/.cache/eslint/ --fix --ext .cjs,.js,.jsx,.mjs,.ts .",
"test:ci": "npm run test:unit",
"test:ci": "yarn run test:unit",
"types:check": "tsc --noEmit"
},
"author": "Overleaf (https://www.overleaf.com)",
+1
View File
@@ -5,5 +5,6 @@ o-error
--esmock-loader=False
--is-library=True
--node-version=24.14.1
--package-name=@overleaf/o-error
--pipeline-owner=32
--public-repo=False
+3 -3
View File
@@ -17,11 +17,11 @@
"index.cjs"
],
"scripts": {
"build": "npm run --silent test",
"build": "yarn run --silent test",
"lint": "eslint --cache --cache-location ../../node_modules/.cache/eslint/ --ext .cjs,.js,.jsx,.mjs,.ts --max-warnings 0 --format unix .",
"lint:fix": "eslint --cache --cache-location ../../node_modules/.cache/eslint/ --fix --ext .cjs,.js,.jsx,.mjs,.ts .",
"test": "npm run lint && npm run types:check && npm run test:unit",
"test:ci": "npm run test:unit",
"test": "yarn run lint && yarn run types:check && yarn run test:unit",
"test:ci": "yarn run test:unit",
"test:unit": "mocha --exit test/**/*.{js,cjs}",
"types:check": "tsc --noEmit"
},
+1 -1
View File
@@ -304,7 +304,7 @@ In order to prevent accidental deletion from outside this mechanism, an event-ba
Contributions should pass lint, formatting and unit test checks. To run these, use
```
npm run test
yarn run test
```
There are no acceptance tests in this module, but https://github.com/overleaf/filestore/ contains a comprehensive set of acceptance tests that use this module. These should also pass, with the changes.
@@ -5,6 +5,7 @@ object-persistor
--esmock-loader=False
--is-library=True
--node-version=24.14.1
--package-name=@overleaf/object-persistor
--pipeline-owner=32
--public-repo=False
--tsconfig-no-implicit-any=True
+6 -6
View File
@@ -4,11 +4,11 @@
"description": "Module for storing objects in multiple backends, with fallback on 404 to assist migration between them",
"main": "index.js",
"scripts": {
"test": "npm run lint && npm run types:check && npm run test:unit",
"test": "yarn run lint && yarn run types:check && yarn run test:unit",
"test:unit": "mocha --exit test/**/*.{js,cjs}",
"lint": "eslint --cache --cache-location ../../node_modules/.cache/eslint/ --ext .cjs,.js,.jsx,.mjs,.ts --max-warnings 0 --format unix .",
"lint:fix": "eslint --cache --cache-location ../../node_modules/.cache/eslint/ --fix --ext .cjs,.js,.jsx,.mjs,.ts .",
"test:ci": "npm run test:unit",
"test:ci": "yarn run test:unit",
"types:check": "tsc --noEmit"
},
"repository": {
@@ -23,10 +23,10 @@
"@aws-sdk/node-http-handler": "^3.374.0",
"@aws-sdk/s3-request-presigner": "^3.994.0",
"@google-cloud/storage": "^7.19.0",
"@overleaf/logger": "*",
"@overleaf/metrics": "*",
"@overleaf/o-error": "*",
"@overleaf/stream-utils": "*",
"@overleaf/logger": "workspace:*",
"@overleaf/metrics": "workspace:*",
"@overleaf/o-error": "workspace:*",
"@overleaf/stream-utils": "workspace:*",
"fast-crc32c": "overleaf/node-fast-crc32c#aae6b2a4c7a7a159395df9cc6c38dfde702d6f51",
"glob": "^12.0.0",
"range-parser": "^1.2.1",
@@ -5,5 +5,6 @@ overleaf-editor-core
--esmock-loader=False
--is-library=True
--node-version=24.14.1
--package-name=overleaf-editor-core
--pipeline-owner=44
--public-repo=False
@@ -4,11 +4,13 @@
const _ = require('lodash')
const assert = require('check-types').assert
const OError = require('@overleaf/o-error')
const Blob = require('../blob')
const FileData = require('./')
const EagerStringFileData = require('./string_file_data')
const EditOperation = require('../operation/edit_operation')
const EditOperationBuilder = require('../operation/edit_operation_builder')
const TextOperation = require('../operation/text_operation')
/**
* @import { BlobStore, ReadonlyBlobStore, RangesBlob, RawHashFileData, RawLazyStringFileData } from '../types'
@@ -152,7 +154,25 @@ class LazyStringFileData extends FileData {
ranges?.comments,
ranges?.trackedChanges
)
applyOperations(this.operations, file)
try {
applyOperations(this.operations, file)
} catch (err) {
const firstOp = this.operations[0]
const firstOpBaseLength =
firstOp instanceof TextOperation ? firstOp.baseLength : undefined
throw OError.tag(err, 'failed to apply operations in toEager', {
blobHash: this.hash,
blobContentLength: content.length,
metadataStringLength: this.stringLength,
totalOperations: this.operations.length,
firstOpBaseLength,
contentMatchesMetadata: content.length === this.stringLength,
contentMatchesFirstOp:
typeof firstOpBaseLength === 'number'
? content.length === firstOpBaseLength
: undefined,
})
}
return file
}
@@ -172,7 +192,18 @@ class LazyStringFileData extends FileData {
* @param {EditOperation} operation
*/
edit(operation) {
this.stringLength = operation.applyToLength(this.stringLength)
try {
this.stringLength = operation.applyToLength(this.stringLength)
} catch (err) {
const baseLength =
operation instanceof TextOperation ? operation.baseLength : undefined
throw OError.tag(err, 'failed to apply operation length in edit', {
blobHash: this.hash,
metadataStringLength: this.stringLength,
operationBaseLength: baseLength,
totalExistingOperations: this.operations.length,
})
}
this.operations.push(operation)
}
@@ -205,7 +236,17 @@ class LazyStringFileData extends FileData {
* @returns {void}
*/
function applyOperations(operations, file) {
_.each(operations, operation => operation.apply(file))
for (let i = 0; i < operations.length; i++) {
try {
operations[i].apply(file)
} catch (err) {
throw OError.tag(err, 'operation failed during applyOperations', {
operationIndex: i,
totalOperations: operations.length,
currentContentLength: file.getStringLength(),
})
}
}
}
module.exports = LazyStringFileData
+4 -4
View File
@@ -4,10 +4,10 @@
"description": "Library shared between the editor server and clients.",
"main": "index.js",
"scripts": {
"test": "npm run lint && npm run types:check && npm run test:unit",
"test": "yarn run lint && yarn run types:check && yarn run test:unit",
"lint": "eslint --cache --cache-location ../../node_modules/.cache/eslint/ --ext .cjs,.js,.jsx,.mjs,.ts --max-warnings 0 --format unix .",
"lint:fix": "eslint --cache --cache-location ../../node_modules/.cache/eslint/ --fix --ext .cjs,.js,.jsx,.mjs,.ts .",
"test:ci": "npm run test:unit",
"test:ci": "yarn run test:unit",
"test:unit": "mocha --exit test/**/*.{js,cjs}",
"types:check": "tsc --noEmit"
},
@@ -25,9 +25,9 @@
"typescript": "^5.0.4"
},
"dependencies": {
"@overleaf/o-error": "*",
"@overleaf/o-error": "workspace:*",
"check-types": "^5.1.0",
"lodash": "^4.17.19",
"lodash": "^4.18.1",
"p-map": "^4.0.0",
"path-browserify": "^1.0.1"
}
@@ -4,6 +4,7 @@
const _ = require('lodash')
const { expect } = require('chai')
const sinon = require('sinon')
const OError = require('@overleaf/o-error')
const ot = require('../..')
const File = ot.File
@@ -202,4 +203,85 @@ describe('LazyStringFileData', function () {
expect(fileData.hash).to.equal(stored.hash)
expect(fileData.operations).to.deep.equal([])
})
describe('error annotation', function () {
it('annotates errors in toEager with blob and operation metadata', async function () {
const testHash = this.fileHash
// Create a file with content length 19 ('the quick brown fox')
// then queue an operation that expects a different base length
const fileData = new LazyStringFileData(testHash, undefined, 19)
// This operation expects base length 999, which won't match the blob
const badOp = new TextOperation()
badOp.retain(999)
fileData.operations.push(badOp)
// Manually set stringLength to match the op's baseLength: pushing directly
// to operations bypasses edit(), which is what normally updates stringLength
fileData.stringLength = 999
try {
await fileData.toEager(this.blobStore)
expect.fail('should have thrown')
} catch (err) {
const info = OError.getFullInfo(err)
expect(info).to.have.property('blobHash', testHash)
expect(info).to.have.property('blobContentLength', 19)
expect(info).to.have.property('metadataStringLength', 999)
expect(info).to.have.property('totalOperations', 1)
expect(info).to.have.property('operationIndex', 0)
expect(info).to.have.property('currentContentLength', 19)
expect(info).to.have.property('firstOpBaseLength', 999)
expect(info).to.have.property('contentMatchesMetadata', false)
expect(info).to.have.property('contentMatchesFirstOp', false)
}
})
it('annotates errors in edit with operation and metadata context', function () {
const testHash = this.fileHash
const fileData = new LazyStringFileData(testHash, undefined, 19)
// Queue one valid operation first so totalExistingOperations > 0
fileData.edit(new TextOperation().retain(19).insert('!'))
// This operation expects base length 999, mismatching stringLength of 20
const badOp = new TextOperation()
badOp.retain(999)
try {
fileData.edit(badOp)
expect.fail('should have thrown')
} catch (err) {
const info = OError.getFullInfo(err)
expect(info).to.have.property('blobHash', testHash)
expect(info).to.have.property('metadataStringLength', 20)
expect(info).to.have.property('operationBaseLength', 999)
expect(info).to.have.property('totalExistingOperations', 1)
}
})
it('annotates errors in applyOperations with the failing operation index', async function () {
const testHash = this.fileHash
// Content is 'the quick brown fox' (length 19)
const fileData = new LazyStringFileData(testHash, undefined, 19)
// First op is valid: insert at end
const goodOp = new TextOperation().retain(19).insert('!')
// Second op expects length 999 — will fail
const badOp = new TextOperation()
badOp.retain(999)
fileData.operations.push(goodOp)
fileData.operations.push(badOp)
fileData.stringLength = 999
try {
await fileData.toEager(this.blobStore)
expect.fail('should have thrown')
} catch (err) {
const info = OError.getFullInfo(err)
// The second operation (index 1) should be the one that fails
expect(info).to.have.property('operationIndex', 1)
expect(info).to.have.property('totalOperations', 2)
expect(info).to.have.property('currentContentLength', 20)
// toEager also tags with blob metadata
expect(info).to.have.property('blobHash', testHash)
expect(info).to.have.property('blobContentLength', 19)
expect(info).to.have.property('metadataStringLength', 999)
}
})
})
})
@@ -17,6 +17,10 @@ const { RetainOp, InsertOp, RemoveOp } = require('../../lib/operation/scan_op')
const TrackingProps = require('../../lib/file_data/tracking_props')
const ClearTrackingProps = require('../../lib/file_data/clear_tracking_props')
function fuzzingErrorMessage(obj) {
return `Failed randomized test with input: ${JSON.stringify(obj)}`
}
describe('TextOperation', function () {
const numTrials = 500
@@ -145,18 +149,12 @@ describe('TextOperation', function () {
const str = random.string(50)
const comments = random.comments(6)
const o = randomOperation(str, comments.ids)
try {
expect(str.length).to.equal(o.baseLength)
const file = new StringFileData(str, comments.comments)
o.apply(file)
const result = file.getContent()
expect(result.length).to.equal(o.targetLength)
} catch (err) {
if (err instanceof Error) {
err.message = `Failing inputs:\n str: ${JSON.stringify(str)}\n comments: ${JSON.stringify(comments)}\n o: ${JSON.stringify(o.toJSON())}\n\n${err.message}`
}
throw err
}
const fuzzingError = fuzzingErrorMessage({ str, comments, o: o.toJSON() })
expect(str.length).to.equal(o.baseLength, fuzzingError)
const file = new StringFileData(str, comments.comments)
o.apply(file)
const result = file.getContent()
expect(result.length).to.equal(o.targetLength, fuzzingError)
})
)
@@ -166,15 +164,11 @@ describe('TextOperation', function () {
const doc = random.string(50)
const comments = random.comments(2)
const operation = randomOperation(doc, comments.ids)
try {
const roundTripOperation = TextOperation.fromJSON(operation.toJSON())
expect(operation.equals(roundTripOperation)).to.be.true
} catch (err) {
if (err instanceof Error) {
err.message = `Failing inputs:\n doc: ${JSON.stringify(doc)}\n comments: ${JSON.stringify(comments)}\n operation: ${JSON.stringify(operation.toJSON())}\n\n${err.message}`
}
throw err
}
const roundTripOperation = TextOperation.fromJSON(operation.toJSON())
expect(operation.equals(roundTripOperation)).to.equal(
true,
fuzzingErrorMessage({ operation })
)
})
)
@@ -227,22 +221,20 @@ describe('TextOperation', function () {
const str = random.string(50)
const comments = random.comments(6)
const o = randomOperation(str, comments.ids)
try {
const originalFile = new StringFileData(str, comments.comments)
const p = o.invert(originalFile)
expect(o.baseLength).to.equal(p.targetLength)
expect(o.targetLength).to.equal(p.baseLength)
const file = new StringFileData(str, comments.comments)
o.apply(file)
p.apply(file)
const result = file.toRaw()
expect(result).to.deep.equal(originalFile.toRaw())
} catch (err) {
if (err instanceof Error) {
err.message = `Failing inputs:\n str: ${JSON.stringify(str)}\n comments: ${JSON.stringify(comments)}\n o: ${JSON.stringify(o.toJSON())}\n\n${err.message}`
}
throw err
}
const originalFile = new StringFileData(str, comments.comments)
const p = o.invert(originalFile)
const fuzzingError = fuzzingErrorMessage({
str,
comments,
o: o.toJSON(),
})
expect(o.baseLength).to.equal(p.targetLength, fuzzingError)
expect(o.targetLength).to.equal(p.baseLength, fuzzingError)
const file = new StringFileData(str, comments.comments)
o.apply(file)
p.apply(file)
const result = file.toRaw()
expect(result).to.deep.equal(originalFile.toRaw(), fuzzingError)
})
)
@@ -394,26 +386,33 @@ describe('TextOperation', function () {
const str = random.string(20)
const comments = random.comments(6)
const a = randomOperation(str, comments.ids)
const fuzzingError = fuzzingErrorMessage({
str,
comments,
a: a.toJSON(),
})
const file = new StringFileData(str, comments.comments)
a.apply(file)
const afterA = file.toRaw()
expect(afterA.content.length).to.equal(a.targetLength, fuzzingError)
const b = randomOperation(afterA.content, comments.ids)
try {
expect(afterA.content.length).to.equal(a.targetLength)
b.apply(file)
const afterB = file.toRaw()
expect(afterB.content.length).to.equal(b.targetLength)
const ab = a.compose(b)
expect(ab.targetLength).to.equal(b.targetLength)
ab.apply(new StringFileData(str, comments.comments))
const afterAB = file.toRaw()
expect(afterAB).to.deep.equal(afterB)
} catch (err) {
if (err instanceof Error) {
err.message = `Failing inputs:\n str: ${JSON.stringify(str)}\n comments: ${JSON.stringify(comments)}\n a: ${JSON.stringify(a.toJSON())}\n b: ${JSON.stringify(b.toJSON())}\n\n${err.message}`
}
throw err
}
const fuzzingErrorWithB = fuzzingErrorMessage({
str,
comments,
a: a.toJSON(),
b: b.toJSON(),
})
b.apply(file)
const afterB = file.toRaw()
expect(afterB.content.length).to.equal(
b.targetLength,
fuzzingErrorWithB
)
const ab = a.compose(b)
expect(ab.targetLength).to.equal(b.targetLength, fuzzingErrorWithB)
ab.apply(new StringFileData(str, comments.comments))
const afterAB = file.toRaw()
expect(afterAB).to.deep.equal(afterB, fuzzingErrorWithB)
})
)
@@ -622,24 +621,23 @@ describe('TextOperation', function () {
const comments = random.comments(6)
const a = randomOperation(str, comments.ids)
const b = randomOperation(str, comments.ids)
try {
const primes = TextOperation.transform(a, b)
const aPrime = primes[0]
const bPrime = primes[1]
const abPrime = a.compose(bPrime)
const baPrime = b.compose(aPrime)
const abFile = new StringFileData(str, comments.comments)
const baFile = new StringFileData(str, comments.comments)
abPrime.apply(abFile)
baPrime.apply(baFile)
expect(abPrime.equals(baPrime)).to.be.true
expect(abFile.toRaw()).to.deep.equal(baFile.toRaw())
} catch (err) {
if (err instanceof Error) {
err.message = `Failing inputs:\n str: ${JSON.stringify(str)}\n comments: ${JSON.stringify(comments)}\n a: ${JSON.stringify(a.toJSON())}\n b: ${JSON.stringify(b.toJSON())}\n\n${err.message}`
}
throw err
}
const primes = TextOperation.transform(a, b)
const aPrime = primes[0]
const bPrime = primes[1]
const abPrime = a.compose(bPrime)
const baPrime = b.compose(aPrime)
const abFile = new StringFileData(str, comments.comments)
const baFile = new StringFileData(str, comments.comments)
abPrime.apply(abFile)
baPrime.apply(baFile)
const fuzzingError = fuzzingErrorMessage({
str,
comments,
a: a.toJSON(),
b: b.toJSON(),
})
expect(abPrime.equals(baPrime)).to.be.equal(true, fuzzingError)
expect(abFile.toRaw()).to.deep.equal(baFile.toRaw(), fuzzingError)
})
)
+1
View File
@@ -5,5 +5,6 @@ promise-utils
--esmock-loader=False
--is-library=True
--node-version=24.14.1
--package-name=@overleaf/promise-utils
--pipeline-owner=32
--public-repo=False
+2 -2
View File
@@ -4,11 +4,11 @@
"description": "utilities to help working with promises",
"main": "index.js",
"scripts": {
"test": "npm run lint && npm run types:check && npm run test:unit",
"test": "yarn run lint && yarn run types:check && yarn run test:unit",
"test:unit": "mocha --exit test/**/*.{js,cjs}",
"lint": "eslint --cache --cache-location ../../node_modules/.cache/eslint/ --ext .cjs,.js,.jsx,.mjs,.ts --max-warnings 0 --format unix .",
"lint:fix": "eslint --cache --cache-location ../../node_modules/.cache/eslint/ --fix --ext .cjs,.js,.jsx,.mjs,.ts .",
"test:ci": "npm run test:unit",
"test:ci": "yarn run test:unit",
"types:check": "tsc --noEmit"
},
"author": "Overleaf (https://www.overleaf.com)",
+1
View File
@@ -5,5 +5,6 @@ ranges-tracker
--esmock-loader=False
--is-library=True
--node-version=24.14.1
--package-name=@overleaf/ranges-tracker
--pipeline-owner=44
--public-repo=False
+2 -2
View File
@@ -11,8 +11,8 @@
"scripts": {
"lint": "eslint --cache --cache-location ../../node_modules/.cache/eslint/ --ext .cjs,.js,.jsx,.mjs,.ts --max-warnings 0 --format unix .",
"lint:fix": "eslint --cache --cache-location ../../node_modules/.cache/eslint/ --fix --ext .cjs,.js,.jsx,.mjs,.ts .",
"test": "npm run lint && npm run types:check && npm run test:unit",
"test:ci": "npm run test:unit",
"test": "yarn run lint && yarn run types:check && yarn run test:unit",
"test:ci": "yarn run test:unit",
"test:unit": "mocha --exit test/**/*.{js,cjs}",
"types:check": "tsc --noEmit"
},
+1
View File
@@ -5,5 +5,6 @@ redis-wrapper
--esmock-loader=False
--is-library=True
--node-version=24.14.1
--package-name=@overleaf/redis-wrapper
--pipeline-owner=32
--public-repo=False
+7 -7
View File
@@ -15,23 +15,23 @@
"scripts": {
"lint": "eslint --cache --cache-location ../../node_modules/.cache/eslint/ --ext .cjs,.js,.jsx,.mjs,.ts --max-warnings 0 --format unix .",
"lint:fix": "eslint --cache --cache-location ../../node_modules/.cache/eslint/ --fix --ext .cjs,.js,.jsx,.mjs,.ts .",
"test": "npm run lint && npm run types:check && npm run test:unit",
"test:ci": "npm run test:unit",
"test": "yarn run lint && yarn run types:check && yarn run test:unit",
"test:ci": "yarn run test:unit",
"test:unit": "mocha --exit test/**/*.{js,cjs}",
"types:check": "tsc --noEmit"
},
"peerDependencies": {
"@overleaf/logger": "*",
"@overleaf/metrics": "*",
"@overleaf/o-error": "*"
"@overleaf/logger": "workspace:*",
"@overleaf/metrics": "workspace:*",
"@overleaf/o-error": "workspace:*"
},
"dependencies": {
"async": "^3.2.5",
"ioredis": "~4.27.1"
},
"devDependencies": {
"@overleaf/logger": "*",
"@overleaf/o-error": "*",
"@overleaf/logger": "workspace:*",
"@overleaf/o-error": "workspace:*",
"chai": "^4.3.6",
"mocha": "^11.1.0",
"mocha-junit-reporter": "^2.2.1",
+1
View File
@@ -5,5 +5,6 @@ settings
--esmock-loader=False
--is-library=True
--node-version=24.14.1
--package-name=@overleaf/settings
--pipeline-owner=32
--public-repo=False
+2 -2
View File
@@ -7,8 +7,8 @@
"scripts": {
"lint": "eslint --cache --cache-location ../../node_modules/.cache/eslint/ --ext .cjs,.js,.jsx,.mjs,.ts --max-warnings 0 --format unix .",
"lint:fix": "eslint --cache --cache-location ../../node_modules/.cache/eslint/ --fix --ext .cjs,.js,.jsx,.mjs,.ts .",
"test": "npm run lint && npm run types:check && npm run test:unit",
"test:ci": "npm run test:unit",
"test": "yarn run lint && yarn run types:check && yarn run test:unit",
"test:ci": "yarn run test:unit",
"test:unit": "mocha --exit test/**/*.{js,cjs}",
"types:check": "tsc --noEmit"
},
+1
View File
@@ -5,5 +5,6 @@ stream-utils
--esmock-loader=False
--is-library=True
--node-version=24.14.1
--package-name=@overleaf/stream-utils
--pipeline-owner=32
--public-repo=False
+2 -2
View File
@@ -4,11 +4,11 @@
"description": "stream handling utilities",
"main": "index.js",
"scripts": {
"test": "npm run lint && npm run types:check && npm run test:unit",
"test": "yarn run lint && yarn run types:check && yarn run test:unit",
"test:unit": "mocha --exit test/**/*.{js,cjs}",
"lint": "eslint --cache --cache-location ../../node_modules/.cache/eslint/ --ext .cjs,.js,.jsx,.mjs,.ts --max-warnings 0 --format unix .",
"lint:fix": "eslint --cache --cache-location ../../node_modules/.cache/eslint/ --fix --ext .cjs,.js,.jsx,.mjs,.ts .",
"test:ci": "npm run test:unit",
"test:ci": "yarn run test:unit",
"types:check": "tsc --noEmit"
},
"author": "Overleaf (https://www.overleaf.com)",
+26 -2
View File
@@ -1,5 +1,29 @@
// @ts-check
const OError = require('@overleaf/o-error')
class ParamsError extends OError {}
/**
* @typedef {import('zod').ZodError} ZodError
*/
module.exports = { ParamsError }
class InvalidRequestError extends OError {
/**
* @param {ZodError} zodError
*/
constructor(zodError) {
super('Invalid request', {}, zodError)
this.zodError = zodError
}
}
class InvalidParamsError extends OError {
/**
* @param {ZodError} zodError
*/
constructor(zodError) {
super('Invalid request parameters', {}, zodError)
this.zodError = zodError
}
}
module.exports = { InvalidParamsError, InvalidRequestError }
@@ -5,6 +5,7 @@ validation-tools
--esmock-loader=False
--is-library=True
--node-version=24.14.1
--package-name=@overleaf/validation-tools
--public-repo=False
--test-acceptance-vitest=True
--test-unit-vitest=True
@@ -1,15 +1,20 @@
const { isZodErrorLike, fromError } = require('zod-validation-error')
const { fromError } = require('zod-validation-error')
const { InvalidParamsError, InvalidRequestError } = require('./Errors')
function createHandleValidationError(statusCode = 400) {
return [
(err, req, res, next) => {
if (!isZodErrorLike(err)) {
return next(err)
}
res.status(statusCode).json({ ...fromError(err), statusCode })
},
]
return (err, req, res, next) => {
if (err instanceof InvalidParamsError) {
res
.status(404)
.json({ error: fromError(err.zodError).toString(), statusCode: 404 })
} else if (err instanceof InvalidRequestError) {
res
.status(statusCode)
.json({ error: fromError(err.zodError).toString(), statusCode })
} else {
next(err)
}
}
}
const handleValidationError = createHandleValidationError(400)
+3 -2
View File
@@ -1,4 +1,4 @@
const { ParamsError } = require('./Errors')
const { InvalidParamsError, InvalidRequestError } = require('./Errors')
const { z } = require('zod')
const { zz } = require('./zodHelpers')
const { parseReq } = require('./parseReq')
@@ -15,5 +15,6 @@ module.exports = {
parseReq,
handleValidationError,
createHandleValidationError,
ParamsError,
InvalidRequestError,
InvalidParamsError,
}
+4 -4
View File
@@ -10,21 +10,21 @@
"license": "AGPL-3.0-only",
"version": "1.0.0",
"scripts": {
"test": "npm run lint && npm run types:check && npm run test:unit",
"test": "yarn run lint && yarn run types:check && yarn run test:unit",
"lint": "eslint --cache --cache-location ../../node_modules/.cache/eslint/ --ext .cjs,.js,.jsx,.mjs,.ts --max-warnings 0 --format unix .",
"lint:fix": "eslint --cache --cache-location ../../node_modules/.cache/eslint/ --fix --ext .cjs,.js,.jsx,.mjs,.ts .",
"test:ci": "npm run test:unit",
"test:ci": "yarn run test:unit",
"test:unit": "vitest --config vitest.config.ts",
"types:check": "tsc --noEmit"
},
"dependencies": {
"@overleaf/o-error": "*",
"@overleaf/o-error": "workspace:*",
"mongodb": "^6.12.0",
"zod": "^4.1.8",
"zod-validation-error": "^4.0.1"
},
"devDependencies": {
"typescript": "^5.0.4",
"vitest": "^4.0.0"
"vitest": "4.1.5"
}
}
+3 -3
View File
@@ -1,5 +1,5 @@
// @ts-check
const { ParamsError } = require('./Errors')
const { InvalidRequestError, InvalidParamsError } = require('./Errors')
/**
* @typedef {import('zod').ZodType} ZodType
@@ -25,9 +25,9 @@ function parseReq(req, schema) {
return parsed.data
} else if (parsed.error.issues.some(issue => issue.path[0] === 'params')) {
// Parts of the URL path failed to validate; throw a specific error
throw new ParamsError('Invalid params').withCause(parsed.error)
throw new InvalidParamsError(parsed.error)
} else {
throw parsed.error
throw new InvalidRequestError(parsed.error)
}
}
@@ -54,7 +54,7 @@ describe('parseReq', () => {
}),
})
)
).toThrowError(expect.objectContaining({ name: 'ParamsError' }))
).toThrowError(expect.objectContaining({ name: 'InvalidParamsError' }))
})
it('should throw an error containing issues if the schema is invalid', () => {
@@ -75,9 +75,11 @@ describe('parseReq', () => {
)
).toThrowError(
expect.objectContaining({
issues: expect.arrayContaining([
expect.objectContaining({ path: ['body', 'name'] }),
]),
zodError: expect.objectContaining({
issues: expect.arrayContaining([
expect.objectContaining({ path: ['body', 'name'] }),
]),
}),
})
)
})
@@ -217,4 +217,121 @@ describe('zodHelpers', () => {
])
})
})
describe('buildId', () => {
it('fails to parse when provided with an invalid buildId', () => {
const parsed = zz.buildId().safeParse('aa')
expect(parsed.success).toBe(false)
expect(parsed.error?.issues).toHaveLength(1)
expect(parsed.error?.issues).toMatchObject([
expect.objectContaining({
message: 'invalid buildId',
}),
])
})
it('parses successfully when provided with a valid buildId', () => {
const parsed = zz.buildId().safeParse('19d6c341530-878fff6cdab7fb0c')
expect(parsed.success).toBe(true)
expect(parsed.data).toBe('19d6c341530-878fff6cdab7fb0c')
})
it('fails to parse when provided with an editorBuildId', () => {
const parsed = zz
.buildId()
.safeParse(
'03b1d773-6203-4669-b365-6a0aa5625878-19d6c341530-878fff6cdab7fb0c'
)
expect(parsed.success).toBe(false)
expect(parsed.error?.issues).toHaveLength(1)
expect(parsed.error?.issues).toMatchObject([
expect.objectContaining({
message: 'invalid buildId',
}),
])
})
})
describe('editorBuildId', () => {
it('fails to parse when provided with an invalid buildId', () => {
const parsed = zz.editorBuildId().safeParse('aa')
expect(parsed.success).toBe(false)
expect(parsed.error?.issues).toHaveLength(1)
expect(parsed.error?.issues).toMatchObject([
expect.objectContaining({
message: 'invalid editorId-buildId',
}),
])
})
it('fails to parse when provided with a buildId', () => {
const parsed = zz
.editorBuildId()
.safeParse('19d6c341530-878fff6cdab7fb0c')
expect(parsed.success).toBe(false)
expect(parsed.error?.issues).toHaveLength(1)
expect(parsed.error?.issues).toMatchObject([
expect.objectContaining({
message: 'invalid editorId-buildId',
}),
])
})
it('parses successfully when provided with a valid editorId-buildId', () => {
const parsed = zz
.editorBuildId()
.safeParse(
'03b1d773-6203-4669-b365-6a0aa5625878-19d6c341530-878fff6cdab7fb0c'
)
expect(parsed.success).toBe(true)
expect(parsed.data).toBe(
'03b1d773-6203-4669-b365-6a0aa5625878-19d6c341530-878fff6cdab7fb0c'
)
})
})
describe('filepath', () => {
it('fails to parse with empty input', () => {
const parsed = zz.filepath().safeParse('')
expect(parsed.success).toBe(false)
expect(parsed.error?.issues).toHaveLength(1)
expect(parsed.error?.issues).toMatchObject([
expect.objectContaining({
message: 'path is empty',
}),
])
})
it('fails to parse with absolute path', () => {
const parsed = zz.filepath().safeParse('/output.pdf')
expect(parsed.success).toBe(false)
expect(parsed.error?.issues).toHaveLength(1)
expect(parsed.error?.issues).toMatchObject([
expect.objectContaining({
message: 'path is absolute',
}),
])
})
it('fails to parse when provided with path traversal', () => {
const parsed = zz.filepath().safeParse('../output.pdf')
expect(parsed.success).toBe(false)
expect(parsed.error?.issues).toHaveLength(1)
expect(parsed.error?.issues).toMatchObject([
expect.objectContaining({
message: 'path traversal detected',
}),
])
})
it('parses successfully when provided a valid path', () => {
const parsed = zz.filepath().safeParse('output.pdf')
expect(parsed.success).toBe(true)
expect(parsed.data).toBe('output.pdf')
})
it('parses successfully when provided a valid nested path', () => {
const parsed = zz.filepath().safeParse('foo/output.pdf')
expect(parsed.success).toBe(true)
expect(parsed.data).toBe('foo/output.pdf')
})
})
})
+8
View File
@@ -0,0 +1,8 @@
function asZodError(...def) {
return {
name: 'ZodError',
_zod: { def },
}
}
module.exports = { asZodError }
+25
View File
@@ -33,6 +33,31 @@ const zz = {
datetimeNullable: options => datetimeSchema({ ...options, allowNull: true }),
datetimeNullish: options =>
datetimeSchema({ ...options, allowNull: true, allowUndefined: true }),
buildId: () =>
z.string().regex(/^[0-9a-f]+-[0-9a-f]+$/, { message: 'invalid buildId' }),
editorBuildId: () =>
z.string().regex(/^[a-f0-9-]{36}-[0-9a-f]+-[0-9a-f]+$/, {
message: 'invalid editorId-buildId',
}),
clsiServerId: () =>
z.string().regex(/^[a-z0-9-]+$/, { message: 'invalid clsiServerId' }),
compileBackendClass: () =>
z
.string()
.regex(/^[a-z0-9-]+$/, { message: 'invalid compileBackendClass' }),
compileGroup: () =>
z.enum(['alpha', 'gvisor', 'standard', 'priority'], {
message: 'invalid compileGroup',
}),
submissionId: () => z.string().regex(/^[a-zA-Z0-9_-]+$/),
filepath: () =>
z
.string()
.nonempty({ message: 'path is empty' })
.refine(s => !s.startsWith('/'), { message: 'path is absolute' })
.refine(s => !s.split('/').includes('..'), {
message: 'path traversal detected',
}),
}
module.exports = { zz }
-55115
View File
File diff suppressed because it is too large Load Diff
+177 -24
View File
@@ -1,10 +1,9 @@
{
"name": "overleaf",
"private": true,
"dependencies": {
"patch-package": "^8.0.0"
},
"packageManager": "yarn@4.14.1",
"devDependencies": {
"@overleaf/eslint-plugin": "workspace:*",
"@prettier/plugin-pug": "^3.4.0",
"@types/chai": "^4.3.0",
"@types/chai-as-promised": "^7.1.8",
@@ -30,23 +29,180 @@
"typescript": "^5.9.3"
},
"engines": {
"npm": "11.11.0"
"node": ">=20.0.0"
},
"overrides": {
"request@2.88.2": {
"tough-cookie": "5.1.2",
"form-data": "2.5.5",
"qs": "6.14.1"
},
"cypress@13.13.2": {
"@cypress/request@3.0.9": {
"qs": "6.14.1"
}
},
"resolutions": {
"@xmldom/xmldom": "0.8.13",
"argparse/underscore": "1.13.8",
"east/underscore": "1.13.8",
"referer-parser/js-yaml": "^4.1.0",
"sandboxed-module": "patch:sandboxed-module@npm%3A2.0.4#~/.yarn/patches/sandboxed-module-npm-2.0.4-f8b45aacc9.patch",
"request/tough-cookie": "5.1.2",
"request/form-data": "2.5.5",
"request/qs": "6.14.1",
"@cypress/request/qs": "6.14.1",
"@opentelemetry/api": "1.9.0",
"mocha@^11.1.0": {
"serialize-javascript": "7.0.5"
}
"mocha/serialize-javascript": "7.0.5",
"pprof/protobufjs": "7.5.5",
"@google-cloud/profiler/protobufjs": "7.5.5",
"mocha-multi-reporters": "patch:mocha-multi-reporters@npm%3A1.5.1#~/.yarn/patches/mocha-multi-reporters-npm-1.5.1-0a1088aed5.patch",
"pdfjs-dist": "patch:pdfjs-dist@npm%3A5.1.91#~/.yarn/patches/pdfjs-dist-npm-5.1.91.patch",
"referer-parser": "patch:referer-parser@npm%3A0.0.3#~/.yarn/patches/referer-parser-npm-0.0.3.patch",
"sass": "1.77.1",
"@codemirror/autocomplete": "patch:@codemirror/autocomplete@npm%3A6.18.4#~/.yarn/patches/@codemirror-autocomplete-npm-6.18.4.patch",
"@codemirror/commands": "6.10.1",
"@codemirror/language": "6.12.1",
"@codemirror/lint": "6.9.2",
"@codemirror/search": "patch:@codemirror/search@npm%3A6.5.8#~/.yarn/patches/@codemirror-search-npm-6.5.8.patch",
"@codemirror/state": "6.5.4",
"@codemirror/view": "6.38.6",
"@lezer/common": "1.5.0",
"@lezer/highlight": "1.2.3",
"@lezer/lr": "1.4.7",
"@types/react": "18.3.28",
"@types/react-dom": "18.3.7",
"@vitest/expect": "4.1.5",
"@vitest/mocker": "4.1.5",
"@vitest/pretty-format": "4.1.5",
"@vitest/spy": "4.1.5",
"@vitest/utils": "4.1.5",
"cheerio": "1.0.0-rc.10",
"react": "18.3.1",
"react-dom": "18.3.1",
"sinon-chai": "3.7.0",
"i18next-scanner/i18next": "23.16.8",
"downshift": "9.0.9",
"body-parser@npm:1.20.4": "patch:body-parser@npm%3A1.20.4#~/.yarn/patches/body-parser-npm-1.20.4.patch",
"cypress-multi-reporters": "patch:cypress-multi-reporters@npm%3A2.0.5#~/.yarn/patches/cypress-multi-reporters-npm-2.0.5.patch",
"forwarded@npm:0.2.0": "patch:forwarded@npm%3A0.2.0#~/.yarn/patches/forwarded-npm-0.2.0.patch",
"multer@npm:2.1.1": "patch:multer@npm%3A2.1.1#~/.yarn/patches/multer-npm-2.1.1.patch",
"node-fetch": "patch:node-fetch@npm%3A2.7.0#~/.yarn/patches/node-fetch-npm-2.7.0.patch",
"passport-oauth2": "patch:passport-oauth2@npm%3A1.6.1#~/.yarn/patches/passport-oauth2-npm-1.6.1.patch",
"send": "patch:send@npm%3A0.19.0#~/.yarn/patches/send-npm-0.19.0.patch",
"serve-static": "1.16.2",
"@uppy/xhr-upload": "3.6.0",
"recurly": "4.12.0",
"mongoose": "8.22.1",
"pg": "8.7.1",
"pg-query-stream": "4.7.1",
"@aws-sdk/client-s3": "3.994.0",
"@aws-sdk/client-ses": "3.994.0",
"@aws-sdk/s3-request-presigner": "3.994.0",
"contentful": "10.8.5",
"@contentful/rich-text-html-renderer": "16.0.2",
"@contentful/rich-text-types": "16.0.2",
"i18next": "23.10.0",
"sanitize-html": "2.17.4",
"lodash": "4.18.1",
"express-session": "1.17.2",
"ioredis": "4.27.11",
"knip": "5.64.1",
"eslint-plugin-testing-library": "7.5.3",
"chart.js": "4.0.1",
"mock-fs": "5.2.0",
"@customerio/cdp-analytics-node": "0.3.9",
"@google-cloud/bigquery": "8.1.1",
"moment": "2.29.4",
"sequelize-cli": "6.6.0",
"async": "3.2.5",
"dockerode": "4.0.9",
"tar-fs": "3.1.1",
"cluster-key-slot": "1.1.0",
"@octokit/request": "9.2.2",
"randomstring": "1.2.2",
"vite": "7.3.1",
"isomorphic-git": "1.33.1",
"http-status": "1.5.0",
"knex": "2.4.0",
"utf-8-validate": "5.0.8",
"samlp": "7.0.2",
"compression": "1.7.4",
"cookie-parser": "1.4.6",
"react-cookie": "7.2.0",
"react-dropzone": "14.2.3",
"@babel/core": "7.28.5",
"@babel/preset-env": "7.28.5",
"@babel/register": "7.28.3",
"@vitejs/plugin-react": "4.4.1",
"cssnano": "7.1.4",
"mini-css-extract-plugin": "2.7.6",
"nodemon": "3.0.1",
"postcss": "8.5.8",
"postcss-reporter": "7.0.5",
"zod": "4.1.11",
"zod-validation-error": "4.0.1",
"simple-oauth2": "5.0.0",
"@types/simple-oauth2": "5.0.7",
"@node-oauth/oauth2-server": "5.3.0",
"@phosphor-icons/react": "2.1.7",
"@slack/webhook": "7.0.2",
"@stripe/react-stripe-js": "3.9.0",
"@stripe/stripe-js": "7.7.0",
"cache-flow": "1.9.0",
"focus-trap-react": "11.0.4",
"i18next-http-middleware": "3.5.0",
"jose": "4.15.5",
"nodemailer": "8.0.5",
"on-headers": "1.0.2",
"pug": "3.0.3",
"rate-limiter-flexible": "2.4.1",
"react-hook-form": "7.71.1",
"stripe": "18.4.0",
"@babel/plugin-proposal-decorators": "7.28.0",
"@floating-ui/react": "0.27.16",
"@juggle/resize-observer": "3.3.1",
"@storybook/addon-a11y": "10.3.5",
"@storybook/addon-essentials": "10.3.5",
"@storybook/addon-interactions": "10.3.5",
"@storybook/addon-links": "10.3.5",
"@storybook/cli": "10.3.5",
"@storybook/react": "10.3.5",
"@storybook/react-webpack5": "10.3.5",
"@storybook/theming": "10.3.5",
"@streamdown/cjk": "1.0.2",
"@testing-library/dom": "10.4.0",
"@testing-library/user-event": "14.5.2",
"@types/express": "4.17.23",
"@types/recurly__recurly-js": "4.38.0",
"@types/sanitize-html": "2.16.0",
"@uppy/dashboard": "3.7.1",
"@uppy/drag-drop": "3.0.3",
"@uppy/file-input": "3.0.4",
"@uppy/progress-bar": "3.0.4",
"@uppy/react": "3.2.1",
"autoprefixer": "10.4.16",
"babel-plugin-module-resolver": "5.0.2",
"backbone": "1.6.0",
"dompurify": "3.4.0",
"eventsource-client": "1.1.4",
"fake-indexeddb": "6.0.0",
"formik": "2.2.9",
"katex": "0.16.28",
"match-sorter": "6.3.1",
"micromark": "4.0.0",
"pirates": "4.0.6",
"qrcode": "1.5.0",
"react-chartjs-2": "5.0.1",
"react-i18next": "13.3.1",
"react-resizable-panels": "2.1.1",
"rehype-harden": "1.1.7",
"scroll-into-view-if-needed": "2.2.28",
"storybook": "10.3.5",
"streamdown": "2.2.0",
"tailwindcss": "3.4.17",
"thread-loader": "patch:thread-loader@npm%3A4.0.2#~/.yarn/patches/thread-loader-npm-4.0.2-dab5735f54.patch",
"unist-util-visit": "5.0.0",
"use-stick-to-bottom": "1.1.1",
"zustand": "5.0.8",
"retry-request@npm:^8.0.0": "patch:retry-request@npm%3A8.0.2#~/.yarn/patches/retry-request-npm-8.0.2-448ad084c8.patch",
"retry-request@npm:^7.0.0": "patch:retry-request@npm%3A7.0.2#~/.yarn/patches/retry-request-npm-7.0.2-a41087680c.patch",
"teeny-request@npm:^10.0.0": "patch:teeny-request@npm%3A10.1.0#~/.yarn/patches/teeny-request-npm-10.1.0.patch",
"teeny-request@npm:^9.0.0": "patch:teeny-request@npm%3A9.0.0#~/.yarn/patches/teeny-request-npm-9.0.0-4d571e3c55.patch",
"@babel/plugin-transform-modules-systemjs": "7.29.4",
"fast-xml-builder": "1.1.7",
"ip-address": "10.1.1",
"systeminformation": "5.31.6",
"fast-uri": "3.1.2"
},
"scripts": {
"format": "prettier --cache --cache-location ./node_modules/.cache/prettier/.prettier-cache --check",
@@ -57,11 +213,10 @@
"format:pug:fix": "prettier --cache --cache-location ./node_modules/.cache/prettier/.prettier-cache --write --check '**/*.pug'",
"format:jenkins": "prettier --cache --cache-location ./node_modules/.cache/prettier/.prettier-cache --check '**/Jenkinsfile'",
"format:jenkins:fix": "prettier --cache --cache-location ./node_modules/.cache/prettier/.prettier-cache --write --check '**/Jenkinsfile'",
"format:monorepo-check": "prettier --cache --cache-location ./node_modules/.cache/prettier/.prettier-cache --check '**/Jenkinsfile' '**/*.md' '**/docker-compose.yml' '**/docker-compose.*.yml'",
"format:monorepo-check:fix": "prettier --cache --cache-location ./node_modules/.cache/prettier/.prettier-cache --write --check '**/Jenkinsfile' '**/*.md' '**/docker-compose.yml' '**/docker-compose.*.yml'",
"format:monorepo-check": "prettier --cache --cache-location ./node_modules/.cache/prettier/.prettier-cache --check '**/Jenkinsfile' '**/*.md' '**/docker-compose.yml' '**/docker-compose.*.yml' '.agents/**/*.js' '.agents/**/*.md'",
"format:monorepo-check:fix": "prettier --cache --cache-location ./node_modules/.cache/prettier/.prettier-cache --write --check '**/Jenkinsfile' '**/*.md' '**/docker-compose.yml' '**/docker-compose.*.yml' '.agents/**/*.js' '.agents/**/*.md'",
"lint": "eslint --cache --cache-location ./node_modules/.cache/eslint/ --max-warnings 0 --format unix .",
"lint:fix": "eslint --cache --cache-location ./node_modules/.cache/eslint/ --fix .",
"postinstall": "patch-package"
"lint:fix": "eslint --cache --cache-location ./node_modules/.cache/eslint/ --fix ."
},
"workspaces": [
"jobs/mirror-documentation",
@@ -72,7 +227,6 @@
"services/clsi",
"services/clsi-cache",
"services/clsi-perf",
"services/contacts",
"services/docstore",
"services/document-updater",
"services/filestore",
@@ -87,7 +241,6 @@
"services/templates",
"services/third-party-datastore",
"services/third-party-references",
"services/tpdsworker",
"services/web",
"tools/dependency-management",
"tools/npm-overrides-helper",
@@ -1,58 +0,0 @@
diff --git a/node_modules/@google-cloud/logging/node_modules/teeny-request/build/src/index.js b/node_modules/@google-cloud/logging/node_modules/teeny-request/build/src/index.js
index af5d15e..2b63d0c 100644
--- a/node_modules/@google-cloud/logging/node_modules/teeny-request/build/src/index.js
+++ b/node_modules/@google-cloud/logging/node_modules/teeny-request/build/src/index.js
@@ -115,6 +115,9 @@ function createMultipartStream(boundary, multipart) {
}
else {
part.body.pipe(stream, { end: false });
+ part.body.on('error', (err) => {
+ stream.destroy(err);
+ });
part.body.on('end', () => {
stream.write('\r\n');
stream.write(finale);
@@ -168,25 +171,27 @@ function teenyRequest(reqOpts, callback) {
// Stream mode
const requestStream = streamEvents(new stream_1.PassThrough());
// eslint-disable-next-line @typescript-eslint/no-explicit-any
- let responseStream;
- requestStream.once('reading', () => {
- if (responseStream) {
- (0, stream_1.pipeline)(responseStream, requestStream, () => { });
- }
- else {
- requestStream.once('response', () => {
- (0, stream_1.pipeline)(responseStream, requestStream, () => { });
- });
- }
- });
+ // let responseStream;
+ // requestStream.once('reading', () => {
+ // if (responseStream) {
+ // (0, stream_1.pipeline)(responseStream, requestStream, () => { });
+ // }
+ // else {
+ // requestStream.once('response', () => {
+ // (0, stream_1.pipeline)(responseStream, requestStream, () => { });
+ // });
+ // }
+ // });
+
+
options.compress = false;
teenyRequest.stats.requestStarting();
(0, node_fetch_1.default)(uri, options).then(res => {
- teenyRequest.stats.requestFinished();
- responseStream = res.body;
- responseStream.on('error', (err) => {
- requestStream.emit('error', err);
- });
+ teenyRequest.stats.requestFinished(); stream_1.pipeline(res.body, requestStream, () => {});
+ // responseStream = res.body;
+ // responseStream.on('error', (err) => {
+ // requestStream.emit('error', err);
+ // });
const response = fetchToRequestResponse(options, res);
requestStream.emit('response', response);
}, err => {
@@ -1,30 +0,0 @@
diff --git a/node_modules/@google-cloud/logging-min/node_modules/retry-request/index.js b/node_modules/@google-cloud/logging-min/node_modules/retry-request/index.js
index 2fae107..5721c54 100644
--- a/node_modules/@google-cloud/logging-min/node_modules/retry-request/index.js
+++ b/node_modules/@google-cloud/logging-min/node_modules/retry-request/index.js
@@ -1,6 +1,6 @@
'use strict';
-const {PassThrough} = require('stream');
+const { PassThrough, pipeline } = require('stream');
const extend = require('extend');
let debug = () => {};
@@ -185,7 +185,7 @@ function retryRequest(requestOpts, opts, callback) {
.on('complete', (...params) => handleFinish(params))
.on('finish', (...params) => handleFinish(params));
- requestStream.pipe(delayStream);
+ pipeline(requestStream, delayStream, () => {});
} else {
activeRequest = opts.request(requestOpts, onResponse);
}
@@ -251,7 +251,7 @@ function retryRequest(requestOpts, opts, callback) {
// No more attempts need to be made, just continue on.
if (streamMode) {
retryStream.emit('response', response);
- delayStream.pipe(retryStream);
+ pipeline(delayStream, retryStream, () => {});
requestStream.on('error', err => {
retryStream.destroy(err);
});
@@ -1,58 +0,0 @@
diff --git a/node_modules/@google-cloud/logging-min/node_modules/teeny-request/build/src/index.js b/node_modules/@google-cloud/logging-min/node_modules/teeny-request/build/src/index.js
index af5d15e..2b63d0c 100644
--- a/node_modules/@google-cloud/logging-min/node_modules/teeny-request/build/src/index.js
+++ b/node_modules/@google-cloud/logging-min/node_modules/teeny-request/build/src/index.js
@@ -115,6 +115,9 @@ function createMultipartStream(boundary, multipart) {
}
else {
part.body.pipe(stream, { end: false });
+ part.body.on('error', (err) => {
+ stream.destroy(err);
+ });
part.body.on('end', () => {
stream.write('\r\n');
stream.write(finale);
@@ -168,25 +171,27 @@ function teenyRequest(reqOpts, callback) {
// Stream mode
const requestStream = streamEvents(new stream_1.PassThrough());
// eslint-disable-next-line @typescript-eslint/no-explicit-any
- let responseStream;
- requestStream.once('reading', () => {
- if (responseStream) {
- (0, stream_1.pipeline)(responseStream, requestStream, () => { });
- }
- else {
- requestStream.once('response', () => {
- (0, stream_1.pipeline)(responseStream, requestStream, () => { });
- });
- }
- });
+ // let responseStream;
+ // requestStream.once('reading', () => {
+ // if (responseStream) {
+ // (0, stream_1.pipeline)(responseStream, requestStream, () => { });
+ // }
+ // else {
+ // requestStream.once('response', () => {
+ // (0, stream_1.pipeline)(responseStream, requestStream, () => { });
+ // });
+ // }
+ // });
+
+
options.compress = false;
teenyRequest.stats.requestStarting();
(0, node_fetch_1.default)(uri, options).then(res => {
- teenyRequest.stats.requestFinished();
- responseStream = res.body;
- responseStream.on('error', (err) => {
- requestStream.emit('error', err);
- });
+ teenyRequest.stats.requestFinished(); stream_1.pipeline(res.body, requestStream, () => {});
+ // responseStream = res.body;
+ // responseStream.on('error', (err) => {
+ // requestStream.emit('error', err);
+ // });
const response = fetchToRequestResponse(options, res);
requestStream.emit('response', response);
}, err => {
@@ -1,30 +0,0 @@
diff --git a/node_modules/@google-cloud/profiler/node_modules/retry-request/index.js b/node_modules/@google-cloud/profiler/node_modules/retry-request/index.js
index 2fae107..5721c54 100644
--- a/node_modules/@google-cloud/profiler/node_modules/retry-request/index.js
+++ b/node_modules/@google-cloud/profiler/node_modules/retry-request/index.js
@@ -1,6 +1,6 @@
'use strict';
-const {PassThrough} = require('stream');
+const { PassThrough, pipeline } = require('stream');
const extend = require('extend');
let debug = () => {};
@@ -185,7 +185,7 @@ function retryRequest(requestOpts, opts, callback) {
.on('complete', (...params) => handleFinish(params))
.on('finish', (...params) => handleFinish(params));
- requestStream.pipe(delayStream);
+ pipeline(requestStream, delayStream, () => {});
} else {
activeRequest = opts.request(requestOpts, onResponse);
}
@@ -251,7 +251,7 @@ function retryRequest(requestOpts, opts, callback) {
// No more attempts need to be made, just continue on.
if (streamMode) {
retryStream.emit('response', response);
- delayStream.pipe(retryStream);
+ pipeline(delayStream, retryStream, () => {});
requestStream.on('error', err => {
retryStream.destroy(err);
});
@@ -1,58 +0,0 @@
diff --git a/node_modules/@google-cloud/profiler/node_modules/teeny-request/build/src/index.js b/node_modules/@google-cloud/profiler/node_modules/teeny-request/build/src/index.js
index af5d15e..2b63d0c 100644
--- a/node_modules/@google-cloud/profiler/node_modules/teeny-request/build/src/index.js
+++ b/node_modules/@google-cloud/profiler/node_modules/teeny-request/build/src/index.js
@@ -115,6 +115,9 @@ function createMultipartStream(boundary, multipart) {
}
else {
part.body.pipe(stream, { end: false });
+ part.body.on('error', (err) => {
+ stream.destroy(err);
+ });
part.body.on('end', () => {
stream.write('\r\n');
stream.write(finale);
@@ -168,25 +171,27 @@ function teenyRequest(reqOpts, callback) {
// Stream mode
const requestStream = streamEvents(new stream_1.PassThrough());
// eslint-disable-next-line @typescript-eslint/no-explicit-any
- let responseStream;
- requestStream.once('reading', () => {
- if (responseStream) {
- (0, stream_1.pipeline)(responseStream, requestStream, () => { });
- }
- else {
- requestStream.once('response', () => {
- (0, stream_1.pipeline)(responseStream, requestStream, () => { });
- });
- }
- });
+ // let responseStream;
+ // requestStream.once('reading', () => {
+ // if (responseStream) {
+ // (0, stream_1.pipeline)(responseStream, requestStream, () => { });
+ // }
+ // else {
+ // requestStream.once('response', () => {
+ // (0, stream_1.pipeline)(responseStream, requestStream, () => { });
+ // });
+ // }
+ // });
+
+
options.compress = false;
teenyRequest.stats.requestStarting();
(0, node_fetch_1.default)(uri, options).then(res => {
- teenyRequest.stats.requestFinished();
- responseStream = res.body;
- responseStream.on('error', (err) => {
- requestStream.emit('error', err);
- });
+ teenyRequest.stats.requestFinished(); stream_1.pipeline(res.body, requestStream, () => {});
+ // responseStream = res.body;
+ // responseStream.on('error', (err) => {
+ // requestStream.emit('error', err);
+ // });
const response = fetchToRequestResponse(options, res);
requestStream.emit('response', response);
}, err => {
@@ -1,30 +0,0 @@
diff --git a/node_modules/@google-cloud/storage/node_modules/retry-request/index.js b/node_modules/@google-cloud/storage/node_modules/retry-request/index.js
index 2fae107..5721c54 100644
--- a/node_modules/@google-cloud/storage/node_modules/retry-request/index.js
+++ b/node_modules/@google-cloud/storage/node_modules/retry-request/index.js
@@ -1,6 +1,6 @@
'use strict';
-const {PassThrough} = require('stream');
+const { PassThrough, pipeline } = require('stream');
const extend = require('extend');
let debug = () => {};
@@ -185,7 +185,7 @@ function retryRequest(requestOpts, opts, callback) {
.on('complete', (...params) => handleFinish(params))
.on('finish', (...params) => handleFinish(params));
- requestStream.pipe(delayStream);
+ pipeline(requestStream, delayStream, () => {});
} else {
activeRequest = opts.request(requestOpts, onResponse);
}
@@ -251,7 +251,7 @@ function retryRequest(requestOpts, opts, callback) {
// No more attempts need to be made, just continue on.
if (streamMode) {
retryStream.emit('response', response);
- delayStream.pipe(retryStream);
+ pipeline(delayStream, retryStream, () => {});
requestStream.on('error', err => {
retryStream.destroy(err);
});
@@ -1,58 +0,0 @@
diff --git a/node_modules/@google-cloud/storage/node_modules/teeny-request/build/src/index.js b/node_modules/@google-cloud/storage/node_modules/teeny-request/build/src/index.js
index af5d15e..2b63d0c 100644
--- a/node_modules/@google-cloud/storage/node_modules/teeny-request/build/src/index.js
+++ b/node_modules/@google-cloud/storage/node_modules/teeny-request/build/src/index.js
@@ -115,6 +115,9 @@ function createMultipartStream(boundary, multipart) {
}
else {
part.body.pipe(stream, { end: false });
+ part.body.on('error', (err) => {
+ stream.destroy(err);
+ });
part.body.on('end', () => {
stream.write('\r\n');
stream.write(finale);
@@ -168,25 +171,27 @@ function teenyRequest(reqOpts, callback) {
// Stream mode
const requestStream = streamEvents(new stream_1.PassThrough());
// eslint-disable-next-line @typescript-eslint/no-explicit-any
- let responseStream;
- requestStream.once('reading', () => {
- if (responseStream) {
- (0, stream_1.pipeline)(responseStream, requestStream, () => { });
- }
- else {
- requestStream.once('response', () => {
- (0, stream_1.pipeline)(responseStream, requestStream, () => { });
- });
- }
- });
+ // let responseStream;
+ // requestStream.once('reading', () => {
+ // if (responseStream) {
+ // (0, stream_1.pipeline)(responseStream, requestStream, () => { });
+ // }
+ // else {
+ // requestStream.once('response', () => {
+ // (0, stream_1.pipeline)(responseStream, requestStream, () => { });
+ // });
+ // }
+ // });
+
+
options.compress = false;
teenyRequest.stats.requestStarting();
(0, node_fetch_1.default)(uri, options).then(res => {
- teenyRequest.stats.requestFinished();
- responseStream = res.body;
- responseStream.on('error', (err) => {
- requestStream.emit('error', err);
- });
+ teenyRequest.stats.requestFinished(); stream_1.pipeline(res.body, requestStream, () => {});
+ // responseStream = res.body;
+ // responseStream.on('error', (err) => {
+ // requestStream.emit('error', err);
+ // });
const response = fetchToRequestResponse(options, res);
requestStream.emit('response', response);
}, err => {
-1
View File
@@ -1 +0,0 @@
The patches in this folder are applied by `patch-package` to dependencies, particularly those which need changes that are difficult to apply upstream.
-22
View File
@@ -1,22 +0,0 @@
diff --git a/node_modules/retry-request/index.js b/node_modules/retry-request/index.js
index 298a351..7ec1ef8 100644
--- a/node_modules/retry-request/index.js
+++ b/node_modules/retry-request/index.js
@@ -185,7 +185,7 @@ function retryRequest(requestOpts, opts, callback) {
.on('complete', (...params) => handleFinish(params))
.on('finish', (...params) => handleFinish(params));
- requestStream.pipe(delayStream);
+ pipeline(requestStream, delayStream, () => {});
} else {
activeRequest = opts.request(requestOpts, onResponse);
}
@@ -251,7 +251,7 @@ function retryRequest(requestOpts, opts, callback) {
// No more attempts need to be made, just continue on.
if (streamMode) {
retryStream.emit('response', response);
- delayStream.pipe(retryStream);
+ pipeline(delayStream, retryStream, () => {});
requestStream.on('error', err => {
retryStream.destroy(err);
});
+19 -9
View File
@@ -8,25 +8,35 @@ FROM $OVERLEAF_BASE_TAG
WORKDIR /overleaf
# Add required source files for npm install
# -----------------------------------------
COPY --parents libraries/*/package.json patches/ services/*/package.json tools/migrations/ package.json package-lock.json /overleaf/
# Add required source files for yarn install
# -------------------------------------------
COPY --parents libraries/*/package.json .yarn/patches/ services/*/package.json tools/migrations/ package.json yarn.lock .yarnrc.yml /overleaf/
COPY server-ce/genScript.js server-ce/services.js /overleaf/
# Install npm dependencies
# ------------------------
# Pre-install yarn via corepack so it is available at runtime for all users
# -------------------------------------------------------------------------
# Corepack setup, shared between all the images.
ENV PATH="/overleaf/node_modules/.bin:$PATH"
ENV COREPACK_HOME=/opt/corepack
RUN corepack enable && corepack install -g yarn@4.14.1
ENV COREPACK_ENABLE_NETWORK=0
# Install yarn dependencies
# -------------------------
RUN --mount=type=cache,target=/root/.cache \
--mount=type=cache,target=/root/.npm \
--mount=type=cache,target=/overleaf/services/web/node_modules/.cache,id=server-ce-webpack-cache \
--mount=type=cache,target=/root/.yarn/berry/cache,id=server-ce-yarn-cache \
--mount=type=cache,target=/usr/local/share/.cache/yarn,id=server-ce-yarn-fallback-cache \
--mount=type=tmpfs,target=/tmp node genScript install | bash
# Add the actual source files
# ---------------------------
COPY --parents libraries/ services/ tools/migrations/ /overleaf/
RUN --mount=type=cache,target=/root/.cache \
--mount=type=cache,target=/root/.npm \
--mount=type=cache,target=/root/.yarn/berry/cache,id=server-ce-yarn-cache \
--mount=type=cache,target=/usr/local/share/.cache/yarn,id=server-ce-yarn-fallback-cache \
--mount=type=cache,target=/overleaf/services/web/node_modules/.cache,id=server-ce-webpack-cache \
--mount=type=tmpfs,target=/tmp node genScript compile | bash
--mount=type=tmpfs,target=/tmp \
node genScript compile | bash
# Copy runit service startup scripts to its location
# --------------------------------------------------
+2 -2
View File
@@ -2,7 +2,7 @@
# Overleaf Base Image (sharelatex/sharelatex-base)
# --------------------------------------------------
FROM phusion/baseimage:noble-1.0.2
FROM phusion/baseimage:noble-1.0.3
# 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="2026-01-13"
ENV REBUILT_AFTER="2026-05-21"
# Install dependencies
# --------------------
+5 -4
View File
@@ -4,12 +4,13 @@ MONOREPO_ROOT := ../
HERE=$(shell pwd)
export MONOREPO_REVISION := $(shell git rev-parse HEAD)
export BRANCH_NAME ?= $(shell git rev-parse --abbrev-ref HEAD)
export OVERLEAF_BASE_BRANCH ?= sharelatex/sharelatex-base:$(BRANCH_NAME)
export BRANCH_NAME_TAG_SAFE ?= $(subst /,--,$(BRANCH_NAME))
export OVERLEAF_BASE_BRANCH ?= sharelatex/sharelatex-base:$(BRANCH_NAME_TAG_SAFE)
export OVERLEAF_BASE_LATEST ?= sharelatex/sharelatex-base
export OVERLEAF_BASE_TAG ?= sharelatex/sharelatex-base:$(BRANCH_NAME)-$(MONOREPO_REVISION)
export OVERLEAF_BRANCH ?= sharelatex/sharelatex:$(BRANCH_NAME)
export OVERLEAF_BASE_TAG ?= sharelatex/sharelatex-base:$(BRANCH_NAME_TAG_SAFE)-$(MONOREPO_REVISION)
export OVERLEAF_BRANCH ?= sharelatex/sharelatex:$(BRANCH_NAME_TAG_SAFE)
export OVERLEAF_LATEST ?= sharelatex/sharelatex
export OVERLEAF_TAG ?= sharelatex/sharelatex:$(BRANCH_NAME)-$(MONOREPO_REVISION)
export OVERLEAF_TAG ?= sharelatex/sharelatex:$(BRANCH_NAME_TAG_SAFE)-$(MONOREPO_REVISION)
all: build-base build-community
+6 -6
View File
@@ -10,9 +10,9 @@ steps:
script: |
#!/usr/bin/env bash
set -e
if [ ${BRANCH_NAME} == main ]; then
docker tag ${_IMAGE_BASE} ${_IMAGE_BASE_LATEST};
docker push ${_IMAGE_BASE_LATEST};
if [ "${BRANCH_NAME}" = "main" ]; then
docker tag "${_IMAGE_BASE}" "${_IMAGE_BASE_LATEST}";
docker push "${_IMAGE_BASE_LATEST}";
fi
automapSubstitutions: true
- id: prefetch_ce
@@ -39,9 +39,9 @@ steps:
script: |
#!/usr/bin/env bash
set -e
if [ $BRANCH_NAME == main ]; then
docker tag ${_IMAGE_TAG} ${_IMAGE_TAG_LATEST};
docker push ${_IMAGE_TAG_LATEST};
if [ "${BRANCH_NAME}" = "main" ]; then
docker tag "${_IMAGE_TAG}" "${_IMAGE_TAG_LATEST}";
docker push "${_IMAGE_TAG_LATEST}";
fi
automapSubstitutions: true
waitFor:
-1
View File
@@ -1,6 +1,5 @@
export CHAT_HOST=127.0.0.1
export CLSI_HOST=127.0.0.1
export CONTACTS_HOST=127.0.0.1
export DOCSTORE_HOST=127.0.0.1
export DOCUMENT_UPDATER_HOST=127.0.0.1
export DOCUPDATER_HOST=127.0.0.1
+5 -5
View File
@@ -5,7 +5,7 @@ console.log('set -ex')
switch (process.argv.pop()) {
case 'install':
console.log('npm install --omit=dev')
console.log('yarn workspaces focus --all --production')
break
case 'compile':
for (const service of services) {
@@ -13,18 +13,18 @@ switch (process.argv.pop()) {
switch (service.name) {
case 'web':
// precompile pug in background
console.log('npm run precompile-pug &')
console.log('yarn run precompile-pug &')
console.log('pug_precompile=$!')
// Avoid downloading of cypress
console.log('export CYPRESS_INSTALL_BINARY=0')
// install webpack and frontend dependencies
console.log('npm install --include=dev')
console.log('yarn install')
// run webpack
console.log('npm run webpack:production')
console.log('yarn run webpack:production')
// uninstall webpack and frontend dependencies
console.log('npm prune --omit=dev')
console.log('yarn workspaces focus --all --production')
// Wait for pug precompile to finish
console.log('wait "$pug_precompile"')
@@ -2,6 +2,7 @@
set -e
mkdir -p /var/lib/overleaf/data
chown www-data:www-data /var/lib/overleaf
chown www-data:www-data /var/lib/overleaf/data
mkdir -p /var/lib/overleaf/data/compiles
@@ -1,7 +0,0 @@
#!/bin/sh
set -e
# Up to version 2.5.0 the logs of the contacts service were written into a
# file that was not picked up by logrotate.
# The service is stable and we can safely discard any logs.
rm -vf /var/log/overleaf/contacts
@@ -9,5 +9,5 @@ fi
echo "Running migrations for $environment"
cd /overleaf/tools/migrations
/sbin/setuser www-data npm run migrations -- migrate -t "$environment"
/sbin/setuser www-data yarn run migrations migrate -t "$environment"
echo "Finished migrations"
-12
View File
@@ -1,12 +0,0 @@
#!/bin/bash
NODE_PARAMS=""
if [ "$DEBUG_NODE" == "true" ]; then
echo "running debug - contacts"
NODE_PARAMS="--inspect=0.0.0.0:30360"
fi
source /etc/overleaf/env.sh
export LISTEN_ADDRESS=127.0.0.1
exec /sbin/setuser www-data /usr/bin/node $NODE_PARAMS /overleaf/services/contacts/app.js >> /var/log/overleaf/contacts.log 2>&1
-3
View File
@@ -20,9 +20,6 @@ module.exports = [
{
name: 'chat',
},
{
name: 'contacts',
},
{
name: 'notifications',
},
+7
View File
@@ -1,4 +1,11 @@
FROM node:24.14.1
# Corepack setup, shared between all the images.
ENV PATH="/overleaf/node_modules/.bin:$PATH"
ENV COREPACK_HOME=/opt/corepack
RUN corepack enable && corepack install -g yarn@4.14.1
ENV COREPACK_ENABLE_NETWORK=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" \
+15 -4
View File
@@ -17,13 +17,25 @@ export USER_UID=$(shell id -u)
export USER_GID=$(shell id -g)
IMAGE_TAG_PRO_BASE := $(shell echo $(IMAGE_TAG_PRO) | sed -E s/:.+//)
print-debug-logs:
-docker compose logs --timestamps --tail=100 e2e
-docker compose logs --timestamps --tail=100 mongo
-docker compose logs --timestamps --tail=100 redis
-docker compose logs --timestamps --tail=100 mailtrap
-docker compose logs --timestamps --tail=100 saml
-docker compose logs --timestamps --tail=100 ldap
-docker compose logs --timestamps --tail=100 git-bridge
-docker compose logs --timestamps --tail=100 host-admin
-docker compose logs --timestamps sharelatex
-docker compose exec sharelatex bash -c 'tail -n100 /var/log/overleaf/*.log'
test-e2e-native:
docker compose -f docker-compose.yml -f docker-compose.native.yml up --no-log-prefix sharelatex host-admin -d
CYPRESS_BASE_URL='http://127.0.0.1:3231' \
CYPRESS_HOST_ADMIN_URL='http://127.0.0.1:3232' \
CYPRESS_SAML_URL='http://127.0.0.1:3233' \
CYPRESS_MAILTRAP_URL='http://127.0.0.1:3234' \
npm run cypress:open
yarn run cypress:open
# For testing "native" on Linux
test-e2e-native-linux:
@@ -35,7 +47,7 @@ test-e2e-native-linux:
docker compose -f docker-compose.yml -f docker-compose.native.yml up --no-log-prefix cypress
test-e2e:
docker compose build host-admin
docker compose build host-admin e2e
docker compose up --detach --wait host-admin
docker compose up --detach --wait mongo
docker compose up --no-log-prefix --exit-code-from=e2e e2e host-admin
@@ -69,7 +81,7 @@ prefetch_default_compose:
prefetch_default: prefetch_default_compose_build
prefetch_default_compose_build:
docker compose build host-admin
docker compose build host-admin e2e
prefetch: prefetch_custom
prefetch_custom: prefetch_custom_compose_pull
@@ -92,7 +104,6 @@ prefetch_old_4_2:
prefetch_custom: prefetch_old_5_0
prefetch_old_5_0:
docker pull $(IMAGE_TAG_PRO_BASE):5.0.1-RC1
docker pull $(IMAGE_TAG_PRO_BASE):5.0
prefetch_custom: build_hotfix_5_5_5
+9 -1
View File
@@ -72,7 +72,7 @@ services:
- USER_UID
- USER_GID
stop_grace_period: 0s
entrypoint: npm
entrypoint: yarn
command: run cypress:run
# See comment in Makefile regarding matching file paths
working_dir: $PWD
@@ -80,6 +80,10 @@ services:
- $PWD:$PWD
- $MONOREPO/libraries:$MONOREPO/libraries:ro
- $MONOREPO/node_modules:$MONOREPO/node_modules:ro
- $MONOREPO/.yarn:$MONOREPO/.yarn:ro
- $MONOREPO/.yarnrc.yml:$MONOREPO/.yarnrc.yml:ro
- $MONOREPO/package.json:$MONOREPO/package.json:ro
- $MONOREPO/yarn.lock:$MONOREPO/yarn.lock:ro
environment:
MONOREPO:
CYPRESS_SHARD:
@@ -110,6 +114,10 @@ services:
- $PWD:$PWD
- $MONOREPO/libraries:$MONOREPO/libraries:ro
- $MONOREPO/node_modules:$MONOREPO/node_modules:ro
- $MONOREPO/.yarn:$MONOREPO/.yarn:ro
- $MONOREPO/.yarnrc.yml:$MONOREPO/.yarnrc.yml:ro
- $MONOREPO/package.json:$MONOREPO/package.json:ro
- $MONOREPO/yarn.lock:$MONOREPO/yarn.lock:ro
- /var/run/docker.sock:/var/run/docker.sock
stop_grace_period: 0s
environment:
+2
View File
@@ -140,6 +140,7 @@ describe('editor', function () {
describe('file menu', function () {
it('can download project sources', function () {
cy.findByRole('button', { name: 'File' }).click()
cy.findByRole('menuitem', { name: 'Download' }).click()
cy.findByRole('menuitem', { name: 'Download as source (.zip)' }).click()
const zipName = projectName.replaceAll('-', '_')
cy.task('readFileInZip', {
@@ -156,6 +157,7 @@ describe('editor', function () {
)
cy.findByRole('button', { name: 'File' }).click()
cy.findByRole('menuitem', { name: 'Download' }).click()
cy.findByRole('menuitem', { name: 'Download as PDF' }).click()
const pdfName = projectName.replaceAll('-', '_')
cy.task('readPdf', `cypress/downloads/${pdfName}.pdf`).should(
+4 -1
View File
@@ -9,7 +9,10 @@ export function stopCompile(options: { delay?: number } = {}) {
cy.wait(delay)
cy.log('Stop compile')
cy.findByRole('button', { name: 'Toggle compile options menu' }).click()
cy.findByRole('menuitem', { name: 'Stop compilation' }).click()
cy.findByRole('menuitem', { name: 'Stop compilation' })
.should('not.have.class', 'disabled')
.and('not.have.attr', 'aria-disabled', 'true')
.click()
}
export function prepareWaitForNextCompileSlot() {
+2 -1
View File
@@ -245,7 +245,8 @@ export function getSpamSafeProjectName() {
// Move from hex/16 to base64/64 possible characters per char in string
const name = Buffer.from(uuid().replaceAll('-', ''), 'hex')
.toString('base64')
.replace('/', '_')
.replaceAll('/', 'x')
.replaceAll('+', 'y')
.slice(0, 10)
const nDigits = (name.match(/\d/g) || []).length
if (nDigits < 6) return name
+11 -6
View File
@@ -5,8 +5,12 @@ import { execFile as execFileCb } from 'node:child_process'
import bodyParser from 'body-parser'
import express from 'express'
import YAML from 'js-yaml'
import { isZodErrorLike } from 'zod-validation-error'
import { ParamsError, parseReq, z } from '@overleaf/validation-tools'
import {
InvalidParamsError,
InvalidRequestError,
parseReq,
z,
} from '@overleaf/validation-tools'
import { expressify } from '@overleaf/promise-utils'
const execFile = promisify(execFileCb)
@@ -474,12 +478,13 @@ app.delete(
)
app.use((error, req, res, next) => {
if (error instanceof ParamsError) {
if (error instanceof InvalidParamsError) {
res.status(404).json({ error })
} else if (isZodErrorLike(error)) {
res.status(400).json({ error })
} else if (error instanceof InvalidRequestError) {
res.status(400).json({ error: error.zodError })
} else {
next(error)
}
next(error)
})
purgeDataDir()
+4 -4
View File
@@ -11,14 +11,14 @@
},
"devDependencies": {
"@isomorphic-git/lightning-fs": "^4.6.0",
"@overleaf/promise-utils": "*",
"@overleaf/validation-tools": "*",
"@testing-library/cypress": "10.0.3",
"@overleaf/promise-utils": "workspace:*",
"@overleaf/validation-tools": "workspace:*",
"@testing-library/cypress": "10.1.0",
"@types/adm-zip": "^0.5.7",
"@types/uuid": "^9.0.8",
"adm-zip": "^0.5.12",
"body-parser": "1.20.4",
"cypress": "13.13.2",
"cypress": "15.12.0",
"cypress-multi-reporters": "^2.0.5",
"express": "4.22.1",
"isomorphic-git": "^1.33.1",

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