mirror of
https://github.com/zadam/trilium.git
synced 2025-06-06 18:08:33 +02:00
Merge pull request #28 from TriliumNext/feature/typescript_backend_4
Convert backend to TypeScript (50% -> 64%)
This commit is contained in:
commit
6ac3c172b1
120
package-lock.json
generated
120
package-lock.json
generated
@ -88,16 +88,22 @@
|
|||||||
"trilium": "src/www.js"
|
"trilium": "src/www.js"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/archiver": "^6.0.2",
|
||||||
"@types/better-sqlite3": "^7.6.9",
|
"@types/better-sqlite3": "^7.6.9",
|
||||||
"@types/cls-hooked": "^4.3.8",
|
"@types/cls-hooked": "^4.3.8",
|
||||||
"@types/escape-html": "^1.0.4",
|
"@types/escape-html": "^1.0.4",
|
||||||
"@types/express": "^4.17.21",
|
"@types/express": "^4.17.21",
|
||||||
|
"@types/html": "^1.0.4",
|
||||||
"@types/ini": "^4.1.0",
|
"@types/ini": "^4.1.0",
|
||||||
"@types/jsdom": "^21.1.6",
|
"@types/jsdom": "^21.1.6",
|
||||||
"@types/mime-types": "^2.1.4",
|
"@types/mime-types": "^2.1.4",
|
||||||
"@types/node": "^20.11.19",
|
"@types/node": "^20.11.19",
|
||||||
"@types/sanitize-html": "^2.11.0",
|
"@types/sanitize-html": "^2.11.0",
|
||||||
|
"@types/sax": "^1.2.7",
|
||||||
|
"@types/stream-throttle": "^0.1.4",
|
||||||
|
"@types/turndown": "^5.0.4",
|
||||||
"@types/ws": "^8.5.10",
|
"@types/ws": "^8.5.10",
|
||||||
|
"@types/xml2js": "^0.4.14",
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"electron": "25.9.8",
|
"electron": "25.9.8",
|
||||||
"electron-builder": "24.13.3",
|
"electron-builder": "24.13.3",
|
||||||
@ -1169,6 +1175,15 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-21.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-21.0.0.tgz",
|
||||||
"integrity": "sha512-qVfOiFh0U8ZSkLgA6tf7kj2MciqRbSCWaJZRwftVO7UbtVDNsZAXpWXqvCDtIefvjC83UJB+vHTDOGm5ibXjEA=="
|
"integrity": "sha512-qVfOiFh0U8ZSkLgA6tf7kj2MciqRbSCWaJZRwftVO7UbtVDNsZAXpWXqvCDtIefvjC83UJB+vHTDOGm5ibXjEA=="
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/archiver": {
|
||||||
|
"version": "6.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/archiver/-/archiver-6.0.2.tgz",
|
||||||
|
"integrity": "sha512-KmROQqbQzKGuaAbmK+ZcytkJ51+YqDa7NmbXjmtC5YBLSyQYo21YaUnQ3HbaPFKL1ooo6RQ6OPYPIDyxfpDDXw==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/readdir-glob": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/better-sqlite3": {
|
"node_modules/@types/better-sqlite3": {
|
||||||
"version": "7.6.9",
|
"version": "7.6.9",
|
||||||
"resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.9.tgz",
|
"resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.9.tgz",
|
||||||
@ -1318,6 +1333,12 @@
|
|||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/html": {
|
||||||
|
"version": "1.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/html/-/html-1.0.4.tgz",
|
||||||
|
"integrity": "sha512-Wb1ymSAftCLxhc3D6vS0Ike/0xg7W6c+DQxAkerU6pD7C8CMzTYwvrwnlcrTfsVO/nMelB9KOKIT7+N5lOeQUg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/@types/http-cache-semantics": {
|
"node_modules/@types/http-cache-semantics": {
|
||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz",
|
||||||
@ -1468,6 +1489,15 @@
|
|||||||
"integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==",
|
"integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/readdir-glob": {
|
||||||
|
"version": "1.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/readdir-glob/-/readdir-glob-1.1.5.tgz",
|
||||||
|
"integrity": "sha512-raiuEPUYqXu+nvtY2Pe8s8FEmZ3x5yAH4VkLdihcPdalvsHltomrRC9BzuStrJ9yk06470hS0Crw0f1pXqD+Hg==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/responselike": {
|
"node_modules/@types/responselike": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz",
|
||||||
@ -1559,6 +1589,15 @@
|
|||||||
"entities": "^4.4.0"
|
"entities": "^4.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/sax": {
|
||||||
|
"version": "1.2.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/sax/-/sax-1.2.7.tgz",
|
||||||
|
"integrity": "sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/send": {
|
"node_modules/@types/send": {
|
||||||
"version": "0.17.4",
|
"version": "0.17.4",
|
||||||
"resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz",
|
"resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz",
|
||||||
@ -1580,12 +1619,27 @@
|
|||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/stream-throttle": {
|
||||||
|
"version": "0.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/stream-throttle/-/stream-throttle-0.1.4.tgz",
|
||||||
|
"integrity": "sha512-VxXIHGjVuK8tYsVm60rIQMmF/0xguCeen5OmK5S4Y6K64A+z+y4/GI6anRnVzaUZaJB9Ah9IfbDcO0o1gZCc/w==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/tough-cookie": {
|
"node_modules/@types/tough-cookie": {
|
||||||
"version": "4.0.5",
|
"version": "4.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz",
|
||||||
"integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==",
|
"integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/turndown": {
|
||||||
|
"version": "5.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/turndown/-/turndown-5.0.4.tgz",
|
||||||
|
"integrity": "sha512-28GI33lCCkU4SGH1GvjDhFgOVr+Tym4PXGBIU1buJUa6xQolniPArtUT+kv42RR2N9MsMLInkr904Aq+ESHBJg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/@types/unist": {
|
"node_modules/@types/unist": {
|
||||||
"version": "2.0.10",
|
"version": "2.0.10",
|
||||||
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz",
|
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz",
|
||||||
@ -1607,6 +1661,15 @@
|
|||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/xml2js": {
|
||||||
|
"version": "0.4.14",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.14.tgz",
|
||||||
|
"integrity": "sha512-4YnrRemBShWRO2QjvUin8ESA41rH+9nQGLUGZV/1IDhi3SL9OhdpNC/MrulTWuptXKwhx/aDxE7toV0f/ypIXQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/yauzl": {
|
"node_modules/@types/yauzl": {
|
||||||
"version": "2.9.2",
|
"version": "2.9.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.2.tgz",
|
||||||
@ -14084,6 +14147,15 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-21.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@tweenjs/tween.js/-/tween.js-21.0.0.tgz",
|
||||||
"integrity": "sha512-qVfOiFh0U8ZSkLgA6tf7kj2MciqRbSCWaJZRwftVO7UbtVDNsZAXpWXqvCDtIefvjC83UJB+vHTDOGm5ibXjEA=="
|
"integrity": "sha512-qVfOiFh0U8ZSkLgA6tf7kj2MciqRbSCWaJZRwftVO7UbtVDNsZAXpWXqvCDtIefvjC83UJB+vHTDOGm5ibXjEA=="
|
||||||
},
|
},
|
||||||
|
"@types/archiver": {
|
||||||
|
"version": "6.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/archiver/-/archiver-6.0.2.tgz",
|
||||||
|
"integrity": "sha512-KmROQqbQzKGuaAbmK+ZcytkJ51+YqDa7NmbXjmtC5YBLSyQYo21YaUnQ3HbaPFKL1ooo6RQ6OPYPIDyxfpDDXw==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/readdir-glob": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/better-sqlite3": {
|
"@types/better-sqlite3": {
|
||||||
"version": "7.6.9",
|
"version": "7.6.9",
|
||||||
"resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.9.tgz",
|
"resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.9.tgz",
|
||||||
@ -14233,6 +14305,12 @@
|
|||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@types/html": {
|
||||||
|
"version": "1.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/html/-/html-1.0.4.tgz",
|
||||||
|
"integrity": "sha512-Wb1ymSAftCLxhc3D6vS0Ike/0xg7W6c+DQxAkerU6pD7C8CMzTYwvrwnlcrTfsVO/nMelB9KOKIT7+N5lOeQUg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"@types/http-cache-semantics": {
|
"@types/http-cache-semantics": {
|
||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz",
|
||||||
@ -14376,6 +14454,15 @@
|
|||||||
"integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==",
|
"integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"@types/readdir-glob": {
|
||||||
|
"version": "1.1.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/readdir-glob/-/readdir-glob-1.1.5.tgz",
|
||||||
|
"integrity": "sha512-raiuEPUYqXu+nvtY2Pe8s8FEmZ3x5yAH4VkLdihcPdalvsHltomrRC9BzuStrJ9yk06470hS0Crw0f1pXqD+Hg==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/responselike": {
|
"@types/responselike": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz",
|
||||||
@ -14444,6 +14531,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@types/sax": {
|
||||||
|
"version": "1.2.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/sax/-/sax-1.2.7.tgz",
|
||||||
|
"integrity": "sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/send": {
|
"@types/send": {
|
||||||
"version": "0.17.4",
|
"version": "0.17.4",
|
||||||
"resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz",
|
"resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz",
|
||||||
@ -14465,12 +14561,27 @@
|
|||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@types/stream-throttle": {
|
||||||
|
"version": "0.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/stream-throttle/-/stream-throttle-0.1.4.tgz",
|
||||||
|
"integrity": "sha512-VxXIHGjVuK8tYsVm60rIQMmF/0xguCeen5OmK5S4Y6K64A+z+y4/GI6anRnVzaUZaJB9Ah9IfbDcO0o1gZCc/w==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/tough-cookie": {
|
"@types/tough-cookie": {
|
||||||
"version": "4.0.5",
|
"version": "4.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz",
|
||||||
"integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==",
|
"integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"@types/turndown": {
|
||||||
|
"version": "5.0.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/turndown/-/turndown-5.0.4.tgz",
|
||||||
|
"integrity": "sha512-28GI33lCCkU4SGH1GvjDhFgOVr+Tym4PXGBIU1buJUa6xQolniPArtUT+kv42RR2N9MsMLInkr904Aq+ESHBJg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"@types/unist": {
|
"@types/unist": {
|
||||||
"version": "2.0.10",
|
"version": "2.0.10",
|
||||||
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz",
|
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz",
|
||||||
@ -14492,6 +14603,15 @@
|
|||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@types/xml2js": {
|
||||||
|
"version": "0.4.14",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.14.tgz",
|
||||||
|
"integrity": "sha512-4YnrRemBShWRO2QjvUin8ESA41rH+9nQGLUGZV/1IDhi3SL9OhdpNC/MrulTWuptXKwhx/aDxE7toV0f/ypIXQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/yauzl": {
|
"@types/yauzl": {
|
||||||
"version": "2.9.2",
|
"version": "2.9.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.2.tgz",
|
||||||
|
@ -109,16 +109,22 @@
|
|||||||
"yauzl": "3.1.2"
|
"yauzl": "3.1.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/archiver": "^6.0.2",
|
||||||
"@types/better-sqlite3": "^7.6.9",
|
"@types/better-sqlite3": "^7.6.9",
|
||||||
"@types/cls-hooked": "^4.3.8",
|
"@types/cls-hooked": "^4.3.8",
|
||||||
"@types/escape-html": "^1.0.4",
|
"@types/escape-html": "^1.0.4",
|
||||||
"@types/express": "^4.17.21",
|
"@types/express": "^4.17.21",
|
||||||
|
"@types/html": "^1.0.4",
|
||||||
"@types/ini": "^4.1.0",
|
"@types/ini": "^4.1.0",
|
||||||
"@types/jsdom": "^21.1.6",
|
"@types/jsdom": "^21.1.6",
|
||||||
"@types/mime-types": "^2.1.4",
|
"@types/mime-types": "^2.1.4",
|
||||||
"@types/node": "^20.11.19",
|
"@types/node": "^20.11.19",
|
||||||
"@types/sanitize-html": "^2.11.0",
|
"@types/sanitize-html": "^2.11.0",
|
||||||
|
"@types/sax": "^1.2.7",
|
||||||
|
"@types/stream-throttle": "^0.1.4",
|
||||||
|
"@types/turndown": "^5.0.4",
|
||||||
"@types/ws": "^8.5.10",
|
"@types/ws": "^8.5.10",
|
||||||
|
"@types/xml2js": "^0.4.14",
|
||||||
"cross-env": "7.0.3",
|
"cross-env": "7.0.3",
|
||||||
"electron": "25.9.8",
|
"electron": "25.9.8",
|
||||||
"electron-builder": "24.13.3",
|
"electron-builder": "24.13.3",
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
const anonymizationService = require('./services/anonymization');
|
import anonymizationService = require('./services/anonymization');
|
||||||
const sqlInit = require('./services/sql_init');
|
import sqlInit = require('./services/sql_init');
|
||||||
require('./becca/entity_constructor');
|
require('./becca/entity_constructor');
|
||||||
|
|
||||||
sqlInit.dbReady.then(async () => {
|
sqlInit.dbReady.then(async () => {
|
||||||
@ -16,7 +16,7 @@ sqlInit.dbReady.then(async () => {
|
|||||||
console.log("Anonymization failed.");
|
console.log("Anonymization failed.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e: any) {
|
||||||
console.error(e.message, e.stack);
|
console.error(e.message, e.stack);
|
||||||
}
|
}
|
||||||
|
|
10
src/app.js
10
src/app.js
@ -26,10 +26,10 @@ app.use(helmet({
|
|||||||
crossOriginEmbedderPolicy: false
|
crossOriginEmbedderPolicy: false
|
||||||
}));
|
}));
|
||||||
|
|
||||||
app.use(express.text({limit: '500mb'}));
|
app.use(express.text({ limit: '500mb' }));
|
||||||
app.use(express.json({limit: '500mb'}));
|
app.use(express.json({ limit: '500mb' }));
|
||||||
app.use(express.raw({limit: '500mb'}));
|
app.use(express.raw({ limit: '500mb' }));
|
||||||
app.use(express.urlencoded({extended: false}));
|
app.use(express.urlencoded({ extended: false }));
|
||||||
app.use(cookieParser());
|
app.use(cookieParser());
|
||||||
app.use(express.static(path.join(__dirname, 'public/root')));
|
app.use(express.static(path.join(__dirname, 'public/root')));
|
||||||
app.use(`/manifest.webmanifest`, express.static(path.join(__dirname, 'public/manifest.webmanifest')));
|
app.use(`/manifest.webmanifest`, express.static(path.join(__dirname, 'public/manifest.webmanifest')));
|
||||||
@ -49,7 +49,7 @@ require('./services/sync');
|
|||||||
require('./services/backup');
|
require('./services/backup');
|
||||||
|
|
||||||
// trigger consistency checks timer
|
// trigger consistency checks timer
|
||||||
require('./services/consistency_checks.js');
|
require('./services/consistency_checks');
|
||||||
|
|
||||||
require('./services/scheduler.js');
|
require('./services/scheduler.js');
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ interface ContentOpts {
|
|||||||
*/
|
*/
|
||||||
abstract class AbstractBeccaEntity<T extends AbstractBeccaEntity<T>> {
|
abstract class AbstractBeccaEntity<T extends AbstractBeccaEntity<T>> {
|
||||||
|
|
||||||
protected utcDateModified?: string;
|
utcDateModified?: string;
|
||||||
protected dateCreated?: string;
|
protected dateCreated?: string;
|
||||||
protected dateModified?: string;
|
protected dateModified?: string;
|
||||||
|
|
||||||
|
@ -62,7 +62,7 @@ export interface BlobRow {
|
|||||||
utcDateModified: string;
|
utcDateModified: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AttributeType = "label" | "relation";
|
export type AttributeType = "label" | "relation" | "label-definition" | "relation-definition";
|
||||||
|
|
||||||
export interface AttributeRow {
|
export interface AttributeRow {
|
||||||
attributeId?: string;
|
attributeId?: string;
|
||||||
|
@ -7,8 +7,8 @@ const TaskContext = require('../services/task_context');
|
|||||||
const v = require('./validators.js');
|
const v = require('./validators.js');
|
||||||
const searchService = require('../services/search/services/search');
|
const searchService = require('../services/search/services/search');
|
||||||
const SearchContext = require('../services/search/search_context');
|
const SearchContext = require('../services/search/search_context');
|
||||||
const zipExportService = require('../services/export/zip.js');
|
const zipExportService = require('../services/export/zip');
|
||||||
const zipImportService = require('../services/import/zip.js');
|
const zipImportService = require('../services/import/zip');
|
||||||
|
|
||||||
function register(router) {
|
function register(router) {
|
||||||
eu.route(router, 'get', '/etapi/notes', (req, res, next) => {
|
eu.route(router, 'get', '/etapi/notes', (req, res, next) => {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
const specialNotesService = require('../services/special_notes.js');
|
const specialNotesService = require('../services/special_notes');
|
||||||
const dateNotesService = require('../services/date_notes');
|
const dateNotesService = require('../services/date_notes');
|
||||||
const eu = require('./etapi_utils');
|
const eu = require('./etapi_utils');
|
||||||
const mappers = require('./mappers.js');
|
const mappers = require('./mappers.js');
|
||||||
@ -17,7 +17,7 @@ function isValidDate(date) {
|
|||||||
|
|
||||||
function register(router) {
|
function register(router) {
|
||||||
eu.route(router, 'get', '/etapi/inbox/:date', (req, res, next) => {
|
eu.route(router, 'get', '/etapi/inbox/:date', (req, res, next) => {
|
||||||
const {date} = req.params;
|
const { date } = req.params;
|
||||||
|
|
||||||
if (!isValidDate(date)) {
|
if (!isValidDate(date)) {
|
||||||
throw getDateInvalidError(date);
|
throw getDateInvalidError(date);
|
||||||
@ -28,7 +28,7 @@ function register(router) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
eu.route(router, 'get', '/etapi/calendar/days/:date', (req, res, next) => {
|
eu.route(router, 'get', '/etapi/calendar/days/:date', (req, res, next) => {
|
||||||
const {date} = req.params;
|
const { date } = req.params;
|
||||||
|
|
||||||
if (!isValidDate(date)) {
|
if (!isValidDate(date)) {
|
||||||
throw getDateInvalidError(date);
|
throw getDateInvalidError(date);
|
||||||
@ -39,7 +39,7 @@ function register(router) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
eu.route(router, 'get', '/etapi/calendar/weeks/:date', (req, res, next) => {
|
eu.route(router, 'get', '/etapi/calendar/weeks/:date', (req, res, next) => {
|
||||||
const {date} = req.params;
|
const { date } = req.params;
|
||||||
|
|
||||||
if (!isValidDate(date)) {
|
if (!isValidDate(date)) {
|
||||||
throw getDateInvalidError(date);
|
throw getDateInvalidError(date);
|
||||||
@ -50,7 +50,7 @@ function register(router) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
eu.route(router, 'get', '/etapi/calendar/months/:month', (req, res, next) => {
|
eu.route(router, 'get', '/etapi/calendar/months/:month', (req, res, next) => {
|
||||||
const {month} = req.params;
|
const { month } = req.params;
|
||||||
|
|
||||||
if (!/[0-9]{4}-[0-9]{2}/.test(month)) {
|
if (!/[0-9]{4}-[0-9]{2}/.test(month)) {
|
||||||
throw getMonthInvalidError(month);
|
throw getMonthInvalidError(month);
|
||||||
@ -61,7 +61,7 @@ function register(router) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
eu.route(router, 'get', '/etapi/calendar/years/:year', (req, res, next) => {
|
eu.route(router, 'get', '/etapi/calendar/years/:year', (req, res, next) => {
|
||||||
const {year} = req.params;
|
const { year } = req.params;
|
||||||
|
|
||||||
if (!/[0-9]{4}/.test(year)) {
|
if (!/[0-9]{4}/.test(year)) {
|
||||||
throw getYearInvalidError(year);
|
throw getYearInvalidError(year);
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
const becca = require('../../becca/becca');
|
const becca = require('../../becca/becca');
|
||||||
const blobService = require('../../services/blob');
|
const blobService = require('../../services/blob');
|
||||||
const ValidationError = require('../../errors/validation_error');
|
const ValidationError = require('../../errors/validation_error');
|
||||||
const imageService = require("../../services/image.js");
|
const imageService = require("../../services/image");
|
||||||
|
|
||||||
function getAttachmentBlob(req) {
|
function getAttachmentBlob(req) {
|
||||||
const preview = req.query.preview === 'true';
|
const preview = req.query.preview === 'true';
|
||||||
|
@ -5,7 +5,7 @@ const cloneService = require('../../services/cloning');
|
|||||||
const noteService = require('../../services/notes');
|
const noteService = require('../../services/notes');
|
||||||
const dateNoteService = require('../../services/date_notes');
|
const dateNoteService = require('../../services/date_notes');
|
||||||
const dateUtils = require('../../services/date_utils');
|
const dateUtils = require('../../services/date_utils');
|
||||||
const imageService = require('../../services/image.js');
|
const imageService = require('../../services/image');
|
||||||
const appInfo = require('../../services/app_info');
|
const appInfo = require('../../services/app_info');
|
||||||
const ws = require('../../services/ws');
|
const ws = require('../../services/ws');
|
||||||
const log = require('../../services/log');
|
const log = require('../../services/log');
|
||||||
|
@ -4,7 +4,7 @@ const sql = require('../../services/sql');
|
|||||||
const log = require('../../services/log');
|
const log = require('../../services/log');
|
||||||
const backupService = require('../../services/backup');
|
const backupService = require('../../services/backup');
|
||||||
const anonymizationService = require('../../services/anonymization');
|
const anonymizationService = require('../../services/anonymization');
|
||||||
const consistencyChecksService = require('../../services/consistency_checks.js');
|
const consistencyChecksService = require('../../services/consistency_checks');
|
||||||
|
|
||||||
function getExistingBackups() {
|
function getExistingBackups() {
|
||||||
return backupService.getExistingBackups();
|
return backupService.getExistingBackups();
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const zipExportService = require('../../services/export/zip.js');
|
const zipExportService = require('../../services/export/zip');
|
||||||
const singleExportService = require('../../services/export/single.js');
|
const singleExportService = require('../../services/export/single');
|
||||||
const opmlExportService = require('../../services/export/opml.js');
|
const opmlExportService = require('../../services/export/opml');
|
||||||
const becca = require('../../becca/becca');
|
const becca = require('../../becca/becca');
|
||||||
const TaskContext = require('../../services/task_context');
|
const TaskContext = require('../../services/task_context');
|
||||||
const log = require('../../services/log');
|
const log = require('../../services/log');
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const imageService = require('../../services/image.js');
|
const imageService = require('../../services/image');
|
||||||
const becca = require('../../becca/becca');
|
const becca = require('../../becca/becca');
|
||||||
const RESOURCE_DIR = require('../../services/resource_dir').RESOURCE_DIR;
|
const RESOURCE_DIR = require('../../services/resource_dir').RESOURCE_DIR;
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const enexImportService = require('../../services/import/enex.js');
|
const enexImportService = require('../../services/import/enex');
|
||||||
const opmlImportService = require('../../services/import/opml.js');
|
const opmlImportService = require('../../services/import/opml');
|
||||||
const zipImportService = require('../../services/import/zip.js');
|
const zipImportService = require('../../services/import/zip');
|
||||||
const singleImportService = require('../../services/import/single.js');
|
const singleImportService = require('../../services/import/single');
|
||||||
const cls = require('../../services/cls');
|
const cls = require('../../services/cls');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const becca = require('../../becca/becca');
|
const becca = require('../../becca/becca');
|
||||||
@ -13,8 +13,8 @@ const TaskContext = require('../../services/task_context');
|
|||||||
const ValidationError = require('../../errors/validation_error');
|
const ValidationError = require('../../errors/validation_error');
|
||||||
|
|
||||||
async function importNotesToBranch(req) {
|
async function importNotesToBranch(req) {
|
||||||
const {parentNoteId} = req.params;
|
const { parentNoteId } = req.params;
|
||||||
const {taskId, last} = req.body;
|
const { taskId, last } = req.body;
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
safeImport: req.body.safeImport !== 'false',
|
safeImport: req.body.safeImport !== 'false',
|
||||||
@ -81,8 +81,8 @@ async function importNotesToBranch(req) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function importAttachmentsToNote(req) {
|
async function importAttachmentsToNote(req) {
|
||||||
const {parentNoteId} = req.params;
|
const { parentNoteId } = req.params;
|
||||||
const {taskId, last} = req.body;
|
const { taskId, last } = req.body;
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
shrinkImages: req.body.shrinkImages !== 'false',
|
shrinkImages: req.body.shrinkImages !== 'false',
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
const becca = require('../../becca/becca');
|
const becca = require('../../becca/becca');
|
||||||
const markdownService = require('../../services/import/markdown.js');
|
const markdownService = require('../../services/import/markdown');
|
||||||
|
|
||||||
function getIconUsage() {
|
function getIconUsage() {
|
||||||
const iconClassToCountMap = {};
|
const iconClassToCountMap = {};
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const imageType = require('image-type');
|
const imageType = require('image-type');
|
||||||
const imageService = require('../../services/image.js');
|
const imageService = require('../../services/image');
|
||||||
const noteService = require('../../services/notes');
|
const noteService = require('../../services/notes');
|
||||||
const {sanitizeAttributeName} = require('../../services/sanitize_attribute_name');
|
const { sanitizeAttributeName } = require('../../services/sanitize_attribute_name');
|
||||||
const specialNotesService = require('../../services/special_notes.js');
|
const specialNotesService = require('../../services/special_notes');
|
||||||
|
|
||||||
function uploadImage(req) {
|
function uploadImage(req) {
|
||||||
const file = req.file;
|
const file = req.file;
|
||||||
@ -17,14 +17,14 @@ function uploadImage(req) {
|
|||||||
|
|
||||||
const parentNote = specialNotesService.getInboxNote(req.headers['x-local-date']);
|
const parentNote = specialNotesService.getInboxNote(req.headers['x-local-date']);
|
||||||
|
|
||||||
const {note, noteId} = imageService.saveImage(parentNote.noteId, file.buffer, originalName, true);
|
const { note, noteId } = imageService.saveImage(parentNote.noteId, file.buffer, originalName, true);
|
||||||
|
|
||||||
const labelsStr = req.headers['x-labels'];
|
const labelsStr = req.headers['x-labels'];
|
||||||
|
|
||||||
if (labelsStr?.trim()) {
|
if (labelsStr?.trim()) {
|
||||||
const labels = JSON.parse(labelsStr);
|
const labels = JSON.parse(labelsStr);
|
||||||
|
|
||||||
for (const {name, value} of labels) {
|
for (const { name, value } of labels) {
|
||||||
note.setLabel(sanitizeAttributeName(name), value);
|
note.setLabel(sanitizeAttributeName(name), value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -39,7 +39,7 @@ function uploadImage(req) {
|
|||||||
function saveNote(req) {
|
function saveNote(req) {
|
||||||
const parentNote = specialNotesService.getInboxNote(req.headers['x-local-date']);
|
const parentNote = specialNotesService.getInboxNote(req.headers['x-local-date']);
|
||||||
|
|
||||||
const {note, branch} = noteService.createNewNote({
|
const { note, branch } = noteService.createNewNote({
|
||||||
parentNoteId: parentNote.noteId,
|
parentNoteId: parentNote.noteId,
|
||||||
title: req.body.title,
|
title: req.body.title,
|
||||||
content: req.body.content,
|
content: req.body.content,
|
||||||
@ -49,7 +49,7 @@ function saveNote(req) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (req.body.labels) {
|
if (req.body.labels) {
|
||||||
for (const {name, value} of req.body.labels) {
|
for (const { name, value } of req.body.labels) {
|
||||||
note.setLabel(sanitizeAttributeName(name), value);
|
note.setLabel(sanitizeAttributeName(name), value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const sqlInit = require('../../services/sql_init');
|
const sqlInit = require('../../services/sql_init');
|
||||||
const setupService = require('../../services/setup.js');
|
const setupService = require('../../services/setup');
|
||||||
const log = require('../../services/log');
|
const log = require('../../services/log');
|
||||||
const appInfo = require('../../services/app_info');
|
const appInfo = require('../../services/app_info');
|
||||||
|
|
||||||
@ -24,7 +24,7 @@ function setupSyncFromServer(req) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function saveSyncSeed(req) {
|
function saveSyncSeed(req) {
|
||||||
const {options, syncVersion} = req.body;
|
const { options, syncVersion } = req.body;
|
||||||
|
|
||||||
if (appInfo.syncVersion !== syncVersion) {
|
if (appInfo.syncVersion !== syncVersion) {
|
||||||
const message = `Could not setup sync since local sync protocol version is ${appInfo.syncVersion} while remote is ${syncVersion}. To fix this issue, use same Trilium version on all instances.`;
|
const message = `Could not setup sync since local sync protocol version is ${appInfo.syncVersion} while remote is ${syncVersion}. To fix this issue, use same Trilium version on all instances.`;
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
const dateNoteService = require('../../services/date_notes');
|
const dateNoteService = require('../../services/date_notes');
|
||||||
const sql = require('../../services/sql');
|
const sql = require('../../services/sql');
|
||||||
const cls = require('../../services/cls');
|
const cls = require('../../services/cls');
|
||||||
const specialNotesService = require('../../services/special_notes.js');
|
const specialNotesService = require('../../services/special_notes');
|
||||||
const becca = require('../../becca/becca');
|
const becca = require('../../becca/becca');
|
||||||
|
|
||||||
function getInboxNote(req) {
|
function getInboxNote(req) {
|
||||||
|
@ -132,7 +132,7 @@ function getChanged(req) {
|
|||||||
const partialRequests = {};
|
const partialRequests = {};
|
||||||
|
|
||||||
function update(req) {
|
function update(req) {
|
||||||
let {body} = req;
|
let { body } = req;
|
||||||
|
|
||||||
const pageCount = parseInt(req.get('pageCount'));
|
const pageCount = parseInt(req.get('pageCount'));
|
||||||
const pageIndex = parseInt(req.get('pageIndex'));
|
const pageIndex = parseInt(req.get('pageIndex'));
|
||||||
@ -164,7 +164,7 @@ function update(req) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const {entities, instanceId} = body;
|
const { entities, instanceId } = body;
|
||||||
|
|
||||||
sql.transactional(() => syncUpdateService.updateEntities(entities, instanceId));
|
sql.transactional(() => syncUpdateService.updateEntities(entities, instanceId));
|
||||||
}
|
}
|
||||||
@ -193,7 +193,7 @@ function queueSector(req) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function checkEntityChanges() {
|
function checkEntityChanges() {
|
||||||
require('../../services/consistency_checks.js').runEntityChangesChecks();
|
require('../../services/consistency_checks').runEntityChangesChecks();
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
@ -41,14 +41,14 @@ const importRoute = require('./api/import.js');
|
|||||||
const setupApiRoute = require('./api/setup.js');
|
const setupApiRoute = require('./api/setup.js');
|
||||||
const sqlRoute = require('./api/sql');
|
const sqlRoute = require('./api/sql');
|
||||||
const databaseRoute = require('./api/database.js');
|
const databaseRoute = require('./api/database.js');
|
||||||
const imageRoute = require('./api/image.js');
|
const imageRoute = require('./api/image');
|
||||||
const attributesRoute = require('./api/attributes');
|
const attributesRoute = require('./api/attributes');
|
||||||
const scriptRoute = require('./api/script.js');
|
const scriptRoute = require('./api/script.js');
|
||||||
const senderRoute = require('./api/sender.js');
|
const senderRoute = require('./api/sender.js');
|
||||||
const filesRoute = require('./api/files.js');
|
const filesRoute = require('./api/files.js');
|
||||||
const searchRoute = require('./api/search');
|
const searchRoute = require('./api/search');
|
||||||
const bulkActionRoute = require('./api/bulk_action.js');
|
const bulkActionRoute = require('./api/bulk_action.js');
|
||||||
const specialNotesRoute = require('./api/special_notes.js');
|
const specialNotesRoute = require('./api/special_notes');
|
||||||
const noteMapRoute = require('./api/note_map.js');
|
const noteMapRoute = require('./api/note_map.js');
|
||||||
const clipperRoute = require('./api/clipper.js');
|
const clipperRoute = require('./api/clipper.js');
|
||||||
const similarNotesRoute = require('./api/similar_notes.js');
|
const similarNotesRoute = require('./api/similar_notes.js');
|
||||||
@ -67,7 +67,7 @@ const etapiAttachmentRoutes = require('../etapi/attachments.js');
|
|||||||
const etapiAttributeRoutes = require('../etapi/attributes');
|
const etapiAttributeRoutes = require('../etapi/attributes');
|
||||||
const etapiBranchRoutes = require('../etapi/branches.js');
|
const etapiBranchRoutes = require('../etapi/branches.js');
|
||||||
const etapiNoteRoutes = require('../etapi/notes.js');
|
const etapiNoteRoutes = require('../etapi/notes.js');
|
||||||
const etapiSpecialNoteRoutes = require('../etapi/special_notes.js');
|
const etapiSpecialNoteRoutes = require('../etapi/special_notes');
|
||||||
const etapiSpecRoute = require('../etapi/spec.js');
|
const etapiSpecRoute = require('../etapi/spec.js');
|
||||||
const etapiBackupRoute = require('../etapi/backup');
|
const etapiBackupRoute = require('../etapi/backup');
|
||||||
|
|
||||||
@ -230,7 +230,7 @@ function register(app) {
|
|||||||
apiRoute(GET, '/api/app-info', appInfoRoute.getAppInfo);
|
apiRoute(GET, '/api/app-info', appInfoRoute.getAppInfo);
|
||||||
|
|
||||||
// docker health check
|
// docker health check
|
||||||
route(GET, '/api/health-check', [], () => ({"status": "ok"}), apiResultHandler);
|
route(GET, '/api/health-check', [], () => ({ "status": "ok" }), apiResultHandler);
|
||||||
|
|
||||||
// group of the services below are meant to be executed from the outside
|
// group of the services below are meant to be executed from the outside
|
||||||
route(GET, '/api/setup/status', [], setupApiRoute.getStatus, apiResultHandler);
|
route(GET, '/api/setup/status', [], setupApiRoute.getStatus, apiResultHandler);
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const sqlInit = require('../services/sql_init');
|
const sqlInit = require('../services/sql_init');
|
||||||
const setupService = require('../services/setup.js');
|
const setupService = require('../services/setup');
|
||||||
const utils = require('../services/utils');
|
const utils = require('../services/utils');
|
||||||
const assetPath = require('../services/asset_path');
|
const assetPath = require('../services/asset_path');
|
||||||
const appPath = require('../services/app_path');
|
const appPath = require('../services/app_path');
|
||||||
@ -10,7 +10,7 @@ function setupPage(req, res) {
|
|||||||
if (sqlInit.isDbInitialized()) {
|
if (sqlInit.isDbInitialized()) {
|
||||||
if (utils.isElectron()) {
|
if (utils.isElectron()) {
|
||||||
const windowService = require('../services/window');
|
const windowService = require('../services/window');
|
||||||
const {app} = require('electron');
|
const { app } = require('electron');
|
||||||
windowService.createMainWindow(app);
|
windowService.createMainWindow(app);
|
||||||
windowService.closeSetupWindow();
|
windowService.closeSetupWindow();
|
||||||
}
|
}
|
||||||
|
@ -90,7 +90,7 @@ function getExistingAnonymizedDatabases() {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
export = {
|
||||||
getFullAnonymizationScript,
|
getFullAnonymizationScript,
|
||||||
createAnonymizedCopy,
|
createAnonymizedCopy,
|
||||||
getExistingAnonymizedDatabases
|
getExistingAnonymizedDatabases
|
||||||
|
17
src/services/api-interface.ts
Normal file
17
src/services/api-interface.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { OptionRow } from "../becca/entities/rows";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response for /api/setup/status.
|
||||||
|
*/
|
||||||
|
export interface SetupStatusResponse {
|
||||||
|
syncVersion: number;
|
||||||
|
schemaExists: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response for /api/setup/sync-seed.
|
||||||
|
*/
|
||||||
|
export interface SetupSyncSeedResponse {
|
||||||
|
syncVersion: number;
|
||||||
|
options: OptionRow[]
|
||||||
|
}
|
@ -15,10 +15,10 @@ const searchService = require('./search/services/search');
|
|||||||
const SearchContext = require('./search/search_context');
|
const SearchContext = require('./search/search_context');
|
||||||
const becca = require('../becca/becca');
|
const becca = require('../becca/becca');
|
||||||
const ws = require('./ws');
|
const ws = require('./ws');
|
||||||
const SpacedUpdate = require('./spaced_update.js');
|
const SpacedUpdate = require('./spaced_update');
|
||||||
const specialNotesService = require('./special_notes.js');
|
const specialNotesService = require('./special_notes');
|
||||||
const branchService = require('./branches');
|
const branchService = require('./branches');
|
||||||
const exportService = require('./export/zip.js');
|
const exportService = require('./export/zip');
|
||||||
const syncMutex = require('./sync_mutex');
|
const syncMutex = require('./sync_mutex');
|
||||||
const backupService = require('./backup');
|
const backupService = require('./backup');
|
||||||
const optionsService = require('./options');
|
const optionsService = require('./options');
|
||||||
@ -320,7 +320,7 @@ function BackendScriptApi(currentNote, apiParams) {
|
|||||||
* @param {string} [extraOptions.attributes.value] - attribute value
|
* @param {string} [extraOptions.attributes.value] - attribute value
|
||||||
* @returns {{note: BNote, branch: BBranch}} object contains newly created entities note and branch
|
* @returns {{note: BNote, branch: BBranch}} object contains newly created entities note and branch
|
||||||
*/
|
*/
|
||||||
this.createNote = (parentNoteId, title, content = "", extraOptions= {}) => {
|
this.createNote = (parentNoteId, title, content = "", extraOptions = {}) => {
|
||||||
extraOptions.parentNoteId = parentNoteId;
|
extraOptions.parentNoteId = parentNoteId;
|
||||||
extraOptions.title = title;
|
extraOptions.title = title;
|
||||||
|
|
||||||
@ -340,7 +340,7 @@ function BackendScriptApi(currentNote, apiParams) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return sql.transactional(() => {
|
return sql.transactional(() => {
|
||||||
const {note, branch} = noteService.createNewNote(extraOptions);
|
const { note, branch } = noteService.createNewNote(extraOptions);
|
||||||
|
|
||||||
for (const attr of extraOptions.attributes || []) {
|
for (const attr of extraOptions.attributes || []) {
|
||||||
attributeService.createAttribute({
|
attributeService.createAttribute({
|
||||||
@ -352,7 +352,7 @@ function BackendScriptApi(currentNote, apiParams) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return {note, branch};
|
return { note, branch };
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -369,7 +369,7 @@ function BackendScriptApi(currentNote, apiParams) {
|
|||||||
this.log = message => {
|
this.log = message => {
|
||||||
log.info(message);
|
log.info(message);
|
||||||
|
|
||||||
const {noteId} = this.startNote;
|
const { noteId } = this.startNote;
|
||||||
|
|
||||||
this.logMessages[noteId] = this.logMessages[noteId] || [];
|
this.logMessages[noteId] = this.logMessages[noteId] || [];
|
||||||
this.logSpacedUpdates[noteId] = this.logSpacedUpdates[noteId] || new SpacedUpdate(() => {
|
this.logSpacedUpdates[noteId] = this.logSpacedUpdates[noteId] || new SpacedUpdate(() => {
|
||||||
@ -600,7 +600,7 @@ function BackendScriptApi(currentNote, apiParams) {
|
|||||||
launcherNote.removeLabel('iconClass');
|
launcherNote.removeLabel('iconClass');
|
||||||
}
|
}
|
||||||
|
|
||||||
return {note: launcherNote};
|
return { note: launcherNote };
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,33 +1,41 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const sql = require('./sql');
|
import sql = require('./sql');
|
||||||
const sqlInit = require('./sql_init');
|
import sqlInit = require('./sql_init');
|
||||||
const log = require('./log');
|
import log = require('./log');
|
||||||
const ws = require('./ws');
|
import ws = require('./ws');
|
||||||
const syncMutexService = require('./sync_mutex');
|
import syncMutexService = require('./sync_mutex');
|
||||||
const cls = require('./cls');
|
import cls = require('./cls');
|
||||||
const entityChangesService = require('./entity_changes');
|
import entityChangesService = require('./entity_changes');
|
||||||
const optionsService = require('./options');
|
import optionsService = require('./options');
|
||||||
const BBranch = require('../becca/entities/bbranch');
|
import BBranch = require('../becca/entities/bbranch');
|
||||||
const revisionService = require('./revisions');
|
import becca = require('../becca/becca');
|
||||||
const becca = require('../becca/becca');
|
import utils = require('../services/utils');
|
||||||
const utils = require('../services/utils');
|
import eraseService = require('../services/erase');
|
||||||
const eraseService = require('../services/erase');
|
import sanitizeAttributeName = require('./sanitize_attribute_name');
|
||||||
const {sanitizeAttributeName} = require('./sanitize_attribute_name');
|
import noteTypesService = require('../services/note_types');
|
||||||
const noteTypes = require('../services/note_types').getNoteTypeNames();
|
import { BranchRow } from '../becca/entities/rows';
|
||||||
|
import { EntityChange } from './entity_changes_interface';
|
||||||
|
const noteTypes = noteTypesService.getNoteTypeNames();
|
||||||
|
|
||||||
class ConsistencyChecks {
|
class ConsistencyChecks {
|
||||||
|
|
||||||
|
private autoFix: boolean;
|
||||||
|
private unrecoveredConsistencyErrors: boolean;
|
||||||
|
private fixedIssues: boolean;
|
||||||
|
private reloadNeeded: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param autoFix - automatically fix all encountered problems. False is only for debugging during development (fail fast)
|
* @param autoFix - automatically fix all encountered problems. False is only for debugging during development (fail fast)
|
||||||
*/
|
*/
|
||||||
constructor(autoFix) {
|
constructor(autoFix: boolean) {
|
||||||
this.autoFix = autoFix;
|
this.autoFix = autoFix;
|
||||||
this.unrecoveredConsistencyErrors = false;
|
this.unrecoveredConsistencyErrors = false;
|
||||||
this.fixedIssues = false;
|
this.fixedIssues = false;
|
||||||
this.reloadNeeded = false;
|
this.reloadNeeded = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
findAndFixIssues(query, fixerCb) {
|
findAndFixIssues(query: string, fixerCb: (res: any) => void) {
|
||||||
const results = sql.getRows(query);
|
const results = sql.getRows(query);
|
||||||
|
|
||||||
for (const res of results) {
|
for (const res of results) {
|
||||||
@ -39,7 +47,7 @@ class ConsistencyChecks {
|
|||||||
} else {
|
} else {
|
||||||
this.unrecoveredConsistencyErrors = true;
|
this.unrecoveredConsistencyErrors = true;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e: any) {
|
||||||
logError(`Fixer failed with ${e.message} ${e.stack}`);
|
logError(`Fixer failed with ${e.message} ${e.stack}`);
|
||||||
this.unrecoveredConsistencyErrors = true;
|
this.unrecoveredConsistencyErrors = true;
|
||||||
}
|
}
|
||||||
@ -49,8 +57,8 @@ class ConsistencyChecks {
|
|||||||
}
|
}
|
||||||
|
|
||||||
checkTreeCycles() {
|
checkTreeCycles() {
|
||||||
const childToParents = {};
|
const childToParents: Record<string, string[]> = {};
|
||||||
const rows = sql.getRows("SELECT noteId, parentNoteId FROM branches WHERE isDeleted = 0");
|
const rows = sql.getRows<BranchRow>("SELECT noteId, parentNoteId FROM branches WHERE isDeleted = 0");
|
||||||
|
|
||||||
for (const row of rows) {
|
for (const row of rows) {
|
||||||
const childNoteId = row.noteId;
|
const childNoteId = row.noteId;
|
||||||
@ -61,7 +69,7 @@ class ConsistencyChecks {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {boolean} true if cycle was found and we should try again */
|
/** @returns {boolean} true if cycle was found and we should try again */
|
||||||
const checkTreeCycle = (noteId, path) => {
|
const checkTreeCycle = (noteId: string, path: string[]) => {
|
||||||
if (noteId === 'root') {
|
if (noteId === 'root') {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -70,8 +78,10 @@ class ConsistencyChecks {
|
|||||||
if (path.includes(parentNoteId)) {
|
if (path.includes(parentNoteId)) {
|
||||||
if (this.autoFix) {
|
if (this.autoFix) {
|
||||||
const branch = becca.getBranchFromChildAndParent(noteId, parentNoteId);
|
const branch = becca.getBranchFromChildAndParent(noteId, parentNoteId);
|
||||||
branch.markAsDeleted('cycle-autofix');
|
if (branch) {
|
||||||
logFix(`Branch '${branch.branchId}' between child '${noteId}' and parent '${parentNoteId}' has been deleted since it was causing a tree cycle.`);
|
branch.markAsDeleted('cycle-autofix');
|
||||||
|
logFix(`Branch '${branch.branchId}' between child '${noteId}' and parent '${parentNoteId}' has been deleted since it was causing a tree cycle.`);
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -133,6 +143,9 @@ class ConsistencyChecks {
|
|||||||
({branchId, noteId}) => {
|
({branchId, noteId}) => {
|
||||||
if (this.autoFix) {
|
if (this.autoFix) {
|
||||||
const branch = becca.getBranch(branchId);
|
const branch = becca.getBranch(branchId);
|
||||||
|
if (!branch) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
branch.markAsDeleted();
|
branch.markAsDeleted();
|
||||||
|
|
||||||
this.reloadNeeded = true;
|
this.reloadNeeded = true;
|
||||||
@ -154,12 +167,21 @@ class ConsistencyChecks {
|
|||||||
if (this.autoFix) {
|
if (this.autoFix) {
|
||||||
// Delete the old branch and recreate it with root as parent.
|
// Delete the old branch and recreate it with root as parent.
|
||||||
const oldBranch = becca.getBranch(branchId);
|
const oldBranch = becca.getBranch(branchId);
|
||||||
|
if (!oldBranch) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const noteId = oldBranch.noteId;
|
const noteId = oldBranch.noteId;
|
||||||
oldBranch.markAsDeleted("missing-parent");
|
oldBranch.markAsDeleted("missing-parent");
|
||||||
|
|
||||||
let message = `Branch '${branchId}' was missing parent note '${parentNoteId}', so it was deleted. `;
|
let message = `Branch '${branchId}' was missing parent note '${parentNoteId}', so it was deleted. `;
|
||||||
|
|
||||||
if (becca.getNote(noteId).getParentBranches().length === 0) {
|
const note = becca.getNote(noteId);
|
||||||
|
if (!note) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (note.getParentBranches().length === 0) {
|
||||||
const newBranch = new BBranch({
|
const newBranch = new BBranch({
|
||||||
parentNoteId: 'root',
|
parentNoteId: 'root',
|
||||||
noteId: noteId,
|
noteId: noteId,
|
||||||
@ -188,6 +210,9 @@ class ConsistencyChecks {
|
|||||||
({attributeId, noteId}) => {
|
({attributeId, noteId}) => {
|
||||||
if (this.autoFix) {
|
if (this.autoFix) {
|
||||||
const attribute = becca.getAttribute(attributeId);
|
const attribute = becca.getAttribute(attributeId);
|
||||||
|
if (!attribute) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
attribute.markAsDeleted();
|
attribute.markAsDeleted();
|
||||||
|
|
||||||
this.reloadNeeded = true;
|
this.reloadNeeded = true;
|
||||||
@ -208,6 +233,9 @@ class ConsistencyChecks {
|
|||||||
({attributeId, noteId}) => {
|
({attributeId, noteId}) => {
|
||||||
if (this.autoFix) {
|
if (this.autoFix) {
|
||||||
const attribute = becca.getAttribute(attributeId);
|
const attribute = becca.getAttribute(attributeId);
|
||||||
|
if (!attribute) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
attribute.markAsDeleted();
|
attribute.markAsDeleted();
|
||||||
|
|
||||||
this.reloadNeeded = true;
|
this.reloadNeeded = true;
|
||||||
@ -230,6 +258,9 @@ class ConsistencyChecks {
|
|||||||
({attachmentId, ownerId}) => {
|
({attachmentId, ownerId}) => {
|
||||||
if (this.autoFix) {
|
if (this.autoFix) {
|
||||||
const attachment = becca.getAttachment(attachmentId);
|
const attachment = becca.getAttachment(attachmentId);
|
||||||
|
if (!attachment) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
attachment.markAsDeleted();
|
attachment.markAsDeleted();
|
||||||
|
|
||||||
this.reloadNeeded = false;
|
this.reloadNeeded = false;
|
||||||
@ -258,6 +289,7 @@ class ConsistencyChecks {
|
|||||||
({branchId, noteId}) => {
|
({branchId, noteId}) => {
|
||||||
if (this.autoFix) {
|
if (this.autoFix) {
|
||||||
const branch = becca.getBranch(branchId);
|
const branch = becca.getBranch(branchId);
|
||||||
|
if (!branch) return;
|
||||||
branch.markAsDeleted();
|
branch.markAsDeleted();
|
||||||
|
|
||||||
this.reloadNeeded = true;
|
this.reloadNeeded = true;
|
||||||
@ -278,6 +310,9 @@ class ConsistencyChecks {
|
|||||||
`, ({branchId, parentNoteId}) => {
|
`, ({branchId, parentNoteId}) => {
|
||||||
if (this.autoFix) {
|
if (this.autoFix) {
|
||||||
const branch = becca.getBranch(branchId);
|
const branch = becca.getBranch(branchId);
|
||||||
|
if (!branch) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
branch.markAsDeleted();
|
branch.markAsDeleted();
|
||||||
|
|
||||||
this.reloadNeeded = true;
|
this.reloadNeeded = true;
|
||||||
@ -321,7 +356,7 @@ class ConsistencyChecks {
|
|||||||
HAVING COUNT(1) > 1`,
|
HAVING COUNT(1) > 1`,
|
||||||
({noteId, parentNoteId}) => {
|
({noteId, parentNoteId}) => {
|
||||||
if (this.autoFix) {
|
if (this.autoFix) {
|
||||||
const branchIds = sql.getColumn(
|
const branchIds = sql.getColumn<string>(
|
||||||
`SELECT branchId
|
`SELECT branchId
|
||||||
FROM branches
|
FROM branches
|
||||||
WHERE noteId = ?
|
WHERE noteId = ?
|
||||||
@ -333,9 +368,17 @@ class ConsistencyChecks {
|
|||||||
|
|
||||||
// it's not necessarily "original" branch, it's just the only one which will survive
|
// it's not necessarily "original" branch, it's just the only one which will survive
|
||||||
const origBranch = branches[0];
|
const origBranch = branches[0];
|
||||||
|
if (!origBranch) {
|
||||||
|
logError(`Unable to find original branch.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// delete all but the first branch
|
// delete all but the first branch
|
||||||
for (const branch of branches.slice(1)) {
|
for (const branch of branches.slice(1)) {
|
||||||
|
if (!branch) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
branch.markAsDeleted();
|
branch.markAsDeleted();
|
||||||
|
|
||||||
logFix(`Removing branch '${branch.branchId}' since it's a parent-child duplicate of branch '${origBranch.branchId}'`);
|
logFix(`Removing branch '${branch.branchId}' since it's a parent-child duplicate of branch '${origBranch.branchId}'`);
|
||||||
@ -357,6 +400,7 @@ class ConsistencyChecks {
|
|||||||
({attachmentId, noteId}) => {
|
({attachmentId, noteId}) => {
|
||||||
if (this.autoFix) {
|
if (this.autoFix) {
|
||||||
const attachment = becca.getAttachment(attachmentId);
|
const attachment = becca.getAttachment(attachmentId);
|
||||||
|
if (!attachment) return;
|
||||||
attachment.markAsDeleted();
|
attachment.markAsDeleted();
|
||||||
|
|
||||||
this.reloadNeeded = false;
|
this.reloadNeeded = false;
|
||||||
@ -379,6 +423,7 @@ class ConsistencyChecks {
|
|||||||
({noteId, type}) => {
|
({noteId, type}) => {
|
||||||
if (this.autoFix) {
|
if (this.autoFix) {
|
||||||
const note = becca.getNote(noteId);
|
const note = becca.getNote(noteId);
|
||||||
|
if (!note) return;
|
||||||
note.type = 'file'; // file is a safe option to recover notes if the type is not known
|
note.type = 'file'; // file is a safe option to recover notes if the type is not known
|
||||||
note.save();
|
note.save();
|
||||||
|
|
||||||
@ -404,6 +449,10 @@ class ConsistencyChecks {
|
|||||||
const fakeDate = "2000-01-01 00:00:00Z";
|
const fakeDate = "2000-01-01 00:00:00Z";
|
||||||
|
|
||||||
const blankContent = getBlankContent(isProtected, type, mime);
|
const blankContent = getBlankContent(isProtected, type, mime);
|
||||||
|
if (!blankContent) {
|
||||||
|
logError(`Unable to recover note ${noteId} since it's content could not be retrieved (might be protected note).`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
const blobId = utils.hashedBlobId(blankContent);
|
const blobId = utils.hashedBlobId(blankContent);
|
||||||
const blobAlreadyExists = !!sql.getValue("SELECT 1 FROM blobs WHERE blobId = ?", [blobId]);
|
const blobAlreadyExists = !!sql.getValue("SELECT 1 FROM blobs WHERE blobId = ?", [blobId]);
|
||||||
|
|
||||||
@ -452,7 +501,11 @@ class ConsistencyChecks {
|
|||||||
if (this.autoFix) {
|
if (this.autoFix) {
|
||||||
const note = becca.getNote(noteId);
|
const note = becca.getNote(noteId);
|
||||||
const blankContent = getBlankContent(false, type, mime);
|
const blankContent = getBlankContent(false, type, mime);
|
||||||
note.setContent(blankContent);
|
if (!note) return;
|
||||||
|
|
||||||
|
if (blankContent) {
|
||||||
|
note.setContent(blankContent);
|
||||||
|
}
|
||||||
|
|
||||||
this.reloadNeeded = true;
|
this.reloadNeeded = true;
|
||||||
|
|
||||||
@ -506,7 +559,7 @@ class ConsistencyChecks {
|
|||||||
AND branches.isDeleted = 0`,
|
AND branches.isDeleted = 0`,
|
||||||
({parentNoteId}) => {
|
({parentNoteId}) => {
|
||||||
if (this.autoFix) {
|
if (this.autoFix) {
|
||||||
const branchIds = sql.getColumn(`
|
const branchIds = sql.getColumn<string>(`
|
||||||
SELECT branchId
|
SELECT branchId
|
||||||
FROM branches
|
FROM branches
|
||||||
WHERE isDeleted = 0
|
WHERE isDeleted = 0
|
||||||
@ -515,6 +568,8 @@ class ConsistencyChecks {
|
|||||||
const branches = branchIds.map(branchId => becca.getBranch(branchId));
|
const branches = branchIds.map(branchId => becca.getBranch(branchId));
|
||||||
|
|
||||||
for (const branch of branches) {
|
for (const branch of branches) {
|
||||||
|
if (!branch) continue;
|
||||||
|
|
||||||
// delete the old wrong branch
|
// delete the old wrong branch
|
||||||
branch.markAsDeleted("parent-is-search");
|
branch.markAsDeleted("parent-is-search");
|
||||||
|
|
||||||
@ -543,6 +598,7 @@ class ConsistencyChecks {
|
|||||||
({attributeId}) => {
|
({attributeId}) => {
|
||||||
if (this.autoFix) {
|
if (this.autoFix) {
|
||||||
const relation = becca.getAttribute(attributeId);
|
const relation = becca.getAttribute(attributeId);
|
||||||
|
if (!relation) return;
|
||||||
relation.markAsDeleted();
|
relation.markAsDeleted();
|
||||||
|
|
||||||
this.reloadNeeded = true;
|
this.reloadNeeded = true;
|
||||||
@ -563,6 +619,7 @@ class ConsistencyChecks {
|
|||||||
({attributeId, type}) => {
|
({attributeId, type}) => {
|
||||||
if (this.autoFix) {
|
if (this.autoFix) {
|
||||||
const attribute = becca.getAttribute(attributeId);
|
const attribute = becca.getAttribute(attributeId);
|
||||||
|
if (!attribute) return;
|
||||||
attribute.type = 'label';
|
attribute.type = 'label';
|
||||||
attribute.save();
|
attribute.save();
|
||||||
|
|
||||||
@ -584,6 +641,7 @@ class ConsistencyChecks {
|
|||||||
({attributeId, noteId}) => {
|
({attributeId, noteId}) => {
|
||||||
if (this.autoFix) {
|
if (this.autoFix) {
|
||||||
const attribute = becca.getAttribute(attributeId);
|
const attribute = becca.getAttribute(attributeId);
|
||||||
|
if (!attribute) return;
|
||||||
attribute.markAsDeleted();
|
attribute.markAsDeleted();
|
||||||
|
|
||||||
this.reloadNeeded = true;
|
this.reloadNeeded = true;
|
||||||
@ -605,6 +663,7 @@ class ConsistencyChecks {
|
|||||||
({attributeId, targetNoteId}) => {
|
({attributeId, targetNoteId}) => {
|
||||||
if (this.autoFix) {
|
if (this.autoFix) {
|
||||||
const attribute = becca.getAttribute(attributeId);
|
const attribute = becca.getAttribute(attributeId);
|
||||||
|
if (!attribute) return;
|
||||||
attribute.markAsDeleted();
|
attribute.markAsDeleted();
|
||||||
|
|
||||||
this.reloadNeeded = true;
|
this.reloadNeeded = true;
|
||||||
@ -616,14 +675,14 @@ class ConsistencyChecks {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
runEntityChangeChecks(entityName, key) {
|
runEntityChangeChecks(entityName: string, key: string) {
|
||||||
this.findAndFixIssues(`
|
this.findAndFixIssues(`
|
||||||
SELECT ${key} as entityId
|
SELECT ${key} as entityId
|
||||||
FROM ${entityName}
|
FROM ${entityName}
|
||||||
LEFT JOIN entity_changes ec ON ec.entityName = '${entityName}' AND ec.entityId = ${entityName}.${key}
|
LEFT JOIN entity_changes ec ON ec.entityName = '${entityName}' AND ec.entityId = ${entityName}.${key}
|
||||||
WHERE ec.id IS NULL`,
|
WHERE ec.id IS NULL`,
|
||||||
({entityId}) => {
|
({entityId}) => {
|
||||||
const entityRow = sql.getRow(`SELECT * FROM ${entityName} WHERE ${key} = ?`, [entityId]);
|
const entityRow = sql.getRow<EntityChange>(`SELECT * FROM ${entityName} WHERE ${key} = ?`, [entityId]);
|
||||||
|
|
||||||
if (this.autoFix) {
|
if (this.autoFix) {
|
||||||
entityChangesService.putEntityChange({
|
entityChangesService.putEntityChange({
|
||||||
@ -691,10 +750,10 @@ class ConsistencyChecks {
|
|||||||
}
|
}
|
||||||
|
|
||||||
findWronglyNamedAttributes() {
|
findWronglyNamedAttributes() {
|
||||||
const attrNames = sql.getColumn(`SELECT DISTINCT name FROM attributes`);
|
const attrNames = sql.getColumn<string>(`SELECT DISTINCT name FROM attributes`);
|
||||||
|
|
||||||
for (const origName of attrNames) {
|
for (const origName of attrNames) {
|
||||||
const fixedName = sanitizeAttributeName(origName);
|
const fixedName = sanitizeAttributeName.sanitizeAttributeName(origName);
|
||||||
|
|
||||||
if (fixedName !== origName) {
|
if (fixedName !== origName) {
|
||||||
if (this.autoFix) {
|
if (this.autoFix) {
|
||||||
@ -721,7 +780,7 @@ class ConsistencyChecks {
|
|||||||
|
|
||||||
findSyncIssues() {
|
findSyncIssues() {
|
||||||
const lastSyncedPush = parseInt(sql.getValue("SELECT value FROM options WHERE name = 'lastSyncedPush'"));
|
const lastSyncedPush = parseInt(sql.getValue("SELECT value FROM options WHERE name = 'lastSyncedPush'"));
|
||||||
const maxEntityChangeId = sql.getValue("SELECT MAX(id) FROM entity_changes");
|
const maxEntityChangeId = sql.getValue<number>("SELECT MAX(id) FROM entity_changes");
|
||||||
|
|
||||||
if (lastSyncedPush > maxEntityChangeId) {
|
if (lastSyncedPush > maxEntityChangeId) {
|
||||||
if (this.autoFix) {
|
if (this.autoFix) {
|
||||||
@ -773,8 +832,8 @@ class ConsistencyChecks {
|
|||||||
}
|
}
|
||||||
|
|
||||||
runDbDiagnostics() {
|
runDbDiagnostics() {
|
||||||
function getTableRowCount(tableName) {
|
function getTableRowCount(tableName: string) {
|
||||||
const count = sql.getValue(`SELECT COUNT(1) FROM ${tableName}`);
|
const count = sql.getValue<number>(`SELECT COUNT(1) FROM ${tableName}`);
|
||||||
|
|
||||||
return `${tableName}: ${count}`;
|
return `${tableName}: ${count}`;
|
||||||
}
|
}
|
||||||
@ -810,7 +869,7 @@ class ConsistencyChecks {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getBlankContent(isProtected, type, mime) {
|
function getBlankContent(isProtected: boolean, type: string, mime: string) {
|
||||||
if (isProtected) {
|
if (isProtected) {
|
||||||
return null; // this is wrong for protected non-erased notes, but we cannot create a valid value without a password
|
return null; // this is wrong for protected non-erased notes, but we cannot create a valid value without a password
|
||||||
}
|
}
|
||||||
@ -822,11 +881,11 @@ function getBlankContent(isProtected, type, mime) {
|
|||||||
return ''; // empty string might be a wrong choice for some note types, but it's the best guess
|
return ''; // empty string might be a wrong choice for some note types, but it's the best guess
|
||||||
}
|
}
|
||||||
|
|
||||||
function logFix(message) {
|
function logFix(message: string) {
|
||||||
log.info(`Consistency issue fixed: ${message}`);
|
log.info(`Consistency issue fixed: ${message}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
function logError(message) {
|
function logError(message: string) {
|
||||||
log.info(`Consistency error: ${message}`);
|
log.info(`Consistency error: ${message}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -837,7 +896,7 @@ function runPeriodicChecks() {
|
|||||||
consistencyChecks.runChecks();
|
consistencyChecks.runChecks();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function runOnDemandChecks(autoFix) {
|
async function runOnDemandChecks(autoFix: boolean) {
|
||||||
const consistencyChecks = new ConsistencyChecks(autoFix);
|
const consistencyChecks = new ConsistencyChecks(autoFix);
|
||||||
await consistencyChecks.runChecks();
|
await consistencyChecks.runChecks();
|
||||||
}
|
}
|
@ -7,6 +7,8 @@ export interface EntityChange {
|
|||||||
positions?: Record<string, number>;
|
positions?: Record<string, number>;
|
||||||
hash: string;
|
hash: string;
|
||||||
utcDateChanged?: string;
|
utcDateChanged?: string;
|
||||||
|
utcDateModified?: string;
|
||||||
|
utcDateCreated?: string;
|
||||||
isSynced: boolean | 1 | 0;
|
isSynced: boolean | 1 | 0;
|
||||||
isErased: boolean | 1 | 0;
|
isErased: boolean | 1 | 0;
|
||||||
componentId?: string | null;
|
componentId?: string | null;
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const TurndownService = require('turndown');
|
import TurndownService = require('turndown');
|
||||||
const turndownPluginGfm = require('joplin-turndown-plugin-gfm');
|
import turndownPluginGfm = require('joplin-turndown-plugin-gfm');
|
||||||
|
|
||||||
let instance = null;
|
let instance: TurndownService | null = null;
|
||||||
|
|
||||||
function toMarkdown(content) {
|
function toMarkdown(content: string) {
|
||||||
if (instance === null) {
|
if (instance === null) {
|
||||||
instance = new TurndownService({ codeBlockStyle: 'fenced' });
|
instance = new TurndownService({ codeBlockStyle: 'fenced' });
|
||||||
instance.use(turndownPluginGfm.gfm);
|
instance.use(turndownPluginGfm.gfm);
|
||||||
@ -14,6 +14,6 @@ function toMarkdown(content) {
|
|||||||
return instance.turndown(content);
|
return instance.turndown(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
export = {
|
||||||
toMarkdown
|
toMarkdown
|
||||||
};
|
};
|
@ -1,9 +1,12 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const utils = require('../utils');
|
import utils = require('../utils');
|
||||||
const becca = require('../../becca/becca');
|
import becca = require('../../becca/becca');
|
||||||
|
import TaskContext = require('../task_context');
|
||||||
|
import BBranch = require('../../becca/entities/bbranch');
|
||||||
|
import { Response } from 'express';
|
||||||
|
|
||||||
function exportToOpml(taskContext, branch, version, res) {
|
function exportToOpml(taskContext: TaskContext, branch: BBranch, version: string, res: Response) {
|
||||||
if (!['1.0', '2.0'].includes(version)) {
|
if (!['1.0', '2.0'].includes(version)) {
|
||||||
throw new Error(`Unrecognized OPML version ${version}`);
|
throw new Error(`Unrecognized OPML version ${version}`);
|
||||||
}
|
}
|
||||||
@ -12,9 +15,12 @@ function exportToOpml(taskContext, branch, version, res) {
|
|||||||
|
|
||||||
const note = branch.getNote();
|
const note = branch.getNote();
|
||||||
|
|
||||||
function exportNoteInner(branchId) {
|
function exportNoteInner(branchId: string) {
|
||||||
const branch = becca.getBranch(branchId);
|
const branch = becca.getBranch(branchId);
|
||||||
|
if (!branch) { throw new Error("Unable to find branch."); }
|
||||||
|
|
||||||
const note = branch.getNote();
|
const note = branch.getNote();
|
||||||
|
if (!note) { throw new Error("Unable to find note."); }
|
||||||
|
|
||||||
if (note.hasOwnedLabel('excludeFromExport')) {
|
if (note.hasOwnedLabel('excludeFromExport')) {
|
||||||
return;
|
return;
|
||||||
@ -24,13 +30,13 @@ function exportToOpml(taskContext, branch, version, res) {
|
|||||||
|
|
||||||
if (opmlVersion === 1) {
|
if (opmlVersion === 1) {
|
||||||
const preparedTitle = escapeXmlAttribute(title);
|
const preparedTitle = escapeXmlAttribute(title);
|
||||||
const preparedContent = note.hasStringContent() ? prepareText(note.getContent()) : '';
|
const preparedContent = note.hasStringContent() ? prepareText(note.getContent() as string) : '';
|
||||||
|
|
||||||
res.write(`<outline title="${preparedTitle}" text="${preparedContent}">\n`);
|
res.write(`<outline title="${preparedTitle}" text="${preparedContent}">\n`);
|
||||||
}
|
}
|
||||||
else if (opmlVersion === 2) {
|
else if (opmlVersion === 2) {
|
||||||
const preparedTitle = escapeXmlAttribute(title);
|
const preparedTitle = escapeXmlAttribute(title);
|
||||||
const preparedContent = note.hasStringContent() ? escapeXmlAttribute(note.getContent()) : '';
|
const preparedContent = note.hasStringContent() ? escapeXmlAttribute(note.getContent() as string) : '';
|
||||||
|
|
||||||
res.write(`<outline text="${preparedTitle}" _note="${preparedContent}">\n`);
|
res.write(`<outline text="${preparedTitle}" _note="${preparedContent}">\n`);
|
||||||
}
|
}
|
||||||
@ -41,7 +47,9 @@ function exportToOpml(taskContext, branch, version, res) {
|
|||||||
taskContext.increaseProgressCount();
|
taskContext.increaseProgressCount();
|
||||||
|
|
||||||
for (const child of note.getChildBranches()) {
|
for (const child of note.getChildBranches()) {
|
||||||
exportNoteInner(child.branchId);
|
if (child?.branchId) {
|
||||||
|
exportNoteInner(child.branchId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
res.write('</outline>');
|
res.write('</outline>');
|
||||||
@ -60,7 +68,9 @@ function exportToOpml(taskContext, branch, version, res) {
|
|||||||
</head>
|
</head>
|
||||||
<body>`);
|
<body>`);
|
||||||
|
|
||||||
exportNoteInner(branch.branchId);
|
if (branch.branchId) {
|
||||||
|
exportNoteInner(branch.branchId);
|
||||||
|
}
|
||||||
|
|
||||||
res.write(`</body>
|
res.write(`</body>
|
||||||
</opml>`);
|
</opml>`);
|
||||||
@ -69,7 +79,7 @@ function exportToOpml(taskContext, branch, version, res) {
|
|||||||
taskContext.taskSucceeded();
|
taskContext.taskSucceeded();
|
||||||
}
|
}
|
||||||
|
|
||||||
function prepareText(text) {
|
function prepareText(text: string) {
|
||||||
const newLines = text.replace(/(<p[^>]*>|<br\s*\/?>)/g, '\n')
|
const newLines = text.replace(/(<p[^>]*>|<br\s*\/?>)/g, '\n')
|
||||||
.replace(/ /g, ' '); // nbsp isn't in XML standard (only HTML)
|
.replace(/ /g, ' '); // nbsp isn't in XML standard (only HTML)
|
||||||
|
|
||||||
@ -80,7 +90,7 @@ function prepareText(text) {
|
|||||||
return escaped.replace(/\n/g, ' ');
|
return escaped.replace(/\n/g, ' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
function escapeXmlAttribute(text) {
|
function escapeXmlAttribute(text: string) {
|
||||||
return text.replace(/&/g, '&')
|
return text.replace(/&/g, '&')
|
||||||
.replace(/</g, '<')
|
.replace(/</g, '<')
|
||||||
.replace(/>/g, '>')
|
.replace(/>/g, '>')
|
||||||
@ -88,6 +98,6 @@ function escapeXmlAttribute(text) {
|
|||||||
.replace(/'/g, ''');
|
.replace(/'/g, ''');
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
export = {
|
||||||
exportToOpml
|
exportToOpml
|
||||||
};
|
};
|
@ -1,12 +1,15 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const mimeTypes = require('mime-types');
|
import mimeTypes = require('mime-types');
|
||||||
const html = require('html');
|
import html = require('html');
|
||||||
const utils = require('../utils');
|
import utils = require('../utils');
|
||||||
const mdService = require('./md.js');
|
import mdService = require('./md');
|
||||||
const becca = require('../../becca/becca');
|
import becca = require('../../becca/becca');
|
||||||
|
import TaskContext = require('../task_context');
|
||||||
|
import BBranch = require('../../becca/entities/bbranch');
|
||||||
|
import { Response } from 'express';
|
||||||
|
|
||||||
function exportSingleNote(taskContext, branch, format, res) {
|
function exportSingleNote(taskContext: TaskContext, branch: BBranch, format: "html" | "markdown", res: Response) {
|
||||||
const note = branch.getNote();
|
const note = branch.getNote();
|
||||||
|
|
||||||
if (note.type === 'image' || note.type === 'file') {
|
if (note.type === 'image' || note.type === 'file') {
|
||||||
@ -20,6 +23,9 @@ function exportSingleNote(taskContext, branch, format, res) {
|
|||||||
let payload, extension, mime;
|
let payload, extension, mime;
|
||||||
|
|
||||||
let content = note.getContent();
|
let content = note.getContent();
|
||||||
|
if (typeof content !== "string") {
|
||||||
|
throw new Error("Unsupported content type for export.");
|
||||||
|
}
|
||||||
|
|
||||||
if (note.type === 'text') {
|
if (note.type === 'text') {
|
||||||
if (format === 'html') {
|
if (format === 'html') {
|
||||||
@ -64,7 +70,7 @@ function exportSingleNote(taskContext, branch, format, res) {
|
|||||||
taskContext.taskSucceeded();
|
taskContext.taskSucceeded();
|
||||||
}
|
}
|
||||||
|
|
||||||
function inlineAttachments(content) {
|
function inlineAttachments(content: string) {
|
||||||
content = content.replace(/src="[^"]*api\/images\/([a-zA-Z0-9_]+)\/?[^"]+"/g, (match, noteId) => {
|
content = content.replace(/src="[^"]*api\/images\/([a-zA-Z0-9_]+)\/?[^"]+"/g, (match, noteId) => {
|
||||||
const note = becca.getNote(noteId);
|
const note = becca.getNote(noteId);
|
||||||
if (!note || !note.mime.startsWith('image/')) {
|
if (!note || !note.mime.startsWith('image/')) {
|
||||||
@ -119,6 +125,6 @@ function inlineAttachments(content) {
|
|||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
export = {
|
||||||
exportSingleNote
|
exportSingleNote
|
||||||
};
|
};
|
@ -1,33 +1,28 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const html = require('html');
|
import html = require('html');
|
||||||
const dateUtils = require('../date_utils');
|
import dateUtils = require('../date_utils');
|
||||||
const path = require('path');
|
import path = require('path');
|
||||||
const mimeTypes = require('mime-types');
|
import mimeTypes = require('mime-types');
|
||||||
const mdService = require('./md.js');
|
import mdService = require('./md');
|
||||||
const packageInfo = require('../../../package.json');
|
import packageInfo = require('../../../package.json');
|
||||||
const utils = require('../utils');
|
import utils = require('../utils');
|
||||||
const protectedSessionService = require('../protected_session');
|
import protectedSessionService = require('../protected_session');
|
||||||
const sanitize = require("sanitize-filename");
|
import sanitize = require("sanitize-filename");
|
||||||
const fs = require("fs");
|
import fs = require("fs");
|
||||||
const becca = require('../../becca/becca');
|
import becca = require('../../becca/becca');
|
||||||
const RESOURCE_DIR = require('../../services/resource_dir').RESOURCE_DIR;
|
const RESOURCE_DIR = require('../../services/resource_dir').RESOURCE_DIR;
|
||||||
const archiver = require('archiver');
|
import archiver = require('archiver');
|
||||||
const log = require('../log');
|
import log = require('../log');
|
||||||
const TaskContext = require('../task_context');
|
import TaskContext = require('../task_context');
|
||||||
const ValidationError = require('../../errors/validation_error');
|
import ValidationError = require('../../errors/validation_error');
|
||||||
const NoteMeta = require('../meta/note_meta');
|
import NoteMeta = require('../meta/note_meta');
|
||||||
const AttachmentMeta = require('../meta/attachment_meta');
|
import AttachmentMeta = require('../meta/attachment_meta');
|
||||||
const AttributeMeta = require('../meta/attribute_meta');
|
import AttributeMeta = require('../meta/attribute_meta');
|
||||||
|
import BBranch = require('../../becca/entities/bbranch');
|
||||||
|
import { Response } from 'express';
|
||||||
|
|
||||||
/**
|
async function exportToZip(taskContext: TaskContext, branch: BBranch, format: "html" | "markdown", res: Response | fs.WriteStream, setHeaders = true) {
|
||||||
* @param {TaskContext} taskContext
|
|
||||||
* @param {BBranch} branch
|
|
||||||
* @param {string} format - 'html' or 'markdown'
|
|
||||||
* @param {object} res - express response
|
|
||||||
* @param {boolean} setHeaders
|
|
||||||
*/
|
|
||||||
async function exportToZip(taskContext, branch, format, res, setHeaders = true) {
|
|
||||||
if (!['html', 'markdown'].includes(format)) {
|
if (!['html', 'markdown'].includes(format)) {
|
||||||
throw new ValidationError(`Only 'html' and 'markdown' allowed as export format, '${format}' given`);
|
throw new ValidationError(`Only 'html' and 'markdown' allowed as export format, '${format}' given`);
|
||||||
}
|
}
|
||||||
@ -36,15 +31,9 @@ async function exportToZip(taskContext, branch, format, res, setHeaders = true)
|
|||||||
zlib: { level: 9 } // Sets the compression level.
|
zlib: { level: 9 } // Sets the compression level.
|
||||||
});
|
});
|
||||||
|
|
||||||
/** @type {Object.<string, NoteMeta>} */
|
const noteIdToMeta: Record<string, NoteMeta> = {};
|
||||||
const noteIdToMeta = {};
|
|
||||||
|
|
||||||
/**
|
function getUniqueFilename(existingFileNames: Record<string, number>, fileName: string) {
|
||||||
* @param {Object.<string, int>} existingFileNames
|
|
||||||
* @param {string} fileName
|
|
||||||
* @returns {string}
|
|
||||||
*/
|
|
||||||
function getUniqueFilename(existingFileNames, fileName) {
|
|
||||||
const lcFileName = fileName.toLowerCase();
|
const lcFileName = fileName.toLowerCase();
|
||||||
|
|
||||||
if (lcFileName in existingFileNames) {
|
if (lcFileName in existingFileNames) {
|
||||||
@ -67,14 +56,7 @@ async function exportToZip(taskContext, branch, format, res, setHeaders = true)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
function getDataFileName(type: string | null, mime: string, baseFileName: string, existingFileNames: Record<string, number>): string {
|
||||||
* @param {string|null} type
|
|
||||||
* @param {string} mime
|
|
||||||
* @param {string} baseFileName
|
|
||||||
* @param {Object.<string, int>} existingFileNames
|
|
||||||
* @return {string}
|
|
||||||
*/
|
|
||||||
function getDataFileName(type, mime, baseFileName, existingFileNames) {
|
|
||||||
let fileName = baseFileName.trim();
|
let fileName = baseFileName.trim();
|
||||||
if (fileName.length > 30) {
|
if (fileName.length > 30) {
|
||||||
fileName = fileName.substr(0, 30).trim();
|
fileName = fileName.substr(0, 30).trim();
|
||||||
@ -115,13 +97,7 @@ async function exportToZip(taskContext, branch, format, res, setHeaders = true)
|
|||||||
return getUniqueFilename(existingFileNames, fileName);
|
return getUniqueFilename(existingFileNames, fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
function createNoteMeta(branch: BBranch, parentMeta: Partial<NoteMeta>, existingFileNames: Record<string, number>): NoteMeta | null {
|
||||||
* @param {BBranch} branch
|
|
||||||
* @param {NoteMeta} parentMeta
|
|
||||||
* @param {Object.<string, int>} existingFileNames
|
|
||||||
* @returns {NoteMeta|null}
|
|
||||||
*/
|
|
||||||
function createNoteMeta(branch, parentMeta, existingFileNames) {
|
|
||||||
const note = branch.getNote();
|
const note = branch.getNote();
|
||||||
|
|
||||||
if (note.hasOwnedLabel('excludeFromExport')) {
|
if (note.hasOwnedLabel('excludeFromExport')) {
|
||||||
@ -136,24 +112,26 @@ async function exportToZip(taskContext, branch, format, res, setHeaders = true)
|
|||||||
baseFileName = baseFileName.substr(0, 200);
|
baseFileName = baseFileName.substr(0, 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!parentMeta.notePath) { throw new Error("Missing parent note path."); }
|
||||||
const notePath = parentMeta.notePath.concat([note.noteId]);
|
const notePath = parentMeta.notePath.concat([note.noteId]);
|
||||||
|
|
||||||
if (note.noteId in noteIdToMeta) {
|
if (note.noteId in noteIdToMeta) {
|
||||||
const fileName = getUniqueFilename(existingFileNames, `${baseFileName}.clone.${format === 'html' ? 'html' : 'md'}`);
|
const fileName = getUniqueFilename(existingFileNames, `${baseFileName}.clone.${format === 'html' ? 'html' : 'md'}`);
|
||||||
|
|
||||||
const meta = new NoteMeta();
|
const meta: NoteMeta = {
|
||||||
meta.isClone = true;
|
isClone: true,
|
||||||
meta.noteId = note.noteId;
|
noteId: note.noteId,
|
||||||
meta.notePath = notePath;
|
notePath: notePath,
|
||||||
meta.title = note.getTitleOrProtected();
|
title: note.getTitleOrProtected(),
|
||||||
meta.prefix = branch.prefix;
|
prefix: branch.prefix,
|
||||||
meta.dataFileName = fileName;
|
dataFileName: fileName,
|
||||||
meta.type = 'text'; // export will have text description
|
type: 'text', // export will have text description
|
||||||
meta.format = format;
|
format: format
|
||||||
|
};
|
||||||
return meta;
|
return meta;
|
||||||
}
|
}
|
||||||
|
|
||||||
const meta = new NoteMeta();
|
const meta: Partial<NoteMeta> = {};
|
||||||
meta.isClone = false;
|
meta.isClone = false;
|
||||||
meta.noteId = note.noteId;
|
meta.noteId = note.noteId;
|
||||||
meta.notePath = notePath;
|
meta.notePath = notePath;
|
||||||
@ -164,12 +142,14 @@ async function exportToZip(taskContext, branch, format, res, setHeaders = true)
|
|||||||
meta.type = note.type;
|
meta.type = note.type;
|
||||||
meta.mime = note.mime;
|
meta.mime = note.mime;
|
||||||
meta.attributes = note.getOwnedAttributes().map(attribute => {
|
meta.attributes = note.getOwnedAttributes().map(attribute => {
|
||||||
const attrMeta = new AttributeMeta();
|
const attrMeta: AttributeMeta = {
|
||||||
attrMeta.type = attribute.type;
|
type: attribute.type,
|
||||||
attrMeta.name = attribute.name;
|
name: attribute.name,
|
||||||
attrMeta.value = attribute.value;
|
value: attribute.value,
|
||||||
attrMeta.isInheritable = attribute.isInheritable;
|
isInheritable: attribute.isInheritable,
|
||||||
attrMeta.position = attribute.position;
|
position: attribute.position
|
||||||
|
};
|
||||||
|
|
||||||
return attrMeta;
|
return attrMeta;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -179,12 +159,12 @@ async function exportToZip(taskContext, branch, format, res, setHeaders = true)
|
|||||||
meta.format = format;
|
meta.format = format;
|
||||||
}
|
}
|
||||||
|
|
||||||
noteIdToMeta[note.noteId] = meta;
|
noteIdToMeta[note.noteId] = meta as NoteMeta;
|
||||||
|
|
||||||
// sort children for having a stable / reproducible export format
|
// sort children for having a stable / reproducible export format
|
||||||
note.sortChildren();
|
note.sortChildren();
|
||||||
const childBranches = note.getChildBranches()
|
const childBranches = note.getChildBranches()
|
||||||
.filter(branch => branch.noteId !== '_hidden');
|
.filter(branch => branch?.noteId !== '_hidden');
|
||||||
|
|
||||||
const available = !note.isProtected || protectedSessionService.isProtectedSessionAvailable();
|
const available = !note.isProtected || protectedSessionService.isProtectedSessionAvailable();
|
||||||
|
|
||||||
@ -196,18 +176,19 @@ async function exportToZip(taskContext, branch, format, res, setHeaders = true)
|
|||||||
const attachments = note.getAttachments();
|
const attachments = note.getAttachments();
|
||||||
meta.attachments = attachments
|
meta.attachments = attachments
|
||||||
.map(attachment => {
|
.map(attachment => {
|
||||||
const attMeta = new AttachmentMeta();
|
const attMeta: AttachmentMeta = {
|
||||||
attMeta.attachmentId = attachment.attachmentId;
|
attachmentId: attachment.attachmentId,
|
||||||
attMeta.title = attachment.title;
|
title: attachment.title,
|
||||||
attMeta.role = attachment.role;
|
role: attachment.role,
|
||||||
attMeta.mime = attachment.mime;
|
mime: attachment.mime,
|
||||||
attMeta.position = attachment.position;
|
position: attachment.position,
|
||||||
attMeta.dataFileName = getDataFileName(
|
dataFileName: getDataFileName(
|
||||||
null,
|
null,
|
||||||
attachment.mime,
|
attachment.mime,
|
||||||
baseFileName + "_" + attachment.title,
|
baseFileName + "_" + attachment.title,
|
||||||
existingFileNames
|
existingFileNames
|
||||||
);
|
)
|
||||||
|
};
|
||||||
return attMeta;
|
return attMeta;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -219,7 +200,9 @@ async function exportToZip(taskContext, branch, format, res, setHeaders = true)
|
|||||||
const childExistingNames = {};
|
const childExistingNames = {};
|
||||||
|
|
||||||
for (const childBranch of childBranches) {
|
for (const childBranch of childBranches) {
|
||||||
const note = createNoteMeta(childBranch, meta, childExistingNames);
|
if (!childBranch) { continue; }
|
||||||
|
|
||||||
|
const note = createNoteMeta(childBranch, meta as NoteMeta, childExistingNames);
|
||||||
|
|
||||||
// can be undefined if export is disabled for this note
|
// can be undefined if export is disabled for this note
|
||||||
if (note) {
|
if (note) {
|
||||||
@ -228,18 +211,13 @@ async function exportToZip(taskContext, branch, format, res, setHeaders = true)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return meta;
|
return meta as NoteMeta;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
function getNoteTargetUrl(targetNoteId: string, sourceMeta: NoteMeta): string | null {
|
||||||
* @param {string} targetNoteId
|
|
||||||
* @param {NoteMeta} sourceMeta
|
|
||||||
* @return {string|null}
|
|
||||||
*/
|
|
||||||
function getNoteTargetUrl(targetNoteId, sourceMeta) {
|
|
||||||
const targetMeta = noteIdToMeta[targetNoteId];
|
const targetMeta = noteIdToMeta[targetNoteId];
|
||||||
|
|
||||||
if (!targetMeta) {
|
if (!targetMeta || !targetMeta.notePath || !sourceMeta.notePath) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -256,24 +234,20 @@ async function exportToZip(taskContext, branch, format, res, setHeaders = true)
|
|||||||
|
|
||||||
for (let i = 0; i < targetPath.length - 1; i++) {
|
for (let i = 0; i < targetPath.length - 1; i++) {
|
||||||
const meta = noteIdToMeta[targetPath[i]];
|
const meta = noteIdToMeta[targetPath[i]];
|
||||||
|
if (meta.dirFileName) {
|
||||||
url += `${encodeURIComponent(meta.dirFileName)}/`;
|
url += `${encodeURIComponent(meta.dirFileName)}/`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const meta = noteIdToMeta[targetPath[targetPath.length - 1]];
|
const meta = noteIdToMeta[targetPath[targetPath.length - 1]];
|
||||||
|
|
||||||
// link can target note which is only "folder-note" and as such, will not have a file in an export
|
// link can target note which is only "folder-note" and as such, will not have a file in an export
|
||||||
url += encodeURIComponent(meta.dataFileName || meta.dirFileName);
|
url += encodeURIComponent(meta.dataFileName || meta.dirFileName || "");
|
||||||
|
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
function rewriteLinks(content: string, noteMeta: NoteMeta): string {
|
||||||
* @param {string} content
|
|
||||||
* @param {NoteMeta} noteMeta
|
|
||||||
* @return {string}
|
|
||||||
*/
|
|
||||||
function rewriteLinks(content, noteMeta) {
|
|
||||||
content = content.replace(/src="[^"]*api\/images\/([a-zA-Z0-9_]+)\/[^"]*"/g, (match, targetNoteId) => {
|
content = content.replace(/src="[^"]*api\/images\/([a-zA-Z0-9_]+)\/[^"]*"/g, (match, targetNoteId) => {
|
||||||
const url = getNoteTargetUrl(targetNoteId, noteMeta);
|
const url = getNoteTargetUrl(targetNoteId, noteMeta);
|
||||||
|
|
||||||
@ -300,10 +274,10 @@ async function exportToZip(taskContext, branch, format, res, setHeaders = true)
|
|||||||
|
|
||||||
return content;
|
return content;
|
||||||
|
|
||||||
function findAttachment(targetAttachmentId) {
|
function findAttachment(targetAttachmentId: string) {
|
||||||
let url;
|
let url;
|
||||||
|
|
||||||
const attachmentMeta = noteMeta.attachments.find(attMeta => attMeta.attachmentId === targetAttachmentId);
|
const attachmentMeta = (noteMeta.attachments || []).find(attMeta => attMeta.attachmentId === targetAttachmentId);
|
||||||
if (attachmentMeta) {
|
if (attachmentMeta) {
|
||||||
// easy job here, because attachment will be in the same directory as the note's data file.
|
// easy job here, because attachment will be in the same directory as the note's data file.
|
||||||
url = attachmentMeta.dataFileName;
|
url = attachmentMeta.dataFileName;
|
||||||
@ -314,21 +288,17 @@ async function exportToZip(taskContext, branch, format, res, setHeaders = true)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
function prepareContent(title: string, content: string | Buffer, noteMeta: NoteMeta): string | Buffer {
|
||||||
* @param {string} title
|
if (['html', 'markdown'].includes(noteMeta?.format || "")) {
|
||||||
* @param {string|Buffer} content
|
|
||||||
* @param {NoteMeta} noteMeta
|
|
||||||
* @return {string|Buffer}
|
|
||||||
*/
|
|
||||||
function prepareContent(title, content, noteMeta) {
|
|
||||||
if (['html', 'markdown'].includes(noteMeta.format)) {
|
|
||||||
content = content.toString();
|
content = content.toString();
|
||||||
|
|
||||||
content = rewriteLinks(content, noteMeta);
|
content = rewriteLinks(content, noteMeta);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (noteMeta.format === 'html') {
|
if (noteMeta.format === 'html' && typeof content === "string") {
|
||||||
if (!content.substr(0, 100).toLowerCase().includes("<html")) {
|
if (!content.substr(0, 100).toLowerCase().includes("<html")) {
|
||||||
|
if (!noteMeta?.notePath?.length) { throw new Error("Missing note path."); }
|
||||||
|
|
||||||
const cssUrl = `${"../".repeat(noteMeta.notePath.length - 1)}style.css`;
|
const cssUrl = `${"../".repeat(noteMeta.notePath.length - 1)}style.css`;
|
||||||
const htmlTitle = utils.escapeHtml(title);
|
const htmlTitle = utils.escapeHtml(title);
|
||||||
|
|
||||||
@ -354,7 +324,7 @@ async function exportToZip(taskContext, branch, format, res, setHeaders = true)
|
|||||||
return content.length < 100_000
|
return content.length < 100_000
|
||||||
? html.prettyPrint(content, {indent_size: 2})
|
? html.prettyPrint(content, {indent_size: 2})
|
||||||
: content;
|
: content;
|
||||||
} else if (noteMeta.format === 'markdown') {
|
} else if (noteMeta.format === 'markdown' && typeof content === "string") {
|
||||||
let markdownContent = mdService.toMarkdown(content);
|
let markdownContent = mdService.toMarkdown(content);
|
||||||
|
|
||||||
if (markdownContent.trim().length > 0 && !markdownContent.startsWith("# ")) {
|
if (markdownContent.trim().length > 0 && !markdownContent.startsWith("# ")) {
|
||||||
@ -368,17 +338,17 @@ ${markdownContent}`;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
function saveNote(noteMeta: NoteMeta, filePathPrefix: string) {
|
||||||
* @param {NoteMeta} noteMeta
|
|
||||||
* @param {string} filePathPrefix
|
|
||||||
*/
|
|
||||||
function saveNote(noteMeta, filePathPrefix) {
|
|
||||||
log.info(`Exporting note '${noteMeta.noteId}'`);
|
log.info(`Exporting note '${noteMeta.noteId}'`);
|
||||||
|
|
||||||
|
if (!noteMeta.noteId || !noteMeta.title) {
|
||||||
|
throw new Error("Missing note meta.");
|
||||||
|
}
|
||||||
|
|
||||||
if (noteMeta.isClone) {
|
if (noteMeta.isClone) {
|
||||||
const targetUrl = getNoteTargetUrl(noteMeta.noteId, noteMeta);
|
const targetUrl = getNoteTargetUrl(noteMeta.noteId, noteMeta);
|
||||||
|
|
||||||
let content = `<p>This is a clone of a note. Go to its <a href="${targetUrl}">primary location</a>.</p>`;
|
let content: string | Buffer = `<p>This is a clone of a note. Go to its <a href="${targetUrl}">primary location</a>.</p>`;
|
||||||
|
|
||||||
content = prepareContent(noteMeta.title, content, noteMeta);
|
content = prepareContent(noteMeta.title, content, noteMeta);
|
||||||
|
|
||||||
@ -388,6 +358,8 @@ ${markdownContent}`;
|
|||||||
}
|
}
|
||||||
|
|
||||||
const note = becca.getNote(noteMeta.noteId);
|
const note = becca.getNote(noteMeta.noteId);
|
||||||
|
if (!note) { throw new Error("Unable to find note."); }
|
||||||
|
if (!note.utcDateModified) { throw new Error("Unable to find modification date."); }
|
||||||
|
|
||||||
if (noteMeta.dataFileName) {
|
if (noteMeta.dataFileName) {
|
||||||
const content = prepareContent(noteMeta.title, note.getContent(), noteMeta);
|
const content = prepareContent(noteMeta.title, note.getContent(), noteMeta);
|
||||||
@ -400,7 +372,9 @@ ${markdownContent}`;
|
|||||||
|
|
||||||
taskContext.increaseProgressCount();
|
taskContext.increaseProgressCount();
|
||||||
|
|
||||||
for (const attachmentMeta of noteMeta.attachments) {
|
for (const attachmentMeta of noteMeta.attachments || []) {
|
||||||
|
if (!attachmentMeta.attachmentId) { continue; }
|
||||||
|
|
||||||
const attachment = note.getAttachmentById(attachmentMeta.attachmentId);
|
const attachment = note.getAttachmentById(attachmentMeta.attachmentId);
|
||||||
const content = attachment.getContent();
|
const content = attachment.getContent();
|
||||||
|
|
||||||
@ -410,29 +384,25 @@ ${markdownContent}`;
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (noteMeta.children?.length > 0) {
|
if (noteMeta.children?.length || 0 > 0) {
|
||||||
const directoryPath = filePathPrefix + noteMeta.dirFileName;
|
const directoryPath = filePathPrefix + noteMeta.dirFileName;
|
||||||
|
|
||||||
// create directory
|
// create directory
|
||||||
archive.append('', { name: `${directoryPath}/`, date: dateUtils.parseDateTime(note.utcDateModified) });
|
archive.append('', { name: `${directoryPath}/`, date: dateUtils.parseDateTime(note.utcDateModified) });
|
||||||
|
|
||||||
for (const childMeta of noteMeta.children) {
|
for (const childMeta of noteMeta.children || []) {
|
||||||
saveNote(childMeta, `${directoryPath}/`);
|
saveNote(childMeta, `${directoryPath}/`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
function saveNavigation(rootMeta: NoteMeta, navigationMeta: NoteMeta) {
|
||||||
* @param {NoteMeta} rootMeta
|
function saveNavigationInner(meta: NoteMeta) {
|
||||||
* @param {NoteMeta} navigationMeta
|
|
||||||
*/
|
|
||||||
function saveNavigation(rootMeta, navigationMeta) {
|
|
||||||
function saveNavigationInner(meta) {
|
|
||||||
let html = '<li>';
|
let html = '<li>';
|
||||||
|
|
||||||
const escapedTitle = utils.escapeHtml(`${meta.prefix ? `${meta.prefix} - ` : ''}${meta.title}`);
|
const escapedTitle = utils.escapeHtml(`${meta.prefix ? `${meta.prefix} - ` : ''}${meta.title}`);
|
||||||
|
|
||||||
if (meta.dataFileName) {
|
if (meta.dataFileName && meta.noteId) {
|
||||||
const targetUrl = getNoteTargetUrl(meta.noteId, rootMeta);
|
const targetUrl = getNoteTargetUrl(meta.noteId, rootMeta);
|
||||||
|
|
||||||
html += `<a href="${targetUrl}" target="detail">${escapedTitle}</a>`;
|
html += `<a href="${targetUrl}" target="detail">${escapedTitle}</a>`;
|
||||||
@ -470,16 +440,12 @@ ${markdownContent}`;
|
|||||||
archive.append(prettyHtml, { name: navigationMeta.dataFileName });
|
archive.append(prettyHtml, { name: navigationMeta.dataFileName });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
function saveIndex(rootMeta: NoteMeta, indexMeta: NoteMeta) {
|
||||||
* @param {NoteMeta} rootMeta
|
|
||||||
* @param {NoteMeta} indexMeta
|
|
||||||
*/
|
|
||||||
function saveIndex(rootMeta, indexMeta) {
|
|
||||||
let firstNonEmptyNote;
|
let firstNonEmptyNote;
|
||||||
let curMeta = rootMeta;
|
let curMeta = rootMeta;
|
||||||
|
|
||||||
while (!firstNonEmptyNote) {
|
while (!firstNonEmptyNote) {
|
||||||
if (curMeta.dataFileName) {
|
if (curMeta.dataFileName && curMeta.noteId) {
|
||||||
firstNonEmptyNote = getNoteTargetUrl(curMeta.noteId, rootMeta);
|
firstNonEmptyNote = getNoteTargetUrl(curMeta.noteId, rootMeta);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -506,17 +472,13 @@ ${markdownContent}`;
|
|||||||
archive.append(fullHtml, { name: indexMeta.dataFileName });
|
archive.append(fullHtml, { name: indexMeta.dataFileName });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
function saveCss(rootMeta: NoteMeta, cssMeta: NoteMeta) {
|
||||||
* @param {NoteMeta} rootMeta
|
|
||||||
* @param {NoteMeta} cssMeta
|
|
||||||
*/
|
|
||||||
function saveCss(rootMeta, cssMeta) {
|
|
||||||
const cssContent = fs.readFileSync(`${RESOURCE_DIR}/libraries/ckeditor/ckeditor-content.css`);
|
const cssContent = fs.readFileSync(`${RESOURCE_DIR}/libraries/ckeditor/ckeditor-content.css`);
|
||||||
|
|
||||||
archive.append(cssContent, { name: cssMeta.dataFileName });
|
archive.append(cssContent, { name: cssMeta.dataFileName });
|
||||||
}
|
}
|
||||||
|
|
||||||
const existingFileNames = format === 'html' ? ['navigation', 'index'] : [];
|
const existingFileNames: Record<string, number> = format === 'html' ? {'navigation': 0, 'index': 1} : {};
|
||||||
const rootMeta = createNoteMeta(branch, { notePath: [] }, existingFileNames);
|
const rootMeta = createNoteMeta(branch, { notePath: [] }, existingFileNames);
|
||||||
|
|
||||||
const metaFile = {
|
const metaFile = {
|
||||||
@ -525,7 +487,9 @@ ${markdownContent}`;
|
|||||||
files: [ rootMeta ]
|
files: [ rootMeta ]
|
||||||
};
|
};
|
||||||
|
|
||||||
let navigationMeta, indexMeta, cssMeta;
|
let navigationMeta: NoteMeta | null = null;
|
||||||
|
let indexMeta: NoteMeta | null = null;
|
||||||
|
let cssMeta: NoteMeta | null = null;
|
||||||
|
|
||||||
if (format === 'html') {
|
if (format === 'html') {
|
||||||
navigationMeta = {
|
navigationMeta = {
|
||||||
@ -552,7 +516,7 @@ ${markdownContent}`;
|
|||||||
|
|
||||||
for (const noteMeta of Object.values(noteIdToMeta)) {
|
for (const noteMeta of Object.values(noteIdToMeta)) {
|
||||||
// filter out relations which are not inside this export
|
// filter out relations which are not inside this export
|
||||||
noteMeta.attributes = noteMeta.attributes.filter(attr => {
|
noteMeta.attributes = (noteMeta.attributes || []).filter(attr => {
|
||||||
if (attr.type !== 'relation') {
|
if (attr.type !== 'relation') {
|
||||||
return true;
|
return true;
|
||||||
} else if (attr.value in noteIdToMeta) {
|
} else if (attr.value in noteIdToMeta) {
|
||||||
@ -567,7 +531,9 @@ ${markdownContent}`;
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!rootMeta) { // corner case of disabled export for exported note
|
if (!rootMeta) { // corner case of disabled export for exported note
|
||||||
res.sendStatus(400);
|
if ("sendStatus" in res) {
|
||||||
|
res.sendStatus(400);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -578,6 +544,10 @@ ${markdownContent}`;
|
|||||||
saveNote(rootMeta, '');
|
saveNote(rootMeta, '');
|
||||||
|
|
||||||
if (format === 'html') {
|
if (format === 'html') {
|
||||||
|
if (!navigationMeta || !indexMeta || !cssMeta) {
|
||||||
|
throw new Error("Missing meta.");
|
||||||
|
}
|
||||||
|
|
||||||
saveNavigation(rootMeta, navigationMeta);
|
saveNavigation(rootMeta, navigationMeta);
|
||||||
saveIndex(rootMeta, indexMeta);
|
saveIndex(rootMeta, indexMeta);
|
||||||
saveCss(rootMeta, cssMeta);
|
saveCss(rootMeta, cssMeta);
|
||||||
@ -586,7 +556,7 @@ ${markdownContent}`;
|
|||||||
const note = branch.getNote();
|
const note = branch.getNote();
|
||||||
const zipFileName = `${branch.prefix ? `${branch.prefix} - ` : ""}${note.getTitleOrProtected()}.zip`;
|
const zipFileName = `${branch.prefix ? `${branch.prefix} - ` : ""}${note.getTitleOrProtected()}.zip`;
|
||||||
|
|
||||||
if (setHeaders) {
|
if (setHeaders && "setHeader" in res) {
|
||||||
res.setHeader('Content-Disposition', utils.getContentDisposition(zipFileName));
|
res.setHeader('Content-Disposition', utils.getContentDisposition(zipFileName));
|
||||||
res.setHeader('Content-Type', 'application/zip');
|
res.setHeader('Content-Type', 'application/zip');
|
||||||
}
|
}
|
||||||
@ -597,7 +567,7 @@ ${markdownContent}`;
|
|||||||
taskContext.taskSucceeded();
|
taskContext.taskSucceeded();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function exportToZipFile(noteId, format, zipFilePath) {
|
async function exportToZipFile(noteId: string, format: "markdown" | "html", zipFilePath: string) {
|
||||||
const fileOutputStream = fs.createWriteStream(zipFilePath);
|
const fileOutputStream = fs.createWriteStream(zipFilePath);
|
||||||
const taskContext = new TaskContext('no-progress-reporting');
|
const taskContext = new TaskContext('no-progress-reporting');
|
||||||
|
|
||||||
@ -612,7 +582,7 @@ async function exportToZipFile(noteId, format, zipFilePath) {
|
|||||||
log.info(`Exported '${noteId}' with format '${format}' to '${zipFilePath}'`);
|
log.info(`Exported '${noteId}' with format '${format}' to '${zipFilePath}'`);
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
export = {
|
||||||
exportToZip,
|
exportToZip,
|
||||||
exportToZipFile
|
exportToZipFile
|
||||||
};
|
};
|
@ -1,19 +1,19 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const becca = require('../becca/becca');
|
import becca = require('../becca/becca');
|
||||||
const log = require('./log');
|
import log = require('./log');
|
||||||
const protectedSessionService = require('./protected_session');
|
import protectedSessionService = require('./protected_session');
|
||||||
const noteService = require('./notes');
|
import noteService = require('./notes');
|
||||||
const optionService = require('./options');
|
import optionService = require('./options');
|
||||||
const sql = require('./sql');
|
import sql = require('./sql');
|
||||||
const jimp = require('jimp');
|
import jimp = require('jimp');
|
||||||
const imageType = require('image-type');
|
import imageType = require('image-type');
|
||||||
const sanitizeFilename = require('sanitize-filename');
|
import sanitizeFilename = require('sanitize-filename');
|
||||||
const isSvg = require('is-svg');
|
import isSvg = require('is-svg');
|
||||||
const isAnimated = require('is-animated');
|
import isAnimated = require('is-animated');
|
||||||
const htmlSanitizer = require('./html_sanitizer');
|
import htmlSanitizer = require('./html_sanitizer');
|
||||||
|
|
||||||
async function processImage(uploadBuffer, originalName, shrinkImageSwitch) {
|
async function processImage(uploadBuffer: Buffer, originalName: string, shrinkImageSwitch: boolean) {
|
||||||
const compressImages = optionService.getOptionBool("compressImages");
|
const compressImages = optionService.getOptionBool("compressImages");
|
||||||
const origImageFormat = getImageType(uploadBuffer);
|
const origImageFormat = getImageType(uploadBuffer);
|
||||||
|
|
||||||
@ -44,7 +44,7 @@ async function processImage(uploadBuffer, originalName, shrinkImageSwitch) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function getImageType(buffer) {
|
function getImageType(buffer: Buffer) {
|
||||||
if (isSvg(buffer)) {
|
if (isSvg(buffer)) {
|
||||||
return {
|
return {
|
||||||
ext: 'svg'
|
ext: 'svg'
|
||||||
@ -57,18 +57,19 @@ function getImageType(buffer) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getImageMimeFromExtension(ext) {
|
function getImageMimeFromExtension(ext: string) {
|
||||||
ext = ext.toLowerCase();
|
ext = ext.toLowerCase();
|
||||||
|
|
||||||
return `image/${ext === 'svg' ? 'svg+xml' : ext}`;
|
return `image/${ext === 'svg' ? 'svg+xml' : ext}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateImage(noteId, uploadBuffer, originalName) {
|
function updateImage(noteId: string, uploadBuffer: Buffer, originalName: string) {
|
||||||
log.info(`Updating image ${noteId}: ${originalName}`);
|
log.info(`Updating image ${noteId}: ${originalName}`);
|
||||||
|
|
||||||
originalName = htmlSanitizer.sanitize(originalName);
|
originalName = htmlSanitizer.sanitize(originalName);
|
||||||
|
|
||||||
const note = becca.getNote(noteId);
|
const note = becca.getNote(noteId);
|
||||||
|
if (!note) { throw new Error("Unable to find note."); }
|
||||||
|
|
||||||
note.saveRevision();
|
note.saveRevision();
|
||||||
|
|
||||||
@ -85,7 +86,7 @@ function updateImage(noteId, uploadBuffer, originalName) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveImage(parentNoteId, uploadBuffer, originalName, shrinkImageSwitch, trimFilename = false) {
|
function saveImage(parentNoteId: string, uploadBuffer: Buffer, originalName: string, shrinkImageSwitch: boolean, trimFilename = false) {
|
||||||
log.info(`Saving image ${originalName} into parent ${parentNoteId}`);
|
log.info(`Saving image ${originalName} into parent ${parentNoteId}`);
|
||||||
|
|
||||||
if (trimFilename && originalName.length > 40) {
|
if (trimFilename && originalName.length > 40) {
|
||||||
@ -95,6 +96,7 @@ function saveImage(parentNoteId, uploadBuffer, originalName, shrinkImageSwitch,
|
|||||||
|
|
||||||
const fileName = sanitizeFilename(originalName);
|
const fileName = sanitizeFilename(originalName);
|
||||||
const parentNote = becca.getNote(parentNoteId);
|
const parentNote = becca.getNote(parentNoteId);
|
||||||
|
if (!parentNote) { throw new Error("Unable to find parent note."); }
|
||||||
|
|
||||||
const {note} = noteService.createNewNote({
|
const {note} = noteService.createNewNote({
|
||||||
parentNoteId,
|
parentNoteId,
|
||||||
@ -131,7 +133,7 @@ function saveImage(parentNoteId, uploadBuffer, originalName, shrinkImageSwitch,
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveImageToAttachment(noteId, uploadBuffer, originalName, shrinkImageSwitch, trimFilename = false) {
|
function saveImageToAttachment(noteId: string, uploadBuffer: Buffer, originalName: string, shrinkImageSwitch?: boolean, trimFilename = false) {
|
||||||
log.info(`Saving image '${originalName}' as attachment into note '${noteId}'`);
|
log.info(`Saving image '${originalName}' as attachment into note '${noteId}'`);
|
||||||
|
|
||||||
if (trimFilename && originalName.length > 40) {
|
if (trimFilename && originalName.length > 40) {
|
||||||
@ -160,9 +162,10 @@ function saveImageToAttachment(noteId, uploadBuffer, originalName, shrinkImageSw
|
|||||||
}, 5000);
|
}, 5000);
|
||||||
|
|
||||||
// resizing images asynchronously since JIMP does not support sync operation
|
// resizing images asynchronously since JIMP does not support sync operation
|
||||||
processImage(uploadBuffer, originalName, shrinkImageSwitch).then(({buffer, imageFormat}) => {
|
processImage(uploadBuffer, originalName, !!shrinkImageSwitch).then(({buffer, imageFormat}) => {
|
||||||
sql.transactional(() => {
|
sql.transactional(() => {
|
||||||
// re-read, might be changed in the meantime
|
// re-read, might be changed in the meantime
|
||||||
|
if (!attachment.attachmentId) { throw new Error("Missing attachment ID."); }
|
||||||
attachment = becca.getAttachmentOrThrow(attachment.attachmentId);
|
attachment = becca.getAttachmentOrThrow(attachment.attachmentId);
|
||||||
|
|
||||||
attachment.mime = getImageMimeFromExtension(imageFormat.ext);
|
attachment.mime = getImageMimeFromExtension(imageFormat.ext);
|
||||||
@ -179,7 +182,7 @@ function saveImageToAttachment(noteId, uploadBuffer, originalName, shrinkImageSw
|
|||||||
return attachment;
|
return attachment;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function shrinkImage(buffer, originalName) {
|
async function shrinkImage(buffer: Buffer, originalName: string) {
|
||||||
let jpegQuality = optionService.getOptionInt('imageJpegQuality', 0);
|
let jpegQuality = optionService.getOptionInt('imageJpegQuality', 0);
|
||||||
|
|
||||||
if (jpegQuality < 10 || jpegQuality > 100) {
|
if (jpegQuality < 10 || jpegQuality > 100) {
|
||||||
@ -190,7 +193,7 @@ async function shrinkImage(buffer, originalName) {
|
|||||||
try {
|
try {
|
||||||
finalImageBuffer = await resize(buffer, jpegQuality);
|
finalImageBuffer = await resize(buffer, jpegQuality);
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e: any) {
|
||||||
log.error(`Failed to resize image '${originalName}', stack: ${e.stack}`);
|
log.error(`Failed to resize image '${originalName}', stack: ${e.stack}`);
|
||||||
|
|
||||||
finalImageBuffer = buffer;
|
finalImageBuffer = buffer;
|
||||||
@ -205,7 +208,7 @@ async function shrinkImage(buffer, originalName) {
|
|||||||
return finalImageBuffer;
|
return finalImageBuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function resize(buffer, quality) {
|
async function resize(buffer: Buffer, quality: number) {
|
||||||
const imageMaxWidthHeight = optionService.getOptionInt('imageMaxWidthHeight');
|
const imageMaxWidthHeight = optionService.getOptionInt('imageMaxWidthHeight');
|
||||||
|
|
||||||
const start = Date.now();
|
const start = Date.now();
|
||||||
@ -231,7 +234,7 @@ async function resize(buffer, quality) {
|
|||||||
return resultBuffer;
|
return resultBuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
export = {
|
||||||
saveImage,
|
saveImage,
|
||||||
saveImageToAttachment,
|
saveImageToAttachment,
|
||||||
updateImage
|
updateImage
|
5
src/services/import/common.ts
Normal file
5
src/services/import/common.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export interface File {
|
||||||
|
originalname: string;
|
||||||
|
mimetype: string;
|
||||||
|
buffer: string | Buffer;
|
||||||
|
}
|
@ -1,20 +1,23 @@
|
|||||||
const sax = require("sax");
|
import sax = require("sax");
|
||||||
const stream = require('stream');
|
import stream = require('stream');
|
||||||
const {Throttle} = require('stream-throttle');
|
import { Throttle } from 'stream-throttle';
|
||||||
const log = require('../log');
|
import log = require('../log');
|
||||||
const utils = require('../utils');
|
import utils = require('../utils');
|
||||||
const sql = require('../sql');
|
import sql = require('../sql');
|
||||||
const noteService = require('../notes');
|
import noteService = require('../notes');
|
||||||
const imageService = require('../image.js');
|
import imageService = require('../image');
|
||||||
const protectedSessionService = require('../protected_session');
|
import protectedSessionService = require('../protected_session');
|
||||||
const htmlSanitizer = require('../html_sanitizer');
|
import htmlSanitizer = require('../html_sanitizer');
|
||||||
const {sanitizeAttributeName} = require('../sanitize_attribute_name');
|
import sanitizeAttributeName = require('../sanitize_attribute_name');
|
||||||
|
import TaskContext = require("../task_context");
|
||||||
|
import BNote = require("../../becca/entities/bnote");
|
||||||
|
import { File } from "./common";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* date format is e.g. 20181121T193703Z or 2013-04-14T16:19:00.000Z (Mac evernote, see #3496)
|
* date format is e.g. 20181121T193703Z or 2013-04-14T16:19:00.000Z (Mac evernote, see #3496)
|
||||||
* @returns trilium date format, e.g. 2013-04-14 16:19:00.000Z
|
* @returns trilium date format, e.g. 2013-04-14 16:19:00.000Z
|
||||||
*/
|
*/
|
||||||
function parseDate(text) {
|
function parseDate(text: string) {
|
||||||
// convert ISO format to the "20181121T193703Z" format
|
// convert ISO format to the "20181121T193703Z" format
|
||||||
text = text.replace(/[-:]/g, "");
|
text = text.replace(/[-:]/g, "");
|
||||||
|
|
||||||
@ -25,10 +28,34 @@ function parseDate(text) {
|
|||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
let note = {};
|
interface Attribute {
|
||||||
let resource;
|
type: string;
|
||||||
|
name: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
function importEnex(taskContext, file, parentNote) {
|
interface Resource {
|
||||||
|
title: string;
|
||||||
|
content?: Buffer | string;
|
||||||
|
mime?: string;
|
||||||
|
attributes: Attribute[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Note {
|
||||||
|
title: string;
|
||||||
|
attributes: Attribute[];
|
||||||
|
utcDateCreated: string;
|
||||||
|
utcDateModified: string;
|
||||||
|
noteId: string;
|
||||||
|
blobId: string;
|
||||||
|
content: string;
|
||||||
|
resources: Resource[]
|
||||||
|
}
|
||||||
|
|
||||||
|
let note: Partial<Note> = {};
|
||||||
|
let resource: Resource;
|
||||||
|
|
||||||
|
function importEnex(taskContext: TaskContext, file: File, parentNote: BNote) {
|
||||||
const saxStream = sax.createStream(true);
|
const saxStream = sax.createStream(true);
|
||||||
|
|
||||||
const rootNoteTitle = file.originalname.toLowerCase().endsWith(".enex")
|
const rootNoteTitle = file.originalname.toLowerCase().endsWith(".enex")
|
||||||
@ -45,7 +72,7 @@ function importEnex(taskContext, file, parentNote) {
|
|||||||
isProtected: parentNote.isProtected && protectedSessionService.isProtectedSessionAvailable(),
|
isProtected: parentNote.isProtected && protectedSessionService.isProtectedSessionAvailable(),
|
||||||
}).note;
|
}).note;
|
||||||
|
|
||||||
function extractContent(content) {
|
function extractContent(content: string) {
|
||||||
const openingNoteIndex = content.indexOf('<en-note>');
|
const openingNoteIndex = content.indexOf('<en-note>');
|
||||||
|
|
||||||
if (openingNoteIndex !== -1) {
|
if (openingNoteIndex !== -1) {
|
||||||
@ -90,7 +117,7 @@ function importEnex(taskContext, file, parentNote) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const path = [];
|
const path: string[] = [];
|
||||||
|
|
||||||
function getCurrentTag() {
|
function getCurrentTag() {
|
||||||
if (path.length >= 1) {
|
if (path.length >= 1) {
|
||||||
@ -108,8 +135,8 @@ function importEnex(taskContext, file, parentNote) {
|
|||||||
// unhandled errors will throw, since this is a proper node event emitter.
|
// unhandled errors will throw, since this is a proper node event emitter.
|
||||||
log.error(`error when parsing ENEX file: ${e}`);
|
log.error(`error when parsing ENEX file: ${e}`);
|
||||||
// clear the error
|
// clear the error
|
||||||
this._parser.error = null;
|
(saxStream._parser as any).error = null;
|
||||||
this._parser.resume();
|
saxStream._parser.resume();
|
||||||
});
|
});
|
||||||
|
|
||||||
saxStream.on("text", text => {
|
saxStream.on("text", text => {
|
||||||
@ -123,13 +150,15 @@ function importEnex(taskContext, file, parentNote) {
|
|||||||
labelName = 'pageUrl';
|
labelName = 'pageUrl';
|
||||||
}
|
}
|
||||||
|
|
||||||
labelName = sanitizeAttributeName(labelName);
|
labelName = sanitizeAttributeName.sanitizeAttributeName(labelName || "");
|
||||||
|
|
||||||
note.attributes.push({
|
if (note.attributes) {
|
||||||
type: 'label',
|
note.attributes.push({
|
||||||
name: labelName,
|
type: 'label',
|
||||||
value: text
|
name: labelName,
|
||||||
});
|
value: text
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (previousTag === 'resource-attributes') {
|
else if (previousTag === 'resource-attributes') {
|
||||||
if (currentTag === 'file-name') {
|
if (currentTag === 'file-name') {
|
||||||
@ -169,10 +198,10 @@ function importEnex(taskContext, file, parentNote) {
|
|||||||
note.utcDateCreated = parseDate(text);
|
note.utcDateCreated = parseDate(text);
|
||||||
} else if (currentTag === 'updated') {
|
} else if (currentTag === 'updated') {
|
||||||
note.utcDateModified = parseDate(text);
|
note.utcDateModified = parseDate(text);
|
||||||
} else if (currentTag === 'tag') {
|
} else if (currentTag === 'tag' && note.attributes) {
|
||||||
note.attributes.push({
|
note.attributes.push({
|
||||||
type: 'label',
|
type: 'label',
|
||||||
name: sanitizeAttributeName(text),
|
name: sanitizeAttributeName.sanitizeAttributeName(text),
|
||||||
value: ''
|
value: ''
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -201,11 +230,13 @@ function importEnex(taskContext, file, parentNote) {
|
|||||||
attributes: []
|
attributes: []
|
||||||
};
|
};
|
||||||
|
|
||||||
note.resources.push(resource);
|
if (note.resources) {
|
||||||
|
note.resources.push(resource);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function updateDates(note, utcDateCreated, utcDateModified) {
|
function updateDates(note: BNote, utcDateCreated?: string, utcDateModified?: string) {
|
||||||
// it's difficult to force custom dateCreated and dateModified to Note entity, so we do it post-creation with SQL
|
// it's difficult to force custom dateCreated and dateModified to Note entity, so we do it post-creation with SQL
|
||||||
sql.execute(`
|
sql.execute(`
|
||||||
UPDATE notes
|
UPDATE notes
|
||||||
@ -227,6 +258,10 @@ function importEnex(taskContext, file, parentNote) {
|
|||||||
// make a copy because stream continues with the next call and note gets overwritten
|
// make a copy because stream continues with the next call and note gets overwritten
|
||||||
let {title, content, attributes, resources, utcDateCreated, utcDateModified} = note;
|
let {title, content, attributes, resources, utcDateCreated, utcDateModified} = note;
|
||||||
|
|
||||||
|
if (!title || !content) {
|
||||||
|
throw new Error("Missing title or content for note.");
|
||||||
|
}
|
||||||
|
|
||||||
content = extractContent(content);
|
content = extractContent(content);
|
||||||
|
|
||||||
const noteEntity = noteService.createNewNote({
|
const noteEntity = noteService.createNewNote({
|
||||||
@ -239,7 +274,7 @@ function importEnex(taskContext, file, parentNote) {
|
|||||||
isProtected: parentNote.isProtected && protectedSessionService.isProtectedSessionAvailable(),
|
isProtected: parentNote.isProtected && protectedSessionService.isProtectedSessionAvailable(),
|
||||||
}).note;
|
}).note;
|
||||||
|
|
||||||
for (const attr of attributes) {
|
for (const attr of attributes || []) {
|
||||||
noteEntity.addAttribute(attr.type, attr.name, attr.value);
|
noteEntity.addAttribute(attr.type, attr.name, attr.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -249,12 +284,14 @@ function importEnex(taskContext, file, parentNote) {
|
|||||||
|
|
||||||
taskContext.increaseProgressCount();
|
taskContext.increaseProgressCount();
|
||||||
|
|
||||||
for (const resource of resources) {
|
for (const resource of resources || []) {
|
||||||
if (!resource.content) {
|
if (!resource.content) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
resource.content = utils.fromBase64(resource.content);
|
if (typeof resource.content === "string") {
|
||||||
|
resource.content = utils.fromBase64(resource.content);
|
||||||
|
}
|
||||||
|
|
||||||
const hash = utils.md5(resource.content);
|
const hash = utils.md5(resource.content);
|
||||||
|
|
||||||
@ -273,6 +310,10 @@ function importEnex(taskContext, file, parentNote) {
|
|||||||
resource.mime = resource.mime || "application/octet-stream";
|
resource.mime = resource.mime || "application/octet-stream";
|
||||||
|
|
||||||
const createFileNote = () => {
|
const createFileNote = () => {
|
||||||
|
if (typeof resource.content !== "string") {
|
||||||
|
throw new Error("Missing or wrong content type for resource.");
|
||||||
|
}
|
||||||
|
|
||||||
const resourceNote = noteService.createNewNote({
|
const resourceNote = noteService.createNewNote({
|
||||||
parentNoteId: noteEntity.noteId,
|
parentNoteId: noteEntity.noteId,
|
||||||
title: resource.title,
|
title: resource.title,
|
||||||
@ -292,7 +333,7 @@ function importEnex(taskContext, file, parentNote) {
|
|||||||
|
|
||||||
const resourceLink = `<a href="#root/${resourceNote.noteId}">${utils.escapeHtml(resource.title)}</a>`;
|
const resourceLink = `<a href="#root/${resourceNote.noteId}">${utils.escapeHtml(resource.title)}</a>`;
|
||||||
|
|
||||||
content = content.replace(mediaRegex, resourceLink);
|
content = (content || "").replace(mediaRegex, resourceLink);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (resource.mime && resource.mime.startsWith('image/')) {
|
if (resource.mime && resource.mime.startsWith('image/')) {
|
||||||
@ -301,7 +342,7 @@ function importEnex(taskContext, file, parentNote) {
|
|||||||
? resource.title
|
? resource.title
|
||||||
: `image.${resource.mime.substr(6)}`; // default if real name is not present
|
: `image.${resource.mime.substr(6)}`; // default if real name is not present
|
||||||
|
|
||||||
const attachment = imageService.saveImageToAttachment(noteEntity.noteId, resource.content, originalName, taskContext.data.shrinkImages);
|
const attachment = imageService.saveImageToAttachment(noteEntity.noteId, resource.content, originalName, !!taskContext.data?.shrinkImages);
|
||||||
|
|
||||||
const encodedTitle = encodeURIComponent(attachment.title);
|
const encodedTitle = encodeURIComponent(attachment.title);
|
||||||
const url = `api/attachments/${attachment.attachmentId}/image/${encodedTitle}`;
|
const url = `api/attachments/${attachment.attachmentId}/image/${encodedTitle}`;
|
||||||
@ -314,7 +355,7 @@ function importEnex(taskContext, file, parentNote) {
|
|||||||
// otherwise the image would be removed since no note would include it
|
// otherwise the image would be removed since no note would include it
|
||||||
content += imageLink;
|
content += imageLink;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e: any) {
|
||||||
log.error(`error when saving image from ENEX file: ${e.message}`);
|
log.error(`error when saving image from ENEX file: ${e.message}`);
|
||||||
createFileNote();
|
createFileNote();
|
||||||
}
|
}
|
||||||
@ -368,4 +409,4 @@ function importEnex(taskContext, file, parentNote) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = { importEnex };
|
export = { importEnex };
|
@ -1,18 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
const marked = require("marked");
|
|
||||||
const htmlSanitizer = require('../html_sanitizer');
|
|
||||||
const importUtils = require('./utils');
|
|
||||||
|
|
||||||
function renderToHtml(content, title) {
|
|
||||||
const html = marked.parse(content, {
|
|
||||||
mangle: false,
|
|
||||||
headerIds: false
|
|
||||||
});
|
|
||||||
const h1Handled = importUtils.handleH1(html, title); // h1 handling needs to come before sanitization
|
|
||||||
return htmlSanitizer.sanitize(h1Handled);
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
renderToHtml
|
|
||||||
};
|
|
17
src/services/import/markdown.ts
Normal file
17
src/services/import/markdown.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
import marked = require("marked");
|
||||||
|
import htmlSanitizer = require('../html_sanitizer');
|
||||||
|
import importUtils = require('./utils');
|
||||||
|
|
||||||
|
function renderToHtml(content: string, title: string) {
|
||||||
|
const html = marked.parse(content, {
|
||||||
|
async: false
|
||||||
|
}) as string;
|
||||||
|
const h1Handled = importUtils.handleH1(html, title); // h1 handling needs to come before sanitization
|
||||||
|
return htmlSanitizer.sanitize(h1Handled);
|
||||||
|
}
|
||||||
|
|
||||||
|
export = {
|
||||||
|
renderToHtml
|
||||||
|
};
|
@ -1,9 +1,10 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const mimeTypes = require('mime-types');
|
import mimeTypes = require('mime-types');
|
||||||
const path = require('path');
|
import path = require('path');
|
||||||
|
import { TaskData } from '../task_context_interface';
|
||||||
|
|
||||||
const CODE_MIME_TYPES = {
|
const CODE_MIME_TYPES: Record<string, boolean | string> = {
|
||||||
'text/plain': true,
|
'text/plain': true,
|
||||||
'text/x-csrc': true,
|
'text/x-csrc': true,
|
||||||
'text/x-c++src': true,
|
'text/x-c++src': true,
|
||||||
@ -44,7 +45,7 @@ const CODE_MIME_TYPES = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// extensions missing in mime-db
|
// extensions missing in mime-db
|
||||||
const EXTENSION_TO_MIME = {
|
const EXTENSION_TO_MIME: Record<string, string> = {
|
||||||
".c": "text/x-csrc",
|
".c": "text/x-csrc",
|
||||||
".cs": "text/x-csharp",
|
".cs": "text/x-csharp",
|
||||||
".clj": "text/x-clojure",
|
".clj": "text/x-clojure",
|
||||||
@ -65,7 +66,7 @@ const EXTENSION_TO_MIME = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/** @returns false if MIME is not detected */
|
/** @returns false if MIME is not detected */
|
||||||
function getMime(fileName) {
|
function getMime(fileName: string) {
|
||||||
if (fileName.toLowerCase() === 'dockerfile') {
|
if (fileName.toLowerCase() === 'dockerfile') {
|
||||||
return "text/x-dockerfile";
|
return "text/x-dockerfile";
|
||||||
}
|
}
|
||||||
@ -79,7 +80,7 @@ function getMime(fileName) {
|
|||||||
return mimeTypes.lookup(fileName);
|
return mimeTypes.lookup(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getType(options, mime) {
|
function getType(options: TaskData, mime: string) {
|
||||||
mime = mime ? mime.toLowerCase() : '';
|
mime = mime ? mime.toLowerCase() : '';
|
||||||
|
|
||||||
if (options.textImportedAsText && (mime === 'text/html' || ['text/markdown', 'text/x-markdown'].includes(mime))) {
|
if (options.textImportedAsText && (mime === 'text/html' || ['text/markdown', 'text/x-markdown'].includes(mime))) {
|
||||||
@ -96,18 +97,20 @@ function getType(options, mime) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeMimeType(mime) {
|
function normalizeMimeType(mime: string) {
|
||||||
mime = mime ? mime.toLowerCase() : '';
|
mime = mime ? mime.toLowerCase() : '';
|
||||||
|
const mappedMime = CODE_MIME_TYPES[mime];
|
||||||
|
|
||||||
if (!(mime in CODE_MIME_TYPES) || CODE_MIME_TYPES[mime] === true) {
|
if (mappedMime === true) {
|
||||||
return mime;
|
return mime;
|
||||||
|
} else if (typeof mappedMime === "string") {
|
||||||
|
return mappedMime;
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
return CODE_MIME_TYPES[mime];
|
return undefined;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
export = {
|
||||||
getMime,
|
getMime,
|
||||||
getType,
|
getType,
|
||||||
normalizeMimeType
|
normalizeMimeType
|
@ -1,20 +1,37 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const noteService = require('../../services/notes');
|
import noteService = require('../../services/notes');
|
||||||
const parseString = require('xml2js').parseString;
|
import xml2js = require("xml2js");
|
||||||
const protectedSessionService = require('../protected_session');
|
import protectedSessionService = require('../protected_session');
|
||||||
const htmlSanitizer = require('../html_sanitizer');
|
import htmlSanitizer = require('../html_sanitizer');
|
||||||
|
import TaskContext = require('../task_context');
|
||||||
|
import BNote = require('../../becca/entities/bnote');
|
||||||
|
const parseString = xml2js.parseString;
|
||||||
|
|
||||||
/**
|
interface OpmlXml {
|
||||||
* @param {TaskContext} taskContext
|
opml: OpmlBody;
|
||||||
* @param {Buffer} fileBuffer
|
}
|
||||||
* @param {BNote} parentNote
|
|
||||||
* @returns {Promise<*[]|*>}
|
interface OpmlBody {
|
||||||
*/
|
$: {
|
||||||
async function importOpml(taskContext, fileBuffer, parentNote) {
|
version: string
|
||||||
const xml = await new Promise(function(resolve, reject)
|
}
|
||||||
|
body: OpmlOutline[]
|
||||||
|
}
|
||||||
|
|
||||||
|
interface OpmlOutline {
|
||||||
|
$: {
|
||||||
|
title: string;
|
||||||
|
text: string;
|
||||||
|
_note: string;
|
||||||
|
};
|
||||||
|
outline: OpmlOutline[];
|
||||||
|
}
|
||||||
|
|
||||||
|
async function importOpml(taskContext: TaskContext, fileBuffer: Buffer, parentNote: BNote) {
|
||||||
|
const xml = await new Promise<OpmlXml>(function(resolve, reject)
|
||||||
{
|
{
|
||||||
parseString(fileBuffer, function (err, result) {
|
parseString(fileBuffer, function (err: any, result: OpmlXml) {
|
||||||
if (err) {
|
if (err) {
|
||||||
reject(err);
|
reject(err);
|
||||||
}
|
}
|
||||||
@ -30,7 +47,7 @@ async function importOpml(taskContext, fileBuffer, parentNote) {
|
|||||||
|
|
||||||
const opmlVersion = parseInt(xml.opml.$.version);
|
const opmlVersion = parseInt(xml.opml.$.version);
|
||||||
|
|
||||||
function importOutline(outline, parentNoteId) {
|
function importOutline(outline: OpmlOutline, parentNoteId: string) {
|
||||||
let title, content;
|
let title, content;
|
||||||
|
|
||||||
if (opmlVersion === 1) {
|
if (opmlVersion === 1) {
|
||||||
@ -83,7 +100,7 @@ async function importOpml(taskContext, fileBuffer, parentNote) {
|
|||||||
return returnNote;
|
return returnNote;
|
||||||
}
|
}
|
||||||
|
|
||||||
function toHtml(text) {
|
function toHtml(text: string) {
|
||||||
if (!text) {
|
if (!text) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
@ -91,6 +108,6 @@ function toHtml(text) {
|
|||||||
return `<p>${text.replace(/(?:\r\n|\r|\n)/g, '</p><p>')}</p>`;
|
return `<p>${text.replace(/(?:\r\n|\r|\n)/g, '</p><p>')}</p>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
export = {
|
||||||
importOpml
|
importOpml
|
||||||
};
|
};
|
@ -1,18 +1,22 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const noteService = require('../../services/notes');
|
import BNote = require("../../becca/entities/bnote");
|
||||||
const imageService = require('../../services/image.js');
|
import TaskContext = require("../task_context");
|
||||||
const protectedSessionService = require('../protected_session');
|
|
||||||
const markdownService = require('./markdown.js');
|
|
||||||
const mimeService = require('./mime.js');
|
|
||||||
const utils = require('../../services/utils');
|
|
||||||
const importUtils = require('./utils');
|
|
||||||
const htmlSanitizer = require('../html_sanitizer');
|
|
||||||
|
|
||||||
function importSingleFile(taskContext, file, parentNote) {
|
import noteService = require('../../services/notes');
|
||||||
|
import imageService = require('../../services/image');
|
||||||
|
import protectedSessionService = require('../protected_session');
|
||||||
|
import markdownService = require('./markdown');
|
||||||
|
import mimeService = require('./mime');
|
||||||
|
import utils = require('../../services/utils');
|
||||||
|
import importUtils = require('./utils');
|
||||||
|
import htmlSanitizer = require('../html_sanitizer');
|
||||||
|
import { File } from "./common";
|
||||||
|
|
||||||
|
function importSingleFile(taskContext: TaskContext, file: File, parentNote: BNote) {
|
||||||
const mime = mimeService.getMime(file.originalname) || file.mimetype;
|
const mime = mimeService.getMime(file.originalname) || file.mimetype;
|
||||||
|
|
||||||
if (taskContext.data.textImportedAsText) {
|
if (taskContext?.data?.textImportedAsText) {
|
||||||
if (mime === 'text/html') {
|
if (mime === 'text/html') {
|
||||||
return importHtml(taskContext, file, parentNote);
|
return importHtml(taskContext, file, parentNote);
|
||||||
} else if (['text/markdown', 'text/x-markdown'].includes(mime)) {
|
} else if (['text/markdown', 'text/x-markdown'].includes(mime)) {
|
||||||
@ -22,7 +26,7 @@ function importSingleFile(taskContext, file, parentNote) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (taskContext.data.codeImportedAsCode && mimeService.getType(taskContext.data, mime) === 'code') {
|
if (taskContext?.data?.codeImportedAsCode && mimeService.getType(taskContext.data, mime) === 'code') {
|
||||||
return importCodeNote(taskContext, file, parentNote);
|
return importCodeNote(taskContext, file, parentNote);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -33,15 +37,21 @@ function importSingleFile(taskContext, file, parentNote) {
|
|||||||
return importFile(taskContext, file, parentNote);
|
return importFile(taskContext, file, parentNote);
|
||||||
}
|
}
|
||||||
|
|
||||||
function importImage(file, parentNote, taskContext) {
|
function importImage(file: File, parentNote: BNote, taskContext: TaskContext) {
|
||||||
const {note} = imageService.saveImage(parentNote.noteId, file.buffer, file.originalname, taskContext.data.shrinkImages);
|
if (typeof file.buffer === "string") {
|
||||||
|
throw new Error("Invalid file content for image.");
|
||||||
|
}
|
||||||
|
const {note} = imageService.saveImage(parentNote.noteId, file.buffer, file.originalname, !!taskContext.data?.shrinkImages);
|
||||||
|
|
||||||
taskContext.increaseProgressCount();
|
taskContext.increaseProgressCount();
|
||||||
|
|
||||||
return note;
|
return note;
|
||||||
}
|
}
|
||||||
|
|
||||||
function importFile(taskContext, file, parentNote) {
|
function importFile(taskContext: TaskContext, file: File, parentNote: BNote) {
|
||||||
|
if (typeof file.buffer !== "string") {
|
||||||
|
throw new Error("Invalid file content for text.");
|
||||||
|
}
|
||||||
const originalName = file.originalname;
|
const originalName = file.originalname;
|
||||||
|
|
||||||
const {note} = noteService.createNewNote({
|
const {note} = noteService.createNewNote({
|
||||||
@ -60,8 +70,8 @@ function importFile(taskContext, file, parentNote) {
|
|||||||
return note;
|
return note;
|
||||||
}
|
}
|
||||||
|
|
||||||
function importCodeNote(taskContext, file, parentNote) {
|
function importCodeNote(taskContext: TaskContext, file: File, parentNote: BNote) {
|
||||||
const title = utils.getNoteTitle(file.originalname, taskContext.data.replaceUnderscoresWithSpaces);
|
const title = utils.getNoteTitle(file.originalname, !!taskContext.data?.replaceUnderscoresWithSpaces);
|
||||||
const content = file.buffer.toString("utf-8");
|
const content = file.buffer.toString("utf-8");
|
||||||
const detectedMime = mimeService.getMime(file.originalname) || file.mimetype;
|
const detectedMime = mimeService.getMime(file.originalname) || file.mimetype;
|
||||||
const mime = mimeService.normalizeMimeType(detectedMime);
|
const mime = mimeService.normalizeMimeType(detectedMime);
|
||||||
@ -80,8 +90,8 @@ function importCodeNote(taskContext, file, parentNote) {
|
|||||||
return note;
|
return note;
|
||||||
}
|
}
|
||||||
|
|
||||||
function importPlainText(taskContext, file, parentNote) {
|
function importPlainText(taskContext: TaskContext, file: File, parentNote: BNote) {
|
||||||
const title = utils.getNoteTitle(file.originalname, taskContext.data.replaceUnderscoresWithSpaces);
|
const title = utils.getNoteTitle(file.originalname, !!taskContext.data?.replaceUnderscoresWithSpaces);
|
||||||
const plainTextContent = file.buffer.toString("utf-8");
|
const plainTextContent = file.buffer.toString("utf-8");
|
||||||
const htmlContent = convertTextToHtml(plainTextContent);
|
const htmlContent = convertTextToHtml(plainTextContent);
|
||||||
|
|
||||||
@ -99,7 +109,7 @@ function importPlainText(taskContext, file, parentNote) {
|
|||||||
return note;
|
return note;
|
||||||
}
|
}
|
||||||
|
|
||||||
function convertTextToHtml(text) {
|
function convertTextToHtml(text: string) {
|
||||||
// 1: Plain Text Search
|
// 1: Plain Text Search
|
||||||
text = text.replace(/&/g, "&").
|
text = text.replace(/&/g, "&").
|
||||||
replace(/</g, "<").
|
replace(/</g, "<").
|
||||||
@ -117,13 +127,13 @@ function convertTextToHtml(text) {
|
|||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
function importMarkdown(taskContext, file, parentNote) {
|
function importMarkdown(taskContext: TaskContext, file: File, parentNote: BNote) {
|
||||||
const title = utils.getNoteTitle(file.originalname, taskContext.data.replaceUnderscoresWithSpaces);
|
const title = utils.getNoteTitle(file.originalname, !!taskContext.data?.replaceUnderscoresWithSpaces);
|
||||||
|
|
||||||
const markdownContent = file.buffer.toString("utf-8");
|
const markdownContent = file.buffer.toString("utf-8");
|
||||||
let htmlContent = markdownService.renderToHtml(markdownContent, title);
|
let htmlContent = markdownService.renderToHtml(markdownContent, title);
|
||||||
|
|
||||||
if (taskContext.data.safeImport) {
|
if (taskContext.data?.safeImport) {
|
||||||
htmlContent = htmlSanitizer.sanitize(htmlContent);
|
htmlContent = htmlSanitizer.sanitize(htmlContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,11 +151,11 @@ function importMarkdown(taskContext, file, parentNote) {
|
|||||||
return note;
|
return note;
|
||||||
}
|
}
|
||||||
|
|
||||||
function importHtml(taskContext, file, parentNote) {
|
function importHtml(taskContext: TaskContext, file: File, parentNote: BNote) {
|
||||||
const title = utils.getNoteTitle(file.originalname, taskContext.data.replaceUnderscoresWithSpaces);
|
const title = utils.getNoteTitle(file.originalname, !!taskContext.data?.replaceUnderscoresWithSpaces);
|
||||||
let content = file.buffer.toString("utf-8");
|
let content = file.buffer.toString("utf-8");
|
||||||
|
|
||||||
if (taskContext.data.safeImport) {
|
if (taskContext?.data?.safeImport) {
|
||||||
content = htmlSanitizer.sanitize(content);
|
content = htmlSanitizer.sanitize(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -165,17 +175,11 @@ function importHtml(taskContext, file, parentNote) {
|
|||||||
return note;
|
return note;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
function importAttachment(taskContext: TaskContext, file: File, parentNote: BNote) {
|
||||||
* @param {TaskContext} taskContext
|
|
||||||
* @param file
|
|
||||||
* @param {BNote} parentNote
|
|
||||||
* @returns {BNote}
|
|
||||||
*/
|
|
||||||
function importAttachment(taskContext, file, parentNote) {
|
|
||||||
const mime = mimeService.getMime(file.originalname) || file.mimetype;
|
const mime = mimeService.getMime(file.originalname) || file.mimetype;
|
||||||
|
|
||||||
if (mime.startsWith("image/")) {
|
if (mime.startsWith("image/") && typeof file.buffer !== "string") {
|
||||||
imageService.saveImageToAttachment(parentNote.noteId, file.buffer, file.originalname, taskContext.data.shrinkImages);
|
imageService.saveImageToAttachment(parentNote.noteId, file.buffer, file.originalname, taskContext.data?.shrinkImages);
|
||||||
|
|
||||||
taskContext.increaseProgressCount();
|
taskContext.increaseProgressCount();
|
||||||
} else {
|
} else {
|
||||||
@ -190,7 +194,7 @@ function importAttachment(taskContext, file, parentNote) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
export = {
|
||||||
importSingleFile,
|
importSingleFile,
|
||||||
importAttachment
|
importAttachment
|
||||||
};
|
};
|
@ -1,6 +1,6 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
function handleH1(content, title) {
|
function handleH1(content: string, title: string) {
|
||||||
content = content.replace(/<h1>([^<]*)<\/h1>/gi, (match, text) => {
|
content = content.replace(/<h1>([^<]*)<\/h1>/gi, (match, text) => {
|
||||||
if (title.trim() === text.trim()) {
|
if (title.trim() === text.trim()) {
|
||||||
return ""; // remove whole H1 tag
|
return ""; // remove whole H1 tag
|
||||||
@ -11,6 +11,6 @@ function handleH1(content, title) {
|
|||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
export = {
|
||||||
handleH1
|
handleH1
|
||||||
};
|
};
|
@ -1,43 +1,45 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
const BAttribute = require('../../becca/entities/battribute');
|
import BAttribute = require('../../becca/entities/battribute');
|
||||||
const utils = require('../../services/utils');
|
import utils = require('../../services/utils');
|
||||||
const log = require('../../services/log');
|
import log = require('../../services/log');
|
||||||
const noteService = require('../../services/notes');
|
import noteService = require('../../services/notes');
|
||||||
const attributeService = require('../../services/attributes');
|
import attributeService = require('../../services/attributes');
|
||||||
const BBranch = require('../../becca/entities/bbranch');
|
import BBranch = require('../../becca/entities/bbranch');
|
||||||
const path = require('path');
|
import path = require('path');
|
||||||
const protectedSessionService = require('../protected_session');
|
import protectedSessionService = require('../protected_session');
|
||||||
const mimeService = require('./mime.js');
|
import mimeService = require('./mime');
|
||||||
const treeService = require('../tree');
|
import treeService = require('../tree');
|
||||||
const yauzl = require("yauzl");
|
import yauzl = require("yauzl");
|
||||||
const htmlSanitizer = require('../html_sanitizer');
|
import htmlSanitizer = require('../html_sanitizer');
|
||||||
const becca = require('../../becca/becca');
|
import becca = require('../../becca/becca');
|
||||||
const BAttachment = require('../../becca/entities/battachment');
|
import BAttachment = require('../../becca/entities/battachment');
|
||||||
const markdownService = require('./markdown.js');
|
import markdownService = require('./markdown');
|
||||||
|
import TaskContext = require('../task_context');
|
||||||
|
import BNote = require('../../becca/entities/bnote');
|
||||||
|
import NoteMeta = require('../meta/note_meta');
|
||||||
|
import AttributeMeta = require('../meta/attribute_meta');
|
||||||
|
import { Stream } from 'stream';
|
||||||
|
import { NoteType } from '../../becca/entities/rows';
|
||||||
|
|
||||||
/**
|
interface MetaFile {
|
||||||
* @param {TaskContext} taskContext
|
files: NoteMeta[]
|
||||||
* @param {Buffer} fileBuffer
|
}
|
||||||
* @param {BNote} importRootNote
|
|
||||||
* @returns {Promise<BNote>}
|
async function importZip(taskContext: TaskContext, fileBuffer: Buffer, importRootNote: BNote): Promise<BNote> {
|
||||||
*/
|
/** maps from original noteId (in ZIP file) to newly generated noteId */
|
||||||
async function importZip(taskContext, fileBuffer, importRootNote) {
|
const noteIdMap: Record<string, string> = {};
|
||||||
/** @type {Object.<string, string>} maps from original noteId (in ZIP file) to newly generated noteId */
|
/** type maps from original attachmentId (in ZIP file) to newly generated attachmentId */
|
||||||
const noteIdMap = {};
|
const attachmentIdMap: Record<string, string> = {};
|
||||||
/** @type {Object.<string, string>} maps from original attachmentId (in ZIP file) to newly generated attachmentId */
|
const attributes: AttributeMeta[] = [];
|
||||||
const attachmentIdMap = {};
|
|
||||||
const attributes = [];
|
|
||||||
// path => noteId, used only when meta file is not available
|
// path => noteId, used only when meta file is not available
|
||||||
/** @type {Object.<string, string>} path => noteId | attachmentId */
|
/** path => noteId | attachmentId */
|
||||||
const createdPaths = { '/': importRootNote.noteId, '\\': importRootNote.noteId };
|
const createdPaths: Record<string, string> = { '/': importRootNote.noteId, '\\': importRootNote.noteId };
|
||||||
let metaFile = null;
|
let metaFile: MetaFile | null = null;
|
||||||
/** @type {BNote} */
|
let firstNote: BNote | null = null;
|
||||||
let firstNote = null;
|
const createdNoteIds = new Set<string>();
|
||||||
/** @type {Set.<string>} */
|
|
||||||
const createdNoteIds = new Set();
|
|
||||||
|
|
||||||
function getNewNoteId(origNoteId) {
|
function getNewNoteId(origNoteId: string) {
|
||||||
if (!origNoteId.trim()) {
|
if (!origNoteId.trim()) {
|
||||||
// this probably shouldn't happen, but still good to have this precaution
|
// this probably shouldn't happen, but still good to have this precaution
|
||||||
return "empty_note_id";
|
return "empty_note_id";
|
||||||
@ -55,7 +57,7 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
|
|||||||
return noteIdMap[origNoteId];
|
return noteIdMap[origNoteId];
|
||||||
}
|
}
|
||||||
|
|
||||||
function getNewAttachmentId(origAttachmentId) {
|
function getNewAttachmentId(origAttachmentId: string) {
|
||||||
if (!origAttachmentId.trim()) {
|
if (!origAttachmentId.trim()) {
|
||||||
// this probably shouldn't happen, but still good to have this precaution
|
// this probably shouldn't happen, but still good to have this precaution
|
||||||
return "empty_attachment_id";
|
return "empty_attachment_id";
|
||||||
@ -68,12 +70,8 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
|
|||||||
return attachmentIdMap[origAttachmentId];
|
return attachmentIdMap[origAttachmentId];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
function getAttachmentMeta(parentNoteMeta: NoteMeta, dataFileName: string) {
|
||||||
* @param {NoteMeta} parentNoteMeta
|
for (const noteMeta of parentNoteMeta.children || []) {
|
||||||
* @param {string} dataFileName
|
|
||||||
*/
|
|
||||||
function getAttachmentMeta(parentNoteMeta, dataFileName) {
|
|
||||||
for (const noteMeta of parentNoteMeta.children) {
|
|
||||||
for (const attachmentMeta of noteMeta.attachments || []) {
|
for (const attachmentMeta of noteMeta.attachments || []) {
|
||||||
if (attachmentMeta.dataFileName === dataFileName) {
|
if (attachmentMeta.dataFileName === dataFileName) {
|
||||||
return {
|
return {
|
||||||
@ -88,22 +86,20 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {{noteMeta: NoteMeta|undefined, parentNoteMeta: NoteMeta|undefined, attachmentMeta: AttachmentMeta|undefined}} */
|
function getMeta(filePath: string) {
|
||||||
function getMeta(filePath) {
|
|
||||||
if (!metaFile) {
|
if (!metaFile) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
const pathSegments = filePath.split(/[\/\\]/g);
|
const pathSegments = filePath.split(/[\/\\]/g);
|
||||||
|
|
||||||
/** @type {NoteMeta} */
|
let cursor: NoteMeta | undefined = {
|
||||||
let cursor = {
|
|
||||||
isImportRoot: true,
|
isImportRoot: true,
|
||||||
children: metaFile.files
|
children: metaFile.files,
|
||||||
|
dataFileName: ""
|
||||||
};
|
};
|
||||||
|
|
||||||
/** @type {NoteMeta} */
|
let parent: NoteMeta | undefined = undefined;
|
||||||
let parent;
|
|
||||||
|
|
||||||
for (const segment of pathSegments) {
|
for (const segment of pathSegments) {
|
||||||
if (!cursor?.children?.length) {
|
if (!cursor?.children?.length) {
|
||||||
@ -111,7 +107,9 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
parent = cursor;
|
parent = cursor;
|
||||||
cursor = parent.children.find(file => file.dataFileName === segment || file.dirFileName === segment);
|
if (parent.children) {
|
||||||
|
cursor = parent.children.find(file => file.dataFileName === segment || file.dirFileName === segment);
|
||||||
|
}
|
||||||
|
|
||||||
if (!cursor) {
|
if (!cursor) {
|
||||||
return getAttachmentMeta(parent, segment);
|
return getAttachmentMeta(parent, segment);
|
||||||
@ -120,19 +118,15 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
parentNoteMeta: parent,
|
parentNoteMeta: parent,
|
||||||
noteMeta: cursor
|
noteMeta: cursor,
|
||||||
|
attachmentMeta: null
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
function getParentNoteId(filePath: string, parentNoteMeta?: NoteMeta) {
|
||||||
* @param {string} filePath
|
|
||||||
* @param {NoteMeta} parentNoteMeta
|
|
||||||
* @return {string}
|
|
||||||
*/
|
|
||||||
function getParentNoteId(filePath, parentNoteMeta) {
|
|
||||||
let parentNoteId;
|
let parentNoteId;
|
||||||
|
|
||||||
if (parentNoteMeta) {
|
if (parentNoteMeta?.noteId) {
|
||||||
parentNoteId = parentNoteMeta.isImportRoot ? importRootNote.noteId : getNewNoteId(parentNoteMeta.noteId);
|
parentNoteId = parentNoteMeta.isImportRoot ? importRootNote.noteId : getNewNoteId(parentNoteMeta.noteId);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -151,13 +145,8 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
|
|||||||
return parentNoteId;
|
return parentNoteId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
function getNoteId(noteMeta: NoteMeta | undefined, filePath: string): string {
|
||||||
* @param {NoteMeta} noteMeta
|
if (noteMeta?.noteId) {
|
||||||
* @param {string} filePath
|
|
||||||
* @return {string}
|
|
||||||
*/
|
|
||||||
function getNoteId(noteMeta, filePath) {
|
|
||||||
if (noteMeta) {
|
|
||||||
return getNewNoteId(noteMeta.noteId);
|
return getNewNoteId(noteMeta.noteId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -176,23 +165,19 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
|
|||||||
return noteId;
|
return noteId;
|
||||||
}
|
}
|
||||||
|
|
||||||
function detectFileTypeAndMime(taskContext, filePath) {
|
function detectFileTypeAndMime(taskContext: TaskContext, filePath: string) {
|
||||||
const mime = mimeService.getMime(filePath) || "application/octet-stream";
|
const mime = mimeService.getMime(filePath) || "application/octet-stream";
|
||||||
const type = mimeService.getType(taskContext.data, mime);
|
const type = mimeService.getType(taskContext.data || {}, mime);
|
||||||
|
|
||||||
return { mime, type };
|
return { mime, type };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
function saveAttributes(note: BNote, noteMeta: NoteMeta | undefined) {
|
||||||
* @param {BNote} note
|
|
||||||
* @param {NoteMeta} noteMeta
|
|
||||||
*/
|
|
||||||
function saveAttributes(note, noteMeta) {
|
|
||||||
if (!noteMeta) {
|
if (!noteMeta) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const attr of noteMeta.attributes) {
|
for (const attr of noteMeta.attributes || []) {
|
||||||
attr.noteId = note.noteId;
|
attr.noteId = note.noteId;
|
||||||
|
|
||||||
if (attr.type === 'label-definition') {
|
if (attr.type === 'label-definition') {
|
||||||
@ -218,11 +203,11 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
|
|||||||
attr.value = getNewNoteId(attr.value);
|
attr.value = getNewNoteId(attr.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (taskContext.data.safeImport && attributeService.isAttributeDangerous(attr.type, attr.name)) {
|
if (taskContext.data?.safeImport && attributeService.isAttributeDangerous(attr.type, attr.name)) {
|
||||||
attr.name = `disabled:${attr.name}`;
|
attr.name = `disabled:${attr.name}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (taskContext.data.safeImport) {
|
if (taskContext.data?.safeImport) {
|
||||||
attr.name = htmlSanitizer.sanitize(attr.name);
|
attr.name = htmlSanitizer.sanitize(attr.name);
|
||||||
attr.value = htmlSanitizer.sanitize(attr.value);
|
attr.value = htmlSanitizer.sanitize(attr.value);
|
||||||
}
|
}
|
||||||
@ -231,7 +216,7 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveDirectory(filePath) {
|
function saveDirectory(filePath: string) {
|
||||||
const { parentNoteMeta, noteMeta } = getMeta(filePath);
|
const { parentNoteMeta, noteMeta } = getMeta(filePath);
|
||||||
|
|
||||||
const noteId = getNoteId(noteMeta, filePath);
|
const noteId = getNoteId(noteMeta, filePath);
|
||||||
@ -240,12 +225,16 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const noteTitle = utils.getNoteTitle(filePath, taskContext.data.replaceUnderscoresWithSpaces, noteMeta);
|
const noteTitle = utils.getNoteTitle(filePath, !!taskContext.data?.replaceUnderscoresWithSpaces, noteMeta);
|
||||||
const parentNoteId = getParentNoteId(filePath, parentNoteMeta);
|
const parentNoteId = getParentNoteId(filePath, parentNoteMeta);
|
||||||
|
|
||||||
|
if (!parentNoteId) {
|
||||||
|
throw new Error("Missing parent note ID.");
|
||||||
|
}
|
||||||
|
|
||||||
const {note} = noteService.createNewNote({
|
const {note} = noteService.createNewNote({
|
||||||
parentNoteId: parentNoteId,
|
parentNoteId: parentNoteId,
|
||||||
title: noteTitle,
|
title: noteTitle || "",
|
||||||
content: '',
|
content: '',
|
||||||
noteId: noteId,
|
noteId: noteId,
|
||||||
type: resolveNoteType(noteMeta?.type),
|
type: resolveNoteType(noteMeta?.type),
|
||||||
@ -265,8 +254,7 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
|
|||||||
return noteId;
|
return noteId;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {{attachmentId: string}|{noteId: string}} */
|
function getEntityIdFromRelativeUrl(url: string, filePath: string) {
|
||||||
function getEntityIdFromRelativeUrl(url, filePath) {
|
|
||||||
while (url.startsWith("./")) {
|
while (url.startsWith("./")) {
|
||||||
url = url.substr(2);
|
url = url.substr(2);
|
||||||
}
|
}
|
||||||
@ -287,7 +275,7 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
|
|||||||
|
|
||||||
const { noteMeta, attachmentMeta } = getMeta(absUrl);
|
const { noteMeta, attachmentMeta } = getMeta(absUrl);
|
||||||
|
|
||||||
if (attachmentMeta) {
|
if (attachmentMeta && attachmentMeta.attachmentId && noteMeta.noteId) {
|
||||||
return {
|
return {
|
||||||
attachmentId: getNewAttachmentId(attachmentMeta.attachmentId),
|
attachmentId: getNewAttachmentId(attachmentMeta.attachmentId),
|
||||||
noteId: getNewNoteId(noteMeta.noteId)
|
noteId: getNewNoteId(noteMeta.noteId)
|
||||||
@ -299,15 +287,8 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
function processTextNoteContent(content: string, noteTitle: string, filePath: string, noteMeta?: NoteMeta) {
|
||||||
* @param {string} content
|
function isUrlAbsolute(url: string) {
|
||||||
* @param {string} noteTitle
|
|
||||||
* @param {string} filePath
|
|
||||||
* @param {NoteMeta} noteMeta
|
|
||||||
* @return {string}
|
|
||||||
*/
|
|
||||||
function processTextNoteContent(content, noteTitle, filePath, noteMeta) {
|
|
||||||
function isUrlAbsolute(url) {
|
|
||||||
return /^(?:[a-z]+:)?\/\//i.test(url);
|
return /^(?:[a-z]+:)?\/\//i.test(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -321,7 +302,7 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (taskContext.data.safeImport) {
|
if (taskContext.data?.safeImport) {
|
||||||
content = htmlSanitizer.sanitize(content);
|
content = htmlSanitizer.sanitize(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -336,7 +317,7 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
url = decodeURIComponent(url).trim();
|
url = decodeURIComponent(url).trim();
|
||||||
} catch (e) {
|
} catch (e: any) {
|
||||||
log.error(`Cannot parse image URL '${url}', keeping original. Error: ${e.message}.`);
|
log.error(`Cannot parse image URL '${url}', keeping original. Error: ${e.message}.`);
|
||||||
return `src="${url}"`;
|
return `src="${url}"`;
|
||||||
}
|
}
|
||||||
@ -359,7 +340,7 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
|
|||||||
content = content.replace(/href="([^"]*)"/g, (match, url) => {
|
content = content.replace(/href="([^"]*)"/g, (match, url) => {
|
||||||
try {
|
try {
|
||||||
url = decodeURIComponent(url).trim();
|
url = decodeURIComponent(url).trim();
|
||||||
} catch (e) {
|
} catch (e: any) {
|
||||||
log.error(`Cannot parse link URL '${url}', keeping original. Error: ${e.message}.`);
|
log.error(`Cannot parse link URL '${url}', keeping original. Error: ${e.message}.`);
|
||||||
return `href="${url}"`;
|
return `href="${url}"`;
|
||||||
}
|
}
|
||||||
@ -395,7 +376,7 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
|
|||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeTriliumTags(content) {
|
function removeTriliumTags(content: string) {
|
||||||
const tagsToRemove = [
|
const tagsToRemove = [
|
||||||
'<h1 data-trilium-h1>([^<]*)<\/h1>',
|
'<h1 data-trilium-h1>([^<]*)<\/h1>',
|
||||||
'<title data-trilium-title>([^<]*)<\/title>'
|
'<title data-trilium-title>([^<]*)<\/title>'
|
||||||
@ -407,26 +388,18 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
|
|||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
function processNoteContent(noteMeta: NoteMeta | undefined, type: string, mime: string, content: string | Buffer, noteTitle: string, filePath: string) {
|
||||||
* @param {NoteMeta} noteMeta
|
if ((noteMeta?.format === 'markdown'
|
||||||
* @param {string} type
|
|| (!noteMeta && taskContext.data?.textImportedAsText && ['text/markdown', 'text/x-markdown'].includes(mime)))
|
||||||
* @param {string} mime
|
&& typeof content === "string") {
|
||||||
* @param {string|Buffer} content
|
|
||||||
* @param {string} noteTitle
|
|
||||||
* @param {string} filePath
|
|
||||||
* @return {string}
|
|
||||||
*/
|
|
||||||
function processNoteContent(noteMeta, type, mime, content, noteTitle, filePath) {
|
|
||||||
if (noteMeta?.format === 'markdown'
|
|
||||||
|| (!noteMeta && taskContext.data.textImportedAsText && ['text/markdown', 'text/x-markdown'].includes(mime))) {
|
|
||||||
content = markdownService.renderToHtml(content, noteTitle);
|
content = markdownService.renderToHtml(content, noteTitle);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type === 'text') {
|
if (type === 'text' && typeof content === "string") {
|
||||||
content = processTextNoteContent(content, noteTitle, filePath, noteMeta);
|
content = processTextNoteContent(content, noteTitle, filePath, noteMeta);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type === 'relationMap' && noteMeta) {
|
if (type === 'relationMap' && noteMeta && typeof content === "string") {
|
||||||
const relationMapLinks = (noteMeta.attributes || [])
|
const relationMapLinks = (noteMeta.attributes || [])
|
||||||
.filter(attr => attr.type === 'relation' && attr.name === 'relationMapLink');
|
.filter(attr => attr.type === 'relation' && attr.name === 'relationMapLink');
|
||||||
|
|
||||||
@ -440,11 +413,7 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
|
|||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
function saveNote(filePath: string, content: string | Buffer) {
|
||||||
* @param {string} filePath
|
|
||||||
* @param {Buffer} content
|
|
||||||
*/
|
|
||||||
function saveNote(filePath, content) {
|
|
||||||
const { parentNoteMeta, noteMeta, attachmentMeta } = getMeta(filePath);
|
const { parentNoteMeta, noteMeta, attachmentMeta } = getMeta(filePath);
|
||||||
|
|
||||||
if (noteMeta?.noImport) {
|
if (noteMeta?.noImport) {
|
||||||
@ -453,7 +422,7 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
|
|||||||
|
|
||||||
const noteId = getNoteId(noteMeta, filePath);
|
const noteId = getNoteId(noteMeta, filePath);
|
||||||
|
|
||||||
if (attachmentMeta) {
|
if (attachmentMeta && attachmentMeta.attachmentId) {
|
||||||
const attachment = new BAttachment({
|
const attachment = new BAttachment({
|
||||||
attachmentId: getNewAttachmentId(attachmentMeta.attachmentId),
|
attachmentId: getNewAttachmentId(attachmentMeta.attachmentId),
|
||||||
ownerId: noteId,
|
ownerId: noteId,
|
||||||
@ -487,16 +456,20 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let { type, mime } = noteMeta ? noteMeta : detectFileTypeAndMime(taskContext, filePath);
|
let { mime } = noteMeta ? noteMeta : detectFileTypeAndMime(taskContext, filePath);
|
||||||
type = resolveNoteType(type);
|
if (!mime) {
|
||||||
|
throw new Error("Unable to resolve mime type.");
|
||||||
|
}
|
||||||
|
|
||||||
|
let type = resolveNoteType(noteMeta?.type);
|
||||||
|
|
||||||
if (type !== 'file' && type !== 'image') {
|
if (type !== 'file' && type !== 'image') {
|
||||||
content = content.toString("utf-8");
|
content = content.toString("utf-8");
|
||||||
}
|
}
|
||||||
|
|
||||||
const noteTitle = utils.getNoteTitle(filePath, taskContext.data.replaceUnderscoresWithSpaces, noteMeta);
|
const noteTitle = utils.getNoteTitle(filePath, taskContext.data?.replaceUnderscoresWithSpaces || false, noteMeta);
|
||||||
|
|
||||||
content = processNoteContent(noteMeta, type, mime, content, noteTitle, filePath);
|
content = processNoteContent(noteMeta, type, mime, content, noteTitle || "", filePath);
|
||||||
|
|
||||||
let note = becca.getNote(noteId);
|
let note = becca.getNote(noteId);
|
||||||
|
|
||||||
@ -508,7 +481,7 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
|
|||||||
if (note.type === undefined) {
|
if (note.type === undefined) {
|
||||||
note.type = type;
|
note.type = type;
|
||||||
note.mime = mime;
|
note.mime = mime;
|
||||||
note.title = noteTitle;
|
note.title = noteTitle || "";
|
||||||
note.isProtected = isProtected;
|
note.isProtected = isProtected;
|
||||||
note.save();
|
note.save();
|
||||||
}
|
}
|
||||||
@ -519,16 +492,20 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
|
|||||||
new BBranch({
|
new BBranch({
|
||||||
noteId,
|
noteId,
|
||||||
parentNoteId,
|
parentNoteId,
|
||||||
isExpanded: noteMeta.isExpanded,
|
isExpanded: noteMeta?.isExpanded,
|
||||||
prefix: noteMeta.prefix,
|
prefix: noteMeta?.prefix,
|
||||||
notePosition: noteMeta.notePosition
|
notePosition: noteMeta?.notePosition
|
||||||
}).save();
|
}).save();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
if (typeof content !== "string") {
|
||||||
|
throw new Error("Incorrect content type.");
|
||||||
|
}
|
||||||
|
|
||||||
({note} = noteService.createNewNote({
|
({note} = noteService.createNewNote({
|
||||||
parentNoteId: parentNoteId,
|
parentNoteId: parentNoteId,
|
||||||
title: noteTitle,
|
title: noteTitle || "",
|
||||||
content: content,
|
content: content,
|
||||||
noteId,
|
noteId,
|
||||||
type,
|
type,
|
||||||
@ -560,7 +537,7 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
|
|||||||
|
|
||||||
// we're running two passes to make sure that the meta file is loaded before the rest of the files is processed.
|
// we're running two passes to make sure that the meta file is loaded before the rest of the files is processed.
|
||||||
|
|
||||||
await readZipFile(fileBuffer, async (zipfile, entry) => {
|
await readZipFile(fileBuffer, async (zipfile: yauzl.ZipFile, entry: yauzl.Entry) => {
|
||||||
const filePath = normalizeFilePath(entry.fileName);
|
const filePath = normalizeFilePath(entry.fileName);
|
||||||
|
|
||||||
if (filePath === '!!!meta.json') {
|
if (filePath === '!!!meta.json') {
|
||||||
@ -572,7 +549,7 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
|
|||||||
zipfile.readEntry();
|
zipfile.readEntry();
|
||||||
});
|
});
|
||||||
|
|
||||||
await readZipFile(fileBuffer, async (zipfile, entry) => {
|
await readZipFile(fileBuffer, async (zipfile: yauzl.ZipFile, entry: yauzl.Entry) => {
|
||||||
const filePath = normalizeFilePath(entry.fileName);
|
const filePath = normalizeFilePath(entry.fileName);
|
||||||
|
|
||||||
if (/\/$/.test(entry.fileName)) {
|
if (/\/$/.test(entry.fileName)) {
|
||||||
@ -590,6 +567,7 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
|
|||||||
|
|
||||||
for (const noteId of createdNoteIds) {
|
for (const noteId of createdNoteIds) {
|
||||||
const note = becca.getNote(noteId);
|
const note = becca.getNote(noteId);
|
||||||
|
if (!note) continue;
|
||||||
await noteService.asyncPostProcessContent(note, note.getContent());
|
await noteService.asyncPostProcessContent(note, note.getContent());
|
||||||
|
|
||||||
if (!metaFile) {
|
if (!metaFile) {
|
||||||
@ -612,11 +590,15 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!firstNote) {
|
||||||
|
throw new Error("Unable to determine first note.");
|
||||||
|
}
|
||||||
|
|
||||||
return firstNote;
|
return firstNote;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {string} path without leading or trailing slash and backslashes converted to forward ones */
|
/** @returns path without leading or trailing slash and backslashes converted to forward ones */
|
||||||
function normalizeFilePath(filePath) {
|
function normalizeFilePath(filePath: string): string {
|
||||||
filePath = filePath.replace(/\\/g, "/");
|
filePath = filePath.replace(/\\/g, "/");
|
||||||
|
|
||||||
if (filePath.startsWith("/")) {
|
if (filePath.startsWith("/")) {
|
||||||
@ -630,29 +612,30 @@ function normalizeFilePath(filePath) {
|
|||||||
return filePath;
|
return filePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {Promise<Buffer>} */
|
function streamToBuffer(stream: Stream): Promise<Buffer> {
|
||||||
function streamToBuffer(stream) {
|
const chunks: Uint8Array[] = [];
|
||||||
const chunks = [];
|
|
||||||
stream.on('data', chunk => chunks.push(chunk));
|
stream.on('data', chunk => chunks.push(chunk));
|
||||||
|
|
||||||
return new Promise((res, rej) => stream.on('end', () => res(Buffer.concat(chunks))));
|
return new Promise((res, rej) => stream.on('end', () => res(Buffer.concat(chunks))));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {Promise<Buffer>} */
|
function readContent(zipfile: yauzl.ZipFile, entry: yauzl.Entry): Promise<Buffer> {
|
||||||
function readContent(zipfile, entry) {
|
|
||||||
return new Promise((res, rej) => {
|
return new Promise((res, rej) => {
|
||||||
zipfile.openReadStream(entry, function(err, readStream) {
|
zipfile.openReadStream(entry, function(err, readStream) {
|
||||||
if (err) rej(err);
|
if (err) rej(err);
|
||||||
|
if (!readStream) throw new Error("Unable to read content.");
|
||||||
|
|
||||||
streamToBuffer(readStream).then(res);
|
streamToBuffer(readStream).then(res);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function readZipFile(buffer, processEntryCallback) {
|
function readZipFile(buffer: Buffer, processEntryCallback: (zipfile: yauzl.ZipFile, entry: yauzl.Entry) => void) {
|
||||||
return new Promise((res, rej) => {
|
return new Promise((res, rej) => {
|
||||||
yauzl.fromBuffer(buffer, {lazyEntries: true, validateEntrySizes: false}, function(err, zipfile) {
|
yauzl.fromBuffer(buffer, {lazyEntries: true, validateEntrySizes: false}, function(err, zipfile) {
|
||||||
if (err) throw err;
|
if (err) throw err;
|
||||||
|
if (!zipfile) throw new Error("Unable to read zip file.");
|
||||||
|
|
||||||
zipfile.readEntry();
|
zipfile.readEntry();
|
||||||
zipfile.on("entry", entry => processEntryCallback(zipfile, entry));
|
zipfile.on("entry", entry => processEntryCallback(zipfile, entry));
|
||||||
zipfile.on("end", res);
|
zipfile.on("end", res);
|
||||||
@ -660,20 +643,19 @@ function readZipFile(buffer, processEntryCallback) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveNoteType(type) {
|
function resolveNoteType(type: string | undefined): NoteType {
|
||||||
// BC for ZIPs created in Triliun 0.57 and older
|
// BC for ZIPs created in Triliun 0.57 and older
|
||||||
if (type === 'relation-map') {
|
if (type === 'relation-map') {
|
||||||
type = 'relationMap';
|
return 'relationMap';
|
||||||
} else if (type === 'note-map') {
|
} else if (type === 'note-map') {
|
||||||
type = 'noteMap';
|
return 'noteMap';
|
||||||
} else if (type === 'web-view') {
|
} else if (type === 'web-view') {
|
||||||
type = 'webView';
|
return 'webView';
|
||||||
}
|
}
|
||||||
|
|
||||||
return type || "text";
|
return "text";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export = {
|
||||||
module.exports = {
|
|
||||||
importZip
|
importZip
|
||||||
};
|
};
|
@ -1,9 +1,9 @@
|
|||||||
interface AttachmentMeta {
|
interface AttachmentMeta {
|
||||||
attachmentId: string;
|
attachmentId?: string;
|
||||||
title: string;
|
title: string;
|
||||||
role: string;
|
role: string;
|
||||||
mime: string;
|
mime: string;
|
||||||
position: number;
|
position?: number;
|
||||||
dataFileName: string;
|
dataFileName: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
|
import { AttributeType } from "../../becca/entities/rows";
|
||||||
|
|
||||||
interface AttributeMeta {
|
interface AttributeMeta {
|
||||||
type: string;
|
noteId?: string;
|
||||||
|
type: AttributeType;
|
||||||
name: string;
|
name: string;
|
||||||
value: string;
|
value: string;
|
||||||
isInheritable: boolean;
|
isInheritable?: boolean;
|
||||||
position: number;
|
position?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export = AttributeMeta;
|
export = AttributeMeta;
|
||||||
|
@ -1,23 +1,25 @@
|
|||||||
|
import AttachmentMeta = require("./attachment_meta");
|
||||||
import AttributeMeta = require("./attribute_meta");
|
import AttributeMeta = require("./attribute_meta");
|
||||||
|
|
||||||
interface NoteMeta {
|
interface NoteMeta {
|
||||||
noteId: string;
|
noteId?: string;
|
||||||
notePath: string;
|
notePath?: string[];
|
||||||
isClone: boolean;
|
isClone?: boolean;
|
||||||
title: string;
|
title?: string;
|
||||||
notePosition: number;
|
notePosition?: number;
|
||||||
prefix: string;
|
prefix?: string | null;
|
||||||
isExpanded: boolean;
|
isExpanded?: boolean;
|
||||||
type: string;
|
type?: string;
|
||||||
mime: string;
|
mime?: string;
|
||||||
/** 'html' or 'markdown', applicable to text notes only */
|
/** 'html' or 'markdown', applicable to text notes only */
|
||||||
format: "html" | "markdown";
|
format?: "html" | "markdown";
|
||||||
dataFileName: string;
|
dataFileName: string;
|
||||||
dirFileName: string;
|
dirFileName?: string;
|
||||||
/** this file should not be imported (e.g., HTML navigation) */
|
/** this file should not be imported (e.g., HTML navigation) */
|
||||||
noImport: boolean;
|
noImport?: boolean;
|
||||||
attributes: AttributeMeta[];
|
isImportRoot?: boolean;
|
||||||
attachments: AttributeMeta[];
|
attributes?: AttributeMeta[];
|
||||||
|
attachments?: AttachmentMeta[];
|
||||||
children?: NoteMeta[];
|
children?: NoteMeta[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
25
src/services/note-interface.ts
Normal file
25
src/services/note-interface.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { NoteType } from "../becca/entities/rows";
|
||||||
|
|
||||||
|
export interface NoteParams {
|
||||||
|
/** optionally can force specific noteId */
|
||||||
|
noteId?: string;
|
||||||
|
parentNoteId: string;
|
||||||
|
templateNoteId?: string;
|
||||||
|
title: string;
|
||||||
|
content: string;
|
||||||
|
type: NoteType;
|
||||||
|
/** default value is derived from default mimes for type */
|
||||||
|
mime?: string;
|
||||||
|
/** default is false */
|
||||||
|
isProtected?: boolean;
|
||||||
|
/** default is false */
|
||||||
|
isExpanded?: boolean;
|
||||||
|
/** default is empty string */
|
||||||
|
prefix?: string;
|
||||||
|
/** default is the last existing notePosition in a parent + 10 */
|
||||||
|
notePosition?: number;
|
||||||
|
dateCreated?: string;
|
||||||
|
utcDateCreated?: string;
|
||||||
|
ignoreForbiddenParents?: boolean;
|
||||||
|
target?: "into";
|
||||||
|
}
|
@ -155,6 +155,7 @@ function getAndValidateParent(params: GetValidateParams) {
|
|||||||
interface NoteParams {
|
interface NoteParams {
|
||||||
/** optionally can force specific noteId */
|
/** optionally can force specific noteId */
|
||||||
noteId?: string;
|
noteId?: string;
|
||||||
|
branchId?: string;
|
||||||
parentNoteId: string;
|
parentNoteId: string;
|
||||||
templateNoteId?: string;
|
templateNoteId?: string;
|
||||||
title: string;
|
title: string;
|
||||||
@ -167,7 +168,7 @@ interface NoteParams {
|
|||||||
/** default is false */
|
/** default is false */
|
||||||
isExpanded?: boolean;
|
isExpanded?: boolean;
|
||||||
/** default is empty string */
|
/** default is empty string */
|
||||||
prefix?: string;
|
prefix?: string | null;
|
||||||
/** default is the last existing notePosition in a parent + 10 */
|
/** default is the last existing notePosition in a parent + 10 */
|
||||||
notePosition?: number;
|
notePosition?: number;
|
||||||
dateCreated?: string;
|
dateCreated?: string;
|
||||||
@ -506,7 +507,7 @@ async function downloadImage(noteId: string, imageUrl: string) {
|
|||||||
const parsedUrl = url.parse(unescapedUrl);
|
const parsedUrl = url.parse(unescapedUrl);
|
||||||
const title = path.basename(parsedUrl.pathname || "");
|
const title = path.basename(parsedUrl.pathname || "");
|
||||||
|
|
||||||
const imageService = require('../services/image.js');
|
const imageService = require('../services/image');
|
||||||
const attachment = imageService.saveImageToAttachment(noteId, imageBuffer, title, true, true);
|
const attachment = imageService.saveImageToAttachment(noteId, imageBuffer, title, true, true);
|
||||||
|
|
||||||
imageUrlToAttachmentIdMapping[imageUrl] = attachment.attachmentId;
|
imageUrlToAttachmentIdMapping[imageUrl] = attachment.attachmentId;
|
||||||
@ -539,7 +540,7 @@ function downloadImages(noteId: string, content: string) {
|
|||||||
const imageBase64 = url.substr(inlineImageMatch[0].length);
|
const imageBase64 = url.substr(inlineImageMatch[0].length);
|
||||||
const imageBuffer = Buffer.from(imageBase64, 'base64');
|
const imageBuffer = Buffer.from(imageBase64, 'base64');
|
||||||
|
|
||||||
const imageService = require('../services/image.js');
|
const imageService = require('../services/image');
|
||||||
const attachment = imageService.saveImageToAttachment(noteId, imageBuffer, "inline image", true, true);
|
const attachment = imageService.saveImageToAttachment(noteId, imageBuffer, "inline image", true, true);
|
||||||
|
|
||||||
const encodedTitle = encodeURIComponent(attachment.title);
|
const encodedTitle = encodeURIComponent(attachment.title);
|
||||||
@ -657,7 +658,7 @@ function saveAttachments(note: BNote, content: string) {
|
|||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveLinks(note: BNote, content: string) {
|
function saveLinks(note: BNote, content: string | Buffer) {
|
||||||
if ((note.type !== 'text' && note.type !== 'relationMap')
|
if ((note.type !== 'text' && note.type !== 'relationMap')
|
||||||
|| (note.isProtected && !protectedSessionService.isProtectedSessionAvailable())) {
|
|| (note.isProtected && !protectedSessionService.isProtectedSessionAvailable())) {
|
||||||
return {
|
return {
|
||||||
@ -669,7 +670,7 @@ function saveLinks(note: BNote, content: string) {
|
|||||||
const foundLinks: FoundLink[] = [];
|
const foundLinks: FoundLink[] = [];
|
||||||
let forceFrontendReload = false;
|
let forceFrontendReload = false;
|
||||||
|
|
||||||
if (note.type === 'text') {
|
if (note.type === 'text' && typeof content === "string") {
|
||||||
content = downloadImages(note.noteId, content);
|
content = downloadImages(note.noteId, content);
|
||||||
content = saveAttachments(note, content);
|
content = saveAttachments(note, content);
|
||||||
|
|
||||||
@ -679,7 +680,7 @@ function saveLinks(note: BNote, content: string) {
|
|||||||
|
|
||||||
({forceFrontendReload, content} = checkImageAttachments(note, content));
|
({forceFrontendReload, content} = checkImageAttachments(note, content));
|
||||||
}
|
}
|
||||||
else if (note.type === 'relationMap') {
|
else if (note.type === 'relationMap' && typeof content === "string") {
|
||||||
findRelationMapLinks(content, foundLinks);
|
findRelationMapLinks(content, foundLinks);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -874,7 +875,7 @@ function getUndeletedParentBranchIds(noteId: string, deleteId: string) {
|
|||||||
AND parentNote.isDeleted = 0`, [noteId, deleteId]);
|
AND parentNote.isDeleted = 0`, [noteId, deleteId]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function scanForLinks(note: BNote, content: string) {
|
function scanForLinks(note: BNote, content: string | Buffer) {
|
||||||
if (!note || !['text', 'relationMap'].includes(note.type)) {
|
if (!note || !['text', 'relationMap'].includes(note.type)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -896,7 +897,7 @@ function scanForLinks(note: BNote, content: string) {
|
|||||||
/**
|
/**
|
||||||
* Things which have to be executed after updating content, but asynchronously (separate transaction)
|
* Things which have to be executed after updating content, but asynchronously (separate transaction)
|
||||||
*/
|
*/
|
||||||
async function asyncPostProcessContent(note: BNote, content: string) {
|
async function asyncPostProcessContent(note: BNote, content: string | Buffer) {
|
||||||
if (cls.isMigrationRunning()) {
|
if (cls.isMigrationRunning()) {
|
||||||
// this is rarely needed for migrations, but can cause trouble by e.g. triggering downloads
|
// this is rarely needed for migrations, but can cause trouble by e.g. triggering downloads
|
||||||
return;
|
return;
|
||||||
|
@ -15,7 +15,7 @@ function getOptionOrNull(name: string): string | null {
|
|||||||
return option ? option.value : null;
|
return option ? option.value : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getOption(name: string): string {
|
function getOption(name: string) {
|
||||||
const val = getOptionOrNull(name);
|
const val = getOptionOrNull(name);
|
||||||
|
|
||||||
if (val === null) {
|
if (val === null) {
|
||||||
@ -44,15 +44,15 @@ function getOptionInt(name: string, defaultValue?: number): number {
|
|||||||
function getOptionBool(name: string): boolean {
|
function getOptionBool(name: string): boolean {
|
||||||
const val = getOption(name);
|
const val = getOption(name);
|
||||||
|
|
||||||
if (!['true', 'false'].includes(val)) {
|
if (typeof val !== "string" || !['true', 'false'].includes(val)) {
|
||||||
throw new Error(`Could not parse '${val}' into boolean for option '${name}'`);
|
throw new Error(`Could not parse '${val}' into boolean for option '${name}'`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return val === 'true';
|
return val === 'true';
|
||||||
}
|
}
|
||||||
|
|
||||||
function setOption(name: string, value: string | boolean) {
|
function setOption(name: string, value: string | number | boolean) {
|
||||||
if (value === true || value === false) {
|
if (value === true || value === false || typeof value === "number") {
|
||||||
value = value.toString();
|
value = value.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,7 +68,7 @@ function setOption(name: string, value: string | boolean) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function createOption(name: string, value: string, isSynced: boolean) {
|
function createOption(name: string, value: string | number, isSynced: boolean) {
|
||||||
// to avoid circular dependency, need to find a better solution
|
// to avoid circular dependency, need to find a better solution
|
||||||
const BOption = require('../becca/entities/boption');
|
const BOption = require('../becca/entities/boption');
|
||||||
|
|
||||||
@ -84,7 +84,7 @@ function getOptions() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getOptionMap() {
|
function getOptionMap() {
|
||||||
const map: Record<string, string> = {};
|
const map: Record<string | number, string> = {};
|
||||||
|
|
||||||
for (const option of Object.values(becca.options)) {
|
for (const option of Object.values(becca.options)) {
|
||||||
map[option.name] = option.value;
|
map[option.name] = option.value;
|
||||||
|
@ -33,7 +33,7 @@ interface Client {
|
|||||||
request(opts: ClientOpts): Request;
|
request(opts: ClientOpts): Request;
|
||||||
}
|
}
|
||||||
|
|
||||||
function exec(opts: ExecOpts) {
|
function exec<T>(opts: ExecOpts): Promise<T> {
|
||||||
const client = getClient(opts);
|
const client = getClient(opts);
|
||||||
|
|
||||||
// hack for cases where electron.net does not work, but we don't want to set proxy
|
// hack for cases where electron.net does not work, but we don't want to set proxy
|
||||||
@ -129,7 +129,7 @@ function exec(opts: ExecOpts) {
|
|||||||
: opts.body;
|
: opts.body;
|
||||||
}
|
}
|
||||||
|
|
||||||
request.end(payload);
|
request.end(payload as string);
|
||||||
}
|
}
|
||||||
catch (e: any) {
|
catch (e: any) {
|
||||||
reject(generateError(opts, e.message));
|
reject(generateError(opts, e.message));
|
||||||
|
@ -3,7 +3,7 @@ export interface CookieJar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ExecOpts {
|
export interface ExecOpts {
|
||||||
proxy: "noproxy" | null;
|
proxy: string | null;
|
||||||
method: string;
|
method: string;
|
||||||
url: string;
|
url: string;
|
||||||
paging?: {
|
paging?: {
|
||||||
@ -16,5 +16,5 @@ export interface ExecOpts {
|
|||||||
password?: string;
|
password?: string;
|
||||||
},
|
},
|
||||||
timeout: number;
|
timeout: number;
|
||||||
body: string;
|
body?: string | {};
|
||||||
}
|
}
|
@ -1,15 +1,16 @@
|
|||||||
const syncService = require('./sync');
|
import syncService = require('./sync');
|
||||||
const log = require('./log');
|
import log = require('./log');
|
||||||
const sqlInit = require('./sql_init');
|
import sqlInit = require('./sql_init');
|
||||||
const optionService = require('./options');
|
import optionService = require('./options');
|
||||||
const syncOptions = require('./sync_options');
|
import syncOptions = require('./sync_options');
|
||||||
const request = require('./request');
|
import request = require('./request');
|
||||||
const appInfo = require('./app_info');
|
import appInfo = require('./app_info');
|
||||||
const utils = require('./utils');
|
import utils = require('./utils');
|
||||||
const becca = require('../becca/becca');
|
import becca = require('../becca/becca');
|
||||||
|
import { SetupStatusResponse, SetupSyncSeedResponse } from './api-interface';
|
||||||
|
|
||||||
async function hasSyncServerSchemaAndSeed() {
|
async function hasSyncServerSchemaAndSeed() {
|
||||||
const response = await requestToSyncServer('GET', '/api/setup/status');
|
const response = await requestToSyncServer<SetupStatusResponse>('GET', '/api/setup/status');
|
||||||
|
|
||||||
if (response.syncVersion !== appInfo.syncVersion) {
|
if (response.syncVersion !== appInfo.syncVersion) {
|
||||||
throw new Error(`Could not setup sync since local sync protocol version is ${appInfo.syncVersion} while remote is ${response.syncVersion}. To fix this issue, use same Trilium version on all instances.`);
|
throw new Error(`Could not setup sync since local sync protocol version is ${appInfo.syncVersion} while remote is ${response.syncVersion}. To fix this issue, use same Trilium version on all instances.`);
|
||||||
@ -32,7 +33,7 @@ function triggerSync() {
|
|||||||
async function sendSeedToSyncServer() {
|
async function sendSeedToSyncServer() {
|
||||||
log.info("Initiating sync to server");
|
log.info("Initiating sync to server");
|
||||||
|
|
||||||
await requestToSyncServer('POST', '/api/setup/sync-seed', {
|
await requestToSyncServer<void>('POST', '/api/setup/sync-seed', {
|
||||||
options: getSyncSeedOptions(),
|
options: getSyncSeedOptions(),
|
||||||
syncVersion: appInfo.syncVersion
|
syncVersion: appInfo.syncVersion
|
||||||
});
|
});
|
||||||
@ -43,7 +44,7 @@ async function sendSeedToSyncServer() {
|
|||||||
optionService.setOption('lastSyncedPull', 0);
|
optionService.setOption('lastSyncedPull', 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function requestToSyncServer(method, path, body = null) {
|
async function requestToSyncServer<T>(method: string, path: string, body?: string | {}): Promise<T> {
|
||||||
const timeout = syncOptions.getSyncTimeout();
|
const timeout = syncOptions.getSyncTimeout();
|
||||||
|
|
||||||
return await utils.timeLimit(request.exec({
|
return await utils.timeLimit(request.exec({
|
||||||
@ -52,10 +53,10 @@ async function requestToSyncServer(method, path, body = null) {
|
|||||||
body,
|
body,
|
||||||
proxy: syncOptions.getSyncProxy(),
|
proxy: syncOptions.getSyncProxy(),
|
||||||
timeout: timeout
|
timeout: timeout
|
||||||
}), timeout);
|
}), timeout) as T;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function setupSyncFromSyncServer(syncServerHost, syncProxy, password) {
|
async function setupSyncFromSyncServer(syncServerHost: string, syncProxy: string, password: string) {
|
||||||
if (sqlInit.isDbInitialized()) {
|
if (sqlInit.isDbInitialized()) {
|
||||||
return {
|
return {
|
||||||
result: 'failure',
|
result: 'failure',
|
||||||
@ -67,7 +68,7 @@ async function setupSyncFromSyncServer(syncServerHost, syncProxy, password) {
|
|||||||
log.info("Getting document options FROM sync server.");
|
log.info("Getting document options FROM sync server.");
|
||||||
|
|
||||||
// the response is expected to contain documentId and documentSecret options
|
// the response is expected to contain documentId and documentSecret options
|
||||||
const resp = await request.exec({
|
const resp = await request.exec<SetupSyncSeedResponse>({
|
||||||
method: 'get',
|
method: 'get',
|
||||||
url: `${syncServerHost}/api/setup/sync-seed`,
|
url: `${syncServerHost}/api/setup/sync-seed`,
|
||||||
auth: { password },
|
auth: { password },
|
||||||
@ -92,7 +93,7 @@ async function setupSyncFromSyncServer(syncServerHost, syncProxy, password) {
|
|||||||
|
|
||||||
return { result: 'success' };
|
return { result: 'success' };
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e: any) {
|
||||||
log.error(`Sync failed: '${e.message}', stack: ${e.stack}`);
|
log.error(`Sync failed: '${e.message}', stack: ${e.stack}`);
|
||||||
|
|
||||||
return {
|
return {
|
@ -1,9 +1,19 @@
|
|||||||
|
type Updater = () => void;
|
||||||
|
|
||||||
class SpacedUpdate {
|
class SpacedUpdate {
|
||||||
constructor(updater, updateInterval = 1000) {
|
|
||||||
|
private updater: Updater;
|
||||||
|
private lastUpdated: number;
|
||||||
|
private changed: boolean;
|
||||||
|
private updateInterval: number;
|
||||||
|
private changeForbidden: boolean;
|
||||||
|
|
||||||
|
constructor(updater: Updater, updateInterval = 1000) {
|
||||||
this.updater = updater;
|
this.updater = updater;
|
||||||
this.lastUpdated = Date.now();
|
this.lastUpdated = Date.now();
|
||||||
this.changed = false;
|
this.changed = false;
|
||||||
this.updateInterval = updateInterval;
|
this.updateInterval = updateInterval;
|
||||||
|
this.changeForbidden = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
scheduleUpdate() {
|
scheduleUpdate() {
|
||||||
@ -52,7 +62,7 @@ class SpacedUpdate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async allowUpdateWithoutChange(callback) {
|
async allowUpdateWithoutChange(callback: () => void) {
|
||||||
this.changeForbidden = true;
|
this.changeForbidden = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -64,4 +74,4 @@ class SpacedUpdate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = SpacedUpdate;
|
export = SpacedUpdate;
|
@ -1,16 +1,20 @@
|
|||||||
const attributeService = require('./attributes');
|
import attributeService = require('./attributes');
|
||||||
const dateNoteService = require('./date_notes');
|
import dateNoteService = require('./date_notes');
|
||||||
const becca = require('../becca/becca');
|
import becca = require('../becca/becca');
|
||||||
const noteService = require('./notes');
|
import noteService = require('./notes');
|
||||||
const dateUtils = require('./date_utils');
|
import dateUtils = require('./date_utils');
|
||||||
const log = require('./log');
|
import log = require('./log');
|
||||||
const hoistedNoteService = require('./hoisted_note');
|
import hoistedNoteService = require('./hoisted_note');
|
||||||
const searchService = require('./search/services/search');
|
import searchService = require('./search/services/search');
|
||||||
const SearchContext = require('./search/search_context');
|
import SearchContext = require('./search/search_context');
|
||||||
const {LBTPL_NOTE_LAUNCHER, LBTPL_CUSTOM_WIDGET, LBTPL_SPACER, LBTPL_SCRIPT} = require('./hidden_subtree');
|
import hiddenSubtree = require('./hidden_subtree');
|
||||||
|
const { LBTPL_NOTE_LAUNCHER, LBTPL_CUSTOM_WIDGET, LBTPL_SPACER, LBTPL_SCRIPT } = hiddenSubtree;
|
||||||
|
|
||||||
function getInboxNote(date) {
|
function getInboxNote(date: string) {
|
||||||
const workspaceNote = hoistedNoteService.getWorkspaceNote();
|
const workspaceNote = hoistedNoteService.getWorkspaceNote();
|
||||||
|
if (!workspaceNote) {
|
||||||
|
throw new Error("Unable to find workspace note");
|
||||||
|
}
|
||||||
|
|
||||||
let inbox;
|
let inbox;
|
||||||
|
|
||||||
@ -48,8 +52,9 @@ function createSqlConsole() {
|
|||||||
return note;
|
return note;
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveSqlConsole(sqlConsoleNoteId) {
|
function saveSqlConsole(sqlConsoleNoteId: string) {
|
||||||
const sqlConsoleNote = becca.getNote(sqlConsoleNoteId);
|
const sqlConsoleNote = becca.getNote(sqlConsoleNoteId);
|
||||||
|
if (!sqlConsoleNote) throw new Error(`Unable to find SQL console note ID: ${sqlConsoleNoteId}`);
|
||||||
const today = dateUtils.localNowDate();
|
const today = dateUtils.localNowDate();
|
||||||
|
|
||||||
const sqlConsoleHome =
|
const sqlConsoleHome =
|
||||||
@ -59,7 +64,7 @@ function saveSqlConsole(sqlConsoleNoteId) {
|
|||||||
const result = sqlConsoleNote.cloneTo(sqlConsoleHome.noteId);
|
const result = sqlConsoleNote.cloneTo(sqlConsoleHome.noteId);
|
||||||
|
|
||||||
for (const parentBranch of sqlConsoleNote.getParentBranches()) {
|
for (const parentBranch of sqlConsoleNote.getParentBranches()) {
|
||||||
if (parentBranch.parentNote.hasAncestor('_hidden')) {
|
if (parentBranch.parentNote?.hasAncestor('_hidden')) {
|
||||||
parentBranch.markAsDeleted();
|
parentBranch.markAsDeleted();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -67,7 +72,7 @@ function saveSqlConsole(sqlConsoleNoteId) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createSearchNote(searchString, ancestorNoteId) {
|
function createSearchNote(searchString: string, ancestorNoteId: string) {
|
||||||
const {note} = noteService.createNewNote({
|
const {note} = noteService.createNewNote({
|
||||||
parentNoteId: getMonthlyParentNoteId('_search', 'search'),
|
parentNoteId: getMonthlyParentNoteId('_search', 'search'),
|
||||||
title: `Search: ${searchString}`,
|
title: `Search: ${searchString}`,
|
||||||
@ -88,6 +93,9 @@ function createSearchNote(searchString, ancestorNoteId) {
|
|||||||
|
|
||||||
function getSearchHome() {
|
function getSearchHome() {
|
||||||
const workspaceNote = hoistedNoteService.getWorkspaceNote();
|
const workspaceNote = hoistedNoteService.getWorkspaceNote();
|
||||||
|
if (!workspaceNote) {
|
||||||
|
throw new Error("Unable to find workspace note");
|
||||||
|
}
|
||||||
|
|
||||||
if (!workspaceNote.isRoot()) {
|
if (!workspaceNote.isRoot()) {
|
||||||
return workspaceNote.searchNoteInSubtree('#workspaceSearchHome')
|
return workspaceNote.searchNoteInSubtree('#workspaceSearchHome')
|
||||||
@ -101,14 +109,18 @@ function getSearchHome() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveSearchNote(searchNoteId) {
|
function saveSearchNote(searchNoteId: string) {
|
||||||
const searchNote = becca.getNote(searchNoteId);
|
const searchNote = becca.getNote(searchNoteId);
|
||||||
|
if (!searchNote) {
|
||||||
|
throw new Error("Unable to find search note");
|
||||||
|
}
|
||||||
|
|
||||||
const searchHome = getSearchHome();
|
const searchHome = getSearchHome();
|
||||||
|
|
||||||
const result = searchNote.cloneTo(searchHome.noteId);
|
const result = searchNote.cloneTo(searchHome.noteId);
|
||||||
|
|
||||||
for (const parentBranch of searchNote.getParentBranches()) {
|
for (const parentBranch of searchNote.getParentBranches()) {
|
||||||
if (parentBranch.parentNote.hasAncestor('_hidden')) {
|
if (parentBranch.parentNote?.hasAncestor('_hidden')) {
|
||||||
parentBranch.markAsDeleted();
|
parentBranch.markAsDeleted();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -116,7 +128,7 @@ function saveSearchNote(searchNoteId) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getMonthlyParentNoteId(rootNoteId, prefix) {
|
function getMonthlyParentNoteId(rootNoteId: string, prefix: string) {
|
||||||
const month = dateUtils.localNowDate().substring(0, 7);
|
const month = dateUtils.localNowDate().substring(0, 7);
|
||||||
const labelName = `${prefix}MonthNote`;
|
const labelName = `${prefix}MonthNote`;
|
||||||
|
|
||||||
@ -138,7 +150,7 @@ function getMonthlyParentNoteId(rootNoteId, prefix) {
|
|||||||
return monthNote.noteId;
|
return monthNote.noteId;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createScriptLauncher(parentNoteId, forceNoteId = null) {
|
function createScriptLauncher(parentNoteId: string, forceNoteId?: string) {
|
||||||
const note = noteService.createNewNote({
|
const note = noteService.createNewNote({
|
||||||
noteId: forceNoteId,
|
noteId: forceNoteId,
|
||||||
title: "Script Launcher",
|
title: "Script Launcher",
|
||||||
@ -151,7 +163,13 @@ function createScriptLauncher(parentNoteId, forceNoteId = null) {
|
|||||||
return note;
|
return note;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createLauncher({parentNoteId, launcherType, noteId}) {
|
interface LauncherConfig {
|
||||||
|
parentNoteId: string;
|
||||||
|
launcherType: string;
|
||||||
|
noteId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createLauncher({ parentNoteId, launcherType, noteId }: LauncherConfig) {
|
||||||
let note;
|
let note;
|
||||||
|
|
||||||
if (launcherType === 'note') {
|
if (launcherType === 'note') {
|
||||||
@ -197,10 +215,10 @@ function createLauncher({parentNoteId, launcherType, noteId}) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetLauncher(noteId) {
|
function resetLauncher(noteId: string) {
|
||||||
const note = becca.getNote(noteId);
|
const note = becca.getNote(noteId);
|
||||||
|
|
||||||
if (note.isLaunchBarConfig()) {
|
if (note?.isLaunchBarConfig()) {
|
||||||
if (note) {
|
if (note) {
|
||||||
if (noteId === '_lbRoot') {
|
if (noteId === '_lbRoot') {
|
||||||
// deleting hoisted notes are not allowed, so we just reset the children
|
// deleting hoisted notes are not allowed, so we just reset the children
|
||||||
@ -228,7 +246,13 @@ function resetLauncher(noteId) {
|
|||||||
* Another use case was for script-packages (e.g. demo Task manager) which could this way register automatically/easily
|
* Another use case was for script-packages (e.g. demo Task manager) which could this way register automatically/easily
|
||||||
* into the launchbar - for this it's recommended to use backend API's createOrUpdateLauncher()
|
* into the launchbar - for this it's recommended to use backend API's createOrUpdateLauncher()
|
||||||
*/
|
*/
|
||||||
function createOrUpdateScriptLauncherFromApi(opts) {
|
function createOrUpdateScriptLauncherFromApi(opts: {
|
||||||
|
id: string;
|
||||||
|
title: string;
|
||||||
|
action: string;
|
||||||
|
icon?: string;
|
||||||
|
shortcut?: string;
|
||||||
|
}) {
|
||||||
if (opts.id && !/^[a-z0-9]+$/i.test(opts.id)) {
|
if (opts.id && !/^[a-z0-9]+$/i.test(opts.id)) {
|
||||||
throw new Error(`Launcher ID can be alphanumeric only, '${opts.id}' given`);
|
throw new Error(`Launcher ID can be alphanumeric only, '${opts.id}' given`);
|
||||||
}
|
}
|
||||||
@ -263,7 +287,7 @@ function createOrUpdateScriptLauncherFromApi(opts) {
|
|||||||
return launcherNote;
|
return launcherNote;
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
export = {
|
||||||
getInboxNote,
|
getInboxNote,
|
||||||
createSqlConsole,
|
createSqlConsole,
|
||||||
saveSqlConsole,
|
saveSqlConsole,
|
@ -96,7 +96,7 @@ async function createInitialDatabase() {
|
|||||||
|
|
||||||
const dummyTaskContext = new TaskContext("no-progress-reporting", 'import', false);
|
const dummyTaskContext = new TaskContext("no-progress-reporting", 'import', false);
|
||||||
|
|
||||||
const zipImportService = require('./import/zip.js');
|
const zipImportService = require('./import/zip');
|
||||||
await zipImportService.importZip(dummyTaskContext, demoFile, rootNote);
|
await zipImportService.importZip(dummyTaskContext, demoFile, rootNote);
|
||||||
|
|
||||||
sql.transactional(() => {
|
sql.transactional(() => {
|
||||||
@ -179,7 +179,7 @@ dbReady.then(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
function getDbSize() {
|
function getDbSize() {
|
||||||
return sql.getValue("SELECT page_count * page_size / 1000 as size FROM pragma_page_count(), pragma_page_size()");
|
return sql.getValue<number>("SELECT page_count * page_size / 1000 as size FROM pragma_page_count(), pragma_page_size()");
|
||||||
}
|
}
|
||||||
|
|
||||||
log.info(`DB size: ${getDbSize()} KB`);
|
log.info(`DB size: ${getDbSize()} KB`);
|
||||||
|
@ -107,7 +107,7 @@ async function sync() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function login() {
|
async function login() {
|
||||||
const setupService = require('./setup.js'); // circular dependency issue
|
const setupService = require('./setup'); // circular dependency issue
|
||||||
|
|
||||||
if (!await setupService.hasSyncServerSchemaAndSeed()) {
|
if (!await setupService.hasSyncServerSchemaAndSeed()) {
|
||||||
await setupService.sendSeedToSyncServer();
|
await setupService.sendSeedToSyncServer();
|
||||||
@ -282,7 +282,7 @@ async function checkContentHash(syncContext: SyncContext) {
|
|||||||
|
|
||||||
if (failedChecks.length > 0) {
|
if (failedChecks.length > 0) {
|
||||||
// before re-queuing sectors, make sure the entity changes are correct
|
// before re-queuing sectors, make sure the entity changes are correct
|
||||||
const consistencyChecks = require('./consistency_checks.js');
|
const consistencyChecks = require('./consistency_checks');
|
||||||
consistencyChecks.runEntityChangesChecks();
|
consistencyChecks.runEntityChangesChecks();
|
||||||
|
|
||||||
await syncRequest(syncContext, 'POST', `/api/sync/check-entity-changes`);
|
await syncRequest(syncContext, 'POST', `/api/sync/check-entity-changes`);
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
|
import { TaskData } from './task_context_interface';
|
||||||
import ws = require('./ws');
|
import ws = require('./ws');
|
||||||
|
|
||||||
// taskId => TaskContext
|
// taskId => TaskContext
|
||||||
@ -9,9 +10,9 @@ class TaskContext {
|
|||||||
|
|
||||||
private taskId: string;
|
private taskId: string;
|
||||||
private taskType: string | null;
|
private taskType: string | null;
|
||||||
private data: {} | null;
|
|
||||||
private progressCount: number;
|
private progressCount: number;
|
||||||
private lastSentCountTs: number;
|
private lastSentCountTs: number;
|
||||||
|
data: TaskData | null;
|
||||||
noteDeletionHandlerTriggered: boolean;
|
noteDeletionHandlerTriggered: boolean;
|
||||||
|
|
||||||
constructor(taskId: string, taskType: string | null = null, data: {} | null = {}) {
|
constructor(taskId: string, taskType: string | null = null, data: {} | null = {}) {
|
||||||
@ -65,7 +66,7 @@ class TaskContext {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
taskSucceeded(result: string) {
|
taskSucceeded(result?: string) {
|
||||||
ws.sendMessageToAllClients({
|
ws.sendMessageToAllClients({
|
||||||
type: 'taskSucceeded',
|
type: 'taskSucceeded',
|
||||||
taskId: this.taskId,
|
taskId: this.taskId,
|
||||||
|
7
src/services/task_context_interface.ts
Normal file
7
src/services/task_context_interface.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export interface TaskData {
|
||||||
|
safeImport?: boolean;
|
||||||
|
textImportedAsText?: boolean;
|
||||||
|
codeImportedAsCode?: boolean;
|
||||||
|
shrinkImages?: boolean;
|
||||||
|
replaceUnderscoresWithSpaces?: boolean;
|
||||||
|
}
|
@ -226,8 +226,8 @@ function removeTextFileExtension(filePath: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getNoteTitle(filePath: string, replaceUnderscoresWithSpaces: boolean, noteMeta: { title: string }) {
|
function getNoteTitle(filePath: string, replaceUnderscoresWithSpaces: boolean, noteMeta?: { title?: string }) {
|
||||||
if (noteMeta) {
|
if (noteMeta?.title) {
|
||||||
return noteMeta.title;
|
return noteMeta.title;
|
||||||
} else {
|
} else {
|
||||||
const basename = path.basename(removeTextFileExtension(filePath));
|
const basename = path.basename(removeTextFileExtension(filePath));
|
||||||
|
@ -12,6 +12,7 @@ import AbstractBeccaEntity = require('../becca/entities/abstract_becca_entity');
|
|||||||
import env = require('./env');
|
import env = require('./env');
|
||||||
import { IncomingMessage, Server } from 'http';
|
import { IncomingMessage, Server } from 'http';
|
||||||
import { EntityChange } from './entity_changes_interface';
|
import { EntityChange } from './entity_changes_interface';
|
||||||
|
|
||||||
if (env.isDev()) {
|
if (env.isDev()) {
|
||||||
const chokidar = require('chokidar');
|
const chokidar = require('chokidar');
|
||||||
const debounce = require('debounce');
|
const debounce = require('debounce');
|
||||||
@ -30,7 +31,8 @@ interface Message {
|
|||||||
type: string;
|
type: string;
|
||||||
data?: {
|
data?: {
|
||||||
lastSyncedPush?: number | null,
|
lastSyncedPush?: number | null,
|
||||||
entityChanges?: any[]
|
entityChanges?: any[],
|
||||||
|
shrinkImages?: boolean
|
||||||
} | null,
|
} | null,
|
||||||
lastSyncedPush?: number | null,
|
lastSyncedPush?: number | null,
|
||||||
|
|
||||||
|
13
src/types.d.ts
vendored
13
src/types.d.ts
vendored
@ -16,4 +16,17 @@ declare module 'html2plaintext' {
|
|||||||
declare module 'normalize-strings' {
|
declare module 'normalize-strings' {
|
||||||
function normalizeString(string: string): string;
|
function normalizeString(string: string): string;
|
||||||
export = normalizeString;
|
export = normalizeString;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module 'joplin-turndown-plugin-gfm' {
|
||||||
|
import TurndownService = require("turndown");
|
||||||
|
namespace gfm {
|
||||||
|
function gfm(service: TurndownService): void;
|
||||||
|
}
|
||||||
|
export = gfm;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module 'is-animated' {
|
||||||
|
function isAnimated(buffer: Buffer): boolean;
|
||||||
|
export = isAnimated;
|
||||||
}
|
}
|
@ -4,7 +4,7 @@ const assetPath = require('./src/services/asset_path');
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
mode: 'production',
|
mode: 'production',
|
||||||
entry: {
|
entry: {
|
||||||
setup: './src/public/app/setup.js',
|
setup: './src/public/app/setup.ts',
|
||||||
mobile: './src/public/app/mobile.js',
|
mobile: './src/public/app/mobile.js',
|
||||||
desktop: './src/public/app/desktop.js',
|
desktop: './src/public/app/desktop.js',
|
||||||
},
|
},
|
||||||
|
Loading…
x
Reference in New Issue
Block a user