mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 18:36:30 +01:00 
			
		
		
		
	Merge branch 'master' into next60
This commit is contained in:
		
							
								
								
									
										7
									
								
								.eslintignore
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								.eslintignore
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | |||||||
|  | node_modules | ||||||
|  | dist | ||||||
|  | bin | ||||||
|  | docs | ||||||
|  | libraries | ||||||
|  | coverage | ||||||
|  | play | ||||||
							
								
								
									
										221
									
								
								.eslintrc.js
									
									
									
									
									
								
							
							
						
						
									
										221
									
								
								.eslintrc.js
									
									
									
									
									
								
							| @@ -1,16 +1,213 @@ | |||||||
| module.exports = { | module.exports = { | ||||||
|     "env": { |     env: { | ||||||
|         "browser": true, |         browser: true, | ||||||
|         "commonjs": true, |         commonjs: true, | ||||||
|         "es2021": true, |         es2021: true, | ||||||
|         "node": true |         node: true, | ||||||
|     }, |     }, | ||||||
|     "extends": "eslint:recommended", |     // plugins: ['prettier'], // to be activated | ||||||
|     "overrides": [ |     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' }, | ||||||
|  |                     }, | ||||||
|  |                 ], | ||||||
|  |             }, | ||||||
|  |         }, | ||||||
|     ], |     ], | ||||||
|     "parserOptions": { |     globals: { | ||||||
|         "ecmaVersion": "latest" |         $: true, | ||||||
|  |         jQuery: true, | ||||||
|  |         glob: true, | ||||||
|  |         log: true, | ||||||
|  |         EditorWatchdog: true, | ||||||
|  |         baseApiUrl: true, | ||||||
|  |         // \src\share\canvas_share.js | ||||||
|  |         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, | ||||||
|  |         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\note_content_renderer.js | ||||||
|  |         mermaid: true, | ||||||
|  |         // src\public\app\services\frontend_script_api.js | ||||||
|  |         dayjs: true, | ||||||
|  |         // \src\public\app\widgets\dialogs\markdown_import.js | ||||||
|  |         commonmark: 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, | ||||||
|  |         // | ||||||
|     }, |     }, | ||||||
|     "rules": { |     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' | ||||||
|  |     }, | ||||||
|  | }; | ||||||
|   | |||||||
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -12,3 +12,4 @@ server-package.json | |||||||
| .idea/httpRequests/ | .idea/httpRequests/ | ||||||
| data/ | data/ | ||||||
| tmp/ | tmp/ | ||||||
|  | .eslintcache | ||||||
							
								
								
									
										1
									
								
								.husky/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								.husky/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | |||||||
|  | _ | ||||||
							
								
								
									
										4
									
								
								.husky/pre-commit
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								.husky/pre-commit
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | |||||||
|  | #!/bin/sh | ||||||
|  | . "$(dirname "$0")/_/husky.sh" | ||||||
|  |  | ||||||
|  | #npx lint-staged | ||||||
							
								
								
									
										11
									
								
								.prettierrc.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								.prettierrc.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | |||||||
|  | //https://prettier.io/docs/en/options.html  | ||||||
|  | module.exports = { | ||||||
|  | 	semi: true, | ||||||
|  | 	trailingComma: 'es5', | ||||||
|  | 	singleQuote: true, | ||||||
|  | 	printWidth: 120, | ||||||
|  | 	tabWidth: 4, | ||||||
|  | 	// useTabs: false, | ||||||
|  | 	// bracketSpacing: true, | ||||||
|  | 	// htmlWhitespaceSensitivity: 'ignore', | ||||||
|  | }; | ||||||
							
								
								
									
										6
									
								
								.vscode/extensions.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								.vscode/extensions.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | |||||||
|  | { | ||||||
|  |   "recommendations": [ | ||||||
|  |     "dbaeumer.vscode-eslint", | ||||||
|  |     "esbenp.prettier-vscode", | ||||||
|  |   ] | ||||||
|  | } | ||||||
							
								
								
									
										33
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | |||||||
|  | { | ||||||
|  |   "[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", | ||||||
|  | } | ||||||
| @@ -8,7 +8,6 @@ if (config.https) { | |||||||
|     // built-in TLS (terminated by trilium) is not supported yet, PRs are welcome |     // built-in TLS (terminated by trilium) is not supported yet, PRs are welcome | ||||||
|     // for reverse proxy terminated TLS this will works since config.https will be false |     // for reverse proxy terminated TLS this will works since config.https will be false | ||||||
|     process.exit(0); |     process.exit(0); | ||||||
|     return; |  | ||||||
| } | } | ||||||
|  |  | ||||||
| const port = require('./src/services/port'); | const port = require('./src/services/port'); | ||||||
|   | |||||||
							
								
								
									
										2
									
								
								libraries/codemirror/addon/lint/eslint.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								libraries/codemirror/addon/lint/eslint.js
									
									
									
									
										vendored
									
									
								
							| @@ -46,7 +46,7 @@ | |||||||
