mirror of
https://github.com/zadam/trilium.git
synced 2025-11-01 10:55:55 +01:00
Compare commits
380 Commits
v0.63.3
...
v0.90.0-be
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b811f3d399 | ||
|
|
bbbbc3b860 | ||
|
|
c9f27547a2 | ||
|
|
6bf063b92f | ||
|
|
590442b0d5 | ||
|
|
4862b324a5 | ||
|
|
f678c4337b | ||
|
|
686b9c840e | ||
|
|
2e8b2d4b81 | ||
|
|
3d423d25f8 | ||
|
|
79123b0c63 | ||
|
|
595402adea | ||
|
|
f40257b591 | ||
|
|
b6ca2281f7 | ||
|
|
186650b1cf | ||
|
|
1dad4bc80f | ||
|
|
d66a70f099 | ||
|
|
0792714e45 | ||
|
|
6ecbf1c528 | ||
|
|
5460359753 | ||
|
|
75ebe24abb | ||
|
|
3f5e5fd840 | ||
|
|
998db5ce95 | ||
|
|
7aed6d4ad4 | ||
|
|
99ea741275 | ||
|
|
e6ff99198f | ||
|
|
7c43a49046 | ||
|
|
5b30bae5b2 | ||
|
|
947aaa6156 | ||
|
|
534fe87fb1 | ||
|
|
1652a7a2ec | ||
|
|
7184c648ab | ||
|
|
be239d2230 | ||
|
|
439bf410ac | ||
|
|
5aadcb69f8 | ||
|
|
543c29e539 | ||
|
|
8bcff49198 | ||
|
|
c0cc1e3b52 | ||
|
|
19550eec6a | ||
|
|
45f2691053 | ||
|
|
ba2931b854 | ||
|
|
d5c1bd1085 | ||
|
|
606490a611 | ||
|
|
d99cc11d8b | ||
|
|
1b0690ddfc | ||
|
|
ef92d17272 | ||
|
|
7fd63e8064 | ||
|
|
d18bfb9d69 | ||
|
|
60d134a89b | ||
|
|
9b88228be9 | ||
|
|
0a7fcf399f | ||
|
|
408369fcac | ||
|
|
c435b97e10 | ||
|
|
e7c3dab56f | ||
|
|
374b1f9bb2 | ||
|
|
5f23a4e3c7 | ||
|
|
4e93209fbd | ||
|
|
1cff016923 | ||
|
|
a80a857e46 | ||
|
|
47c5f60a85 | ||
|
|
ec2e6f2507 | ||
|
|
8eb1e803b5 | ||
|
|
44ad22ceea | ||
|
|
40dfc46250 | ||
|
|
9775a8b7c5 | ||
|
|
82a437f2a8 | ||
|
|
a64a0e52ec | ||
|
|
88c4171031 | ||
|
|
b6c7e2e48f | ||
|
|
305d28b5b3 | ||
|
|
a88bf68eb6 | ||
|
|
8ebebecd93 | ||
|
|
f0d30dbe49 | ||
|
|
d142d3261d | ||
|
|
25b49e1ca2 | ||
|
|
c7f19e04fa | ||
|
|
a2711cfb7b | ||
|
|
ade6d08ded | ||
|
|
045f318612 | ||
|
|
2d67abbc12 | ||
|
|
5f539427a9 | ||
|
|
f984b361ee | ||
|
|
3e82d30a32 | ||
|
|
d2604e91d4 | ||
|
|
e2b4eaf9cc | ||
|
|
0a69ed91b6 | ||
|
|
8c0ce174b5 | ||
|
|
c8adf2a685 | ||
|
|
a68b75f069 | ||
|
|
2771bd4ece | ||
|
|
19cf9df52c | ||
|
|
15168fb213 | ||
|
|
0d700d6951 | ||
|
|
67cb02ed92 | ||
|
|
1d1ccc8d63 | ||
|
|
262e4db0f2 | ||
|
|
c814187a25 | ||
|
|
138be84e45 | ||
|
|
625d935f08 | ||
|
|
98d12901a5 | ||
|
|
1372cc1cb9 | ||
|
|
a072016fc5 | ||
|
|
97230ca82a | ||
|
|
4c69384b5d | ||
|
|
6ac3c172b1 | ||
|
|
a66e4435ba | ||
|
|
ae29699e65 | ||
|
|
9022bc338a | ||
|
|
dededcd303 | ||
|
|
4638351ec8 | ||
|
|
1ac65fff47 | ||
|
|
17eda952e4 | ||
|
|
8629993fe4 | ||
|
|
8297e1a835 | ||
|
|
b517b18394 | ||
|
|
32e34eec90 | ||
|
|
fb32ab9707 | ||
|
|
8acfb5b558 | ||
|
|
a7ae16bb39 | ||
|
|
c08393f04b | ||
|
|
88aba1c844 | ||
|
|
7c76d28f75 | ||
|
|
b3c2602620 | ||
|
|
0865e90cae | ||
|
|
e1d74cd2f5 | ||
|
|
c4c2259e69 | ||
|
|
3e4b0d5f91 | ||
|
|
b07df6061f | ||
|
|
80a1b8b44d | ||
|
|
45e3632c6e | ||
|
|
39e152b0b9 | ||
|
|
2a99ecc384 | ||
|
|
22477f90ae | ||
|
|
d8c2ba0ed8 | ||
|
|
74441273a8 | ||
|
|
ed79c1c62a | ||
|
|
9b9b452055 | ||
|
|
5fdf094e9d | ||
|
|
e4024408bd | ||
|
|
602b4988ae | ||
|
|
071f9400d7 | ||
|
|
1e2a30adcc | ||
|
|
a6de065bf4 | ||
|
|
3bd7231ba9 | ||
|
|
4bb46aeb9c | ||
|
|
26859e83e4 | ||
|
|
569bdf19be | ||
|
|
b8eb301f34 | ||
|
|
10c21d3403 | ||
|
|
a18b054dbb | ||
|
|
2e906af77d | ||
|
|
34cd2eba91 | ||
|
|
7fe6d1ab4d | ||
|
|
c2eefad287 | ||
|
|
202fda3c30 | ||
|
|
4940207b15 | ||
|
|
79c9d7e305 | ||
|
|
6e042c20e9 | ||
|
|
7a98718e64 | ||
|
|
6bbb1f8404 | ||
|
|
cecfc4cd34 | ||
|
|
c1875a8c8f | ||
|
|
249e81c9eb | ||
|
|
5804dc52bc | ||
|
|
90cf913083 | ||
|
|
fa82158e30 | ||
|
|
de42df40bb | ||
|
|
6265aa99d3 | ||
|
|
4ab6f159e5 | ||
|
|
b50ceaf299 | ||
|
|
96c8c9080d | ||
|
|
66d7548046 | ||
|
|
4b1c351195 | ||
|
|
eb7a7e4988 | ||
|
|
3d75366f02 | ||
|
|
c63c7d518c | ||
|
|
37697c7db7 | ||
|
|
b1744c3867 | ||
|
|
b0d6035a67 | ||
|
|
3902719008 | ||
|
|
291b791b67 | ||
|
|
b552f40ae8 | ||
|
|
0ab137de03 | ||
|
|
952c3cc12f | ||
|
|
e451237361 | ||
|
|
27637b0483 | ||
|
|
122ff3bb1d | ||
|
|
f98f84d419 | ||
|
|
40ef533c5f | ||
|
|
fd77c5e8c4 | ||
|
|
706b9d0f46 | ||
|
|
82f5553980 | ||
|
|
c0349b3f84 | ||
|
|
9330241045 | ||
|
|
b13ad5d01e | ||
|
|
ea36b37f66 | ||
|
|
92ca32bd70 | ||
|
|
59c533cb6c | ||
|
|
884b6618fb | ||
|
|
15dee4b952 | ||
|
|
a154dc76ce | ||
|
|
532ed1d3f9 | ||
|
|
984ce49168 | ||
|
|
16283d4054 | ||
|
|
915de23e34 | ||
|
|
53d4873c1f | ||
|
|
764d251b0a | ||
|
|
aa233b8adb | ||
|
|
ed47c23e23 | ||
|
|
5baabecdee | ||
|
|
17c7e2d8e7 | ||
|
|
5d452a1525 | ||
|
|
f857b8a9bb | ||
|
|
a354b54a08 | ||
|
|
e96b56e061 | ||
|
|
0daa4cc89a | ||
|
|
99d50957dd | ||
|
|
d4c8d24d50 | ||
|
|
a420129631 | ||
|
|
090d353fd0 | ||
|
|
b84b27692c | ||
|
|
adc384a971 | ||
|
|
08f0c01eef | ||
|
|
db2b33704f | ||
|
|
3eb7ed5dda | ||
|
|
5e5add7e47 | ||
|
|
ac1e1ebe43 | ||
|
|
499bfaea9b | ||
|
|
fa4772b91e | ||
|
|
fa7b190c6b | ||
|
|
84feec2e7e | ||
|
|
3035473751 | ||
|
|
399458b52f | ||
|
|
becac5fbad | ||
|
|
1e26b31090 | ||
|
|
0ebd03869d | ||
|
|
a4a713f102 | ||
|
|
83d5d6bbd8 | ||
|
|
90f4c6b0d1 | ||
|
|
926b3e9650 | ||
|
|
cbad58201e | ||
|
|
040ed39a4e | ||
|
|
9f6a8dc75c | ||
|
|
bfb8aa6481 | ||
|
|
2bb4cccd82 | ||
|
|
6fad5f2b51 | ||
|
|
36357bdf86 | ||
|
|
80eaf10656 | ||
|
|
f46ce0417d | ||
|
|
6f1e6402f0 | ||
|
|
bdfa13a8a0 | ||
|
|
d5622dfbf7 | ||
|
|
8c5f680dca | ||
|
|
7f0102181d | ||
|
|
59d618f06b | ||
|
|
cc1a545e13 | ||
|
|
fd37fd3a45 | ||
|
|
052a0a44f2 | ||
|
|
fa0ed35752 | ||
|
|
29d37c40c1 | ||
|
|
1728365fa1 | ||
|
|
cfeb0cc6f7 | ||
|
|
ec4bd6659a | ||
|
|
0903cf2646 | ||
|
|
cbc01e1a8d | ||
|
|
11c7533984 | ||
|
|
aff1c30557 | ||
|
|
3d9da26bb3 | ||
|
|
91ddabbb9b | ||
|
|
3030fbe60b | ||
|
|
5f4f30f84b | ||
|
|
45cb7ced2c | ||
|
|
dbccf6b433 | ||
|
|
6df09cb157 | ||
|
|
6cedad07e5 | ||
|
|
e4556afcc9 | ||
|
|
d89b791914 | ||
|
|
43c89c0e9d | ||
|
|
0ba80b176c | ||
|
|
3ea4b7a72b | ||
|
|
330334dcb4 | ||
|
|
ddcbb29a67 | ||
|
|
9ea4fcd667 | ||
|
|
0d4fb42731 | ||
|
|
d8d729342d | ||
|
|
2fbd2e3c29 | ||
|
|
15169289f0 | ||
|
|
29b3fb3646 | ||
|
|
533a597a5c | ||
|
|
deed58c2fc | ||
|
|
8acb64198c | ||
|
|
f5b690d088 | ||
|
|
0f7f0ceedc | ||
|
|
87708aa9c3 | ||
|
|
c63d05b582 | ||
|
|
460982d290 | ||
|
|
414964e791 | ||
|
|
3df6acda32 | ||
|
|
571c9f544c | ||
|
|
acb31b621f | ||
|
|
35886c3b00 | ||
|
|
ccc51e0123 | ||
|
|
0df488667e | ||
|
|
7018cc77a3 | ||
|
|
8d2eddd14d | ||
|
|
414515bc87 | ||
|
|
ce60fc0c3a | ||
|
|
216f3f2c07 | ||
|
|
80e6ced5db | ||
|
|
fbf77f3382 | ||
|
|
1010d11827 | ||
|
|
5d683721b1 | ||
|
|
de7f4de05b | ||
|
|
669988953d | ||
|
|
dc22d05657 | ||
|
|
00c692cf28 | ||
|
|
77fb7bc6e8 | ||
|
|
f31d788e2e | ||
|
|
45582ebaac | ||
|
|
748a551def | ||
|
|
d35613f510 | ||
|
|
3d5ef81860 | ||
|
|
9f99b4282a | ||
|
|
3b7812f829 | ||
|
|
1c7532df46 | ||
|
|
a029ee268a | ||
|
|
1b58dbe401 | ||
|
|
56ca0ea5d0 | ||
|
|
246a90d73c | ||
|
|
a58abffe58 | ||
|
|
8936a3fa3b | ||
|
|
6b8ee084e6 | ||
|
|
5916a8b5b8 | ||
|
|
a183f21078 | ||
|
|
9826fed905 | ||
|
|
a06aa9df8a | ||
|
|
1e91db865b | ||
|
|
8af5434462 | ||
|
|
6f0659c03c | ||
|
|
b8ccf5ba8f | ||
|
|
45a6c9558f | ||
|
|
4df599fec2 | ||
|
|
030aec2f3e | ||
|
|
8b250e4a13 | ||
|
|
2f96dc2d9d | ||
|
|
26388ad3b6 | ||
|
|
2c0063a5cc | ||
|
|
3a20bef1a9 | ||
|
|
9aec3390dd | ||
|
|
f9ba8ca87d | ||
|
|
f51f070b2f | ||
|
|
cf18e61a33 | ||
|
|
4b1d2c6bad | ||
|
|
06287da9d8 | ||
|
|
eef8297ce1 | ||
|
|
2f15d79476 | ||
|
|
768aaf2d78 | ||
|
|
6dd2cd39aa | ||
|
|
e04bd5aaf0 | ||
|
|
cb14d4d8f9 | ||
|
|
85af0a24ee | ||
|
|
3a7d0d1f13 | ||
|
|
5309d4ad5c | ||
|
|
786d851293 | ||
|
|
b63e870038 | ||
|
|
c20d2273e6 | ||
|
|
dc359b2a74 | ||
|
|
d3c9e7e157 | ||
|
|
82a9808dea | ||
|
|
e2a727ec8a | ||
|
|
7a9365457a | ||
|
|
28735fa16a | ||
|
|
ff2e05fe83 | ||
|
|
96b6d17d77 | ||
|
|
78f631373b | ||
|
|
981221d599 | ||
|
|
bda11fad40 | ||
|
|
39627a4fc4 | ||
|
|
094895ec72 | ||
|
|
8199073342 |
@@ -5,3 +5,6 @@
|
||||
/docs
|
||||
/npm-debug.log
|
||||
node_modules
|
||||
|
||||
src/**/*.ts
|
||||
!src/services/asset_path.ts
|
||||
@@ -1,7 +0,0 @@
|
||||
node_modules
|
||||
dist
|
||||
bin
|
||||
docs
|
||||
libraries
|
||||
coverage
|
||||
play
|
||||
212
.eslintrc.js
212
.eslintrc.js
@@ -1,212 +0,0 @@
|
||||
module.exports = {
|
||||
env: {
|
||||
browser: true,
|
||||
commonjs: true,
|
||||
es2021: true,
|
||||
node: true,
|
||||
},
|
||||
// plugins: ['prettier'], // to be activated
|
||||
extends: ['eslint:recommended', 'airbnb-base', 'plugin:jsonc/recommended-with-jsonc', 'prettier'],
|
||||
overrides: [
|
||||
{
|
||||
files: ['*.json', '*.json5', '*.jsonc'],
|
||||
parser: 'jsonc-eslint-parser',
|
||||
},
|
||||
{
|
||||
files: ['package.json'],
|
||||
parser: 'jsonc-eslint-parser',
|
||||
rules: {
|
||||
'jsonc/sort-keys': [
|
||||
'off',
|
||||
{
|
||||
pathPattern: '^$',
|
||||
order: [
|
||||
'name',
|
||||
'version',
|
||||
'private',
|
||||
'packageManager',
|
||||
'description',
|
||||
'type',
|
||||
'keywords',
|
||||
'homepage',
|
||||
'bugs',
|
||||
'license',
|
||||
'author',
|
||||
'contributors',
|
||||
'funding',
|
||||
'files',
|
||||
'main',
|
||||
'module',
|
||||
'exports',
|
||||
'unpkg',
|
||||
'jsdelivr',
|
||||
'browser',
|
||||
'bin',
|
||||
'man',
|
||||
'directories',
|
||||
'repository',
|
||||
'publishConfig',
|
||||
'scripts',
|
||||
'peerDependencies',
|
||||
'peerDependenciesMeta',
|
||||
'optionalDependencies',
|
||||
'dependencies',
|
||||
'devDependencies',
|
||||
'engines',
|
||||
'config',
|
||||
'overrides',
|
||||
'pnpm',
|
||||
'husky',
|
||||
'lint-staged',
|
||||
'eslintConfig',
|
||||
],
|
||||
},
|
||||
{
|
||||
pathPattern: '^(?:dev|peer|optional|bundled)?[Dd]ependencies$',
|
||||
order: { type: 'asc' },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
globals: {
|
||||
$: true,
|
||||
jQuery: true,
|
||||
glob: true,
|
||||
log: true,
|
||||
EditorWatchdog: true,
|
||||
React: true,
|
||||
appState: true,
|
||||
ExcalidrawLib: true,
|
||||
elements: true,
|
||||
files: true,
|
||||
ReactDOM: true,
|
||||
// src\public\app\widgets\type_widgets\relation_map.js
|
||||
jsPlumb: true,
|
||||
panzoom: true,
|
||||
logError: true,
|
||||
// src\public\app\widgets\type_widgets\image.js
|
||||
WZoom: true,
|
||||
// \src\public\app\widgets\type_widgets\read_only_text.js
|
||||
renderMathInElement: true,
|
||||
// \src\public\app\widgets\type_widgets\editable_text.js
|
||||
BalloonEditor: true,
|
||||
FancytreeNode: true,
|
||||
CKEditorInspector: true,
|
||||
// \src\public\app\widgets\type_widgets\editable_code.js
|
||||
CodeMirror: true,
|
||||
// \src\public\app\services\resizer.js
|
||||
Split: true,
|
||||
// \src\public\app\services\content_renderer.js
|
||||
mermaid: true,
|
||||
// src\public\app\services\frontend_script_api.js
|
||||
dayjs: true,
|
||||
// \src\public\app\widgets\note_map.js
|
||||
ForceGraph: true,
|
||||
// \src\public\app\setup.js
|
||||
ko: true,
|
||||
syncInProgress: true,
|
||||
// src\public\app\services\utils.js
|
||||
logInfo: true,
|
||||
__non_webpack_require__: true,
|
||||
describe: true,
|
||||
it: true,
|
||||
expect: true
|
||||
},
|
||||
parserOptions: {
|
||||
ecmaVersion: 'latest',
|
||||
sourceType: 'module',
|
||||
},
|
||||
rules: {
|
||||
// eslint:recommended
|
||||
'no-unused-vars': 'off',
|
||||
'linebreak-style': 'off',
|
||||
'no-useless-escape': 'off',
|
||||
'no-empty': 'off',
|
||||
'no-constant-condition': 'off',
|
||||
'getter-return': 'off',
|
||||
'no-cond-assign': 'off',
|
||||
'no-async-promise-executor': 'off',
|
||||
'no-extra-semi': 'off',
|
||||
'no-inner-declarations': 'off',
|
||||
|
||||
// prettier
|
||||
'prettier/prettier': ['off', { endOfLine: 'auto' }],
|
||||
|
||||
// airbnb-base
|
||||
'no-console': 'off',
|
||||
'no-plusplus': 'off',
|
||||
'no-param-reassign': 'off',
|
||||
'global-require': 'off',
|
||||
'no-use-before-define': 'off',
|
||||
'no-await-in-loop': 'off',
|
||||
radix: 'off',
|
||||
'import/order': 'off',
|
||||
'import/no-extraneous-dependencies': 'off',
|
||||
'prefer-destructuring': 'off',
|
||||
'no-shadow': 'off',
|
||||
'no-new': 'off',
|
||||
'no-restricted-syntax': 'off',
|
||||
strict: 'off',
|
||||
'class-methods-use-this': 'off',
|
||||
'no-else-return': 'off',
|
||||
'import/no-dynamic-require': 'off',
|
||||
'no-underscore-dangle': 'off',
|
||||
'prefer-template': 'off',
|
||||
'consistent-return': 'off',
|
||||
'no-continue': 'off',
|
||||
'object-shorthand': 'off',
|
||||
'one-var': 'off',
|
||||
'prefer-const': 'off',
|
||||
'spaced-comment': 'off',
|
||||
'no-loop-func': 'off',
|
||||
'arrow-body-style': 'off',
|
||||
|
||||
'guard-for-in': 'off',
|
||||
'no-return-assign': 'off',
|
||||
'dot-notation': 'off',
|
||||
|
||||
'func-names': 'off',
|
||||
'import/no-useless-path-segments': 'off',
|
||||
'default-param-last': 'off',
|
||||
'prefer-arrow-callback': 'off',
|
||||
'no-unneeded-ternary': 'off',
|
||||
'no-return-await': 'off',
|
||||
'import/extensions': 'off',
|
||||
|
||||
'no-var': 'off',
|
||||
'import/newline-after-import': 'off',
|
||||
'no-restricted-globals': 'off',
|
||||
'operator-assignment': 'off',
|
||||
'no-eval': 'off',
|
||||
'max-classes-per-file': 'off',
|
||||
'vars-on-top': 'off',
|
||||
'no-bitwise': 'off',
|
||||
'no-lonely-if': 'off',
|
||||
'no-multi-assign': 'off',
|
||||
'no-promise-executor-return': 'off',
|
||||
'no-empty-function': 'off',
|
||||
'import/no-unresolved': 'off',
|
||||
camelcase: 'off',
|
||||
eqeqeq: 'off',
|
||||
'lines-between-class-members': 'off',
|
||||
'import/no-cycle': 'off',
|
||||
'new-cap': 'off',
|
||||
'prefer-object-spread': 'off',
|
||||
'no-new-func': 'off',
|
||||
'no-unused-expressions': 'off',
|
||||
'lines-around-directive': 'off',
|
||||
'prefer-exponentiation-operator': 'off',
|
||||
'no-restricted-properties': 'off',
|
||||
'prefer-rest-params': 'off',
|
||||
'no-unreachable-loop': 'off',
|
||||
'no-alert': 'off',
|
||||
'no-useless-return': 'off',
|
||||
'no-nested-ternary': 'off',
|
||||
'prefer-regex-literals': 'off',
|
||||
'import/no-named-as-default-member': 'off',
|
||||
yoda: 'off',
|
||||
'no-script-url': 'off',
|
||||
'no-prototype-builtins':'off'
|
||||
},
|
||||
};
|
||||
25
.github/workflows/dev.yml
vendored
Normal file
25
.github/workflows/dev.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
name: Dev
|
||||
on:
|
||||
push:
|
||||
jobs:
|
||||
build_docker:
|
||||
name: Build Docker image
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up node & dependencies
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
cache: "npm"
|
||||
- run: npm ci
|
||||
- name: Run the TypeScript build
|
||||
run: npx tsc
|
||||
- name: Create server-package.json
|
||||
run: cat package.json | grep -v electron > server-package.json
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
- uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
147
.github/workflows/main.yml
vendored
Normal file
147
.github/workflows/main.yml
vendored
Normal file
@@ -0,0 +1,147 @@
|
||||
name: Main
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'develop'
|
||||
env:
|
||||
REGISTRY: ghcr.io
|
||||
IMAGE_NAME: ${{ github.repository }}
|
||||
jobs:
|
||||
build_darwin-x64:
|
||||
name: Build macOS x86_64
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up node & dependencies
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
cache: "npm"
|
||||
- run: npm ci
|
||||
- run: ./bin/build-mac-x64.sh
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: trilium-mac-x64.zip
|
||||
path: dist/trilium-mac-x64*.zip
|
||||
build_darwin-arm64:
|
||||
name: Build macOS aarch64
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up node & dependencies
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
cache: "npm"
|
||||
- run: npm ci
|
||||
- run: ./bin/build-mac-arm64.sh
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: trilium-mac-arm64.zip
|
||||
path: dist/trilium-mac-arm64*.zip
|
||||
build_linux-x64:
|
||||
name: Build Linux x86_64
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up node & dependencies
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
cache: "npm"
|
||||
- run: npm ci
|
||||
- run: ./bin/build-linux-x64.sh
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: trilium-linux-x64.tar.xz
|
||||
path: dist/trilium-linux-x64-*.tar.xz
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: trilium_amd64.deb
|
||||
path: dist/trilium_*.deb
|
||||
build_linux_server-x64:
|
||||
name: Build Linux Server x86_64
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up node & dependencies
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
cache: "npm"
|
||||
- run: npm ci
|
||||
- run: ./bin/build-server.sh
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: trilium-linux-x64-server.tar.xz
|
||||
path: dist/trilium-linux-x64-server-*.tar.xz
|
||||
build_windows-x64:
|
||||
name: Build Windows x86_64
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up Wine
|
||||
run: |
|
||||
sudo dpkg --add-architecture i386
|
||||
wget -qO - https://dl.winehq.org/wine-builds/winehq.key | sudo apt-key add -
|
||||
sudo add-apt-repository ppa:cybermax-dexter/sdl2-backport
|
||||
sudo apt-add-repository "deb https://dl.winehq.org/wine-builds/ubuntu $(lsb_release -cs) main"
|
||||
sudo apt install --install-recommends winehq-stable
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up node & dependencies
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
cache: "npm"
|
||||
- run: npm ci
|
||||
- run: ./bin/build-win-x64.sh
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: trilium-windows-x64.zip
|
||||
path: dist/trilium-windows-x64-*.zip
|
||||
build_docker:
|
||||
name: Build Docker image
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
attestations: write
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@65b78e6e13532edd9afa3aa52ac7964289d1a9c1
|
||||
with:
|
||||
registry: ${{ env.REGISTRY }}
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@9ec57ed1fcdbf14dcef7dfbe97b2010124a938b7
|
||||
with:
|
||||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||
- name: Set up node & dependencies
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 18
|
||||
cache: "npm"
|
||||
- run: npm ci
|
||||
- name: Run the TypeScript build
|
||||
run: npx tsc
|
||||
- name: Create server-package.json
|
||||
run: cat package.json | grep -v electron > server-package.json
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
- uses: docker/build-push-action@v6
|
||||
id: push
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
- name: Generate artifact attestation
|
||||
uses: actions/attest-build-provenance@v1
|
||||
with:
|
||||
subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME}}
|
||||
subject-digest: ${{ steps.push.outputs.digest }}
|
||||
push-to-registry: true
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,6 +1,7 @@
|
||||
.DS_Store
|
||||
node_modules/
|
||||
dist/
|
||||
build/
|
||||
src/public/app-dist/
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
|
||||
1
.husky/.gitignore
vendored
1
.husky/.gitignore
vendored
@@ -1 +0,0 @@
|
||||
_
|
||||
@@ -1,4 +0,0 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
#npx lint-staged
|
||||
6
.idea/jsLinters/eslint.xml
generated
6
.idea/jsLinters/eslint.xml
generated
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="EslintConfiguration">
|
||||
<option name="fix-on-save" value="true" />
|
||||
</component>
|
||||
</project>
|
||||
14
.prettierrc
Normal file
14
.prettierrc
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"printWidth": 200,
|
||||
"tabWidth": 4,
|
||||
"useTabs": false,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"quoteProps": "as-needed",
|
||||
"trailingComma": "none",
|
||||
"bracketSpacing": false,
|
||||
"arrowParens": "always",
|
||||
"proseWrap": "preserve",
|
||||
"htmlWhitespaceSensitivity": "css",
|
||||
"endOfLine": "lf"
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
//https://prettier.io/docs/en/options.html
|
||||
module.exports = {
|
||||
semi: true,
|
||||
trailingComma: 'none',
|
||||
singleQuote: true,
|
||||
printWidth: 100,
|
||||
tabWidth: 4,
|
||||
useTabs: false,
|
||||
quoteProps: "as-needed",
|
||||
bracketSpacing: true,
|
||||
arrowParens: "avoid"
|
||||
// htmlWhitespaceSensitivity: 'ignore',
|
||||
};
|
||||
6
.vscode/extensions.json
vendored
6
.vscode/extensions.json
vendored
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"dbaeumer.vscode-eslint",
|
||||
"esbenp.prettier-vscode",
|
||||
]
|
||||
}
|
||||
37
.vscode/settings.json
vendored
37
.vscode/settings.json
vendored
@@ -1,33 +1,6 @@
|
||||
{
|
||||
"[javascript]": {
|
||||
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
|
||||
},
|
||||
"[json]": {
|
||||
"editor.defaultFormatter": "dbaeumer.vscode-eslint"
|
||||
},
|
||||
"editor.formatOnSave": true,
|
||||
"eslint.format.enable": true,
|
||||
"eslint.probe": [
|
||||
"javascript",
|
||||
"javascriptreact",
|
||||
"typescript",
|
||||
"typescriptreact",
|
||||
"html",
|
||||
"vue",
|
||||
"markdown",
|
||||
"json",
|
||||
"jsonc"
|
||||
],
|
||||
"eslint.validate": [
|
||||
"javascript",
|
||||
"javascriptreact",
|
||||
"typescript",
|
||||
"typescriptreact",
|
||||
"html",
|
||||
"vue",
|
||||
"markdown",
|
||||
"json",
|
||||
"jsonc"
|
||||
],
|
||||
"files.eol": "\n",
|
||||
}
|
||||
"editor.formatOnSave": false,
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"files.eol": "\n",
|
||||
"typescript.tsdk": "node_modules/typescript/lib"
|
||||
}
|
||||
|
||||
30
Dockerfile
30
Dockerfile
@@ -1,6 +1,18 @@
|
||||
# !!! Don't try to build this Dockerfile directly, run it through bin/build-docker.sh script !!!
|
||||
FROM node:18.18.2-alpine
|
||||
|
||||
# Configure system dependencies
|
||||
RUN apk add --no-cache --virtual .build-dependencies \
|
||||
autoconf \
|
||||
automake \
|
||||
g++ \
|
||||
gcc \
|
||||
libtool \
|
||||
make \
|
||||
nasm \
|
||||
libpng-dev \
|
||||
python3
|
||||
|
||||
# Create app directory
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
@@ -9,25 +21,21 @@ COPY . .
|
||||
|
||||
COPY server-package.json package.json
|
||||
|
||||
# Copy TypeScript build artifacts into the original directory structure.
|
||||
RUN ls
|
||||
RUN cp -R build/src/* src/.
|
||||
RUN rm -r build
|
||||
|
||||
# Install app dependencies
|
||||
RUN set -x \
|
||||
&& apk add --no-cache --virtual .build-dependencies \
|
||||
autoconf \
|
||||
automake \
|
||||
g++ \
|
||||
gcc \
|
||||
libtool \
|
||||
make \
|
||||
nasm \
|
||||
libpng-dev \
|
||||
python3 \
|
||||
&& npm install \
|
||||
&& apk del .build-dependencies \
|
||||
&& npm run webpack \
|
||||
&& npm prune --omit=dev \
|
||||
&& cp src/public/app/share.js src/public/app-dist/. \
|
||||
&& cp -r src/public/app/doc_notes src/public/app-dist/. \
|
||||
&& rm -rf src/public/app
|
||||
&& rm -rf src/public/app \
|
||||
&& rm src/services/asset_path.ts
|
||||
|
||||
# Some setup tools need to be kept
|
||||
RUN apk add --no-cache su-exec shadow
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Trilium Notes
|
||||
|
||||
[English](https://github.com/zadam/trilium/blob/master/README.md) | [Chinese](https://github.com/zadam/trilium/blob/master/README-ZH_CN.md) | [Russian](https://github.com/zadam/trilium/blob/master/README.ru.md) | [Japanese](https://github.com/zadam/trilium/blob/master/README.ja.md)
|
||||
[English](https://github.com/zadam/trilium/blob/master/README.md) | [Chinese](https://github.com/zadam/trilium/blob/master/README-ZH_CN.md) | [Russian](https://github.com/zadam/trilium/blob/master/README.ru.md) | [Japanese](https://github.com/zadam/trilium/blob/master/README.ja.md) | [Italian](https://github.com/zadam/trilium/blob/master/README.it.md)
|
||||
|
||||
[](https://gitter.im/trilium-notes/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
Trilium Notes 是一个层次化的笔记应用程序,专注于建立大型个人知识库。请参阅[屏幕截图](https://github.com/zadam/trilium/wiki/Screenshot-tour)以快速了解:
|
||||
|
||||
93
README.it.md
Normal file
93
README.it.md
Normal file
@@ -0,0 +1,93 @@
|
||||
# Trilium Notes
|
||||
|
||||
## Trilium è in manutenzione - vedi i dettagli in https://github.com/zadam/trilium/issues/4620
|
||||
|
||||
Le discussioni preliminari sull'organizzazione si stanno svolgendo in [Trilium Next discussions](https://github.com/orgs/TriliumNext/discussions).
|
||||
|
||||
[](https://gitter.im/trilium-notes/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [English](https://github.com/zadam/trilium/blob/master/README.md) | [Chinese](https://github.com/zadam/trilium/blob/master/README-ZH_CN.md) | [Russian](https://github.com/zadam/trilium/blob/master/README.ru.md) | [Japanese](https://github.com/zadam/trilium/blob/master/README.ja.md) | [Italian](https://github.com/zadam/trilium/blob/master/README.it.md)
|
||||
|
||||
|
||||
Trilium Notes è un'applicazione per appunti ad organizzazione gerarchica, studiata per la costruzione di archivi di conoscenza personali di grandi dimensioni.
|
||||
|
||||
Vedi [fotografie](https://github.com/zadam/trilium/wiki/Screenshot-tour) per una panoramica veloce:
|
||||
|
||||
<a href="https://github.com/zadam/trilium/wiki/Screenshot-tour"><img src="https://raw.githubusercontent.com/wiki/zadam/trilium/images/screenshot.png" alt="Trilium Screenshot" width="1000"></a>
|
||||
|
||||
L'Ucraina si sta difendendo dall'aggressione russa, considera [donare all'esercito ucraino o a organizzazioni umanitarie](https://standforukraine.com/).
|
||||
|
||||
<p float="left">
|
||||
<img src="https://upload.wikimedia.org/wikipedia/commons/4/49/Flag_of_Ukraine.svg" alt="drawing" width="400"/>
|
||||
<img src="https://signmyrocket.com//uploads/2b2a523cd0c0e76cdbba95a89a9636b2_1676971281.jpg" alt="Trilium Notes supports Ukraine!" width="570"/>
|
||||
</p>
|
||||
|
||||
## 🎁 Funzionalità
|
||||
|
||||
|
||||
* Gli appunti possono essere organizzati in un albero di profondità arbitraria. Un singolo appunto può essere collocato in più posti nell'albero (vedi [clonazione](https://github.com/zadam/trilium/wiki/Cloning-notes))
|
||||
* Ricco editor visuale (WYSIWYG), con supporto -tra l'altro- per tabelle, immagini ed [espressioni matematiche](https://github.com/zadam/trilium/wiki/Text-notes#math-support) e con [formattazione automatica](https://github.com/zadam/trilium/wiki/Text-notes#autoformat) per markdown
|
||||
* Supporto per la modifica di [appunti con codice sorgente](https://github.com/zadam/trilium/wiki/Code-notes), con evidenziazione della sintassi
|
||||
* [Navigazione veloce](https://github.com/zadam/trilium/wiki/Note-navigation) tra gli appunti, ricerca testuale completa e [fissaggio degli appunti](https://github.com/zadam/trilium/wiki/Note-hoisting)
|
||||
* Supporto integrato ed automatico per le [revisioni degli appunti](https://github.com/zadam/trilium/wiki/Note-revisions)
|
||||
* Gli [attributi](https://github.com/zadam/trilium/wiki/Attributes) degli appunti possono essere utilizzati per l'organizzazione, per l'interrogazione e per lo scripting avanzato (prorgrammazione).
|
||||
* [Sincronizzazione](https://github.com/zadam/trilium/wiki/Synchronization) con un server di sincronizzazione auto-ospitato
|
||||
* c'è un [servizio di terze parti per ospitare server di sincronizzazione](https://trilium.cc/paid-hosting)
|
||||
* [Condivisione](https://github.com/zadam/trilium/wiki/Sharing) (pubblicazione) di appunti sull'internet pubblico
|
||||
* Robusta [crittografia](https://github.com/zadam/trilium/wiki/Protected-notes) configurabile singolarmente per ogni appunto
|
||||
* Disegno di diagrammi con Excalidraw (tipo di appunto "canvas")
|
||||
* [Mappe relazionali](https://github.com/zadam/trilium/wiki/Relation-map) e [mappe di collegamenti](https://github.com/zadam/trilium/wiki/Link-map) per visualizzare gli appunti e le loro relazioni
|
||||
* [Scripting](https://github.com/zadam/trilium/wiki/Scripts) - vedi [Esempi avanzati](https://github.com/zadam/trilium/wiki/Advanced-showcases)
|
||||
* [API REST](https://github.com/zadam/trilium/wiki/ETAPI) per l'automazione
|
||||
* Si adatta bene sia in termini di usabilità che di prestazioni fino ad oltre 100 000 appunti
|
||||
* Interfaccia utente ottimizzata per il [mobile](https://github.com/zadam/trilium/wiki/Mobile-frontend) (smartphone e tablet)
|
||||
* [Tema Notturno](https://github.com/zadam/trilium/wiki/Themes)
|
||||
* Supporto per importazione ed esportazione da e per [Evernote](https://github.com/zadam/trilium/wiki/Evernote-import) e [Markdown import](https://github.com/zadam/trilium/wiki/Markdown)
|
||||
* [Web Clipper](https://github.com/zadam/trilium/wiki/Web-clipper) per il salvataggio facile di contenuti web
|
||||
|
||||
|
||||
Dai un'occhiata a [awesome-trilium](https://github.com/Nriver/awesome-trilium) per temi, script, plugin e altro di terze parti.
|
||||
|
||||
## 🏗 Rilasci
|
||||
|
||||
|
||||
Trilium è fornito come applicazione desktop (Linux e Windows) o come applicazione web ospitata sul tuo server (Linux). La versione desktop per Mac OS è disponibile, ma [non è supportata](https://github.com/zadam/trilium/wiki/FAQ#mac-os-support).
|
||||
|
||||
* Se vuoi usare Trilium sul tuo desktop, scarica il rilascio binario per la tua piattaforma dall'[ultimo rilascio](https://github.com/zadam/trilium/releases/latest), decomprimi l'archivio e avvia l'eseguibile ```trilium```.
|
||||
* Se vuoi installare Trilium su un server, segui [questa pagina](https://github.com/zadam/trilium/wiki/Server-installation).
|
||||
* Per ora solo Chrome e Firefox sono i browser supportati (testati).
|
||||
|
||||
Trilium è anche disponibile su Flatpak:
|
||||
|
||||
[<img width="240" src="https://flathub.org/assets/badges/flathub-badge-en.png">](https://flathub.org/apps/details/com.github.zadam.trilium)
|
||||
|
||||
## 📝 Documentazione
|
||||
|
||||
[Vedi la wiki per una lista completa delle pagine di documentazione.](https://github.com/zadam/trilium/wiki/)
|
||||
|
||||
Puoi anche leggere ["Patterns of personal knowledge base"](https://github.com/zadam/trilium/wiki/Patterns-of-personal-knowledge-base) per avere un'ispirazione su come potresti utilizzare Trilium.
|
||||
|
||||
## 💻 Contribuire
|
||||
|
||||
Usa un ambiente di sviluppo basato su browser
|
||||
|
||||
[](https://gitpod.io/#https://github.com/zadam/trilium)
|
||||
|
||||
O clona localmente ed esegui
|
||||
```
|
||||
npm install
|
||||
npm run start-server
|
||||
```
|
||||
|
||||
## 📢 Riconoscimenti
|
||||
|
||||
* [CKEditor 5](https://github.com/ckeditor/ckeditor5) - miglior editor visuale (WYSIWYG) sul mercato, squadra di sviluppo attenta e reattiva
|
||||
* [FancyTree](https://github.com/mar10/fancytree) - libreria per alberi molto ricca di funzionalità, senza pari. Trilium Notes non sarebbe lo stesso senza di essa.
|
||||
* [CodeMirror](https://github.com/codemirror/CodeMirror) - editor di codice con supporto per un'enorme quantità di linguaggi.
|
||||
* [jsPlumb](https://github.com/jsplumb/jsplumb) - libreria per la connettività visuale senza pari. Utilizzata per [mappe relazionali](https://github.com/zadam/trilium/wiki/Relation-map) e [mappe di collegamenti](https://github.com/zadam/trilium/wiki/Link-map).
|
||||
|
||||
## 🤝 Supporto
|
||||
|
||||
È possibile supportare Trilium attraverso Github Sponsors, [PayPal](https://paypal.me/za4am) o Bitcoin (bitcoin:bc1qv3svjn40v89mnkre5vyvs2xw6y8phaltl385d2).
|
||||
|
||||
## 🔑 Licenza
|
||||
|
||||
Questo programma è software libero: è possibile redistribuirlo e/o modificarlo nei termini della GNU Affero General Public License come pubblicata dalla Free Software Foundation, sia la versione 3 della Licenza, o (a propria scelta) qualsiasi versione successiva.
|
||||
@@ -1,6 +1,6 @@
|
||||
# Trilium Notes
|
||||
|
||||
[English](https://github.com/zadam/trilium/blob/master/README.md) | [Chinese](https://github.com/zadam/trilium/blob/master/README-ZH_CN.md) | [Russian](https://github.com/zadam/trilium/blob/master/README.ru.md) | [Japanese](https://github.com/zadam/trilium/blob/master/README.ja.md)
|
||||
[English](https://github.com/zadam/trilium/blob/master/README.md) | [Chinese](https://github.com/zadam/trilium/blob/master/README-ZH_CN.md) | [Russian](https://github.com/zadam/trilium/blob/master/README.ru.md) | [Japanese](https://github.com/zadam/trilium/blob/master/README.ja.md) | [Italian](https://github.com/zadam/trilium/blob/master/README.it.md)
|
||||
|
||||
Trilium Notes は、大規模な個人知識ベースの構築に焦点を当てた、階層型ノートアプリケーションです。概要は[スクリーンショット](https://github.com/zadam/trilium/wiki/Screenshot-tour)をご覧ください:
|
||||
|
||||
|
||||
97
README.md
97
README.md
@@ -1,88 +1,95 @@
|
||||
# Trilium Notes
|
||||
# TriliumNext Notes
|
||||
|
||||
## Trilium is in maintenance mode - see details in https://github.com/zadam/trilium/issues/4620
|
||||
[English](https://github.com/TriliumNext/Notes/blob/master/README.md) | [Chinese](https://github.com/TriliumNext/Notes/blob/master/README-ZH_CN.md) | [Russian](https://github.com/TriliumNext/Notes/blob/master/README.ru.md) | [Japanese](https://github.com/TriliumNext/Notes/blob/master/README.ja.md) | [Italian](https://github.com/TriliumNext/Notes/blob/master/README.it.md)
|
||||
|
||||
Preliminary disccusions on the successor organization are taking place in [Trilium Next discussions](https://github.com/orgs/TriliumNext/discussions).
|
||||
TriliumNext Notes is a hierarchical note taking application with focus on building large personal knowledge bases.
|
||||
|
||||
[](https://gitter.im/trilium-notes/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [English](https://github.com/zadam/trilium/blob/master/README.md) | [Chinese](https://github.com/zadam/trilium/blob/master/README-ZH_CN.md) | [Russian](https://github.com/zadam/trilium/blob/master/README.ru.md) | [Japanese](https://github.com/zadam/trilium/blob/master/README.ja.md)
|
||||
See [screenshots](https://triliumnext.github.io/Docs/Wiki/Screenshot%20tour) for quick overview:
|
||||
|
||||
Trilium Notes is a hierarchical note taking application with focus on building large personal knowledge bases.
|
||||
<a href="https://triliumnext.github.io/Docs/Wiki/Screenshot%20tour"><img src="https://github.com/TriliumNext/Docs/blob/main/Wiki/images/screenshot.png?raw=true" alt="Trilium Screenshot" width="1000"></a>
|
||||
|
||||
See [screenshots](https://github.com/zadam/trilium/wiki/Screenshot-tour) for quick overview:
|
||||
## ⚠️ Why TriliumNext?
|
||||
|
||||
<a href="https://github.com/zadam/trilium/wiki/Screenshot-tour"><img src="https://raw.githubusercontent.com/wiki/zadam/trilium/images/screenshot.png" alt="Trilium Screenshot" width="1000"></a>
|
||||
[The original Trilium project is in maintenance mode](https://github.com/zadam/trilium/issues/4620)
|
||||
|
||||
Ukraine is currently defending itself from Russian aggression, please consider [donating to Ukrainian Army or humanitarian charities](https://standforukraine.com/).
|
||||
## 🗭 Discuss with us
|
||||
|
||||
<p float="left">
|
||||
<img src="https://upload.wikimedia.org/wikipedia/commons/4/49/Flag_of_Ukraine.svg" alt="drawing" width="400"/>
|
||||
<img src="https://signmyrocket.com//uploads/2b2a523cd0c0e76cdbba95a89a9636b2_1676971281.jpg" alt="Trilium Notes supports Ukraine!" width="570"/>
|
||||
</p>
|
||||
Feel free to join our official discussions and community. We are focused on the development on Trilium, and would love to hear what features, suggestions, or issues you may have!
|
||||
|
||||
- [Matrix](https://matrix.to/#/#triliumnext:matrix.org) (For synchronous discussions)
|
||||
- [Github Discussions](https://github.com/TriliumNext/Notes/discussions) (For Asynchronous discussions)
|
||||
- [Wiki](https://github.com/zadam/trilium/wiki) (For common how-to questions and user guides)
|
||||
|
||||
The two rooms linked above are mirrored, so you can use either XMPP or Matrix, from any client you prefer, on pretty much any platform under the sun!
|
||||
|
||||
### Unofficial Communities
|
||||
|
||||
[Trilium Rocks](https://discord.gg/aqdX9mXX4r)
|
||||
|
||||
## 🎁 Features
|
||||
|
||||
* Notes can be arranged into arbitrarily deep tree. Single note can be placed into multiple places in the tree (see [cloning](https://github.com/zadam/trilium/wiki/Cloning-notes))
|
||||
* Rich WYSIWYG note editing including e.g. tables, images and [math](https://github.com/zadam/trilium/wiki/Text-notes#math-support) with markdown [autoformat](https://github.com/zadam/trilium/wiki/Text-notes#autoformat)
|
||||
* Support for editing [notes with source code](https://github.com/zadam/trilium/wiki/Code-notes), including syntax highlighting
|
||||
* Fast and easy [navigation between notes](https://github.com/zadam/trilium/wiki/Note-navigation), full text search and [note hoisting](https://github.com/zadam/trilium/wiki/Note-hoisting)
|
||||
* Seamless [note versioning](https://github.com/zadam/trilium/wiki/Note-revisions)
|
||||
* Note [attributes](https://github.com/zadam/trilium/wiki/Attributes) can be used for note organization, querying and advanced [scripting](https://github.com/zadam/trilium/wiki/Scripts)
|
||||
* [Synchronization](https://github.com/zadam/trilium/wiki/Synchronization) with self-hosted sync server
|
||||
* Notes can be arranged into arbitrarily deep tree. Single note can be placed into multiple places in the tree (see [cloning](https://triliumnext.github.io/Docs/Wiki/Cloning-notes))
|
||||
* Rich WYSIWYG note editing including e.g. tables, images and [math](https://triliumnext.github.io/Docs/Wiki/Text-notes) with markdown [autoformat](https://triliumnext.github.io/Docs/Wiki/Text-notes#autoformat)
|
||||
* Support for editing [notes with source code](https://triliumnext.github.io/Docs/Wiki/Code-notes), including syntax highlighting
|
||||
* Fast and easy [navigation between notes](https://triliumnext.github.io/Docs/Wiki/Note-navigation), full text search and [note hoisting](https://triliumnext.github.io/Docs/Wiki/Note-hoisting)
|
||||
* Seamless [note versioning](https://triliumnext.github.io/Docs/Wiki/Note-revisions)
|
||||
* Note [attributes](https://triliumnext.github.io/Docs/Wiki/Attributes) can be used for note organization, querying and advanced [scripting](https://triliumnext.github.io/Docs/Wiki/Scripts)
|
||||
* [Synchronization](https://triliumnext.github.io/Docs/Wiki/Synchronization) with self-hosted sync server
|
||||
* there's a [3rd party service for hosting synchronisation server](https://trilium.cc/paid-hosting)
|
||||
* [Sharing](https://github.com/zadam/trilium/wiki/Sharing) (publishing) notes to public internet
|
||||
* Strong [note encryption](https://github.com/zadam/trilium/wiki/Protected-notes) with per-note granularity
|
||||
* [Sharing](https://triliumnext.github.io/Docs/Wiki/Sharing) (publishing) notes to public internet
|
||||
* Strong [note encryption](https://triliumnext.github.io/Docs/Wiki/Protected-notes) with per-note granularity
|
||||
* Sketching diagrams with built-in Excalidraw (note type "canvas")
|
||||
* [Relation maps](https://github.com/zadam/trilium/wiki/Relation-map) and [link maps](https://github.com/zadam/trilium/wiki/Link-map) for visualizing notes and their relations
|
||||
* [Scripting](https://github.com/zadam/trilium/wiki/Scripts) - see [Advanced showcases](https://github.com/zadam/trilium/wiki/Advanced-showcases)
|
||||
* [REST API](https://github.com/zadam/trilium/wiki/ETAPI) for automation
|
||||
* [Relation maps](https://triliumnext.github.io/Docs/Wiki/Relation-map) and [link maps](https://triliumnext.github.io/Docs/Wiki/Link-map) for visualizing notes and their relations
|
||||
* [Scripting](https://triliumnext.github.io/Docs/Wiki/Scripts) - see [Advanced showcases](https://triliumnext.github.io/Docs/Wiki/Advanced-showcases)
|
||||
* [REST API](https://triliumnext.github.io/Docs/Wiki/ETAPI) for automation
|
||||
* Scales well in both usability and performance upwards of 100 000 notes
|
||||
* Touch optimized [mobile frontend](https://github.com/zadam/trilium/wiki/Mobile-frontend) for smartphones and tablets
|
||||
* [Night theme](https://github.com/zadam/trilium/wiki/Themes)
|
||||
* [Evernote](https://github.com/zadam/trilium/wiki/Evernote-import) and [Markdown import & export](https://github.com/zadam/trilium/wiki/Markdown)
|
||||
* [Web Clipper](https://github.com/zadam/trilium/wiki/Web-clipper) for easy saving of web content
|
||||
* Touch optimized [mobile frontend](https://triliumnext.github.io/Docs/Wiki/Mobile-frontend) for smartphones and tablets
|
||||
* [Night theme](https://triliumnext.github.io/Docs/Wiki/Themes)
|
||||
* [Evernote](https://triliumnext.github.io/Docs/Wiki/Evernote-import) and [Markdown import & export](https://triliumnext.github.io/Docs/Wiki/Markdown)
|
||||
* [Web Clipper](https://triliumnext.github.io/Docs/Wiki/Web-clipper) for easy saving of web content
|
||||
|
||||
Check out [awesome-trilium](https://github.com/Nriver/awesome-trilium) for 3rd party themes, scripts, plugins and more.
|
||||
✨ Check out the following third-party resources for more TriliumNext related goodies:
|
||||
|
||||
- [awesome-trilium](https://github.com/Nriver/awesome-trilium) for 3rd party themes, scripts, plugins and more.
|
||||
- [TriliumRocks!](https://trilium.rocks/) for tutorials, guides, and much more.
|
||||
|
||||
## 🏗 Builds
|
||||
|
||||
Trilium is provided as either desktop application (Linux and Windows) or web application hosted on your server (Linux). Mac OS desktop build is available, but it is [unsupported](https://github.com/zadam/trilium/wiki/FAQ#mac-os-support).
|
||||
Trilium is provided as either desktop application (Linux and Windows) or web application hosted on your server (Linux). Mac OS desktop build is available, but it is [unsupported](https://triliumnext.github.io/Docs/Wiki/FAQ#mac-os-support).
|
||||
|
||||
* If you want to use Trilium on the desktop, download binary release for your platform from [latest release](https://github.com/zadam/trilium/releases/latest), unzip the package and run ```trilium``` executable.
|
||||
* If you want to install Trilium on server, follow [this page](https://github.com/zadam/trilium/wiki/Server-installation).
|
||||
* Currently only recent Chrome and Firefox are supported (tested) browsers.
|
||||
* If you want to use TriliumNext on the desktop, download binary release for your platform from [latest release](https://github.com/TriliumNext/Notes/releases/latest), unzip the package and run ```trilium``` executable.
|
||||
* If you want to install TriliumNext on your own server, follow [this page](https://triliumnext.github.io/Docs/Wiki/Server-installation).
|
||||
* Currently only recent versions of Chrome and Firefox are supported (tested) browsers.
|
||||
|
||||
Trilium is also provided as a Flatpak:
|
||||
TriliumNext will also provided as a Flatpak:
|
||||
|
||||
[<img width="240" src="https://flathub.org/assets/badges/flathub-badge-en.png">](https://flathub.org/apps/details/com.github.zadam.trilium)
|
||||
<img width="240" src="https://flathub.org/assets/badges/flathub-badge-en.png">
|
||||
|
||||
## 📝 Documentation
|
||||
|
||||
[See wiki for complete list of documentation pages.](https://github.com/zadam/trilium/wiki/)
|
||||
[See wiki for complete list of documentation pages.](https://triliumnext.github.io/Docs)
|
||||
|
||||
You can also read [Patterns of personal knowledge base](https://github.com/zadam/trilium/wiki/Patterns-of-personal-knowledge-base) to get some inspiration on how you might use Trilium.
|
||||
You can also read [Patterns of personal knowledge base](https://triliumnext.github.io/Docs/Wiki/Patterns-of-personal-knowledge-base) to get some inspiration on how you might use Trilium.
|
||||
|
||||
## 💻 Contribute
|
||||
|
||||
Use a browser based dev environment
|
||||
|
||||
[](https://gitpod.io/#https://github.com/zadam/trilium)
|
||||
|
||||
Or clone locally and run
|
||||
Clone locally and run
|
||||
```
|
||||
npm install
|
||||
npm run start-server
|
||||
```
|
||||
|
||||
## 📢 Shoutouts
|
||||
## 👏 Shoutouts
|
||||
|
||||
* [CKEditor 5](https://github.com/ckeditor/ckeditor5) - best WYSIWYG editor on the market, very interactive and listening team
|
||||
* [FancyTree](https://github.com/mar10/fancytree) - very feature rich tree library without real competition. Trilium Notes would not be the same without it.
|
||||
* [CodeMirror](https://github.com/codemirror/CodeMirror) - code editor with support for huge amount of languages
|
||||
* [jsPlumb](https://github.com/jsplumb/jsplumb) - visual connectivity library without competition. Used in [relation maps](https://github.com/zadam/trilium/wiki/Relation-map) and [link maps](https://github.com/zadam/trilium/wiki/Link-map)
|
||||
* [jsPlumb](https://github.com/jsplumb/jsplumb) - visual connectivity library without competition. Used in [relation maps](https://triliumnext.github.io/Docs/Wiki/Relation-map) and [link maps](https://triliumnext.github.io/Docs/Wiki/Link-map)
|
||||
|
||||
## 🤝 Support
|
||||
|
||||
You can support Trilium using GitHub Sponsors, [PayPal](https://paypal.me/za4am) or Bitcoin (bitcoin:bc1qv3svjn40v89mnkre5vyvs2xw6y8phaltl385d2).
|
||||
You can support the original Trilium developer using GitHub Sponsors, [PayPal](https://paypal.me/za4am) or Bitcoin (bitcoin:bc1qv3svjn40v89mnkre5vyvs2xw6y8phaltl385d2).
|
||||
Support for the TriliumNext organization will be possible in the near future.
|
||||
|
||||
## 🔑 License
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Trilium Notes
|
||||
|
||||
[English](https://github.com/zadam/trilium/blob/master/README.md) | [Chinese](https://github.com/zadam/trilium/blob/master/README-ZH_CN.md) | [Russian](https://github.com/zadam/trilium/blob/master/README.ru.md) | [Japanese](https://github.com/zadam/trilium/blob/master/README.ja.md)
|
||||
[English](https://github.com/zadam/trilium/blob/master/README.md) | [Chinese](https://github.com/zadam/trilium/blob/master/README-ZH_CN.md) | [Russian](https://github.com/zadam/trilium/blob/master/README.ru.md) | [Japanese](https://github.com/zadam/trilium/blob/master/README.ja.md) | [Italian](https://github.com/zadam/trilium/blob/master/README.it.md)
|
||||
|
||||
[](https://gitter.im/trilium-notes/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
Trilium Notes – это приложение для заметок с иерархической структурой, ориентированное на создание больших персональных баз знаний. Для быстрого ознакомления посмотрите [скриншот-тур](https://github.com/zadam/trilium/wiki/Screenshot-tour):
|
||||
|
||||
10
_check_ts_progress.sh
Executable file
10
_check_ts_progress.sh
Executable file
@@ -0,0 +1,10 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
cloc HEAD \
|
||||
--git --md \
|
||||
--include-lang=javascript,typescript \
|
||||
--found=filelist.txt \
|
||||
--exclude-dir=public,libraries,views,docs
|
||||
|
||||
grep -R \.js$ filelist.txt
|
||||
rm filelist.txt
|
||||
BIN
bin/better-sqlite3/mac-arm64-better_sqlite3.node
Normal file
BIN
bin/better-sqlite3/mac-arm64-better_sqlite3.node
Normal file
Binary file not shown.
@@ -1,5 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
if ! command -v dpkg-deb &> /dev/null; then
|
||||
echo "Missing command: dpkg-deb"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if dpkg-deb 2>&1 | grep BusyBox &> /dev/null; then
|
||||
echo "The dpkg-deb binary provided by BusyBox is not compatible. The Debian tool needs to be used instead."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Packaging debian x64 distribution..."
|
||||
|
||||
VERSION=`jq -r ".version" package.json`
|
||||
|
||||
@@ -5,6 +5,9 @@ SERIES=${VERSION:0:4}-latest
|
||||
|
||||
cat package.json | grep -v electron > server-package.json
|
||||
|
||||
echo "Compiling typescript..."
|
||||
npx tsc
|
||||
|
||||
sudo docker build -t zadam/trilium:$VERSION --network host -t zadam/trilium:$SERIES .
|
||||
|
||||
if [[ $VERSION != *"beta"* ]]; then
|
||||
|
||||
@@ -1,5 +1,25 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
if ! command -v jq &> /dev/null; then
|
||||
echo "Missing command: jq"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v fakeroot &> /dev/null; then
|
||||
echo "Missing command: fakeroot"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v dpkg-deb &> /dev/null; then
|
||||
echo "Missing command: dpkg-deb"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if dpkg-deb 2>&1 | grep BusyBox &> /dev/null; then
|
||||
echo "The dpkg-deb binary provided by BusyBox is not compatible. The Debian tool needs to be used instead."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SRC_DIR=./dist/trilium-linux-x64-src
|
||||
|
||||
[ "$1" != "DONTCOPY" ] && ./bin/copy-trilium.sh "$SRC_DIR"
|
||||
|
||||
38
bin/build-mac-arm64.sh
Executable file
38
bin/build-mac-arm64.sh
Executable file
@@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
SRC_DIR=./dist/trilium-mac-arm64-src
|
||||
|
||||
if [ "$1" != "DONTCOPY" ]
|
||||
then
|
||||
./bin/copy-trilium.sh $SRC_DIR
|
||||
fi
|
||||
|
||||
echo "Copying required mac arm64 binaries"
|
||||
|
||||
cp -r bin/better-sqlite3/mac-arm64-better_sqlite3.node $SRC_DIR/node_modules/better-sqlite3/build/Release/better_sqlite3.node
|
||||
|
||||
rm -r $SRC_DIR/src/public/app-dist/*.mobile.*
|
||||
|
||||
echo "Packaging mac arm64 electron build"
|
||||
|
||||
./node_modules/.bin/electron-packager $SRC_DIR --asar --out=dist --executable-name=trilium --platform=darwin --arch=arm64 --overwrite --icon=images/app-icons/mac/icon.icns
|
||||
|
||||
BUILD_DIR=./dist/trilium-mac-arm64
|
||||
rm -rf $BUILD_DIR
|
||||
|
||||
# Mac build has by default useless directory level
|
||||
mv "./dist/Trilium Notes-darwin-arm64" $BUILD_DIR
|
||||
|
||||
cp bin/tpl/anonymize-database.sql $BUILD_DIR/
|
||||
|
||||
cp -r dump-db $BUILD_DIR/
|
||||
rm -rf $BUILD_DIR/dump-db/node_modules
|
||||
|
||||
echo "Zipping mac arm64 electron distribution..."
|
||||
|
||||
VERSION=`jq -r ".version" package.json`
|
||||
|
||||
cd dist
|
||||
|
||||
rm trilium-mac-arm64-${VERSION}.zip
|
||||
zip -r9 --symlinks trilium-mac-arm64-${VERSION}.zip trilium-mac-arm64
|
||||
@@ -7,9 +7,9 @@ then
|
||||
./bin/copy-trilium.sh $SRC_DIR
|
||||
fi
|
||||
|
||||
echo "Copying required mac binaries"
|
||||
echo "Copying required mac x64 binaries"
|
||||
|
||||
cp -r bin/better-sqlite3/mac-better_sqlite3.node $SRC_DIR/node_modules/better-sqlite3/build/Release/better_sqlite3.node
|
||||
cp -r bin/better-sqlite3/mac-x64-better_sqlite3.node $SRC_DIR/node_modules/better-sqlite3/build/Release/better_sqlite3.node
|
||||
|
||||
rm -r $SRC_DIR/src/public/app-dist/*.mobile.*
|
||||
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
if ! command -v wine &> /dev/null; then
|
||||
echo "Missing command: wine"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
SRC_DIR=./dist/trilium-windows-x64-src
|
||||
|
||||
if [ "$1" != "DONTCOPY" ]
|
||||
|
||||
29
bin/build.sh
29
bin/build.sh
@@ -1,5 +1,30 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
if ! command -v jq &> /dev/null; then
|
||||
echo "Missing command: jq"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v fakeroot &> /dev/null; then
|
||||
echo "Missing command: fakeroot"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v dpkg-deb &> /dev/null; then
|
||||
echo "Missing command: dpkg-deb"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if dpkg-deb 2>&1 | grep BusyBox &> /dev/null; then
|
||||
echo "The dpkg-deb binary provided by BusyBox is not compatible. The Debian tool needs to be used instead."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v wine &> /dev/null; then
|
||||
echo "Missing command: wine"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Deleting existing builds"
|
||||
|
||||
rm -rf dist/*
|
||||
@@ -13,11 +38,15 @@ cp -r $SRC_DIR ./dist/trilium-linux-x64-src
|
||||
cp -r $SRC_DIR ./dist/trilium-linux-x64-server
|
||||
cp -r $SRC_DIR ./dist/trilium-windows-x64-src
|
||||
cp -r $SRC_DIR ./dist/trilium-mac-x64-src
|
||||
cp -r $SRC_DIR ./dist/trilium-mac-arm64-src
|
||||
|
||||
set -e
|
||||
bin/build-win-x64.sh DONTCOPY
|
||||
|
||||
bin/build-mac-x64.sh DONTCOPY
|
||||
|
||||
bin/build-mac-arm64.sh DONTCOPY
|
||||
|
||||
bin/build-linux-x64.sh DONTCOPY
|
||||
|
||||
bin/build-server.sh DONTCOPY
|
||||
|
||||
78
bin/copy-dist.ts
Normal file
78
bin/copy-dist.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import fs from "fs-extra";
|
||||
import path from "path";
|
||||
|
||||
const DEST_DIR = "./dist";
|
||||
const DEST_DIR_SRC = path.join(DEST_DIR, "src");
|
||||
const DEST_DIR_NODE_MODULES = path.join(DEST_DIR, "node_modules");
|
||||
|
||||
async function copyNodeModuleFileOrFolder(source: string) {
|
||||
const adjustedSource = source.substring(13);
|
||||
const destination = path.join(DEST_DIR_NODE_MODULES, adjustedSource);
|
||||
|
||||
console.log(`Copying ${source} to ${destination}`);
|
||||
await fs.ensureDir(path.dirname(destination));
|
||||
await fs.copy(source, destination);
|
||||
}
|
||||
|
||||
const copy = async () => {
|
||||
for (const srcFile of fs.readdirSync("build")) {
|
||||
const destFile = path.join(DEST_DIR, path.basename(srcFile));
|
||||
console.log(`Copying source ${srcFile} -> ${destFile}.`);
|
||||
fs.copySync(path.join("build", srcFile), destFile, { recursive: true });
|
||||
}
|
||||
|
||||
const filesToCopy = ["config-sample.ini"];
|
||||
for (const file of filesToCopy) {
|
||||
console.log(`Copying ${file}`);
|
||||
await fs.copy(file, path.join(DEST_DIR, file));
|
||||
}
|
||||
|
||||
const dirsToCopy = ["images", "libraries", "db"];
|
||||
for (const dir of dirsToCopy) {
|
||||
console.log(`Copying ${dir}`);
|
||||
await fs.copy(dir, path.join(DEST_DIR, dir));
|
||||
}
|
||||
|
||||
const srcDirsToCopy = ["./src/public", "./src/views", "./build"];
|
||||
for (const dir of srcDirsToCopy) {
|
||||
console.log(`Copying ${dir}`);
|
||||
await fs.copy(dir, path.join(DEST_DIR_SRC, path.basename(dir)));
|
||||
}
|
||||
|
||||
const nodeModulesFile = [
|
||||
"node_modules/react/umd/react.production.min.js",
|
||||
"node_modules/react/umd/react.development.js",
|
||||
"node_modules/react-dom/umd/react-dom.production.min.js",
|
||||
"node_modules/react-dom/umd/react-dom.development.js",
|
||||
"node_modules/katex/dist/katex.min.js",
|
||||
"node_modules/katex/dist/contrib/mhchem.min.js",
|
||||
"node_modules/katex/dist/contrib/auto-render.min.js",
|
||||
];
|
||||
|
||||
for (const file of nodeModulesFile) {
|
||||
await copyNodeModuleFileOrFolder(file);
|
||||
}
|
||||
|
||||
const nodeModulesFolder = [
|
||||
"node_modules/@excalidraw/excalidraw/dist/",
|
||||
"node_modules/katex/dist/",
|
||||
"node_modules/dayjs/",
|
||||
"node_modules/force-graph/dist/",
|
||||
"node_modules/boxicons/css/",
|
||||
"node_modules/boxicons/fonts/",
|
||||
"node_modules/mermaid/dist/",
|
||||
"node_modules/jquery/dist/",
|
||||
"node_modules/jquery-hotkeys/",
|
||||
"node_modules/print-this/",
|
||||
"node_modules/split.js/dist/",
|
||||
"node_modules/panzoom/dist/",
|
||||
];
|
||||
|
||||
for (const folder of nodeModulesFolder) {
|
||||
await copyNodeModuleFileOrFolder(folder);
|
||||
}
|
||||
};
|
||||
|
||||
copy()
|
||||
.then(() => console.log("Copying complete!"))
|
||||
.catch((err) => console.error("Error during copy:", err));
|
||||
@@ -1,5 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
shopt -s globstar
|
||||
|
||||
if [[ $# -eq 0 ]] ; then
|
||||
echo "Missing argument of target directory"
|
||||
exit 1
|
||||
@@ -9,25 +11,36 @@ if ! [[ $(which npm) ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
n exec 18.18.2 npm run webpack || npm run webpack
|
||||
# Trigger the TypeScript build
|
||||
echo TypeScript build start
|
||||
npx tsc
|
||||
echo TypeScript build finished
|
||||
|
||||
# Copy the TypeScript artifacts
|
||||
DIR="$1"
|
||||
|
||||
rm -rf "$DIR"
|
||||
mkdir -pv "$DIR"
|
||||
|
||||
echo Webpack start
|
||||
npm run webpack
|
||||
echo Webpack finish
|
||||
|
||||
echo "Copying Trilium to build directory $DIR"
|
||||
|
||||
for d in 'images' 'libraries' 'src' 'db'; do
|
||||
cp -r "$d" "$DIR"/
|
||||
done
|
||||
for f in 'package.json' 'package-lock.json' 'README.md' 'LICENSE' 'config-sample.ini' 'electron.js'; do
|
||||
|
||||
for f in 'package.json' 'package-lock.json' 'README.md' 'LICENSE' 'config-sample.ini'; do
|
||||
cp "$f" "$DIR"/
|
||||
done
|
||||
cp webpack-* "$DIR"/ # here warning because there is no 'webpack-*', but webpack.config.js only
|
||||
|
||||
script_dir=$(realpath $(dirname $0))
|
||||
cp -Rv "$script_dir/../build/src" "$DIR"
|
||||
cp "$script_dir/../build/electron.js" "$DIR"
|
||||
|
||||
# run in subshell (so we return to original dir)
|
||||
(cd $DIR && n exec 18.18.2 npm install --only=prod)
|
||||
(cd $DIR && npm install --only=prod)
|
||||
|
||||
if [[ -d "$DIR"/node_modules ]]; then
|
||||
# cleanup of useless files in dependencies
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const anonymizationService = require('../src/services/anonymization.js');
|
||||
const anonymizationService = require('../src/services/anonymization');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
|
||||
@@ -7,6 +7,11 @@ if [[ $# -eq 0 ]] ; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v jq &> /dev/null; then
|
||||
echo "Missing command: jq"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
VERSION=$1
|
||||
|
||||
if ! [[ ${VERSION} =~ ^[0-9]{1,2}\.[0-9]{1,2}\.[0-9]{1,2}(-.+)?$ ]] ;
|
||||
@@ -22,13 +27,14 @@ fi
|
||||
|
||||
echo "Releasing Trilium $VERSION"
|
||||
|
||||
jq '.version = "'$VERSION'"' package.json|sponge package.json
|
||||
jq '.version = "'$VERSION'"' package.json > package.json.tmp
|
||||
mv package.json.tmp package.json
|
||||
|
||||
git add package.json
|
||||
|
||||
echo 'module.exports = { buildDate:"'`date --iso-8601=seconds`'", buildRevision: "'`git log -1 --format="%H"`'" };' > src/services/build.js
|
||||
echo 'export = { buildDate:"'`date --iso-8601=seconds`'", buildRevision: "'`git log -1 --format="%H"`'" };' > src/services/build.ts
|
||||
|
||||
git add src/services/build.js
|
||||
git add src/services/build.ts
|
||||
|
||||
TAG=v$VERSION
|
||||
|
||||
@@ -48,6 +54,7 @@ LINUX_X64_BUILD=trilium-linux-x64-$VERSION.tar.xz
|
||||
DEBIAN_X64_BUILD=trilium_${VERSION}_amd64.deb
|
||||
WINDOWS_X64_BUILD=trilium-windows-x64-$VERSION.zip
|
||||
MAC_X64_BUILD=trilium-mac-x64-$VERSION.zip
|
||||
MAC_ARM64_BUILD=trilium-mac-arm64-$VERSION.zip
|
||||
SERVER_BUILD=trilium-linux-x64-server-$VERSION.tar.xz
|
||||
|
||||
echo "Creating release in GitHub"
|
||||
@@ -68,4 +75,5 @@ gh release create "$TAG" \
|
||||
"dist/$LINUX_X64_BUILD" \
|
||||
"dist/$WINDOWS_X64_BUILD" \
|
||||
"dist/$MAC_X64_BUILD" \
|
||||
"dist/$MAC_ARM64_BUILD" \
|
||||
"dist/$SERVER_BUILD"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
module.exports = () => {
|
||||
const sql = require('../../src/services/sql.js');
|
||||
const utils = require('../../src/services/utils.js');
|
||||
const sql = require('../../src/services/sql');
|
||||
const utils = require('../../src/services/utils');
|
||||
|
||||
const existingBlobIds = new Set();
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
module.exports = () => {
|
||||
const beccaLoader = require('../../src/becca/becca_loader.js');
|
||||
const becca = require('../../src/becca/becca.js');
|
||||
const cls = require('../../src/services/cls.js');
|
||||
const log = require('../../src/services/log.js');
|
||||
const sql = require('../../src/services/sql.js');
|
||||
const beccaLoader = require('../../src/becca/becca_loader');
|
||||
const becca = require('../../src/becca/becca');
|
||||
const cls = require('../../src/services/cls');
|
||||
const log = require('../../src/services/log');
|
||||
const sql = require('../../src/services/sql');
|
||||
|
||||
cls.init(() => {
|
||||
// emergency disabling of image compression since it appears to make problems in migration to 0.61
|
||||
@@ -13,7 +13,7 @@ module.exports = () => {
|
||||
|
||||
for (const note of Object.values(becca.notes)) {
|
||||
try {
|
||||
const attachment = note.convertToParentAttachment({autoConversion: true});
|
||||
const attachment = note.convertToParentAttachment({ autoConversion: true });
|
||||
|
||||
if (attachment) {
|
||||
log.info(`Auto-converted note '${note.noteId}' into attachment '${attachment.attachmentId}'.`);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const http = require("http");
|
||||
const ini = require("ini");
|
||||
const fs = require("fs");
|
||||
const dataDir = require('./src/services/data_dir.js');
|
||||
const dataDir = require('./src/services/data_dir');
|
||||
const config = ini.parse(fs.readFileSync(dataDir.CONFIG_INI_PATH, 'utf-8'));
|
||||
|
||||
if (config.Network.https) {
|
||||
@@ -10,8 +10,8 @@ if (config.Network.https) {
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
const port = require('./src/services/port.js');
|
||||
const host = require('./src/services/host.js');
|
||||
const port = require('./src/services/port');
|
||||
const host = require('./src/services/host');
|
||||
|
||||
const options = { timeout: 2000 };
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const crypto = require("crypto");
|
||||
const sql = require('./sql.js');
|
||||
const sql = require('./sql');
|
||||
const decryptService = require('./decrypt.js');
|
||||
|
||||
function getDataKey(password) {
|
||||
|
||||
@@ -74,7 +74,7 @@ function dumpDocument(documentPath, targetPath, options) {
|
||||
return;
|
||||
}
|
||||
|
||||
let {content} = sql.getRow("SELECT content FROM blobs WHERE blobId = ?", [noteRow.blobId]);
|
||||
let { content } = sql.getRow("SELECT content FROM blobs WHERE blobId = ?", [noteRow.blobId]);
|
||||
|
||||
if (content !== null && noteRow.isProtected && dataKey) {
|
||||
content = decryptService.decrypt(dataKey, content);
|
||||
@@ -108,7 +108,7 @@ function dumpDocument(documentPath, targetPath, options) {
|
||||
}
|
||||
|
||||
try {
|
||||
fs.mkdirSync(childTargetPath, {recursive: true});
|
||||
fs.mkdirSync(childTargetPath, { recursive: true });
|
||||
}
|
||||
catch (e) {
|
||||
console.error(`DUMPERROR: Creating directory ${childTargetPath} failed with error '${e.message}'`);
|
||||
@@ -157,7 +157,7 @@ function validatePaths(documentPath, targetPath) {
|
||||
}
|
||||
|
||||
if (!fs.existsSync(targetPath)) {
|
||||
const ret = fs.mkdirSync(targetPath, {recursive: true});
|
||||
const ret = fs.mkdirSync(targetPath, { recursive: true });
|
||||
|
||||
if (!ret) {
|
||||
console.error(`Target path '${targetPath}' could not be created. Run with --help to see usage.`);
|
||||
|
||||
62
electron.js
62
electron.js
@@ -1,62 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
const {app, globalShortcut, BrowserWindow} = require('electron');
|
||||
const sqlInit = require('./src/services/sql_init.js');
|
||||
const appIconService = require('./src/services/app_icon.js');
|
||||
const windowService = require('./src/services/window.js');
|
||||
const tray = require('./src/services/tray.js');
|
||||
|
||||
// Adds debug features like hotkeys for triggering dev tools and reload
|
||||
require('electron-debug')();
|
||||
|
||||
appIconService.installLocalAppIcon();
|
||||
|
||||
require('electron-dl')({ saveAs: true });
|
||||
|
||||
// needed for excalidraw export https://github.com/zadam/trilium/issues/4271
|
||||
app.commandLine.appendSwitch("enable-experimental-web-platform-features");
|
||||
|
||||
// Quit when all windows are closed, except on macOS. There, it's common
|
||||
// for applications and their menu bar to stay active until the user quits
|
||||
// explicitly with Cmd + Q.
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit()
|
||||
}
|
||||
});
|
||||
|
||||
app.on('ready', async () => {
|
||||
// app.setAppUserModelId('com.github.zadam.trilium');
|
||||
|
||||
// if db is not initialized -> setup process
|
||||
// if db is initialized, then we need to wait until the migration process is finished
|
||||
if (sqlInit.isDbInitialized()) {
|
||||
await sqlInit.dbReady;
|
||||
|
||||
await windowService.createMainWindow(app);
|
||||
|
||||
if (process.platform === 'darwin') {
|
||||
app.on('activate', async () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
await windowService.createMainWindow(app);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
tray.createTray();
|
||||
}
|
||||
else {
|
||||
await windowService.createSetupWindow();
|
||||
}
|
||||
|
||||
await windowService.registerGlobalShortcuts();
|
||||
});
|
||||
|
||||
app.on('will-quit', () => {
|
||||
globalShortcut.unregisterAll();
|
||||
});
|
||||
|
||||
// this is to disable electron warning spam in the dev console (local development only)
|
||||
process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = 'true';
|
||||
|
||||
require('./src/www.js');
|
||||
63
electron.ts
Normal file
63
electron.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
"use strict";
|
||||
|
||||
import electron = require("electron");
|
||||
import sqlInit = require("./src/services/sql_init");
|
||||
import appIconService = require("./src/services/app_icon");
|
||||
import windowService = require("./src/services/window");
|
||||
import tray = require("./src/services/tray");
|
||||
|
||||
// Adds debug features like hotkeys for triggering dev tools and reload
|
||||
require("electron-debug")();
|
||||
|
||||
appIconService.installLocalAppIcon();
|
||||
|
||||
require("electron-dl")({ saveAs: true });
|
||||
|
||||
// needed for excalidraw export https://github.com/zadam/trilium/issues/4271
|
||||
electron.app.commandLine.appendSwitch(
|
||||
"enable-experimental-web-platform-features"
|
||||
);
|
||||
|
||||
// Quit when all windows are closed, except on macOS. There, it's common
|
||||
// for applications and their menu bar to stay active until the user quits
|
||||
// explicitly with Cmd + Q.
|
||||
electron.app.on("window-all-closed", () => {
|
||||
if (process.platform !== "darwin") {
|
||||
electron.app.quit();
|
||||
}
|
||||
});
|
||||
|
||||
electron.app.on("ready", async () => {
|
||||
// electron.app.setAppUserModelId('com.github.zadam.trilium');
|
||||
|
||||
// if db is not initialized -> setup process
|
||||
// if db is initialized, then we need to wait until the migration process is finished
|
||||
if (sqlInit.isDbInitialized()) {
|
||||
await sqlInit.dbReady;
|
||||
|
||||
await windowService.createMainWindow(electron.app);
|
||||
|
||||
if (process.platform === "darwin") {
|
||||
electron.app.on("activate", async () => {
|
||||
if (electron.BrowserWindow.getAllWindows().length === 0) {
|
||||
await windowService.createMainWindow(electron.app);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
tray.createTray();
|
||||
} else {
|
||||
await windowService.createSetupWindow();
|
||||
}
|
||||
|
||||
await windowService.registerGlobalShortcuts();
|
||||
});
|
||||
|
||||
electron.app.on("will-quit", () => {
|
||||
electron.globalShortcut.unregisterAll();
|
||||
});
|
||||
|
||||
// this is to disable electron warning spam in the dev console (local development only)
|
||||
process.env["ELECTRON_DISABLE_SECURITY_WARNINGS"] = "true";
|
||||
|
||||
require("./src/www.js");
|
||||
2
libraries/ckeditor/ckeditor.js
vendored
2
libraries/ckeditor/ckeditor.js
vendored
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -2,12 +2,11 @@
|
||||
"restartable": "rs",
|
||||
"ignore": [".git", "node_modules/**/node_modules", "src/public/"],
|
||||
"verbose": false,
|
||||
"execMap": {
|
||||
"js": "node --harmony"
|
||||
},
|
||||
"exec": "ts-node",
|
||||
"watch": ["src/"],
|
||||
"signal": "SIGTERM",
|
||||
"env": {
|
||||
"NODE_ENV": "development"
|
||||
},
|
||||
"ext": "js,json"
|
||||
"ext": "ts,js,json"
|
||||
}
|
||||
|
||||
8913
package-lock.json
generated
8913
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
114
package.json
114
package.json
@@ -2,7 +2,7 @@
|
||||
"name": "trilium",
|
||||
"productName": "Trilium Notes",
|
||||
"description": "Trilium Notes",
|
||||
"version": "0.63.3",
|
||||
"version": "0.90.0-beta",
|
||||
"license": "AGPL-3.0-only",
|
||||
"main": "electron.js",
|
||||
"bin": {
|
||||
@@ -13,13 +13,13 @@
|
||||
"url": "https://github.com/zadam/trilium.git"
|
||||
},
|
||||
"scripts": {
|
||||
"start-server": "cross-env TRILIUM_SAFE_MODE=1 TRILIUM_DATA_DIR=./data TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 nodemon src/www.js",
|
||||
"start-server-no-dir": "cross-env TRILIUM_SAFE_MODE=1 TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 nodemon src/www.js",
|
||||
"qstart-server": "npm run qswitch-server && TRILIUM_SAFE_MODE=1 TRILIUM_DATA_DIR=./data TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 nodemon src/www.js",
|
||||
"start-electron": "cross-env TRILIUM_SAFE_MODE=1 TRILIUM_DATA_DIR=./data TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 TRILIUM_ENV=dev electron --inspect=5858 .",
|
||||
"start-server": "cross-env TRILIUM_SAFE_MODE=1 TRILIUM_DATA_DIR=./data TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 nodemon src/www.ts",
|
||||
"start-server-no-dir": "cross-env TRILIUM_SAFE_MODE=1 TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 nodemon src/www.ts",
|
||||
"qstart-server": "npm run qswitch-server && TRILIUM_SAFE_MODE=1 TRILIUM_DATA_DIR=./data TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 nodemon src/www.ts",
|
||||
"start-electron": "rimraf ./dist && tsc && ts-node ./bin/copy-dist.ts && cross-env TRILIUM_SAFE_MODE=1 TRILIUM_DATA_DIR=./data TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 TRILIUM_ENV=dev electron ./dist/electron.js --inspect=5858 .",
|
||||
"start-electron-no-dir": "cross-env TRILIUM_SAFE_MODE=1 TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 electron --inspect=5858 .",
|
||||
"qstart-electron": "npm run qswitch-electron && TRILIUM_SAFE_MODE=1 TRILIUM_DATA_DIR=./data TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 TRILIUM_ENV=dev electron --inspect=5858 .",
|
||||
"start-test-server": "npm run qswitch-server; rm -rf ./data-test; cross-env TRILIUM_SAFE_MODE=1 TRILIUM_DATA_DIR=./data-test TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 TRILIUM_ENV=dev TRILIUM_PORT=9999 node src/www.js",
|
||||
"start-test-server": "npm run qswitch-server; rm -rf ./data-test; cross-env TRILIUM_SAFE_MODE=1 TRILIUM_DATA_DIR=./data-test TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 TRILIUM_ENV=dev TRILIUM_PORT=9999 ts-node src/www.ts",
|
||||
"switch-server": "rm -rf ./node_modules/better-sqlite3 && npm install",
|
||||
"switch-electron": "./node_modules/.bin/electron-rebuild",
|
||||
"qswitch-server": "rm -rf ./node_modules/better-sqlite3/bin ; mkdir -p ./node_modules/better-sqlite3/build ; cp ./bin/better-sqlite3/linux-server-better_sqlite3.node ./node_modules/better-sqlite3/build/better_sqlite3.node",
|
||||
@@ -27,24 +27,22 @@
|
||||
"build-backend-docs": "rm -rf ./docs/backend_api && ./node_modules/.bin/jsdoc -c jsdoc-conf.json -d ./docs/backend_api src/becca/entities/*.js src/services/backend_script_api.js src/services/sql.js",
|
||||
"build-frontend-docs": "rm -rf ./docs/frontend_api && ./node_modules/.bin/jsdoc -c jsdoc-conf.json -d ./docs/frontend_api src/public/app/entities/*.js src/public/app/services/frontend_script_api.js src/public/app/widgets/basic_widget.js src/public/app/widgets/note_context_aware_widget.js src/public/app/widgets/right_panel_widget.js",
|
||||
"build-docs": "npm run build-backend-docs && npm run build-frontend-docs",
|
||||
"webpack": "webpack -c webpack.config.js",
|
||||
"webpack": "webpack -c webpack.config.ts",
|
||||
"test-jasmine": "TRILIUM_DATA_DIR=~/trilium/data-test jasmine",
|
||||
"test-es6": "node -r esm spec-es6/attribute_parser.spec.js ",
|
||||
"test": "npm run test-jasmine && npm run test-es6",
|
||||
"postinstall": "rimraf ./node_modules/canvas",
|
||||
"lint": "eslint . --cache",
|
||||
"prepare": "husky install || echo 'Husky install failed, expected on flatpak build'"
|
||||
"postinstall": "rimraf ./node_modules/canvas"
|
||||
},
|
||||
"dependencies": {
|
||||
"@braintree/sanitize-url": "6.0.4",
|
||||
"@electron/remote": "2.1.0",
|
||||
"@excalidraw/excalidraw": "0.16.1",
|
||||
"archiver": "6.0.1",
|
||||
"async-mutex": "0.4.0",
|
||||
"axios": "1.6.2",
|
||||
"@electron/remote": "2.1.2",
|
||||
"@excalidraw/excalidraw": "0.17.3",
|
||||
"archiver": "7.0.0",
|
||||
"async-mutex": "0.4.1",
|
||||
"axios": "1.6.7",
|
||||
"better-sqlite3": "8.4.0",
|
||||
"boxicons": "2.1.4",
|
||||
"chokidar": "3.5.3",
|
||||
"chokidar": "3.6.0",
|
||||
"cls-hooked": "4.2.2",
|
||||
"compression": "1.7.4",
|
||||
"cookie-parser": "1.4.6",
|
||||
@@ -54,35 +52,35 @@
|
||||
"debounce": "1.2.1",
|
||||
"ejs": "3.1.9",
|
||||
"electron-debug": "3.2.0",
|
||||
"electron-dl": "3.5.1",
|
||||
"electron-dl": "3.5.2",
|
||||
"electron-window-state": "5.0.3",
|
||||
"escape-html": "1.0.3",
|
||||
"express": "4.18.2",
|
||||
"express": "4.18.3",
|
||||
"express-partial-content": "1.0.2",
|
||||
"express-rate-limit": "7.1.4",
|
||||
"express-session": "1.17.3",
|
||||
"force-graph": "1.43.4",
|
||||
"fs-extra": "11.1.1",
|
||||
"express-rate-limit": "7.2.0",
|
||||
"express-session": "1.18.0",
|
||||
"force-graph": "1.43.5",
|
||||
"fs-extra": "11.2.0",
|
||||
"helmet": "7.1.0",
|
||||
"html": "1.0.0",
|
||||
"html2plaintext": "2.1.4",
|
||||
"http-proxy-agent": "7.0.0",
|
||||
"https-proxy-agent": "7.0.2",
|
||||
"http-proxy-agent": "7.0.2",
|
||||
"https-proxy-agent": "7.0.4",
|
||||
"image-type": "4.1.0",
|
||||
"ini": "3.0.1",
|
||||
"is-animated": "2.0.2",
|
||||
"is-svg": "4.3.2",
|
||||
"jimp": "0.22.10",
|
||||
"jimp": "0.22.12",
|
||||
"joplin-turndown-plugin-gfm": "1.0.12",
|
||||
"jquery": "3.7.1",
|
||||
"jquery-hotkeys": "0.2.2",
|
||||
"jsdom": "22.1.0",
|
||||
"jsdom": "24.0.0",
|
||||
"katex": "0.16.9",
|
||||
"marked": "9.1.6",
|
||||
"mermaid": "10.6.1",
|
||||
"marked": "12.0.0",
|
||||
"mermaid": "10.9.0",
|
||||
"mime-types": "2.1.35",
|
||||
"multer": "1.4.5-lts.1",
|
||||
"node-abi": "3.51.0",
|
||||
"node-abi": "3.56.0",
|
||||
"normalize-strings": "1.1.1",
|
||||
"open": "8.4.1",
|
||||
"panzoom": "9.4.3",
|
||||
@@ -94,51 +92,67 @@
|
||||
"rimraf": "5.0.5",
|
||||
"safe-compare": "1.1.4",
|
||||
"sanitize-filename": "1.6.3",
|
||||
"sanitize-html": "2.11.0",
|
||||
"sanitize-html": "2.12.1",
|
||||
"sax": "1.3.0",
|
||||
"semver": "7.5.4",
|
||||
"semver": "7.6.0",
|
||||
"serve-favicon": "2.5.0",
|
||||
"session-file-store": "1.5.0",
|
||||
"split.js": "1.6.5",
|
||||
"stream-throttle": "0.1.3",
|
||||
"striptags": "3.2.0",
|
||||
"tmp": "0.2.1",
|
||||
"tmp": "0.2.3",
|
||||
"tree-kill": "1.2.2",
|
||||
"turndown": "7.1.2",
|
||||
"unescape": "1.0.1",
|
||||
"ws": "8.14.2",
|
||||
"ws": "8.16.0",
|
||||
"xml2js": "0.6.2",
|
||||
"yauzl": "2.10.0"
|
||||
"yauzl": "3.1.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/archiver": "^6.0.2",
|
||||
"@types/better-sqlite3": "^7.6.9",
|
||||
"@types/cls-hooked": "^4.3.8",
|
||||
"@types/compression": "^1.7.5",
|
||||
"@types/cookie-parser": "^1.4.7",
|
||||
"@types/csurf": "^1.11.5",
|
||||
"@types/ejs": "^3.1.5",
|
||||
"@types/escape-html": "^1.0.4",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/express-session": "^1.18.0",
|
||||
"@types/html": "^1.0.4",
|
||||
"@types/ini": "^4.1.0",
|
||||
"@types/jsdom": "^21.1.6",
|
||||
"@types/mime-types": "^2.1.4",
|
||||
"@types/multer": "^1.4.11",
|
||||
"@types/node": "^20.11.19",
|
||||
"@types/safe-compare": "^1.1.2",
|
||||
"@types/sanitize-html": "^2.11.0",
|
||||
"@types/sax": "^1.2.7",
|
||||
"@types/semver": "^7.5.8",
|
||||
"@types/serve-favicon": "^2.5.7",
|
||||
"@types/stream-throttle": "^0.1.4",
|
||||
"@types/tmp": "^0.2.6",
|
||||
"@types/turndown": "^5.0.4",
|
||||
"@types/ws": "^8.5.10",
|
||||
"@types/xml2js": "^0.4.14",
|
||||
"cross-env": "7.0.3",
|
||||
"electron": "25.9.8",
|
||||
"electron-builder": "24.6.4",
|
||||
"electron-builder": "24.13.3",
|
||||
"electron-packager": "17.1.2",
|
||||
"electron-rebuild": "3.2.9",
|
||||
"eslint": "8.54.0",
|
||||
"eslint-config-airbnb-base": "15.0.0",
|
||||
"eslint-config-prettier": "9.0.0",
|
||||
"eslint-plugin-import": "2.29.0",
|
||||
"eslint-plugin-jsonc": "2.10.0",
|
||||
"eslint-plugin-prettier": "5.0.1",
|
||||
"esm": "3.2.25",
|
||||
"husky": "8.0.3",
|
||||
"jasmine": "5.1.0",
|
||||
"jsdoc": "4.0.2",
|
||||
"jsonc-eslint-parser": "2.4.0",
|
||||
"lint-staged": "15.1.0",
|
||||
"lorem-ipsum": "2.0.8",
|
||||
"nodemon": "3.0.1",
|
||||
"prettier": "3.1.0",
|
||||
"nodemon": "3.1.0",
|
||||
"rcedit": "4.0.1",
|
||||
"webpack": "5.89.0",
|
||||
"ts-node": "^10.9.2",
|
||||
"tslib": "^2.6.2",
|
||||
"typescript": "^5.3.3",
|
||||
"webpack": "5.90.3",
|
||||
"webpack-cli": "5.1.4"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"electron-installer-debian": "3.2.0"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.js": "eslint --cache --fix"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const lex = require('../../src/services/search/services/lex.js');
|
||||
const lex = require('../../src/services/search/services/lex');
|
||||
|
||||
describe("Lexer fulltext", () => {
|
||||
it("simple lexing", () => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const handleParens = require('../../src/services/search/services/handle_parens.js');
|
||||
const handleParens = require('../../src/services/search/services/handle_parens');
|
||||
|
||||
describe("Parens handler", () => {
|
||||
it("handles parens", () => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
const SearchContext = require('../../src/services/search/search_context.js');
|
||||
const parse = require('../../src/services/search/services/parse.js');
|
||||
const SearchContext = require('../../src/services/search/search_context');
|
||||
const parse = require('../../src/services/search/services/parse');
|
||||
|
||||
function tokens(toks, cur = 0) {
|
||||
return toks.map(arg => {
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
const searchService = require('../../src/services/search/services/search.js');
|
||||
const searchService = require('../../src/services/search/services/search');
|
||||
const BNote = require('../../src/becca/entities/bnote.js');
|
||||
const BBranch = require('../../src/becca/entities/bbranch.js');
|
||||
const SearchContext = require('../../src/services/search/search_context.js');
|
||||
const dateUtils = require('../../src/services/date_utils.js');
|
||||
const SearchContext = require('../../src/services/search/search_context');
|
||||
const dateUtils = require('../../src/services/date_utils');
|
||||
const becca = require('../../src/becca/becca.js');
|
||||
const {NoteBuilder, findNoteByTitle, note} = require('./becca_mocking.js');
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const {note} = require('./becca_mocking.js');
|
||||
const ValueExtractor = require('../../src/services/search/value_extractor.js');
|
||||
const ValueExtractor = require('../../src/services/search/value_extractor');
|
||||
const becca = require('../../src/becca/becca.js');
|
||||
const SearchContext = require('../../src/services/search/search_context.js');
|
||||
const SearchContext = require('../../src/services/search/search_context');
|
||||
|
||||
const dsc = new SearchContext();
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const anonymizationService = require('./services/anonymization.js');
|
||||
const sqlInit = require('./services/sql_init.js');
|
||||
require('./becca/entity_constructor.js');
|
||||
import anonymizationService = require('./services/anonymization');
|
||||
import sqlInit = require('./services/sql_init');
|
||||
require('./becca/entity_constructor');
|
||||
|
||||
sqlInit.dbReady.then(async () => {
|
||||
try {
|
||||
@@ -16,7 +16,7 @@ sqlInit.dbReady.then(async () => {
|
||||
console.log("Anonymization failed.");
|
||||
}
|
||||
}
|
||||
catch (e) {
|
||||
catch (e: any) {
|
||||
console.error(e.message, e.stack);
|
||||
}
|
||||
|
||||
60
src/app.js
60
src/app.js
@@ -1,60 +0,0 @@
|
||||
const express = require('express');
|
||||
const path = require('path');
|
||||
const favicon = require('serve-favicon');
|
||||
const cookieParser = require('cookie-parser');
|
||||
const helmet = require('helmet');
|
||||
const compression = require('compression');
|
||||
const sessionParser = require('./routes/session_parser.js');
|
||||
const utils = require('./services/utils.js');
|
||||
|
||||
require('./services/handlers.js');
|
||||
require('./becca/becca_loader.js');
|
||||
|
||||
const app = express();
|
||||
|
||||
// view engine setup
|
||||
app.set('views', path.join(__dirname, 'views'));
|
||||
app.set('view engine', 'ejs');
|
||||
|
||||
if (!utils.isElectron()) {
|
||||
app.use(compression()); // HTTP compression
|
||||
}
|
||||
|
||||
app.use(helmet({
|
||||
hidePoweredBy: false, // errors out in electron
|
||||
contentSecurityPolicy: false,
|
||||
crossOriginEmbedderPolicy: false
|
||||
}));
|
||||
|
||||
app.use(express.text({limit: '500mb'}));
|
||||
app.use(express.json({limit: '500mb'}));
|
||||
app.use(express.raw({limit: '500mb'}));
|
||||
app.use(express.urlencoded({extended: false}));
|
||||
app.use(cookieParser());
|
||||
app.use(express.static(path.join(__dirname, 'public/root')));
|
||||
app.use(`/manifest.webmanifest`, express.static(path.join(__dirname, 'public/manifest.webmanifest')));
|
||||
app.use(`/robots.txt`, express.static(path.join(__dirname, 'public/robots.txt')));
|
||||
app.use(sessionParser);
|
||||
app.use(favicon(`${__dirname}/../images/app-icons/win/icon.ico`));
|
||||
|
||||
require('./routes/assets.js').register(app);
|
||||
require('./routes/routes.js').register(app);
|
||||
require('./routes/custom.js').register(app);
|
||||
require('./routes/error_handlers.js').register(app);
|
||||
|
||||
// triggers sync timer
|
||||
require('./services/sync.js');
|
||||
|
||||
// triggers backup timer
|
||||
require('./services/backup.js');
|
||||
|
||||
// trigger consistency checks timer
|
||||
require('./services/consistency_checks.js');
|
||||
|
||||
require('./services/scheduler.js');
|
||||
|
||||
if (utils.isElectron()) {
|
||||
require('@electron/remote/main').initialize();
|
||||
}
|
||||
|
||||
module.exports = app;
|
||||
60
src/app.ts
Normal file
60
src/app.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
import express = require('express');
|
||||
import path = require('path');
|
||||
import favicon = require('serve-favicon');
|
||||
import cookieParser = require('cookie-parser');
|
||||
import helmet = require('helmet');
|
||||
import compression = require('compression');
|
||||
import sessionParser = require('./routes/session_parser');
|
||||
import utils = require('./services/utils');
|
||||
|
||||
require('./services/handlers');
|
||||
require('./becca/becca_loader');
|
||||
|
||||
const app = express();
|
||||
|
||||
// view engine setup
|
||||
app.set('views', path.join(__dirname, 'views'));
|
||||
app.set('view engine', 'ejs');
|
||||
|
||||
if (!utils.isElectron()) {
|
||||
app.use(compression()); // HTTP compression
|
||||
}
|
||||
|
||||
app.use(helmet.default({
|
||||
hidePoweredBy: false, // errors out in electron
|
||||
contentSecurityPolicy: false,
|
||||
crossOriginEmbedderPolicy: false
|
||||
}));
|
||||
|
||||
app.use(express.text({ limit: '500mb' }));
|
||||
app.use(express.json({ limit: '500mb' }));
|
||||
app.use(express.raw({ limit: '500mb' }));
|
||||
app.use(express.urlencoded({ extended: false }));
|
||||
app.use(cookieParser());
|
||||
app.use(express.static(path.join(__dirname, 'public/root')));
|
||||
app.use(`/manifest.webmanifest`, express.static(path.join(__dirname, 'public/manifest.webmanifest')));
|
||||
app.use(`/robots.txt`, express.static(path.join(__dirname, 'public/robots.txt')));
|
||||
app.use(sessionParser);
|
||||
app.use(favicon(`${__dirname}/../images/app-icons/win/icon.ico`));
|
||||
|
||||
require('./routes/assets').register(app);
|
||||
require('./routes/routes').register(app);
|
||||
require('./routes/custom').register(app);
|
||||
require('./routes/error_handlers').register(app);
|
||||
|
||||
// triggers sync timer
|
||||
require('./services/sync');
|
||||
|
||||
// triggers backup timer
|
||||
require('./services/backup');
|
||||
|
||||
// trigger consistency checks timer
|
||||
require('./services/consistency_checks');
|
||||
|
||||
require('./services/scheduler');
|
||||
|
||||
if (utils.isElectron()) {
|
||||
require('@electron/remote/main').initialize();
|
||||
}
|
||||
|
||||
export = app;
|
||||
@@ -1,32 +1,52 @@
|
||||
"use strict";
|
||||
import sql = require('../services/sql');
|
||||
import NoteSet = require('../services/search/note_set');
|
||||
import NotFoundError = require('../errors/not_found_error');
|
||||
import BOption = require('./entities/boption');
|
||||
import BNote = require('./entities/bnote');
|
||||
import BEtapiToken = require('./entities/betapi_token');
|
||||
import BAttribute = require('./entities/battribute');
|
||||
import BBranch = require('./entities/bbranch');
|
||||
import BRevision = require('./entities/brevision');
|
||||
import BAttachment = require('./entities/battachment');
|
||||
import { AttachmentRow, RevisionRow } from './entities/rows';
|
||||
import BBlob = require('./entities/bblob');
|
||||
import BRecentNote = require('./entities/brecent_note');
|
||||
import AbstractBeccaEntity = require('./entities/abstract_becca_entity');
|
||||
|
||||
const sql = require('../services/sql.js');
|
||||
const NoteSet = require('../services/search/note_set.js');
|
||||
const NotFoundError = require('../errors/not_found_error.js');
|
||||
interface AttachmentOpts {
|
||||
includeContentLength?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Becca is a backend cache of all notes, branches, and attributes.
|
||||
* There's a similar frontend cache Froca, and share cache Shaca.
|
||||
*/
|
||||
class Becca {
|
||||
export default class Becca {
|
||||
loaded!: boolean;
|
||||
|
||||
notes!: Record<string, BNote>;
|
||||
branches!: Record<string, BBranch>;
|
||||
childParentToBranch!: Record<string, BBranch>;
|
||||
attributes!: Record<string, BAttribute>;
|
||||
/** Points from attribute type-name to list of attributes */
|
||||
attributeIndex!: Record<string, BAttribute[]>;
|
||||
options!: Record<string, BOption>;
|
||||
etapiTokens!: Record<string, BEtapiToken>;
|
||||
|
||||
allNoteSetCache: NoteSet | null;
|
||||
|
||||
constructor() {
|
||||
this.reset();
|
||||
this.allNoteSetCache = null;
|
||||
}
|
||||
|
||||
reset() {
|
||||
/** @type {Object.<String, BNote>} */
|
||||
this.notes = {};
|
||||
/** @type {Object.<String, BBranch>} */
|
||||
this.branches = {};
|
||||
/** @type {Object.<String, BBranch>} */
|
||||
this.childParentToBranch = {};
|
||||
/** @type {Object.<String, BAttribute>} */
|
||||
this.attributes = {};
|
||||
/** @type {Object.<String, BAttribute[]>} Points from attribute type-name to list of attributes */
|
||||
this.attributes = {};
|
||||
this.attributeIndex = {};
|
||||
/** @type {Object.<String, BOption>} */
|
||||
this.options = {};
|
||||
/** @type {Object.<String, BEtapiToken>} */
|
||||
this.etapiTokens = {};
|
||||
|
||||
this.dirtyNoteSetCache();
|
||||
@@ -38,8 +58,7 @@ class Becca {
|
||||
return this.getNote('root');
|
||||
}
|
||||
|
||||
/** @returns {BAttribute[]} */
|
||||
findAttributes(type, name) {
|
||||
findAttributes(type: string, name: string): BAttribute[] {
|
||||
name = name.trim().toLowerCase();
|
||||
|
||||
if (name.startsWith('#') || name.startsWith('~')) {
|
||||
@@ -49,9 +68,8 @@ class Becca {
|
||||
return this.attributeIndex[`${type}-${name}`] || [];
|
||||
}
|
||||
|
||||
/** @returns {BAttribute[]} */
|
||||
findAttributesWithPrefix(type, name) {
|
||||
const resArr = [];
|
||||
findAttributesWithPrefix(type: string, name: string): BAttribute[] {
|
||||
const resArr: BAttribute[][] = [];
|
||||
const key = `${type}-${name}`;
|
||||
|
||||
for (const idx in this.attributeIndex) {
|
||||
@@ -69,18 +87,16 @@ class Becca {
|
||||
}
|
||||
}
|
||||
|
||||
addNote(noteId, note) {
|
||||
addNote(noteId: string, note: BNote) {
|
||||
this.notes[noteId] = note;
|
||||
this.dirtyNoteSetCache();
|
||||
}
|
||||
|
||||
/** @returns {BNote|null} */
|
||||
getNote(noteId) {
|
||||
getNote(noteId: string): BNote | null {
|
||||
return this.notes[noteId];
|
||||
}
|
||||
|
||||
/** @returns {BNote|null} */
|
||||
getNoteOrThrow(noteId) {
|
||||
getNoteOrThrow(noteId: string): BNote {
|
||||
const note = this.notes[noteId];
|
||||
if (!note) {
|
||||
throw new NotFoundError(`Note '${noteId}' doesn't exist.`);
|
||||
@@ -89,9 +105,8 @@ class Becca {
|
||||
return note;
|
||||
}
|
||||
|
||||
/** @returns {BNote[]} */
|
||||
getNotes(noteIds, ignoreMissing = false) {
|
||||
const filteredNotes = [];
|
||||
getNotes(noteIds: string[], ignoreMissing: boolean = false): BNote[] {
|
||||
const filteredNotes: BNote[] = [];
|
||||
|
||||
for (const noteId of noteIds) {
|
||||
const note = this.notes[noteId];
|
||||
@@ -110,13 +125,11 @@ class Becca {
|
||||
return filteredNotes;
|
||||
}
|
||||
|
||||
/** @returns {BBranch|null} */
|
||||
getBranch(branchId) {
|
||||
getBranch(branchId: string): BBranch | null {
|
||||
return this.branches[branchId];
|
||||
}
|
||||
|
||||
/** @returns {BBranch|null} */
|
||||
getBranchOrThrow(branchId) {
|
||||
getBranchOrThrow(branchId: string): BBranch {
|
||||
const branch = this.getBranch(branchId);
|
||||
if (!branch) {
|
||||
throw new NotFoundError(`Branch '${branchId}' was not found in becca.`);
|
||||
@@ -124,13 +137,11 @@ class Becca {
|
||||
return branch;
|
||||
}
|
||||
|
||||
/** @returns {BAttribute|null} */
|
||||
getAttribute(attributeId) {
|
||||
getAttribute(attributeId: string): BAttribute | null {
|
||||
return this.attributes[attributeId];
|
||||
}
|
||||
|
||||
/** @returns {BAttribute} */
|
||||
getAttributeOrThrow(attributeId) {
|
||||
getAttributeOrThrow(attributeId: string): BAttribute {
|
||||
const attribute = this.getAttribute(attributeId);
|
||||
if (!attribute) {
|
||||
throw new NotFoundError(`Attribute '${attributeId}' does not exist.`);
|
||||
@@ -139,21 +150,26 @@ class Becca {
|
||||
return attribute;
|
||||
}
|
||||
|
||||
/** @returns {BBranch|null} */
|
||||
getBranchFromChildAndParent(childNoteId, parentNoteId) {
|
||||
getBranchFromChildAndParent(childNoteId: string, parentNoteId: string): BBranch | null {
|
||||
return this.childParentToBranch[`${childNoteId}-${parentNoteId}`];
|
||||
}
|
||||
|
||||
/** @returns {BRevision|null} */
|
||||
getRevision(revisionId) {
|
||||
getRevision(revisionId: string): BRevision | null {
|
||||
const row = sql.getRow("SELECT * FROM revisions WHERE revisionId = ?", [revisionId]);
|
||||
|
||||
const BRevision = require('./entities/brevision.js'); // avoiding circular dependency problems
|
||||
const BRevision = require('./entities/brevision'); // avoiding circular dependency problems
|
||||
return row ? new BRevision(row) : null;
|
||||
}
|
||||
|
||||
/** @returns {BAttachment|null} */
|
||||
getAttachment(attachmentId, opts = {}) {
|
||||
getRevisionOrThrow(revisionId: string): BRevision {
|
||||
const revision = this.getRevision(revisionId);
|
||||
if (!revision) {
|
||||
throw new NotFoundError(`Revision '${revisionId}' has not been found.`);
|
||||
}
|
||||
return revision;
|
||||
}
|
||||
|
||||
getAttachment(attachmentId: string, opts: AttachmentOpts = {}): BAttachment | null {
|
||||
opts.includeContentLength = !!opts.includeContentLength;
|
||||
|
||||
const query = opts.includeContentLength
|
||||
@@ -163,14 +179,13 @@ class Becca {
|
||||
WHERE attachmentId = ? AND isDeleted = 0`
|
||||
: `SELECT * FROM attachments WHERE attachmentId = ? AND isDeleted = 0`;
|
||||
|
||||
const BAttachment = require('./entities/battachment.js'); // avoiding circular dependency problems
|
||||
const BAttachment = require('./entities/battachment'); // avoiding circular dependency problems
|
||||
|
||||
return sql.getRows(query, [attachmentId])
|
||||
.map(row => new BAttachment(row))[0];
|
||||
}
|
||||
|
||||
/** @returns {BAttachment} */
|
||||
getAttachmentOrThrow(attachmentId, opts = {}) {
|
||||
getAttachmentOrThrow(attachmentId: string, opts: AttachmentOpts = {}): BAttachment {
|
||||
const attachment = this.getAttachment(attachmentId, opts);
|
||||
if (!attachment) {
|
||||
throw new NotFoundError(`Attachment '${attachmentId}' has not been found.`);
|
||||
@@ -178,38 +193,36 @@ class Becca {
|
||||
return attachment;
|
||||
}
|
||||
|
||||
/** @returns {BAttachment[]} */
|
||||
getAttachments(attachmentIds) {
|
||||
const BAttachment = require('./entities/battachment.js'); // avoiding circular dependency problems
|
||||
return sql.getManyRows("SELECT * FROM attachments WHERE attachmentId IN (???) AND isDeleted = 0", attachmentIds)
|
||||
getAttachments(attachmentIds: string[]): BAttachment[] {
|
||||
const BAttachment = require('./entities/battachment'); // avoiding circular dependency problems
|
||||
return sql.getManyRows<AttachmentRow>("SELECT * FROM attachments WHERE attachmentId IN (???) AND isDeleted = 0", attachmentIds)
|
||||
.map(row => new BAttachment(row));
|
||||
}
|
||||
|
||||
/** @returns {BBlob|null} */
|
||||
getBlob(entity) {
|
||||
getBlob(entity: { blobId?: string }): BBlob | null {
|
||||
if (!entity.blobId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const row = sql.getRow("SELECT *, LENGTH(content) AS contentLength FROM blobs WHERE blobId = ?", [entity.blobId]);
|
||||
|
||||
const BBlob = require('./entities/bblob.js'); // avoiding circular dependency problems
|
||||
const BBlob = require('./entities/bblob'); // avoiding circular dependency problems
|
||||
return row ? new BBlob(row) : null;
|
||||
}
|
||||
|
||||
/** @returns {BOption|null} */
|
||||
getOption(name) {
|
||||
getOption(name: string): BOption | null {
|
||||
return this.options[name];
|
||||
}
|
||||
|
||||
/** @returns {BEtapiToken[]} */
|
||||
getEtapiTokens() {
|
||||
getEtapiTokens(): BEtapiToken[] {
|
||||
return Object.values(this.etapiTokens);
|
||||
}
|
||||
|
||||
/** @returns {BEtapiToken|null} */
|
||||
getEtapiToken(etapiTokenId) {
|
||||
getEtapiToken(etapiTokenId: string): BEtapiToken | null {
|
||||
return this.etapiTokens[etapiTokenId];
|
||||
}
|
||||
|
||||
/** @returns {AbstractBeccaEntity|null} */
|
||||
getEntity(entityName, entityId) {
|
||||
getEntity<T extends AbstractBeccaEntity<T>>(entityName: string, entityId: string): AbstractBeccaEntity<T> | null {
|
||||
if (!entityName || !entityId) {
|
||||
return null;
|
||||
}
|
||||
@@ -231,22 +244,20 @@ class Becca {
|
||||
throw new Error(`Unknown entity name '${camelCaseEntityName}' (original argument '${entityName}')`);
|
||||
}
|
||||
|
||||
return this[camelCaseEntityName][entityId];
|
||||
return (this as any)[camelCaseEntityName][entityId];
|
||||
}
|
||||
|
||||
/** @returns {BRecentNote[]} */
|
||||
getRecentNotesFromQuery(query, params = []) {
|
||||
getRecentNotesFromQuery(query: string, params: string[] = []): BRecentNote[] {
|
||||
const rows = sql.getRows(query, params);
|
||||
|
||||
const BRecentNote = require('./entities/brecent_note.js'); // avoiding circular dependency problems
|
||||
const BRecentNote = require('./entities/brecent_note'); // avoiding circular dependency problems
|
||||
return rows.map(row => new BRecentNote(row));
|
||||
}
|
||||
|
||||
/** @returns {BRevision[]} */
|
||||
getRevisionsFromQuery(query, params = []) {
|
||||
const rows = sql.getRows(query, params);
|
||||
getRevisionsFromQuery(query: string, params: string[] = []): BRevision[] {
|
||||
const rows = sql.getRows<RevisionRow>(query, params);
|
||||
|
||||
const BRevision = require('./entities/brevision.js'); // avoiding circular dependency problems
|
||||
const BRevision = require('./entities/brevision'); // avoiding circular dependency problems
|
||||
return rows.map(row => new BRevision(row));
|
||||
}
|
||||
|
||||
@@ -260,8 +271,8 @@ class Becca {
|
||||
if (!this.allNoteSetCache) {
|
||||
const allNotes = [];
|
||||
|
||||
for (const noteId in becca.notes) {
|
||||
const note = becca.notes[noteId];
|
||||
for (const noteId in this.notes) {
|
||||
const note = this.notes[noteId];
|
||||
|
||||
// in the process of loading data sometimes we create "skeleton" note instances which are expected to be filled later
|
||||
// in case of inconsistent data this might not work and search will then crash on these
|
||||
@@ -277,6 +288,26 @@ class Becca {
|
||||
}
|
||||
}
|
||||
|
||||
const becca = new Becca();
|
||||
/**
|
||||
* This interface contains the data that is shared across all the objects of a given derived class of {@link AbstractBeccaEntity}.
|
||||
* For example, all BAttributes will share their content, but all BBranches will have another set of this data.
|
||||
*/
|
||||
export interface ConstructorData<T extends AbstractBeccaEntity<T>> {
|
||||
primaryKeyName: string;
|
||||
entityName: string;
|
||||
hashedProperties: (keyof T)[];
|
||||
}
|
||||
|
||||
module.exports = becca;
|
||||
export interface NotePojo {
|
||||
noteId: string;
|
||||
title?: string;
|
||||
isProtected?: boolean;
|
||||
type: string;
|
||||
mime: string;
|
||||
blobId?: string;
|
||||
isDeleted: boolean;
|
||||
dateCreated?: string;
|
||||
dateModified?: string;
|
||||
utcDateCreated: string;
|
||||
utcDateModified?: string;
|
||||
}
|
||||
7
src/becca/becca.ts
Normal file
7
src/becca/becca.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
"use strict";
|
||||
|
||||
import Becca from "./becca-interface";
|
||||
|
||||
const becca = new Becca();
|
||||
|
||||
export = becca;
|
||||
@@ -1,24 +1,26 @@
|
||||
"use strict";
|
||||
|
||||
const sql = require('../services/sql.js');
|
||||
const eventService = require('../services/events.js');
|
||||
const becca = require('./becca.js');
|
||||
const sqlInit = require('../services/sql_init.js');
|
||||
const log = require('../services/log.js');
|
||||
const BNote = require('./entities/bnote.js');
|
||||
const BBranch = require('./entities/bbranch.js');
|
||||
const BAttribute = require('./entities/battribute.js');
|
||||
const BOption = require('./entities/boption.js');
|
||||
const BEtapiToken = require('./entities/betapi_token.js');
|
||||
const cls = require('../services/cls.js');
|
||||
const entityConstructor = require('../becca/entity_constructor.js');
|
||||
import sql = require('../services/sql');
|
||||
import eventService = require('../services/events');
|
||||
import becca = require('./becca');
|
||||
import sqlInit = require('../services/sql_init');
|
||||
import log = require('../services/log');
|
||||
import BNote = require('./entities/bnote');
|
||||
import BBranch = require('./entities/bbranch');
|
||||
import BAttribute = require('./entities/battribute');
|
||||
import BOption = require('./entities/boption');
|
||||
import BEtapiToken = require('./entities/betapi_token');
|
||||
import cls = require('../services/cls');
|
||||
import entityConstructor = require('../becca/entity_constructor');
|
||||
import { AttributeRow, BranchRow, EtapiTokenRow, NoteRow, OptionRow } from './entities/rows';
|
||||
import AbstractBeccaEntity = require('./entities/abstract_becca_entity');
|
||||
|
||||
const beccaLoaded = new Promise((res, rej) => {
|
||||
const beccaLoaded = new Promise<void>((res, rej) => {
|
||||
sqlInit.dbReady.then(() => {
|
||||
cls.init(() => {
|
||||
load();
|
||||
|
||||
require('../services/options_init.js').initStartupOptions();
|
||||
require('../services/options_init').initStartupOptions();
|
||||
|
||||
res();
|
||||
});
|
||||
@@ -38,23 +40,23 @@ function load() {
|
||||
new BNote().update(row).init();
|
||||
}
|
||||
|
||||
const branchRows = sql.getRawRows(`SELECT branchId, noteId, parentNoteId, prefix, notePosition, isExpanded, utcDateModified FROM branches WHERE isDeleted = 0`);
|
||||
const branchRows = sql.getRawRows<BranchRow>(`SELECT branchId, noteId, parentNoteId, prefix, notePosition, isExpanded, utcDateModified FROM branches WHERE isDeleted = 0`);
|
||||
// in-memory sort is faster than in the DB
|
||||
branchRows.sort((a, b) => a.notePosition - b.notePosition);
|
||||
branchRows.sort((a, b) => (a.notePosition || 0) - (b.notePosition || 0));
|
||||
|
||||
for (const row of branchRows) {
|
||||
new BBranch().update(row).init();
|
||||
}
|
||||
|
||||
for (const row of sql.getRawRows(`SELECT attributeId, noteId, type, name, value, isInheritable, position, utcDateModified FROM attributes WHERE isDeleted = 0`)) {
|
||||
for (const row of sql.getRawRows<AttributeRow>(`SELECT attributeId, noteId, type, name, value, isInheritable, position, utcDateModified FROM attributes WHERE isDeleted = 0`)) {
|
||||
new BAttribute().update(row).init();
|
||||
}
|
||||
|
||||
for (const row of sql.getRows(`SELECT name, value, isSynced, utcDateModified FROM options`)) {
|
||||
for (const row of sql.getRows<OptionRow>(`SELECT name, value, isSynced, utcDateModified FROM options`)) {
|
||||
new BOption(row);
|
||||
}
|
||||
|
||||
for (const row of sql.getRows(`SELECT etapiTokenId, name, tokenHash, utcDateCreated, utcDateModified FROM etapi_tokens WHERE isDeleted = 0`)) {
|
||||
for (const row of sql.getRows<EtapiTokenRow>(`SELECT etapiTokenId, name, tokenHash, utcDateCreated, utcDateModified FROM etapi_tokens WHERE isDeleted = 0`)) {
|
||||
new BEtapiToken(row);
|
||||
}
|
||||
});
|
||||
@@ -68,13 +70,13 @@ function load() {
|
||||
log.info(`Becca (note cache) load took ${Date.now() - start}ms`);
|
||||
}
|
||||
|
||||
function reload(reason) {
|
||||
function reload(reason: string) {
|
||||
load();
|
||||
|
||||
require('../services/ws.js').reloadFrontend(reason || "becca reloaded");
|
||||
require('../services/ws').reloadFrontend(reason || "becca reloaded");
|
||||
}
|
||||
|
||||
eventService.subscribeBeccaLoader([eventService.ENTITY_CHANGE_SYNCED], ({entityName, entityRow}) => {
|
||||
eventService.subscribeBeccaLoader([eventService.ENTITY_CHANGE_SYNCED], ({ entityName, entityRow }) => {
|
||||
if (!becca.loaded) {
|
||||
return;
|
||||
}
|
||||
@@ -88,7 +90,7 @@ eventService.subscribeBeccaLoader([eventService.ENTITY_CHANGE_SYNCED], ({entity
|
||||
if (beccaEntity) {
|
||||
beccaEntity.updateFromRow(entityRow);
|
||||
} else {
|
||||
beccaEntity = new EntityClass();
|
||||
beccaEntity = new EntityClass() as AbstractBeccaEntity<AbstractBeccaEntity<any>>;
|
||||
beccaEntity.updateFromRow(entityRow);
|
||||
beccaEntity.init();
|
||||
}
|
||||
@@ -97,7 +99,7 @@ eventService.subscribeBeccaLoader([eventService.ENTITY_CHANGE_SYNCED], ({entity
|
||||
postProcessEntityUpdate(entityName, entityRow);
|
||||
});
|
||||
|
||||
eventService.subscribeBeccaLoader(eventService.ENTITY_CHANGED, ({entityName, entity}) => {
|
||||
eventService.subscribeBeccaLoader(eventService.ENTITY_CHANGED, ({ entityName, entity }) => {
|
||||
if (!becca.loaded) {
|
||||
return;
|
||||
}
|
||||
@@ -112,7 +114,7 @@ eventService.subscribeBeccaLoader(eventService.ENTITY_CHANGED, ({entityName, en
|
||||
* @param entityRow - can be a becca entity (change comes from this trilium instance) or just a row (from sync).
|
||||
* It should be therefore treated as a row.
|
||||
*/
|
||||
function postProcessEntityUpdate(entityName, entityRow) {
|
||||
function postProcessEntityUpdate(entityName: string, entityRow: any) {
|
||||
if (entityName === 'notes') {
|
||||
noteUpdated(entityRow);
|
||||
} else if (entityName === 'branches') {
|
||||
@@ -124,7 +126,7 @@ function postProcessEntityUpdate(entityName, entityRow) {
|
||||
}
|
||||
}
|
||||
|
||||
eventService.subscribeBeccaLoader([eventService.ENTITY_DELETED, eventService.ENTITY_DELETE_SYNCED], ({entityName, entityId}) => {
|
||||
eventService.subscribeBeccaLoader([eventService.ENTITY_DELETED, eventService.ENTITY_DELETE_SYNCED], ({ entityName, entityId }) => {
|
||||
if (!becca.loaded) {
|
||||
return;
|
||||
}
|
||||
@@ -140,13 +142,13 @@ eventService.subscribeBeccaLoader([eventService.ENTITY_DELETED, eventService.ENT
|
||||
}
|
||||
});
|
||||
|
||||
function noteDeleted(noteId) {
|
||||
function noteDeleted(noteId: string) {
|
||||
delete becca.notes[noteId];
|
||||
|
||||
becca.dirtyNoteSetCache();
|
||||
}
|
||||
|
||||
function branchDeleted(branchId) {
|
||||
function branchDeleted(branchId: string) {
|
||||
const branch = becca.branches[branchId];
|
||||
|
||||
if (!branch) {
|
||||
@@ -173,23 +175,26 @@ function branchDeleted(branchId) {
|
||||
}
|
||||
|
||||
delete becca.childParentToBranch[`${branch.noteId}-${branch.parentNoteId}`];
|
||||
delete becca.branches[branch.branchId];
|
||||
}
|
||||
|
||||
function noteUpdated(entityRow) {
|
||||
const note = becca.notes[entityRow.noteId];
|
||||
|
||||
if (note) {
|
||||
// type / mime could have been changed, and they are present in flatTextCache
|
||||
note.flatTextCache = null;
|
||||
if (branch.branchId) {
|
||||
delete becca.branches[branch.branchId];
|
||||
}
|
||||
}
|
||||
|
||||
function branchUpdated(branchRow) {
|
||||
function noteUpdated(entityRow: NoteRow) {
|
||||
const note = becca.notes[entityRow.noteId];
|
||||
|
||||
if (note) {
|
||||
// TODO, this wouldn't have worked in the original implementation since the variable was named __flatTextCache.
|
||||
// type / mime could have been changed, and they are present in flatTextCache
|
||||
note.__flatTextCache = null;
|
||||
}
|
||||
}
|
||||
|
||||
function branchUpdated(branchRow: BranchRow) {
|
||||
const childNote = becca.notes[branchRow.noteId];
|
||||
|
||||
if (childNote) {
|
||||
childNote.flatTextCache = null;
|
||||
childNote.__flatTextCache = null;
|
||||
childNote.sortParents();
|
||||
|
||||
// notes in the subtree can get new inherited attributes
|
||||
@@ -204,7 +209,7 @@ function branchUpdated(branchRow) {
|
||||
}
|
||||
}
|
||||
|
||||
function attributeDeleted(attributeId) {
|
||||
function attributeDeleted(attributeId: string) {
|
||||
const attribute = becca.attributes[attributeId];
|
||||
|
||||
if (!attribute) {
|
||||
@@ -239,8 +244,7 @@ function attributeDeleted(attributeId) {
|
||||
}
|
||||
}
|
||||
|
||||
/** @param {BAttribute} attributeRow */
|
||||
function attributeUpdated(attributeRow) {
|
||||
function attributeUpdated(attributeRow: BAttribute) {
|
||||
const attribute = becca.attributes[attributeRow.attributeId];
|
||||
const note = becca.notes[attributeRow.noteId];
|
||||
|
||||
@@ -253,7 +257,7 @@ function attributeUpdated(attributeRow) {
|
||||
}
|
||||
}
|
||||
|
||||
function noteReorderingUpdated(branchIdList) {
|
||||
function noteReorderingUpdated(branchIdList: number[]) {
|
||||
const parentNoteIds = new Set();
|
||||
|
||||
for (const branchId in branchIdList) {
|
||||
@@ -267,7 +271,7 @@ function noteReorderingUpdated(branchIdList) {
|
||||
}
|
||||
}
|
||||
|
||||
function etapiTokenDeleted(etapiTokenId) {
|
||||
function etapiTokenDeleted(etapiTokenId: string) {
|
||||
delete becca.etapiTokens[etapiTokenId];
|
||||
}
|
||||
|
||||
@@ -275,14 +279,14 @@ eventService.subscribeBeccaLoader(eventService.ENTER_PROTECTED_SESSION, () => {
|
||||
try {
|
||||
becca.decryptProtectedNotes();
|
||||
}
|
||||
catch (e) {
|
||||
catch (e: any) {
|
||||
log.error(`Could not decrypt protected notes: ${e.message} ${e.stack}`);
|
||||
}
|
||||
});
|
||||
|
||||
eventService.subscribeBeccaLoader(eventService.LEAVE_PROTECTED_SESSION, load);
|
||||
|
||||
module.exports = {
|
||||
export = {
|
||||
load,
|
||||
reload,
|
||||
beccaLoaded
|
||||
@@ -1,10 +1,10 @@
|
||||
"use strict";
|
||||
|
||||
const becca = require('./becca.js');
|
||||
const cls = require('../services/cls.js');
|
||||
const log = require('../services/log.js');
|
||||
import becca = require('./becca');
|
||||
import cls = require('../services/cls');
|
||||
import log = require('../services/log');
|
||||
|
||||
function isNotePathArchived(notePath) {
|
||||
function isNotePathArchived(notePath: string[]) {
|
||||
const noteId = notePath[notePath.length - 1];
|
||||
const note = becca.notes[noteId];
|
||||
|
||||
@@ -24,9 +24,9 @@ function isNotePathArchived(notePath) {
|
||||
return false;
|
||||
}
|
||||
|
||||
function getNoteTitle(childNoteId, parentNoteId) {
|
||||
function getNoteTitle(childNoteId: string, parentNoteId?: string) {
|
||||
const childNote = becca.notes[childNoteId];
|
||||
const parentNote = becca.notes[parentNoteId];
|
||||
const parentNote = parentNoteId ? becca.notes[parentNoteId] : null;
|
||||
|
||||
if (!childNote) {
|
||||
log.info(`Cannot find note '${childNoteId}'`);
|
||||
@@ -40,7 +40,7 @@ function getNoteTitle(childNoteId, parentNoteId) {
|
||||
return `${(branch && branch.prefix) ? `${branch.prefix} - ` : ''}${title}`;
|
||||
}
|
||||
|
||||
function getNoteTitleArrayForPath(notePathArray) {
|
||||
function getNoteTitleArrayForPath(notePathArray: string[]) {
|
||||
if (!notePathArray || !Array.isArray(notePathArray)) {
|
||||
throw new Error(`${notePathArray} is not an array.`);
|
||||
}
|
||||
@@ -76,13 +76,13 @@ function getNoteTitleArrayForPath(notePathArray) {
|
||||
return titles;
|
||||
}
|
||||
|
||||
function getNoteTitleForPath(notePathArray) {
|
||||
function getNoteTitleForPath(notePathArray: string[]) {
|
||||
const titles = getNoteTitleArrayForPath(notePathArray);
|
||||
|
||||
return titles.join(' / ');
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
export = {
|
||||
getNoteTitle,
|
||||
getNoteTitleForPath,
|
||||
isNotePathArchived
|
||||
@@ -1,66 +1,77 @@
|
||||
"use strict";
|
||||
|
||||
const utils = require('../../services/utils.js');
|
||||
const sql = require('../../services/sql.js');
|
||||
const entityChangesService = require('../../services/entity_changes.js');
|
||||
const eventService = require('../../services/events.js');
|
||||
const dateUtils = require('../../services/date_utils.js');
|
||||
const cls = require('../../services/cls.js');
|
||||
const log = require('../../services/log.js');
|
||||
const protectedSessionService = require('../../services/protected_session.js');
|
||||
const blobService = require('../../services/blob.js');
|
||||
import utils = require('../../services/utils');
|
||||
import sql = require('../../services/sql');
|
||||
import entityChangesService = require('../../services/entity_changes');
|
||||
import eventService = require('../../services/events');
|
||||
import dateUtils = require('../../services/date_utils');
|
||||
import cls = require('../../services/cls');
|
||||
import log = require('../../services/log');
|
||||
import protectedSessionService = require('../../services/protected_session');
|
||||
import blobService = require('../../services/blob');
|
||||
import Becca, { ConstructorData } from '../becca-interface';
|
||||
|
||||
let becca = null;
|
||||
let becca: Becca;
|
||||
|
||||
interface ContentOpts {
|
||||
forceSave?: boolean;
|
||||
forceFrontendReload?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Base class for all backend entities.
|
||||
*
|
||||
* @type T the same entity type needed for self-reference in {@link ConstructorData}.
|
||||
*/
|
||||
class AbstractBeccaEntity {
|
||||
/** @protected */
|
||||
beforeSaving() {
|
||||
if (!this[this.constructor.primaryKeyName]) {
|
||||
this[this.constructor.primaryKeyName] = utils.newEntityId();
|
||||
abstract class AbstractBeccaEntity<T extends AbstractBeccaEntity<T>> {
|
||||
|
||||
utcDateModified?: string;
|
||||
dateCreated?: string;
|
||||
dateModified?: string;
|
||||
|
||||
utcDateCreated!: string;
|
||||
|
||||
isProtected?: boolean;
|
||||
isSynced?: boolean;
|
||||
blobId?: string;
|
||||
|
||||
protected beforeSaving() {
|
||||
const constructorData = (this.constructor as unknown as ConstructorData<T>);
|
||||
if (!(this as any)[constructorData.primaryKeyName]) {
|
||||
(this as any)[constructorData.primaryKeyName] = utils.newEntityId();
|
||||
}
|
||||
}
|
||||
|
||||
/** @protected */
|
||||
getUtcDateChanged() {
|
||||
return this.utcDateModified || this.utcDateCreated;
|
||||
}
|
||||
|
||||
/**
|
||||
* @protected
|
||||
* @returns {Becca}
|
||||
*/
|
||||
get becca() {
|
||||
protected get becca(): Becca {
|
||||
if (!becca) {
|
||||
becca = require('../becca.js');
|
||||
becca = require('../becca');
|
||||
}
|
||||
|
||||
return becca;
|
||||
return becca as Becca;
|
||||
}
|
||||
|
||||
/** @protected */
|
||||
putEntityChange(isDeleted) {
|
||||
protected putEntityChange(isDeleted: boolean) {
|
||||
const constructorData = (this.constructor as unknown as ConstructorData<T>);
|
||||
entityChangesService.putEntityChange({
|
||||
entityName: this.constructor.entityName,
|
||||
entityId: this[this.constructor.primaryKeyName],
|
||||
entityName: constructorData.entityName,
|
||||
entityId: (this as any)[constructorData.primaryKeyName],
|
||||
hash: this.generateHash(isDeleted),
|
||||
isErased: false,
|
||||
utcDateChanged: this.getUtcDateChanged(),
|
||||
isSynced: this.constructor.entityName !== 'options' || !!this.isSynced
|
||||
isSynced: constructorData.entityName !== 'options' || !!this.isSynced
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @protected
|
||||
* @returns {string}
|
||||
*/
|
||||
generateHash(isDeleted) {
|
||||
generateHash(isDeleted?: boolean): string {
|
||||
const constructorData = (this.constructor as unknown as ConstructorData<T>);
|
||||
let contentToHash = "";
|
||||
|
||||
for (const propertyName of this.constructor.hashedProperties) {
|
||||
contentToHash += `|${this[propertyName]}`;
|
||||
for (const propertyName of constructorData.hashedProperties) {
|
||||
contentToHash += `|${(this as any)[propertyName]}`;
|
||||
}
|
||||
|
||||
if (isDeleted) {
|
||||
@@ -70,31 +81,40 @@ class AbstractBeccaEntity {
|
||||
return utils.hash(contentToHash).substr(0, 10);
|
||||
}
|
||||
|
||||
/** @protected */
|
||||
getPojoToSave() {
|
||||
protected getPojoToSave() {
|
||||
return this.getPojo();
|
||||
}
|
||||
|
||||
/**
|
||||
* @protected
|
||||
* @abstract
|
||||
*/
|
||||
getPojo() {
|
||||
throw new Error(`Unimplemented getPojo() for entity '${this.constructor.name}'`)
|
||||
hasStringContent(): boolean {
|
||||
// TODO: Not sure why some entities don't implement it.
|
||||
return true;
|
||||
}
|
||||
|
||||
abstract getPojo(): {};
|
||||
|
||||
init() {
|
||||
// Do nothing by default, can be overriden in derived classes.
|
||||
}
|
||||
|
||||
abstract updateFromRow(row: unknown): void;
|
||||
|
||||
get isDeleted(): boolean {
|
||||
// TODO: Not sure why some entities don't implement it.
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves entity - executes SQL, but doesn't commit the transaction on its own
|
||||
*
|
||||
* @returns {this}
|
||||
*/
|
||||
save(opts = {}) {
|
||||
const entityName = this.constructor.entityName;
|
||||
const primaryKeyName = this.constructor.primaryKeyName;
|
||||
// TODO: opts not used but called a few times, maybe should be used by derived classes or passed to beforeSaving.
|
||||
save(opts?: {}): this {
|
||||
const constructorData = (this.constructor as unknown as ConstructorData<T>);
|
||||
const entityName = constructorData.entityName;
|
||||
const primaryKeyName = constructorData.primaryKeyName;
|
||||
|
||||
const isNewEntity = !this[primaryKeyName];
|
||||
|
||||
this.beforeSaving(opts);
|
||||
const isNewEntity = !(this as any)[primaryKeyName];
|
||||
|
||||
this.beforeSaving();
|
||||
|
||||
const pojo = this.getPojoToSave();
|
||||
|
||||
@@ -124,14 +144,14 @@ class AbstractBeccaEntity {
|
||||
return this;
|
||||
}
|
||||
|
||||
/** @protected */
|
||||
_setContent(content, opts = {}) {
|
||||
protected _setContent(content: string | Buffer, opts: ContentOpts = {}) {
|
||||
// client code asks to save entity even if blobId didn't change (something else was changed)
|
||||
opts.forceSave = !!opts.forceSave;
|
||||
opts.forceFrontendReload = !!opts.forceFrontendReload;
|
||||
|
||||
if (content === null || content === undefined) {
|
||||
throw new Error(`Cannot set null content to ${this.constructor.primaryKeyName} '${this[this.constructor.primaryKeyName]}'`);
|
||||
const constructorData = (this.constructor as unknown as ConstructorData<T>);
|
||||
throw new Error(`Cannot set null content to ${constructorData.primaryKeyName} '${(this as any)[constructorData.primaryKeyName]}'`);
|
||||
}
|
||||
|
||||
if (this.hasStringContent()) {
|
||||
@@ -140,32 +160,36 @@ class AbstractBeccaEntity {
|
||||
content = Buffer.isBuffer(content) ? content : Buffer.from(content);
|
||||
}
|
||||
|
||||
const unencryptedContentForHashCalculation = this.#getUnencryptedContentForHashCalculation(content);
|
||||
const unencryptedContentForHashCalculation = this.getUnencryptedContentForHashCalculation(content);
|
||||
|
||||
if (this.isProtected) {
|
||||
if (protectedSessionService.isProtectedSessionAvailable()) {
|
||||
content = protectedSessionService.encrypt(content);
|
||||
const encryptedContent = protectedSessionService.encrypt(content);
|
||||
if (!encryptedContent) {
|
||||
throw new Error(`Unable to encrypt the content of the entity.`);
|
||||
}
|
||||
content = encryptedContent;
|
||||
} else {
|
||||
throw new Error(`Cannot update content of blob since protected session is not available.`);
|
||||
}
|
||||
}
|
||||
|
||||
sql.transactional(() => {
|
||||
const newBlobId = this.#saveBlob(content, unencryptedContentForHashCalculation, opts);
|
||||
const newBlobId = this.saveBlob(content, unencryptedContentForHashCalculation, opts);
|
||||
const oldBlobId = this.blobId;
|
||||
|
||||
if (newBlobId !== oldBlobId || opts.forceSave) {
|
||||
this.blobId = newBlobId;
|
||||
this.save();
|
||||
|
||||
if (newBlobId !== oldBlobId) {
|
||||
this.#deleteBlobIfNotUsed(oldBlobId);
|
||||
if (oldBlobId && newBlobId !== oldBlobId) {
|
||||
this.deleteBlobIfNotUsed(oldBlobId);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#deleteBlobIfNotUsed(oldBlobId) {
|
||||
private deleteBlobIfNotUsed(oldBlobId: string) {
|
||||
if (sql.getValue("SELECT 1 FROM notes WHERE blobId = ? LIMIT 1", [oldBlobId])) {
|
||||
return;
|
||||
}
|
||||
@@ -184,7 +208,7 @@ class AbstractBeccaEntity {
|
||||
sql.execute("DELETE FROM entity_changes WHERE entityName = 'blobs' AND entityId = ?", [oldBlobId]);
|
||||
}
|
||||
|
||||
#getUnencryptedContentForHashCalculation(unencryptedContent) {
|
||||
private getUnencryptedContentForHashCalculation(unencryptedContent: Buffer | string) {
|
||||
if (this.isProtected) {
|
||||
// a "random" prefix makes sure that the calculated hash/blobId is different for a decrypted/encrypted content
|
||||
const encryptedPrefixSuffix = "t$[nvQg7q)&_ENCRYPTED_?M:Bf&j3jr_";
|
||||
@@ -196,7 +220,7 @@ class AbstractBeccaEntity {
|
||||
}
|
||||
}
|
||||
|
||||
#saveBlob(content, unencryptedContentForHashCalculation, opts = {}) {
|
||||
private saveBlob(content: string | Buffer, unencryptedContentForHashCalculation: string | Buffer, opts: ContentOpts = {}) {
|
||||
/*
|
||||
* We're using the unencrypted blob for the hash calculation, because otherwise the random IV would
|
||||
* cause every content blob to be unique which would balloon the database size (esp. with revisioning).
|
||||
@@ -243,41 +267,37 @@ class AbstractBeccaEntity {
|
||||
return newBlobId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @protected
|
||||
* @returns {string|Buffer}
|
||||
*/
|
||||
_getContent() {
|
||||
const row = sql.getRow(`SELECT content FROM blobs WHERE blobId = ?`, [this.blobId]);
|
||||
protected _getContent(): string | Buffer {
|
||||
const row = sql.getRow<{ content: string | Buffer }>(`SELECT content FROM blobs WHERE blobId = ?`, [this.blobId]);
|
||||
|
||||
if (!row) {
|
||||
throw new Error(`Cannot find content for ${this.constructor.primaryKeyName} '${this[this.constructor.primaryKeyName]}', blobId '${this.blobId}'`);
|
||||
const constructorData = (this.constructor as unknown as ConstructorData<T>);
|
||||
throw new Error(`Cannot find content for ${constructorData.primaryKeyName} '${(this as any)[constructorData.primaryKeyName]}', blobId '${this.blobId}'`);
|
||||
}
|
||||
|
||||
return blobService.processContent(row.content, this.isProtected, this.hasStringContent());
|
||||
return blobService.processContent(row.content, this.isProtected || false, this.hasStringContent());
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark the entity as (soft) deleted. It will be completely erased later.
|
||||
*
|
||||
* This is a low-level method, for notes and branches use `note.deleteNote()` and 'branch.deleteBranch()` instead.
|
||||
*
|
||||
* @param [deleteId=null]
|
||||
*/
|
||||
markAsDeleted(deleteId = null) {
|
||||
const entityId = this[this.constructor.primaryKeyName];
|
||||
const entityName = this.constructor.entityName;
|
||||
markAsDeleted(deleteId: string | null = null) {
|
||||
const constructorData = (this.constructor as unknown as ConstructorData<T>);
|
||||
const entityId = (this as any)[constructorData.primaryKeyName];
|
||||
const entityName = constructorData.entityName;
|
||||
|
||||
this.utcDateModified = dateUtils.utcNowDateTime();
|
||||
|
||||
sql.execute(`UPDATE ${entityName} SET isDeleted = 1, deleteId = ?, utcDateModified = ?
|
||||
WHERE ${this.constructor.primaryKeyName} = ?`,
|
||||
WHERE ${constructorData.primaryKeyName} = ?`,
|
||||
[deleteId, this.utcDateModified, entityId]);
|
||||
|
||||
if (this.dateModified) {
|
||||
this.dateModified = dateUtils.localNowDateTime();
|
||||
|
||||
sql.execute(`UPDATE ${entityName} SET dateModified = ? WHERE ${this.constructor.primaryKeyName} = ?`,
|
||||
sql.execute(`UPDATE ${entityName} SET dateModified = ? WHERE ${constructorData.primaryKeyName} = ?`,
|
||||
[this.dateModified, entityId]);
|
||||
}
|
||||
|
||||
@@ -289,13 +309,14 @@ class AbstractBeccaEntity {
|
||||
}
|
||||
|
||||
markAsDeletedSimple() {
|
||||
const entityId = this[this.constructor.primaryKeyName];
|
||||
const entityName = this.constructor.entityName;
|
||||
const constructorData = (this.constructor as unknown as ConstructorData<T>);
|
||||
const entityId = (this as any)[constructorData.primaryKeyName];
|
||||
const entityName = constructorData.entityName;
|
||||
|
||||
this.utcDateModified = dateUtils.utcNowDateTime();
|
||||
|
||||
sql.execute(`UPDATE ${entityName} SET isDeleted = 1, utcDateModified = ?
|
||||
WHERE ${this.constructor.primaryKeyName} = ?`,
|
||||
WHERE ${constructorData.primaryKeyName} = ?`,
|
||||
[this.utcDateModified, entityId]);
|
||||
|
||||
log.info(`Marking ${entityName} ${entityId} as deleted`);
|
||||
@@ -306,4 +327,4 @@ class AbstractBeccaEntity {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AbstractBeccaEntity;
|
||||
export = AbstractBeccaEntity;
|
||||
@@ -1,30 +1,64 @@
|
||||
"use strict";
|
||||
|
||||
const utils = require('../../services/utils.js');
|
||||
const dateUtils = require('../../services/date_utils.js');
|
||||
const AbstractBeccaEntity = require('./abstract_becca_entity.js');
|
||||
const sql = require('../../services/sql.js');
|
||||
const protectedSessionService = require('../../services/protected_session.js');
|
||||
const log = require('../../services/log.js');
|
||||
import utils = require('../../services/utils');
|
||||
import dateUtils = require('../../services/date_utils');
|
||||
import AbstractBeccaEntity = require('./abstract_becca_entity');
|
||||
import sql = require('../../services/sql');
|
||||
import protectedSessionService = require('../../services/protected_session');
|
||||
import log = require('../../services/log');
|
||||
import { AttachmentRow } from './rows';
|
||||
import BNote = require('./bnote');
|
||||
import BBranch = require('./bbranch');
|
||||
|
||||
const attachmentRoleToNoteTypeMapping = {
|
||||
'image': 'image'
|
||||
'image': 'image',
|
||||
'file': 'file'
|
||||
};
|
||||
|
||||
interface ContentOpts {
|
||||
// TODO: Found in bnote.ts, to check if it's actually used and not a typo.
|
||||
forceSave?: boolean;
|
||||
|
||||
/** will also save this BAttachment entity */
|
||||
forceFullSave?: boolean;
|
||||
/** override frontend heuristics on when to reload, instruct to reload */
|
||||
forceFrontendReload?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attachment represent data related/attached to the note. Conceptually similar to attributes, but intended for
|
||||
* larger amounts of data and generally not accessible to the user.
|
||||
*
|
||||
* @extends AbstractBeccaEntity
|
||||
*/
|
||||
class BAttachment extends AbstractBeccaEntity {
|
||||
class BAttachment extends AbstractBeccaEntity<BAttachment> {
|
||||
static get entityName() { return "attachments"; }
|
||||
static get primaryKeyName() { return "attachmentId"; }
|
||||
static get hashedProperties() { return ["attachmentId", "ownerId", "role", "mime", "title", "blobId", "utcDateScheduledForErasureSince"]; }
|
||||
|
||||
constructor(row) {
|
||||
noteId?: number;
|
||||
attachmentId?: string;
|
||||
/** either noteId or revisionId to which this attachment belongs */
|
||||
ownerId!: string;
|
||||
role!: string;
|
||||
mime!: string;
|
||||
title!: string;
|
||||
type?: keyof typeof attachmentRoleToNoteTypeMapping;
|
||||
position?: number;
|
||||
blobId?: string;
|
||||
isProtected?: boolean;
|
||||
dateModified?: string;
|
||||
utcDateScheduledForErasureSince?: string | null;
|
||||
/** optionally added to the entity */
|
||||
contentLength?: number;
|
||||
isDecrypted?: boolean;
|
||||
|
||||
constructor(row: AttachmentRow) {
|
||||
super();
|
||||
|
||||
this.updateFromRow(row);
|
||||
this.decrypt();
|
||||
}
|
||||
|
||||
updateFromRow(row: AttachmentRow): void {
|
||||
if (!row.ownerId?.trim()) {
|
||||
throw new Error("'ownerId' must be given to initialize a Attachment entity");
|
||||
} else if (!row.role?.trim()) {
|
||||
@@ -35,43 +69,21 @@ class BAttachment extends AbstractBeccaEntity {
|
||||
throw new Error("'title' must be given to initialize a Attachment entity");
|
||||
}
|
||||
|
||||
/** @type {string} */
|
||||
this.attachmentId = row.attachmentId;
|
||||
/**
|
||||
* either noteId or revisionId to which this attachment belongs
|
||||
* @type {string}
|
||||
*/
|
||||
this.ownerId = row.ownerId;
|
||||
/** @type {string} */
|
||||
this.role = row.role;
|
||||
/** @type {string} */
|
||||
this.mime = row.mime;
|
||||
/** @type {string} */
|
||||
this.title = row.title;
|
||||
/** @type {int} */
|
||||
this.position = row.position;
|
||||
/** @type {string} */
|
||||
this.blobId = row.blobId;
|
||||
/** @type {boolean} */
|
||||
this.isProtected = !!row.isProtected;
|
||||
/** @type {string} */
|
||||
this.dateModified = row.dateModified;
|
||||
/** @type {string} */
|
||||
this.utcDateModified = row.utcDateModified;
|
||||
/** @type {string} */
|
||||
this.utcDateScheduledForErasureSince = row.utcDateScheduledForErasureSince;
|
||||
|
||||
/**
|
||||
* optionally added to the entity
|
||||
* @type {int}
|
||||
*/
|
||||
this.contentLength = row.contentLength;
|
||||
|
||||
this.decrypt();
|
||||
}
|
||||
|
||||
/** @returns {BAttachment} */
|
||||
copy() {
|
||||
copy(): BAttachment {
|
||||
return new BAttachment({
|
||||
ownerId: this.ownerId,
|
||||
role: this.role,
|
||||
@@ -82,14 +94,13 @@ class BAttachment extends AbstractBeccaEntity {
|
||||
});
|
||||
}
|
||||
|
||||
/** @returns {BNote} */
|
||||
getNote() {
|
||||
getNote(): BNote {
|
||||
return this.becca.notes[this.ownerId];
|
||||
}
|
||||
|
||||
/** @returns {boolean} true if the note has string content (not binary) */
|
||||
hasStringContent() {
|
||||
return utils.isStringNote(this.type, this.mime);
|
||||
/** @returns true if the note has string content (not binary) */
|
||||
hasStringContent(): boolean {
|
||||
return this.type !== undefined && utils.isStringNote(this.type, this.mime);
|
||||
}
|
||||
|
||||
isContentAvailable() {
|
||||
@@ -110,33 +121,26 @@ class BAttachment extends AbstractBeccaEntity {
|
||||
|
||||
if (!this.isDecrypted && protectedSessionService.isProtectedSessionAvailable()) {
|
||||
try {
|
||||
this.title = protectedSessionService.decryptString(this.title);
|
||||
this.title = protectedSessionService.decryptString(this.title) || "";
|
||||
this.isDecrypted = true;
|
||||
}
|
||||
catch (e) {
|
||||
catch (e: any) {
|
||||
log.error(`Could not decrypt attachment ${this.attachmentId}: ${e.message} ${e.stack}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @returns {string|Buffer} */
|
||||
getContent() {
|
||||
return this._getContent();
|
||||
getContent(): Buffer {
|
||||
return this._getContent() as Buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param content
|
||||
* @param {object} [opts]
|
||||
* @param {object} [opts.forceSave=false] - will also save this BAttachment entity
|
||||
* @param {object} [opts.forceFrontendReload=false] - override frontend heuristics on when to reload, instruct to reload
|
||||
*/
|
||||
setContent(content, opts) {
|
||||
setContent(content: string | Buffer, opts?: ContentOpts) {
|
||||
this._setContent(content, opts);
|
||||
}
|
||||
|
||||
/** @returns {{note: BNote, branch: BBranch}} */
|
||||
convertToNote() {
|
||||
if (this.type === 'search') {
|
||||
convertToNote(): { note: BNote, branch: BBranch } {
|
||||
// TODO: can this ever be "search"?
|
||||
if (this.type as string === 'search') {
|
||||
throw new Error(`Note of type search cannot have child notes`);
|
||||
}
|
||||
|
||||
@@ -153,12 +157,12 @@ class BAttachment extends AbstractBeccaEntity {
|
||||
throw new Error(`Cannot convert protected attachment outside of protected session`);
|
||||
}
|
||||
|
||||
const noteService = require('../../services/notes.js');
|
||||
const noteService = require('../../services/notes');
|
||||
|
||||
const { note, branch } = noteService.createNewNote({
|
||||
parentNoteId: this.ownerId,
|
||||
title: this.title,
|
||||
type: attachmentRoleToNoteTypeMapping[this.role],
|
||||
type: (attachmentRoleToNoteTypeMapping as any)[this.role],
|
||||
mime: this.mime,
|
||||
content: this.getContent(),
|
||||
isProtected: this.isProtected
|
||||
@@ -170,6 +174,11 @@ class BAttachment extends AbstractBeccaEntity {
|
||||
|
||||
if (this.role === 'image' && parentNote.type === 'text') {
|
||||
const origContent = parentNote.getContent();
|
||||
|
||||
if (typeof origContent !== "string") {
|
||||
throw new Error(`Note with ID '${note.noteId} has a text type but non-string content.`);
|
||||
}
|
||||
|
||||
const oldAttachmentUrl = `api/attachments/${this.attachmentId}/image/`;
|
||||
const newNoteUrl = `api/images/${note.noteId}/`;
|
||||
|
||||
@@ -195,9 +204,9 @@ class BAttachment extends AbstractBeccaEntity {
|
||||
super.beforeSaving();
|
||||
|
||||
if (this.position === undefined || this.position === null) {
|
||||
this.position = 10 + sql.getValue(`SELECT COALESCE(MAX(position), 0)
|
||||
FROM attachments
|
||||
WHERE ownerId = ?`, [this.noteId]);
|
||||
this.position = 10 + sql.getValue<number>(`SELECT COALESCE(MAX(position), 0)
|
||||
FROM attachments
|
||||
WHERE ownerId = ?`, [this.noteId]);
|
||||
}
|
||||
|
||||
this.dateModified = dateUtils.localNowDateTime();
|
||||
@@ -210,7 +219,7 @@ class BAttachment extends AbstractBeccaEntity {
|
||||
ownerId: this.ownerId,
|
||||
role: this.role,
|
||||
mime: this.mime,
|
||||
title: this.title,
|
||||
title: this.title || undefined,
|
||||
position: this.position,
|
||||
blobId: this.blobId,
|
||||
isProtected: !!this.isProtected,
|
||||
@@ -228,7 +237,7 @@ class BAttachment extends AbstractBeccaEntity {
|
||||
|
||||
if (pojo.isProtected) {
|
||||
if (this.isDecrypted) {
|
||||
pojo.title = protectedSessionService.encrypt(pojo.title);
|
||||
pojo.title = protectedSessionService.encrypt(pojo.title || "") || undefined;
|
||||
}
|
||||
else {
|
||||
// updating protected note outside of protected session means we will keep original ciphertexts
|
||||
@@ -240,4 +249,4 @@ class BAttachment extends AbstractBeccaEntity {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = BAttachment;
|
||||
export = BAttachment;
|
||||
@@ -1,30 +1,34 @@
|
||||
"use strict";
|
||||
|
||||
const BNote = require('./bnote.js');
|
||||
const AbstractBeccaEntity = require('./abstract_becca_entity.js');
|
||||
const sql = require('../../services/sql.js');
|
||||
const dateUtils = require('../../services/date_utils.js');
|
||||
const promotedAttributeDefinitionParser = require('../../services/promoted_attribute_definition_parser.js');
|
||||
const {sanitizeAttributeName} = require('../../services/sanitize_attribute_name.js');
|
||||
import BNote = require('./bnote');
|
||||
import AbstractBeccaEntity = require('./abstract_becca_entity');
|
||||
import dateUtils = require('../../services/date_utils');
|
||||
import promotedAttributeDefinitionParser = require('../../services/promoted_attribute_definition_parser');
|
||||
import sanitizeAttributeName = require('../../services/sanitize_attribute_name');
|
||||
import { AttributeRow, AttributeType } from './rows';
|
||||
|
||||
|
||||
/**
|
||||
* There are currently only two types of attributes, labels or relations.
|
||||
* @typedef {"label" | "relation"} AttributeType
|
||||
*/
|
||||
interface SavingOpts {
|
||||
skipValidation?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attribute is an abstract concept which has two real uses - label (key - value pair)
|
||||
* and relation (representing named relationship between source and target note)
|
||||
*
|
||||
* @extends AbstractBeccaEntity
|
||||
*/
|
||||
class BAttribute extends AbstractBeccaEntity {
|
||||
class BAttribute extends AbstractBeccaEntity<BAttribute> {
|
||||
static get entityName() { return "attributes"; }
|
||||
static get primaryKeyName() { return "attributeId"; }
|
||||
static get hashedProperties() { return ["attributeId", "noteId", "type", "name", "value", "isInheritable"]; }
|
||||
|
||||
constructor(row) {
|
||||
attributeId!: string;
|
||||
noteId!: string;
|
||||
type!: AttributeType;
|
||||
name!: string;
|
||||
position!: number;
|
||||
value!: string;
|
||||
isInheritable!: boolean;
|
||||
|
||||
constructor(row?: AttributeRow) {
|
||||
super();
|
||||
|
||||
if (!row) {
|
||||
@@ -35,7 +39,7 @@ class BAttribute extends AbstractBeccaEntity {
|
||||
this.init();
|
||||
}
|
||||
|
||||
updateFromRow(row) {
|
||||
updateFromRow(row: AttributeRow) {
|
||||
this.update([
|
||||
row.attributeId,
|
||||
row.noteId,
|
||||
@@ -48,22 +52,14 @@ class BAttribute extends AbstractBeccaEntity {
|
||||
]);
|
||||
}
|
||||
|
||||
update([attributeId, noteId, type, name, value, isInheritable, position, utcDateModified]) {
|
||||
/** @type {string} */
|
||||
update([attributeId, noteId, type, name, value, isInheritable, position, utcDateModified]: any) {
|
||||
this.attributeId = attributeId;
|
||||
/** @type {string} */
|
||||
this.noteId = noteId;
|
||||
/** @type {AttributeType} */
|
||||
this.type = type;
|
||||
/** @type {string} */
|
||||
this.name = name;
|
||||
/** @type {int} */
|
||||
this.position = position;
|
||||
/** @type {string} */
|
||||
this.value = value || "";
|
||||
/** @type {boolean} */
|
||||
this.isInheritable = !!isInheritable;
|
||||
/** @type {string} */
|
||||
this.utcDateModified = utcDateModified;
|
||||
|
||||
return this;
|
||||
@@ -129,9 +125,6 @@ class BAttribute extends AbstractBeccaEntity {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {BNote|null}
|
||||
*/
|
||||
getNote() {
|
||||
const note = this.becca.getNote(this.noteId);
|
||||
|
||||
@@ -142,9 +135,6 @@ class BAttribute extends AbstractBeccaEntity {
|
||||
return note;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {BNote|null}
|
||||
*/
|
||||
getTargetNote() {
|
||||
if (this.type !== 'relation') {
|
||||
throw new Error(`Attribute '${this.attributeId}' is not a relation.`);
|
||||
@@ -157,9 +147,6 @@ class BAttribute extends AbstractBeccaEntity {
|
||||
return this.becca.getNote(this.value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isDefinition() {
|
||||
return this.type === 'label' && (this.name.startsWith('label:') || this.name.startsWith('relation:'));
|
||||
}
|
||||
@@ -182,12 +169,12 @@ class BAttribute extends AbstractBeccaEntity {
|
||||
return !(this.attributeId in this.becca.attributes);
|
||||
}
|
||||
|
||||
beforeSaving(opts = {}) {
|
||||
beforeSaving(opts: SavingOpts = {}) {
|
||||
if (!opts.skipValidation) {
|
||||
this.validate();
|
||||
}
|
||||
|
||||
this.name = sanitizeAttributeName(this.name);
|
||||
this.name = sanitizeAttributeName.sanitizeAttributeName(this.name);
|
||||
|
||||
if (!this.value) {
|
||||
// null value isn't allowed
|
||||
@@ -226,7 +213,7 @@ class BAttribute extends AbstractBeccaEntity {
|
||||
};
|
||||
}
|
||||
|
||||
createClone(type, name, value, isInheritable) {
|
||||
createClone(type: AttributeType, name: string, value: string, isInheritable?: boolean) {
|
||||
return new BAttribute({
|
||||
noteId: this.noteId,
|
||||
type: type,
|
||||
@@ -239,4 +226,4 @@ class BAttribute extends AbstractBeccaEntity {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = BAttribute;
|
||||
export = BAttribute;
|
||||
@@ -1,25 +1,35 @@
|
||||
class BBlob {
|
||||
import AbstractBeccaEntity = require("./abstract_becca_entity");
|
||||
import { BlobRow } from "./rows";
|
||||
|
||||
// TODO: Why this does not extend the abstract becca?
|
||||
class BBlob extends AbstractBeccaEntity<BBlob> {
|
||||
static get entityName() { return "blobs"; }
|
||||
static get primaryKeyName() { return "blobId"; }
|
||||
static get hashedProperties() { return ["blobId", "content"]; }
|
||||
|
||||
constructor(row) {
|
||||
/** @type {string} */
|
||||
blobId!: string;
|
||||
content!: string | Buffer;
|
||||
contentLength!: number;
|
||||
dateModified!: string;
|
||||
utcDateModified!: string;
|
||||
|
||||
constructor(row: BlobRow) {
|
||||
super();
|
||||
this.updateFromRow(row);
|
||||
}
|
||||
|
||||
updateFromRow(row: BlobRow): void {
|
||||
this.blobId = row.blobId;
|
||||
/** @type {string|Buffer} */
|
||||
this.content = row.content;
|
||||
/** @type {int} */
|
||||
this.contentLength = row.contentLength;
|
||||
/** @type {string} */
|
||||
this.dateModified = row.dateModified;
|
||||
/** @type {string} */
|
||||
this.utcDateModified = row.utcDateModified;
|
||||
}
|
||||
|
||||
getPojo() {
|
||||
return {
|
||||
blobId: this.blobId,
|
||||
content: this.content,
|
||||
content: this.content || null,
|
||||
contentLength: this.contentLength,
|
||||
dateModified: this.dateModified,
|
||||
utcDateModified: this.utcDateModified
|
||||
@@ -27,4 +37,4 @@ class BBlob {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = BBlob;
|
||||
export = BBlob;
|
||||
@@ -1,12 +1,13 @@
|
||||
"use strict";
|
||||
|
||||
const BNote = require('./bnote.js');
|
||||
const AbstractBeccaEntity = require('./abstract_becca_entity.js');
|
||||
const dateUtils = require('../../services/date_utils.js');
|
||||
const utils = require('../../services/utils.js');
|
||||
const TaskContext = require('../../services/task_context.js');
|
||||
const cls = require('../../services/cls.js');
|
||||
const log = require('../../services/log.js');
|
||||
import BNote = require('./bnote');
|
||||
import AbstractBeccaEntity = require('./abstract_becca_entity');
|
||||
import dateUtils = require('../../services/date_utils');
|
||||
import utils = require('../../services/utils');
|
||||
import TaskContext = require('../../services/task_context');
|
||||
import cls = require('../../services/cls');
|
||||
import log = require('../../services/log');
|
||||
import { BranchRow } from './rows';
|
||||
|
||||
/**
|
||||
* Branch represents a relationship between a child note and its parent note. Trilium allows a note to have multiple
|
||||
@@ -14,16 +15,22 @@ const log = require('../../services/log.js');
|
||||
*
|
||||
* Note that you should not rely on the branch's identity, since it can change easily with a note's move.
|
||||
* Always check noteId instead.
|
||||
*
|
||||
* @extends AbstractBeccaEntity
|
||||
*/
|
||||
class BBranch extends AbstractBeccaEntity {
|
||||
class BBranch extends AbstractBeccaEntity<BBranch> {
|
||||
static get entityName() { return "branches"; }
|
||||
static get primaryKeyName() { return "branchId"; }
|
||||
// notePosition is not part of hash because it would produce a lot of updates in case of reordering
|
||||
static get hashedProperties() { return ["branchId", "noteId", "parentNoteId", "prefix"]; }
|
||||
|
||||
constructor(row) {
|
||||
branchId?: string;
|
||||
noteId!: string;
|
||||
parentNoteId!: string;
|
||||
prefix!: string | null;
|
||||
notePosition!: number;
|
||||
isExpanded!: boolean;
|
||||
utcDateModified?: string;
|
||||
|
||||
constructor(row?: BranchRow) {
|
||||
super();
|
||||
|
||||
if (!row) {
|
||||
@@ -34,7 +41,7 @@ class BBranch extends AbstractBeccaEntity {
|
||||
this.init();
|
||||
}
|
||||
|
||||
updateFromRow(row) {
|
||||
updateFromRow(row: BranchRow) {
|
||||
this.update([
|
||||
row.branchId,
|
||||
row.noteId,
|
||||
@@ -46,20 +53,13 @@ class BBranch extends AbstractBeccaEntity {
|
||||
]);
|
||||
}
|
||||
|
||||
update([branchId, noteId, parentNoteId, prefix, notePosition, isExpanded, utcDateModified]) {
|
||||
/** @type {string} */
|
||||
update([branchId, noteId, parentNoteId, prefix, notePosition, isExpanded, utcDateModified]: any) {
|
||||
this.branchId = branchId;
|
||||
/** @type {string} */
|
||||
this.noteId = noteId;
|
||||
/** @type {string} */
|
||||
this.parentNoteId = parentNoteId;
|
||||
/** @type {string|null} */
|
||||
this.prefix = prefix;
|
||||
/** @type {int} */
|
||||
this.notePosition = notePosition;
|
||||
/** @type {boolean} */
|
||||
this.isExpanded = !!isExpanded;
|
||||
/** @type {string} */
|
||||
this.utcDateModified = utcDateModified;
|
||||
|
||||
return this;
|
||||
@@ -83,18 +83,18 @@ class BBranch extends AbstractBeccaEntity {
|
||||
}
|
||||
|
||||
const parentNote = this.parentNote;
|
||||
|
||||
if (!childNote.parents.includes(parentNote)) {
|
||||
childNote.parents.push(parentNote);
|
||||
}
|
||||
|
||||
if (!parentNote.children.includes(childNote)) {
|
||||
parentNote.children.push(childNote);
|
||||
if (parentNote) {
|
||||
if (!childNote.parents.includes(parentNote)) {
|
||||
childNote.parents.push(parentNote);
|
||||
}
|
||||
|
||||
if (!parentNote.children.includes(childNote)) {
|
||||
parentNote.children.push(childNote);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @returns {BNote} */
|
||||
get childNote() {
|
||||
get childNote(): BNote {
|
||||
if (!(this.noteId in this.becca.notes)) {
|
||||
// entities can come out of order in sync/import, create skeleton which will be filled later
|
||||
this.becca.addNote(this.noteId, new BNote({noteId: this.noteId}));
|
||||
@@ -103,13 +103,12 @@ class BBranch extends AbstractBeccaEntity {
|
||||
return this.becca.notes[this.noteId];
|
||||
}
|
||||
|
||||
/** @returns {BNote} */
|
||||
getNote() {
|
||||
getNote(): BNote {
|
||||
return this.childNote;
|
||||
}
|
||||
|
||||
/** @returns {BNote|undefined} - root branch will have undefined parent, all other branches have to have a parent note */
|
||||
get parentNote() {
|
||||
/** @returns root branch will have undefined parent, all other branches have to have a parent note */
|
||||
get parentNote(): BNote | undefined {
|
||||
if (!(this.parentNoteId in this.becca.notes) && this.parentNoteId !== 'none') {
|
||||
// entities can come out of order in sync/import, create skeleton which will be filled later
|
||||
this.becca.addNote(this.parentNoteId, new BNote({noteId: this.parentNoteId}));
|
||||
@@ -119,7 +118,7 @@ class BBranch extends AbstractBeccaEntity {
|
||||
}
|
||||
|
||||
get isDeleted() {
|
||||
return !(this.branchId in this.becca.branches);
|
||||
return (this.branchId == undefined || !(this.branchId in this.becca.branches));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -128,8 +127,6 @@ class BBranch extends AbstractBeccaEntity {
|
||||
* An example is shared or bookmarked clones - they are created automatically and exist for technical reasons,
|
||||
* not as user-intended actions. From user perspective, they don't count as real clones and for the purpose
|
||||
* of deletion should not act as a clone.
|
||||
*
|
||||
* @returns {boolean}
|
||||
*/
|
||||
get isWeak() {
|
||||
return ['_share', '_lbBookmarks'].includes(this.parentNoteId);
|
||||
@@ -138,12 +135,11 @@ class BBranch extends AbstractBeccaEntity {
|
||||
/**
|
||||
* Delete a branch. If this is a last note's branch, delete the note as well.
|
||||
*
|
||||
* @param {string} [deleteId] - optional delete identified
|
||||
* @param {TaskContext} [taskContext]
|
||||
* @param deleteId - optional delete identified
|
||||
*
|
||||
* @returns {boolean} - true if note has been deleted, false otherwise
|
||||
* @returns true if note has been deleted, false otherwise
|
||||
*/
|
||||
deleteBranch(deleteId, taskContext) {
|
||||
deleteBranch(deleteId?: string, taskContext?: TaskContext): boolean {
|
||||
if (!deleteId) {
|
||||
deleteId = utils.randomString(10);
|
||||
}
|
||||
@@ -161,7 +157,7 @@ class BBranch extends AbstractBeccaEntity {
|
||||
|
||||
if (parentBranches.length === 1 && parentBranches[0] === this) {
|
||||
// needs to be run before branches and attributes are deleted and thus attached relations disappear
|
||||
const handlers = require('../../services/handlers.js');
|
||||
const handlers = require('../../services/handlers');
|
||||
handlers.runAttachedRelations(note, 'runOnNoteDeletion', note);
|
||||
}
|
||||
}
|
||||
@@ -182,7 +178,9 @@ class BBranch extends AbstractBeccaEntity {
|
||||
}
|
||||
|
||||
for (const childBranch of note.getChildBranches()) {
|
||||
childBranch.deleteBranch(deleteId, taskContext);
|
||||
if (childBranch) {
|
||||
childBranch.deleteBranch(deleteId, taskContext);
|
||||
}
|
||||
}
|
||||
|
||||
// first delete children and then parent - this will show up better in recent changes
|
||||
@@ -222,11 +220,17 @@ class BBranch extends AbstractBeccaEntity {
|
||||
if (this.notePosition === undefined || this.notePosition === null) {
|
||||
let maxNotePos = 0;
|
||||
|
||||
for (const childBranch of this.parentNote.getChildBranches()) {
|
||||
if (maxNotePos < childBranch.notePosition
|
||||
&& childBranch.noteId !== '_hidden' // hidden has a very large notePosition to always stay last
|
||||
) {
|
||||
maxNotePos = childBranch.notePosition;
|
||||
if (this.parentNote) {
|
||||
for (const childBranch of this.parentNote.getChildBranches()) {
|
||||
if (!childBranch) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (maxNotePos < childBranch.notePosition
|
||||
&& childBranch.noteId !== '_hidden' // hidden has a very large notePosition to always stay last
|
||||
) {
|
||||
maxNotePos = childBranch.notePosition;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -261,17 +265,19 @@ class BBranch extends AbstractBeccaEntity {
|
||||
};
|
||||
}
|
||||
|
||||
createClone(parentNoteId, notePosition) {
|
||||
createClone(parentNoteId: string, notePosition?: number) {
|
||||
const existingBranch = this.becca.getBranchFromChildAndParent(this.noteId, parentNoteId);
|
||||
|
||||
if (existingBranch) {
|
||||
existingBranch.notePosition = notePosition;
|
||||
if (notePosition) {
|
||||
existingBranch.notePosition = notePosition;
|
||||
}
|
||||
return existingBranch;
|
||||
} else {
|
||||
return new BBranch({
|
||||
noteId: this.noteId,
|
||||
parentNoteId: parentNoteId,
|
||||
notePosition: notePosition,
|
||||
notePosition: notePosition || null,
|
||||
prefix: this.prefix,
|
||||
isExpanded: this.isExpanded
|
||||
});
|
||||
@@ -279,4 +285,4 @@ class BBranch extends AbstractBeccaEntity {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = BBranch;
|
||||
export = BBranch;
|
||||
@@ -1,7 +1,9 @@
|
||||
"use strict";
|
||||
|
||||
const dateUtils = require('../../services/date_utils.js');
|
||||
const AbstractBeccaEntity = require('./abstract_becca_entity.js');
|
||||
import { EtapiTokenRow } from "./rows";
|
||||
|
||||
import dateUtils = require('../../services/date_utils');
|
||||
import AbstractBeccaEntity = require('./abstract_becca_entity');
|
||||
|
||||
/**
|
||||
* EtapiToken is an entity representing token used to authenticate against Trilium REST API from client applications.
|
||||
@@ -11,15 +13,18 @@ const AbstractBeccaEntity = require('./abstract_becca_entity.js');
|
||||
*
|
||||
* The format user is presented with is "<etapiTokenId>_<tokenHash>". This is also called "authToken" to distinguish it
|
||||
* from tokenHash and token.
|
||||
*
|
||||
* @extends AbstractBeccaEntity
|
||||
*/
|
||||
class BEtapiToken extends AbstractBeccaEntity {
|
||||
class BEtapiToken extends AbstractBeccaEntity<BEtapiToken> {
|
||||
static get entityName() { return "etapi_tokens"; }
|
||||
static get primaryKeyName() { return "etapiTokenId"; }
|
||||
static get hashedProperties() { return ["etapiTokenId", "name", "tokenHash", "utcDateCreated", "utcDateModified", "isDeleted"]; }
|
||||
|
||||
constructor(row) {
|
||||
etapiTokenId?: string;
|
||||
name!: string;
|
||||
tokenHash!: string;
|
||||
private _isDeleted?: boolean;
|
||||
|
||||
constructor(row?: EtapiTokenRow) {
|
||||
super();
|
||||
|
||||
if (!row) {
|
||||
@@ -30,19 +35,17 @@ class BEtapiToken extends AbstractBeccaEntity {
|
||||
this.init();
|
||||
}
|
||||
|
||||
updateFromRow(row) {
|
||||
/** @type {string} */
|
||||
get isDeleted() {
|
||||
return !!this._isDeleted;
|
||||
}
|
||||
|
||||
updateFromRow(row: EtapiTokenRow) {
|
||||
this.etapiTokenId = row.etapiTokenId;
|
||||
/** @type {string} */
|
||||
this.name = row.name;
|
||||
/** @type {string} */
|
||||
this.tokenHash = row.tokenHash;
|
||||
/** @type {string} */
|
||||
this.utcDateCreated = row.utcDateCreated || dateUtils.utcNowDateTime();
|
||||
/** @type {string} */
|
||||
this.utcDateModified = row.utcDateModified || this.utcDateCreated;
|
||||
/** @type {boolean} */
|
||||
this.isDeleted = !!row.isDeleted;
|
||||
this._isDeleted = !!row.isDeleted;
|
||||
|
||||
if (this.etapiTokenId) {
|
||||
this.becca.etapiTokens[this.etapiTokenId] = this;
|
||||
@@ -71,8 +74,10 @@ class BEtapiToken extends AbstractBeccaEntity {
|
||||
|
||||
super.beforeSaving();
|
||||
|
||||
this.becca.etapiTokens[this.etapiTokenId] = this;
|
||||
if (this.etapiTokenId) {
|
||||
this.becca.etapiTokens[this.etapiTokenId] = this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = BEtapiToken;
|
||||
export = BEtapiToken;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,33 +1,34 @@
|
||||
"use strict";
|
||||
|
||||
const dateUtils = require('../../services/date_utils.js');
|
||||
const AbstractBeccaEntity = require('./abstract_becca_entity.js');
|
||||
import dateUtils = require('../../services/date_utils');
|
||||
import AbstractBeccaEntity = require('./abstract_becca_entity');
|
||||
import { OptionRow } from './rows';
|
||||
|
||||
/**
|
||||
* Option represents a name-value pair, either directly configurable by the user or some system property.
|
||||
*
|
||||
* @extends AbstractBeccaEntity
|
||||
*/
|
||||
class BOption extends AbstractBeccaEntity {
|
||||
class BOption extends AbstractBeccaEntity<BOption> {
|
||||
static get entityName() { return "options"; }
|
||||
static get primaryKeyName() { return "name"; }
|
||||
static get hashedProperties() { return ["name", "value"]; }
|
||||
|
||||
constructor(row) {
|
||||
name!: string;
|
||||
value!: string;
|
||||
isSynced!: boolean;
|
||||
|
||||
constructor(row?: OptionRow) {
|
||||
super();
|
||||
|
||||
this.updateFromRow(row);
|
||||
if (row) {
|
||||
this.updateFromRow(row);
|
||||
}
|
||||
this.becca.options[this.name] = this;
|
||||
}
|
||||
|
||||
updateFromRow(row) {
|
||||
/** @type {string} */
|
||||
updateFromRow(row: OptionRow) {
|
||||
this.name = row.name;
|
||||
/** @type {string} */
|
||||
this.value = row.value;
|
||||
/** @type {boolean} */
|
||||
this.isSynced = !!row.isSynced;
|
||||
/** @type {string} */
|
||||
this.utcDateModified = row.utcDateModified;
|
||||
}
|
||||
|
||||
@@ -47,4 +48,4 @@ class BOption extends AbstractBeccaEntity {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = BOption;
|
||||
export = BOption;
|
||||
@@ -1,25 +1,31 @@
|
||||
"use strict";
|
||||
|
||||
const dateUtils = require('../../services/date_utils.js');
|
||||
const AbstractBeccaEntity = require('./abstract_becca_entity.js');
|
||||
import { RecentNoteRow } from "./rows";
|
||||
|
||||
import dateUtils = require('../../services/date_utils');
|
||||
import AbstractBeccaEntity = require('./abstract_becca_entity');
|
||||
|
||||
/**
|
||||
* RecentNote represents recently visited note.
|
||||
*
|
||||
* @extends AbstractBeccaEntity
|
||||
*/
|
||||
class BRecentNote extends AbstractBeccaEntity {
|
||||
class BRecentNote extends AbstractBeccaEntity<BRecentNote> {
|
||||
static get entityName() { return "recent_notes"; }
|
||||
static get primaryKeyName() { return "noteId"; }
|
||||
static get hashedProperties() { return ["noteId", "notePath"]; }
|
||||
|
||||
constructor(row) {
|
||||
noteId!: string;
|
||||
notePath!: string;
|
||||
utcDateCreated!: string;
|
||||
|
||||
constructor(row: RecentNoteRow) {
|
||||
super();
|
||||
|
||||
/** @type {string} */
|
||||
this.updateFromRow(row);
|
||||
}
|
||||
|
||||
updateFromRow(row: RecentNoteRow): void {
|
||||
this.noteId = row.noteId;
|
||||
/** @type {string} */
|
||||
this.notePath = row.notePath;
|
||||
/** @type {string} */
|
||||
this.utcDateCreated = row.utcDateCreated || dateUtils.utcNowDateTime();
|
||||
}
|
||||
|
||||
@@ -32,4 +38,4 @@ class BRecentNote extends AbstractBeccaEntity {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = BRecentNote;
|
||||
export = BRecentNote;
|
||||
@@ -1,68 +1,79 @@
|
||||
"use strict";
|
||||
|
||||
const protectedSessionService = require('../../services/protected_session.js');
|
||||
const utils = require('../../services/utils.js');
|
||||
const dateUtils = require('../../services/date_utils.js');
|
||||
const becca = require('../becca.js');
|
||||
const AbstractBeccaEntity = require('./abstract_becca_entity.js');
|
||||
const sql = require('../../services/sql.js');
|
||||
const BAttachment = require('./battachment.js');
|
||||
import protectedSessionService = require('../../services/protected_session');
|
||||
import utils = require('../../services/utils');
|
||||
import dateUtils = require('../../services/date_utils');
|
||||
import becca = require('../becca');
|
||||
import AbstractBeccaEntity = require('./abstract_becca_entity');
|
||||
import sql = require('../../services/sql');
|
||||
import BAttachment = require('./battachment');
|
||||
import { AttachmentRow, RevisionRow } from './rows';
|
||||
|
||||
interface ContentOpts {
|
||||
/** will also save this BRevision entity */
|
||||
forceSave?: boolean;
|
||||
}
|
||||
|
||||
interface GetByIdOpts {
|
||||
includeContentLength?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Revision represents a snapshot of note's title and content at some point in the past.
|
||||
* It's used for seamless note versioning.
|
||||
*
|
||||
* @extends AbstractBeccaEntity
|
||||
*/
|
||||
class BRevision extends AbstractBeccaEntity {
|
||||
class BRevision extends AbstractBeccaEntity<BRevision> {
|
||||
static get entityName() { return "revisions"; }
|
||||
static get primaryKeyName() { return "revisionId"; }
|
||||
static get hashedProperties() { return ["revisionId", "noteId", "title", "isProtected", "dateLastEdited", "dateCreated",
|
||||
"utcDateLastEdited", "utcDateCreated", "utcDateModified", "blobId"]; }
|
||||
|
||||
constructor(row, titleDecrypted = false) {
|
||||
revisionId?: string;
|
||||
noteId!: string;
|
||||
type!: string;
|
||||
mime!: string;
|
||||
isProtected!: boolean;
|
||||
title!: string;
|
||||
blobId?: string;
|
||||
dateLastEdited?: string;
|
||||
dateCreated!: string;
|
||||
utcDateLastEdited?: string;
|
||||
utcDateCreated!: string;
|
||||
contentLength?: number;
|
||||
content?: string | Buffer;
|
||||
|
||||
constructor(row: RevisionRow, titleDecrypted = false) {
|
||||
super();
|
||||
|
||||
/** @type {string} */
|
||||
this.revisionId = row.revisionId;
|
||||
/** @type {string} */
|
||||
this.noteId = row.noteId;
|
||||
/** @type {string} */
|
||||
this.type = row.type;
|
||||
/** @type {string} */
|
||||
this.mime = row.mime;
|
||||
/** @type {boolean} */
|
||||
this.isProtected = !!row.isProtected;
|
||||
/** @type {string} */
|
||||
this.title = row.title;
|
||||
/** @type {string} */
|
||||
this.blobId = row.blobId;
|
||||
/** @type {string} */
|
||||
this.dateLastEdited = row.dateLastEdited;
|
||||
/** @type {string} */
|
||||
this.dateCreated = row.dateCreated;
|
||||
/** @type {string} */
|
||||
this.utcDateLastEdited = row.utcDateLastEdited;
|
||||
/** @type {string} */
|
||||
this.utcDateCreated = row.utcDateCreated;
|
||||
/** @type {string} */
|
||||
this.utcDateModified = row.utcDateModified;
|
||||
/** @type {int} */
|
||||
this.contentLength = row.contentLength;
|
||||
|
||||
this.updateFromRow(row);
|
||||
if (this.isProtected && !titleDecrypted) {
|
||||
this.title = protectedSessionService.isProtectedSessionAvailable()
|
||||
? protectedSessionService.decryptString(this.title)
|
||||
: "[protected]";
|
||||
const decryptedTitle = protectedSessionService.isProtectedSessionAvailable() ? protectedSessionService.decryptString(this.title) : null;
|
||||
this.title = decryptedTitle || "[protected]";
|
||||
}
|
||||
}
|
||||
|
||||
updateFromRow(row: RevisionRow) {
|
||||
this.revisionId = row.revisionId;
|
||||
this.noteId = row.noteId;
|
||||
this.type = row.type;
|
||||
this.mime = row.mime;
|
||||
this.isProtected = !!row.isProtected;
|
||||
this.title = row.title;
|
||||
this.blobId = row.blobId;
|
||||
this.dateLastEdited = row.dateLastEdited;
|
||||
this.dateCreated = row.dateCreated;
|
||||
this.utcDateLastEdited = row.utcDateLastEdited;
|
||||
this.utcDateCreated = row.utcDateCreated;
|
||||
this.utcDateModified = row.utcDateModified;
|
||||
this.contentLength = row.contentLength;
|
||||
}
|
||||
|
||||
getNote() {
|
||||
return becca.notes[this.noteId];
|
||||
}
|
||||
|
||||
/** @returns {boolean} true if the note has string content (not binary) */
|
||||
hasStringContent() {
|
||||
/** @returns true if the note has string content (not binary) */
|
||||
hasStringContent(): boolean {
|
||||
return utils.isStringNote(this.type, this.mime);
|
||||
}
|
||||
|
||||
@@ -80,27 +91,24 @@ class BRevision extends AbstractBeccaEntity {
|
||||
*
|
||||
* This is the same approach as is used for Note's content.
|
||||
*/
|
||||
|
||||
/** @returns {string|Buffer} */
|
||||
getContent() {
|
||||
getContent(): string | Buffer {
|
||||
return this._getContent();
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {*}
|
||||
* @throws Error in case of invalid JSON */
|
||||
getJsonContent() {
|
||||
getJsonContent(): {} | null {
|
||||
const content = this.getContent();
|
||||
|
||||
if (!content || !content.trim()) {
|
||||
if (!content || typeof content !== "string" || !content.trim()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return JSON.parse(content);
|
||||
}
|
||||
|
||||
/** @returns {*|null} valid object or null if the content cannot be parsed as JSON */
|
||||
getJsonContentSafely() {
|
||||
/** @returns valid object or null if the content cannot be parsed as JSON */
|
||||
getJsonContentSafely(): {} | null {
|
||||
try {
|
||||
return this.getJsonContent();
|
||||
}
|
||||
@@ -109,18 +117,12 @@ class BRevision extends AbstractBeccaEntity {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param content
|
||||
* @param {object} [opts]
|
||||
* @param {object} [opts.forceSave=false] - will also save this BRevision entity
|
||||
*/
|
||||
setContent(content, opts) {
|
||||
setContent(content: string | Buffer, opts: ContentOpts = {}) {
|
||||
this._setContent(content, opts);
|
||||
}
|
||||
|
||||
/** @returns {BAttachment[]} */
|
||||
getAttachments() {
|
||||
return sql.getRows(`
|
||||
getAttachments(): BAttachment[] {
|
||||
return sql.getRows<AttachmentRow>(`
|
||||
SELECT attachments.*
|
||||
FROM attachments
|
||||
WHERE ownerId = ?
|
||||
@@ -128,8 +130,7 @@ class BRevision extends AbstractBeccaEntity {
|
||||
.map(row => new BAttachment(row));
|
||||
}
|
||||
|
||||
/** @returns {BAttachment|null} */
|
||||
getAttachmentById(attachmentId, opts = {}) {
|
||||
getAttachmentById(attachmentId: String, opts: GetByIdOpts = {}): BAttachment | null {
|
||||
opts.includeContentLength = !!opts.includeContentLength;
|
||||
|
||||
const query = opts.includeContentLength
|
||||
@@ -139,13 +140,12 @@ class BRevision extends AbstractBeccaEntity {
|
||||
WHERE ownerId = ? AND attachmentId = ? AND isDeleted = 0`
|
||||
: `SELECT * FROM attachments WHERE ownerId = ? AND attachmentId = ? AND isDeleted = 0`;
|
||||
|
||||
return sql.getRows(query, [this.revisionId, attachmentId])
|
||||
return sql.getRows<AttachmentRow>(query, [this.revisionId, attachmentId])
|
||||
.map(row => new BAttachment(row))[0];
|
||||
}
|
||||
|
||||
/** @returns {BAttachment[]} */
|
||||
getAttachmentsByRole(role) {
|
||||
return sql.getRows(`
|
||||
getAttachmentsByRole(role: string): BAttachment[] {
|
||||
return sql.getRows<AttachmentRow>(`
|
||||
SELECT attachments.*
|
||||
FROM attachments
|
||||
WHERE ownerId = ?
|
||||
@@ -155,8 +155,7 @@ class BRevision extends AbstractBeccaEntity {
|
||||
.map(row => new BAttachment(row));
|
||||
}
|
||||
|
||||
/** @returns {BAttachment} */
|
||||
getAttachmentByTitle(title) {
|
||||
getAttachmentByTitle(title: string): BAttachment {
|
||||
// cannot use SQL to filter by title since it can be encrypted
|
||||
return this.getAttachments().filter(attachment => attachment.title === title)[0];
|
||||
}
|
||||
@@ -181,7 +180,7 @@ class BRevision extends AbstractBeccaEntity {
|
||||
type: this.type,
|
||||
mime: this.mime,
|
||||
isProtected: this.isProtected,
|
||||
title: this.title,
|
||||
title: this.title || undefined,
|
||||
blobId: this.blobId,
|
||||
dateLastEdited: this.dateLastEdited,
|
||||
dateCreated: this.dateCreated,
|
||||
@@ -200,7 +199,7 @@ class BRevision extends AbstractBeccaEntity {
|
||||
|
||||
if (pojo.isProtected) {
|
||||
if (protectedSessionService.isProtectedSessionAvailable()) {
|
||||
pojo.title = protectedSessionService.encrypt(this.title);
|
||||
pojo.title = protectedSessionService.encrypt(this.title) || undefined;
|
||||
}
|
||||
else {
|
||||
// updating protected note outside of protected session means we will keep original ciphertexts
|
||||
@@ -212,4 +211,4 @@ class BRevision extends AbstractBeccaEntity {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = BRevision;
|
||||
export = BRevision;
|
||||
111
src/becca/entities/rows.ts
Normal file
111
src/becca/entities/rows.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
// TODO: Booleans should probably be numbers instead (as SQLite does not have booleans.);
|
||||
|
||||
export interface AttachmentRow {
|
||||
attachmentId?: string;
|
||||
ownerId?: string;
|
||||
role: string;
|
||||
mime: string;
|
||||
title: string;
|
||||
position?: number;
|
||||
blobId?: string;
|
||||
isProtected?: boolean;
|
||||
dateModified?: string;
|
||||
utcDateModified?: string;
|
||||
utcDateScheduledForErasureSince?: string;
|
||||
contentLength?: number;
|
||||
content?: Buffer | string;
|
||||
}
|
||||
|
||||
export interface RevisionRow {
|
||||
revisionId?: string;
|
||||
noteId: string;
|
||||
type: string;
|
||||
mime: string;
|
||||
isProtected?: boolean;
|
||||
title: string;
|
||||
blobId?: string;
|
||||
dateLastEdited?: string;
|
||||
dateCreated: string;
|
||||
utcDateLastEdited?: string;
|
||||
utcDateCreated: string;
|
||||
utcDateModified: string;
|
||||
contentLength?: number;
|
||||
}
|
||||
|
||||
export interface RecentNoteRow {
|
||||
noteId: string;
|
||||
notePath: string;
|
||||
utcDateCreated?: string;
|
||||
}
|
||||
|
||||
export interface OptionRow {
|
||||
name: string;
|
||||
value: string;
|
||||
isSynced: boolean;
|
||||
utcDateModified: string;
|
||||
}
|
||||
|
||||
export interface EtapiTokenRow {
|
||||
etapiTokenId?: string;
|
||||
name: string;
|
||||
tokenHash: string;
|
||||
utcDateCreated?: string;
|
||||
utcDateModified?: string;
|
||||
isDeleted?: boolean;
|
||||
}
|
||||
|
||||
export interface BlobRow {
|
||||
blobId: string;
|
||||
content: string | Buffer;
|
||||
contentLength: number;
|
||||
dateModified: string;
|
||||
utcDateModified: string;
|
||||
}
|
||||
|
||||
export type AttributeType = "label" | "relation" | "label-definition" | "relation-definition";
|
||||
|
||||
export interface AttributeRow {
|
||||
attributeId?: string;
|
||||
noteId?: string;
|
||||
type: AttributeType;
|
||||
name: string;
|
||||
position?: number;
|
||||
value?: string;
|
||||
isInheritable?: boolean;
|
||||
utcDateModified?: string;
|
||||
}
|
||||
|
||||
export interface BranchRow {
|
||||
branchId?: string;
|
||||
noteId: string;
|
||||
parentNoteId: string;
|
||||
prefix?: string | null;
|
||||
notePosition?: number | null;
|
||||
isExpanded?: boolean;
|
||||
isDeleted?: boolean;
|
||||
utcDateModified?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* There are many different Note types, some of which are entirely opaque to the
|
||||
* end user. Those types should be used only for checking against, they are
|
||||
* not for direct use.
|
||||
*/
|
||||
export const ALLOWED_NOTE_TYPES = [ "file", "image", "search", "noteMap", "launcher", "doc", "contentWidget", "text", "relationMap", "render", "canvas", "mermaid", "book", "webView", "code" ] as const;
|
||||
export type NoteType = typeof ALLOWED_NOTE_TYPES[number];
|
||||
|
||||
export interface NoteRow {
|
||||
noteId: string;
|
||||
deleteId: string;
|
||||
title: string;
|
||||
type: NoteType;
|
||||
mime: string;
|
||||
isProtected: boolean;
|
||||
isDeleted: boolean;
|
||||
blobId: string;
|
||||
dateCreated: string;
|
||||
dateModified: string;
|
||||
utcDateCreated: string;
|
||||
utcDateModified: string;
|
||||
content?: string | Buffer;
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
const BAttachment = require('./entities/battachment.js');
|
||||
const BAttribute = require('./entities/battribute.js');
|
||||
const BBlob = require('./entities/bblob.js');
|
||||
const BBranch = require('./entities/bbranch.js');
|
||||
const BEtapiToken = require('./entities/betapi_token.js');
|
||||
const BNote = require('./entities/bnote.js');
|
||||
const BOption = require('./entities/boption.js');
|
||||
const BRecentNote = require('./entities/brecent_note.js');
|
||||
const BRevision = require('./entities/brevision.js');
|
||||
|
||||
const ENTITY_NAME_TO_ENTITY = {
|
||||
"attachments": BAttachment,
|
||||
"attributes": BAttribute,
|
||||
"blobs": BBlob,
|
||||
"branches": BBranch,
|
||||
"etapi_tokens": BEtapiToken,
|
||||
"notes": BNote,
|
||||
"options": BOption,
|
||||
"recent_notes": BRecentNote,
|
||||
"revisions": BRevision
|
||||
};
|
||||
|
||||
function getEntityFromEntityName(entityName) {
|
||||
if (!(entityName in ENTITY_NAME_TO_ENTITY)) {
|
||||
throw new Error(`Entity for table '${entityName}' not found!`);
|
||||
}
|
||||
|
||||
return ENTITY_NAME_TO_ENTITY[entityName];
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getEntityFromEntityName
|
||||
};
|
||||
37
src/becca/entity_constructor.ts
Normal file
37
src/becca/entity_constructor.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import { ConstructorData } from './becca-interface';
|
||||
import AbstractBeccaEntity = require('./entities/abstract_becca_entity');
|
||||
import BAttachment = require('./entities/battachment');
|
||||
import BAttribute = require('./entities/battribute');
|
||||
import BBlob = require('./entities/bblob');
|
||||
import BBranch = require('./entities/bbranch');
|
||||
import BEtapiToken = require('./entities/betapi_token');
|
||||
import BNote = require('./entities/bnote');
|
||||
import BOption = require('./entities/boption');
|
||||
import BRecentNote = require('./entities/brecent_note');
|
||||
import BRevision = require('./entities/brevision');
|
||||
|
||||
type EntityClass = new (row?: any) => AbstractBeccaEntity<any>;
|
||||
|
||||
const ENTITY_NAME_TO_ENTITY: Record<string, ConstructorData<any> & EntityClass> = {
|
||||
"attachments": BAttachment,
|
||||
"attributes": BAttribute,
|
||||
"blobs": BBlob,
|
||||
"branches": BBranch,
|
||||
"etapi_tokens": BEtapiToken,
|
||||
"notes": BNote,
|
||||
"options": BOption,
|
||||
"recent_notes": BRecentNote,
|
||||
"revisions": BRevision
|
||||
};
|
||||
|
||||
function getEntityFromEntityName(entityName: keyof typeof ENTITY_NAME_TO_ENTITY) {
|
||||
if (!(entityName in ENTITY_NAME_TO_ENTITY)) {
|
||||
throw new Error(`Entity for table '${entityName}' not found!`);
|
||||
}
|
||||
|
||||
return ENTITY_NAME_TO_ENTITY[entityName];
|
||||
}
|
||||
|
||||
export = {
|
||||
getEntityFromEntityName
|
||||
};
|
||||
@@ -1,8 +1,9 @@
|
||||
const becca = require('./becca.js');
|
||||
const log = require('../services/log.js');
|
||||
const beccaService = require('./becca_service.js');
|
||||
const dateUtils = require('../services/date_utils.js');
|
||||
const {JSDOM} = require("jsdom");
|
||||
import becca = require('./becca');
|
||||
import log = require('../services/log');
|
||||
import beccaService = require('./becca_service');
|
||||
import dateUtils = require('../services/date_utils');
|
||||
import { JSDOM } from "jsdom";
|
||||
import BNote = require('./entities/bnote');
|
||||
|
||||
const DEBUG = false;
|
||||
|
||||
@@ -32,21 +33,25 @@ const IGNORED_ATTR_NAMES = [
|
||||
"pageurl",
|
||||
];
|
||||
|
||||
function filterUrlValue(value) {
|
||||
interface DateLimits {
|
||||
minDate: string;
|
||||
minExcludedDate: string;
|
||||
maxExcludedDate: string;
|
||||
maxDate: string;
|
||||
}
|
||||
|
||||
function filterUrlValue(value: string) {
|
||||
return value
|
||||
.replace(/https?:\/\//ig, "")
|
||||
.replace(/www.js\./ig, "")
|
||||
.replace(/(\.net|\.com|\.org|\.info|\.edu)/ig, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {BNote} note
|
||||
*/
|
||||
function buildRewardMap(note) {
|
||||
function buildRewardMap(note: BNote) {
|
||||
// Need to use Map instead of object: https://github.com/zadam/trilium/issues/1895
|
||||
const map = new Map();
|
||||
|
||||
function addToRewardMap(text, rewardFactor) {
|
||||
function addToRewardMap(text: string | undefined | null, rewardFactor: number) {
|
||||
if (!text) {
|
||||
return;
|
||||
}
|
||||
@@ -126,7 +131,7 @@ function buildRewardMap(note) {
|
||||
const content = note.getContent();
|
||||
const dom = new JSDOM(content);
|
||||
|
||||
function addHeadingsToRewardMap(elName, rewardFactor) {
|
||||
const addHeadingsToRewardMap = (elName: string, rewardFactor: number) => {
|
||||
for (const el of dom.window.document.querySelectorAll(elName)) {
|
||||
addToRewardMap(el.textContent, rewardFactor);
|
||||
}
|
||||
@@ -146,9 +151,9 @@ function buildRewardMap(note) {
|
||||
return map;
|
||||
}
|
||||
|
||||
const mimeCache = {};
|
||||
const mimeCache: Record<string, string> = {};
|
||||
|
||||
function trimMime(mime) {
|
||||
function trimMime(mime: string) {
|
||||
if (!mime || mime === 'text/html') {
|
||||
return;
|
||||
}
|
||||
@@ -173,7 +178,7 @@ function trimMime(mime) {
|
||||
return mimeCache[mime];
|
||||
}
|
||||
|
||||
function buildDateLimits(baseNote) {
|
||||
function buildDateLimits(baseNote: BNote): DateLimits {
|
||||
const dateCreatedTs = dateUtils.parseDateTime(baseNote.utcDateCreated).getTime();
|
||||
|
||||
return {
|
||||
@@ -193,7 +198,7 @@ const WORD_BLACKLIST = [
|
||||
"than", "then", "and", "either", "or", "neither", "nor", "both", "also"
|
||||
];
|
||||
|
||||
function splitToWords(text) {
|
||||
function splitToWords(text: string) {
|
||||
let words = wordCache.get(text);
|
||||
|
||||
if (!words) {
|
||||
@@ -221,13 +226,13 @@ function splitToWords(text) {
|
||||
* includeNoteLink and imageLink relation mean that notes are clearly related, but so clearly
|
||||
* that it doesn't actually need to be shown to the user.
|
||||
*/
|
||||
function hasConnectingRelation(sourceNote, targetNote) {
|
||||
function hasConnectingRelation(sourceNote: BNote, targetNote: BNote) {
|
||||
return sourceNote.getAttributes().find(attr => attr.type === 'relation'
|
||||
&& ['includenotelink', 'imagelink'].includes(attr.name)
|
||||
&& attr.value === targetNote.noteId);
|
||||
}
|
||||
|
||||
async function findSimilarNotes(noteId) {
|
||||
async function findSimilarNotes(noteId: string) {
|
||||
const results = [];
|
||||
let i = 0;
|
||||
|
||||
@@ -237,23 +242,23 @@ async function findSimilarNotes(noteId) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let dateLimits;
|
||||
let dateLimits: DateLimits;
|
||||
|
||||
try {
|
||||
dateLimits = buildDateLimits(baseNote);
|
||||
}
|
||||
catch (e) {
|
||||
catch (e: any) {
|
||||
throw new Error(`Date limits failed with ${e.message}, entity: ${JSON.stringify(baseNote.getPojo())}`);
|
||||
}
|
||||
|
||||
const rewardMap = buildRewardMap(baseNote);
|
||||
let ancestorRewardCache = {};
|
||||
let ancestorRewardCache: Record<string, number> = {};
|
||||
const ancestorNoteIds = new Set(baseNote.getAncestors().map(note => note.noteId));
|
||||
ancestorNoteIds.add(baseNote.noteId);
|
||||
|
||||
let displayRewards = false;
|
||||
|
||||
function gatherRewards(text, factor = 1) {
|
||||
function gatherRewards(text?: string | null, factor: number = 1) {
|
||||
if (!text) {
|
||||
return 0;
|
||||
}
|
||||
@@ -279,7 +284,7 @@ async function findSimilarNotes(noteId) {
|
||||
return counter;
|
||||
}
|
||||
|
||||
function gatherAncestorRewards(note) {
|
||||
function gatherAncestorRewards(note?: BNote) {
|
||||
if (!note || ancestorNoteIds.has(note.noteId)) {
|
||||
return 0;
|
||||
}
|
||||
@@ -311,7 +316,7 @@ async function findSimilarNotes(noteId) {
|
||||
return ancestorRewardCache[note.noteId];
|
||||
}
|
||||
|
||||
function computeScore(candidateNote) {
|
||||
function computeScore(candidateNote: BNote) {
|
||||
let score = gatherRewards(trimMime(candidateNote.mime))
|
||||
+ gatherAncestorRewards(candidateNote);
|
||||
|
||||
@@ -451,11 +456,11 @@ async function findSimilarNotes(noteId) {
|
||||
* see https://snyk.io/blog/nodejs-how-even-quick-async-functions-can-block-the-event-loop-starve-io/
|
||||
*/
|
||||
function setImmediatePromise() {
|
||||
return new Promise((resolve) => {
|
||||
return new Promise<void>((resolve) => {
|
||||
setTimeout(() => resolve(), 0);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
export = {
|
||||
findSimilarNotes
|
||||
};
|
||||
@@ -1,7 +0,0 @@
|
||||
class NotFoundError {
|
||||
constructor(message) {
|
||||
this.message = message;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = NotFoundError;
|
||||
9
src/errors/not_found_error.ts
Normal file
9
src/errors/not_found_error.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
class NotFoundError {
|
||||
message: string;
|
||||
|
||||
constructor(message: string) {
|
||||
this.message = message;
|
||||
}
|
||||
}
|
||||
|
||||
export = NotFoundError;
|
||||
@@ -1,7 +0,0 @@
|
||||
class ValidationError {
|
||||
constructor(message) {
|
||||
this.message = message;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ValidationError;
|
||||
9
src/errors/validation_error.ts
Normal file
9
src/errors/validation_error.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
class ValidationError {
|
||||
message: string;
|
||||
|
||||
constructor(message: string) {
|
||||
this.message = message;
|
||||
}
|
||||
}
|
||||
|
||||
export = ValidationError;
|
||||
@@ -1,12 +0,0 @@
|
||||
const appInfo = require('../services/app_info.js');
|
||||
const eu = require('./etapi_utils.js');
|
||||
|
||||
function register(router) {
|
||||
eu.route(router, 'get', '/etapi/app-info', (req, res, next) => {
|
||||
res.status(200).json(appInfo);
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
register
|
||||
};
|
||||
13
src/etapi/app_info.ts
Normal file
13
src/etapi/app_info.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { Router } from 'express';
|
||||
import appInfo = require('../services/app_info');
|
||||
import eu = require('./etapi_utils');
|
||||
|
||||
function register(router: Router) {
|
||||
eu.route(router, 'get', '/etapi/app-info', (req, res, next) => {
|
||||
res.status(200).json(appInfo);
|
||||
});
|
||||
}
|
||||
|
||||
export = {
|
||||
register
|
||||
};
|
||||
@@ -1,11 +1,13 @@
|
||||
const becca = require('../becca/becca.js');
|
||||
const eu = require('./etapi_utils.js');
|
||||
const mappers = require('./mappers.js');
|
||||
const v = require('./validators.js');
|
||||
const utils = require('../services/utils.js');
|
||||
import becca = require('../becca/becca');
|
||||
import eu = require('./etapi_utils');
|
||||
import mappers = require('./mappers');
|
||||
import v = require('./validators');
|
||||
import utils = require('../services/utils');
|
||||
import { Router } from 'express';
|
||||
import { AttachmentRow } from '../becca/entities/rows';
|
||||
|
||||
function register(router) {
|
||||
const ALLOWED_PROPERTIES_FOR_CREATE_ATTACHMENT = {
|
||||
function register(router: Router) {
|
||||
const ALLOWED_PROPERTIES_FOR_CREATE_ATTACHMENT: ValidatorMap = {
|
||||
'ownerId': [v.notNull, v.isNoteId],
|
||||
'role': [v.notNull, v.isString],
|
||||
'mime': [v.notNull, v.isString],
|
||||
@@ -14,18 +16,21 @@ function register(router) {
|
||||
'content': [v.isString],
|
||||
};
|
||||
|
||||
eu.route(router, 'post' ,'/etapi/attachments', (req, res, next) => {
|
||||
const params = {};
|
||||
|
||||
eu.validateAndPatch(params, req.body, ALLOWED_PROPERTIES_FOR_CREATE_ATTACHMENT);
|
||||
eu.route(router, 'post', '/etapi/attachments', (req, res, next) => {
|
||||
const _params: Partial<AttachmentRow> = {};
|
||||
eu.validateAndPatch(_params, req.body, ALLOWED_PROPERTIES_FOR_CREATE_ATTACHMENT);
|
||||
const params = _params as AttachmentRow;
|
||||
|
||||
try {
|
||||
if (!params.ownerId) {
|
||||
throw new Error("Missing owner ID.");
|
||||
}
|
||||
const note = becca.getNoteOrThrow(params.ownerId);
|
||||
const attachment = note.saveAttachment(params);
|
||||
|
||||
res.status(201).json(mappers.mapAttachmentToPojo(attachment));
|
||||
}
|
||||
catch (e) {
|
||||
catch (e: any) {
|
||||
throw new eu.EtapiError(500, eu.GENERIC_CODE, e.message);
|
||||
}
|
||||
});
|
||||
@@ -43,7 +48,7 @@ function register(router) {
|
||||
'position': [v.notNull, v.isInteger],
|
||||
};
|
||||
|
||||
eu.route(router, 'patch' ,'/etapi/attachments/:attachmentId', (req, res, next) => {
|
||||
eu.route(router, 'patch', '/etapi/attachments/:attachmentId', (req, res, next) => {
|
||||
const attachment = eu.getAndCheckAttachment(req.params.attachmentId);
|
||||
|
||||
if (attachment.isProtected) {
|
||||
@@ -85,7 +90,7 @@ function register(router) {
|
||||
return res.sendStatus(204);
|
||||
});
|
||||
|
||||
eu.route(router, 'delete' ,'/etapi/attachments/:attachmentId', (req, res, next) => {
|
||||
eu.route(router, 'delete', '/etapi/attachments/:attachmentId', (req, res, next) => {
|
||||
const attachment = becca.getAttachment(req.params.attachmentId);
|
||||
|
||||
if (!attachment) {
|
||||
@@ -98,6 +103,6 @@ function register(router) {
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
export = {
|
||||
register
|
||||
};
|
||||
@@ -1,17 +1,19 @@
|
||||
const becca = require('../becca/becca.js');
|
||||
const eu = require('./etapi_utils.js');
|
||||
const mappers = require('./mappers.js');
|
||||
const attributeService = require('../services/attributes.js');
|
||||
const v = require('./validators.js');
|
||||
import becca = require('../becca/becca');
|
||||
import eu = require('./etapi_utils');
|
||||
import mappers = require('./mappers');
|
||||
import attributeService = require('../services/attributes');
|
||||
import v = require('./validators');
|
||||
import { Router } from 'express';
|
||||
import { AttributeRow } from '../becca/entities/rows';
|
||||
|
||||
function register(router) {
|
||||
function register(router: Router) {
|
||||
eu.route(router, 'get', '/etapi/attributes/:attributeId', (req, res, next) => {
|
||||
const attribute = eu.getAndCheckAttribute(req.params.attributeId);
|
||||
|
||||
res.json(mappers.mapAttributeToPojo(attribute));
|
||||
});
|
||||
|
||||
const ALLOWED_PROPERTIES_FOR_CREATE_ATTRIBUTE = {
|
||||
const ALLOWED_PROPERTIES_FOR_CREATE_ATTRIBUTE: ValidatorMap = {
|
||||
'attributeId': [v.mandatory, v.notNull, v.isValidEntityId],
|
||||
'noteId': [v.mandatory, v.notNull, v.isNoteId],
|
||||
'type': [v.mandatory, v.notNull, v.isAttributeType],
|
||||
@@ -21,21 +23,21 @@ function register(router) {
|
||||
'position': [v.notNull, v.isInteger]
|
||||
};
|
||||
|
||||
eu.route(router, 'post' ,'/etapi/attributes', (req, res, next) => {
|
||||
eu.route(router, 'post', '/etapi/attributes', (req, res, next) => {
|
||||
if (req.body.type === 'relation') {
|
||||
eu.getAndCheckNote(req.body.value);
|
||||
}
|
||||
|
||||
const params = {};
|
||||
|
||||
eu.validateAndPatch(params, req.body, ALLOWED_PROPERTIES_FOR_CREATE_ATTRIBUTE);
|
||||
const _params = {};
|
||||
eu.validateAndPatch(_params, req.body, ALLOWED_PROPERTIES_FOR_CREATE_ATTRIBUTE);
|
||||
const params: AttributeRow = _params as AttributeRow;
|
||||
|
||||
try {
|
||||
const attr = attributeService.createAttribute(params);
|
||||
|
||||
res.status(201).json(mappers.mapAttributeToPojo(attr));
|
||||
}
|
||||
catch (e) {
|
||||
catch (e: any) {
|
||||
throw new eu.EtapiError(500, eu.GENERIC_CODE, e.message);
|
||||
}
|
||||
});
|
||||
@@ -49,7 +51,7 @@ function register(router) {
|
||||
'position': [v.notNull, v.isInteger]
|
||||
};
|
||||
|
||||
eu.route(router, 'patch' ,'/etapi/attributes/:attributeId', (req, res, next) => {
|
||||
eu.route(router, 'patch', '/etapi/attributes/:attributeId', (req, res, next) => {
|
||||
const attribute = eu.getAndCheckAttribute(req.params.attributeId);
|
||||
|
||||
if (attribute.type === 'label') {
|
||||
@@ -65,7 +67,7 @@ function register(router) {
|
||||
res.json(mappers.mapAttributeToPojo(attribute));
|
||||
});
|
||||
|
||||
eu.route(router, 'delete' ,'/etapi/attributes/:attributeId', (req, res, next) => {
|
||||
eu.route(router, 'delete', '/etapi/attributes/:attributeId', (req, res, next) => {
|
||||
const attribute = becca.getAttribute(req.params.attributeId);
|
||||
|
||||
if (!attribute) {
|
||||
@@ -78,6 +80,6 @@ function register(router) {
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
export = {
|
||||
register
|
||||
};
|
||||
@@ -1,9 +1,10 @@
|
||||
const becca = require('../becca/becca.js');
|
||||
const eu = require('./etapi_utils.js');
|
||||
const passwordEncryptionService = require('../services/encryption/password_encryption.js');
|
||||
const etapiTokenService = require('../services/etapi_tokens.js');
|
||||
import becca = require('../becca/becca');
|
||||
import eu = require('./etapi_utils');
|
||||
import passwordEncryptionService = require('../services/encryption/password_encryption');
|
||||
import etapiTokenService = require('../services/etapi_tokens');
|
||||
import { RequestHandler, Router } from 'express';
|
||||
|
||||
function register(router, loginMiddleware) {
|
||||
function register(router: Router, loginMiddleware: RequestHandler[]) {
|
||||
eu.NOT_AUTHENTICATED_ROUTE(router, 'post', '/etapi/auth/login', loginMiddleware, (req, res, next) => {
|
||||
const {password, tokenName} = req.body;
|
||||
|
||||
@@ -38,6 +39,6 @@ function register(router, loginMiddleware) {
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
export = {
|
||||
register
|
||||
}
|
||||
@@ -1,7 +1,9 @@
|
||||
const eu = require('./etapi_utils.js');
|
||||
const backupService = require('../services/backup.js');
|
||||
import { Router } from "express";
|
||||
|
||||
function register(router) {
|
||||
import eu = require('./etapi_utils');
|
||||
import backupService = require('../services/backup');
|
||||
|
||||
function register(router: Router) {
|
||||
eu.route(router, 'put', '/etapi/backup/:backupName', async (req, res, next) => {
|
||||
await backupService.backupNow(req.params.backupName);
|
||||
|
||||
@@ -9,6 +11,6 @@ function register(router) {
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
export = {
|
||||
register
|
||||
};
|
||||
@@ -1,11 +1,14 @@
|
||||
const becca = require('../becca/becca.js');
|
||||
const eu = require('./etapi_utils.js');
|
||||
const mappers = require('./mappers.js');
|
||||
const BBranch = require('../becca/entities/bbranch.js');
|
||||
const entityChangesService = require('../services/entity_changes.js');
|
||||
const v = require('./validators.js');
|
||||
import { Router } from "express";
|
||||
|
||||
function register(router) {
|
||||
import becca = require('../becca/becca');
|
||||
import eu = require('./etapi_utils');
|
||||
import mappers = require('./mappers');
|
||||
import BBranch = require('../becca/entities/bbranch');
|
||||
import entityChangesService = require('../services/entity_changes');
|
||||
import v = require('./validators');
|
||||
import { BranchRow } from "../becca/entities/rows";
|
||||
|
||||
function register(router: Router) {
|
||||
eu.route(router, 'get', '/etapi/branches/:branchId', (req, res, next) => {
|
||||
const branch = eu.getAndCheckBranch(req.params.branchId);
|
||||
|
||||
@@ -20,17 +23,17 @@ function register(router) {
|
||||
'isExpanded': [v.notNull, v.isBoolean]
|
||||
};
|
||||
|
||||
eu.route(router, 'post' ,'/etapi/branches', (req, res, next) => {
|
||||
const params = {};
|
||||
|
||||
eu.validateAndPatch(params, req.body, ALLOWED_PROPERTIES_FOR_CREATE_BRANCH);
|
||||
eu.route(router, 'post', '/etapi/branches', (req, res, next) => {
|
||||
const _params = {};
|
||||
eu.validateAndPatch(_params, req.body, ALLOWED_PROPERTIES_FOR_CREATE_BRANCH);
|
||||
const params: BranchRow = _params as BranchRow;
|
||||
|
||||
const existing = becca.getBranchFromChildAndParent(params.noteId, params.parentNoteId);
|
||||
|
||||
if (existing) {
|
||||
existing.notePosition = params.notePosition;
|
||||
existing.prefix = params.prefix;
|
||||
existing.isExpanded = params.isExpanded;
|
||||
existing.notePosition = params.notePosition as number;
|
||||
existing.prefix = params.prefix as string;
|
||||
existing.isExpanded = params.isExpanded as boolean;
|
||||
existing.save();
|
||||
|
||||
return res.status(200).json(mappers.mapBranchToPojo(existing));
|
||||
@@ -39,7 +42,7 @@ function register(router) {
|
||||
const branch = new BBranch(params).save();
|
||||
|
||||
res.status(201).json(mappers.mapBranchToPojo(branch));
|
||||
} catch (e) {
|
||||
} catch (e: any) {
|
||||
throw new eu.EtapiError(400, eu.GENERIC_CODE, e.message);
|
||||
}
|
||||
}
|
||||
@@ -51,7 +54,7 @@ function register(router) {
|
||||
'isExpanded': [v.notNull, v.isBoolean]
|
||||
};
|
||||
|
||||
eu.route(router, 'patch' ,'/etapi/branches/:branchId', (req, res, next) => {
|
||||
eu.route(router, 'patch', '/etapi/branches/:branchId', (req, res, next) => {
|
||||
const branch = eu.getAndCheckBranch(req.params.branchId);
|
||||
|
||||
eu.validateAndPatch(branch, req.body, ALLOWED_PROPERTIES_FOR_PATCH);
|
||||
@@ -60,7 +63,7 @@ function register(router) {
|
||||
res.json(mappers.mapBranchToPojo(branch));
|
||||
});
|
||||
|
||||
eu.route(router, 'delete' ,'/etapi/branches/:branchId', (req, res, next) => {
|
||||
eu.route(router, 'delete', '/etapi/branches/:branchId', (req, res, next) => {
|
||||
const branch = becca.getBranch(req.params.branchId);
|
||||
|
||||
if (!branch) {
|
||||
@@ -72,7 +75,7 @@ function register(router) {
|
||||
res.sendStatus(204);
|
||||
});
|
||||
|
||||
eu.route(router, 'post' ,'/etapi/refresh-note-ordering/:parentNoteId', (req, res, next) => {
|
||||
eu.route(router, 'post', '/etapi/refresh-note-ordering/:parentNoteId', (req, res, next) => {
|
||||
eu.getAndCheckNote(req.params.parentNoteId);
|
||||
|
||||
entityChangesService.putNoteReorderingEntityChange(req.params.parentNoteId, "etapi");
|
||||
@@ -81,6 +84,6 @@ function register(router) {
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
export = {
|
||||
register
|
||||
};
|
||||
3
src/etapi/etapi-interface.ts
Normal file
3
src/etapi/etapi-interface.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
type ValidatorFunc = (obj: unknown) => (string | undefined);
|
||||
|
||||
type ValidatorMap = Record<string, ValidatorFunc[]>;
|
||||
@@ -1,24 +1,33 @@
|
||||
const cls = require('../services/cls.js');
|
||||
const sql = require('../services/sql.js');
|
||||
const log = require('../services/log.js');
|
||||
const becca = require('../becca/becca.js');
|
||||
const etapiTokenService = require('../services/etapi_tokens.js');
|
||||
const config = require('../services/config.js');
|
||||
import cls = require('../services/cls');
|
||||
import sql = require('../services/sql');
|
||||
import log = require('../services/log');
|
||||
import becca = require('../becca/becca');
|
||||
import etapiTokenService = require('../services/etapi_tokens');
|
||||
import config = require('../services/config');
|
||||
import { NextFunction, Request, RequestHandler, Response, Router } from 'express';
|
||||
import { AppRequest, AppRequestHandler } from '../routes/route-interface';
|
||||
const GENERIC_CODE = "GENERIC";
|
||||
|
||||
type HttpMethod = "all" | "get" | "post" | "put" | "delete" | "patch" | "options" | "head";
|
||||
|
||||
const noAuthentication = config.General && config.General.noAuthentication === true;
|
||||
|
||||
class EtapiError extends Error {
|
||||
constructor(statusCode, code, message) {
|
||||
super();
|
||||
statusCode: number;
|
||||
code: string;
|
||||
|
||||
constructor(statusCode: number, code: string, message: string) {
|
||||
super(message);
|
||||
|
||||
// Set the prototype explicitly.
|
||||
Object.setPrototypeOf(this, EtapiError.prototype);
|
||||
|
||||
this.statusCode = statusCode;
|
||||
this.code = code;
|
||||
this.message = message;
|
||||
}
|
||||
}
|
||||
|
||||
function sendError(res, statusCode, code, message) {
|
||||
function sendError(res: Response, statusCode: number, code: string, message: string) {
|
||||
return res
|
||||
.set('Content-Type', 'application/json')
|
||||
.status(statusCode)
|
||||
@@ -29,7 +38,7 @@ function sendError(res, statusCode, code, message) {
|
||||
}));
|
||||
}
|
||||
|
||||
function checkEtapiAuth(req, res, next) {
|
||||
function checkEtapiAuth(req: Request, res: Response, next: NextFunction) {
|
||||
if (noAuthentication || etapiTokenService.isValidAuthHeader(req.headers.authorization)) {
|
||||
next();
|
||||
}
|
||||
@@ -38,7 +47,7 @@ function checkEtapiAuth(req, res, next) {
|
||||
}
|
||||
}
|
||||
|
||||
function processRequest(req, res, routeHandler, next, method, path) {
|
||||
function processRequest(req: Request, res: Response, routeHandler: AppRequestHandler, next: NextFunction, method: string, path: string) {
|
||||
try {
|
||||
cls.namespace.bindEmitter(req);
|
||||
cls.namespace.bindEmitter(res);
|
||||
@@ -47,11 +56,11 @@ function processRequest(req, res, routeHandler, next, method, path) {
|
||||
cls.set('componentId', "etapi");
|
||||
cls.set('localNowDateTime', req.headers['trilium-local-now-datetime']);
|
||||
|
||||
const cb = () => routeHandler(req, res, next);
|
||||
const cb = () => routeHandler(req as AppRequest, res, next);
|
||||
|
||||
return sql.transactional(cb);
|
||||
});
|
||||
} catch (e) {
|
||||
} catch (e: any) {
|
||||
log.error(`${method} ${path} threw exception ${e.message} with stacktrace: ${e.stack}`);
|
||||
|
||||
if (e instanceof EtapiError) {
|
||||
@@ -62,15 +71,15 @@ function processRequest(req, res, routeHandler, next, method, path) {
|
||||
}
|
||||
}
|
||||
|
||||
function route(router, method, path, routeHandler) {
|
||||
router[method](path, checkEtapiAuth, (req, res, next) => processRequest(req, res, routeHandler, next, method, path));
|
||||
function route(router: Router, method: HttpMethod, path: string, routeHandler: AppRequestHandler) {
|
||||
router[method](path, checkEtapiAuth, (req: Request, res: Response, next: NextFunction) => processRequest(req, res, routeHandler, next, method, path));
|
||||
}
|
||||
|
||||
function NOT_AUTHENTICATED_ROUTE(router, method, path, middleware, routeHandler) {
|
||||
router[method](path, ...middleware, (req, res, next) => processRequest(req, res, routeHandler, next, method, path));
|
||||
function NOT_AUTHENTICATED_ROUTE(router: Router, method: HttpMethod, path: string, middleware: RequestHandler[], routeHandler: RequestHandler) {
|
||||
router[method](path, ...middleware, (req: Request, res: Response, next: NextFunction) => processRequest(req, res, routeHandler, next, method, path));
|
||||
}
|
||||
|
||||
function getAndCheckNote(noteId) {
|
||||
function getAndCheckNote(noteId: string) {
|
||||
const note = becca.getNote(noteId);
|
||||
|
||||
if (note) {
|
||||
@@ -81,7 +90,7 @@ function getAndCheckNote(noteId) {
|
||||
}
|
||||
}
|
||||
|
||||
function getAndCheckAttachment(attachmentId) {
|
||||
function getAndCheckAttachment(attachmentId: string) {
|
||||
const attachment = becca.getAttachment(attachmentId, {includeContentLength: true});
|
||||
|
||||
if (attachment) {
|
||||
@@ -92,7 +101,7 @@ function getAndCheckAttachment(attachmentId) {
|
||||
}
|
||||
}
|
||||
|
||||
function getAndCheckBranch(branchId) {
|
||||
function getAndCheckBranch(branchId: string) {
|
||||
const branch = becca.getBranch(branchId);
|
||||
|
||||
if (branch) {
|
||||
@@ -103,7 +112,7 @@ function getAndCheckBranch(branchId) {
|
||||
}
|
||||
}
|
||||
|
||||
function getAndCheckAttribute(attributeId) {
|
||||
function getAndCheckAttribute(attributeId: string) {
|
||||
const attribute = becca.getAttribute(attributeId);
|
||||
|
||||
if (attribute) {
|
||||
@@ -114,7 +123,7 @@ function getAndCheckAttribute(attributeId) {
|
||||
}
|
||||
}
|
||||
|
||||
function validateAndPatch(target, source, allowedProperties) {
|
||||
function validateAndPatch(target: any, source: any, allowedProperties: ValidatorMap) {
|
||||
for (const key of Object.keys(source)) {
|
||||
if (!(key in allowedProperties)) {
|
||||
throw new EtapiError(400, "PROPERTY_NOT_ALLOWED", `Property '${key}' is not allowed for this method.`);
|
||||
@@ -136,7 +145,7 @@ function validateAndPatch(target, source, allowedProperties) {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
export = {
|
||||
EtapiError,
|
||||
sendError,
|
||||
route,
|
||||
@@ -1,5 +1,9 @@
|
||||
/** @param {BNote} note */
|
||||
function mapNoteToPojo(note) {
|
||||
import BAttachment = require("../becca/entities/battachment");
|
||||
import BAttribute = require("../becca/entities/battribute");
|
||||
import BBranch = require("../becca/entities/bbranch");
|
||||
import BNote = require("../becca/entities/bnote");
|
||||
|
||||
function mapNoteToPojo(note: BNote) {
|
||||
return {
|
||||
noteId: note.noteId,
|
||||
isProtected: note.isProtected,
|
||||
@@ -19,8 +23,7 @@ function mapNoteToPojo(note) {
|
||||
};
|
||||
}
|
||||
|
||||
/** @param {BBranch} branch */
|
||||
function mapBranchToPojo(branch) {
|
||||
function mapBranchToPojo(branch: BBranch) {
|
||||
return {
|
||||
branchId: branch.branchId,
|
||||
noteId: branch.noteId,
|
||||
@@ -32,8 +35,7 @@ function mapBranchToPojo(branch) {
|
||||
};
|
||||
}
|
||||
|
||||
/** @param {BAttribute} attr */
|
||||
function mapAttributeToPojo(attr) {
|
||||
function mapAttributeToPojo(attr: BAttribute) {
|
||||
return {
|
||||
attributeId: attr.attributeId,
|
||||
noteId: attr.noteId,
|
||||
@@ -46,8 +48,7 @@ function mapAttributeToPojo(attr) {
|
||||
};
|
||||
}
|
||||
|
||||
/** @param {BAttachment} attachment */
|
||||
function mapAttachmentToPojo(attachment) {
|
||||
function mapAttachmentToPojo(attachment: BAttachment) {
|
||||
return {
|
||||
attachmentId: attachment.attachmentId,
|
||||
ownerId: attachment.ownerId,
|
||||
@@ -63,7 +64,7 @@ function mapAttachmentToPojo(attachment) {
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
export = {
|
||||
mapNoteToPojo,
|
||||
mapBranchToPojo,
|
||||
mapAttributeToPojo,
|
||||
@@ -1,20 +1,26 @@
|
||||
const becca = require('../becca/becca.js');
|
||||
const utils = require('../services/utils.js');
|
||||
const eu = require('./etapi_utils.js');
|
||||
const mappers = require('./mappers.js');
|
||||
const noteService = require('../services/notes.js');
|
||||
const TaskContext = require('../services/task_context.js');
|
||||
const v = require('./validators.js');
|
||||
const searchService = require('../services/search/services/search.js');
|
||||
const SearchContext = require('../services/search/search_context.js');
|
||||
const zipExportService = require('../services/export/zip.js');
|
||||
const zipImportService = require('../services/import/zip.js');
|
||||
import becca = require('../becca/becca');
|
||||
import utils = require('../services/utils');
|
||||
import eu = require('./etapi_utils');
|
||||
import mappers = require('./mappers');
|
||||
import noteService = require('../services/notes');
|
||||
import TaskContext = require('../services/task_context');
|
||||
import v = require('./validators');
|
||||
import searchService = require('../services/search/services/search');
|
||||
import SearchContext = require('../services/search/search_context');
|
||||
import zipExportService = require('../services/export/zip');
|
||||
import zipImportService = require('../services/import/zip');
|
||||
import { Router } from 'express';
|
||||
import { AppRequest } from '../routes/route-interface';
|
||||
import { ParsedQs } from 'qs';
|
||||
import { NoteParams } from '../services/note-interface';
|
||||
import BNote = require('../becca/entities/bnote');
|
||||
import { SearchParams } from '../services/search/services/types';
|
||||
|
||||
function register(router) {
|
||||
function register(router: Router) {
|
||||
eu.route(router, 'get', '/etapi/notes', (req, res, next) => {
|
||||
const {search} = req.query;
|
||||
const { search } = req.query;
|
||||
|
||||
if (!search?.trim()) {
|
||||
if (typeof search !== "string" || !search?.trim()) {
|
||||
throw new eu.EtapiError(400, 'SEARCH_QUERY_PARAM_MANDATORY', "'search' query parameter is mandatory.");
|
||||
}
|
||||
|
||||
@@ -24,8 +30,8 @@ function register(router) {
|
||||
const searchResults = searchService.findResultsWithQuery(search, searchContext);
|
||||
const foundNotes = searchResults.map(sr => becca.notes[sr.noteId]);
|
||||
|
||||
const resp = {
|
||||
results: foundNotes.map(note => mappers.mapNoteToPojo(note))
|
||||
const resp: any = {
|
||||
results: foundNotes.map(note => mappers.mapNoteToPojo(note)),
|
||||
};
|
||||
|
||||
if (searchContext.debugInfo) {
|
||||
@@ -41,7 +47,7 @@ function register(router) {
|
||||
res.json(mappers.mapNoteToPojo(note));
|
||||
});
|
||||
|
||||
const ALLOWED_PROPERTIES_FOR_CREATE_NOTE = {
|
||||
const ALLOWED_PROPERTIES_FOR_CREATE_NOTE: ValidatorMap = {
|
||||
'parentNoteId': [v.mandatory, v.notNull, v.isNoteId],
|
||||
'title': [v.mandatory, v.notNull, v.isString],
|
||||
'type': [v.mandatory, v.notNull, v.isNoteType],
|
||||
@@ -55,10 +61,10 @@ function register(router) {
|
||||
'utcDateCreated': [v.notNull, v.isString, v.isUtcDateTime]
|
||||
};
|
||||
|
||||
eu.route(router, 'post' ,'/etapi/create-note', (req, res, next) => {
|
||||
const params = {};
|
||||
|
||||
eu.validateAndPatch(params, req.body, ALLOWED_PROPERTIES_FOR_CREATE_NOTE);
|
||||
eu.route(router, 'post', '/etapi/create-note', (req, res, next) => {
|
||||
const _params = {};
|
||||
eu.validateAndPatch(_params, req.body, ALLOWED_PROPERTIES_FOR_CREATE_NOTE);
|
||||
const params = _params as NoteParams;
|
||||
|
||||
try {
|
||||
const resp = noteService.createNewNote(params);
|
||||
@@ -68,7 +74,7 @@ function register(router) {
|
||||
branch: mappers.mapBranchToPojo(resp.branch)
|
||||
});
|
||||
}
|
||||
catch (e) {
|
||||
catch (e: any) {
|
||||
return eu.sendError(res, 500, eu.GENERIC_CODE, e.message);
|
||||
}
|
||||
});
|
||||
@@ -81,7 +87,7 @@ function register(router) {
|
||||
'utcDateCreated': [v.notNull, v.isString, v.isUtcDateTime]
|
||||
};
|
||||
|
||||
eu.route(router, 'patch' ,'/etapi/notes/:noteId', (req, res, next) => {
|
||||
eu.route(router, 'patch', '/etapi/notes/:noteId', (req, res, next) => {
|
||||
const note = eu.getAndCheckNote(req.params.noteId);
|
||||
|
||||
if (note.isProtected) {
|
||||
@@ -94,8 +100,8 @@ function register(router) {
|
||||
res.json(mappers.mapNoteToPojo(note));
|
||||
});
|
||||
|
||||
eu.route(router, 'delete' ,'/etapi/notes/:noteId', (req, res, next) => {
|
||||
const {noteId} = req.params;
|
||||
eu.route(router, 'delete', '/etapi/notes/:noteId', (req, res, next) => {
|
||||
const { noteId } = req.params;
|
||||
|
||||
const note = becca.getNote(noteId);
|
||||
|
||||
@@ -139,11 +145,11 @@ function register(router) {
|
||||
return res.sendStatus(204);
|
||||
});
|
||||
|
||||
eu.route(router, 'get' ,'/etapi/notes/:noteId/export', (req, res, next) => {
|
||||
eu.route(router, 'get', '/etapi/notes/:noteId/export', (req, res, next) => {
|
||||
const note = eu.getAndCheckNote(req.params.noteId);
|
||||
const format = req.query.format || "html";
|
||||
|
||||
if (!["html", "markdown"].includes(format)) {
|
||||
if (typeof format !== "string" || !["html", "markdown"].includes(format)) {
|
||||
throw new eu.EtapiError(400, "UNRECOGNIZED_EXPORT_FORMAT", `Unrecognized export format '${format}', supported values are 'html' (default) or 'markdown'.`);
|
||||
}
|
||||
|
||||
@@ -153,10 +159,10 @@ function register(router) {
|
||||
// (e.g. branchIds are not seen in UI), that we export "note export" instead.
|
||||
const branch = note.getParentBranches()[0];
|
||||
|
||||
zipExportService.exportToZip(taskContext, branch, format, res);
|
||||
zipExportService.exportToZip(taskContext, branch, format as "html" | "markdown", res);
|
||||
});
|
||||
|
||||
eu.route(router, 'post' ,'/etapi/notes/:noteId/import', (req, res, next) => {
|
||||
eu.route(router, 'post', '/etapi/notes/:noteId/import', (req, res, next) => {
|
||||
const note = eu.getAndCheckNote(req.params.noteId);
|
||||
const taskContext = new TaskContext('no-progress-reporting');
|
||||
|
||||
@@ -168,7 +174,7 @@ function register(router) {
|
||||
}); // we need better error handling here, async errors won't be properly processed.
|
||||
});
|
||||
|
||||
eu.route(router, 'post' ,'/etapi/notes/:noteId/revision', (req, res, next) => {
|
||||
eu.route(router, 'post', '/etapi/notes/:noteId/revision', (req, res, next) => {
|
||||
const note = eu.getAndCheckNote(req.params.noteId);
|
||||
|
||||
note.saveRevision();
|
||||
@@ -178,7 +184,7 @@ function register(router) {
|
||||
|
||||
eu.route(router, 'get', '/etapi/notes/:noteId/attachments', (req, res, next) => {
|
||||
const note = eu.getAndCheckNote(req.params.noteId);
|
||||
const attachments = note.getAttachments({includeContentLength: true})
|
||||
const attachments = note.getAttachments({ includeContentLength: true })
|
||||
|
||||
res.json(
|
||||
attachments.map(attachment => mappers.mapAttachmentToPojo(attachment))
|
||||
@@ -186,23 +192,24 @@ function register(router) {
|
||||
});
|
||||
}
|
||||
|
||||
function parseSearchParams(req) {
|
||||
const rawSearchParams = {
|
||||
function parseSearchParams(req: AppRequest) {
|
||||
const rawSearchParams: SearchParams = {
|
||||
fastSearch: parseBoolean(req.query, 'fastSearch'),
|
||||
includeArchivedNotes: parseBoolean(req.query, 'includeArchivedNotes'),
|
||||
ancestorNoteId: req.query['ancestorNoteId'],
|
||||
ancestorDepth: req.query['ancestorDepth'], // e.g. "eq5"
|
||||
orderBy: req.query['orderBy'],
|
||||
orderDirection: parseOrderDirection(req.query, 'orderDirection'),
|
||||
ancestorNoteId: parseString(req.query['ancestorNoteId']),
|
||||
ancestorDepth: parseString(req.query['ancestorDepth']), // e.g. "eq5"
|
||||
orderBy: parseString(req.query['orderBy']),
|
||||
// TODO: Check why the order direction was provided as a number, but it's a string everywhere else.
|
||||
orderDirection: parseOrderDirection(req.query, 'orderDirection') as unknown as string,
|
||||
limit: parseInteger(req.query, 'limit'),
|
||||
debug: parseBoolean(req.query, 'debug')
|
||||
};
|
||||
|
||||
const searchParams = {};
|
||||
const searchParams: SearchParams = {};
|
||||
|
||||
for (const paramName of Object.keys(rawSearchParams)) {
|
||||
for (const paramName of Object.keys(rawSearchParams) as (keyof SearchParams)[]) {
|
||||
if (rawSearchParams[paramName] !== undefined) {
|
||||
searchParams[paramName] = rawSearchParams[paramName];
|
||||
(searchParams as any)[paramName] = rawSearchParams[paramName];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,7 +218,15 @@ function parseSearchParams(req) {
|
||||
|
||||
const SEARCH_PARAM_ERROR = "SEARCH_PARAM_VALIDATION_ERROR";
|
||||
|
||||
function parseBoolean(obj, name) {
|
||||
function parseString(value: string | ParsedQs | string[] | ParsedQs[] | undefined): string | undefined {
|
||||
if (typeof value === "string") {
|
||||
return value;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function parseBoolean(obj: any, name: string) {
|
||||
if (!(name in obj)) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -223,7 +238,7 @@ function parseBoolean(obj, name) {
|
||||
return obj[name] === 'true';
|
||||
}
|
||||
|
||||
function parseOrderDirection(obj, name) {
|
||||
function parseOrderDirection(obj: any, name: string) {
|
||||
if (!(name in obj)) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -237,7 +252,7 @@ function parseOrderDirection(obj, name) {
|
||||
return integer;
|
||||
}
|
||||
|
||||
function parseInteger(obj, name) {
|
||||
function parseInteger(obj: any, name: string) {
|
||||
if (!(name in obj)) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -251,6 +266,6 @@ function parseInteger(obj, name) {
|
||||
return integer;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
export = {
|
||||
register
|
||||
};
|
||||
@@ -1,10 +1,12 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
import { Router } from "express";
|
||||
|
||||
import fs = require('fs');
|
||||
import path = require('path');
|
||||
|
||||
const specPath = path.join(__dirname, 'etapi.openapi.yaml');
|
||||
let spec = null;
|
||||
let spec: string | null = null;
|
||||
|
||||
function register(router) {
|
||||
function register(router: Router) {
|
||||
router.get('/etapi/etapi.openapi.yaml', (req, res, next) => {
|
||||
if (!spec) {
|
||||
spec = fs.readFileSync(specPath, 'utf8');
|
||||
@@ -15,6 +17,6 @@ function register(router) {
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
export = {
|
||||
register
|
||||
};
|
||||
@@ -1,13 +1,14 @@
|
||||
const specialNotesService = require('../services/special_notes.js');
|
||||
const dateNotesService = require('../services/date_notes.js');
|
||||
const eu = require('./etapi_utils.js');
|
||||
const mappers = require('./mappers.js');
|
||||
import specialNotesService = require('../services/special_notes');
|
||||
import dateNotesService = require('../services/date_notes');
|
||||
import eu = require('./etapi_utils');
|
||||
import mappers = require('./mappers');
|
||||
import { Router } from 'express';
|
||||
|
||||
const getDateInvalidError = date => new eu.EtapiError(400, "DATE_INVALID", `Date "${date}" is not valid.`);
|
||||
const getMonthInvalidError = month => new eu.EtapiError(400, "MONTH_INVALID", `Month "${month}" is not valid.`);
|
||||
const getYearInvalidError = year => new eu.EtapiError(400, "YEAR_INVALID", `Year "${year}" is not valid.`);
|
||||
const getDateInvalidError = (date: string) => new eu.EtapiError(400, "DATE_INVALID", `Date "${date}" is not valid.`);
|
||||
const getMonthInvalidError = (month: string)=> new eu.EtapiError(400, "MONTH_INVALID", `Month "${month}" is not valid.`);
|
||||
const getYearInvalidError = (year: string) => new eu.EtapiError(400, "YEAR_INVALID", `Year "${year}" is not valid.`);
|
||||
|
||||
function isValidDate(date) {
|
||||
function isValidDate(date: string) {
|
||||
if (!/[0-9]{4}-[0-9]{2}-[0-9]{2}/.test(date)) {
|
||||
return false;
|
||||
}
|
||||
@@ -15,9 +16,9 @@ function isValidDate(date) {
|
||||
return !!Date.parse(date);
|
||||
}
|
||||
|
||||
function register(router) {
|
||||
function register(router: Router) {
|
||||
eu.route(router, 'get', '/etapi/inbox/:date', (req, res, next) => {
|
||||
const {date} = req.params;
|
||||
const { date } = req.params;
|
||||
|
||||
if (!isValidDate(date)) {
|
||||
throw getDateInvalidError(date);
|
||||
@@ -28,7 +29,7 @@ function register(router) {
|
||||
});
|
||||
|
||||
eu.route(router, 'get', '/etapi/calendar/days/:date', (req, res, next) => {
|
||||
const {date} = req.params;
|
||||
const { date } = req.params;
|
||||
|
||||
if (!isValidDate(date)) {
|
||||
throw getDateInvalidError(date);
|
||||
@@ -39,7 +40,7 @@ function register(router) {
|
||||
});
|
||||
|
||||
eu.route(router, 'get', '/etapi/calendar/weeks/:date', (req, res, next) => {
|
||||
const {date} = req.params;
|
||||
const { date } = req.params;
|
||||
|
||||
if (!isValidDate(date)) {
|
||||
throw getDateInvalidError(date);
|
||||
@@ -50,7 +51,7 @@ function register(router) {
|
||||
});
|
||||
|
||||
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)) {
|
||||
throw getMonthInvalidError(month);
|
||||
@@ -61,7 +62,7 @@ function register(router) {
|
||||
});
|
||||
|
||||
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)) {
|
||||
throw getYearInvalidError(year);
|
||||
@@ -72,6 +73,6 @@ function register(router) {
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
export = {
|
||||
register
|
||||
};
|
||||
@@ -1,19 +1,19 @@
|
||||
const noteTypeService = require('../services/note_types.js');
|
||||
const dateUtils = require('../services/date_utils.js');
|
||||
import noteTypeService = require('../services/note_types');
|
||||
import dateUtils = require('../services/date_utils');
|
||||
|
||||
function mandatory(obj) {
|
||||
if (obj === undefined ) {
|
||||
function mandatory(obj: unknown) {
|
||||
if (obj === undefined) {
|
||||
return `mandatory, but not set`;
|
||||
}
|
||||
}
|
||||
|
||||
function notNull(obj) {
|
||||
function notNull(obj: unknown) {
|
||||
if (obj === null) {
|
||||
return `cannot be null`;
|
||||
}
|
||||
}
|
||||
|
||||
function isString(obj) {
|
||||
function isString(obj: unknown) {
|
||||
if (obj === undefined || obj === null) {
|
||||
return;
|
||||
}
|
||||
@@ -23,23 +23,23 @@ function isString(obj) {
|
||||
}
|
||||
}
|
||||
|
||||
function isLocalDateTime(obj) {
|
||||
if (obj === undefined || obj === null) {
|
||||
function isLocalDateTime(obj: unknown) {
|
||||
if (typeof obj !== "string") {
|
||||
return;
|
||||
}
|
||||
|
||||
return dateUtils.validateLocalDateTime(obj);
|
||||
}
|
||||
|
||||
function isUtcDateTime(obj) {
|
||||
if (obj === undefined || obj === null) {
|
||||
function isUtcDateTime(obj: unknown) {
|
||||
if (typeof obj !== "string") {
|
||||
return;
|
||||
}
|
||||
|
||||
return dateUtils.validateUtcDateTime(obj);
|
||||
}
|
||||
|
||||
function isBoolean(obj) {
|
||||
function isBoolean(obj: unknown) {
|
||||
if (obj === undefined || obj === null) {
|
||||
return;
|
||||
}
|
||||
@@ -49,7 +49,7 @@ function isBoolean(obj) {
|
||||
}
|
||||
}
|
||||
|
||||
function isInteger(obj) {
|
||||
function isInteger(obj: unknown) {
|
||||
if (obj === undefined || obj === null) {
|
||||
return;
|
||||
}
|
||||
@@ -59,12 +59,12 @@ function isInteger(obj) {
|
||||
}
|
||||
}
|
||||
|
||||
function isNoteId(obj) {
|
||||
function isNoteId(obj: unknown) {
|
||||
if (obj === undefined || obj === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const becca = require('../becca/becca.js');
|
||||
const becca = require('../becca/becca');
|
||||
|
||||
if (typeof obj !== 'string') {
|
||||
return `'${obj}' is not a valid noteId`;
|
||||
@@ -75,29 +75,29 @@ function isNoteId(obj) {
|
||||
}
|
||||
}
|
||||
|
||||
function isNoteType(obj) {
|
||||
function isNoteType(obj: unknown) {
|
||||
if (obj === undefined || obj === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const noteTypes = noteTypeService.getNoteTypeNames();
|
||||
|
||||
if (!noteTypes.includes(obj)) {
|
||||
if (typeof obj !== "string" || !noteTypes.includes(obj)) {
|
||||
return `'${obj}' is not a valid note type, allowed types are: ${noteTypes.join(", ")}`;
|
||||
}
|
||||
}
|
||||
|
||||
function isAttributeType(obj) {
|
||||
function isAttributeType(obj: unknown) {
|
||||
if (obj === undefined || obj === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!['label', 'relation'].includes(obj)) {
|
||||
if (typeof obj !== "string" || !['label', 'relation'].includes(obj)) {
|
||||
return `'${obj}' is not a valid attribute type, allowed types are: label, relation`;
|
||||
}
|
||||
}
|
||||
|
||||
function isValidEntityId(obj) {
|
||||
function isValidEntityId(obj: unknown) {
|
||||
if (obj === undefined || obj === null) {
|
||||
return;
|
||||
}
|
||||
@@ -107,7 +107,7 @@ function isValidEntityId(obj) {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
export = {
|
||||
mandatory,
|
||||
notNull,
|
||||
isString,
|
||||
@@ -645,7 +645,7 @@ export default class TabManager extends Component {
|
||||
const titleFragments = [
|
||||
// it helps to navigate in history if note title is included in the title
|
||||
await activeNoteContext.getNavigationTitle(),
|
||||
"Trilium Notes"
|
||||
"TriliumNext Notes"
|
||||
].filter(Boolean);
|
||||
|
||||
document.title = titleFragments.join(" - ");
|
||||
|
||||
@@ -50,10 +50,10 @@ export default class TreeContextMenu {
|
||||
{ title: 'Open in a new tab <kbd>Ctrl+Click</kbd>', command: "openInTab", uiIcon: "bx bx-empty", enabled: noSelectedNotes },
|
||||
{ title: 'Open in a new split', command: "openNoteInSplit", uiIcon: "bx bx-dock-right", enabled: noSelectedNotes },
|
||||
{ title: 'Insert note after <kbd data-command="createNoteAfter"></kbd>', command: "insertNoteAfter", uiIcon: "bx bx-plus",
|
||||
items: insertNoteAfterEnabled ? await noteTypesService.getNoteTypeItems("insertNoteAfter", {removeDeprecatedTypes: true}) : null,
|
||||
items: insertNoteAfterEnabled ? await noteTypesService.getNoteTypeItems("insertNoteAfter") : null,
|
||||
enabled: insertNoteAfterEnabled && noSelectedNotes },
|
||||
{ title: 'Insert child note <kbd data-command="createNoteInto"></kbd>', command: "insertChildNote", uiIcon: "bx bx-plus",
|
||||
items: notSearch ? await noteTypesService.getNoteTypeItems("insertChildNote", {removeDeprecatedTypes: true}) : null,
|
||||
items: notSearch ? await noteTypesService.getNoteTypeItems("insertChildNote") : null,
|
||||
enabled: notSearch && noSelectedNotes },
|
||||
{ title: 'Delete <kbd data-command="deleteNotes"></kbd>', command: "deleteNotes", uiIcon: "bx bx-trash",
|
||||
enabled: isNotRoot && !isHoisted && parentNotSearch },
|
||||
|
||||
@@ -1,21 +1,19 @@
|
||||
import server from "./server.js";
|
||||
import froca from "./froca.js";
|
||||
|
||||
async function getNoteTypeItems(command, opts = {}) {
|
||||
const removeDeprecatedTypes = !!opts.removeDeprecatedTypes;
|
||||
|
||||
async function getNoteTypeItems(command) {
|
||||
const items = [
|
||||
{ title: "Text", command: command, type: "text", uiIcon: "bx bx-note" },
|
||||
{ title: "Code", command: command, type: "code", uiIcon: "bx bx-code" },
|
||||
{ title: "Saved Search", command: command, type: "search", uiIcon: "bx bx-file-find" },
|
||||
{ title: "Relation Map", command: command, type: "relationMap", uiIcon: "bx bx-map-alt", deprecated: true },
|
||||
{ title: "Relation Map", command: command, type: "relationMap", uiIcon: "bx bx-map-alt" },
|
||||
{ title: "Note Map", command: command, type: "noteMap", uiIcon: "bx bx-map-alt" },
|
||||
{ title: "Render Note", command: command, type: "render", uiIcon: "bx bx-extension" },
|
||||
{ title: "Book", command: command, type: "book", uiIcon: "bx bx-book" },
|
||||
{ title: "Mermaid Diagram", command: command, type: "mermaid", uiIcon: "bx bx-selection" },
|
||||
{ title: "Canvas", command: command, type: "canvas", uiIcon: "bx bx-pen" },
|
||||
{ title: "Web View", command: command, type: "webView", uiIcon: "bx bx-globe-alt" },
|
||||
].filter(item => !removeDeprecatedTypes || !item.deprecated);
|
||||
];
|
||||
|
||||
const templateNoteIds = await server.get("search-templates");
|
||||
const templateNotes = await froca.getNotes(templateNoteIds);
|
||||
|
||||
@@ -487,12 +487,14 @@ function areObjectsEqual () {
|
||||
}
|
||||
|
||||
function copyHtmlToClipboard(content) {
|
||||
const clipboardItem = new ClipboardItem({
|
||||
'text/html': new Blob([content], {type: 'text/html'}),
|
||||
'text/plain': new Blob([content], {type: 'text/plain'})
|
||||
});
|
||||
|
||||
navigator.clipboard.write([clipboardItem]);
|
||||
function listener(e) {
|
||||
e.clipboardData.setData("text/html", content);
|
||||
e.clipboardData.setData("text/plain", content);
|
||||
e.preventDefault();
|
||||
}
|
||||
document.addEventListener("copy", listener);
|
||||
document.execCommand("copy");
|
||||
document.removeEventListener("copy", listener);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -204,7 +204,7 @@ const TPL = `
|
||||
|
||||
<li class="dropdown-item show-about-dialog-button">
|
||||
<span class="bx bx-empty"></span>
|
||||
About Trilium Notes
|
||||
About TriliumNext Notes
|
||||
</li>
|
||||
|
||||
<li class="dropdown-item update-to-latest-version-button" data-trigger-command="downloadLatestVersion">
|
||||
|
||||
@@ -7,7 +7,7 @@ const TPL = `
|
||||
<div class="modal-dialog modal-lg" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title mr-auto">About Trilium Notes</h5>
|
||||
<h5 class="modal-title mr-auto">About TriliumNext Notes</h5>
|
||||
|
||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close" style="margin-left: 0;">
|
||||
<span aria-hidden="true">×</span>
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
import libraryLoader from "../../services/library_loader.js";
|
||||
import TypeWidget from "./type_widget.js";
|
||||
import libraryLoader from '../../services/library_loader.js';
|
||||
import TypeWidget from './type_widget.js';
|
||||
import utils from '../../services/utils.js';
|
||||
import linkService from '../../services/link.js';
|
||||
import debounce from "../../services/debounce.js";
|
||||
|
||||
const {sleep} = utils;
|
||||
|
||||
const TPL = `
|
||||
<div class="canvas-widget note-detail-canvas note-detail-printable note-detail">
|
||||
@@ -105,8 +102,6 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
|
||||
this.SCENE_VERSION_INITIAL = -1; // -1 indicates that it is fresh. excalidraw scene version is always >0
|
||||
this.SCENE_VERSION_ERROR = -2; // -2 indicates error
|
||||
|
||||
// config
|
||||
this.DEBOUNCE_TIME_ONCHANGEHANDLER = 750; // ms
|
||||
// ensure that assets are loaded from trilium
|
||||
window.EXCALIDRAW_ASSET_PATH = `${window.location.origin}/node_modules/@excalidraw/excalidraw/dist/`;
|
||||
|
||||
@@ -115,16 +110,10 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
|
||||
this.currentSceneVersion = this.SCENE_VERSION_INITIAL;
|
||||
|
||||
// will be overwritten
|
||||
this.excalidrawRef;
|
||||
this.$render;
|
||||
this.$widget;
|
||||
this.reactHandlers; // used to control react state
|
||||
|
||||
// binds
|
||||
this.createExcalidrawReactApp = this.createExcalidrawReactApp.bind(this);
|
||||
this.onChangeHandler = this.onChangeHandler.bind(this);
|
||||
this.isNewSceneVersion = this.isNewSceneVersion.bind(this);
|
||||
|
||||
this.libraryChanged = false;
|
||||
}
|
||||
|
||||
@@ -155,7 +144,8 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
|
||||
const renderElement = this.$render.get(0);
|
||||
|
||||
ReactDOM.unmountComponentAtNode(renderElement);
|
||||
ReactDOM.render(React.createElement(this.createExcalidrawReactApp), renderElement);
|
||||
const root = ReactDOM.createRoot(renderElement);
|
||||
root.render(React.createElement(() => this.createExcalidrawReactApp()));
|
||||
});
|
||||
|
||||
return this.$widget;
|
||||
@@ -179,9 +169,9 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
|
||||
const blob = await note.getBlob();
|
||||
|
||||
// before we load content into excalidraw, make sure excalidraw has loaded
|
||||
while (!this.excalidrawRef?.current) {
|
||||
console.log("excalidrawRef not yet loaded, sleep 200ms...");
|
||||
await sleep(200);
|
||||
while (!this.excalidrawApi) {
|
||||
console.log("excalidrawApi not yet loaded, sleep 200ms...");
|
||||
await utils.sleep(200);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -199,7 +189,7 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
|
||||
collaborators: []
|
||||
};
|
||||
|
||||
this.excalidrawRef.current.updateScene(sceneData);
|
||||
this.excalidrawApi.updateScene(sceneData);
|
||||
}
|
||||
else if (blob.content) {
|
||||
// load saved content into excalidraw canvas
|
||||
@@ -246,9 +236,9 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
|
||||
fileArray.push(file);
|
||||
}
|
||||
|
||||
this.excalidrawRef.current.updateScene(sceneData);
|
||||
this.excalidrawRef.current.addFiles(fileArray);
|
||||
this.excalidrawRef.current.history.clear();
|
||||
this.excalidrawApi.updateScene(sceneData);
|
||||
this.excalidrawApi.addFiles(fileArray);
|
||||
this.excalidrawApi.history.clear();
|
||||
}
|
||||
|
||||
Promise.all(
|
||||
@@ -261,7 +251,7 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
|
||||
}
|
||||
|
||||
const libraryItems = blobs.map(blob => blob.getJsonContentSafely()).filter(item => !!item);
|
||||
this.excalidrawRef.current.updateLibrary({libraryItems, merge: false});
|
||||
this.excalidrawApi.updateLibrary({libraryItems, merge: false});
|
||||
});
|
||||
|
||||
// set initial scene version
|
||||
@@ -275,17 +265,17 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
|
||||
* this is automatically called after this.saveData();
|
||||
*/
|
||||
async getData() {
|
||||
const elements = this.excalidrawRef.current.getSceneElements();
|
||||
const appState = this.excalidrawRef.current.getAppState();
|
||||
const elements = this.excalidrawApi.getSceneElements();
|
||||
const appState = this.excalidrawApi.getAppState();
|
||||
|
||||
/**
|
||||
* A file is not deleted, even though removed from canvas. Therefore, we only keep
|
||||
* files that are referenced by an element. Maybe this will change with a new excalidraw version?
|
||||
*/
|
||||
const files = this.excalidrawRef.current.getFiles();
|
||||
const files = this.excalidrawApi.getFiles();
|
||||
|
||||
// parallel svg export to combat bitrot and enable rendering image for note inclusion, preview, and share
|
||||
const svg = await window.ExcalidrawLib.exportToSvg({
|
||||
const svg = await ExcalidrawLib.exportToSvg({
|
||||
elements,
|
||||
appState,
|
||||
exportPadding: 5, // 5 px padding
|
||||
@@ -321,7 +311,7 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
|
||||
// this.libraryChanged is unset in dataSaved()
|
||||
|
||||
// there's no separate method to get library items, so have to abuse this one
|
||||
const libraryItems = await this.excalidrawRef.current.updateLibrary({merge: true});
|
||||
const libraryItems = await this.excalidrawApi.updateLibrary({merge: true});
|
||||
|
||||
let position = 10;
|
||||
|
||||
@@ -379,9 +369,6 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
|
||||
createExcalidrawReactApp() {
|
||||
const React = window.React;
|
||||
const { Excalidraw } = window.ExcalidrawLib;
|
||||
|
||||
const excalidrawRef = React.useRef(null);
|
||||
this.excalidrawRef = excalidrawRef;
|
||||
const excalidrawWrapperRef = React.useRef(null);
|
||||
this.excalidrawWrapperRef = excalidrawWrapperRef;
|
||||
const [dimensions, setDimensions] = React.useState({
|
||||
@@ -439,7 +426,7 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
|
||||
React.createElement(Excalidraw, {
|
||||
// this makes sure that 1) manual theme switch button is hidden 2) theme stays as it should after opening menu
|
||||
theme: this.themeStyle,
|
||||
ref: excalidrawRef,
|
||||
excalidrawAPI: api => { this.excalidrawApi = api; },
|
||||
width: dimensions.width,
|
||||
height: dimensions.height,
|
||||
onPaste: (data, event) => {
|
||||
@@ -450,7 +437,7 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
|
||||
|
||||
this.saveData();
|
||||
},
|
||||
onChange: debounce(this.onChangeHandler, this.DEBOUNCE_TIME_ONCHANGEHANDLER),
|
||||
onChange: () => this.onChangeHandler(),
|
||||
viewModeEnabled: false,
|
||||
zenModeEnabled: false,
|
||||
gridModeEnabled: false,
|
||||
@@ -483,8 +470,8 @@ export default class ExcalidrawTypeWidget extends TypeWidget {
|
||||
}
|
||||
|
||||
getSceneVersion() {
|
||||
if (this.excalidrawRef) {
|
||||
const elements = this.excalidrawRef.current.getSceneElements();
|
||||
if (this.excalidrawApi) {
|
||||
const elements = this.excalidrawApi.getSceneElements();
|
||||
return window.ExcalidrawLib.getSceneVersion(elements);
|
||||
} else {
|
||||
return this.SCENE_VERSION_ERROR;
|
||||
|
||||
@@ -184,8 +184,6 @@ export default class RelationMapTypeWidget extends TypeWidget {
|
||||
}
|
||||
|
||||
async loadMapData() {
|
||||
toastService.showMessage("Relation Map has been deprecated since Trilium 0.63 and will be removed in a future version. Migrate your content to some other note type (e.g. canvas) as soon as possible.", 5000);
|
||||
|
||||
this.mapData = {
|
||||
notes: [],
|
||||
// it is important to have this exact value here so that initial transform is the same as this
|
||||
|
||||
@@ -88,3 +88,7 @@ body .CodeMirror {
|
||||
.excalidraw.theme--dark {
|
||||
--theme-filter: invert(80%) hue-rotate(180deg) !important;
|
||||
}
|
||||
|
||||
body .todo-list input[type="checkbox"]:not(:checked):before {
|
||||
border-color: var(--muted-text-color) !important;
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user