diff --git a/package-lock.json b/package-lock.json index d723638e4..4dfca96fb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -67,7 +67,7 @@ "katex": "0.16.19", "knockout": "3.5.1", "mark.js": "8.11.1", - "marked": "15.0.4", + "marked": "15.0.5", "mermaid": "11.4.1", "mime-types": "2.1.35", "mind-elixir": "4.3.3", @@ -133,7 +133,7 @@ "@types/jsdom": "21.1.7", "@types/mime-types": "2.1.4", "@types/multer": "1.4.12", - "@types/node": "22.10.3", + "@types/node": "22.10.5", "@types/safe-compare": "1.1.2", "@types/sanitize-html": "2.13.0", "@types/sax": "1.2.7", @@ -4389,9 +4389,9 @@ } }, "node_modules/@types/node": { - "version": "22.10.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.3.tgz", - "integrity": "sha512-DifAyw4BkrufCILvD3ucnuN8eydUfc/C1GlyrnI+LK6543w5/L3VeVgf05o3B4fqSXP1dKYLOZsKfutpxPzZrw==", + "version": "22.10.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.5.tgz", + "integrity": "sha512-F8Q+SeGimwOo86fiovQh8qiXfFEh2/ocYv7tU5pJ3EXMSSxk1Joj5wefpFK2fHTf/N6HKGSxIDBT9f3gCxXPkQ==", "dev": true, "license": "MIT", "dependencies": { @@ -12909,9 +12909,9 @@ } }, "node_modules/marked": { - "version": "15.0.4", - "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.4.tgz", - "integrity": "sha512-TCHvDqmb3ZJ4PWG7VEGVgtefA5/euFmsIhxtD0XsBxI39gUSKL81mIRFdt0AiNQozUahd4ke98ZdirExd/vSEw==", + "version": "15.0.5", + "resolved": "https://registry.npmjs.org/marked/-/marked-15.0.5.tgz", + "integrity": "sha512-xN+kSuqHjxWg+Q47yhhZMUP+kO1qHobvXkkm6FX+7N6lDvanLDd8H7AQ0jWDDyq+fDt/cSrJaBGyWYHXy0KQWA==", "license": "MIT", "bin": { "marked": "bin/marked.js" diff --git a/package.json b/package.json index fae522558..e1ea6be0e 100644 --- a/package.json +++ b/package.json @@ -110,7 +110,7 @@ "katex": "0.16.19", "knockout": "3.5.1", "mark.js": "8.11.1", - "marked": "15.0.4", + "marked": "15.0.5", "mermaid": "11.4.1", "mime-types": "2.1.35", "mind-elixir": "4.3.3", @@ -173,7 +173,7 @@ "@types/jsdom": "21.1.7", "@types/mime-types": "2.1.4", "@types/multer": "1.4.12", - "@types/node": "22.10.3", + "@types/node": "22.10.5", "@types/safe-compare": "1.1.2", "@types/sanitize-html": "2.13.0", "@types/sax": "1.2.7", diff --git a/spec-es6/utils/formatDownloadTitle.spec.ts b/spec-es6/utils/formatDownloadTitle.spec.ts new file mode 100644 index 000000000..923786915 --- /dev/null +++ b/spec-es6/utils/formatDownloadTitle.spec.ts @@ -0,0 +1,129 @@ +import { formatDownloadTitle } from "../../src/services/utils.ts"; +import { describe, it, execute, expect } from "../mini_test.ts"; + +const testCases: [fnValue: Parameters, expectedValue: ReturnType][] = [ + // empty fileName tests + [ + ["", "text", ""], + "untitled.html" + ], + + [ + ["", "canvas", ""], + "untitled.json" + ], + + [ + ["", null, ""], + "untitled" + ], + + // json extension from type tests + [ + ["test_file", "canvas", ""], + "test_file.json" + ], + + [ + ["test_file", "relationMap", ""], + "test_file.json" + ], + + [ + ["test_file", "search", ""], + "test_file.json" + ], + + // extension based on mime type + [ + ["test_file", null, "text/csv"], + "test_file.csv" + ], + + [ + ["test_file_wo_ext", "image", "image/svg+xml"], + "test_file_wo_ext.svg" + ], + + [ + ["test_file_wo_ext", "file", "application/json"], + "test_file_wo_ext.json" + ], + + [ + ["test_file_w_fake_ext.ext", "image", "image/svg+xml"], + "test_file_w_fake_ext.ext.svg" + ], + + [ + ["test_file_w_correct_ext.svg", "image", "image/svg+xml"], + "test_file_w_correct_ext.svg" + ], + + [ + ["test_file_w_correct_ext.svgz", "image", "image/svg+xml"], + "test_file_w_correct_ext.svgz" + ], + + [ + ["test_file.zip", "file", "application/zip"], + "test_file.zip" + ], + + [ + ["test_file", "file", "application/zip"], + "test_file.zip" + ], + + // application/octet-stream tests + [ + ["test_file", "file", "application/octet-stream"], + "test_file" + ], + + [ + ["test_file.zip", "file", "application/octet-stream"], + "test_file.zip" + ], + + [ + ["test_file.unknown", null, "application/octet-stream"], + "test_file.unknown" + ], + + // sanitized filename tests + [ + ["test/file", null, "application/octet-stream"], + "testfile" + ], + + [ + ["test:file.zip", "file", "application/zip"], + "testfile.zip" + ], + + [ + [":::", "file", "application/zip"], + ".zip" + ], + + [ + [":::a", "file", "application/zip"], + "a.zip" + ], +] + + +describe("utils/formatDownloadTitle unit tests", () => { + + testCases.forEach(testCase => { + return it(`With args '${JSON.stringify(testCase[0])}' it should return '${testCase[1]}'`, () => { + const [value, expected] = testCase; + const actual = formatDownloadTitle(...value); + expect(actual).toEqual(expected); + }) + }) + +}) + +execute() \ No newline at end of file diff --git a/src/services/utils.ts b/src/services/utils.ts index ddb51609d..854953f75 100644 --- a/src/services/utils.ts +++ b/src/services/utils.ts @@ -178,45 +178,29 @@ export function replaceAll(string: string, replaceWhat: string, replaceWith: str } export function formatDownloadTitle(fileName: string, type: string | null, mime: string) { - if (!fileName) { - fileName = "untitled"; - } + const fileNameBase = (!fileName) ? "untitled" : sanitize(fileName); - fileName = sanitize(fileName); + const getExtension = () => { + if (type === "text") return ".html"; + if (type === "relationMap" || type === "canvas" || type === "search") return ".json"; + if (!mime) return ""; - if (type === 'text') { - return `${fileName}.html`; - } else if (type && ['relationMap', 'canvas', 'search'].includes(type)) { - return `${fileName}.json`; - } else { - if (!mime) { - return fileName; - } + const mimeLc = mime.toLowerCase(); - mime = mime.toLowerCase(); - const filenameLc = fileName.toLowerCase(); - const extensions = mimeTypes.extensions[mime]; + // better to just return the current name without a fake extension + // it's possible that the title still preserves the correct extension anyways + if (mimeLc === 'application/octet-stream') return ""; - if (!extensions || extensions.length === 0) { - return fileName; - } + // if fileName has an extension matching the mime already - reuse it + const mimeTypeFromFileName = mimeTypes.lookup(fileName); + if (mimeTypeFromFileName === mimeLc) return ""; - for (const ext of extensions) { - if (filenameLc.endsWith(`.${ext}`)) { - return fileName; - } - } + // as last resort try to get extension from mimeType + const extensions = mimeTypes.extension(mime); + return extensions ? `.${extensions}` : ""; + }; - if (mime === 'application/octet-stream') { - // we didn't find any good guess for this one, it will be better to just return - // the current name without a fake extension. It's possible that the title still preserves the correct - // extension too - - return fileName; - } - - return `${fileName}.${extensions[0]}`; - } + return `${fileNameBase}${getExtension()}`; } export function removeTextFileExtension(filePath: string) {