|         const errors = new eslint().verify(text, { |         const errors = new eslint().verify(text, { | ||||||
|             root: true, |             root: true, | ||||||
|             parserOptions: { |             parserOptions: { | ||||||
|                 ecmaVersion: 2019 |                 ecmaVersion: 2022 | ||||||
|             }, |             }, | ||||||
|             extends: ['eslint:recommended', 'airbnb-base'], |             extends: ['eslint:recommended', 'airbnb-base'], | ||||||
|             env: { |             env: { | ||||||
|   | |||||||
							
								
								
									
										13
									
								
								nodemon.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								nodemon.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | |||||||
|  | { | ||||||
|  |     "restartable": "rs", | ||||||
|  |     "ignore": [".git", "node_modules/**/node_modules", "src/public/"], | ||||||
|  |     "verbose": false, | ||||||
|  |     "execMap": { | ||||||
|  |         "js": "node --harmony" | ||||||
|  |     }, | ||||||
|  |     "watch": ["src/"], | ||||||
|  |     "env": { | ||||||
|  |         "NODE_ENV": "development" | ||||||
|  |     }, | ||||||
|  |     "ext": "js,json" | ||||||
|  | } | ||||||
							
								
								
									
										27
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										27
									
								
								package.json
									
									
									
									
									
								
							| @@ -2,7 +2,7 @@ | |||||||
|   "name": "trilium", |   "name": "trilium", | ||||||
|   "productName": "Trilium Notes", |   "productName": "Trilium Notes", | ||||||
|   "description": "Trilium Notes", |   "description": "Trilium Notes", | ||||||
|   "version": "0.59.3", |   "version": "0.59.4", | ||||||
|   "license": "AGPL-3.0-only", |   "license": "AGPL-3.0-only", | ||||||
|   "main": "electron.js", |   "main": "electron.js", | ||||||
|   "bin": { |   "bin": { | ||||||
| @@ -13,20 +13,22 @@ | |||||||
|     "url": "https://github.com/zadam/trilium.git" |     "url": "https://github.com/zadam/trilium.git" | ||||||
|   }, |   }, | ||||||
|   "scripts": { |   "scripts": { | ||||||
|     "start-server": "cross-env TRILIUM_DATA_DIR=./data TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 node ./src/www", |     "start-server": "cross-env TRILIUM_DATA_DIR=./data TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 nodemon ./src/www", | ||||||
|     "start-server-no-dir": "cross-env TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 node ./src/www", |     "start-server-no-dir": "cross-env TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 nodemon ./src/www", | ||||||
|     "start-electron": "cross-env TRILIUM_DATA_DIR=./data TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 TRILIUM_ENV=dev electron --inspect=5858 .", |     "start-electron": "cross-env TRILIUM_DATA_DIR=./data TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 TRILIUM_ENV=dev electron --inspect=5858 .", | ||||||
|     "start-electron-no-dir": "cross-env TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 electron --inspect=5858 .", |     "start-electron-no-dir": "cross-env TRILIUM_ENV=dev TRILIUM_SYNC_SERVER_HOST=http://tsyncserver:4000 electron --inspect=5858 .", | ||||||
|     "switch-server": "rm -rf ./node_modules/better-sqlite3 && npm install", |     "switch-server": "rm -rf ./node_modules/better-sqlite3 && npm install", | ||||||
|     "switch-electron": "rm -rf ./node_modules/better-sqlite3 && npm install && ./node_modules/.bin/electron-rebuild", |     "switch-electron": "./node_modules/.bin/electron-rebuild", | ||||||
|     "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-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/right_panel_widget.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/right_panel_widget.js", | ||||||
|     "build-docs": "npm run build-backend-docs && npm run build-frontend-docs", |     "build-docs": "npm run build-backend-docs && npm run build-frontend-docs", | ||||||
|     "webpack": "npx webpack -c webpack-desktop.config.js && npx webpack -c webpack-mobile.config.js && npx webpack -c webpack-setup.config.js", |     "webpack": "webpack -c webpack.config.js", | ||||||
|     "test-jasmine": "jasmine", |     "test-jasmine": "jasmine", | ||||||
|     "test-es6": "node -r esm spec-es6/attribute_parser.spec.js ", |     "test-es6": "node -r esm spec-es6/attribute_parser.spec.js ", | ||||||
|     "test": "npm run test-jasmine && npm run test-es6", |     "test": "npm run test-jasmine && npm run test-es6", | ||||||
|     "postinstall": "rimraf ./node_modules/canvas" |     "postinstall": "rimraf ./node_modules/canvas", | ||||||
|  |     "lint": "eslint .  --cache", | ||||||
|  |     "prepare": "husky install" | ||||||
|   }, |   }, | ||||||
|   "dependencies": { |   "dependencies": { | ||||||
|     "@braintree/sanitize-url": "6.0.2", |     "@braintree/sanitize-url": "6.0.2", | ||||||
| @@ -100,15 +102,28 @@ | |||||||
|     "electron-packager": "17.1.1", |     "electron-packager": "17.1.1", | ||||||
|     "electron-rebuild": "3.2.9", |     "electron-rebuild": "3.2.9", | ||||||
|     "eslint": "^8.38.0", |     "eslint": "^8.38.0", | ||||||
|  |     "eslint-config-airbnb-base": "^15.0.0", | ||||||
|  |     "eslint-config-prettier": "^8.8.0", | ||||||
|  |     "eslint-plugin-import": "^2.27.5", | ||||||
|  |     "eslint-plugin-jsonc": "^2.7.0", | ||||||
|  |     "eslint-plugin-prettier": "^4.2.1", | ||||||
|     "esm": "3.2.25", |     "esm": "3.2.25", | ||||||
|  |     "husky": "^8.0.3", | ||||||
|  |     "jsonc-eslint-parser": "^2.2.0", | ||||||
|  |     "lint-staged": "^13.2.1", | ||||||
|     "jasmine": "4.6.0", |     "jasmine": "4.6.0", | ||||||
|     "jsdoc": "4.0.2", |     "jsdoc": "4.0.2", | ||||||
|     "lorem-ipsum": "2.0.8", |     "lorem-ipsum": "2.0.8", | ||||||
|  |     "prettier": "2.8.7", | ||||||
|  |     "nodemon": "^2.0.22", | ||||||
|     "rcedit": "3.0.1", |     "rcedit": "3.0.1", | ||||||
|     "webpack": "5.78.0", |     "webpack": "5.78.0", | ||||||
|     "webpack-cli": "5.0.1" |     "webpack-cli": "5.0.1" | ||||||
|   }, |   }, | ||||||
|   "optionalDependencies": { |   "optionalDependencies": { | ||||||
|     "electron-installer-debian": "3.1.0" |     "electron-installer-debian": "3.1.0" | ||||||
|  |   }, | ||||||
|  |   "lint-staged": { | ||||||
|  |     "*.js": "eslint --cache --fix" | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -24,49 +24,12 @@ function isNotePathArchived(notePath) { | |||||||
|     return false; |     return false; | ||||||
| } | } | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * This assumes that note is available. "archived" note means that there isn't a single non-archived note-path |  | ||||||
|  * leading to this note. |  | ||||||
|  * |  | ||||||
|  * @param noteId |  | ||||||
|  */ |  | ||||||
| function isArchived(noteId) { |  | ||||||
|     const notePath = getSomePath(noteId); |  | ||||||
|  |  | ||||||
|     return isNotePathArchived(notePath); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * @param {string} noteId |  | ||||||
|  * @param {string} ancestorNoteId |  | ||||||
|  * @returns {boolean} - true if given noteId has ancestorNoteId in any of its paths (even archived) |  | ||||||
|  */ |  | ||||||
| function isInAncestor(noteId, ancestorNoteId) { |  | ||||||
|     if (ancestorNoteId === 'root' || ancestorNoteId === noteId) { |  | ||||||
|         return true; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     const note = becca.notes[noteId]; |  | ||||||
|  |  | ||||||
|     if (!note) { |  | ||||||
|         return false; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     for (const parentNote of note.parents) { |  | ||||||
|         if (isInAncestor(parentNote.noteId, ancestorNoteId)) { |  | ||||||
|             return true; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     return false; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| function getNoteTitle(childNoteId, parentNoteId) { | function getNoteTitle(childNoteId, parentNoteId) { | ||||||
|     const childNote = becca.notes[childNoteId]; |     const childNote = becca.notes[childNoteId]; | ||||||
|     const parentNote = becca.notes[parentNoteId]; |     const parentNote = becca.notes[parentNoteId]; | ||||||
|  |  | ||||||
|     if (!childNote) { |     if (!childNote) { | ||||||
|         log.info(`Cannot find note in cache for noteId '${childNoteId}'`); |         log.info(`Cannot find note '${childNoteId}'`); | ||||||
|         return "[error fetching title]"; |         return "[error fetching title]"; | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -119,107 +82,8 @@ function getNoteTitleForPath(notePathArray) { | |||||||
|     return titles.join(' / '); |     return titles.join(' / '); | ||||||
| } | } | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Returns notePath for noteId from cache. Note hoisting is respected. |  | ||||||
|  * Archived (and hidden) notes are also returned, but non-archived paths are preferred if available |  | ||||||
|  * - this means that archived paths is returned only if there's no non-archived path |  | ||||||
|  * - you can check whether returned path is archived using isArchived |  | ||||||
|  * |  | ||||||
|  * @param {BNote} note |  | ||||||
|  * @param {string[]} path |  | ||||||
|  */ |  | ||||||
| function getSomePath(note, path = []) { |  | ||||||
|     // first try to find note within hoisted note, otherwise take any existing note path |  | ||||||
|     return getSomePathInner(note, path, true) |  | ||||||
|         || getSomePathInner(note, path, false); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * @param {BNote} note |  | ||||||
|  * @param {string[]} path |  | ||||||
|  * @param {boolean}respectHoisting |  | ||||||
|  * @returns {string[]|false} |  | ||||||
|  */ |  | ||||||
| function getSomePathInner(note, path, respectHoisting) { |  | ||||||
|     if (note.isRoot()) { |  | ||||||
|         const foundPath = [...path, note.noteId]; |  | ||||||
|         foundPath.reverse(); |  | ||||||
|  |  | ||||||
|         if (respectHoisting && !foundPath.includes(cls.getHoistedNoteId())) { |  | ||||||
|             return false; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return foundPath; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     const parents = note.parents; |  | ||||||
|     if (parents.length === 0) { |  | ||||||
|         console.log(`Note '${note.noteId}' - '${note.title}' has no parents.`); |  | ||||||
|  |  | ||||||
|         return false; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     for (const parentNote of parents) { |  | ||||||
|         const retPath = getSomePathInner(parentNote, [...path, note.noteId], respectHoisting); |  | ||||||
|  |  | ||||||
|         if (retPath) { |  | ||||||
|             return retPath; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     return false; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| function getNotePath(noteId) { |  | ||||||
|     const note = becca.notes[noteId]; |  | ||||||
|  |  | ||||||
|     if (!note) { |  | ||||||
|         console.trace(`Cannot find note '${noteId}' in cache.`); |  | ||||||
|         return; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     const retPath = getSomePath(note); |  | ||||||
|  |  | ||||||
|     if (retPath) { |  | ||||||
|         const noteTitle = getNoteTitleForPath(retPath); |  | ||||||
|  |  | ||||||
|         let branchId; |  | ||||||
|  |  | ||||||
|         if (note.isRoot()) { |  | ||||||
|             branchId = 'none_root'; |  | ||||||
|         } |  | ||||||
|         else { |  | ||||||
|             const parentNote = note.parents[0]; |  | ||||||
|             branchId = becca.getBranchFromChildAndParent(noteId, parentNote.noteId).branchId; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         return { |  | ||||||
|             noteId: noteId, |  | ||||||
|             branchId: branchId, |  | ||||||
|             title: noteTitle, |  | ||||||
|             notePath: retPath, |  | ||||||
|             path: retPath.join('/') |  | ||||||
|         }; |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * @param noteId |  | ||||||
|  * @returns {boolean} - true if note exists (is not deleted) and is available in current note hoisting |  | ||||||
|  */ |  | ||||||
| function isAvailable(noteId) { |  | ||||||
|     const notePath = getNotePath(noteId); |  | ||||||
|  |  | ||||||
|     return !!notePath; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|     getSomePath, |  | ||||||
|     getNotePath, |  | ||||||
|     getNoteTitle, |     getNoteTitle, | ||||||
|     getNoteTitleForPath, |     getNoteTitleForPath, | ||||||
|     isAvailable, |  | ||||||
|     isArchived, |  | ||||||
|     isInAncestor, |  | ||||||
|     isNotePathArchived |     isNotePathArchived | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -688,6 +688,21 @@ class BNote extends AbstractBeccaEntity { | |||||||
|         return this.hasAttribute('label', 'archived'); |         return this.hasAttribute('label', 'archived'); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     areAllNotePathsArchived() { | ||||||
|  |         // there's a slight difference between note being itself archived and all its note paths being archived | ||||||
|  |         // - note is archived when it itself has an archived label or inherits it | ||||||
|  |         // - note does not have or inherit archived label, but each note paths contains a note with (non-inheritable) | ||||||
|  |         //   archived label | ||||||
|  |  | ||||||
|  |         const bestNotePathRecord = this.getSortedNotePathRecords()[0]; | ||||||
|  |  | ||||||
|  |         if (!bestNotePathRecord) { | ||||||
|  |             throw new Error(`No note path available for note '${this.noteId}'`); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return bestNotePathRecord.isArchived; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     hasInheritableArchivedLabel() { |     hasInheritableArchivedLabel() { | ||||||
|         for (const attr of this.getAttributes()) { |         for (const attr of this.getAttributes()) { | ||||||
|             if (attr.name === 'archived' && attr.type === LABEL && attr.isInheritable) { |             if (attr.name === 'archived' && attr.type === LABEL && attr.isInheritable) { | ||||||
| @@ -1118,6 +1133,8 @@ class BNote extends AbstractBeccaEntity { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|  |      * Gives all possible note paths leading to this note. Paths containing search note are ignored (could form cycles) | ||||||
|  |      * | ||||||
|      * @returns {string[][]} - array of notePaths (each represented by array of noteIds constituting the particular note path) |      * @returns {string[][]} - array of notePaths (each represented by array of noteIds constituting the particular note path) | ||||||
|      */ |      */ | ||||||
|     getAllNotePaths() { |     getAllNotePaths() { | ||||||
| @@ -1125,18 +1142,73 @@ class BNote extends AbstractBeccaEntity { | |||||||
|             return [['root']]; |             return [['root']]; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         const notePaths = []; |         const parentNotes = this.getParentNotes(); | ||||||
|  |         let notePaths = []; | ||||||
|  |  | ||||||
|         for (const parentNote of this.getParentNotes()) { |         if (parentNotes.length === 1) { // optimization for most common case | ||||||
|             for (const parentPath of parentNote.getAllNotePaths()) { |             notePaths = parentNotes[0].getAllNotePaths(); | ||||||
|                 parentPath.push(this.noteId); |         } else { | ||||||
|                 notePaths.push(parentPath); |             notePaths = parentNotes.flatMap(parentNote => parentNote.getAllNotePaths()); | ||||||
|             } |         } | ||||||
|  |  | ||||||
|  |         for (const notePath of notePaths) { | ||||||
|  |             notePath.push(this.noteId); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         return notePaths; |         return notePaths; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * @param {string} [hoistedNoteId='root'] | ||||||
|  |      * @return {{isArchived: boolean, isInHoistedSubTree: boolean, notePath: string[], isHidden: boolean}[]} | ||||||
|  |      */ | ||||||
|  |     getSortedNotePathRecords(hoistedNoteId = 'root') { | ||||||
|  |         const isHoistedRoot = hoistedNoteId === 'root'; | ||||||
|  |  | ||||||
|  |         const notePaths = this.getAllNotePaths().map(path => ({ | ||||||
|  |             notePath: path, | ||||||
|  |             isInHoistedSubTree: isHoistedRoot || path.includes(hoistedNoteId), | ||||||
|  |             isArchived: path.some(noteId => this.becca.notes[noteId].isArchived), | ||||||
|  |             isHidden: path.includes('_hidden') | ||||||
|  |         })); | ||||||
|  |  | ||||||
|  |         notePaths.sort((a, b) => { | ||||||
|  |             if (a.isInHoistedSubTree !== b.isInHoistedSubTree) { | ||||||
|  |                 return a.isInHoistedSubTree ? -1 : 1; | ||||||
|  |             } else if (a.isArchived !== b.isArchived) { | ||||||
|  |                 return a.isArchived ? 1 : -1; | ||||||
|  |             } else if (a.isHidden !== b.isHidden) { | ||||||
|  |                 return a.isHidden ? 1 : -1; | ||||||
|  |             } else { | ||||||
|  |                 return a.notePath.length - b.notePath.length; | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         return notePaths; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Returns note path considered to be the "best" | ||||||
|  |      * | ||||||
|  |      * @param {string} [hoistedNoteId='root'] | ||||||
|  |      * @return {string[]} array of noteIds constituting the particular note path | ||||||
|  |      */ | ||||||
|  |     getBestNotePath(hoistedNoteId = 'root') { | ||||||
|  |         return this.getSortedNotePathRecords(hoistedNoteId)[0]?.notePath; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Returns note path considered to be the "best" | ||||||
|  |      * | ||||||
|  |      * @param {string} [hoistedNoteId='root'] | ||||||
|  |      * @return {string} serialized note path (e.g. 'root/a1h315/js725h') | ||||||
|  |      */ | ||||||
|  |     getBestNotePathString(hoistedNoteId = 'root') { | ||||||
|  |         const notePath = this.getBestNotePath(hoistedNoteId); | ||||||
|  |  | ||||||
|  |         return notePath?.join("/"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * @return boolean - true if there's no non-hidden path, note is not cloned to the visible tree |      * @return boolean - true if there's no non-hidden path, note is not cloned to the visible tree | ||||||
|      */ |      */ | ||||||
|   | |||||||
| @@ -404,7 +404,7 @@ async function findSimilarNotes(noteId) { | |||||||
|         let score = computeScore(candidateNote); |         let score = computeScore(candidateNote); | ||||||
|  |  | ||||||
|         if (score >= 1.5) { |         if (score >= 1.5) { | ||||||
|             const notePath = beccaService.getSomePath(candidateNote); |             const notePath = candidateNote.getBestNotePath(); | ||||||
|  |  | ||||||
|             // this takes care of note hoisting |             // this takes care of note hoisting | ||||||
|             if (!notePath) { |             if (!notePath) { | ||||||
|   | |||||||
| @@ -413,7 +413,12 @@ export default class TabManager extends Component { | |||||||
|             await this.triggerEvent('beforeNoteContextRemove', { ntxIds: ntxIdsToRemove }); |             await this.triggerEvent('beforeNoteContextRemove', { ntxIds: ntxIdsToRemove }); | ||||||
|  |  | ||||||
|             if (!noteContextToRemove.isMainContext()) { |             if (!noteContextToRemove.isMainContext()) { | ||||||
|                 await this.activateNoteContext(noteContextToRemove.getMainContext().ntxId); |                 const siblings = noteContextToRemove.getMainContext().getSubContexts(); | ||||||
|  |                 const idx = siblings.findIndex(nc => nc.ntxId === noteContextToRemove.ntxId); | ||||||
|  |                 const contextToActivateIdx = idx === siblings.length - 1 ? idx - 1 : idx + 1; | ||||||
|  |                 const contextToActivate = siblings[contextToActivateIdx]; | ||||||
|  |  | ||||||
|  |                 await this.activateNoteContext(contextToActivate.ntxId); | ||||||
|             } |             } | ||||||
|             else if (this.mainNoteContexts.length <= 1) { |             else if (this.mainNoteContexts.length <= 1) { | ||||||
|                 await this.openAndActivateEmptyTab(); |                 await this.openAndActivateEmptyTab(); | ||||||
|   | |||||||
| @@ -268,6 +268,11 @@ class FNote { | |||||||
|         return this.__filterAttrs(this.__getCachedAttributes([]), type, name); |         return this.__filterAttrs(this.__getCachedAttributes([]), type, name); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * @param {string[]} path | ||||||
|  |      * @return {FAttribute[]} | ||||||
|  |      * @private | ||||||
|  |      */ | ||||||
|     __getCachedAttributes(path) { |     __getCachedAttributes(path) { | ||||||
|         // notes/clones cannot form tree cycles, it is possible to create attribute inheritance cycle via templates |         // notes/clones cannot form tree cycles, it is possible to create attribute inheritance cycle via templates | ||||||
|         // when template instance is a parent of template itself |         // when template instance is a parent of template itself | ||||||
| @@ -320,63 +325,49 @@ class FNote { | |||||||
|         return this.noteId === 'root'; |         return this.noteId === 'root'; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     getAllNotePaths(encounteredNoteIds = null) { |     /** | ||||||
|  |      * Gives all possible note paths leading to this note. Paths containing search note are ignored (could form cycles) | ||||||
|  |      * | ||||||
|  |      * @returns {string[][]} - array of notePaths (each represented by array of noteIds constituting the particular note path) | ||||||
|  |      */ | ||||||
|  |     getAllNotePaths() { | ||||||
|         if (this.noteId === 'root') { |         if (this.noteId === 'root') { | ||||||
|             return [['root']]; |             return [['root']]; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         if (!encounteredNoteIds) { |  | ||||||
|             encounteredNoteIds = new Set(); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         encounteredNoteIds.add(this.noteId); |  | ||||||
|  |  | ||||||
|         const parentNotes = this.getParentNotes(); |         const parentNotes = this.getParentNotes(); | ||||||
|         let paths; |         let notePaths = []; | ||||||
|  |  | ||||||
|         if (parentNotes.length === 1) { // optimization for the most common case |         if (parentNotes.length === 1) { // optimization for most common case | ||||||
|             if (encounteredNoteIds.has(parentNotes[0].noteId)) { |             notePaths = parentNotes[0].getAllNotePaths(); | ||||||
|                 return []; |         } else { | ||||||
|             } |             notePaths = parentNotes.flatMap(parentNote => parentNote.getAllNotePaths()); | ||||||
|             else { |  | ||||||
|                 paths = parentNotes[0].getAllNotePaths(encounteredNoteIds); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|         else { |  | ||||||
|             paths = []; |  | ||||||
|  |  | ||||||
|             for (const parentNote of parentNotes) { |  | ||||||
|                 if (encounteredNoteIds.has(parentNote.noteId)) { |  | ||||||
|                     continue; |  | ||||||
|                 } |  | ||||||
|  |  | ||||||
|                 const newSet = new Set(encounteredNoteIds); |  | ||||||
|  |  | ||||||
|                 paths.push(...parentNote.getAllNotePaths(newSet)); |  | ||||||
|             } |  | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         for (const path of paths) { |         for (const notePath of notePaths) { | ||||||
|             path.push(this.noteId); |             notePath.push(this.noteId); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         return paths; |         return notePaths; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     getSortedNotePaths(hoistedNotePath = 'root') { |     /** | ||||||
|  |      * @param {string} [hoistedNoteId='root'] | ||||||
|  |      * @return {{isArchived: boolean, isInHoistedSubTree: boolean, notePath: string[], isHidden: boolean}[]} | ||||||
|  |      */ | ||||||
|  |     getSortedNotePathRecords(hoistedNoteId = 'root') { | ||||||
|  |         const isHoistedRoot = hoistedNoteId === 'root'; | ||||||
|  |  | ||||||
|         const notePaths = this.getAllNotePaths().map(path => ({ |         const notePaths = this.getAllNotePaths().map(path => ({ | ||||||
|             notePath: path, |             notePath: path, | ||||||
|             isInHoistedSubTree: path.includes(hoistedNotePath), |             isInHoistedSubTree: isHoistedRoot || path.includes(hoistedNoteId), | ||||||
|             isArchived: path.find(noteId => froca.notes[noteId].isArchived), |             isArchived: path.some(noteId => froca.notes[noteId].isArchived), | ||||||
|             isSearch: path.find(noteId => froca.notes[noteId].type === 'search'), |  | ||||||
|             isHidden: path.includes('_hidden') |             isHidden: path.includes('_hidden') | ||||||
|         })); |         })); | ||||||
|  |  | ||||||
|         notePaths.sort((a, b) => { |         notePaths.sort((a, b) => { | ||||||
|             if (a.isInHoistedSubTree !== b.isInHoistedSubTree) { |             if (a.isInHoistedSubTree !== b.isInHoistedSubTree) { | ||||||
|                 return a.isInHoistedSubTree ? -1 : 1; |                 return a.isInHoistedSubTree ? -1 : 1; | ||||||
|             } else if (a.isSearch !== b.isSearch) { |  | ||||||
|                 return a.isSearch ? 1 : -1; |  | ||||||
|             } else if (a.isArchived !== b.isArchived) { |             } else if (a.isArchived !== b.isArchived) { | ||||||
|                 return a.isArchived ? 1 : -1; |                 return a.isArchived ? 1 : -1; | ||||||
|             } else if (a.isHidden !== b.isHidden) { |             } else if (a.isHidden !== b.isHidden) { | ||||||
| @@ -389,6 +380,28 @@ class FNote { | |||||||
|         return notePaths; |         return notePaths; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Returns note path considered to be the "best" | ||||||
|  |      * | ||||||
|  |      * @param {string} [hoistedNoteId='root'] | ||||||
|  |      * @return {string[]} array of noteIds constituting the particular note path | ||||||
|  |      */ | ||||||
|  |     getBestNotePath(hoistedNoteId = 'root') { | ||||||
|  |         return this.getSortedNotePathRecords(hoistedNoteId)[0]?.notePath; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Returns note path considered to be the "best" | ||||||
|  |      * | ||||||
|  |      * @param {string} [hoistedNoteId='root'] | ||||||
|  |      * @return {string} serialized note path (e.g. 'root/a1h315/js725h') | ||||||
|  |      */ | ||||||
|  |     getBestNotePathString(hoistedNoteId = 'root') { | ||||||
|  |         const notePath = this.getBestNotePath(hoistedNoteId); | ||||||
|  |  | ||||||
|  |         return notePath?.join("/"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * @return boolean - true if there's no non-hidden path, note is not cloned to the visible tree |      * @return boolean - true if there's no non-hidden path, note is not cloned to the visible tree | ||||||
|      */ |      */ | ||||||
| @@ -412,6 +425,13 @@ class FNote { | |||||||
|         return true; |         return true; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * @param {FAttribute[]} attributes | ||||||
|  |      * @param {string} type | ||||||
|  |      * @param {string} name | ||||||
|  |      * @return {FAttribute[]} | ||||||
|  |      * @private | ||||||
|  |      */ | ||||||
|     __filterAttrs(attributes, type, name) { |     __filterAttrs(attributes, type, name) { | ||||||
|         this.__validateTypeName(type, name); |         this.__validateTypeName(type, name); | ||||||
|  |  | ||||||
| @@ -541,7 +561,9 @@ class FNote { | |||||||
|      * @returns {boolean} true if note has an attribute with given type and name (including inherited) |      * @returns {boolean} true if note has an attribute with given type and name (including inherited) | ||||||
|      */ |      */ | ||||||
|     hasAttribute(type, name) { |     hasAttribute(type, name) { | ||||||
|         return !!this.getAttribute(type, name); |         const attributes = this.getAttributes(); | ||||||
|  |  | ||||||
|  |         return attributes.some(attr => attr.name === name && attr.type === type); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|   | |||||||
| @@ -227,7 +227,7 @@ async function cloneNoteToBranch(childNoteId, parentBranchId, prefix) { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| async function cloneNoteToNote(childNoteId, parentNoteId, prefix) { | async function cloneNoteToParentNote(childNoteId, parentNoteId, prefix) { | ||||||
|     const resp = await server.put(`notes/${childNoteId}/clone-to-note/${parentNoteId}`, { |     const resp = await server.put(`notes/${childNoteId}/clone-to-note/${parentNoteId}`, { | ||||||
|         prefix: prefix |         prefix: prefix | ||||||
|     }); |     }); | ||||||
| @@ -254,5 +254,5 @@ export default { | |||||||
|     moveNodeUpInHierarchy, |     moveNodeUpInHierarchy, | ||||||
|     cloneNoteAfter, |     cloneNoteAfter, | ||||||
|     cloneNoteToBranch, |     cloneNoteToBranch, | ||||||
|     cloneNoteToNote, |     cloneNoteToParentNote, | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -140,7 +140,7 @@ async function processBranchChange(loadResults, ec) { | |||||||
|     const childNote = froca.notes[ec.entity.noteId]; |     const childNote = froca.notes[ec.entity.noteId]; | ||||||
|     let parentNote = froca.notes[ec.entity.parentNoteId]; |     let parentNote = froca.notes[ec.entity.parentNoteId]; | ||||||
|  |  | ||||||
|     if (childNote && !parentNote) { |     if (childNote && !childNote.isRoot() && !parentNote) { | ||||||
|         // a branch cannot exist without the parent |         // a branch cannot exist without the parent | ||||||
|         // a note loaded into froca has to also contain all its ancestors |         // a note loaded into froca has to also contain all its ancestors | ||||||
|         // this problem happened e.g. in sharing where _share was hidden and thus not loaded |         // this problem happened e.g. in sharing where _share was hidden and thus not loaded | ||||||
|   | |||||||
| @@ -2,7 +2,6 @@ import server from "./server.js"; | |||||||
| import appContext from "../components/app_context.js"; | import appContext from "../components/app_context.js"; | ||||||
| import utils from './utils.js'; | import utils from './utils.js'; | ||||||
| import noteCreateService from './note_create.js'; | import noteCreateService from './note_create.js'; | ||||||
| import treeService from './tree.js'; |  | ||||||
| import froca from "./froca.js"; | import froca from "./froca.js"; | ||||||
|  |  | ||||||
| // this key needs to have this value, so it's hit by the tooltip | // this key needs to have this value, so it's hit by the tooltip | ||||||
| @@ -188,7 +187,8 @@ function initNoteAutocomplete($el, options) { | |||||||
|                 templateNoteId: templateNoteId |                 templateNoteId: templateNoteId | ||||||
|             }); |             }); | ||||||
|  |  | ||||||
|             suggestion.notePath = treeService.getSomeNotePath(note); |             const hoistedNoteId = appContext.tabManager.getActiveContext()?.hoistedNoteId; | ||||||
|  |             suggestion.notePath = note.getBestNotePathString(hoistedNoteId); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         $el.setSelectedNotePath(suggestion.notePath); |         $el.setSelectedNotePath(suggestion.notePath); | ||||||
|   | |||||||
| @@ -4,6 +4,7 @@ import froca from "./froca.js"; | |||||||
| import utils from "./utils.js"; | import utils from "./utils.js"; | ||||||
| import attributeRenderer from "./attribute_renderer.js"; | import attributeRenderer from "./attribute_renderer.js"; | ||||||
| import noteContentRenderer from "./note_content_renderer.js"; | import noteContentRenderer from "./note_content_renderer.js"; | ||||||
|  | import appContext from "../components/app_context.js"; | ||||||
|  |  | ||||||
| function setupGlobalTooltip() { | function setupGlobalTooltip() { | ||||||
|     $(document).on("mouseenter", "a", mouseEnterHandler); |     $(document).on("mouseenter", "a", mouseEnterHandler); | ||||||
| @@ -77,13 +78,14 @@ async function renderTooltip(note) { | |||||||
|         return '<div>Note has been deleted.</div>'; |         return '<div>Note has been deleted.</div>'; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     const someNotePath = treeService.getSomeNotePath(note); |     const hoistedNoteId = appContext.tabManager.getActiveContext()?.hoistedNoteId; | ||||||
|  |     const bestNotePath = note.getBestNotePathString(hoistedNoteId); | ||||||
|  |  | ||||||
|     if (!someNotePath) { |     if (!bestNotePath) { | ||||||
|         return; |         return; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     let content = `<h5 class="note-tooltip-title">${(await treeService.getNoteTitleWithPathAsSuffix(someNotePath)).prop('outerHTML')}</h5>`; |     let content = `<h5 class="note-tooltip-title">${(await treeService.getNoteTitleWithPathAsSuffix(bestNotePath)).prop('outerHTML')}</h5>`; | ||||||
|  |  | ||||||
|     const {$renderedAttributes} = await attributeRenderer.renderNormalAttributes(note); |     const {$renderedAttributes} = await attributeRenderer.renderNormalAttributes(note); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -79,14 +79,10 @@ async function resolveNotePathToSegments(notePath, hoistedNoteId = 'root', logEr | |||||||
|                         You can ignore this message as it is mostly harmless.`); |                         You can ignore this message as it is mostly harmless.`); | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|                 const someNotePath = getSomeNotePath(child, hoistedNoteId); |                 const bestNotePath = child.getBestNotePath(hoistedNoteId); | ||||||
|  |  | ||||||
|                 if (someNotePath) { // in case it's root the path may be empty |                 if (bestNotePath) { | ||||||
|                     const pathToRoot = someNotePath.split("/").reverse().slice(1); |                     const pathToRoot = bestNotePath.reverse().slice(1); | ||||||
|  |  | ||||||
|                     if (!pathToRoot.includes("root")) { |  | ||||||
|                         pathToRoot.push('root'); |  | ||||||
|                     } |  | ||||||
|  |  | ||||||
|                     for (const noteId of pathToRoot) { |                     for (const noteId of pathToRoot) { | ||||||
|                         effectivePathSegments.push(noteId); |                         effectivePathSegments.push(noteId); | ||||||
| @@ -109,31 +105,17 @@ async function resolveNotePathToSegments(notePath, hoistedNoteId = 'root', logEr | |||||||
|     else { |     else { | ||||||
|         const note = await froca.getNote(getNoteIdFromNotePath(notePath)); |         const note = await froca.getNote(getNoteIdFromNotePath(notePath)); | ||||||
|  |  | ||||||
|         const someNotePathSegments = getSomeNotePathSegments(note, hoistedNoteId); |         const bestNotePath = note.getBestNotePath(hoistedNoteId); | ||||||
|  |  | ||||||
|         if (!someNotePathSegments) { |         if (!bestNotePath) { | ||||||
|             throw new Error(`Did not find any path segments for ${note.toString()}, hoisted note ${hoistedNoteId}`); |             throw new Error(`Did not find any path segments for '${note.toString()}', hoisted note '${hoistedNoteId}'`); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         // if there isn't actually any note path with hoisted note then return the original resolved note path |         // if there isn't actually any note path with hoisted note then return the original resolved note path | ||||||
|         return someNotePathSegments.includes(hoistedNoteId) ? someNotePathSegments : effectivePathSegments; |         return bestNotePath.includes(hoistedNoteId) ? bestNotePath : effectivePathSegments; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| function getSomeNotePathSegments(note, hoistedNotePath = 'root') { |  | ||||||
|     utils.assertArguments(note); |  | ||||||
|  |  | ||||||
|     const notePaths = note.getSortedNotePaths(hoistedNotePath); |  | ||||||
|  |  | ||||||
|     return notePaths.length > 0 ? notePaths[0].notePath : null; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| function getSomeNotePath(note, hoistedNotePath = 'root') { |  | ||||||
|     const notePath = getSomeNotePathSegments(note, hoistedNotePath); |  | ||||||
|  |  | ||||||
|     return notePath === null ? null : notePath.join('/'); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| ws.subscribeToMessages(message => { | ws.subscribeToMessages(message => { | ||||||
|    if (message.type === 'openNote') { |    if (message.type === 'openNote') { | ||||||
|        appContext.tabManager.activateOrOpenNote(message.noteId); |        appContext.tabManager.activateOrOpenNote(message.noteId); | ||||||
| @@ -341,16 +323,6 @@ function isNotePathInAddress() { | |||||||
|         || (notePath === '' && !!ntxId); |         || (notePath === '' && !!ntxId); | ||||||
| } | } | ||||||
|  |  | ||||||
| function parseNotePath(notePath) { |  | ||||||
|     let noteIds = notePath.split('/'); |  | ||||||
|  |  | ||||||
|     if (noteIds[0] !== 'root') { |  | ||||||
|         noteIds = ['root'].concat(noteIds); |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     return noteIds; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| function isNotePathInHiddenSubtree(notePath) { | function isNotePathInHiddenSubtree(notePath) { | ||||||
|     return notePath?.includes("root/_hidden"); |     return notePath?.includes("root/_hidden"); | ||||||
| } | } | ||||||
| @@ -358,8 +330,6 @@ function isNotePathInHiddenSubtree(notePath) { | |||||||
| export default { | export default { | ||||||
|     resolveNotePath, |     resolveNotePath, | ||||||
|     resolveNotePathToSegments, |     resolveNotePathToSegments, | ||||||
|     getSomeNotePath, |  | ||||||
|     getSomeNotePathSegments, |  | ||||||
|     getParentProtectedStatus, |     getParentProtectedStatus, | ||||||
|     getNotePath, |     getNotePath, | ||||||
|     getNoteIdFromNotePath, |     getNoteIdFromNotePath, | ||||||
| @@ -370,6 +340,5 @@ export default { | |||||||
|     getNoteTitleWithPathAsSuffix, |     getNoteTitleWithPathAsSuffix, | ||||||
|     parseNavigationStateFromAddress, |     parseNavigationStateFromAddress, | ||||||
|     isNotePathInAddress, |     isNotePathInAddress, | ||||||
|     parseNotePath, |  | ||||||
|     isNotePathInHiddenSubtree |     isNotePathInHiddenSubtree | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -1,6 +1,5 @@ | |||||||
| import server from "../../services/server.js"; | import server from "../../services/server.js"; | ||||||
| import froca from "../../services/froca.js"; | import froca from "../../services/froca.js"; | ||||||
| import treeService from "../../services/tree.js"; |  | ||||||
| import linkService from "../../services/link.js"; | import linkService from "../../services/link.js"; | ||||||
| import attributeAutocompleteService from "../../services/attribute_autocomplete.js"; | import attributeAutocompleteService from "../../services/attribute_autocomplete.js"; | ||||||
| import noteAutocompleteService from "../../services/note_autocomplete.js"; | import noteAutocompleteService from "../../services/note_autocomplete.js"; | ||||||
| @@ -9,6 +8,7 @@ import NoteContextAwareWidget from "../note_context_aware_widget.js"; | |||||||
| import SpacedUpdate from "../../services/spaced_update.js"; | import SpacedUpdate from "../../services/spaced_update.js"; | ||||||
| import utils from "../../services/utils.js"; | import utils from "../../services/utils.js"; | ||||||
| import shortcutService from "../../services/shortcuts.js"; | import shortcutService from "../../services/shortcuts.js"; | ||||||
|  | import appContext from "../../components/app_context.js"; | ||||||
|  |  | ||||||
| const TPL = ` | const TPL = ` | ||||||
| <div class="attr-detail"> | <div class="attr-detail"> | ||||||
| @@ -598,9 +598,10 @@ export default class AttributeDetailWidget extends NoteContextAwareWidget { | |||||||
|  |  | ||||||
|             const displayedResults = results.length <= DISPLAYED_NOTES ? results : results.slice(0, DISPLAYED_NOTES); |             const displayedResults = results.length <= DISPLAYED_NOTES ? results : results.slice(0, DISPLAYED_NOTES); | ||||||
|             const displayedNotes = await froca.getNotes(displayedResults.map(res => res.noteId)); |             const displayedNotes = await froca.getNotes(displayedResults.map(res => res.noteId)); | ||||||
|  |             const hoistedNoteId = appContext.tabManager.getActiveContext()?.hoistedNoteId; | ||||||
|  |  | ||||||
|             for (const note of displayedNotes) { |             for (const note of displayedNotes) { | ||||||
|                 const notePath = treeService.getSomeNotePath(note); |                 const notePath = note.getBestNotePathString(hoistedNoteId); | ||||||
|                 const $noteLink = await linkService.createNoteLink(notePath, {showNotePath: true}); |                 const $noteLink = await linkService.createNoteLink(notePath, {showNotePath: true}); | ||||||
|  |  | ||||||
|                 this.$relatedNotesList.append( |                 this.$relatedNotesList.append( | ||||||
|   | |||||||
| @@ -7,7 +7,6 @@ import libraryLoader from "../../services/library_loader.js"; | |||||||
| import froca from "../../services/froca.js"; | import froca from "../../services/froca.js"; | ||||||
| import attributeRenderer from "../../services/attribute_renderer.js"; | import attributeRenderer from "../../services/attribute_renderer.js"; | ||||||
| import noteCreateService from "../../services/note_create.js"; | import noteCreateService from "../../services/note_create.js"; | ||||||
| import treeService from "../../services/tree.js"; |  | ||||||
| import attributeService from "../../services/attributes.js"; | import attributeService from "../../services/attributes.js"; | ||||||
|  |  | ||||||
| const HELP_TEXT = ` | const HELP_TEXT = ` | ||||||
| @@ -503,7 +502,7 @@ export default class AttributeEditorWidget extends NoteContextAwareWidget { | |||||||
|             title: title |             title: title | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         return treeService.getSomeNotePath(note); |         return note.getBestNotePathString(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     async updateAttributeList(attributes) { |     async updateAttributeList(attributes) { | ||||||
|   | |||||||
| @@ -240,7 +240,7 @@ export default class NoteRevisionsDialog extends BasicWidget { | |||||||
|             if (this.$content.find('span.math-tex').length > 0) { |             if (this.$content.find('span.math-tex').length > 0) { | ||||||
|                 await libraryLoader.requireLibrary(libraryLoader.KATEX); |                 await libraryLoader.requireLibrary(libraryLoader.KATEX); | ||||||
|  |  | ||||||
|                 renderMathInElement($content[0], {trust: true}); |                 renderMathInElement(this.$content[0], {trust: true}); | ||||||
|             } |             } | ||||||
|         } else if (revisionItem.type === 'code' || revisionItem.type === 'mermaid') { |         } else if (revisionItem.type === 'code' || revisionItem.type === 'mermaid') { | ||||||
|             this.$content.html($("<pre>").text(fullNoteRevision.content)); |             this.$content.html($("<pre>").text(fullNoteRevision.content)); | ||||||
|   | |||||||
| @@ -1,7 +1,6 @@ | |||||||
| import linkService from '../../services/link.js'; | import linkService from '../../services/link.js'; | ||||||
| import utils from '../../services/utils.js'; | import utils from '../../services/utils.js'; | ||||||
| import server from '../../services/server.js'; | import server from '../../services/server.js'; | ||||||
| import treeService from "../../services/tree.js"; |  | ||||||
| import froca from "../../services/froca.js"; | import froca from "../../services/froca.js"; | ||||||
| import appContext from "../../components/app_context.js"; | import appContext from "../../components/app_context.js"; | ||||||
| import hoistedNoteService from "../../services/hoisted_note.js"; | import hoistedNoteService from "../../services/hoisted_note.js"; | ||||||
| @@ -108,7 +107,7 @@ export default class RecentChangesDialog extends BasicWidget { | |||||||
|                     } |                     } | ||||||
|                 } else { |                 } else { | ||||||
|                     const note = await froca.getNote(change.noteId); |                     const note = await froca.getNote(change.noteId); | ||||||
|                     const notePath = treeService.getSomeNotePath(note); |                     const notePath = note.getBestNotePathString(); | ||||||
|  |  | ||||||
|                     if (notePath) { |                     if (notePath) { | ||||||
|                         $noteLink = await linkService.createNoteLink(notePath, { |                         $noteLink = await linkService.createNoteLink(notePath, { | ||||||
|   | |||||||
| @@ -1,7 +1,6 @@ | |||||||
| import libraryLoader from "../services/library_loader.js"; | import libraryLoader from "../services/library_loader.js"; | ||||||
| import NoteContextAwareWidget from "./note_context_aware_widget.js"; | import NoteContextAwareWidget from "./note_context_aware_widget.js"; | ||||||
| import froca from "../services/froca.js"; | import froca from "../services/froca.js"; | ||||||
| import server from "../services/server.js"; |  | ||||||
|  |  | ||||||
| const TPL = `<div class="mermaid-widget"> | const TPL = `<div class="mermaid-widget"> | ||||||
|     <style> |     <style> | ||||||
| @@ -74,6 +73,8 @@ export default class MermaidWidget extends NoteContextAwareWidget { | |||||||
|  |  | ||||||
|         const wheelZoomLoaded = libraryLoader.requireLibrary(libraryLoader.WHEEL_ZOOM); |         const wheelZoomLoaded = libraryLoader.requireLibrary(libraryLoader.WHEEL_ZOOM); | ||||||
|  |  | ||||||
|  |         this.$errorContainer.hide(); | ||||||
|  |  | ||||||
|         try { |         try { | ||||||
|             await this.renderSvg(async renderedSvg => { |             await this.renderSvg(async renderedSvg => { | ||||||
|                 this.$display.html(renderedSvg); |                 this.$display.html(renderedSvg); | ||||||
| @@ -88,8 +89,6 @@ export default class MermaidWidget extends NoteContextAwareWidget { | |||||||
|                     speed: 20, |                     speed: 20, | ||||||
|                     zoomOnClick: false |                     zoomOnClick: false | ||||||
|                 }); |                 }); | ||||||
|  |  | ||||||
|                 this.$errorContainer.hide(); |  | ||||||
|             }); |             }); | ||||||
|         } catch (e) { |         } catch (e) { | ||||||
|             this.$errorMessage.text(e.message); |             this.$errorMessage.text(e.message); | ||||||
|   | |||||||
| @@ -212,7 +212,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget { | |||||||
|             return false; |             return false; | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         this.$treeSettingsPopup.on("click", e => { e.stopPropagation(); }); |         this.$treeSettingsPopup.on("click", e => {e.stopPropagation();}); | ||||||
|  |  | ||||||
|         $(document).on('click', () => this.$treeSettingsPopup.hide()); |         $(document).on('click', () => this.$treeSettingsPopup.hide()); | ||||||
|  |  | ||||||
| @@ -239,12 +239,12 @@ export default class NoteTreeWidget extends NoteContextAwareWidget { | |||||||
|  |  | ||||||
|         // code inspired by https://gist.github.com/jtsternberg/c272d7de5b967cec2d3d |         // code inspired by https://gist.github.com/jtsternberg/c272d7de5b967cec2d3d | ||||||
|         const isEnclosing = ($container, $sub) => { |         const isEnclosing = ($container, $sub) => { | ||||||
|             const conOffset           = $container.offset(); |             const conOffset = $container.offset(); | ||||||
|             const conDistanceFromTop  = conOffset.top + $container.outerHeight(true); |             const conDistanceFromTop = conOffset.top + $container.outerHeight(true); | ||||||
|             const conDistanceFromLeft = conOffset.left + $container.outerWidth(true); |             const conDistanceFromLeft = conOffset.left + $container.outerWidth(true); | ||||||
|  |  | ||||||
|             const subOffset           = $sub.offset(); |             const subOffset = $sub.offset(); | ||||||
|             const subDistanceFromTop  = subOffset.top + $sub.outerHeight(true); |             const subDistanceFromTop = subOffset.top + $sub.outerHeight(true); | ||||||
|             const subDistanceFromLeft = subOffset.left + $sub.outerWidth(true); |             const subDistanceFromLeft = subOffset.left + $sub.outerWidth(true); | ||||||
|  |  | ||||||
|             return conDistanceFromTop > subDistanceFromTop |             return conDistanceFromTop > subDistanceFromTop | ||||||
| @@ -653,7 +653,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget { | |||||||
|         return noteList; |         return noteList; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     updateNode(node) { |     async updateNode(node) { | ||||||
|         const note = froca.getNoteFromCache(node.data.noteId); |         const note = froca.getNoteFromCache(node.data.noteId); | ||||||
|         const branch = froca.getBranch(node.data.branchId); |         const branch = froca.getBranch(node.data.branchId); | ||||||
|  |  | ||||||
| @@ -677,7 +677,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget { | |||||||
|         node.title = utils.escapeHtml(title); |         node.title = utils.escapeHtml(title); | ||||||
|  |  | ||||||
|         if (node.isExpanded() !== branch.isExpanded) { |         if (node.isExpanded() !== branch.isExpanded) { | ||||||
|             node.setExpanded(branch.isExpanded, {noEvents: true, noAnimation: true}); |             await node.setExpanded(branch.isExpanded, {noEvents: true, noAnimation: true}); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         node.renderTitle(); |         node.renderTitle(); | ||||||
| @@ -829,7 +829,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget { | |||||||
|         await this.setExpandedStatusForSubtree(node, false); |         await this.setExpandedStatusForSubtree(node, false); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     collapseTreeEvent() { this.collapseTree(); } |     collapseTreeEvent() {this.collapseTree();} | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * @returns {FancytreeNode|null} |      * @returns {FancytreeNode|null} | ||||||
| @@ -900,7 +900,9 @@ export default class NoteTreeWidget extends NoteContextAwareWidget { | |||||||
|                 } |                 } | ||||||
|  |  | ||||||
|                 if (expand) { |                 if (expand) { | ||||||
|                     await parentNode.setExpanded(true, {noAnimation: true}); |                     if (!parentNode.isExpanded()) { | ||||||
|  |                         await parentNode.setExpanded(true, {noAnimation: true}); | ||||||
|  |                     } | ||||||
|  |  | ||||||
|                     // although previous line should set the expanded status, it seems to happen asynchronously, |                     // although previous line should set the expanded status, it seems to happen asynchronously, | ||||||
|                     // so we need to make sure it is set properly before calling updateNode which uses this flag |                     // so we need to make sure it is set properly before calling updateNode which uses this flag | ||||||
| @@ -908,7 +910,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget { | |||||||
|                     branch.isExpanded = true; |                     branch.isExpanded = true; | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|                 this.updateNode(parentNode); |                 await this.updateNode(parentNode); | ||||||
|  |  | ||||||
|                 let foundChildNode = this.findChildNode(parentNode, childNoteId); |                 let foundChildNode = this.findChildNode(parentNode, childNoteId); | ||||||
|  |  | ||||||
| @@ -1076,10 +1078,10 @@ export default class NoteTreeWidget extends NoteContextAwareWidget { | |||||||
|         const activeNode = this.getActiveNode(); |         const activeNode = this.getActiveNode(); | ||||||
|         const activeNodeFocused = activeNode && activeNode.hasFocus(); |         const activeNodeFocused = activeNode && activeNode.hasFocus(); | ||||||
|         const nextNode = activeNode ? (activeNode.getNextSibling() || activeNode.getPrevSibling() || activeNode.getParent()) : null; |         const nextNode = activeNode ? (activeNode.getNextSibling() || activeNode.getPrevSibling() || activeNode.getParent()) : null; | ||||||
|         const activeNotePath = activeNode ? treeService.getNotePath(activeNode) : null; |         let activeNotePath = activeNode ? treeService.getNotePath(activeNode) : null; | ||||||
|  |  | ||||||
|         const nextNotePath = nextNode ? treeService.getNotePath(nextNode) : null; |         const nextNotePath = nextNode ? treeService.getNotePath(nextNode) : null; | ||||||
|         const activeNoteId = activeNode ? activeNode.data.noteId : null; |         let activeNoteId = activeNode ? activeNode.data.noteId : null; | ||||||
|  |  | ||||||
|         const noteIdsToUpdate = new Set(); |         const noteIdsToUpdate = new Set(); | ||||||
|         const noteIdsToReload = new Set(); |         const noteIdsToReload = new Set(); | ||||||
| @@ -1122,7 +1124,14 @@ export default class NoteTreeWidget extends NoteContextAwareWidget { | |||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         for (const ecBranch of loadResults.getBranches()) { |         // activeNode is supposed to be moved when we find out activeNode is deleted but not all branches are deleted. save it for fixing activeNodePath after all nodes loaded. | ||||||
|  |         let movedActiveNode = null; | ||||||
|  |         let parentsOfAddedNodes = []; | ||||||
|  |  | ||||||
|  |         const allBranches = loadResults.getBranches(); | ||||||
|  |         const allBranchesDeleted = allBranches.every(branch => !!branch.isDeleted); | ||||||
|  |  | ||||||
|  |         for (const ecBranch of allBranches) { | ||||||
|             if (ecBranch.parentNoteId === '_share') { |             if (ecBranch.parentNoteId === '_share') { | ||||||
|                 // all shared notes have a sign in the tree, even the descendants of shared notes |                 // all shared notes have a sign in the tree, even the descendants of shared notes | ||||||
|                 noteIdsToReload.add(ecBranch.noteId); |                 noteIdsToReload.add(ecBranch.noteId); | ||||||
| @@ -1135,12 +1144,16 @@ export default class NoteTreeWidget extends NoteContextAwareWidget { | |||||||
|             for (const node of this.getNodesByBranch(ecBranch)) { |             for (const node of this.getNodesByBranch(ecBranch)) { | ||||||
|                 if (ecBranch.isDeleted) { |                 if (ecBranch.isDeleted) { | ||||||
|                     if (node.isActive()) { |                     if (node.isActive()) { | ||||||
|                         const newActiveNode = node.getNextSibling() |                         if (allBranchesDeleted) { | ||||||
|                             || node.getPrevSibling() |                             const newActiveNode = node.getNextSibling() | ||||||
|                             || node.getParent(); |                                 || node.getPrevSibling() | ||||||
|  |                                 || node.getParent(); | ||||||
|  |  | ||||||
|                         if (newActiveNode) { |                             if (newActiveNode) { | ||||||
|                             newActiveNode.setActive(true, {noEvents: true, noFocus: true}); |                                 newActiveNode.setActive(true, {noEvents: true, noFocus: true}); | ||||||
|  |                             } | ||||||
|  |                         } else { | ||||||
|  |                             movedActiveNode = node; | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|  |  | ||||||
| @@ -1154,12 +1167,13 @@ export default class NoteTreeWidget extends NoteContextAwareWidget { | |||||||
|  |  | ||||||
|             if (!ecBranch.isDeleted) { |             if (!ecBranch.isDeleted) { | ||||||
|                 for (const parentNode of this.getNodesByNoteId(ecBranch.parentNoteId)) { |                 for (const parentNode of this.getNodesByNoteId(ecBranch.parentNoteId)) { | ||||||
|  |                     parentsOfAddedNodes.push(parentNode) | ||||||
|  |  | ||||||
|                     if (parentNode.isFolder() && !parentNode.isLoaded()) { |                     if (parentNode.isFolder() && !parentNode.isLoaded()) { | ||||||
|                         continue; |                         continue; | ||||||
|                     } |                     } | ||||||
|  |  | ||||||
|                     const found = (parentNode.getChildren() || []).find(child => child.data.noteId === ecBranch.noteId); |                     const found = (parentNode.getChildren() || []).find(child => child.data.noteId === ecBranch.noteId); | ||||||
|  |  | ||||||
|                     if (!found) { |                     if (!found) { | ||||||
|                         // make sure it's loaded |                         // make sure it's loaded | ||||||
|                         await froca.getNote(ecBranch.noteId); |                         await froca.getNote(ecBranch.noteId); | ||||||
| @@ -1202,7 +1216,18 @@ export default class NoteTreeWidget extends NoteContextAwareWidget { | |||||||
|         // for some reason node update cannot be in the batchUpdate() block (node is not re-rendered) |         // for some reason node update cannot be in the batchUpdate() block (node is not re-rendered) | ||||||
|         for (const noteId of noteIdsToUpdate) { |         for (const noteId of noteIdsToUpdate) { | ||||||
|             for (const node of this.getNodesByNoteId(noteId)) { |             for (const node of this.getNodesByNoteId(noteId)) { | ||||||
|                 this.updateNode(node); |                 await this.updateNode(node); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (movedActiveNode) { | ||||||
|  |             for (const parentNode of parentsOfAddedNodes) { | ||||||
|  |                 const found = (parentNode.getChildren() || []).find(child => child.data.noteId === movedActiveNode.data.noteId); | ||||||
|  |                 if (found) { | ||||||
|  |                     activeNotePath = treeService.getNotePath(found); | ||||||
|  |                     activeNoteId = found.data.noteId; | ||||||
|  |                     break | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -72,7 +72,7 @@ export default class NotePathsWidget extends NoteContextAwareWidget { | |||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         const sortedNotePaths = this.note.getSortedNotePaths(this.hoistedNoteId) |         const sortedNotePaths = this.note.getSortedNotePathRecords(this.hoistedNoteId) | ||||||
|             .filter(notePath => !notePath.isHidden); |             .filter(notePath => !notePath.isHidden); | ||||||
|  |  | ||||||
|         if (sortedNotePaths.length > 0) { |         if (sortedNotePaths.length > 0) { | ||||||
|   | |||||||
| @@ -25,7 +25,7 @@ export default class SharedSwitchWidget extends SwitchWidget { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     async switchOn() { |     async switchOn() { | ||||||
|         await branchService.cloneNoteToNote(this.noteId, '_share'); |         await branchService.cloneNoteToParentNote(this.noteId, '_share'); | ||||||
|  |  | ||||||
|         syncService.syncNow(true); |         syncService.syncNow(true); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -53,7 +53,7 @@ export default class EditableCodeTypeWidget extends TypeWidget { | |||||||
|             matchBrackets: true, |             matchBrackets: true, | ||||||
|             keyMap: options.is('vimKeymapEnabled') ? "vim": "default", |             keyMap: options.is('vimKeymapEnabled') ? "vim": "default", | ||||||
|             matchTags: {bothTags: true}, |             matchTags: {bothTags: true}, | ||||||
|             highlightSelectionMatches: {showToken: /\w/, annotateScrollbar: false}, |             highlightSelectionMatches: {showToken: false, annotateScrollbar: false}, | ||||||
|             lint: true, |             lint: true, | ||||||
|             gutters: ["CodeMirror-lint-markers"], |             gutters: ["CodeMirror-lint-markers"], | ||||||
|             lineNumbers: true, |             lineNumbers: true, | ||||||
| @@ -62,7 +62,7 @@ export default class EditableCodeTypeWidget extends TypeWidget { | |||||||
|             // all the way to the bottom of the note. With line wrap there's no horizontal scrollbar so no problem |             // all the way to the bottom of the note. With line wrap there's no horizontal scrollbar so no problem | ||||||
|             lineWrapping: options.is('codeLineWrapEnabled'), |             lineWrapping: options.is('codeLineWrapEnabled'), | ||||||
|             dragDrop: false, // with true the editor inlines dropped files which is not what we expect |             dragDrop: false, // with true the editor inlines dropped files which is not what we expect | ||||||
|             placeholder: "Type the content of your code note here..." |             placeholder: "Type the content of your code note here...", | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         this.codeEditor.on('change', () => this.spacedUpdate.scheduleUpdate()); |         this.codeEditor.on('change', () => this.spacedUpdate.scheduleUpdate()); | ||||||
|   | |||||||
| @@ -4,7 +4,6 @@ import mimeTypesService from '../../services/mime_types.js'; | |||||||
| import utils from "../../services/utils.js"; | import utils from "../../services/utils.js"; | ||||||
| import keyboardActionService from "../../services/keyboard_actions.js"; | import keyboardActionService from "../../services/keyboard_actions.js"; | ||||||
| import froca from "../../services/froca.js"; | import froca from "../../services/froca.js"; | ||||||
| import treeService from "../../services/tree.js"; |  | ||||||
| import noteCreateService from "../../services/note_create.js"; | import noteCreateService from "../../services/note_create.js"; | ||||||
| import AbstractTextTypeWidget from "./abstract_text_type_widget.js"; | import AbstractTextTypeWidget from "./abstract_text_type_widget.js"; | ||||||
| import link from "../../services/link.js"; | import link from "../../services/link.js"; | ||||||
| @@ -378,7 +377,7 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget { | |||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         return treeService.getSomeNotePath(resp.note); |         return resp.note.getBestNotePathString(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     async refreshIncludedNoteEvent({noteId}) { |     async refreshIncludedNoteEvent({noteId}) { | ||||||
|   | |||||||
| @@ -9,11 +9,11 @@ function cloneNoteToBranch(req) { | |||||||
|     return cloningService.cloneNoteToBranch(noteId, parentBranchId, prefix); |     return cloningService.cloneNoteToBranch(noteId, parentBranchId, prefix); | ||||||
| } | } | ||||||
|  |  | ||||||
| function cloneNoteToNote(req) { | function cloneNoteToParentNote(req) { | ||||||
|     const {noteId, parentNoteId} = req.params; |     const {noteId, parentNoteId} = req.params; | ||||||
|     const {prefix} = req.body; |     const {prefix} = req.body; | ||||||
|  |  | ||||||
|     return cloningService.cloneNoteToNote(noteId, parentNoteId, prefix); |     return cloningService.cloneNoteToParentNote(noteId, parentNoteId, prefix); | ||||||
| } | } | ||||||
|  |  | ||||||
| function cloneNoteAfter(req) { | function cloneNoteAfter(req) { | ||||||
| @@ -30,7 +30,7 @@ function toggleNoteInParent(req) { | |||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|     cloneNoteToBranch, |     cloneNoteToBranch, | ||||||
|     cloneNoteToNote, |     cloneNoteToParentNote, | ||||||
|     cloneNoteAfter, |     cloneNoteAfter, | ||||||
|     toggleNoteInParent |     toggleNoteInParent | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -129,7 +129,7 @@ function getEditedNotesOnDate(req) { | |||||||
|     notes = notes.map(note => note.getPojo()); |     notes = notes.map(note => note.getPojo()); | ||||||
|  |  | ||||||
|     for (const note of notes) { |     for (const note of notes) { | ||||||
|         const notePath = note.isDeleted ? null : beccaService.getNotePath(note.noteId); |         const notePath = note.isDeleted ? null : getNotePathData(note); | ||||||
|  |  | ||||||
|         note.notePath = notePath ? notePath.notePath : null; |         note.notePath = notePath ? notePath.notePath : null; | ||||||
|     } |     } | ||||||
| @@ -137,6 +137,32 @@ function getEditedNotesOnDate(req) { | |||||||
|     return notes; |     return notes; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | function getNotePathData(note) { | ||||||
|  |     const retPath = note.getBestNotePath(); | ||||||
|  |  | ||||||
|  |     if (retPath) { | ||||||
|  |         const noteTitle = beccaService.getNoteTitleForPath(retPath); | ||||||
|  |  | ||||||
|  |         let branchId; | ||||||
|  |  | ||||||
|  |         if (note.isRoot()) { | ||||||
|  |             branchId = 'none_root'; | ||||||
|  |         } | ||||||
|  |         else { | ||||||
|  |             const parentNote = note.parents[0]; | ||||||
|  |             branchId = becca.getBranchFromChildAndParent(note.noteId, parentNote.noteId).branchId; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return { | ||||||
|  |             noteId: note.noteId, | ||||||
|  |             branchId: branchId, | ||||||
|  |             title: noteTitle, | ||||||
|  |             notePath: retPath, | ||||||
|  |             path: retPath.join('/') | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|     getNoteRevisions, |     getNoteRevisions, | ||||||
|     getNoteRevision, |     getNoteRevision, | ||||||
|   | |||||||
| @@ -3,14 +3,14 @@ | |||||||
| const sql = require('../../services/sql'); | const sql = require('../../services/sql'); | ||||||
| const protectedSessionService = require('../../services/protected_session'); | const protectedSessionService = require('../../services/protected_session'); | ||||||
| const noteService = require('../../services/notes'); | const noteService = require('../../services/notes'); | ||||||
| const beccaService = require('../../becca/becca_service'); | const becca = require("../../becca/becca"); | ||||||
|  |  | ||||||
| function getRecentChanges(req) { | function getRecentChanges(req) { | ||||||
|     const {ancestorNoteId} = req.params; |     const {ancestorNoteId} = req.params; | ||||||
|  |  | ||||||
|     let recentChanges = []; |     let recentChanges = []; | ||||||
|  |  | ||||||
|     const noteRevisions = sql.getRows(` |     const noteRevisionRows = sql.getRows(` | ||||||
|         SELECT  |         SELECT  | ||||||
|             notes.noteId, |             notes.noteId, | ||||||
|             notes.isDeleted AS current_isDeleted, |             notes.isDeleted AS current_isDeleted, | ||||||
| @@ -24,16 +24,18 @@ function getRecentChanges(req) { | |||||||
|             note_revisions |             note_revisions | ||||||
|             JOIN notes USING(noteId)`); |             JOIN notes USING(noteId)`); | ||||||
|  |  | ||||||
|     for (const noteRevision of noteRevisions) { |     for (const noteRevisionRow of noteRevisionRows) { | ||||||
|         if (beccaService.isInAncestor(noteRevision.noteId, ancestorNoteId)) { |         const note = becca.getNote(noteRevisionRow.noteId); | ||||||
|             recentChanges.push(noteRevision); |  | ||||||
|  |         if (note?.hasAncestor(ancestorNoteId)) { | ||||||
|  |             recentChanges.push(noteRevisionRow); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // now we need to also collect date points not represented in note revisions: |     // now we need to also collect date points not represented in note revisions: | ||||||
|     // 1. creation for all notes (dateCreated) |     // 1. creation for all notes (dateCreated) | ||||||
|     // 2. deletion for deleted notes (dateModified) |     // 2. deletion for deleted notes (dateModified) | ||||||
|     const notes = sql.getRows(` |     const noteRows = sql.getRows(` | ||||||
|             SELECT |             SELECT | ||||||
|                 notes.noteId, |                 notes.noteId, | ||||||
|                 notes.isDeleted AS current_isDeleted, |                 notes.isDeleted AS current_isDeleted, | ||||||
| @@ -57,9 +59,11 @@ function getRecentChanges(req) { | |||||||
|             FROM notes |             FROM notes | ||||||
|             WHERE notes.isDeleted = 1`); |             WHERE notes.isDeleted = 1`); | ||||||
|  |  | ||||||
|     for (const note of notes) { |     for (const noteRow of noteRows) { | ||||||
|         if (beccaService.isInAncestor(note.noteId, ancestorNoteId)) { |         const note = becca.getNote(noteRow.noteId); | ||||||
|             recentChanges.push(note); |  | ||||||
|  |         if (note?.hasAncestor(ancestorNoteId)) { | ||||||
|  |             recentChanges.push(noteRow); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -125,7 +125,7 @@ function register(app) { | |||||||
|     apiRoute(PST, '/api/notes/:noteId/upload-modified-file', notesApiRoute.uploadModifiedFile); |     apiRoute(PST, '/api/notes/:noteId/upload-modified-file', notesApiRoute.uploadModifiedFile); | ||||||
|     apiRoute(PUT, '/api/notes/:noteId/clone-to-branch/:parentBranchId', cloningApiRoute.cloneNoteToBranch); |     apiRoute(PUT, '/api/notes/:noteId/clone-to-branch/:parentBranchId', cloningApiRoute.cloneNoteToBranch); | ||||||
|     apiRoute(PUT, '/api/notes/:noteId/toggle-in-parent/:parentNoteId/:present', cloningApiRoute.toggleNoteInParent); |     apiRoute(PUT, '/api/notes/:noteId/toggle-in-parent/:parentNoteId/:present', cloningApiRoute.toggleNoteInParent); | ||||||
|     apiRoute(PUT, '/api/notes/:noteId/clone-to-note/:parentNoteId', cloningApiRoute.cloneNoteToNote); |     apiRoute(PUT, '/api/notes/:noteId/clone-to-note/:parentNoteId', cloningApiRoute.cloneNoteToParentNote); | ||||||
|     apiRoute(PUT, '/api/notes/:noteId/clone-after/:afterBranchId', cloningApiRoute.cloneNoteAfter); |     apiRoute(PUT, '/api/notes/:noteId/clone-after/:afterBranchId', cloningApiRoute.cloneNoteAfter); | ||||||
|     route(PUT, '/api/notes/:noteId/file', [auth.checkApiAuthOrElectron, uploadMiddlewareWithErrorHandling, csrfMiddleware], |     route(PUT, '/api/notes/:noteId/file', [auth.checkApiAuthOrElectron, uploadMiddlewareWithErrorHandling, csrfMiddleware], | ||||||
|         filesRoute.updateFile, apiResultHandler); |         filesRoute.updateFile, apiResultHandler); | ||||||
|   | |||||||
| @@ -1 +1 @@ | |||||||
| module.exports = { buildDate:"", buildRevision: "9881e6de3e4966af39ec6245562dca6ac7b25eaa" }; | module.exports = { buildDate:"2023-04-17T21:40:35+02:00", buildRevision: "1d3272e9f8c27106a66227fbb580677ae5d70427" }; | ||||||
|   | |||||||
| @@ -83,7 +83,7 @@ const ACTION_HANDLERS = { | |||||||
|         let res; |         let res; | ||||||
|  |  | ||||||
|         if (note.getParentBranches().length > 1) { |         if (note.getParentBranches().length > 1) { | ||||||
|             res = cloningService.cloneNoteToNote(note.noteId, action.targetParentNoteId); |             res = cloningService.cloneNoteToParentNote(note.noteId, action.targetParentNoteId); | ||||||
|         } |         } | ||||||
|         else { |         else { | ||||||
|             res = branchService.moveBranchToNote(note.getParentBranches()[0], action.targetParentNoteId); |             res = branchService.moveBranchToNote(note.getParentBranches()[0], action.targetParentNoteId); | ||||||
|   | |||||||
| @@ -8,7 +8,7 @@ const becca = require("../becca/becca"); | |||||||
| const beccaService = require("../becca/becca_service"); | const beccaService = require("../becca/becca_service"); | ||||||
| const log = require("./log"); | const log = require("./log"); | ||||||
|  |  | ||||||
| function cloneNoteToNote(noteId, parentNoteId, prefix) { | function cloneNoteToParentNote(noteId, parentNoteId, prefix) { | ||||||
|     const parentNote = becca.getNote(parentNoteId); |     const parentNote = becca.getNote(parentNoteId); | ||||||
|  |  | ||||||
|     if (parentNote.type === 'search') { |     if (parentNote.type === 'search') { | ||||||
| @@ -19,7 +19,7 @@ function cloneNoteToNote(noteId, parentNoteId, prefix) { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     if (isNoteDeleted(noteId) || isNoteDeleted(parentNoteId)) { |     if (isNoteDeleted(noteId) || isNoteDeleted(parentNoteId)) { | ||||||
|         return { success: false, message: 'Note is deleted.' }; |         return { success: false, message: 'Note cannot be cloned because either the cloned note or the intended parent is deleted.' }; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     const validationResult = treeService.validateParentChild(parentNoteId, noteId); |     const validationResult = treeService.validateParentChild(parentNoteId, noteId); | ||||||
| @@ -35,12 +35,12 @@ function cloneNoteToNote(noteId, parentNoteId, prefix) { | |||||||
|         isExpanded: 0 |         isExpanded: 0 | ||||||
|     }).save(); |     }).save(); | ||||||
|  |  | ||||||
|     log.info(`Cloned note '${noteId}' to new parent note '${parentNoteId}' with prefix '${prefix}'`); |     log.info(`Cloned note '${noteId}' to a new parent note '${parentNoteId}' with prefix '${prefix}'`); | ||||||
|  |  | ||||||
|     return { |     return { | ||||||
|         success: true, |         success: true, | ||||||
|         branchId: branch.branchId, |         branchId: branch.branchId, | ||||||
|         notePath: `${beccaService.getNotePath(parentNoteId).path}/${noteId}` |         notePath: `${parentNote.getBestNotePathString()}/${noteId}` | ||||||
|     }; |     }; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -51,7 +51,7 @@ function cloneNoteToBranch(noteId, parentBranchId, prefix) { | |||||||
|         return { success: false, message: `Parent branch ${parentBranchId} does not exist.` }; |         return { success: false, message: `Parent branch ${parentBranchId} does not exist.` }; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     const ret = cloneNoteToNote(noteId, parentBranch.noteId, prefix); |     const ret = cloneNoteToParentNote(noteId, parentBranch.noteId, prefix); | ||||||
|  |  | ||||||
|     parentBranch.isExpanded = true; // the new target should be expanded, so it immediately shows up to the user |     parentBranch.isExpanded = true; // the new target should be expanded, so it immediately shows up to the user | ||||||
|     parentBranch.save(); |     parentBranch.save(); | ||||||
| @@ -182,7 +182,7 @@ function isNoteDeleted(noteId) { | |||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|     cloneNoteToBranch, |     cloneNoteToBranch, | ||||||
|     cloneNoteToNote, |     cloneNoteToParentNote, | ||||||
|     ensureNoteIsPresentInParent, |     ensureNoteIsPresentInParent, | ||||||
|     ensureNoteIsAbsentFromParent, |     ensureNoteIsAbsentFromParent, | ||||||
|     toggleNoteInParent, |     toggleNoteInParent, | ||||||
|   | |||||||
| @@ -20,6 +20,7 @@ const dayjs = require("dayjs"); | |||||||
| const htmlSanitizer = require("./html_sanitizer"); | const htmlSanitizer = require("./html_sanitizer"); | ||||||
| const ValidationError = require("../errors/validation_error"); | const ValidationError = require("../errors/validation_error"); | ||||||
| const noteTypesService = require("./note_types"); | const noteTypesService = require("./note_types"); | ||||||
|  | const fs = require("fs"); | ||||||
|  |  | ||||||
| /** @param {BNote} parentNote */ | /** @param {BNote} parentNote */ | ||||||
| function getNewNotePosition(parentNote) { | function getNewNotePosition(parentNote) { | ||||||
| @@ -395,7 +396,24 @@ const imageUrlToAttachmentIdMapping = {}; | |||||||
|  |  | ||||||
| async function downloadImage(noteId, imageUrl) { | async function downloadImage(noteId, imageUrl) { | ||||||
|     try { |     try { | ||||||
|         const imageBuffer = await request.getImage(imageUrl); |         let imageBuffer; | ||||||
|  |  | ||||||
|  |         if (imageUrl.toLowerCase().startsWith("file://")) { | ||||||
|  |             imageBuffer = await new Promise((res, rej) => { | ||||||
|  |                 const localFilePath = imageUrl.substr("file://".length); | ||||||
|  |  | ||||||
|  |                 return fs.readFile(localFilePath, (err, data) => { | ||||||
|  |                     if (err) { | ||||||
|  |                         rej(err); | ||||||
|  |                     } else { | ||||||
|  |                         res(data); | ||||||
|  |                     } | ||||||
|  |                 }); | ||||||
|  |             }); | ||||||
|  |         } else { | ||||||
|  |             imageBuffer = await request.getImage(imageUrl); | ||||||
|  |         } | ||||||
|  |  | ||||||
|         const parsedUrl = url.parse(imageUrl); |         const parsedUrl = url.parse(imageUrl); | ||||||
|         const title = path.basename(parsedUrl.pathname); |         const title = path.basename(parsedUrl.pathname); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -22,9 +22,9 @@ class NoteFlatTextExp extends Expression { | |||||||
|          * @param {string[]} tokens |          * @param {string[]} tokens | ||||||
|          * @param {string[]} path |          * @param {string[]} path | ||||||
|          */ |          */ | ||||||
|         function searchDownThePath(note, tokens, path) { |         const searchDownThePath = (note, tokens, path) => { | ||||||
|             if (tokens.length === 0) { |             if (tokens.length === 0) { | ||||||
|                 const retPath = beccaService.getSomePath(note, path); |                 const retPath = this.getNotePath(note, path); | ||||||
|  |  | ||||||
|                 if (retPath) { |                 if (retPath) { | ||||||
|                     const noteId = retPath[retPath.length - 1]; |                     const noteId = retPath[retPath.length - 1]; | ||||||
| @@ -131,6 +131,17 @@ class NoteFlatTextExp extends Expression { | |||||||
|         return resultNoteSet; |         return resultNoteSet; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     getNotePath(note, path) { | ||||||
|  |         if (path.length === 0) { | ||||||
|  |             return note.getBestNotePath(); | ||||||
|  |         } else { | ||||||
|  |             const closestNoteId = path[0]; | ||||||
|  |             const closestNoteBestNotePath = becca.getNote(closestNoteId).getBestNotePath(); | ||||||
|  |  | ||||||
|  |             return [...closestNoteBestNotePath, ...path.slice(1)]; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * Returns noteIds which have at least one matching tokens |      * Returns noteIds which have at least one matching tokens | ||||||
|      * |      * | ||||||
|   | |||||||
| @@ -157,7 +157,7 @@ function findResultsWithExpression(expression, searchContext) { | |||||||
|     const searchResults = noteSet.notes |     const searchResults = noteSet.notes | ||||||
|         .filter(note => !note.isDeleted) |         .filter(note => !note.isDeleted) | ||||||
|         .map(note => { |         .map(note => { | ||||||
|             const notePathArray = executionContext.noteIdToNotePath[note.noteId] || beccaService.getSomePath(note); |             const notePathArray = executionContext.noteIdToNotePath[note.noteId] || note.getBestNotePath(); | ||||||
|  |  | ||||||
|             if (!notePathArray) { |             if (!notePathArray) { | ||||||
|                 throw new Error(`Can't find note path for note ${JSON.stringify(note.getPojo())}`); |                 throw new Error(`Can't find note path for note ${JSON.stringify(note.getPojo())}`); | ||||||
|   | |||||||
| @@ -9,6 +9,18 @@ const protectedSessionService = require('./protected_session'); | |||||||
| const becca = require("../becca/becca"); | const becca = require("../becca/becca"); | ||||||
| const AbstractBeccaEntity = require("../becca/entities/abstract_becca_entity"); | const AbstractBeccaEntity = require("../becca/entities/abstract_becca_entity"); | ||||||
|  |  | ||||||
|  | const env = require('./env'); | ||||||
|  | if (env.isDev()) { | ||||||
|  |     const chokidar = require('chokidar'); | ||||||
|  |     const debounce = require('debounce'); | ||||||
|  |     const debouncedReloadFrontend = debounce(reloadFrontend, 200); | ||||||
|  |     chokidar | ||||||
|  |         .watch('src/public') | ||||||
|  |         .on('add', debouncedReloadFrontend) | ||||||
|  |         .on('change', debouncedReloadFrontend) | ||||||
|  |         .on('unlink', debouncedReloadFrontend); | ||||||
|  | } | ||||||
|  |  | ||||||
| let webSocketServer; | let webSocketServer; | ||||||
| let lastSyncedPush = null; | let lastSyncedPush = null; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,16 +0,0 @@ | |||||||
| const path = require('path'); |  | ||||||
| const assetPath = require('./src/services/asset_path'); |  | ||||||
|  |  | ||||||
| module.exports = { |  | ||||||
|     mode: 'production', |  | ||||||
|     entry: { |  | ||||||
|         mobile: './src/public/app/desktop.js', |  | ||||||
|     }, |  | ||||||
|     output: { |  | ||||||
|         publicPath: `${assetPath}/app-dist/`, |  | ||||||
|         path: path.resolve(__dirname, 'src/public/app-dist'), |  | ||||||
|         filename: 'desktop.js' |  | ||||||
|     }, |  | ||||||
|     devtool: 'source-map', |  | ||||||
|     target: 'electron-renderer' |  | ||||||
| }; |  | ||||||
| @@ -1,16 +0,0 @@ | |||||||
| const path = require('path'); |  | ||||||
| const assetPath = require('./src/services/asset_path'); |  | ||||||
|  |  | ||||||
| module.exports = { |  | ||||||
|     mode: 'production', |  | ||||||
|     entry: { |  | ||||||
|         mobile: './src/public/app/setup.js', |  | ||||||
|     }, |  | ||||||
|     output: { |  | ||||||
|         publicPath: `${assetPath}/app-dist/`, |  | ||||||
|         path: path.resolve(__dirname, 'src/public/app-dist'), |  | ||||||
|         filename: 'setup.js' |  | ||||||
|     }, |  | ||||||
|     devtool: 'source-map', |  | ||||||
|     target: 'electron-renderer' |  | ||||||
| }; |  | ||||||
| @@ -4,13 +4,15 @@ const assetPath = require('./src/services/asset_path'); | |||||||
| module.exports = { | module.exports = { | ||||||
|     mode: 'production', |     mode: 'production', | ||||||
|     entry: { |     entry: { | ||||||
|  |         setup: './src/public/app/setup.js', | ||||||
|         mobile: './src/public/app/mobile.js', |         mobile: './src/public/app/mobile.js', | ||||||
|  |         desktop: './src/public/app/desktop.js', | ||||||
|     }, |     }, | ||||||
|     output: { |     output: { | ||||||
|         publicPath: `${assetPath}/app-dist/`, |         publicPath: `${assetPath}/app-dist/`, | ||||||
|         path: path.resolve(__dirname, 'src/public/app-dist'), |         path: path.resolve(__dirname, 'src/public/app-dist'), | ||||||
|         filename: 'mobile.js' |         filename: '[name].js', | ||||||
|     }, |     }, | ||||||
|     devtool: 'source-map', |     devtool: 'source-map', | ||||||
|     target: 'electron-renderer' |     target: 'electron-renderer', | ||||||
| }; | }; | ||||||
		Reference in New Issue
	
	Block a user