diff --git a/bin/tpl/anonymize-database.sql b/bin/tpl/anonymize-database.sql index d72e0b29a..cf701e53f 100644 --- a/bin/tpl/anonymize-database.sql +++ b/bin/tpl/anonymize-database.sql @@ -4,6 +4,7 @@ UPDATE notes SET title = 'title' WHERE noteId != 'root' AND noteId NOT LIKE '\_% UPDATE note_contents SET content = 'text' WHERE content IS NOT NULL; UPDATE note_revisions SET title = 'title'; UPDATE note_revision_contents SET content = 'text' WHERE content IS NOT NULL; +UPDATE note_ancillary_contents SET content = 'text' WHERE content IS NOT NULL; UPDATE attributes SET name = 'name', value = 'value' WHERE type = 'label' diff --git a/db/migrations/0213__note_ancillaries.sql b/db/migrations/0213__note_ancillaries.sql new file mode 100644 index 000000000..03cadd8d8 --- /dev/null +++ b/db/migrations/0213__note_ancillaries.sql @@ -0,0 +1,20 @@ +CREATE TABLE IF NOT EXISTS "note_ancillaries" +( + noteAncillaryId TEXT not null primary key, + noteId TEXT not null, + name TEXT not null, + mime TEXT not null, + isProtected INT not null DEFAULT 0, + contentCheckSum TEXT not null, + utcDateModified TEXT not null, + isDeleted INT not null, + `deleteId` TEXT DEFAULT NULL); + +CREATE TABLE IF NOT EXISTS "note_ancillary_contents" (`noteAncillaryId` TEXT NOT NULL PRIMARY KEY, + `content` TEXT DEFAULT NULL, + `utcDateModified` TEXT NOT NULL); + +CREATE INDEX IDX_note_ancillaries_name + on note_ancillaries (name); +CREATE UNIQUE INDEX IDX_note_ancillaries_noteId_name + on note_ancillaries (noteId, name); diff --git a/db/schema.sql b/db/schema.sql index 01a40c554..6537ea720 100644 --- a/db/schema.sql +++ b/db/schema.sql @@ -112,3 +112,21 @@ CREATE TABLE IF NOT EXISTS "recent_notes" notePath TEXT not null, utcDateCreated TEXT not null ); +CREATE TABLE IF NOT EXISTS "note_ancillaries" +( + noteAncillaryId TEXT not null primary key, + noteId TEXT not null, + name TEXT not null, + mime TEXT not null, + isProtected INT not null DEFAULT 0, + contentCheckSum TEXT not null, + utcDateModified TEXT not null, + isDeleted INT not null, + `deleteId` TEXT DEFAULT NULL); +CREATE TABLE IF NOT EXISTS "note_ancillary_contents" (`noteAncillaryId` TEXT NOT NULL PRIMARY KEY, + `content` TEXT DEFAULT NULL, + `utcDateModified` TEXT NOT NULL); +CREATE INDEX IDX_note_ancillaries_name + on note_ancillaries (name); +CREATE UNIQUE INDEX IDX_note_ancillaries_noteId_name + on note_ancillaries (noteId, name); diff --git a/package-lock.json b/package-lock.json index dbd019d3b..87f8406c3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,7 @@ "@excalidraw/excalidraw": "0.14.2", "archiver": "5.3.1", "async-mutex": "0.4.0", - "axios": "1.3.3", + "axios": "1.3.4", "better-sqlite3": "7.4.5", "chokidar": "3.5.3", "cls-hooked": "4.2.2", @@ -45,7 +45,7 @@ "ini": "3.0.1", "is-animated": "2.0.2", "is-svg": "4.3.2", - "jimp": "0.22.4", + "jimp": "0.22.7", "joplin-turndown-plugin-gfm": "1.0.12", "jsdom": "21.1.0", "mime-types": "2.1.35", @@ -85,7 +85,7 @@ "electron-rebuild": "3.2.9", "esm": "3.2.25", "jasmine": "4.5.0", - "jsdoc": "4.0.1", + "jsdoc": "4.0.2", "lorem-ipsum": "2.0.8", "rcedit": "3.0.1", "webpack": "5.75.0", @@ -96,9 +96,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.20.13", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.13.tgz", - "integrity": "sha512-gFDLKMfpiXCsjt4za2JA9oTMn70CeseCehb11kRZgvd7+F67Hih3OHOK24cRrWECJ/ljfPGac6ygXAs/C8kIvw==", + "version": "7.21.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.2.tgz", + "integrity": "sha512-URpaIJQwEkEC2T9Kn+Ai6Xe/02iNaVCuT/PtoRz3GPVJVDpPd7mLo+VddTbhCRU9TXqW5mSrQfXZyi8kDKOVpQ==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -386,11 +386,11 @@ "dev": true }, "node_modules/@jimp/bmp": { - "version": "0.22.4", - "resolved": "https://registry.npmjs.org/@jimp/bmp/-/bmp-0.22.4.tgz", - "integrity": "sha512-ZDwQ/tLihpZuTCFGGa0zcyZIWHfhvHkrdbsoHUY0GG/JpH/y2xzlm2I48/TicCpoujN8oGKLHIJje0HmVX3xaA==", + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/bmp/-/bmp-0.22.7.tgz", + "integrity": "sha512-0cfBPvugURS7G+60vRBL+penDRst8x40alS5Rhn2nlGsgsBHljFDw7+H4o5r6gldw9nv9PR9JA90Wloy7KMZdQ==", "dependencies": { - "@jimp/utils": "^0.22.4", + "@jimp/utils": "^0.22.7", "bmp-js": "^0.1.0" }, "peerDependencies": { @@ -398,11 +398,11 @@ } }, "node_modules/@jimp/core": { - "version": "0.22.4", - "resolved": "https://registry.npmjs.org/@jimp/core/-/core-0.22.4.tgz", - "integrity": "sha512-K7guEYpXV44SCLR35QdPyKqF+mFZaEUAqiSL8qQ/F4N4Ws9JkPzFI/qYTjOkDoKxSWkXlKnlsk1sfMzy0yqA5g==", + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/core/-/core-0.22.7.tgz", + "integrity": "sha512-lg4z+pw23v2Gp9LWQur0NqYtnmoNWnyN/Or96elhJgeEJskrDGwROdajortHCCOI1xDnUZSirg8sFvStC8BIlg==", "dependencies": { - "@jimp/utils": "^0.22.4", + "@jimp/utils": "^0.22.7", "any-base": "^1.1.0", "buffer": "^5.2.0", "exif-parser": "^0.1.12", @@ -414,9 +414,9 @@ } }, "node_modules/@jimp/core/node_modules/mkdirp": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-2.1.3.tgz", - "integrity": "sha512-sjAkg21peAG9HS+Dkx7hlG9Ztx7HLeKnvB3NQRcu/mltCVmvkF0pisbiTSfDVYTT86XEfZrTUosLdZLStquZUw==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-2.1.5.tgz", + "integrity": "sha512-jbjfql+shJtAPrFoKxHOXip4xS+kul9W3OzfzzrqueWK2QMGon2bFH2opl6W9EagBThjEz+iysyi/swOoVfB/w==", "bin": { "mkdirp": "dist/cjs/src/bin.js" }, @@ -428,19 +428,19 @@ } }, "node_modules/@jimp/custom": { - "version": "0.22.4", - "resolved": "https://registry.npmjs.org/@jimp/custom/-/custom-0.22.4.tgz", - "integrity": "sha512-k9m/RfxjPjklUsgZ2nszlyNkodUG/4xlxlif70UELhxW8bdqZqqlQGzwA9p+PUiSnlSJYZjL6q+P8cd7yj1ggA==", + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/custom/-/custom-0.22.7.tgz", + "integrity": "sha512-n+1+ZVDNumB1E+sL7KdGKAJ6MbgniX1/v/xOEFEQ46WDZ4cRTqP4+tXjHTuHSlOXiANH+K9zD6qgzqmgO6mCVw==", "dependencies": { - "@jimp/core": "^0.22.4" + "@jimp/core": "^0.22.7" } }, "node_modules/@jimp/gif": { - "version": "0.22.4", - "resolved": "https://registry.npmjs.org/@jimp/gif/-/gif-0.22.4.tgz", - "integrity": "sha512-KmN7GoaQTzLAX4JXLBRkIiZAXthgQdKe+Y7BOw4n6CMe6LAS/XCQqrYCG3Av/GqIO7UAKems6D7kIGAUuhpNlQ==", + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/gif/-/gif-0.22.7.tgz", + "integrity": "sha512-PGZMS8sYFnDcqg+t8IT3RaSJLrqB+3GzhI0hU5D4mmSuJ5UO/6Bdgu8nrwh3uFPxw0ZH6h9ozYk88cz0pKEhLQ==", "dependencies": { - "@jimp/utils": "^0.22.4", + "@jimp/utils": "^0.22.7", "gifwrap": "^0.9.2", "omggif": "^1.0.9" }, @@ -449,11 +449,11 @@ } }, "node_modules/@jimp/jpeg": { - "version": "0.22.4", - "resolved": "https://registry.npmjs.org/@jimp/jpeg/-/jpeg-0.22.4.tgz", - "integrity": "sha512-mMJNhEtJpne65mxpIXEvT0VIzmsKiZWmaFT/c2eQ2tBLEtWAFpkvoP+F7jEaU+F3Ur4fXKFkJ/xOSXtRr/gGNw==", + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/jpeg/-/jpeg-0.22.7.tgz", + "integrity": "sha512-ptwWyX/7RPcREy8SpPN/8IlywbwyPXiuXmoHwM6m4iKcyaCmmnfCdZwLNXYliJzFAFLLOWDuOrwO3cZSkH6Czg==", "dependencies": { - "@jimp/utils": "^0.22.4", + "@jimp/utils": "^0.22.7", "jpeg-js": "^0.4.4" }, "peerDependencies": { @@ -461,44 +461,44 @@ } }, "node_modules/@jimp/plugin-blit": { - "version": "0.22.4", - "resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-0.22.4.tgz", - "integrity": "sha512-QQHe+rFarsxJQxWKlyHEMfLyXmUG9AiQky+8WfwjZVBYilIFyiBywOc3sThonOsru+7LOSUDmbN6mvbFk4R+gw==", + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-0.22.7.tgz", + "integrity": "sha512-8oXcBTSd/sBmTQATrCxQ1ZBER31Lge8vXzWqNCbC3b1ZvRggCcqnDzRRH1+JiI4i+jPRo3Fi6/sdvEUyQ5LY3g==", "dependencies": { - "@jimp/utils": "^0.22.4" + "@jimp/utils": "^0.22.7" }, "peerDependencies": { "@jimp/custom": ">=0.3.5" } }, "node_modules/@jimp/plugin-blur": { - "version": "0.22.4", - "resolved": "https://registry.npmjs.org/@jimp/plugin-blur/-/plugin-blur-0.22.4.tgz", - "integrity": "sha512-p57Ac5LEQckIogiwf7qyOojGvLOD08eMaQd5ylOhet/fbdwAzD/8flWFhSIKsdAVzvnfGcszuLtrsV07jDutTw==", + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/plugin-blur/-/plugin-blur-0.22.7.tgz", + "integrity": "sha512-M+0I5CKFIpnIQE27j8o8NECBsOFBd4z7C95ydy2UohYopugFq+hSVtMs1D4pQgb0RW1DJPiXD/4PHqb+lzV5mA==", "dependencies": { - "@jimp/utils": "^0.22.4" + "@jimp/utils": "^0.22.7" }, "peerDependencies": { "@jimp/custom": ">=0.3.5" } }, "node_modules/@jimp/plugin-circle": { - "version": "0.22.4", - "resolved": "https://registry.npmjs.org/@jimp/plugin-circle/-/plugin-circle-0.22.4.tgz", - "integrity": "sha512-T+TpG+s+wM9kKHlpIEfCAfOM+QrYVqcMoWjkULddc0KtaDEhqgGYFhN+/SlzJfDbZKw0xUgIuAw89sXuzMIUjw==", + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/plugin-circle/-/plugin-circle-0.22.7.tgz", + "integrity": "sha512-zfZKKpOhlyiDeFjGW5JB9K4h/kvbdaAJWUEwmKrvvGar67G3j8dKu46AX0MeWRNZ1yk/lfz+JIa7TzKfxEBf6w==", "dependencies": { - "@jimp/utils": "^0.22.4" + "@jimp/utils": "^0.22.7" }, "peerDependencies": { "@jimp/custom": ">=0.3.5" } }, "node_modules/@jimp/plugin-color": { - "version": "0.22.4", - "resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-0.22.4.tgz", - "integrity": "sha512-TZqcqepoCcIlF7VodPPfS3WET+LL5Y/XnXOBk4tWnG5i+lhNrs7/U0HOJY6Iw9o4g267DddnlfKWmunvzBcvOQ==", + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-0.22.7.tgz", + "integrity": "sha512-OXro9pdB0twQjV4LgW0bTEXaX1VgBsTBcFoDAs8q9mtQzD5p3UQmJ+ykCiQ5rTPxNN1Buc44tcCIfp8haB1ZVQ==", "dependencies": { - "@jimp/utils": "^0.22.4", + "@jimp/utils": "^0.22.7", "tinycolor2": "^1.6.0" }, "peerDependencies": { @@ -506,11 +506,11 @@ } }, "node_modules/@jimp/plugin-contain": { - "version": "0.22.4", - "resolved": "https://registry.npmjs.org/@jimp/plugin-contain/-/plugin-contain-0.22.4.tgz", - "integrity": "sha512-Hl+TO4v+EpRfEl3R6k/bEgOGOpm6JqNfEIyCFWLi6yqJQjMGzBQ0vt+VHe2u3WIFaFrDWsGxeuFZBDzgtjTwxw==", + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/plugin-contain/-/plugin-contain-0.22.7.tgz", + "integrity": "sha512-GwUxZp4jMA0O0qbknUPDONJAfHFaTRs8kK+jgRtUfgb1Xi96l5RN/PMMDv4owZCUiPVAON80X1BMj7nSQWNVUw==", "dependencies": { - "@jimp/utils": "^0.22.4" + "@jimp/utils": "^0.22.7" }, "peerDependencies": { "@jimp/custom": ">=0.3.5", @@ -520,11 +520,11 @@ } }, "node_modules/@jimp/plugin-cover": { - "version": "0.22.4", - "resolved": "https://registry.npmjs.org/@jimp/plugin-cover/-/plugin-cover-0.22.4.tgz", - "integrity": "sha512-KMTQjN/B7r/RNzoLFwnhqhLrgT0kMqTkBMEZQSopj5vPLPNjIX0ElEYC8AIVFKeZAV+1mYkyss+IDdxq4fyRng==", + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/plugin-cover/-/plugin-cover-0.22.7.tgz", + "integrity": "sha512-PVXeQyofGepMoJaQ5XapLwCcZfsOF1IoAotHosh8AOP8niCP/Erm8T6ZWf5tf0sMJiLHQMPUyns186H5isqEMQ==", "dependencies": { - "@jimp/utils": "^0.22.4" + "@jimp/utils": "^0.22.7" }, "peerDependencies": { "@jimp/custom": ">=0.3.5", @@ -534,55 +534,55 @@ } }, "node_modules/@jimp/plugin-crop": { - "version": "0.22.4", - "resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-0.22.4.tgz", - "integrity": "sha512-8krDt7xzBa1fbtlYCzEMZIgNjTkhgywho0FJpgIMkIUMjaZITS1Ea/Veb3UrWt8EsgQS6hxjGVE/Q1FvP5iPLA==", + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-0.22.7.tgz", + "integrity": "sha512-XXvUU+hPdodtTBSgyUJUnzh7JgKMVlS1GxjcQsjYU8iGr1dbpuazKMTQxc76ChVmy8ue4goi8bGstacWUHpl/Q==", "dependencies": { - "@jimp/utils": "^0.22.4" + "@jimp/utils": "^0.22.7" }, "peerDependencies": { "@jimp/custom": ">=0.3.5" } }, "node_modules/@jimp/plugin-displace": { - "version": "0.22.4", - "resolved": "https://registry.npmjs.org/@jimp/plugin-displace/-/plugin-displace-0.22.4.tgz", - "integrity": "sha512-3gBfwYVFrOjp8SUpb7H0UMgqvsG/sxY1PVBIfRW9MUCosiH1eE/Mo5cbxhQ6/w5f3sh23lBmG8W0WuSrnXLorg==", + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/plugin-displace/-/plugin-displace-0.22.7.tgz", + "integrity": "sha512-CCNAkmm2OS4QQtNRfQvXqoAMxNE0maSlVEV5DNdioHOUKycy02EJ5hNYR3l0FG+NraQHOuqv9XV37sGRl6QzMA==", "dependencies": { - "@jimp/utils": "^0.22.4" + "@jimp/utils": "^0.22.7" }, "peerDependencies": { "@jimp/custom": ">=0.3.5" } }, "node_modules/@jimp/plugin-dither": { - "version": "0.22.4", - "resolved": "https://registry.npmjs.org/@jimp/plugin-dither/-/plugin-dither-0.22.4.tgz", - "integrity": "sha512-oOhdZBDJpSGIoTUhPOIvLIVUwILRWgrWdA4Vbzcyz2RHvaPHS8gdBH0EdIPbJ5agNyFnY8sJWFM7YKx/rLNKsw==", + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/plugin-dither/-/plugin-dither-0.22.7.tgz", + "integrity": "sha512-ndCW5MIGMdh3aBvvgRCO7el9cIPG29kU7xQYlOs5+3JsDk3Vf7X30QGPjzxABOY95qLUNUjf5Qe/p/tqv/vbcw==", "dependencies": { - "@jimp/utils": "^0.22.4" + "@jimp/utils": "^0.22.7" }, "peerDependencies": { "@jimp/custom": ">=0.3.5" } }, "node_modules/@jimp/plugin-fisheye": { - "version": "0.22.4", - "resolved": "https://registry.npmjs.org/@jimp/plugin-fisheye/-/plugin-fisheye-0.22.4.tgz", - "integrity": "sha512-2myNZyDrwUOV8MKd4NeULnEOojYF7XRbnRHiUPsNptpmK6g/gI4xt+5k7BallAYZD8ZLfZVjstUogsObprHd/Q==", + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/plugin-fisheye/-/plugin-fisheye-0.22.7.tgz", + "integrity": "sha512-boI1QowhZRfb6OF+ZPWtiSJP1GATsTHjd5Oy/lJ+n0L4rp439ZOTB1Elzcgc44O2C1mgZDdybRPQQvYdPF8slA==", "dependencies": { - "@jimp/utils": "^0.22.4" + "@jimp/utils": "^0.22.7" }, "peerDependencies": { "@jimp/custom": ">=0.3.5" } }, "node_modules/@jimp/plugin-flip": { - "version": "0.22.4", - "resolved": "https://registry.npmjs.org/@jimp/plugin-flip/-/plugin-flip-0.22.4.tgz", - "integrity": "sha512-9FZ0k5N5leLDefeDjizXXTl17dzo23PYtCD/T4xeSVr96d1pQDwbeIk7pEhhHr1rl98tJe0U/OV2dFXFYauKPw==", + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/plugin-flip/-/plugin-flip-0.22.7.tgz", + "integrity": "sha512-/jkbgtvQPcKadAEV5ZXyoEpSdd7GEvGs/Ya/f48+LNszc+S24u4UXtuP3QPRJ5FHm0Re1t4uztM7xa6IPklAOA==", "dependencies": { - "@jimp/utils": "^0.22.4" + "@jimp/utils": "^0.22.7" }, "peerDependencies": { "@jimp/custom": ">=0.3.5", @@ -590,55 +590,55 @@ } }, "node_modules/@jimp/plugin-gaussian": { - "version": "0.22.4", - "resolved": "https://registry.npmjs.org/@jimp/plugin-gaussian/-/plugin-gaussian-0.22.4.tgz", - "integrity": "sha512-irOSwLdZ9kTq5Wd5dpkMgIMJVwemYcqgnzd04+P6TJGYmem2HR6JUCDpjbET3Fpbo/snFLm4mZ+2A+SmeCGjKA==", + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/plugin-gaussian/-/plugin-gaussian-0.22.7.tgz", + "integrity": "sha512-OB1sdnjzq2rfUHmx9Rvi3SJIDbQAgWFgYEw6KhN3TSVOdrJHvwrQkEnwR9PoUzQg992VIpGcVc9Y1s/SOU2oCA==", "dependencies": { - "@jimp/utils": "^0.22.4" + "@jimp/utils": "^0.22.7" }, "peerDependencies": { "@jimp/custom": ">=0.3.5" } }, "node_modules/@jimp/plugin-invert": { - "version": "0.22.4", - "resolved": "https://registry.npmjs.org/@jimp/plugin-invert/-/plugin-invert-0.22.4.tgz", - "integrity": "sha512-/WtZeLrF+H+mzbjqudeGvvSxudlHy1kyiP1gVWDxhYNQOuZJI57Vn20kSTYvHBNjvy31LV4/uestyX8j8tE2Qg==", + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/plugin-invert/-/plugin-invert-0.22.7.tgz", + "integrity": "sha512-dX/TqACJ/M5uXDIEJlVPPwietMD6EWUeA/CV4uvhLz9EMjTgHociJ3TWqGCY/70phhIBLbhLcHUVBL/q65ynfQ==", "dependencies": { - "@jimp/utils": "^0.22.4" + "@jimp/utils": "^0.22.7" }, "peerDependencies": { "@jimp/custom": ">=0.3.5" } }, "node_modules/@jimp/plugin-mask": { - "version": "0.22.4", - "resolved": "https://registry.npmjs.org/@jimp/plugin-mask/-/plugin-mask-0.22.4.tgz", - "integrity": "sha512-U0SrOwBNKkMYTNPTz5CXeJdZ4c5easFlq2B9Txy0kPsav2OraTv8cZjpMxrWUejo/AQGVUDbaGtXMm9pE13/6w==", + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/plugin-mask/-/plugin-mask-0.22.7.tgz", + "integrity": "sha512-rfKHKJLAtJG7qbB4zYAMcQ9ue3CIFRuAJ3xX0lzCxC0fGvCVuXlcxiAEauBxqaTWqiKMnahqpR3/Ah679K2FKQ==", "dependencies": { - "@jimp/utils": "^0.22.4" + "@jimp/utils": "^0.22.7" }, "peerDependencies": { "@jimp/custom": ">=0.3.5" } }, "node_modules/@jimp/plugin-normalize": { - "version": "0.22.4", - "resolved": "https://registry.npmjs.org/@jimp/plugin-normalize/-/plugin-normalize-0.22.4.tgz", - "integrity": "sha512-XJiPBJGCHWmIzUdmL4mWP1Ev5LMp77oMmPXdgQGDty1cxfyo3CbkTjZSsnwF/XLlrQ1yfLW+8JB+ihGKcVEOxA==", + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/plugin-normalize/-/plugin-normalize-0.22.7.tgz", + "integrity": "sha512-t8x2jjKDmvUAZB4Wbeagr4D0BvoVCIWquy94mpglvSZ8ujKLt0aQBl3CBEIbXFAoVqNif+G36NtxPHNsjxIXOg==", "dependencies": { - "@jimp/utils": "^0.22.4" + "@jimp/utils": "^0.22.7" }, "peerDependencies": { "@jimp/custom": ">=0.3.5" } }, "node_modules/@jimp/plugin-print": { - "version": "0.22.4", - "resolved": "https://registry.npmjs.org/@jimp/plugin-print/-/plugin-print-0.22.4.tgz", - "integrity": "sha512-mayiPhg6c7KYjvq3fYOW9ohhXD1eWdEiseV9dAWqTOEbDbohT8S6eTGhVIiVa2sVySLcpNEKZSk07c5EhJAMcw==", + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/plugin-print/-/plugin-print-0.22.7.tgz", + "integrity": "sha512-kx0+cPeinki1IFg9cJy7LC4uVuOEOa8TIrcERioB6PVgJ7EDzCAfatTKULZ+t4uSs2K/lQF97wPYlbiyxs/Hzg==", "dependencies": { - "@jimp/utils": "^0.22.4", + "@jimp/utils": "^0.22.7", "load-bmfont": "^1.4.1" }, "peerDependencies": { @@ -647,22 +647,22 @@ } }, "node_modules/@jimp/plugin-resize": { - "version": "0.22.4", - "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-0.22.4.tgz", - "integrity": "sha512-2wMdpPNGf6Zo2lfJg1QHHQ+ds5baQH75IcFpdjw717dcEISpn1jPG//iClXOGh16OJsRQlwHESaZTgEo/5Dw/g==", + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-0.22.7.tgz", + "integrity": "sha512-pg7i0JIYt7x7ag+CoD/yG70Xvwm1sKRfcFjQh954yestiin14uppPgXchAmTBmctecBjLNdsVlqSXbPvU4Jvxw==", "dependencies": { - "@jimp/utils": "^0.22.4" + "@jimp/utils": "^0.22.7" }, "peerDependencies": { "@jimp/custom": ">=0.3.5" } }, "node_modules/@jimp/plugin-rotate": { - "version": "0.22.4", - "resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-0.22.4.tgz", - "integrity": "sha512-g08LBsPENbeA6NVoeq0iuDgAL89+N+aZrvYVKYkiJZIM7vUvueJyAIq4+bjDl4r54OR8XBFX0GsrKsqrULh1eA==", + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-0.22.7.tgz", + "integrity": "sha512-Uh3Gb18IY8uXWk6E1bzMopum2GP+xwohbnMIDE0MSWmLaz7LXrfnvgXFba1uRGgn73CJz8UDS4fC1KIJMuxQZA==", "dependencies": { - "@jimp/utils": "^0.22.4" + "@jimp/utils": "^0.22.7" }, "peerDependencies": { "@jimp/custom": ">=0.3.5", @@ -672,11 +672,11 @@ } }, "node_modules/@jimp/plugin-scale": { - "version": "0.22.4", - "resolved": "https://registry.npmjs.org/@jimp/plugin-scale/-/plugin-scale-0.22.4.tgz", - "integrity": "sha512-cJiLQtTcNk6/+j05R23TFGXy+smDV0BdlmzJVDb+7Ye9qcmWpkdjVSioQQqZr0QScIYKhhRCY/lFTepBx67yzw==", + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/plugin-scale/-/plugin-scale-0.22.7.tgz", + "integrity": "sha512-3uHUrk5Rl6MCxuoJtHTSeJjSHIxHWqOOgmD2caKIvyxds0Zmofu/Fva+N4V/m80E4q4G2RXNsUplFpFGhUM7hw==", "dependencies": { - "@jimp/utils": "^0.22.4" + "@jimp/utils": "^0.22.7" }, "peerDependencies": { "@jimp/custom": ">=0.3.5", @@ -684,11 +684,11 @@ } }, "node_modules/@jimp/plugin-shadow": { - "version": "0.22.4", - "resolved": "https://registry.npmjs.org/@jimp/plugin-shadow/-/plugin-shadow-0.22.4.tgz", - "integrity": "sha512-a5hdpzGBzBo91DNiKaGvs8iJbs2bYQmDRm/BrCh4NET+h5l5AwXNu/Ak0bWRhN16YQ55XYNGHer2jOwBPrf2WQ==", + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/plugin-shadow/-/plugin-shadow-0.22.7.tgz", + "integrity": "sha512-NKEq5VR8U/d0OKf0hxFtrrbMCuNv7by31V+Kwgxb1oTP+j+zZEaww+m3YgEwIwRe7E8/yeDSHa5bJ+CmuyFZjw==", "dependencies": { - "@jimp/utils": "^0.22.4" + "@jimp/utils": "^0.22.7" }, "peerDependencies": { "@jimp/custom": ">=0.3.5", @@ -697,11 +697,11 @@ } }, "node_modules/@jimp/plugin-threshold": { - "version": "0.22.4", - "resolved": "https://registry.npmjs.org/@jimp/plugin-threshold/-/plugin-threshold-0.22.4.tgz", - "integrity": "sha512-jTT/+p2zb2NESzd7O0bVRowiQszoaHeBf2OgP7lFde10fHd+fn78m5brUmSmlGAdlMRwm8S8ZcxTj5ZjdQns5w==", + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/plugin-threshold/-/plugin-threshold-0.22.7.tgz", + "integrity": "sha512-BH4aLwfmnqjRVhdzMIqUns4ycZ6QoHHFR6Qz+X2iSpH5a33xFA4DRbd3Ehtrs4Gk7XiCjWkUyM6wjmH7l/1hNQ==", "dependencies": { - "@jimp/utils": "^0.22.4" + "@jimp/utils": "^0.22.7" }, "peerDependencies": { "@jimp/custom": ">=0.3.5", @@ -710,31 +710,31 @@ } }, "node_modules/@jimp/plugins": { - "version": "0.22.4", - "resolved": "https://registry.npmjs.org/@jimp/plugins/-/plugins-0.22.4.tgz", - "integrity": "sha512-yAxcA4UR3Bs7j73I7wt4ty52vm5MzPmr+8DYk8jrS/ng2Z2iuXzbcTe4mf9eEqXYVah3rTIggo4dPjW75DRZtA==", + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/plugins/-/plugins-0.22.7.tgz", + "integrity": "sha512-AJmzTG/sa+CDpvle/UE89hjHR85gnRGSwLuQqPbhlY6GFCmC3uqHRJz9O5I8A4zdi9+e8LsBphuTlKV7RbuXOw==", "dependencies": { - "@jimp/plugin-blit": "^0.22.4", - "@jimp/plugin-blur": "^0.22.4", - "@jimp/plugin-circle": "^0.22.4", - "@jimp/plugin-color": "^0.22.4", - "@jimp/plugin-contain": "^0.22.4", - "@jimp/plugin-cover": "^0.22.4", - "@jimp/plugin-crop": "^0.22.4", - "@jimp/plugin-displace": "^0.22.4", - "@jimp/plugin-dither": "^0.22.4", - "@jimp/plugin-fisheye": "^0.22.4", - "@jimp/plugin-flip": "^0.22.4", - "@jimp/plugin-gaussian": "^0.22.4", - "@jimp/plugin-invert": "^0.22.4", - "@jimp/plugin-mask": "^0.22.4", - "@jimp/plugin-normalize": "^0.22.4", - "@jimp/plugin-print": "^0.22.4", - "@jimp/plugin-resize": "^0.22.4", - "@jimp/plugin-rotate": "^0.22.4", - "@jimp/plugin-scale": "^0.22.4", - "@jimp/plugin-shadow": "^0.22.4", - "@jimp/plugin-threshold": "^0.22.4", + "@jimp/plugin-blit": "^0.22.7", + "@jimp/plugin-blur": "^0.22.7", + "@jimp/plugin-circle": "^0.22.7", + "@jimp/plugin-color": "^0.22.7", + "@jimp/plugin-contain": "^0.22.7", + "@jimp/plugin-cover": "^0.22.7", + "@jimp/plugin-crop": "^0.22.7", + "@jimp/plugin-displace": "^0.22.7", + "@jimp/plugin-dither": "^0.22.7", + "@jimp/plugin-fisheye": "^0.22.7", + "@jimp/plugin-flip": "^0.22.7", + "@jimp/plugin-gaussian": "^0.22.7", + "@jimp/plugin-invert": "^0.22.7", + "@jimp/plugin-mask": "^0.22.7", + "@jimp/plugin-normalize": "^0.22.7", + "@jimp/plugin-print": "^0.22.7", + "@jimp/plugin-resize": "^0.22.7", + "@jimp/plugin-rotate": "^0.22.7", + "@jimp/plugin-scale": "^0.22.7", + "@jimp/plugin-shadow": "^0.22.7", + "@jimp/plugin-threshold": "^0.22.7", "timm": "^1.6.1" }, "peerDependencies": { @@ -742,11 +742,11 @@ } }, "node_modules/@jimp/png": { - "version": "0.22.4", - "resolved": "https://registry.npmjs.org/@jimp/png/-/png-0.22.4.tgz", - "integrity": "sha512-kDovx9dTyV/TSR40HQHdRyVgNNb7Cny4/0PPEa+xeR7snuDC3dV5hu9s/QJwY0RMGiAkiuKDpiaBuSZuz8dwRQ==", + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/png/-/png-0.22.7.tgz", + "integrity": "sha512-LxD3O9FKEwVv+j+HcUV7ez72Miy+823EjhtFZbBYXNp9qjHtHFBpgcSJBftUOCei8OlmmVgULYn9XjyfPsDgGw==", "dependencies": { - "@jimp/utils": "^0.22.4", + "@jimp/utils": "^0.22.7", "pngjs": "^6.0.0" }, "peerDependencies": { @@ -754,9 +754,9 @@ } }, "node_modules/@jimp/tiff": { - "version": "0.22.4", - "resolved": "https://registry.npmjs.org/@jimp/tiff/-/tiff-0.22.4.tgz", - "integrity": "sha512-RStUATRnb+unYzzuGxU+SPZALqh5NxYdcS6UGTBvhCMlijopGiY/iL01wstIOst0ypKIjwbcSVj7mAHn6B7Qbw==", + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/tiff/-/tiff-0.22.7.tgz", + "integrity": "sha512-/oE8kLumzBfU1Z6h4TrDXYCGQNc4CjbZQvPssjImEqNLr5vbefpIpoy1fVMpsyuHZHsGovsBhBHxTJaRLO4+Og==", "dependencies": { "utif2": "^4.0.1" }, @@ -765,15 +765,15 @@ } }, "node_modules/@jimp/types": { - "version": "0.22.4", - "resolved": "https://registry.npmjs.org/@jimp/types/-/types-0.22.4.tgz", - "integrity": "sha512-v3hm8LGc3we6P6ML0ticiLX7wtdvywrKthYxqVrJVIu3vRL0Z4q3ngFjwzqDmaIF8wC0neq98s/t7ODWfeIiRQ==", + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/types/-/types-0.22.7.tgz", + "integrity": "sha512-1T8BxwDh5HJvBh3tt6HUd8r7ir5Ge3JWATXC8O3Y9QYwOaERjA2+FVhGSjtoo5xCeJvLRjSzEtfZ8heowMBL4w==", "dependencies": { - "@jimp/bmp": "^0.22.4", - "@jimp/gif": "^0.22.4", - "@jimp/jpeg": "^0.22.4", - "@jimp/png": "^0.22.4", - "@jimp/tiff": "^0.22.4", + "@jimp/bmp": "^0.22.7", + "@jimp/gif": "^0.22.7", + "@jimp/jpeg": "^0.22.7", + "@jimp/png": "^0.22.7", + "@jimp/tiff": "^0.22.7", "timm": "^1.6.1" }, "peerDependencies": { @@ -781,9 +781,9 @@ } }, "node_modules/@jimp/utils": { - "version": "0.22.4", - "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-0.22.4.tgz", - "integrity": "sha512-EPaBMNg4NvVXnMpSFJEsdCQqdSVU2ACreAL+Ipkq19C/FkDEj9Q10t6Mjx8zOe/AAjBQj1vTALS/DykcHOn4bQ==", + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-0.22.7.tgz", + "integrity": "sha512-4ax4IOWLIERx4yz9y3fNXKvQaPOY23yJF5h4sizxVkQUObkZHWE0kL0TVHodBt3rS8ksdbCL8Jkz4GeNP/Katg==", "dependencies": { "regenerator-runtime": "^0.13.3" } @@ -1918,9 +1918,9 @@ "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" }, "node_modules/axios": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.3.3.tgz", - "integrity": "sha512-eYq77dYIFS77AQlhzEL937yUBSepBfPIe8FcgEDN35vMNZKMrs81pgnyrQpwfy4NF4b4XWX1Zgx7yX+25w8QJA==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.3.4.tgz", + "integrity": "sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==", "dependencies": { "follow-redirects": "^1.15.0", "form-data": "^4.0.0", @@ -6331,13 +6331,13 @@ } }, "node_modules/jimp": { - "version": "0.22.4", - "resolved": "https://registry.npmjs.org/jimp/-/jimp-0.22.4.tgz", - "integrity": "sha512-reGESbcYp38VlGtdAe8qrmbjLLEYXMrQWc2XXb7+czulKfCCidUHEpNfrS3hx5XXMWrAmoYKkxPTqCvll6Q6ug==", + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/jimp/-/jimp-0.22.7.tgz", + "integrity": "sha512-TJCTJ4ZcFUw6W8XZnR6ajdEu8vSyPi3AuoChs+zLHalXnhAPZgwkzwcXnxey4LNjh1p9dfIUkg8YSQ+q8pBW0A==", "dependencies": { - "@jimp/custom": "^0.22.4", - "@jimp/plugins": "^0.22.4", - "@jimp/types": "^0.22.4", + "@jimp/custom": "^0.22.7", + "@jimp/plugins": "^0.22.7", + "@jimp/types": "^0.22.7", "regenerator-runtime": "^0.13.3" } }, @@ -6383,12 +6383,12 @@ "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" }, "node_modules/jsdoc": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.1.tgz", - "integrity": "sha512-UjvSrLYb270Mq25RN4AGGg2uqKRV90nCqkGsI4gD3RIR1lgMN8nWxK/am8Rsj33tWyprzZdA+0q1qY07m0Ar7w==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.2.tgz", + "integrity": "sha512-e8cIg2z62InH7azBBi3EsSEqrKx+nUtAS5bBcYTSpZFA+vhNPyhv8PTFZ0WsjOPDj04/dOLlm08EDcQJDqaGQg==", "dev": true, "dependencies": { - "@babel/parser": "^7.9.4", + "@babel/parser": "^7.20.15", "@jsdoc/salty": "^0.2.1", "@types/markdown-it": "^12.2.3", "bluebird": "^3.7.2", @@ -8470,9 +8470,9 @@ } }, "node_modules/readable-web-to-node-stream/node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.1.tgz", + "integrity": "sha512-+rQmrWMYGA90yenhTYsLWAsLsqVC8osOw6PKE1HDYiO0gdPeKe/xDHNzIAIn4C91YQ6oenEhfYqqc1883qHbjQ==", "dependencies": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -10615,9 +10615,9 @@ }, "dependencies": { "@babel/parser": { - "version": "7.20.13", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.13.tgz", - "integrity": "sha512-gFDLKMfpiXCsjt4za2JA9oTMn70CeseCehb11kRZgvd7+F67Hih3OHOK24cRrWECJ/ljfPGac6ygXAs/C8kIvw==", + "version": "7.21.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.2.tgz", + "integrity": "sha512-URpaIJQwEkEC2T9Kn+Ai6Xe/02iNaVCuT/PtoRz3GPVJVDpPd7mLo+VddTbhCRU9TXqW5mSrQfXZyi8kDKOVpQ==", "dev": true }, "@braintree/sanitize-url": { @@ -10836,20 +10836,20 @@ "dev": true }, "@jimp/bmp": { - "version": "0.22.4", - "resolved": "https://registry.npmjs.org/@jimp/bmp/-/bmp-0.22.4.tgz", - "integrity": "sha512-ZDwQ/tLihpZuTCFGGa0zcyZIWHfhvHkrdbsoHUY0GG/JpH/y2xzlm2I48/TicCpoujN8oGKLHIJje0HmVX3xaA==", + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/bmp/-/bmp-0.22.7.tgz", + "integrity": "sha512-0cfBPvugURS7G+60vRBL+penDRst8x40alS5Rhn2nlGsgsBHljFDw7+H4o5r6gldw9nv9PR9JA90Wloy7KMZdQ==", "requires": { - "@jimp/utils": "^0.22.4", + "@jimp/utils": "^0.22.7", "bmp-js": "^0.1.0" } }, "@jimp/core": { - "version": "0.22.4", - "resolved": "https://registry.npmjs.org/@jimp/core/-/core-0.22.4.tgz", - "integrity": "sha512-K7guEYpXV44SCLR35QdPyKqF+mFZaEUAqiSL8qQ/F4N4Ws9JkPzFI/qYTjOkDoKxSWkXlKnlsk1sfMzy0yqA5g==", + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/core/-/core-0.22.7.tgz", + "integrity": "sha512-lg4z+pw23v2Gp9LWQur0NqYtnmoNWnyN/Or96elhJgeEJskrDGwROdajortHCCOI1xDnUZSirg8sFvStC8BIlg==", "requires": { - "@jimp/utils": "^0.22.4", + "@jimp/utils": "^0.22.7", "any-base": "^1.1.0", "buffer": "^5.2.0", "exif-parser": "^0.1.12", @@ -10861,272 +10861,272 @@ }, "dependencies": { "mkdirp": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-2.1.3.tgz", - "integrity": "sha512-sjAkg21peAG9HS+Dkx7hlG9Ztx7HLeKnvB3NQRcu/mltCVmvkF0pisbiTSfDVYTT86XEfZrTUosLdZLStquZUw==" + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-2.1.5.tgz", + "integrity": "sha512-jbjfql+shJtAPrFoKxHOXip4xS+kul9W3OzfzzrqueWK2QMGon2bFH2opl6W9EagBThjEz+iysyi/swOoVfB/w==" } } }, "@jimp/custom": { - "version": "0.22.4", - "resolved": "https://registry.npmjs.org/@jimp/custom/-/custom-0.22.4.tgz", - "integrity": "sha512-k9m/RfxjPjklUsgZ2nszlyNkodUG/4xlxlif70UELhxW8bdqZqqlQGzwA9p+PUiSnlSJYZjL6q+P8cd7yj1ggA==", + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/custom/-/custom-0.22.7.tgz", + "integrity": "sha512-n+1+ZVDNumB1E+sL7KdGKAJ6MbgniX1/v/xOEFEQ46WDZ4cRTqP4+tXjHTuHSlOXiANH+K9zD6qgzqmgO6mCVw==", "requires": { - "@jimp/core": "^0.22.4" + "@jimp/core": "^0.22.7" } }, "@jimp/gif": { - "version": "0.22.4", - "resolved": "https://registry.npmjs.org/@jimp/gif/-/gif-0.22.4.tgz", - "integrity": "sha512-KmN7GoaQTzLAX4JXLBRkIiZAXthgQdKe+Y7BOw4n6CMe6LAS/XCQqrYCG3Av/GqIO7UAKems6D7kIGAUuhpNlQ==", + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/gif/-/gif-0.22.7.tgz", + "integrity": "sha512-PGZMS8sYFnDcqg+t8IT3RaSJLrqB+3GzhI0hU5D4mmSuJ5UO/6Bdgu8nrwh3uFPxw0ZH6h9ozYk88cz0pKEhLQ==", "requires": { - "@jimp/utils": "^0.22.4", + "@jimp/utils": "^0.22.7", "gifwrap": "^0.9.2", "omggif": "^1.0.9" } }, "@jimp/jpeg": { - "version": "0.22.4", - "resolved": "https://registry.npmjs.org/@jimp/jpeg/-/jpeg-0.22.4.tgz", - "integrity": "sha512-mMJNhEtJpne65mxpIXEvT0VIzmsKiZWmaFT/c2eQ2tBLEtWAFpkvoP+F7jEaU+F3Ur4fXKFkJ/xOSXtRr/gGNw==", + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/jpeg/-/jpeg-0.22.7.tgz", + "integrity": "sha512-ptwWyX/7RPcREy8SpPN/8IlywbwyPXiuXmoHwM6m4iKcyaCmmnfCdZwLNXYliJzFAFLLOWDuOrwO3cZSkH6Czg==", "requires": { - "@jimp/utils": "^0.22.4", + "@jimp/utils": "^0.22.7", "jpeg-js": "^0.4.4" } }, "@jimp/plugin-blit": { - "version": "0.22.4", - "resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-0.22.4.tgz", - "integrity": "sha512-QQHe+rFarsxJQxWKlyHEMfLyXmUG9AiQky+8WfwjZVBYilIFyiBywOc3sThonOsru+7LOSUDmbN6mvbFk4R+gw==", + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-0.22.7.tgz", + "integrity": "sha512-8oXcBTSd/sBmTQATrCxQ1ZBER31Lge8vXzWqNCbC3b1ZvRggCcqnDzRRH1+JiI4i+jPRo3Fi6/sdvEUyQ5LY3g==", "requires": { - "@jimp/utils": "^0.22.4" + "@jimp/utils": "^0.22.7" } }, "@jimp/plugin-blur": { - "version": "0.22.4", - "resolved": "https://registry.npmjs.org/@jimp/plugin-blur/-/plugin-blur-0.22.4.tgz", - "integrity": "sha512-p57Ac5LEQckIogiwf7qyOojGvLOD08eMaQd5ylOhet/fbdwAzD/8flWFhSIKsdAVzvnfGcszuLtrsV07jDutTw==", + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/plugin-blur/-/plugin-blur-0.22.7.tgz", + "integrity": "sha512-M+0I5CKFIpnIQE27j8o8NECBsOFBd4z7C95ydy2UohYopugFq+hSVtMs1D4pQgb0RW1DJPiXD/4PHqb+lzV5mA==", "requires": { - "@jimp/utils": "^0.22.4" + "@jimp/utils": "^0.22.7" } }, "@jimp/plugin-circle": { - "version": "0.22.4", - "resolved": "https://registry.npmjs.org/@jimp/plugin-circle/-/plugin-circle-0.22.4.tgz", - "integrity": "sha512-T+TpG+s+wM9kKHlpIEfCAfOM+QrYVqcMoWjkULddc0KtaDEhqgGYFhN+/SlzJfDbZKw0xUgIuAw89sXuzMIUjw==", + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/plugin-circle/-/plugin-circle-0.22.7.tgz", + "integrity": "sha512-zfZKKpOhlyiDeFjGW5JB9K4h/kvbdaAJWUEwmKrvvGar67G3j8dKu46AX0MeWRNZ1yk/lfz+JIa7TzKfxEBf6w==", "requires": { - "@jimp/utils": "^0.22.4" + "@jimp/utils": "^0.22.7" } }, "@jimp/plugin-color": { - "version": "0.22.4", - "resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-0.22.4.tgz", - "integrity": "sha512-TZqcqepoCcIlF7VodPPfS3WET+LL5Y/XnXOBk4tWnG5i+lhNrs7/U0HOJY6Iw9o4g267DddnlfKWmunvzBcvOQ==", + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-0.22.7.tgz", + "integrity": "sha512-OXro9pdB0twQjV4LgW0bTEXaX1VgBsTBcFoDAs8q9mtQzD5p3UQmJ+ykCiQ5rTPxNN1Buc44tcCIfp8haB1ZVQ==", "requires": { - "@jimp/utils": "^0.22.4", + "@jimp/utils": "^0.22.7", "tinycolor2": "^1.6.0" } }, "@jimp/plugin-contain": { - "version": "0.22.4", - "resolved": "https://registry.npmjs.org/@jimp/plugin-contain/-/plugin-contain-0.22.4.tgz", - "integrity": "sha512-Hl+TO4v+EpRfEl3R6k/bEgOGOpm6JqNfEIyCFWLi6yqJQjMGzBQ0vt+VHe2u3WIFaFrDWsGxeuFZBDzgtjTwxw==", + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/plugin-contain/-/plugin-contain-0.22.7.tgz", + "integrity": "sha512-GwUxZp4jMA0O0qbknUPDONJAfHFaTRs8kK+jgRtUfgb1Xi96l5RN/PMMDv4owZCUiPVAON80X1BMj7nSQWNVUw==", "requires": { - "@jimp/utils": "^0.22.4" + "@jimp/utils": "^0.22.7" } }, "@jimp/plugin-cover": { - "version": "0.22.4", - "resolved": "https://registry.npmjs.org/@jimp/plugin-cover/-/plugin-cover-0.22.4.tgz", - "integrity": "sha512-KMTQjN/B7r/RNzoLFwnhqhLrgT0kMqTkBMEZQSopj5vPLPNjIX0ElEYC8AIVFKeZAV+1mYkyss+IDdxq4fyRng==", + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/plugin-cover/-/plugin-cover-0.22.7.tgz", + "integrity": "sha512-PVXeQyofGepMoJaQ5XapLwCcZfsOF1IoAotHosh8AOP8niCP/Erm8T6ZWf5tf0sMJiLHQMPUyns186H5isqEMQ==", "requires": { - "@jimp/utils": "^0.22.4" + "@jimp/utils": "^0.22.7" } }, "@jimp/plugin-crop": { - "version": "0.22.4", - "resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-0.22.4.tgz", - "integrity": "sha512-8krDt7xzBa1fbtlYCzEMZIgNjTkhgywho0FJpgIMkIUMjaZITS1Ea/Veb3UrWt8EsgQS6hxjGVE/Q1FvP5iPLA==", + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-0.22.7.tgz", + "integrity": "sha512-XXvUU+hPdodtTBSgyUJUnzh7JgKMVlS1GxjcQsjYU8iGr1dbpuazKMTQxc76ChVmy8ue4goi8bGstacWUHpl/Q==", "requires": { - "@jimp/utils": "^0.22.4" + "@jimp/utils": "^0.22.7" } }, "@jimp/plugin-displace": { - "version": "0.22.4", - "resolved": "https://registry.npmjs.org/@jimp/plugin-displace/-/plugin-displace-0.22.4.tgz", - "integrity": "sha512-3gBfwYVFrOjp8SUpb7H0UMgqvsG/sxY1PVBIfRW9MUCosiH1eE/Mo5cbxhQ6/w5f3sh23lBmG8W0WuSrnXLorg==", + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/plugin-displace/-/plugin-displace-0.22.7.tgz", + "integrity": "sha512-CCNAkmm2OS4QQtNRfQvXqoAMxNE0maSlVEV5DNdioHOUKycy02EJ5hNYR3l0FG+NraQHOuqv9XV37sGRl6QzMA==", "requires": { - "@jimp/utils": "^0.22.4" + "@jimp/utils": "^0.22.7" } }, "@jimp/plugin-dither": { - "version": "0.22.4", - "resolved": "https://registry.npmjs.org/@jimp/plugin-dither/-/plugin-dither-0.22.4.tgz", - "integrity": "sha512-oOhdZBDJpSGIoTUhPOIvLIVUwILRWgrWdA4Vbzcyz2RHvaPHS8gdBH0EdIPbJ5agNyFnY8sJWFM7YKx/rLNKsw==", + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/plugin-dither/-/plugin-dither-0.22.7.tgz", + "integrity": "sha512-ndCW5MIGMdh3aBvvgRCO7el9cIPG29kU7xQYlOs5+3JsDk3Vf7X30QGPjzxABOY95qLUNUjf5Qe/p/tqv/vbcw==", "requires": { - "@jimp/utils": "^0.22.4" + "@jimp/utils": "^0.22.7" } }, "@jimp/plugin-fisheye": { - "version": "0.22.4", - "resolved": "https://registry.npmjs.org/@jimp/plugin-fisheye/-/plugin-fisheye-0.22.4.tgz", - "integrity": "sha512-2myNZyDrwUOV8MKd4NeULnEOojYF7XRbnRHiUPsNptpmK6g/gI4xt+5k7BallAYZD8ZLfZVjstUogsObprHd/Q==", + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/plugin-fisheye/-/plugin-fisheye-0.22.7.tgz", + "integrity": "sha512-boI1QowhZRfb6OF+ZPWtiSJP1GATsTHjd5Oy/lJ+n0L4rp439ZOTB1Elzcgc44O2C1mgZDdybRPQQvYdPF8slA==", "requires": { - "@jimp/utils": "^0.22.4" + "@jimp/utils": "^0.22.7" } }, "@jimp/plugin-flip": { - "version": "0.22.4", - "resolved": "https://registry.npmjs.org/@jimp/plugin-flip/-/plugin-flip-0.22.4.tgz", - "integrity": "sha512-9FZ0k5N5leLDefeDjizXXTl17dzo23PYtCD/T4xeSVr96d1pQDwbeIk7pEhhHr1rl98tJe0U/OV2dFXFYauKPw==", + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/plugin-flip/-/plugin-flip-0.22.7.tgz", + "integrity": "sha512-/jkbgtvQPcKadAEV5ZXyoEpSdd7GEvGs/Ya/f48+LNszc+S24u4UXtuP3QPRJ5FHm0Re1t4uztM7xa6IPklAOA==", "requires": { - "@jimp/utils": "^0.22.4" + "@jimp/utils": "^0.22.7" } }, "@jimp/plugin-gaussian": { - "version": "0.22.4", - "resolved": "https://registry.npmjs.org/@jimp/plugin-gaussian/-/plugin-gaussian-0.22.4.tgz", - "integrity": "sha512-irOSwLdZ9kTq5Wd5dpkMgIMJVwemYcqgnzd04+P6TJGYmem2HR6JUCDpjbET3Fpbo/snFLm4mZ+2A+SmeCGjKA==", + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/plugin-gaussian/-/plugin-gaussian-0.22.7.tgz", + "integrity": "sha512-OB1sdnjzq2rfUHmx9Rvi3SJIDbQAgWFgYEw6KhN3TSVOdrJHvwrQkEnwR9PoUzQg992VIpGcVc9Y1s/SOU2oCA==", "requires": { - "@jimp/utils": "^0.22.4" + "@jimp/utils": "^0.22.7" } }, "@jimp/plugin-invert": { - "version": "0.22.4", - "resolved": "https://registry.npmjs.org/@jimp/plugin-invert/-/plugin-invert-0.22.4.tgz", - "integrity": "sha512-/WtZeLrF+H+mzbjqudeGvvSxudlHy1kyiP1gVWDxhYNQOuZJI57Vn20kSTYvHBNjvy31LV4/uestyX8j8tE2Qg==", + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/plugin-invert/-/plugin-invert-0.22.7.tgz", + "integrity": "sha512-dX/TqACJ/M5uXDIEJlVPPwietMD6EWUeA/CV4uvhLz9EMjTgHociJ3TWqGCY/70phhIBLbhLcHUVBL/q65ynfQ==", "requires": { - "@jimp/utils": "^0.22.4" + "@jimp/utils": "^0.22.7" } }, "@jimp/plugin-mask": { - "version": "0.22.4", - "resolved": "https://registry.npmjs.org/@jimp/plugin-mask/-/plugin-mask-0.22.4.tgz", - "integrity": "sha512-U0SrOwBNKkMYTNPTz5CXeJdZ4c5easFlq2B9Txy0kPsav2OraTv8cZjpMxrWUejo/AQGVUDbaGtXMm9pE13/6w==", + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/plugin-mask/-/plugin-mask-0.22.7.tgz", + "integrity": "sha512-rfKHKJLAtJG7qbB4zYAMcQ9ue3CIFRuAJ3xX0lzCxC0fGvCVuXlcxiAEauBxqaTWqiKMnahqpR3/Ah679K2FKQ==", "requires": { - "@jimp/utils": "^0.22.4" + "@jimp/utils": "^0.22.7" } }, "@jimp/plugin-normalize": { - "version": "0.22.4", - "resolved": "https://registry.npmjs.org/@jimp/plugin-normalize/-/plugin-normalize-0.22.4.tgz", - "integrity": "sha512-XJiPBJGCHWmIzUdmL4mWP1Ev5LMp77oMmPXdgQGDty1cxfyo3CbkTjZSsnwF/XLlrQ1yfLW+8JB+ihGKcVEOxA==", + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/plugin-normalize/-/plugin-normalize-0.22.7.tgz", + "integrity": "sha512-t8x2jjKDmvUAZB4Wbeagr4D0BvoVCIWquy94mpglvSZ8ujKLt0aQBl3CBEIbXFAoVqNif+G36NtxPHNsjxIXOg==", "requires": { - "@jimp/utils": "^0.22.4" + "@jimp/utils": "^0.22.7" } }, "@jimp/plugin-print": { - "version": "0.22.4", - "resolved": "https://registry.npmjs.org/@jimp/plugin-print/-/plugin-print-0.22.4.tgz", - "integrity": "sha512-mayiPhg6c7KYjvq3fYOW9ohhXD1eWdEiseV9dAWqTOEbDbohT8S6eTGhVIiVa2sVySLcpNEKZSk07c5EhJAMcw==", + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/plugin-print/-/plugin-print-0.22.7.tgz", + "integrity": "sha512-kx0+cPeinki1IFg9cJy7LC4uVuOEOa8TIrcERioB6PVgJ7EDzCAfatTKULZ+t4uSs2K/lQF97wPYlbiyxs/Hzg==", "requires": { - "@jimp/utils": "^0.22.4", + "@jimp/utils": "^0.22.7", "load-bmfont": "^1.4.1" } }, "@jimp/plugin-resize": { - "version": "0.22.4", - "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-0.22.4.tgz", - "integrity": "sha512-2wMdpPNGf6Zo2lfJg1QHHQ+ds5baQH75IcFpdjw717dcEISpn1jPG//iClXOGh16OJsRQlwHESaZTgEo/5Dw/g==", + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-0.22.7.tgz", + "integrity": "sha512-pg7i0JIYt7x7ag+CoD/yG70Xvwm1sKRfcFjQh954yestiin14uppPgXchAmTBmctecBjLNdsVlqSXbPvU4Jvxw==", "requires": { - "@jimp/utils": "^0.22.4" + "@jimp/utils": "^0.22.7" } }, "@jimp/plugin-rotate": { - "version": "0.22.4", - "resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-0.22.4.tgz", - "integrity": "sha512-g08LBsPENbeA6NVoeq0iuDgAL89+N+aZrvYVKYkiJZIM7vUvueJyAIq4+bjDl4r54OR8XBFX0GsrKsqrULh1eA==", + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-0.22.7.tgz", + "integrity": "sha512-Uh3Gb18IY8uXWk6E1bzMopum2GP+xwohbnMIDE0MSWmLaz7LXrfnvgXFba1uRGgn73CJz8UDS4fC1KIJMuxQZA==", "requires": { - "@jimp/utils": "^0.22.4" + "@jimp/utils": "^0.22.7" } }, "@jimp/plugin-scale": { - "version": "0.22.4", - "resolved": "https://registry.npmjs.org/@jimp/plugin-scale/-/plugin-scale-0.22.4.tgz", - "integrity": "sha512-cJiLQtTcNk6/+j05R23TFGXy+smDV0BdlmzJVDb+7Ye9qcmWpkdjVSioQQqZr0QScIYKhhRCY/lFTepBx67yzw==", + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/plugin-scale/-/plugin-scale-0.22.7.tgz", + "integrity": "sha512-3uHUrk5Rl6MCxuoJtHTSeJjSHIxHWqOOgmD2caKIvyxds0Zmofu/Fva+N4V/m80E4q4G2RXNsUplFpFGhUM7hw==", "requires": { - "@jimp/utils": "^0.22.4" + "@jimp/utils": "^0.22.7" } }, "@jimp/plugin-shadow": { - "version": "0.22.4", - "resolved": "https://registry.npmjs.org/@jimp/plugin-shadow/-/plugin-shadow-0.22.4.tgz", - "integrity": "sha512-a5hdpzGBzBo91DNiKaGvs8iJbs2bYQmDRm/BrCh4NET+h5l5AwXNu/Ak0bWRhN16YQ55XYNGHer2jOwBPrf2WQ==", + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/plugin-shadow/-/plugin-shadow-0.22.7.tgz", + "integrity": "sha512-NKEq5VR8U/d0OKf0hxFtrrbMCuNv7by31V+Kwgxb1oTP+j+zZEaww+m3YgEwIwRe7E8/yeDSHa5bJ+CmuyFZjw==", "requires": { - "@jimp/utils": "^0.22.4" + "@jimp/utils": "^0.22.7" } }, "@jimp/plugin-threshold": { - "version": "0.22.4", - "resolved": "https://registry.npmjs.org/@jimp/plugin-threshold/-/plugin-threshold-0.22.4.tgz", - "integrity": "sha512-jTT/+p2zb2NESzd7O0bVRowiQszoaHeBf2OgP7lFde10fHd+fn78m5brUmSmlGAdlMRwm8S8ZcxTj5ZjdQns5w==", + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/plugin-threshold/-/plugin-threshold-0.22.7.tgz", + "integrity": "sha512-BH4aLwfmnqjRVhdzMIqUns4ycZ6QoHHFR6Qz+X2iSpH5a33xFA4DRbd3Ehtrs4Gk7XiCjWkUyM6wjmH7l/1hNQ==", "requires": { - "@jimp/utils": "^0.22.4" + "@jimp/utils": "^0.22.7" } }, "@jimp/plugins": { - "version": "0.22.4", - "resolved": "https://registry.npmjs.org/@jimp/plugins/-/plugins-0.22.4.tgz", - "integrity": "sha512-yAxcA4UR3Bs7j73I7wt4ty52vm5MzPmr+8DYk8jrS/ng2Z2iuXzbcTe4mf9eEqXYVah3rTIggo4dPjW75DRZtA==", + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/plugins/-/plugins-0.22.7.tgz", + "integrity": "sha512-AJmzTG/sa+CDpvle/UE89hjHR85gnRGSwLuQqPbhlY6GFCmC3uqHRJz9O5I8A4zdi9+e8LsBphuTlKV7RbuXOw==", "requires": { - "@jimp/plugin-blit": "^0.22.4", - "@jimp/plugin-blur": "^0.22.4", - "@jimp/plugin-circle": "^0.22.4", - "@jimp/plugin-color": "^0.22.4", - "@jimp/plugin-contain": "^0.22.4", - "@jimp/plugin-cover": "^0.22.4", - "@jimp/plugin-crop": "^0.22.4", - "@jimp/plugin-displace": "^0.22.4", - "@jimp/plugin-dither": "^0.22.4", - "@jimp/plugin-fisheye": "^0.22.4", - "@jimp/plugin-flip": "^0.22.4", - "@jimp/plugin-gaussian": "^0.22.4", - "@jimp/plugin-invert": "^0.22.4", - "@jimp/plugin-mask": "^0.22.4", - "@jimp/plugin-normalize": "^0.22.4", - "@jimp/plugin-print": "^0.22.4", - "@jimp/plugin-resize": "^0.22.4", - "@jimp/plugin-rotate": "^0.22.4", - "@jimp/plugin-scale": "^0.22.4", - "@jimp/plugin-shadow": "^0.22.4", - "@jimp/plugin-threshold": "^0.22.4", + "@jimp/plugin-blit": "^0.22.7", + "@jimp/plugin-blur": "^0.22.7", + "@jimp/plugin-circle": "^0.22.7", + "@jimp/plugin-color": "^0.22.7", + "@jimp/plugin-contain": "^0.22.7", + "@jimp/plugin-cover": "^0.22.7", + "@jimp/plugin-crop": "^0.22.7", + "@jimp/plugin-displace": "^0.22.7", + "@jimp/plugin-dither": "^0.22.7", + "@jimp/plugin-fisheye": "^0.22.7", + "@jimp/plugin-flip": "^0.22.7", + "@jimp/plugin-gaussian": "^0.22.7", + "@jimp/plugin-invert": "^0.22.7", + "@jimp/plugin-mask": "^0.22.7", + "@jimp/plugin-normalize": "^0.22.7", + "@jimp/plugin-print": "^0.22.7", + "@jimp/plugin-resize": "^0.22.7", + "@jimp/plugin-rotate": "^0.22.7", + "@jimp/plugin-scale": "^0.22.7", + "@jimp/plugin-shadow": "^0.22.7", + "@jimp/plugin-threshold": "^0.22.7", "timm": "^1.6.1" } }, "@jimp/png": { - "version": "0.22.4", - "resolved": "https://registry.npmjs.org/@jimp/png/-/png-0.22.4.tgz", - "integrity": "sha512-kDovx9dTyV/TSR40HQHdRyVgNNb7Cny4/0PPEa+xeR7snuDC3dV5hu9s/QJwY0RMGiAkiuKDpiaBuSZuz8dwRQ==", + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/png/-/png-0.22.7.tgz", + "integrity": "sha512-LxD3O9FKEwVv+j+HcUV7ez72Miy+823EjhtFZbBYXNp9qjHtHFBpgcSJBftUOCei8OlmmVgULYn9XjyfPsDgGw==", "requires": { - "@jimp/utils": "^0.22.4", + "@jimp/utils": "^0.22.7", "pngjs": "^6.0.0" } }, "@jimp/tiff": { - "version": "0.22.4", - "resolved": "https://registry.npmjs.org/@jimp/tiff/-/tiff-0.22.4.tgz", - "integrity": "sha512-RStUATRnb+unYzzuGxU+SPZALqh5NxYdcS6UGTBvhCMlijopGiY/iL01wstIOst0ypKIjwbcSVj7mAHn6B7Qbw==", + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/tiff/-/tiff-0.22.7.tgz", + "integrity": "sha512-/oE8kLumzBfU1Z6h4TrDXYCGQNc4CjbZQvPssjImEqNLr5vbefpIpoy1fVMpsyuHZHsGovsBhBHxTJaRLO4+Og==", "requires": { "utif2": "^4.0.1" } }, "@jimp/types": { - "version": "0.22.4", - "resolved": "https://registry.npmjs.org/@jimp/types/-/types-0.22.4.tgz", - "integrity": "sha512-v3hm8LGc3we6P6ML0ticiLX7wtdvywrKthYxqVrJVIu3vRL0Z4q3ngFjwzqDmaIF8wC0neq98s/t7ODWfeIiRQ==", + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/types/-/types-0.22.7.tgz", + "integrity": "sha512-1T8BxwDh5HJvBh3tt6HUd8r7ir5Ge3JWATXC8O3Y9QYwOaERjA2+FVhGSjtoo5xCeJvLRjSzEtfZ8heowMBL4w==", "requires": { - "@jimp/bmp": "^0.22.4", - "@jimp/gif": "^0.22.4", - "@jimp/jpeg": "^0.22.4", - "@jimp/png": "^0.22.4", - "@jimp/tiff": "^0.22.4", + "@jimp/bmp": "^0.22.7", + "@jimp/gif": "^0.22.7", + "@jimp/jpeg": "^0.22.7", + "@jimp/png": "^0.22.7", + "@jimp/tiff": "^0.22.7", "timm": "^1.6.1" } }, "@jimp/utils": { - "version": "0.22.4", - "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-0.22.4.tgz", - "integrity": "sha512-EPaBMNg4NvVXnMpSFJEsdCQqdSVU2ACreAL+Ipkq19C/FkDEj9Q10t6Mjx8zOe/AAjBQj1vTALS/DykcHOn4bQ==", + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-0.22.7.tgz", + "integrity": "sha512-4ax4IOWLIERx4yz9y3fNXKvQaPOY23yJF5h4sizxVkQUObkZHWE0kL0TVHodBt3rS8ksdbCL8Jkz4GeNP/Katg==", "requires": { "regenerator-runtime": "^0.13.3" } @@ -12084,9 +12084,9 @@ "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" }, "axios": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.3.3.tgz", - "integrity": "sha512-eYq77dYIFS77AQlhzEL937yUBSepBfPIe8FcgEDN35vMNZKMrs81pgnyrQpwfy4NF4b4XWX1Zgx7yX+25w8QJA==", + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.3.4.tgz", + "integrity": "sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==", "requires": { "follow-redirects": "^1.15.0", "form-data": "^4.0.0", @@ -15475,13 +15475,13 @@ } }, "jimp": { - "version": "0.22.4", - "resolved": "https://registry.npmjs.org/jimp/-/jimp-0.22.4.tgz", - "integrity": "sha512-reGESbcYp38VlGtdAe8qrmbjLLEYXMrQWc2XXb7+czulKfCCidUHEpNfrS3hx5XXMWrAmoYKkxPTqCvll6Q6ug==", + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/jimp/-/jimp-0.22.7.tgz", + "integrity": "sha512-TJCTJ4ZcFUw6W8XZnR6ajdEu8vSyPi3AuoChs+zLHalXnhAPZgwkzwcXnxey4LNjh1p9dfIUkg8YSQ+q8pBW0A==", "requires": { - "@jimp/custom": "^0.22.4", - "@jimp/plugins": "^0.22.4", - "@jimp/types": "^0.22.4", + "@jimp/custom": "^0.22.7", + "@jimp/plugins": "^0.22.7", + "@jimp/types": "^0.22.7", "regenerator-runtime": "^0.13.3" } }, @@ -15524,12 +15524,12 @@ "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" }, "jsdoc": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.1.tgz", - "integrity": "sha512-UjvSrLYb270Mq25RN4AGGg2uqKRV90nCqkGsI4gD3RIR1lgMN8nWxK/am8Rsj33tWyprzZdA+0q1qY07m0Ar7w==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/jsdoc/-/jsdoc-4.0.2.tgz", + "integrity": "sha512-e8cIg2z62InH7azBBi3EsSEqrKx+nUtAS5bBcYTSpZFA+vhNPyhv8PTFZ0WsjOPDj04/dOLlm08EDcQJDqaGQg==", "dev": true, "requires": { - "@babel/parser": "^7.9.4", + "@babel/parser": "^7.20.15", "@jsdoc/salty": "^0.2.1", "@types/markdown-it": "^12.2.3", "bluebird": "^3.7.2", @@ -17140,9 +17140,9 @@ }, "dependencies": { "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.1.tgz", + "integrity": "sha512-+rQmrWMYGA90yenhTYsLWAsLsqVC8osOw6PKE1HDYiO0gdPeKe/xDHNzIAIn4C91YQ6oenEhfYqqc1883qHbjQ==", "requires": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", diff --git a/package.json b/package.json index 05baeaf2f..1e3111c81 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "@excalidraw/excalidraw": "0.14.2", "archiver": "5.3.1", "async-mutex": "0.4.0", - "axios": "1.3.3", + "axios": "1.3.4", "better-sqlite3": "7.4.5", "chokidar": "3.5.3", "cls-hooked": "4.2.2", @@ -64,7 +64,7 @@ "ini": "3.0.1", "is-animated": "2.0.2", "is-svg": "4.3.2", - "jimp": "0.22.4", + "jimp": "0.22.7", "joplin-turndown-plugin-gfm": "1.0.12", "jsdom": "21.1.0", "mime-types": "2.1.35", @@ -101,7 +101,7 @@ "electron-rebuild": "3.2.9", "esm": "3.2.25", "jasmine": "4.5.0", - "jsdoc": "4.0.1", + "jsdoc": "4.0.2", "lorem-ipsum": "2.0.8", "rcedit": "3.0.1", "webpack": "5.75.0", diff --git a/src/becca/becca.js b/src/becca/becca.js index 60422e672..3f142f35b 100644 --- a/src/becca/becca.js +++ b/src/becca/becca.js @@ -121,6 +121,14 @@ class Becca { return row ? new BNoteRevision(row) : null; } + /** @returns {BNoteAncillary|null} */ + getNoteAncillary(noteAncillaryId) { + const row = sql.getRow("SELECT * FROM note_ancillaries WHERE noteAncillaryId = ?", [noteAncillaryId]); + + const BNoteAncillary = require("./entities/bnote_ancillary"); // avoiding circular dependency problems + return row ? new BNoteAncillary(row) : null; + } + /** @returns {BOption|null} */ getOption(name) { return this.options[name]; @@ -143,6 +151,8 @@ class Becca { if (entityName === 'note_revisions') { return this.getNoteRevision(entityId); + } else if (entityName === 'note_ancillaries') { + return this.getNoteAncillary(entityId); } const camelCaseEntityName = entityName.toLowerCase().replace(/(_[a-z])/g, diff --git a/src/becca/entities/bbranch.js b/src/becca/entities/bbranch.js index 18b19588c..06912b9fb 100644 --- a/src/becca/entities/bbranch.js +++ b/src/becca/entities/bbranch.js @@ -198,6 +198,10 @@ class BBranch extends AbstractBeccaEntity { relation.markAsDeleted(deleteId); } + for (const noteAncillary of note.getNoteAncillaries()) { + noteAncillary.markAsDeleted(deleteId); + } + note.markAsDeleted(deleteId); return true; diff --git a/src/becca/entities/bnote.js b/src/becca/entities/bnote.js index 6ef73b28d..d0698601f 100644 --- a/src/becca/entities/bnote.js +++ b/src/becca/entities/bnote.js @@ -8,6 +8,7 @@ const dateUtils = require('../../services/date_utils'); const entityChangesService = require('../../services/entity_changes'); const AbstractBeccaEntity = require("./abstract_becca_entity"); const BNoteRevision = require("./bnote_revision"); +const BNoteAncillary = require("./bnote_ancillary"); const TaskContext = require("../../services/task_context"); const dayjs = require("dayjs"); const utc = require('dayjs/plugin/utc'); @@ -1135,6 +1136,19 @@ class BNote extends AbstractBeccaEntity { .map(row => new BNoteRevision(row)); } + /** @returns {BNoteAncillary[]} */ + getNoteAncillaries() { + return sql.getRows("SELECT * FROM note_ancillaries WHERE noteId = ? AND isDeleted = 0", [this.noteId]) + .map(row => new BNoteAncillary(row)); + } + + /** @returns {BNoteAncillary|undefined} */ + getNoteAncillaryByName(name) { + return sql.getRows("SELECT * FROM note_ancillaries WHERE noteId = ? AND name = ? AND isDeleted = 0", [this.noteId, name]) + .map(row => new BNoteAncillary(row)) + [0]; + } + /** * @returns {string[][]} - array of notePaths (each represented by array of noteIds constituting the particular note path) */ @@ -1462,6 +1476,31 @@ class BNote extends AbstractBeccaEntity { return noteRevision; } + /** + * @returns {BNoteAncillary} + */ + saveNoteAncillary(name, mime, content) { + let noteAncillary = this.getNoteAncillaryByName(name); + + if (noteAncillary + && noteAncillary.mime === mime + && noteAncillary.contentCheckSum === noteAncillary.calculateCheckSum(content)) { + + return noteAncillary; // no change + } + + noteAncillary = new BNoteAncillary({ + noteId: this.noteId, + name, + mime, + isProtected: this.isProtected + }); + + noteAncillary.setContent(content); + + return noteAncillary; + } + beforeSaving() { super.beforeSaving(); diff --git a/src/becca/entities/bnote_ancillary.js b/src/becca/entities/bnote_ancillary.js new file mode 100644 index 000000000..600889391 --- /dev/null +++ b/src/becca/entities/bnote_ancillary.js @@ -0,0 +1,161 @@ +"use strict"; + +const protectedSessionService = require('../../services/protected_session'); +const utils = require('../../services/utils'); +const sql = require('../../services/sql'); +const dateUtils = require('../../services/date_utils'); +const becca = require('../becca'); +const entityChangesService = require('../../services/entity_changes'); +const AbstractBeccaEntity = require("./abstract_becca_entity"); + +/** + * NoteAncillary represent data related/attached to the note. Conceptually similar to attributes, but intended for + * larger amounts of data and generally not accessible to the user. + * + * @extends AbstractBeccaEntity + */ +class BNoteAncillary extends AbstractBeccaEntity { + static get entityName() { return "note_ancillaries"; } + static get primaryKeyName() { return "noteAncillaryId"; } + static get hashedProperties() { return ["noteAncillaryId", "noteId", "name", "content", "utcDateModified"]; } + + constructor(row) { + super(); + + if (!row.noteId) { + throw new Error("'noteId' must be given to initialize a NoteAncillary entity"); + } + + if (!row.name) { + throw new Error("'name' must be given to initialize a NoteAncillary entity"); + } + + /** @type {string} needs to be set at the initialization time since it's used in the .setContent() */ + this.noteAncillaryId = row.noteAncillaryId || `${this.noteId}_${this.name}`; + /** @type {string} */ + this.noteId = row.noteId; + /** @type {string} */ + this.name = row.name; + /** @type {string} */ + this.mime = row.mime; + /** @type {boolean} */ + this.isProtected = !!row.isProtected; + /** @type {string} */ + this.contentCheckSum = row.contentCheckSum; + /** @type {string} */ + this.utcDateModified = row.utcDateModified; + } + + getNote() { + return becca.notes[this.noteId]; + } + + /** @returns {boolean} true if the note has string content (not binary) */ + isStringNote() { + return utils.isStringNote(this.type, this.mime); + } + + /** @returns {*} */ + getContent(silentNotFoundError = false) { + const res = sql.getRow(`SELECT content FROM note_ancillary_contents WHERE noteAncillaryId = ?`, [this.noteAncillaryId]); + + if (!res) { + if (silentNotFoundError) { + return undefined; + } + else { + throw new Error(`Cannot find note ancillary content for noteAncillaryId=${this.noteAncillaryId}`); + } + } + + let content = res.content; + + if (this.isProtected) { + if (protectedSessionService.isProtectedSessionAvailable()) { + content = protectedSessionService.decrypt(content); + } + else { + content = ""; + } + } + + if (this.isStringNote()) { + return content === null + ? "" + : content.toString("UTF-8"); + } + else { + return content; + } + } + + setContent(content) { + sql.transactional(() => { + this.contentCheckSum = this.calculateCheckSum(content); + this.save(); // also explicitly save note_ancillary to update contentCheckSum + + const pojo = { + noteAncillaryId: this.noteAncillaryId, + content: content, + utcDateModified: dateUtils.utcNowDateTime() + }; + + if (this.isProtected) { + if (protectedSessionService.isProtectedSessionAvailable()) { + pojo.content = protectedSessionService.encrypt(pojo.content); + } else { + throw new Error(`Cannot update content of noteAncillaryId=${this.noteAncillaryId} since we're out of protected session.`); + } + } + + sql.upsert("note_ancillary_contents", "noteAncillaryId", pojo); + + entityChangesService.addEntityChange({ + entityName: 'note_ancillary_contents', + entityId: this.noteAncillaryId, + hash: this.contentCheckSum, + isErased: false, + utcDateChanged: pojo.utcDateModified, + isSynced: true + }); + }); + } + + calculateCheckSum(content) { + return utils.hash(`${this.noteAncillaryId}|${content.toString()}`); + } + + beforeSaving() { + if (!this.name.match(/^[a-z0-9]+$/i)) { + throw new Error(`Name must be alphanumerical, "${this.name}" given.`); + } + + this.noteAncillaryId = `${this.noteId}_${this.name}`; + + super.beforeSaving(); + + this.utcDateModified = dateUtils.utcNowDateTime(); + } + + getPojo() { + return { + noteAncillaryId: this.noteAncillaryId, + noteId: this.noteId, + name: this.name, + mime: this.mime, + isProtected: !!this.isProtected, + contentCheckSum: this.contentCheckSum, + isDeleted: false, + utcDateModified: this.utcDateModified + }; + } + + getPojoToSave() { + const pojo = this.getPojo(); + delete pojo.content; // not getting persisted + + return pojo; + } +} + +module.exports = BNoteAncillary; diff --git a/src/becca/entity_constructor.js b/src/becca/entity_constructor.js index 17f186854..2b2cdf3c7 100644 --- a/src/becca/entity_constructor.js +++ b/src/becca/entity_constructor.js @@ -1,5 +1,6 @@ const BNote = require('./entities/bnote'); const BNoteRevision = require('./entities/bnote_revision'); +const BNoteAncillary = require("./entities/bnote_ancillary"); const BBranch = require('./entities/bbranch'); const BAttribute = require('./entities/battribute'); const BRecentNote = require('./entities/brecent_note'); @@ -13,6 +14,8 @@ const ENTITY_NAME_TO_ENTITY = { "note_contents": BNote, "note_revisions": BNoteRevision, "note_revision_contents": BNoteRevision, + "note_ancillaries": BNoteAncillary, + "note_ancillary_contents": BNoteAncillary, "recent_notes": BRecentNote, "etapi_tokens": BEtapiToken, "options": BOption diff --git a/src/public/app/services/froca_updater.js b/src/public/app/services/froca_updater.js index 64eb653c0..a858f64ea 100644 --- a/src/public/app/services/froca_updater.js +++ b/src/public/app/services/froca_updater.js @@ -36,7 +36,7 @@ async function processEntityChanges(entityChanges) { loadResults.addOption(ec.entity.name); } - else if (['etapi_tokens'].includes(ec.entityName)) { + else if (['etapi_tokens', 'note_ancillaries', 'note_ancillary_contents'].includes(ec.entityName)) { // NOOP } else { diff --git a/src/public/app/widgets/buttons/note_actions.js b/src/public/app/widgets/buttons/note_actions.js index fb2c19918..b75c118a5 100644 --- a/src/public/app/widgets/buttons/note_actions.js +++ b/src/public/app/widgets/buttons/note_actions.js @@ -28,6 +28,7 @@ const TPL = ` Re-render note Search in note Note source + Note ancillaries Open note externally Import files Export note diff --git a/src/public/app/widgets/note_detail.js b/src/public/app/widgets/note_detail.js index aaf2c568d..433a20a71 100644 --- a/src/public/app/widgets/note_detail.js +++ b/src/public/app/widgets/note_detail.js @@ -27,6 +27,7 @@ import NoteMapTypeWidget from "./type_widgets/note_map.js"; import WebViewTypeWidget from "./type_widgets/web_view.js"; import DocTypeWidget from "./type_widgets/doc.js"; import ContentWidgetTypeWidget from "./type_widgets/content_widget.js"; +import AncillariesTypeWidget from "./type_widgets/ancillaries.js"; const TPL = `
@@ -61,7 +62,8 @@ const typeWidgetClasses = { 'noteMap': NoteMapTypeWidget, 'webView': WebViewTypeWidget, 'doc': DocTypeWidget, - 'contentWidget': ContentWidgetTypeWidget + 'contentWidget': ContentWidgetTypeWidget, + 'ancillaries': AncillariesTypeWidget }; export default class NoteDetailWidget extends NoteContextAwareWidget { @@ -189,6 +191,8 @@ export default class NoteDetailWidget extends NoteContextAwareWidget { if (type === 'text' && this.noteContext.viewScope.viewMode === 'source') { type = 'readOnlyCode'; + } else if (this.noteContext.viewScope.viewMode === 'ancillaries') { + type = 'ancillaries'; } else if (type === 'text' && await this.noteContext.isReadOnly()) { type = 'readOnlyText'; } else if ((type === 'code' || type === 'mermaid') && await this.noteContext.isReadOnly()) { diff --git a/src/public/app/widgets/type_widgets/ancillaries.js b/src/public/app/widgets/type_widgets/ancillaries.js new file mode 100644 index 000000000..a88d39f53 --- /dev/null +++ b/src/public/app/widgets/type_widgets/ancillaries.js @@ -0,0 +1,79 @@ +import TypeWidget from "./type_widget.js"; +import server from "../../services/server.js"; + +const TPL = ` +
+ + +
+ Note ancillaries are pieces of data attached to a given note, providing ancillary support. + This view is useful for diagnostics. +
+ +
+
`; + +export default class AncillariesTypeWidget extends TypeWidget { + static getType() { return "ancillaries"; } + + doRender() { + this.$widget = $(TPL); + this.$list = this.$widget.find('.note-ancillary-list'); + + super.doRender(); + } + + async doRefresh(note) { + this.$list.empty(); + + const ancillaries = await server.get(`notes/${this.noteId}/ancillaries?includeContent=true`); + + if (ancillaries.length === 0) { + this.$list.html("This note has no ancillaries."); + + return; + } + + for (const ancillary of ancillaries) { + this.$list.append( + $('
') + .append( + $('

').append($('').text(ancillary.name)) + ) + .append( + $('') + .append( + $('') + .append($('
').text('Length:')) + .append($('').text(ancillary.contentLength)) + .append($('').text('MIME:')) + .append($('').text(ancillary.mime)) + .append($('').text('Date modified:')) + .append($('').text(ancillary.utcDateModified)) + ) + ) + .append( + $('
')
+                            .text(ancillary.content)
+                    )
+            );
+        }
+    }
+}
\ No newline at end of file
diff --git a/src/routes/api/notes.js b/src/routes/api/notes.js
index 2083a9858..be69c5aa5 100644
--- a/src/routes/api/notes.js
+++ b/src/routes/api/notes.js
@@ -54,10 +54,10 @@ function createNote(req) {
 }
 
 function updateNoteData(req) {
-    const {content} = req.body;
+    const {content, ancillaries} = req.body;
     const {noteId} = req.params;
 
-    return noteService.updateNoteData(noteId, content);
+    return noteService.updateNoteData(noteId, content, ancillaries);
 }
 
 function deleteNote(req) {
@@ -127,6 +127,49 @@ function setNoteTypeMime(req) {
     note.save();
 }
 
+function getNoteAncillaries(req) {
+    const includeContent = req.query.includeContent === 'true';
+    const {noteId} = req.params;
+
+    const note = becca.getNote(noteId);
+
+    if (!note) {
+        throw new NotFoundError(`Note '${noteId}' doesn't exist.`);
+    }
+
+    const noteAncillaries = note.getNoteAncillaries();
+
+    return noteAncillaries.map(ancillary => {
+       const pojo = ancillary.getPojo();
+
+       if (includeContent && utils.isStringNote(null, ancillary.mime)) {
+           pojo.content = ancillary.getContent()?.toString();
+           pojo.contentLength = pojo.content.length;
+
+           const MAX_ANCILLARY_LENGTH = 1_000_000;
+
+           if (pojo.content.length > MAX_ANCILLARY_LENGTH) {
+               pojo.content = pojo.content.substring(0, MAX_ANCILLARY_LENGTH);
+           }
+       }
+
+       return pojo;
+    });
+}
+
+function saveNoteAncillary(req) {
+    const {noteId, name} = req.params;
+    const {mime, content} = req.body;
+
+    const note = becca.getNote(noteId);
+
+    if (!note) {
+        throw new NotFoundError(`Note '${noteId}' doesn't exist.`);
+    }
+
+    note.saveNoteAncillary(name, mime, content);
+}
+
 function getRelationMap(req) {
     const {relationMapNoteId, noteIds} = req.body;
 
@@ -340,5 +383,7 @@ module.exports = {
     eraseDeletedNotesNow,
     getDeleteNotesPreview,
     uploadModifiedFile,
-    forceSaveNoteRevision
+    forceSaveNoteRevision,
+    getNoteAncillaries,
+    saveNoteAncillary
 };
diff --git a/src/routes/api/sync.js b/src/routes/api/sync.js
index e2947f096..3600aa743 100644
--- a/src/routes/api/sync.js
+++ b/src/routes/api/sync.js
@@ -114,6 +114,14 @@ function forceNoteSync(req) {
         entityChangesService.moveEntityChangeToTop('note_revision_contents', noteRevisionId);
     }
 
+    for (const noteAncillaryId of sql.getColumn("SELECT noteAncillaryId FROM note_ancillaries WHERE noteId = ?", [noteId])) {
+        sql.execute(`UPDATE note_ancillaries SET utcDateModified = ? WHERE noteAncillaryId = ?`, [now, noteAncillaryId]);
+        entityChangesService.moveEntityChangeToTop('note_ancillaries', noteAncillaryId);
+
+        sql.execute(`UPDATE note_ancillary_contents SET utcDateModified = ? WHERE noteAncillaryId = ?`, [now, noteAncillaryId]);
+        entityChangesService.moveEntityChangeToTop('note_ancillary_contents', noteAncillaryId);
+    }
+
     log.info(`Forcing note sync for ${noteId}`);
 
     // not awaiting for the job to finish (will probably take a long time)
diff --git a/src/routes/routes.js b/src/routes/routes.js
index cdca32975..b6b5d4e39 100644
--- a/src/routes/routes.js
+++ b/src/routes/routes.js
@@ -126,6 +126,8 @@ function register(app) {
     apiRoute(PUT, '/api/notes/:noteId/sort-children', notesApiRoute.sortChildNotes);
     apiRoute(PUT, '/api/notes/:noteId/protect/:isProtected', notesApiRoute.protectNote);
     apiRoute(PUT, '/api/notes/:noteId/type', notesApiRoute.setNoteTypeMime);
+    apiRoute(GET, '/api/notes/:noteId/ancillaries', notesApiRoute.getNoteAncillaries);
+    apiRoute(PUT, '/api/notes/:noteId/ancillaries/:name', notesApiRoute.saveNoteAncillary);
     apiRoute(GET, '/api/notes/:noteId/revisions', noteRevisionsApiRoute.getNoteRevisions);
     apiRoute(DELETE, '/api/notes/:noteId/revisions', noteRevisionsApiRoute.eraseAllNoteRevisions);
     apiRoute(GET, '/api/notes/:noteId/revisions/:noteRevisionId', noteRevisionsApiRoute.getNoteRevision);
diff --git a/src/services/app_info.js b/src/services/app_info.js
index 3f5f02f3b..5b7582d61 100644
--- a/src/services/app_info.js
+++ b/src/services/app_info.js
@@ -4,8 +4,8 @@ const build = require('./build');
 const packageJson = require('../../package');
 const {TRILIUM_DATA_DIR} = require('./data_dir');
 
-const APP_DB_VERSION = 213;
-const SYNC_VERSION = 29;
+const APP_DB_VERSION = 214;
+const SYNC_VERSION = 30;
 const CLIPPER_PROTOCOL_VERSION = "1.0";
 
 module.exports = {
diff --git a/src/services/consistency_checks.js b/src/services/consistency_checks.js
index 3fecf607c..6bb6e066d 100644
--- a/src/services/consistency_checks.js
+++ b/src/services/consistency_checks.js
@@ -213,6 +213,25 @@ class ConsistencyChecks {
                     logError(`Relation '${attributeId}' references missing note '${noteId}'`)
                 }
             });
+
+        this.findAndFixIssues(`
+                    SELECT noteAncillaryId, note_ancillaries.noteId AS noteId
+                    FROM note_ancillaries
+                      LEFT JOIN notes USING (noteId)
+                    WHERE notes.noteId IS NULL
+                      AND note_ancillaries.isDeleted = 0`,
+            ({noteAncillaryId, noteId}) => {
+                if (this.autoFix) {
+                    const noteAncillary = becca.getNoteAncillary(noteAncillaryId);
+                    noteAncillary.markAsDeleted();
+
+                    this.reloadNeeded = false;
+
+                    logFix(`Note ancillary '${noteAncillaryId}' has been deleted since it references missing note '${noteId}'`);
+                } else {
+                    logError(`Note ancillary '${noteAncillaryId}' references missing note '${noteId}'`);
+                }
+            });
     }
 
     findExistencyIssues() {
@@ -320,6 +339,26 @@ class ConsistencyChecks {
                     logError(`Duplicate branches for note '${noteId}' and parent '${parentNoteId}'`);
                 }
             });
+
+        this.findAndFixIssues(`
+                    SELECT noteAncillaryId,
+                           note_ancillaries.noteId AS noteId
+                    FROM note_ancillaries
+                      JOIN notes USING (noteId)
+                    WHERE notes.isDeleted = 1
+                      AND note_ancillaries.isDeleted = 0`,
+            ({noteAncillaryId, noteId}) => {
+                if (this.autoFix) {
+                    const noteAncillary = becca.getNoteAncillary(noteAncillaryId);
+                    noteAncillary.markAsDeleted();
+
+                    this.reloadNeeded = false;
+
+                    logFix(`Note ancillary '${noteAncillaryId}' has been deleted since associated note '${noteId}' is deleted.`);
+                } else {
+                    logError(`Note ancillary '${noteAncillaryId}' is not deleted even though associated note '${noteId}' is deleted.`)
+                }
+            });
     }
 
     findLogicIssues() {
@@ -620,6 +659,8 @@ class ConsistencyChecks {
         this.runEntityChangeChecks("note_contents", "noteId");
         this.runEntityChangeChecks("note_revisions", "noteRevisionId");
         this.runEntityChangeChecks("note_revision_contents", "noteRevisionId");
+        this.runEntityChangeChecks("note_ancillaries", "noteAncillaryId");
+        this.runEntityChangeChecks("note_ancillary_contents", "noteAncillaryId");
         this.runEntityChangeChecks("branches", "branchId");
         this.runEntityChangeChecks("attributes", "attributeId");
         this.runEntityChangeChecks("etapi_tokens", "etapiTokenId");
@@ -715,7 +756,7 @@ class ConsistencyChecks {
             return `${tableName}: ${count}`;
         }
 
-        const tables = [ "notes", "note_revisions", "branches", "attributes", "etapi_tokens" ];
+        const tables = [ "notes", "note_revisions", "note_ancillaries", "branches", "attributes", "etapi_tokens" ];
 
         log.info(`Table counts: ${tables.map(tableName => getTableRowCount(tableName)).join(", ")}`);
     }
diff --git a/src/services/entity_changes.js b/src/services/entity_changes.js
index 6f9d5034d..2c3991d81 100644
--- a/src/services/entity_changes.js
+++ b/src/services/entity_changes.js
@@ -151,6 +151,8 @@ function fillAllEntityChanges() {
         fillEntityChanges("branches", "branchId");
         fillEntityChanges("note_revisions", "noteRevisionId");
         fillEntityChanges("note_revision_contents", "noteRevisionId");
+        fillEntityChanges("note_ancillaries", "noteAncillaryId");
+        fillEntityChanges("note_ancillary_contents", "noteAncillaryId");
         fillEntityChanges("attributes", "attributeId");
         fillEntityChanges("etapi_tokens", "etapiTokenId");
         fillEntityChanges("options", "name", 'isSynced = 1');
diff --git a/src/services/export/zip.js b/src/services/export/zip.js
index c58afca9f..8196c9e84 100644
--- a/src/services/export/zip.js
+++ b/src/services/export/zip.js
@@ -170,6 +170,24 @@ async function exportToZip(taskContext, branch, format, res, setHeaders = true)
             meta.dataFileName = getDataFileName(note.type, note.mime, baseFileName, existingFileNames);
         }
 
+        const ancillaries = note.getNoteAncillaries();
+
+        if (ancillaries.length > 0) {
+            meta.ancillaries = ancillaries
+                .filter(ancillary => ["canvasSvg", "mermaidSvg"].includes(ancillary.name))
+                .map(ancillary => ({
+
+                name: ancillary.name,
+                mime: ancillary.mime,
+                dataFileName: getDataFileName(
+                    null,
+                    ancillary.mime,
+                    baseFileName + "_" + ancillary.name,
+                    existingFileNames
+                )
+            }));
+        }
+
         if (childBranches.length > 0) {
             meta.dirFileName = getUniqueFilename(existingFileNames, baseFileName);
             meta.children = [];
@@ -319,6 +337,16 @@ ${markdownContent}`;
 
         taskContext.increaseProgressCount();
 
+        for (const ancillaryMeta of noteMeta.ancillaries || []) {
+            const noteAncillary = note.getNoteAncillaryByName(ancillaryMeta.name);
+            const content = noteAncillary.getContent();
+
+            archive.append(content, {
+                name: filePathPrefix + ancillaryMeta.dataFileName,
+                date: dateUtils.parseDateTime(note.utcDateModified)
+            });
+        }
+
         if (noteMeta.children && noteMeta.children.length > 0) {
             const directoryPath = filePathPrefix + noteMeta.dirFileName;
 
diff --git a/src/services/import/zip.js b/src/services/import/zip.js
index abe3b58a9..64df89e28 100644
--- a/src/services/import/zip.js
+++ b/src/services/import/zip.js
@@ -14,6 +14,7 @@ const treeService = require("../tree");
 const yauzl = require("yauzl");
 const htmlSanitizer = require('../html_sanitizer');
 const becca = require("../../becca/becca");
+const BNoteAncillary = require("../../becca/entities/bnote_ancillary");
 
 /**
  * @param {TaskContext} taskContext
@@ -64,6 +65,7 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
         };
 
         let parent;
+        let ancillaryMeta = false;
 
         for (const segment of pathSegments) {
             if (!cursor || !cursor.children || cursor.children.length === 0) {
@@ -72,11 +74,28 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
 
             parent = cursor;
             cursor = parent.children.find(file => file.dataFileName === segment || file.dirFileName === segment);
+
+            if (!cursor) {
+                for (const file of parent.children) {
+                    for (const ancillary of file.ancillaries || []) {
+                        if (ancillary.dataFileName === segment) {
+                            cursor = file;
+                            ancillaryMeta = ancillary;
+                            break;
+                        }
+                    }
+
+                    if (cursor) {
+                        break;
+                    }
+                }
+            }
         }
 
         return {
             parentNoteMeta: parent,
-            noteMeta: cursor
+            noteMeta: cursor,
+            ancillaryMeta
         };
     }
 
@@ -351,7 +370,7 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
     }
 
     function saveNote(filePath, content) {
-        const {parentNoteMeta, noteMeta} = getMeta(filePath);
+        const {parentNoteMeta, noteMeta, ancillaryMeta} = getMeta(filePath);
 
         if (noteMeta?.noImport) {
             return;
@@ -359,6 +378,17 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
 
         const noteId = getNoteId(noteMeta, filePath);
 
+        if (ancillaryMeta) {
+            const noteAncillary = new BNoteAncillary({
+                noteId,
+                name: ancillaryMeta.name,
+                mime: ancillaryMeta.mime
+            });
+
+            noteAncillary.setContent(content);
+            return;
+        }
+
         const parentNoteId = getParentNoteId(filePath, parentNoteMeta);
 
         if (!parentNoteId) {
diff --git a/src/services/note_ancillaries.js b/src/services/note_ancillaries.js
new file mode 100644
index 000000000..9336b14bb
--- /dev/null
+++ b/src/services/note_ancillaries.js
@@ -0,0 +1,37 @@
+const protectedSession = require("./protected_session");
+const log = require("./log");
+
+/**
+ * @param {BNote} note
+ */
+function protectNoteAncillaries(note) {
+    for (const noteAncillary of note.getNoteAncillaries()) {
+        if (note.isProtected !== noteAncillary.isProtected) {
+            if (!protectedSession.isProtectedSessionAvailable()) {
+                log.error("Protected session is not available to fix note ancillaries.");
+
+                return;
+            }
+
+            try {
+                const content = noteAncillary.getContent();
+
+                noteAncillary.isProtected = note.isProtected;
+
+                // this will force de/encryption
+                noteAncillary.setContent(content);
+
+                noteAncillary.save();
+            }
+            catch (e) {
+                log.error(`Could not un/protect note ancillary ID = ${noteAncillary.noteAncillaryId}`);
+
+                throw e;
+            }
+        }
+    }
+}
+
+module.exports = {
+    protectNoteAncillaries
+}
diff --git a/src/services/sync.js b/src/services/sync.js
index 80adc235f..243266cf6 100644
--- a/src/services/sync.js
+++ b/src/services/sync.js
@@ -321,7 +321,7 @@ function getEntityChangeRow(entityName, entityId) {
             throw new Error(`Entity ${entityName} ${entityId} not found.`);
         }
 
-        if (['note_contents', 'note_revision_contents'].includes(entityName) && entity.content !== null) {
+        if (['note_contents', 'note_revision_contents', 'note_ancillary_contents'].includes(entityName) && entity.content !== null) {
             if (typeof entity.content === 'string') {
                 entity.content = Buffer.from(entity.content, 'UTF-8');
             }
diff --git a/src/services/sync_update.js b/src/services/sync_update.js
index d650fdb0b..50fb29a0e 100644
--- a/src/services/sync_update.js
+++ b/src/services/sync_update.js
@@ -64,7 +64,7 @@ function updateNormalEntity(remoteEntityChange, remoteEntityRow, instanceId) {
         || localEntityChange.utcDateChanged < remoteEntityChange.utcDateChanged
         || localEntityChange.hash !== remoteEntityChange.hash // sync error, we should still update
     ) {
-        if (['note_contents', 'note_revision_contents'].includes(remoteEntityChange.entityName)) {
+        if (['note_contents', 'note_revision_contents', 'note_ancillary_contents'].includes(remoteEntityChange.entityName)) {
             remoteEntityRow.content = handleContent(remoteEntityRow.content);
         }
 
@@ -115,7 +115,9 @@ function eraseEntity(entityChange, instanceId) {
         "branches",
         "attributes",
         "note_revisions",
-        "note_revision_contents"
+        "note_revision_contents",
+        "note_ancillaries",
+        "note_ancillary_contents"
     ];
 
     if (!entityNames.includes(entityName)) {