Merge pull request #28760 from overleaf/dp-hackathon-knip
Add Knip and remove a bunch of unused code GitOrigin-RevId: 42ab99fc65973c883d2361e0027e7181767e714e
This commit is contained in:
Generated
+517
-10
@@ -10524,6 +10524,40 @@
|
||||
"url": "https://github.com/sponsors/wooorm"
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/core": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.5.0.tgz",
|
||||
"integrity": "sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emnapi/wasi-threads": "1.1.0",
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/runtime": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.5.0.tgz",
|
||||
"integrity": "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@emnapi/wasi-threads": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.1.0.tgz",
|
||||
"integrity": "sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.25.3",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.3.tgz",
|
||||
@@ -13205,6 +13239,19 @@
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/wasm-runtime": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.0.5.tgz",
|
||||
"integrity": "sha512-TBr9Cf9onSAS2LQ2+QHx6XcC6h9+RIzJgbqG3++9TUZSH204AwEy5jg3BTQ0VATsyoGj4ee49tN/y6rvaOOtcg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emnapi/core": "^1.5.0",
|
||||
"@emnapi/runtime": "^1.5.0",
|
||||
"@tybys/wasm-util": "^0.10.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@nicolo-ribaudo/chokidar-2": {
|
||||
"version": "2.1.8-no-fsevents.3",
|
||||
"resolved": "https://registry.npmjs.org/@nicolo-ribaudo/chokidar-2/-/chokidar-2-2.1.8-no-fsevents.3.tgz",
|
||||
@@ -14904,6 +14951,275 @@
|
||||
"resolved": "services/web",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@oxc-resolver/binding-android-arm-eabi": {
|
||||
"version": "11.8.4",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm-eabi/-/binding-android-arm-eabi-11.8.4.tgz",
|
||||
"integrity": "sha512-6BjMji0TcvQfJ4EoSunOSyu/SiyHKficBD0V3Y0NxF0beaNnnZ7GYEi2lHmRNnRCuIPK8IuVqQ6XizYau+CkKw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
]
|
||||
},
|
||||
"node_modules/@oxc-resolver/binding-android-arm64": {
|
||||
"version": "11.8.4",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm64/-/binding-android-arm64-11.8.4.tgz",
|
||||
"integrity": "sha512-SxF4X6rzCBS9XNPXKZGoIHIABjfGmtQpEgRBDzpDHx5VTuLAUmwLTHXnVBAZoX5bmnhF79RiMElavzFdJ2cA1A==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
]
|
||||
},
|
||||
"node_modules/@oxc-resolver/binding-darwin-arm64": {
|
||||
"version": "11.8.4",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-arm64/-/binding-darwin-arm64-11.8.4.tgz",
|
||||
"integrity": "sha512-8zWeERrzgscAniE6kh1TQ4E7GJyglYsvdoKrHYLBCbHWD+0/soffiwAYxZuckKEQSc2RXMSPjcu+JTCALaY0Dw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
]
|
||||
},
|
||||
"node_modules/@oxc-resolver/binding-darwin-x64": {
|
||||
"version": "11.8.4",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-x64/-/binding-darwin-x64-11.8.4.tgz",
|
||||
"integrity": "sha512-BUwggKz8Hi5uEQ0AeVTSun1+sp4lzNcItn+L7fDsHu5Cx0Zueuo10BtVm+dIwmYVVPL5oGYOeD0fS7MKAazKiw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
]
|
||||
},
|
||||
"node_modules/@oxc-resolver/binding-freebsd-x64": {
|
||||
"version": "11.8.4",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-freebsd-x64/-/binding-freebsd-x64-11.8.4.tgz",
|
||||
"integrity": "sha512-fPO5TQhnn8gA6yP4o49lc4Gn8KeDwAp9uYd4PlE3Q00JVqU6cY9WecDhYHrWtiFcyoZ8UVBlIxuhRqT/DP4Z4A==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"freebsd"
|
||||
]
|
||||
},
|
||||
"node_modules/@oxc-resolver/binding-linux-arm-gnueabihf": {
|
||||
"version": "11.8.4",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-11.8.4.tgz",
|
||||
"integrity": "sha512-QuNbdUaVGiP0W0GrXsvCDZjqeL4lZGU7aXlx/S2tCvyTk3wh6skoiLJgqUf/eeqXfUPnzTfntYqyfolzCAyBYA==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@oxc-resolver/binding-linux-arm-musleabihf": {
|
||||
"version": "11.8.4",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-11.8.4.tgz",
|
||||
"integrity": "sha512-p/zLMfza8OsC4BDKxqeZ9Qel+4eA/oiMSyKLRkMrTgt6OWQq1d5nHntjfG35Abcw4ev6Q9lRU3NOW5hj0xlUbw==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@oxc-resolver/binding-linux-arm64-gnu": {
|
||||
"version": "11.8.4",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-11.8.4.tgz",
|
||||
"integrity": "sha512-bvJF9wWxF1+a5YZATlS5JojpOMC7OsnTatA6sXVHoOb7MIigjledYB5ZMAeRrnWWexRMiEX3YSaA46oSfOzmOg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@oxc-resolver/binding-linux-arm64-musl": {
|
||||
"version": "11.8.4",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-musl/-/binding-linux-arm64-musl-11.8.4.tgz",
|
||||
"integrity": "sha512-gf4nwGBfu+EFwOn5p7/T7VF4jmIdfodwJS9MRkOBHvuAm3LQgCX7O6d3Y80mm0TV7ZMRD/trfW628rHfd5++vQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@oxc-resolver/binding-linux-ppc64-gnu": {
|
||||
"version": "11.8.4",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-11.8.4.tgz",
|
||||
"integrity": "sha512-T120R5GIzRd41rYWWKCI6cSYrZjmRQzf3X4xeE1WX396Uabz5DX8KU7RnVHihSK+KDxccCVOFBxcH3ITd+IEpw==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@oxc-resolver/binding-linux-riscv64-gnu": {
|
||||
"version": "11.8.4",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-11.8.4.tgz",
|
||||
"integrity": "sha512-PVG7SxBFFjAaQ76p9O/0Xt5mTBlziRwpck+6cRNhy/hbWY/hSt8BFfPqw0EDSfnl40Uuh+NPsHFMnaWWyxbQEg==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@oxc-resolver/binding-linux-riscv64-musl": {
|
||||
"version": "11.8.4",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-11.8.4.tgz",
|
||||
"integrity": "sha512-L0OklUhM2qLGaKvPSyKmwWpoijfc++VJtPyVgz031ShOXyo0WjD0ZGzusyJMsA1a/gdulAmN6CQ/0Sf4LGXEcw==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@oxc-resolver/binding-linux-s390x-gnu": {
|
||||
"version": "11.8.4",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-11.8.4.tgz",
|
||||
"integrity": "sha512-18Ajz5hqO4cRGuoHzLFUsIPod9GIaIRDiXFg2m6CS3NgVdHx7iCZscplYH7KtjdE42M8nGWYMyyq5BOk7QVgPw==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@oxc-resolver/binding-linux-x64-gnu": {
|
||||
"version": "11.8.4",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-gnu/-/binding-linux-x64-gnu-11.8.4.tgz",
|
||||
"integrity": "sha512-uHvH4RyYBdQ/lFGV9H+R1ScHg6EBnAhE3mnX+u+mO/btnalvg7j80okuHf8Qw0tLQiP5P1sEBoVeE6zviXY9IA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@oxc-resolver/binding-linux-x64-musl": {
|
||||
"version": "11.8.4",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-musl/-/binding-linux-x64-musl-11.8.4.tgz",
|
||||
"integrity": "sha512-X5z44qh5DdJfVhcqXAQFTDFUpcxdpf6DT/lHL5CFcdQGIZxatjc7gFUy05IXPI9xwfq39RValjJBvFovUk9XBw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@oxc-resolver/binding-wasm32-wasi": {
|
||||
"version": "11.8.4",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-wasm32-wasi/-/binding-wasm32-wasi-11.8.4.tgz",
|
||||
"integrity": "sha512-z3906y+cd8RRhBGNwHRrRAFxnKjXsBeL3+rdQjZpBrUyrhhsaV5iKD/ROx64FNJ9GjL/9mfon8A5xx/McYIqHA==",
|
||||
"cpu": [
|
||||
"wasm32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@napi-rs/wasm-runtime": "^1.0.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@oxc-resolver/binding-win32-arm64-msvc": {
|
||||
"version": "11.8.4",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-11.8.4.tgz",
|
||||
"integrity": "sha512-70vXFs74uA3X5iYOkpclbkWlQEF+MI325uAQ+Or2n8HJip2T0SEmuBlyw/sRL2E8zLC4oocb+1g25fmzlDVkmg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@oxc-resolver/binding-win32-ia32-msvc": {
|
||||
"version": "11.8.4",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-11.8.4.tgz",
|
||||
"integrity": "sha512-SEOUAzTvr+nyMia3nx1dMtD7YUxZwuhQ3QAPnxy21261Lj0yT3JY4EIfwWH54lAWWfMdRSRRMFuGeF/dq7XjEw==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@oxc-resolver/binding-win32-x64-msvc": {
|
||||
"version": "11.8.4",
|
||||
"resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-x64-msvc/-/binding-win32-x64-msvc-11.8.4.tgz",
|
||||
"integrity": "sha512-1gARIQsOPOU7LJ7jvMyPmZEVMapL/PymeG3J7naOdLZDrIZKX6CTvgawJmETYKt+8icP8M6KbBinrVkKVqFd+A==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@paralleldrive/cuid2": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.2.2.tgz",
|
||||
@@ -18830,6 +19146,17 @@
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tybys/wasm-util": {
|
||||
"version": "0.10.1",
|
||||
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
|
||||
"integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/accepts": {
|
||||
"version": "1.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/accepts/-/accepts-1.3.5.tgz",
|
||||
@@ -19386,12 +19713,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "24.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.1.tgz",
|
||||
"integrity": "sha512-3vXmQDXy+woz+gnrTvuvNrPzekOi+Ds0ReMxw0LzBiK3a+1k0kQn9f2NWk+lgD4rJehFUmYy2gMhJ2ZI+7YP9g==",
|
||||
"version": "24.5.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.5.2.tgz",
|
||||
"integrity": "sha512-FYxk1I7wPv3K2XBaoyH2cTnocQEu8AOZ60hPbsyukMPLv5/5qr7V1i8PLHdl6Zf87I+xZXFvPCXYjiTFq+YSDQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~7.10.0"
|
||||
"undici-types": "~7.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node-fetch": {
|
||||
@@ -19413,9 +19740,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node/node_modules/undici-types": {
|
||||
"version": "7.10.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz",
|
||||
"integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==",
|
||||
"version": "7.12.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.12.0.tgz",
|
||||
"integrity": "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/normalize-package-data": {
|
||||
@@ -30562,6 +30889,42 @@
|
||||
"node": ">=0.4.x"
|
||||
}
|
||||
},
|
||||
"node_modules/formatly": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/formatly/-/formatly-0.3.0.tgz",
|
||||
"integrity": "sha512-9XNj/o4wrRFyhSMJOvsuyMwy8aUfBaZ1VrqHVfohyXf0Sw0e+yfKG+xZaY3arGCOMdwFsqObtzVOc1gU9KiT9w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fd-package-json": "^2.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"formatly": "bin/index.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/formatly/node_modules/fd-package-json": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fd-package-json/-/fd-package-json-2.0.0.tgz",
|
||||
"integrity": "sha512-jKmm9YtsNXN789RS/0mSzOC1NUq9mkVd65vbSSVsKdjGvYXBuE4oWe2QOEoFeRmJg+lPuZxpmrfFclNhoRMneQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"walk-up-path": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/formatly/node_modules/walk-up-path": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/walk-up-path/-/walk-up-path-4.0.0.tgz",
|
||||
"integrity": "sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": "20 || >=22"
|
||||
}
|
||||
},
|
||||
"node_modules/formdata-node": {
|
||||
"version": "4.3.2",
|
||||
"resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.3.2.tgz",
|
||||
@@ -34931,6 +35294,84 @@
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||
},
|
||||
"node_modules/knip": {
|
||||
"version": "5.64.1",
|
||||
"resolved": "https://registry.npmjs.org/knip/-/knip-5.64.1.tgz",
|
||||
"integrity": "sha512-80XnLsyeXuyxj1F4+NBtQFHxaRH0xWRw8EKwfQ6EkVZZ0bSz/kqqan08k/Qg8ajWsFPhFq+0S2RbLCBGIQtuOg==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/webpro"
|
||||
},
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/knip"
|
||||
}
|
||||
],
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@nodelib/fs.walk": "^1.2.3",
|
||||
"fast-glob": "^3.3.3",
|
||||
"formatly": "^0.3.0",
|
||||
"jiti": "^2.6.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"minimist": "^1.2.8",
|
||||
"oxc-resolver": "^11.8.3",
|
||||
"picocolors": "^1.1.1",
|
||||
"picomatch": "^4.0.1",
|
||||
"smol-toml": "^1.4.1",
|
||||
"strip-json-comments": "5.0.2",
|
||||
"zod": "^4.1.11"
|
||||
},
|
||||
"bin": {
|
||||
"knip": "bin/knip.js",
|
||||
"knip-bun": "bin/knip-bun.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.18.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/node": ">=18",
|
||||
"typescript": ">=5.0.4 <7"
|
||||
}
|
||||
},
|
||||
"node_modules/knip/node_modules/jiti": {
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.0.tgz",
|
||||
"integrity": "sha512-VXe6RjJkBPj0ohtqaO8vSWP3ZhAKo66fKrFNCll4BTcwljPLz03pCbaNKfzGP5MbrCYcbJ7v0nOYYwUzTEIdXQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"jiti": "lib/jiti-cli.mjs"
|
||||
}
|
||||
},
|
||||
"node_modules/knip/node_modules/picomatch": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/knip/node_modules/strip-json-comments": {
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.2.tgz",
|
||||
"integrity": "sha512-4X2FR3UwhNUE9G49aIsJW5hRRR3GXGTBTZRMfv568O60ojM8HcWjV/VxAxCDW3SUND33O6ZY66ZuRcdkj73q2g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14.16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/known-css-properties": {
|
||||
"version": "0.30.0",
|
||||
"resolved": "https://registry.npmjs.org/known-css-properties/-/known-css-properties-0.30.0.tgz",
|
||||
@@ -37630,6 +38071,22 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/napi-postinstall": {
|
||||
"version": "0.3.3",
|
||||
"resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.3.tgz",
|
||||
"integrity": "sha512-uTp172LLXSxuSYHv/kou+f6KW3SMppU9ivthaVTXian9sOt3XM/zHYHpRZiLgQoxeWfYUnslNWQHF1+G71xcow==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"napi-postinstall": "lib/cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.18.0 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/napi-postinstall"
|
||||
}
|
||||
},
|
||||
"node_modules/native-promise-only": {
|
||||
"version": "0.8.1",
|
||||
"resolved": "https://registry.npmjs.org/native-promise-only/-/native-promise-only-0.8.1.tgz",
|
||||
@@ -38573,6 +39030,41 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/oxc-resolver": {
|
||||
"version": "11.8.4",
|
||||
"resolved": "https://registry.npmjs.org/oxc-resolver/-/oxc-resolver-11.8.4.tgz",
|
||||
"integrity": "sha512-qpimS3tHHEf+kgESMAme+q+rj7aCzMya00u9YdKOKyX2o7q4lozjPo6d7ZTTi979KHEcVOPWdNTueAKdeNq72w==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"napi-postinstall": "^0.3.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/Boshen"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@oxc-resolver/binding-android-arm-eabi": "11.8.4",
|
||||
"@oxc-resolver/binding-android-arm64": "11.8.4",
|
||||
"@oxc-resolver/binding-darwin-arm64": "11.8.4",
|
||||
"@oxc-resolver/binding-darwin-x64": "11.8.4",
|
||||
"@oxc-resolver/binding-freebsd-x64": "11.8.4",
|
||||
"@oxc-resolver/binding-linux-arm-gnueabihf": "11.8.4",
|
||||
"@oxc-resolver/binding-linux-arm-musleabihf": "11.8.4",
|
||||
"@oxc-resolver/binding-linux-arm64-gnu": "11.8.4",
|
||||
"@oxc-resolver/binding-linux-arm64-musl": "11.8.4",
|
||||
"@oxc-resolver/binding-linux-ppc64-gnu": "11.8.4",
|
||||
"@oxc-resolver/binding-linux-riscv64-gnu": "11.8.4",
|
||||
"@oxc-resolver/binding-linux-riscv64-musl": "11.8.4",
|
||||
"@oxc-resolver/binding-linux-s390x-gnu": "11.8.4",
|
||||
"@oxc-resolver/binding-linux-x64-gnu": "11.8.4",
|
||||
"@oxc-resolver/binding-linux-x64-musl": "11.8.4",
|
||||
"@oxc-resolver/binding-wasm32-wasi": "11.8.4",
|
||||
"@oxc-resolver/binding-win32-arm64-msvc": "11.8.4",
|
||||
"@oxc-resolver/binding-win32-ia32-msvc": "11.8.4",
|
||||
"@oxc-resolver/binding-win32-x64-msvc": "11.8.4"
|
||||
}
|
||||
},
|
||||
"node_modules/p-event": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/p-event/-/p-event-4.2.0.tgz",
|
||||
@@ -44670,6 +45162,19 @@
|
||||
"npm": ">= 3.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/smol-toml": {
|
||||
"version": "1.4.2",
|
||||
"resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.4.2.tgz",
|
||||
"integrity": "sha512-rInDH6lCNiEyn3+hH8KVGFdbjc099j47+OSgbMrfDYX1CmXLfdKd7qi6IfcWj2wFxvSVkuI46M+wPGYfEOEj6g==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/cyyynthia"
|
||||
}
|
||||
},
|
||||
"node_modules/snapdragon": {
|
||||
"version": "0.8.2",
|
||||
"resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz",
|
||||
@@ -50694,9 +51199,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/zod": {
|
||||
"version": "4.1.8",
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-4.1.8.tgz",
|
||||
"integrity": "sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ==",
|
||||
"version": "4.1.11",
|
||||
"resolved": "https://registry.npmjs.org/zod/-/zod-4.1.11.tgz",
|
||||
"integrity": "sha512-WPsqwxITS2tzx1bzhIKsEs19ABD5vmCVa4xBo2tq/SrV4RNZtfws1EnCWQXM6yh8bD08a1idvkB5MZSBiZsjwg==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/colinhacks"
|
||||
@@ -52810,6 +53315,7 @@
|
||||
"@types/express": "^4.17.23",
|
||||
"@types/mocha": "^9.1.0",
|
||||
"@types/mocha-each": "^2.0.4",
|
||||
"@types/node": "^24.5.2",
|
||||
"@types/react": "^18.3.20",
|
||||
"@types/react-color": "^3.0.13",
|
||||
"@types/react-dom": "^18.3.6",
|
||||
@@ -52893,6 +53399,7 @@
|
||||
"jscodeshift": "^17.0.0",
|
||||
"jsdom": "^19.0.0",
|
||||
"jsdom-global": "^3.0.2",
|
||||
"knip": "^5.64.1",
|
||||
"match-sorter": "^6.2.0",
|
||||
"mathjax": "^3.2.2",
|
||||
"mediatr-ts": "^2.0.1",
|
||||
|
||||
@@ -978,7 +978,6 @@ module.exports = {
|
||||
tprFileViewRefreshButton: [],
|
||||
tprFileViewNotOriginalImporter: [],
|
||||
contactUsModal: [],
|
||||
editorToolbarButtons: [],
|
||||
sourceEditorExtensions: [],
|
||||
sourceEditorComponents: [],
|
||||
pdfLogEntryHeaderActionComponents: [],
|
||||
|
||||
@@ -101,7 +101,6 @@
|
||||
"added_by_on": "",
|
||||
"adding": "",
|
||||
"additional_certificate": "",
|
||||
"additional_licenses": "",
|
||||
"address_line_1": "",
|
||||
"address_second_line_optional": "",
|
||||
"adjust_column_width": "",
|
||||
@@ -334,7 +333,6 @@
|
||||
"connection_lost_with_unsaved_changes": "",
|
||||
"contact_group_admin": "",
|
||||
"contact_sales": "",
|
||||
"contact_support_to_change_group_subscription": "",
|
||||
"contact_us": "",
|
||||
"contacting_the_sales_team": "",
|
||||
"continue": "",
|
||||
|
||||
@@ -58,17 +58,6 @@ prototype\
|
||||
|
||||
const MAX_PATH = 1024 // Maximum path length, in characters. This is fairly arbitrary.
|
||||
|
||||
export function clean(filename: string): string {
|
||||
filename = filename.replace(BADCHAR_RX, '_')
|
||||
// for BADFILE_RX replace any matches with an equal number of underscores
|
||||
filename = filename.replace(BADFILE_RX, match =>
|
||||
new Array(match.length + 1).join('_')
|
||||
)
|
||||
// replace blocked filenames 'prototype' with '@prototype'
|
||||
filename = filename.replace(BLOCKEDFILE_RX, '@$1')
|
||||
return filename
|
||||
}
|
||||
|
||||
export function isCleanFilename(filename: string): boolean {
|
||||
return (
|
||||
isAllowedLength(filename) &&
|
||||
@@ -81,30 +70,6 @@ export function isBlockedFilename(filename: string): boolean {
|
||||
return BLOCKEDFILE_RX.test(filename)
|
||||
}
|
||||
|
||||
// returns whether a full path is 'clean' - e.g. is a full or relative path
|
||||
// that points to a file, and each element passes the rules in 'isCleanFilename'
|
||||
export function isCleanPath(path: string): boolean {
|
||||
const elements = path.split('/')
|
||||
|
||||
const lastElementIsEmpty = elements[elements.length - 1].length === 0
|
||||
if (lastElementIsEmpty) {
|
||||
return false
|
||||
}
|
||||
|
||||
for (const element of Array.from(elements)) {
|
||||
if (element.length > 0 && !isCleanFilename(element)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// check for a top-level reserved name
|
||||
if (BLOCKEDFILE_RX.test(path.replace(/^\/?/, ''))) {
|
||||
return false
|
||||
} // remove leading slash if present
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
export function isAllowedLength(pathname: string): boolean {
|
||||
return pathname.length > 0 && pathname.length <= MAX_PATH
|
||||
}
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
import { debugConsole } from '@/utils/debugging'
|
||||
|
||||
export function waitFor<T>(
|
||||
testFunction: () => T,
|
||||
timeout: number,
|
||||
pollInterval = 500
|
||||
): Promise<T> {
|
||||
const iterationLimit = Math.floor(timeout / pollInterval)
|
||||
let iterations = 0
|
||||
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
const tryIteration = () => {
|
||||
if (iterations > iterationLimit) {
|
||||
const err = new Error(
|
||||
`waiting too long, ${JSON.stringify({ timeout, pollInterval })}`
|
||||
)
|
||||
debugConsole.error(err)
|
||||
reject(err)
|
||||
return
|
||||
}
|
||||
|
||||
iterations += 1
|
||||
const result = testFunction()
|
||||
|
||||
if (result) {
|
||||
resolve(result)
|
||||
return
|
||||
}
|
||||
|
||||
setTimeout(tryIteration, pollInterval)
|
||||
}
|
||||
|
||||
tryIteration()
|
||||
})
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
import { debugConsole } from '@/utils/debugging'
|
||||
|
||||
type EditorEvent = { type: string; meta: unknown; date: Date }
|
||||
|
||||
// Record events and then do nothing with them.
|
||||
export class EventLog {
|
||||
private recentEvents: EditorEvent[] = []
|
||||
|
||||
pushEvent = (type: string, meta: unknown = {}) => {
|
||||
debugConsole.log('event', type, meta)
|
||||
this.recentEvents.push({ type, meta, date: new Date() })
|
||||
if (this.recentEvents.length > 100) {
|
||||
return this.recentEvents.shift()
|
||||
}
|
||||
}
|
||||
}
|
||||
-10
@@ -1,10 +0,0 @@
|
||||
import PdfLogsViewer from '@/features/pdf-preview/components/pdf-logs-viewer'
|
||||
import { PdfPreviewProvider } from '@/features/pdf-preview/components/pdf-preview-provider'
|
||||
|
||||
export default function OldErrorPane() {
|
||||
return (
|
||||
<PdfPreviewProvider>
|
||||
<PdfLogsViewer alwaysVisible />
|
||||
</PdfPreviewProvider>
|
||||
)
|
||||
}
|
||||
@@ -6,6 +6,7 @@ import classNames from 'classnames'
|
||||
import { useCallback, useEffect } from 'react'
|
||||
import { Overlay, OverlayProps, Popover } from 'react-bootstrap'
|
||||
|
||||
/** @knipignore keep this file around even when there is no current promo using it */
|
||||
export default function TooltipPromotion({
|
||||
target,
|
||||
tutorialKey,
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import { useTutorial } from '@/shared/hooks/promotions/use-tutorial'
|
||||
import useTutorial from '@/shared/hooks/promotions/use-tutorial'
|
||||
import { useEditorContext } from '@/shared/context/editor-context'
|
||||
import { useProjectContext } from '@/shared/context/project-context'
|
||||
import OLNotification from '@/shared/components/ol/ol-notification'
|
||||
|
||||
-30
@@ -1,30 +0,0 @@
|
||||
import { ReactNode } from 'react'
|
||||
|
||||
type MenuItemButtonProps = {
|
||||
children: ReactNode
|
||||
onClick?: (e?: React.MouseEvent) => void
|
||||
className?: string
|
||||
afterNode?: React.ReactNode
|
||||
}
|
||||
|
||||
export default function MenuItemButton({
|
||||
children,
|
||||
onClick,
|
||||
className,
|
||||
afterNode,
|
||||
...buttonProps
|
||||
}: MenuItemButtonProps) {
|
||||
return (
|
||||
<li role="presentation" className={className}>
|
||||
<button
|
||||
className="menu-item-button"
|
||||
role="menuitem"
|
||||
onClick={onClick}
|
||||
{...buttonProps}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
{afterNode}
|
||||
</li>
|
||||
)
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
import OLFormSwitch from '@/shared/components/ol/ol-form-switch'
|
||||
|
||||
type TrackChangesToggleProps = {
|
||||
id: string
|
||||
description: string
|
||||
disabled: boolean
|
||||
handleToggle: () => void
|
||||
value: boolean
|
||||
}
|
||||
|
||||
function TrackChangesToggle({
|
||||
id,
|
||||
description,
|
||||
disabled,
|
||||
handleToggle,
|
||||
value,
|
||||
}: TrackChangesToggleProps) {
|
||||
return (
|
||||
<OLFormSwitch
|
||||
id={`input-switch-${id}`}
|
||||
disabled={disabled}
|
||||
onChange={handleToggle}
|
||||
checked={value}
|
||||
label={description}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default TrackChangesToggle
|
||||
@@ -1,79 +1,11 @@
|
||||
import { ReactNode } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { sendMB } from '@/infrastructure/event-tracking'
|
||||
import OLBadge from '@/shared/components/ol/ol-badge'
|
||||
import OLButton from '@/shared/components/ol/ol-button'
|
||||
|
||||
function trackUpgradeClick() {
|
||||
sendMB('settings-upgrade-click')
|
||||
}
|
||||
|
||||
type EnableWidgetProps = {
|
||||
logo: ReactNode
|
||||
title: string
|
||||
description: string
|
||||
helpPath: string
|
||||
helpTextOverride?: string
|
||||
hasFeature?: boolean
|
||||
isPremiumFeature?: boolean
|
||||
statusIndicator?: ReactNode
|
||||
children?: ReactNode
|
||||
linked?: boolean
|
||||
handleLinkClick: () => void
|
||||
handleUnlinkClick: () => void
|
||||
disabled?: boolean
|
||||
}
|
||||
|
||||
export function EnableWidget({
|
||||
logo,
|
||||
title,
|
||||
description,
|
||||
helpPath,
|
||||
helpTextOverride,
|
||||
hasFeature,
|
||||
isPremiumFeature,
|
||||
statusIndicator,
|
||||
linked,
|
||||
handleLinkClick,
|
||||
handleUnlinkClick,
|
||||
children,
|
||||
disabled,
|
||||
}: EnableWidgetProps) {
|
||||
const { t } = useTranslation()
|
||||
const helpText = helpTextOverride || t('learn_more')
|
||||
|
||||
return (
|
||||
<div className="settings-widget-container">
|
||||
<div>{logo}</div>
|
||||
<div className="description-container">
|
||||
<div className="title-row">
|
||||
<h4>{title}</h4>
|
||||
{!hasFeature && isPremiumFeature && (
|
||||
<OLBadge bg="info">{t('premium_feature')}</OLBadge>
|
||||
)}
|
||||
</div>
|
||||
<p className="small">
|
||||
{description}{' '}
|
||||
<a href={helpPath} target="_blank" rel="noreferrer">
|
||||
{helpText}
|
||||
</a>
|
||||
</p>
|
||||
{children}
|
||||
{hasFeature && statusIndicator}
|
||||
</div>
|
||||
<div>
|
||||
<ActionButton
|
||||
hasFeature={hasFeature}
|
||||
linked={linked}
|
||||
handleUnlinkClick={handleUnlinkClick}
|
||||
handleLinkClick={handleLinkClick}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
type ActionButtonProps = {
|
||||
hasFeature?: boolean
|
||||
linked?: boolean
|
||||
@@ -128,5 +60,3 @@ export function ActionButton({
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default EnableWidget
|
||||
|
||||
-16
@@ -27,19 +27,3 @@ type AbsoluteWidth = {
|
||||
}
|
||||
|
||||
export type WidthSelection = PercentageWidth | CustomWidth | AbsoluteWidth
|
||||
|
||||
export const isPercentageWidth = (
|
||||
width: WidthSelection
|
||||
): width is PercentageWidth => {
|
||||
return width.unit === '%'
|
||||
}
|
||||
|
||||
export const isAbsoluteWidth = (
|
||||
width: WidthSelection
|
||||
): width is AbsoluteWidth => {
|
||||
return (ABSOLUTE_UNITS as readonly string[]).includes(width.unit)
|
||||
}
|
||||
|
||||
export const isCustomWidth = (width: WidthSelection): width is CustomWidth => {
|
||||
return width.unit === 'custom'
|
||||
}
|
||||
|
||||
@@ -21,10 +21,6 @@ export const addNewCommentRangeEffect = StateEffect.define<Range<Decoration>>()
|
||||
|
||||
export const removeNewCommentRangeEffect = StateEffect.define<string>()
|
||||
|
||||
export const textSelectedEffect = StateEffect.define<null>()
|
||||
|
||||
export const removeReviewPanelTooltipEffect = StateEffect.define()
|
||||
|
||||
const mouseDownEffect = StateEffect.define()
|
||||
const mouseUpEffect = StateEffect.define()
|
||||
const mouseDownStateField = StateField.define<boolean>({
|
||||
|
||||
@@ -1,15 +1,8 @@
|
||||
import {
|
||||
Extension,
|
||||
Facet,
|
||||
StateEffect,
|
||||
StateField,
|
||||
TransactionSpec,
|
||||
} from '@codemirror/state'
|
||||
import { Extension, Facet, StateEffect, StateField } from '@codemirror/state'
|
||||
import {
|
||||
Decoration,
|
||||
EditorView,
|
||||
ViewPlugin,
|
||||
ViewUpdate,
|
||||
WidgetType,
|
||||
} from '@codemirror/view'
|
||||
|
||||
@@ -222,30 +215,3 @@ const topPaddingDecoration = EditorView.decorations.compute(
|
||||
])
|
||||
}
|
||||
)
|
||||
|
||||
export function setVerticalOverflow(padding: VerticalPadding): TransactionSpec {
|
||||
return {
|
||||
effects: [setOverflowPaddingEffect.of(padding)],
|
||||
}
|
||||
}
|
||||
|
||||
export function updateSetsVerticalOverflow(update: ViewUpdate): boolean {
|
||||
return update.transactions.some(tr => {
|
||||
return tr.effects.some(effect => effect.is(setOverflowPaddingEffect))
|
||||
})
|
||||
}
|
||||
|
||||
export function updateChangesTopPadding(update: ViewUpdate): boolean {
|
||||
return (
|
||||
update.state.field(overflowPaddingState).top !==
|
||||
update.startState.field(overflowPaddingState).top
|
||||
)
|
||||
}
|
||||
|
||||
export function editorVerticalTopPadding(view: EditorView): number {
|
||||
return view.state.field(overflowPaddingState, false)?.top ?? 0
|
||||
}
|
||||
|
||||
export function editorOverflowPadding(view: EditorView) {
|
||||
return view.state.field(overflowPaddingState, false)
|
||||
}
|
||||
|
||||
@@ -88,11 +88,6 @@ const inlineElements = new Set([
|
||||
export const isInlineElement = (node: Node): node is HTMLElement =>
|
||||
inlineElements.has(node.nodeName)
|
||||
|
||||
const codeElements = new Set(['CODE', 'PRE'])
|
||||
|
||||
export const isCodeElement = (node: Node): node is HTMLElement =>
|
||||
codeElements.has(node.nodeName)
|
||||
|
||||
const keepEmptyBlockElements = new Set(['TD', 'TH', 'CANVAS', 'DT', 'DD', 'HR'])
|
||||
|
||||
export const shouldRemoveEmptyBlockElement = (
|
||||
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
import { EditorView } from '@codemirror/view'
|
||||
import useCodeMirrorMeasurement from './use-codemirror-measurement'
|
||||
|
||||
// view.contentHeight, which is measured for us by CodeMirror and is what the
|
||||
// gutters use, is sometimes a pixel or so short of the full height of the
|
||||
// editor content, which leaves a small gap at the bottom, so use the DOM
|
||||
// scrollHeight property instead.
|
||||
const measureContentHeight = (view: EditorView) => view.contentDOM.scrollHeight
|
||||
|
||||
export default function useCodeMirrorContentHeight() {
|
||||
return useCodeMirrorMeasurement('content-height', measureContentHeight)
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
import { useCallback, useState } from 'react'
|
||||
import { useCodeMirrorViewContext } from '../components/codemirror-context'
|
||||
import { EditorView } from '@codemirror/view'
|
||||
import useEventListener from '../../../shared/hooks/use-event-listener'
|
||||
|
||||
export default function useCodeMirrorMeasurement(
|
||||
key: string,
|
||||
measure: (view: EditorView) => number
|
||||
) {
|
||||
const view = useCodeMirrorViewContext()
|
||||
const [measurement, setMeasurement] = useState(() => measure(view))
|
||||
|
||||
useEventListener(
|
||||
'editor:geometry-change',
|
||||
useCallback(() => {
|
||||
view.requestMeasure({
|
||||
key,
|
||||
read: () => measure(view),
|
||||
write(value) {
|
||||
// wrap the React state setter in a timeout so it doesn't run inside the CodeMirror update cycle
|
||||
window.setTimeout(() => {
|
||||
setMeasurement(value)
|
||||
})
|
||||
},
|
||||
})
|
||||
}, [view, measure, key])
|
||||
)
|
||||
|
||||
return measurement
|
||||
}
|
||||
@@ -44,7 +44,6 @@ function filterByType(type: 'file' | 'doc' | 'folder') {
|
||||
}
|
||||
|
||||
export const filterFiles = filterByType('file')
|
||||
export const filterDocs = filterByType('doc')
|
||||
export const filterFolders = filterByType('folder')
|
||||
|
||||
const IMAGE_FILE_EXTENSIONS = ['png', 'jpg', 'jpeg', 'pdf']
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
import { SubView } from '../../../../../types/review-panel/review-panel'
|
||||
|
||||
export const isCurrentFileView = (view: SubView) => view === 'cur_file'
|
||||
export const isOverviewView = (view: SubView) => view === 'overview'
|
||||
@@ -1,68 +1,8 @@
|
||||
import { ensureSyntaxTree, syntaxTree } from '@codemirror/language'
|
||||
import { syntaxTree } from '@codemirror/language'
|
||||
import { EditorSelection, EditorState, SelectionRange } from '@codemirror/state'
|
||||
import { SyntaxNode, Tree } from '@lezer/common'
|
||||
import { ListEnvironment } from '../../lezer-latex/latex.terms.mjs'
|
||||
|
||||
const HUNDRED_MS = 100
|
||||
|
||||
export type AncestorItem = {
|
||||
node: SyntaxNode
|
||||
label: string
|
||||
type?: string
|
||||
from: number
|
||||
to: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the stack of 'ancestor' nodes at the given position.
|
||||
* The first element is the most distant ancestor, while the last element
|
||||
* is the node at the position.
|
||||
*/
|
||||
export function getAncestorStack(
|
||||
state: EditorState,
|
||||
pos: number
|
||||
): AncestorItem[] | null {
|
||||
const tree = ensureSyntaxTree(state, pos, HUNDRED_MS)
|
||||
|
||||
if (!tree) {
|
||||
return null
|
||||
}
|
||||
|
||||
const stack: AncestorItem[] = []
|
||||
const selectedNode = tree.resolve(pos, 0)
|
||||
|
||||
let node: SyntaxNode | null = selectedNode
|
||||
while (node) {
|
||||
const name = node.type.name
|
||||
switch (name) {
|
||||
case 'Environment':
|
||||
{
|
||||
const data: AncestorItem = {
|
||||
node,
|
||||
label: name,
|
||||
from: node.from,
|
||||
to: node.to,
|
||||
}
|
||||
|
||||
const child = node.getChild('EnvNameGroup')
|
||||
if (child) {
|
||||
data.type = state.doc.sliceString(child.from + 1, child.to - 1)
|
||||
}
|
||||
stack.push(data)
|
||||
}
|
||||
break
|
||||
|
||||
default:
|
||||
stack.push({ node, label: name, from: node.from, to: node.to })
|
||||
break
|
||||
}
|
||||
|
||||
node = node.parent
|
||||
}
|
||||
|
||||
return stack.reverse()
|
||||
}
|
||||
|
||||
export const wrappedNodeOfType = (
|
||||
state: EditorState,
|
||||
range: SelectionRange,
|
||||
|
||||
@@ -1,29 +1,9 @@
|
||||
import { ensureSyntaxTree } from '@codemirror/language'
|
||||
import { EditorState } from '@codemirror/state'
|
||||
import { IterMode, SyntaxNode, SyntaxNodeRef, Tree } from '@lezer/common'
|
||||
import { SyntaxNode } from '@lezer/common'
|
||||
|
||||
const HUNDRED_MS = 100
|
||||
|
||||
export function iterateDescendantsOf(
|
||||
tree: Tree,
|
||||
ancestors: (string | number)[],
|
||||
spec: {
|
||||
enter(node: SyntaxNodeRef): boolean | void
|
||||
leave?(node: SyntaxNodeRef): void
|
||||
from?: number | undefined
|
||||
to?: number | undefined
|
||||
mode?: IterMode | undefined
|
||||
}
|
||||
) {
|
||||
const filteredEnter = (node: SyntaxNodeRef): boolean | void => {
|
||||
if (!ancestors.some(x => node.type.is(x))) {
|
||||
return false
|
||||
}
|
||||
return spec.enter(node)
|
||||
}
|
||||
tree.iterate({ ...spec, enter: filteredEnter })
|
||||
}
|
||||
|
||||
export const previousSiblingIs = (
|
||||
state: EditorState,
|
||||
pos: number,
|
||||
@@ -38,20 +18,6 @@ export const previousSiblingIs = (
|
||||
return previousNode?.type.name === expectedName
|
||||
}
|
||||
|
||||
export const nextSiblingIs = (
|
||||
state: EditorState,
|
||||
pos: number,
|
||||
expectedName: string
|
||||
): boolean | null => {
|
||||
const tree = ensureSyntaxTree(state, pos, HUNDRED_MS)
|
||||
if (!tree) {
|
||||
return null
|
||||
}
|
||||
const thisNode = tree.resolve(pos)
|
||||
const previousNode = thisNode?.nextSibling
|
||||
return previousNode?.type.name === expectedName
|
||||
}
|
||||
|
||||
export const getOptionalArgumentText = (
|
||||
state: EditorState,
|
||||
optionalArgumentNode: SyntaxNode
|
||||
|
||||
@@ -297,11 +297,3 @@ export function parseFigureData(
|
||||
graphicsCommandArguments,
|
||||
})
|
||||
}
|
||||
|
||||
export const getBeginEnvSuffix = (state: EditorState, node: SyntaxNode) => {
|
||||
const argumentNode = node
|
||||
.getChild('OptionalArgument')
|
||||
?.getChild('ShortOptionalArg')
|
||||
|
||||
return argumentNode && state.sliceDoc(argumentNode.from, argumentNode.to)
|
||||
}
|
||||
|
||||
@@ -6,11 +6,7 @@ export type {
|
||||
Outline,
|
||||
} from './tree-operations/outline'
|
||||
|
||||
export {
|
||||
iterateDescendantsOf,
|
||||
previousSiblingIs,
|
||||
nextSiblingIs,
|
||||
} from './tree-operations/common'
|
||||
export { previousSiblingIs } from './tree-operations/common'
|
||||
|
||||
export {
|
||||
cursorIsAtBeginEnvironment,
|
||||
@@ -19,7 +15,6 @@ export {
|
||||
} from './tree-operations/environments'
|
||||
|
||||
export {
|
||||
getAncestorStack,
|
||||
ancestorNodeOfType,
|
||||
ancestorOfNodeWithType,
|
||||
getBibkeyArgumentNode,
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import OLNotification from '@/shared/components/ol/ol-notification'
|
||||
|
||||
export function RedirectAlerts() {
|
||||
function RedirectAlerts() {
|
||||
const queryParams = new URLSearchParams(window.location.search)
|
||||
const redirectReason = queryParams.get('redirect-reason')
|
||||
const { t } = useTranslation()
|
||||
|
||||
+1
-1
@@ -112,7 +112,7 @@ function WritefullGrantedAddOn({
|
||||
)
|
||||
}
|
||||
|
||||
export function WritefullManagedBundleAddOn() {
|
||||
function WritefullManagedBundleAddOn() {
|
||||
const { setModalIdShown } = useSubscriptionDashboardContext()
|
||||
const handleManageOnWritefull = () => setModalIdShown('manage-on-writefull')
|
||||
return (
|
||||
|
||||
-15
@@ -1,15 +0,0 @@
|
||||
import { Trans } from 'react-i18next'
|
||||
|
||||
export function ContactSupportToChangeGroupPlan() {
|
||||
return (
|
||||
<p>
|
||||
<Trans
|
||||
i18nKey="contact_support_to_change_group_subscription"
|
||||
components={[
|
||||
// eslint-disable-next-line react/jsx-key, jsx-a11y/anchor-has-content
|
||||
<a href="/contact" />,
|
||||
]}
|
||||
/>
|
||||
</p>
|
||||
)
|
||||
}
|
||||
-27
@@ -1,27 +0,0 @@
|
||||
import { Trans } from 'react-i18next'
|
||||
|
||||
export function PendingAdditionalLicenses({
|
||||
additionalLicenses,
|
||||
totalLicenses,
|
||||
}: {
|
||||
additionalLicenses: number
|
||||
totalLicenses: number
|
||||
}) {
|
||||
return (
|
||||
<Trans
|
||||
i18nKey="additional_licenses"
|
||||
values={{
|
||||
additionalLicenses,
|
||||
totalLicenses,
|
||||
}}
|
||||
shouldUnescape
|
||||
tOptions={{ interpolation: { escapeValue: true } }}
|
||||
components={[
|
||||
// eslint-disable-next-line react/jsx-key
|
||||
<strong />,
|
||||
// eslint-disable-next-line react/jsx-key
|
||||
<strong />,
|
||||
]}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
export function swapModal(selectorBefore, selectorAfter) {
|
||||
const modalBefore = $(selectorBefore)
|
||||
const modalAfter = $(selectorAfter)
|
||||
|
||||
// Disable the fade-out + fade-in animation when swapping the forms.
|
||||
modalBefore.removeClass('fade')
|
||||
modalAfter.removeClass('fade')
|
||||
modalAfter.modal()
|
||||
modalBefore.modal('hide')
|
||||
modalBefore.addClass('fade')
|
||||
modalAfter.addClass('fade')
|
||||
}
|
||||
@@ -19,6 +19,7 @@ type IntegrationLinkingWidgetProps = {
|
||||
setOptedIn: (optedIn: boolean) => void
|
||||
}
|
||||
|
||||
/** @knipignore */
|
||||
export function LabsExperimentWidget({
|
||||
logo,
|
||||
title,
|
||||
|
||||
@@ -4,7 +4,7 @@ import { postJSON } from '@/infrastructure/fetch-json'
|
||||
import { debugConsole } from '@/utils/debugging'
|
||||
import { useEditorContext } from '@/shared/context/editor-context'
|
||||
|
||||
export const useTutorial = (
|
||||
const useTutorial = (
|
||||
tutorialKey: string,
|
||||
eventData: Record<string, any> = {}
|
||||
) => {
|
||||
|
||||
@@ -121,5 +121,4 @@ function useAsync<T = any, E extends Error | FetchError = Error>(
|
||||
}
|
||||
|
||||
export default useAsync
|
||||
export type UseAsyncReturnType = ReturnType<typeof useAsync>
|
||||
export { useAsync, abortError }
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
import { useState, useCallback, useEffect } from 'react'
|
||||
import customLocalStorage from '@/infrastructure/local-storage'
|
||||
import usePersistedState from '@/shared/hooks/use-persisted-state'
|
||||
|
||||
/**
|
||||
* @typedef {Object} RemindMeLater
|
||||
* @property {boolean} stillDissmissed - whether the user has dismissed the notification, or if the notification is still withing the 1 day reminder period
|
||||
* @property {function} remindThemLater - saves that the user has dismissed the notification for 1 day in local storage
|
||||
* @property {function} saveDismissed - saves that the user has dismissed the notification in local storage
|
||||
*/
|
||||
|
||||
/**
|
||||
*
|
||||
* @param {string} key the unique key used to keep track of what popup is currently being shown (usually the component name)
|
||||
* @param {string} notificationLocation what page the notification originates from (eg, the editor page, project page, etc)
|
||||
* @returns {RemindMeLater} an object containing whether the notification is still dismissed, and functions to remind the user later or save that they have dismissed the notification
|
||||
*/
|
||||
export default function useRemindMeLater(
|
||||
key: string,
|
||||
notificationLocation: string = 'editor'
|
||||
) {
|
||||
const [dismissedUntil, setDismissedUntil] = usePersistedState<
|
||||
Date | undefined
|
||||
>(`${notificationLocation}.has_dismissed_${key}_until`)
|
||||
|
||||
const [stillDissmissed, setStillDismissed] = useState(true)
|
||||
|
||||
useEffect(() => {
|
||||
const alertDismissed = customLocalStorage.getItem(
|
||||
`${notificationLocation}.has_dismissed_${key}`
|
||||
)
|
||||
|
||||
const isStillDismissed = Boolean(
|
||||
dismissedUntil && new Date(dismissedUntil) > new Date()
|
||||
)
|
||||
|
||||
setStillDismissed(alertDismissed || isStillDismissed)
|
||||
}, [setStillDismissed, dismissedUntil, key, notificationLocation])
|
||||
|
||||
const remindThemLater = useCallback(() => {
|
||||
const until = new Date()
|
||||
until.setDate(until.getDate() + 1) // 1 day
|
||||
setDismissedUntil(until)
|
||||
}, [setDismissedUntil])
|
||||
|
||||
const saveDismissed = useCallback(() => {
|
||||
customLocalStorage.setItem(
|
||||
`${notificationLocation}.has_dismissed_${key}`,
|
||||
true
|
||||
)
|
||||
}, [key, notificationLocation])
|
||||
|
||||
return { stillDissmissed, remindThemLater, saveDismissed }
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -22,10 +22,6 @@ export const isInsertChange = (
|
||||
change: Change<EditOperation>
|
||||
): change is Change<InsertOperation> => isInsertOperation(change.op)
|
||||
|
||||
export const isCommentChange = (
|
||||
change: Change<CommentOperation>
|
||||
): change is Change<CommentOperation> => isCommentOperation(change.op)
|
||||
|
||||
export const isDeleteChange = (
|
||||
change: Change<EditOperation>
|
||||
): change is Change<DeleteOperation> => isDeleteOperation(change.op)
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
export function cleanupServiceWorker() {
|
||||
try {
|
||||
navigator.serviceWorker
|
||||
.getRegistrations()
|
||||
.catch(() => {
|
||||
// fail silently if permission not given (e.g. SecurityError)
|
||||
return []
|
||||
})
|
||||
.then(registrations => {
|
||||
registrations.forEach(worker => {
|
||||
worker.unregister()
|
||||
})
|
||||
})
|
||||
} catch (e) {
|
||||
// fail silently if service worker are not available (on the navigator)
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ export function getSplitTestVariant(name: string, fallback?: string) {
|
||||
return getMeta('ol-splitTestVariants')?.[name] || fallback
|
||||
}
|
||||
|
||||
/** @knipignore */
|
||||
export function parseIntFromSplitTest(name: string, defaultValue: number) {
|
||||
const v = getMeta('ol-splitTestVariants')?.[name]
|
||||
const n = parseInt(v, 10)
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Configuration for knip, a tool to find unused exports in a project.
|
||||
*
|
||||
* To run knip, use the command: bin/npm -w services/web run knip
|
||||
*
|
||||
* It currently only runs in the frontend web service, but could be
|
||||
* adapted to run in the backend as well.
|
||||
*
|
||||
* This is an initial version built in a hackathon. It is useful but not yet complete.
|
||||
* In future we should add it to our CI checks but we need to make it more robust first.
|
||||
*/
|
||||
|
||||
import type { KnipConfig } from 'knip'
|
||||
import Path from 'path'
|
||||
import settings from './config/settings.webpack'
|
||||
|
||||
const moduleEntryPoints = Object.values(settings.overleafModuleImports).flatMap(
|
||||
paths =>
|
||||
paths.map(path => {
|
||||
const cleanedPath = path.replace(Path.join(__dirname, '/'), '')
|
||||
if (cleanedPath.match(/\.(js|jsx|ts|tsx?)$/)) {
|
||||
return cleanedPath
|
||||
}
|
||||
|
||||
return `${cleanedPath}.{js,jsx,ts,tsx}`
|
||||
})
|
||||
)
|
||||
|
||||
const knipConfig: KnipConfig = {
|
||||
$schema: 'https://unpkg.com/knip@5/schema.json',
|
||||
entry: [
|
||||
'frontend/js/pages/**/*.{js,jsx,ts,tsx}',
|
||||
'frontend/stories/**/*.{js,jsx,ts,tsx}',
|
||||
'test/frontend/**/*.{js,jsx,ts,tsx}',
|
||||
'modules/**/test/**/*.{js,jsx,ts,tsx}',
|
||||
'modules/**/*.test.{js,jsx,ts,tsx}',
|
||||
'modules/**/stories/**/*.{js,jsx,ts,tsx}',
|
||||
// TODO: update this when I work out how writefull entry points work
|
||||
'modules/writefull/**/*.{js,jsx,ts,tsx}',
|
||||
'types/window.ts',
|
||||
// Workers are loaded dynamically so we explicitly include all workers
|
||||
// here until we work out a way to follow the dynamic imports
|
||||
'modules/**/*.worker*',
|
||||
'frontend/js/**/*.worker*',
|
||||
...moduleEntryPoints,
|
||||
],
|
||||
project: [
|
||||
'frontend/js/**/*.{js,jsx,ts,tsx}',
|
||||
'modules/**/frontend/**/*.{js,jsx,ts,tsx}',
|
||||
'!frontend/js/.storybook/**/*.{js,jsx,ts,tsx}',
|
||||
],
|
||||
ignore: [
|
||||
// TODO: Files that we should keep around even when unused.
|
||||
// I would like to do this in the file itself but I can't seem
|
||||
// to work out a way to do that (@knipignore only works for
|
||||
// individual exports rather than whole files)
|
||||
'frontend/js/shared/components/labs/labs-experiments-widget.tsx',
|
||||
'frontend/js/features/ide-redesign/components/tooltip-promo.tsx',
|
||||
],
|
||||
ignoreExportsUsedInFile: true,
|
||||
ignoreBinaries: ['.*'],
|
||||
ignoreDependencies: ['.*'],
|
||||
tags: ['-knipignore'],
|
||||
}
|
||||
|
||||
export default knipConfig
|
||||
@@ -117,7 +117,6 @@
|
||||
"added_by_on": "Added by __name__ on __date__",
|
||||
"adding": "Adding",
|
||||
"additional_certificate": "Additional certificate",
|
||||
"additional_licenses": "Your subscription includes <0>__additionalLicenses__</0> additional license(s) for a total of <1>__totalLicenses__</1> licenses.",
|
||||
"address": "Address",
|
||||
"address_line_1": "Address",
|
||||
"address_second_line_optional": "Address second line (optional)",
|
||||
@@ -432,7 +431,6 @@
|
||||
"contact_message_label": "Message",
|
||||
"contact_sales": "Contact sales",
|
||||
"contact_support": "Contact Support",
|
||||
"contact_support_to_change_group_subscription": "Please <0>contact Support</0> if you wish to change your group subscription.",
|
||||
"contact_us": "Contact us",
|
||||
"contact_us_lowercase": "Contact us",
|
||||
"contacting_the_sales_team": "Contacting the Sales team",
|
||||
|
||||
@@ -71,7 +71,8 @@
|
||||
"local:test:acceptance": "npm run local:test:acceptance:app && npm run local:test:acceptance:modules",
|
||||
"local:test:unit": "npm run test:unit:all",
|
||||
"local:test:frontend": "npm run test:frontend",
|
||||
"local:test": "npm run local:test:unit && npm run local:test:frontend && npm run local:test:acceptance"
|
||||
"local:test": "npm run local:test:unit && npm run local:test:frontend && npm run local:test:acceptance",
|
||||
"knip": "knip --config knip.ts"
|
||||
},
|
||||
"browserslist": [
|
||||
"defaults and supports woff2",
|
||||
@@ -255,6 +256,7 @@
|
||||
"@types/express": "^4.17.23",
|
||||
"@types/mocha": "^9.1.0",
|
||||
"@types/mocha-each": "^2.0.4",
|
||||
"@types/node": "^24.5.2",
|
||||
"@types/react": "^18.3.20",
|
||||
"@types/react-color": "^3.0.13",
|
||||
"@types/react-dom": "^18.3.6",
|
||||
@@ -338,6 +340,7 @@
|
||||
"jscodeshift": "^17.0.0",
|
||||
"jsdom": "^19.0.0",
|
||||
"jsdom-global": "^3.0.2",
|
||||
"knip": "^5.64.1",
|
||||
"match-sorter": "^6.2.0",
|
||||
"mathjax": "^3.2.2",
|
||||
"mediatr-ts": "^2.0.1",
|
||||
|
||||
@@ -94,9 +94,9 @@ export const trashedAndNotOwnedProject = {
|
||||
owner: users.picard,
|
||||
} as Project
|
||||
|
||||
export const sharedProject = archiveableProject
|
||||
export const sharedProject = { ...archiveableProject }
|
||||
|
||||
export const ownedProject = copyableProject
|
||||
export const ownedProject = { ...copyableProject }
|
||||
|
||||
export const projectsData: Array<Project> = [
|
||||
copyableProject,
|
||||
|
||||
Reference in New Issue
Block a user