diff options
-rw-r--r-- | package-lock.json | 131 | ||||
-rw-r--r-- | package.json | 13 | ||||
-rw-r--r-- | src/ClientUtils.ts | 2 | ||||
-rw-r--r-- | src/client/documents/Documents.ts | 2 | ||||
-rw-r--r-- | src/client/util/CurrentUserUtils.ts | 2 | ||||
-rw-r--r-- | src/client/views/MarqueeAnnotator.tsx | 1 | ||||
-rw-r--r-- | src/client/views/collections/CollectionSubView.tsx | 18 | ||||
-rw-r--r-- | src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx | 7 | ||||
-rw-r--r-- | src/client/views/nodes/WebBox.tsx | 22 | ||||
-rw-r--r-- | src/client/views/nodes/WebBoxRenderer.js | 2 | ||||
-rw-r--r-- | src/server/server_Initialization.ts | 220 |
11 files changed, 135 insertions, 285 deletions
diff --git a/package-lock.json b/package-lock.json index b4c008ffd..28b81dbc7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -68,7 +68,7 @@ "any-base": "^1.1.0", "archiver": "^7.0.1", "async": "^3.2.5", - "axios": "^1.7.3", + "axios": "^1.9.0", "babel-loader": "^10.0.0", "bcrypt-nodejs": "0.0.3", "better-react-mathjax": "^2.0.4-beta1", @@ -83,7 +83,6 @@ "bson": "^6.2.0", "canvas": "^3.1.0", "chart.js": "^4.4.0", - "cheerio": "^1.0.0", "child_process": "^1.0.2", "class-transformer": "^0.5.1", "cohere-ai": "^7.10.6", @@ -95,6 +94,7 @@ "cookie-parser": "^1.4.6", "cookie-session": "^2.0.0", "copy-webpack-plugin": "^13.0.0", + "copyfiles": "^2.4.1", "core-js": "^3.33.3", "cors": "^2.8.5", "css-loader": "^7.1.2", @@ -111,7 +111,7 @@ "eslint-webpack-plugin": "^5.0.0", "exif": "^0.6.0", "exifr": "^7.1.3", - "express": "^4.18.2", + "express": "^4.21.2", "express-flash": "0.0.2", "express-session": "^1.17.3", "express-validator": "^7.0.1", @@ -158,7 +158,7 @@ "jpeg-autorotate": "^9.0.0", "jquery": "^3.7.1", "js-datepicker": "^5.18.2", - "jsdom": "^26.0.0", + "jsdom": "^26.1.0", "jsonschema": "^1.4.1", "jszip": "^3.10.1", "ldrs": "^1.0.2", @@ -259,7 +259,6 @@ "tough-cookie": "^5.0.0", "tslint": "^5.20.1", "tslint-loader": "^3.5.4", - "typescript": "^5.3.3", "typescript-collections": "^1.3.3", "typescript-language-server": "^4.1.3", "uninstall": "^0.0.0", @@ -297,7 +296,7 @@ "@types/dom-mediacapture-record": "^1.0.19", "@types/dompurify": "^3.0.5", "@types/exif": "^0.6.5", - "@types/express": "^5.0.0", + "@types/express": "^5.0.1", "@types/express-session": "^1.17.10", "@types/file-saver": "^2.0.7", "@types/fuzzy-search": "^2.1.5", @@ -308,7 +307,7 @@ "@types/libxmljs": "^0.18.12", "@types/lodash": "^4.14.202", "@types/mocha": "^10.0.6", - "@types/node": "^22.4.2", + "@types/node": "^22.15.17", "@types/nodemailer": "^6.4.14", "@types/passport": "^1.0.16", "@types/passport-google-oauth20": "^2.0.14", @@ -332,7 +331,6 @@ "@types/webscopeio__react-textarea-autocomplete": "^4.7.5", "@types/youtube": "^0.1.0", "chai": "^5.0.0", - "copyfiles": "^2.4.1", "cross-env": "^7.0.3", "eslint": "^9.12.0", "eslint-plugin-react": "^7.37.1", @@ -343,6 +341,7 @@ "ts-loader": "^9.5.1", "ts-node": "^10.9.1", "ts-node-dev": "^2.0.0", + "typescript": "^5.8.3", "typescript-eslint": "^8.8.0", "webpack-dev-server": "^5.0.4" }, @@ -14952,9 +14951,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "22.14.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.14.1.tgz", - "integrity": "sha512-u0HuPQwe/dHrItgHHpmw3N2fYCR6x4ivMNbPHRkBVP4CvN+kiRrKHWk3i8tXiO/joPwXLMYvF9TTF0eqgHIuOw==", + "version": "22.15.17", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.17.tgz", + "integrity": "sha512-wIX2aSZL5FE+MR0JlvF87BNVrtFWf6AE6rxSE9X7OwnVvoyCQjpzSRJ+M87se/4QCkCiebQAqrJ0y6fwIyi7nw==", "license": "MIT", "dependencies": { "undici-types": "~6.21.0" @@ -16832,9 +16831,9 @@ "license": "MIT" }, "node_modules/axios": { - "version": "1.8.4", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz", - "integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz", + "integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", @@ -17899,31 +17898,6 @@ "node": ">= 16" } }, - "node_modules/cheerio": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0.tgz", - "integrity": "sha512-quS9HgjQpdaXOvsZz82Oz7uxtXiy6UIsIQcpBj7HRw2M63Skasm9qlDocAM7jNuaxdhpPU7c4kJN+gA5MCu4ww==", - "license": "MIT", - "dependencies": { - "cheerio-select": "^2.1.0", - "dom-serializer": "^2.0.0", - "domhandler": "^5.0.3", - "domutils": "^3.1.0", - "encoding-sniffer": "^0.2.0", - "htmlparser2": "^9.1.0", - "parse5": "^7.1.2", - "parse5-htmlparser2-tree-adapter": "^7.0.0", - "parse5-parser-stream": "^7.1.2", - "undici": "^6.19.5", - "whatwg-mimetype": "^4.0.0" - }, - "engines": { - "node": ">=18.17" - }, - "funding": { - "url": "https://github.com/cheeriojs/cheerio?sponsor=1" - } - }, "node_modules/cheerio-select": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", @@ -18747,7 +18721,6 @@ "version": "2.4.1", "resolved": "https://registry.npmjs.org/copyfiles/-/copyfiles-2.4.1.tgz", "integrity": "sha512-fereAvAvxDrQDOXybk3Qu3dPbOoKoysFMWtkY3mv5BsL8//OSZVL5DCLYqgRfY5cWirgRzlC+WSrxp6Bo3eNZg==", - "dev": true, "license": "MIT", "dependencies": { "glob": "^7.0.5", @@ -18768,7 +18741,6 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", @@ -20477,31 +20449,6 @@ "iconv-lite": "^0.6.2" } }, - "node_modules/encoding-sniffer": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.0.tgz", - "integrity": "sha512-ju7Wq1kg04I3HtiYIOrUrdfdDvkyO9s5XM8QAj/bN61Yo/Vb4vgJxy5vi4Yxk01gWHbrofpPtpxM8bKger9jhg==", - "license": "MIT", - "dependencies": { - "iconv-lite": "^0.6.3", - "whatwg-encoding": "^3.1.1" - }, - "funding": { - "url": "https://github.com/fb55/encoding-sniffer?sponsor=1" - } - }, - "node_modules/encoding-sniffer/node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/encoding/node_modules/iconv-lite": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", @@ -23863,25 +23810,6 @@ "node": ">=8.0.0" } }, - "node_modules/htmlparser2": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz", - "integrity": "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==", - "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "MIT", - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3", - "domutils": "^3.1.0", - "entities": "^4.5.0" - } - }, "node_modules/http-browserify": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/http-browserify/-/http-browserify-1.7.0.tgz", @@ -27707,7 +27635,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, "license": "MIT", "bin": { "mkdirp": "bin/cmd.js" @@ -28569,7 +28496,6 @@ "version": "0.0.0", "resolved": "https://registry.npmjs.org/noms/-/noms-0.0.0.tgz", "integrity": "sha512-lNDU9VJaOPxUmXcLb+HQFeUgQQPtMI24Gt6hgfuMHRJgMRHMF/qZ4HJD3GDru4sSw9IQl2jPjAYnQrdIeLbwow==", - "dev": true, "license": "ISC", "dependencies": { "inherits": "^2.0.1", @@ -28580,14 +28506,12 @@ "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", - "dev": true, "license": "MIT" }, "node_modules/noms/node_modules/readable-stream": { "version": "1.0.34", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", "integrity": "sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg==", - "dev": true, "license": "MIT", "dependencies": { "core-util-is": "~1.0.0", @@ -28600,7 +28524,6 @@ "version": "0.10.31", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==", - "dev": true, "license": "MIT" }, "node_modules/normalize-path": { @@ -31794,18 +31717,6 @@ "url": "https://github.com/inikulin/parse5?sponsor=1" } }, - "node_modules/parse5-parser-stream": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz", - "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==", - "license": "MIT", - "dependencies": { - "parse5": "^7.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, "node_modules/parseley": { "version": "0.12.1", "resolved": "https://registry.npmjs.org/parseley/-/parseley-0.12.1.tgz", @@ -37293,7 +37204,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, "license": "MIT", "dependencies": { "readable-stream": "~2.3.6", @@ -37304,14 +37214,12 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true, "license": "MIT" }, "node_modules/through2/node_modules/readable-stream": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "dev": true, "license": "MIT", "dependencies": { "core-util-is": "~1.0.0", @@ -37327,14 +37235,12 @@ "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true, "license": "MIT" }, "node_modules/through2/node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, "license": "MIT", "dependencies": { "safe-buffer": "~5.1.0" @@ -38496,15 +38402,6 @@ "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", "license": "MIT" }, - "node_modules/undici": { - "version": "6.21.2", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.2.tgz", - "integrity": "sha512-uROZWze0R0itiAKVPsYhFov9LxrPMHLMEQFszeI2gCN6bnIIZ8twzBCJcN2LJrBBLfrP0t1FW0g+JmKVl8Vk1g==", - "license": "MIT", - "engines": { - "node": ">=18.17" - } - }, "node_modules/undici-types": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", @@ -38781,7 +38678,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", - "dev": true, "license": "MIT", "engines": { "node": ">=8" @@ -40242,7 +40138,6 @@ "version": "4.0.2", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=0.4" diff --git a/package.json b/package.json index 07e72792c..6a579efd2 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "@types/dom-mediacapture-record": "^1.0.19", "@types/dompurify": "^3.0.5", "@types/exif": "^0.6.5", - "@types/express": "^5.0.0", + "@types/express": "^5.0.1", "@types/express-session": "^1.17.10", "@types/file-saver": "^2.0.7", "@types/fuzzy-search": "^2.1.5", @@ -57,7 +57,7 @@ "@types/libxmljs": "^0.18.12", "@types/lodash": "^4.14.202", "@types/mocha": "^10.0.6", - "@types/node": "^22.4.2", + "@types/node": "^22.15.17", "@types/nodemailer": "^6.4.14", "@types/passport": "^1.0.16", "@types/passport-google-oauth20": "^2.0.14", @@ -91,6 +91,7 @@ "ts-loader": "^9.5.1", "ts-node": "^10.9.1", "ts-node-dev": "^2.0.0", + "typescript": "^5.8.3", "typescript-eslint": "^8.8.0", "webpack-dev-server": "^5.0.4" }, @@ -151,7 +152,7 @@ "any-base": "^1.1.0", "archiver": "^7.0.1", "async": "^3.2.5", - "axios": "^1.7.3", + "axios": "^1.9.0", "babel-loader": "^10.0.0", "bcrypt-nodejs": "0.0.3", "better-react-mathjax": "^2.0.4-beta1", @@ -166,7 +167,6 @@ "bson": "^6.2.0", "canvas": "^3.1.0", "chart.js": "^4.4.0", - "cheerio": "^1.0.0", "child_process": "^1.0.2", "class-transformer": "^0.5.1", "cohere-ai": "^7.10.6", @@ -195,7 +195,7 @@ "eslint-webpack-plugin": "^5.0.0", "exif": "^0.6.0", "exifr": "^7.1.3", - "express": "^4.18.2", + "express": "^4.21.2", "express-flash": "0.0.2", "express-session": "^1.17.3", "express-validator": "^7.0.1", @@ -242,7 +242,7 @@ "jpeg-autorotate": "^9.0.0", "jquery": "^3.7.1", "js-datepicker": "^5.18.2", - "jsdom": "^26.0.0", + "jsdom": "^26.1.0", "jsonschema": "^1.4.1", "jszip": "^3.10.1", "ldrs": "^1.0.2", @@ -343,7 +343,6 @@ "tough-cookie": "^5.0.0", "tslint": "^5.20.1", "tslint-loader": "^3.5.4", - "typescript": "^5.3.3", "typescript-collections": "^1.3.3", "typescript-language-server": "^4.1.3", "uninstall": "^0.0.0", diff --git a/src/ClientUtils.ts b/src/ClientUtils.ts index 03ff13924..cc8b715b4 100644 --- a/src/ClientUtils.ts +++ b/src/ClientUtils.ts @@ -194,7 +194,7 @@ export namespace ClientUtils { } export function CorsProxy(url: string): string { - return prepend('/corsProxy/') + encodeURIComponent(url); + return prepend('/corsproxy/') + encodeURIComponent(url); } export function CopyText(text: string) { diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 9b6acef7b..2df6f3e23 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1029,7 +1029,7 @@ export namespace Docs { const nwid = options._nativeWidth || undefined; const nhght = options._nativeHeight || undefined; if (!nhght && width && height && nwid) options._nativeHeight = (Number(nwid) * Number(height)) / Number(width); - return InstanceFromProto(Prototypes.get(DocumentType.WEB), new WebField(url || 'https://www.wikipedia.org/'), options); + return InstanceFromProto(Prototypes.get(DocumentType.WEB), new WebField(url || 'https://wikipedia.org/'), options); } export function HtmlDocument(html: string, options: DocumentOptions = {}) { diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index ea3b8d146..28b19d55e 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -1073,7 +1073,7 @@ pie title Minerals in my tap water Doc.MyRecentlyClosed && Doc.AddDocToList(Doc.MyFilesystem, undefined, Doc.MyRecentlyClosed); } - DocCast(Doc.UserDoc().emptyWebpage) && (Doc.GetProto(DocCast(Doc.UserDoc().emptyWebpage)!).data = new WebField("https://www.wikipedia.org")); + DocCast(Doc.UserDoc().emptyWebpage) && (Doc.GetProto(DocCast(Doc.UserDoc().emptyWebpage)!).data = new WebField("https://wikipedia.org")); DocServer.CacheNeedsUpdate() && setTimeout(UPDATE_SERVER_CACHE, 2500); setInterval(UPDATE_SERVER_CACHE, 120000); diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx index e4811a902..b2e42652d 100644 --- a/src/client/views/MarqueeAnnotator.tsx +++ b/src/client/views/MarqueeAnnotator.tsx @@ -198,6 +198,7 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP const targetCreator = (annotationOn: Doc | undefined) => { const target = DocUtils.GetNewTextDoc('Note linked to ' + this.props.Document.title, 0, 0, 100, 100, annotationOn, 'yellow'); + target.layout_fitWidth = true; DocumentView.SetSelectOnLoad(target); return target; }; diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index cafe367b7..bdec694e8 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -356,12 +356,16 @@ export function CollectionSubView<X>() { return !!added; } if (de.complete.annoDragData) { - const dropCreator = de.complete.annoDragData.dropDocCreator; - de.complete.annoDragData.dropDocCreator = () => { - const dropped = dropCreator(this._props.isAnnotationOverlay ? this.Document : undefined); - this.addDocument(dropped); - return dropped; - }; + if (![de.complete.annoDragData.dragDocument.embedContainer, de.complete.annoDragData.dragDocument].includes(this.Document)) { + de.complete.annoDragData.dropDocCreator = () => this.getAnchor?.(true) || this.Document; + } else { + const dropCreator = de.complete.annoDragData.dropDocCreator; + de.complete.annoDragData.dropDocCreator = () => { + const dropped = dropCreator(this._props.isAnnotationOverlay ? this.Document : undefined); + this.addDocument(dropped); + return dropped; + }; + } return true; } return false; @@ -415,7 +419,7 @@ export function CollectionSubView<X>() { const tags = html.split('<'); if (tags[0] === '') tags.splice(0, 1); let img = tags[0].startsWith('img') ? tags[0] : tags.length > 1 && tags[1].startsWith('img') ? tags[1] : ''; - const cors = img.includes('corsProxy') ? img.match(/http.*corsProxy\//)![0] : ''; + const cors = img.includes('corsproxy') ? img.match(/http.*corsproxy\//)![0] : ''; img = cors ? img.replace(cors, '') : img; if (img) { const imgSrc = img.split('src="')[1].split('"')[0]; diff --git a/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx index 16d33eb93..134f2ed31 100644 --- a/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx +++ b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx @@ -115,12 +115,11 @@ export class SchemaColumnHeader extends ObservableReactComponent<SchemaColumnHea }; const readOnly = this.getFinfo(fieldKey)?.readOnly ?? false; const cursor = !readOnly ? 'text' : 'default'; - const pointerEvents: 'all' | 'none' = 'all'; - return { color, fieldProps, cursor, pointerEvents }; + return { color, fieldProps, cursor }; }; @computed get editableView() { - const { color, fieldProps, pointerEvents } = this.renderProps(this._props); + const { color, fieldProps } = this.renderProps(this._props); return ( <div @@ -132,7 +131,6 @@ export class SchemaColumnHeader extends ObservableReactComponent<SchemaColumnHea style={{ color, width: '100%', - pointerEvents, }}> <EditableView ref={r => { @@ -232,6 +230,7 @@ export class SchemaColumnHeader extends ObservableReactComponent<SchemaColumnHea className="schema-column-header" style={{ width: this._props.columnWidths[this._props.columnIndex], + pointerEvents: this.props.isContentActive() ? undefined : 'none', }} onPointerEnter={() => { this.handlePointerEnter(); diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 5603786f0..838dbea9d 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -454,7 +454,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { iframeDown = (e: PointerEvent) => { this._textAnnotationCreator = undefined; const sel = this._url ? this._iframe?.contentDocument?.getSelection() : window.document.getSelection(); - if (sel?.empty) + if (sel?.empty && !(e.target as any).textContent) sel.empty(); // Chrome else if (sel?.removeAllRanges) sel.removeAllRanges(); // Firefox @@ -509,10 +509,10 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { try { href = iframe?.contentWindow?.location.href; } catch { - runInAction(() => this._warning++); + // runInAction(() => this._warning++); href = undefined; } - let requrlraw = decodeURIComponent(href?.replace(ClientUtils.prepend('') + '/corsProxy/', '') ?? this._url.toString()); + let requrlraw = decodeURIComponent(href?.replace(ClientUtils.prepend('') + '/corsproxy/', '') ?? this._url.toString()); if (requrlraw !== this._url.toString()) { if (requrlraw.match(/q=.*&/)?.length && this._url.toString().match(/q=.*&/)?.length) { const matches = requrlraw.match(/[^a-zA-z]q=[^&]*/g); @@ -565,9 +565,9 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { 'click', undoable( action((e: MouseEvent) => { - let eleHref = ''; + let eleHref = (e.target as any)?.outerHTML?.split('"="')[1]?.split('"')[0]; for (let ele = e.target as HTMLElement | Element | null; ele; ele = ele.parentElement) { - if (ele instanceof HTMLAnchorElement) { + if ('href' in ele) { eleHref = (typeof ele.href === 'string' ? ele.href : eleHref) || (ele.parentElement && 'href' in ele.parentElement ? (ele.parentElement.href as string) : eleHref); } } @@ -576,7 +576,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { const batch = UndoManager.StartBatch('webclick'); e.stopPropagation(); setTimeout(() => { - this.setData(eleHref.replace(ClientUtils.prepend(''), origin)); + const url = eleHref.replace(ClientUtils.prepend(''), origin); + this.setData(url); batch.end(); }); if (this._outerRef.current) { @@ -858,7 +859,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { ); } if (field instanceof WebField) { - const url = this.layoutDoc[this.fieldKey + '_useCors'] ? ClientUtils.CorsProxy(this._webUrl) : this._webUrl; + const url = this.layoutDoc[this.fieldKey + '_useCors'] ? '/corsproxy/' + this._webUrl : this._webUrl; const scripts = this.dataDoc[this.fieldKey + '_allowScripts'] || this._webUrl.includes('wikipedia.org') || this._webUrl.includes('google.com') || this._webUrl.startsWith('https://bing'); // if (!scripts) console.log('No scripts for: ' + url); return ( @@ -1074,15 +1075,15 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { childPointerEvents = () => (this._props.isContentActive() ? 'all' : undefined); @computed get webpage() { TraceMobx(); - const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1; + // const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1; const pointerEvents = this.layoutDoc._lockedPosition ? 'none' : (this._props.pointerEvents?.() as Property.PointerEvents | undefined); - const scale = previewScale * (this._props.NativeDimScaling?.() || 1); + // const scale = previewScale * (this._props.NativeDimScaling?.() || 1); return ( <div className="webBox-outerContent" ref={this._outerRef} style={{ - height: `${100 / scale}%`, + height: '100%', //`${100 / scale}%`, pointerEvents, }} // when active, block wheel events from propagating since they're handled by the iframe @@ -1175,6 +1176,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { className="webBox-container" style={{ width: `calc(${100 / scale}% - ${!this.SidebarShown ? 0 : ((this.sidebarWidth() - WebBox.sidebarResizerWidth) / scale) * (this._previewWidth ? scale : 1)}px)`, + height: `${100 / scale}%`, transform: `scale(${scale})`, pointerEvents, }} diff --git a/src/client/views/nodes/WebBoxRenderer.js b/src/client/views/nodes/WebBoxRenderer.js index b727107a9..ef465c453 100644 --- a/src/client/views/nodes/WebBoxRenderer.js +++ b/src/client/views/nodes/WebBoxRenderer.js @@ -21,7 +21,7 @@ const ForeignHtmlRenderer = function (styleSheets) { return window.location.origin + extension; } function CorsProxy(url) { - return prepend('/corsProxy/') + encodeURIComponent(url); + return prepend('/corsproxy/') + encodeURIComponent(url); } /** * diff --git a/src/server/server_Initialization.ts b/src/server/server_Initialization.ts index a56ab5d18..514e2ce1e 100644 --- a/src/server/server_Initialization.ts +++ b/src/server/server_Initialization.ts @@ -1,19 +1,15 @@ import * as bodyParser from 'body-parser'; -import * as brotli from 'brotli'; import { blue, yellow } from 'colors'; import * as flash from 'connect-flash'; import * as MongoStoreConnect from 'connect-mongo'; -import * as cors from 'cors'; import * as express from 'express'; import * as expressFlash from 'express-flash'; import * as session from 'express-session'; import { createServer } from 'https'; import * as passport from 'passport'; -import * as request from 'request'; import * as webpack from 'webpack'; import * as wdm from 'webpack-dev-middleware'; import * as whm from 'webpack-hot-middleware'; -import * as zlib from 'zlib'; import * as config from '../../webpack.config'; import { logPort } from './ActionUtilities'; import RouteManager from './RouteManager'; @@ -23,6 +19,8 @@ import { SSL } from './apis/google/CredentialsLoader'; import { getForgot, getLogin, getLogout, getReset, getSignup, postForgot, postLogin, postReset, postSignup } from './authentication/AuthenticationManager'; import { Database } from './database'; import { WebSocket } from './websocket'; +import axios from 'axios'; +import { JSDOM } from 'jsdom'; /* RouteSetter is a wrapper around the server that prevents the server from being exposed. */ @@ -84,142 +82,96 @@ function buildWithMiddleware(server: express.Express) { return server; } -function registerEmbeddedBrowseRelativePathHandler(server: express.Express) { - server.use('*', (req, res) => { - // res.setHeader('Access-Control-Allow-Origin', '*'); - // res.header('Access-Control-Allow-Methods', 'GET, PUT, PATCH, POST, DELETE'); - // res.header('Access-Control-Allow-Headers', req.header('access-control-request-headers')); - const relativeUrl = req.originalUrl; - if (!res.headersSent && req.headers.referer?.includes('corsProxy')) { - if (!req.user) res.redirect('/home'); // When no user is logged in, we interpret a relative URL as being a reference to something they don't have access to and redirect to /home - // a request for something by a proxied referrer means it must be a relative reference. So construct a proxied absolute reference here. - try { - const proxiedRefererUrl = decodeURIComponent(req.headers.referer); // (e.g., http://localhost:<port>/corsProxy/https://en.wikipedia.org/wiki/Engelbart) - const dashServerUrl = proxiedRefererUrl.match(/.*corsProxy\//)![0]; // the dash server url (e.g.: http://localhost:<port>/corsProxy/ ) - const actualReferUrl = proxiedRefererUrl.replace(dashServerUrl, ''); // the url of the referer without the proxy (e.g., : https://en.wikipedia.org/wiki/Engelbart) - const absoluteTargetBaseUrl = actualReferUrl.match(/https?:\/\/[^/]*/)![0]; // the base of the original url (e.g., https://en.wikipedia.org) - const redirectedProxiedUrl = dashServerUrl + encodeURIComponent(absoluteTargetBaseUrl + relativeUrl); // the new proxied full url (e.g., http://localhost:<port>/corsProxy/https://en.wikipedia.org/<somethingelse>) - const redirectUrl = relativeUrl.startsWith('//') ? 'http:' + relativeUrl : redirectedProxiedUrl; - res.redirect(redirectUrl); - } catch (e) { - console.log('Error embed: ', e); +function registerCorsProxy(server: express.Express) { + // .replace('<head>', '<head> <style>[id ^= "google"] { display: none; } </style>') + server.use('/corsproxy', async (req, res) => { + try { + // Extract URL from either query param or path + let targetUrl: string; + + if (req.query.url) { + // Case 1: URL passed as query parameter (/corsproxy?url=...) + targetUrl = req.query.url as string; + } else { + // Case 2: URL passed as path (/corsproxy/http://example.com) + const path = req.originalUrl.replace(/^\/corsproxy\/?/, ''); + targetUrl = decodeURIComponent(path); + + // Add protocol if missing (assuming https as default) + if (!targetUrl.startsWith('http://') && !targetUrl.startsWith('https://')) { + targetUrl = `https://${targetUrl}`; + } + } + + if (!targetUrl) { + res.send(`<html><body bgcolor="red" link="006666" alink="8B4513" vlink="006666"> + <title>Error</title> + <div align="center"><h1>Failed to load: ${targetUrl} </h1></div> + <p>URL is required</p> + </body></html>`); + // res.status(400).json({ error: 'URL is required' }); + return; } - } else if (relativeUrl.startsWith('/search') && !req.headers.referer?.includes('corsProxy')) { - // detect search query and use default search engine - res.redirect(req.headers.referer + 'corsProxy/' + encodeURIComponent('http://www.google.com' + relativeUrl)); - } else { - res.status(404).json({ error: 'no such file or endpoint: try /home /logout /login' }); - } - }); -} -// eslint-disable-next-line @typescript-eslint/no-explicit-any -function proxyServe(req: any, requrl: string, response: any) { - // eslint-disable-next-line @typescript-eslint/no-require-imports - const htmlBodyMemoryStream = new (require('memorystream'))(); - let wasinBrFormat = false; - const sendModifiedBody = () => { - const header = response.headers['content-encoding']; - const refToCors = (match: string, tag: string, sym: string, href: string) => `${tag}=${sym + resolvedServerUrl}/corsProxy/${href + sym}`; - // const relpathToCors = (match: any, href: string, offset: any, string: any) => `="${resolvedServerUrl + '/corsProxy/' + decodeURIComponent(req.originalUrl.split('/corsProxy/')[1].match(/https?:\/\/[^\/]*/)?.[0] ?? '') + '/' + href}"`; - if (header) { + // Validate URL format try { - const bodyStream = htmlBodyMemoryStream.read(); - if (bodyStream) { - const htmlInputText = wasinBrFormat ? Buffer.from(brotli.decompress(bodyStream)) : header.includes('gzip') ? zlib.gunzipSync(bodyStream) : bodyStream; - const htmlText = htmlInputText - .toString('utf8') - .replace('<head>', '<head> <style>[id ^= "google"] { display: none; } </style>') - .replace(/(src|href)=(['"])(https?[^\n]*)\1/g, refToCors) // replace src or href='http(s)://...' or href="http(s)://.." - // .replace(/= *"\/([^"]*)"/g, relpathToCors) - .replace(/data-srcset="[^"]*"/g, '') - .replace(/srcset="[^"]*"/g, '') - .replace(/target="_blank"/g, ''); - response.send(header?.includes('gzip') ? zlib.gzipSync(htmlText) : htmlText); - } else { - req.pipe(request(requrl)) - // eslint-disable-next-line @typescript-eslint/no-explicit-any - .on('error', (e: any) => console.log('requrl ', e)) - .pipe(response) - // eslint-disable-next-line @typescript-eslint/no-explicit-any - .on('error', (e: any) => console.log('response pipe error', e)); - console.log('EMPTY body:' + req.url); - } + new URL(targetUrl); } catch (e) { - console.log('ERROR?: ', e); - } - } else { - req.pipe(htmlBodyMemoryStream) - // eslint-disable-next-line @typescript-eslint/no-explicit-any - .on('error', (e: any) => console.log('html body memorystream error', e)) - .pipe(response) - // eslint-disable-next-line @typescript-eslint/no-explicit-any - .on('error', (e: any) => console.log('html body memory stream response error', e)); - } - }; - const retrieveHTTPBody = () => { - // req.headers.cookie = ''; - req.pipe(request(requrl)) - // eslint-disable-next-line @typescript-eslint/no-explicit-any - .on('error', (e: any) => { - console.log(`CORS url error: ${requrl}`, e); - response.send(`<html><body bgcolor="red" link="006666" alink="8B4513" vlink="006666"> + res.send(`<html><body bgcolor="red" link="006666" alink="8B4513" vlink="006666"> <title>Error</title> - <div align="center"><h1>Failed to load: ${requrl} </h1></div> + <div align="center"><h1>Failed to load: ${targetUrl} </h1></div> <p>${e}</p> </body></html>`); - }) - // eslint-disable-next-line @typescript-eslint/no-explicit-any - .on('response', (res: any) => { - res.headers; - const headers = Object.keys(res.headers); - const headerCharRegex = /[^\t\x20-\x7e\x80-\xff]/; - headers.forEach(headerName => { - const header = res.headers[headerName]; - if (Array.isArray(header)) { - res.headers[headerName] = header.filter(h => !headerCharRegex.test(h)); - } else if (headerCharRegex.test(header || '')) { - delete res.headers[headerName]; - } else res.headers[headerName] = header; - if (headerName === 'content-encoding') { - wasinBrFormat = res.headers[headerName] === 'br'; - res.headers[headerName] = 'gzip'; - } + //res.status(400).json({ error: 'Invalid URL format' }); + return; + } + + const response = await axios.get(targetUrl as string, { + headers: { 'User-Agent': req.headers['user-agent'] || 'Mozilla/5.0' }, + responseType: 'text', + }); + + const baseUrl = new URL(targetUrl as string); + + if (response.headers['content-type']?.includes('text/html')) { + const dom = new JSDOM(response.data); + const document = dom.window.document; + + // Process all elements with href/src + const elements = document.querySelectorAll('[href],[src]'); + elements.forEach(elem => { + const attrs = []; + if (elem.hasAttribute('href')) attrs.push('href'); + if (elem.hasAttribute('src')) attrs.push('src'); + + attrs.forEach(attr => { + const originalUrl = elem.getAttribute(attr); + if (!originalUrl || originalUrl.startsWith('http://') || originalUrl.startsWith('https://') || originalUrl.startsWith('data:') || /^[a-z]+:/.test(originalUrl)) { + return; + } + + const resolvedUrl = new URL(originalUrl, baseUrl).toString(); + elem.setAttribute(attr, resolvedUrl); + }); }); - res.headers['x-permitted-cross-domain-policies'] = 'all'; - res.headers['x-frame-options'] = ''; - res.headers['content-security-policy'] = ''; - response.headers = response._headers = res.headers; - }) - .on('end', sendModifiedBody) - .pipe(htmlBodyMemoryStream) - // eslint-disable-next-line @typescript-eslint/no-explicit-any - .on('error', (e: any) => console.log('http body pipe error', e)); - }; - retrieveHTTPBody(); -} -function registerCorsProxy(server: express.Express) { - server.use('/corsProxy', async (req, res) => { - res.setHeader('Access-Control-Allow-Origin', '*'); - res.header('Access-Control-Allow-Methods', 'GET, PUT, PATCH, POST, DELETE'); - res.header('Access-Control-Allow-Headers', req.header('access-control-request-headers')); - const referer = req.headers.referer ? decodeURIComponent(req.headers.referer) : ''; - let requrlraw = decodeURIComponent(req.url.substring(1)); - const qsplit = requrlraw.split('?q='); - const newqsplit = requrlraw.split('&q='); - if (qsplit.length > 1 && newqsplit.length > 1) { - const lastq = newqsplit[newqsplit.length - 1]; - requrlraw = qsplit[0] + '?q=' + lastq.split('&')[0] + '&' + qsplit[1].split('&')[1]; - } - const requrl = requrlraw.startsWith('/') ? referer + requrlraw : requrlraw; - // cors weirdness here... - // if the referer is a cors page and the cors() route (I think) redirected to /corsProxy/<path> and the requested url path was relative, - // then we redirect again to the cors referer and just add the relative path. - if (!requrl.startsWith('http') && req.originalUrl.startsWith('/corsProxy') && referer?.includes('corsProxy')) { - res.redirect(referer + (referer.endsWith('/') ? '' : '/') + requrl); - } else { - proxyServe(req, requrl, res); + // Handle base tag + const baseTags = document.querySelectorAll('base'); + baseTags.forEach(tag => tag.remove()); + + const newBase = document.createElement('base'); + newBase.setAttribute('href', `${baseUrl}/`); + document.head.insertBefore(newBase, document.head.firstChild); + + response.data = dom.serialize(); + } + + res.set({ + 'Access-Control-Allow-Origin': '*', + 'Content-Type': response.headers['content-type'], + }).send(response.data); + } catch (error: unknown) { + res.status(500).json({ error: 'Proxy error', details: (error as { message: string }).message }); } }); } @@ -255,13 +207,11 @@ export default async function InitializeServer(routeSetter: RouteSetter) { app.use(whm(compiler)); app.get(/^\/+$/, (req, res) => res.redirect(req.user ? '/home' : '/login')); // target urls that consist of one or more '/'s with nothing in between app.use(express.static(publicDirectory, { setHeaders: res => res.setHeader('Access-Control-Allow-Origin', '*') })); // all urls that start with dash's public directory: /files/ (e.g., /files/images, /files/audio, etc) - // eslint-disable-next-line @typescript-eslint/no-explicit-any - app.use(cors({ origin: (_origin: any, callback: any) => callback(null, true) })); + // app.use(cors({ origin: (_origin: any, callback: any) => callback(null, true) })); registerAuthenticationRoutes(app); // this adds routes to authenticate a user (login, etc) - registerCorsProxy(app); // this adds a /corsProxy/ route to allow clients to get to urls that would otherwise be blocked by cors policies + registerCorsProxy(app); // this adds a /corsproxy/ route to allow clients to get to urls that would otherwise be blocked by cors policies isRelease && !SSL.Loaded && SSL.exit(); routeSetter(new RouteManager(app, isRelease)); // this sets up all the regular supervised routes (things like /home, download/upload api's, pdf, search, session, etc) - registerEmbeddedBrowseRelativePathHandler(app); // this allows renered web pages which internally have relative paths to find their content isRelease && process.env.serverPort && (resolvedPorts.server = Number(process.env.serverPort)); const server = isRelease ? createServer(SSL.Credentials, app) : app; await new Promise<void>(resolve => { |