mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 18:36:30 +01:00 
			
		
		
		
	Compare commits
	
		
			36 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 25ce2e4253 | ||
|  | c27f573eed | ||
|  | 1d99c4e80b | ||
|  | dc7c64a94d | ||
|  | dc6a530d8c | ||
|  | a674a12706 | ||
|  | c29f1af48f | ||
|  | a8d72c46e4 | ||
|  | ec36fbd83e | ||
|  | aedb05cbab | ||
|  | bb16840a72 | ||
|  | 7b5d44a329 | ||
|  | a53a65be1f | ||
|  | 44af431a93 | ||
|  | caa11b8f7e | ||
|  | 41cce4dcb9 | ||
|  | 858072cc10 | ||
|  | 855c5e0e67 | ||
|  | f0cc3d0bcd | ||
|  | 7672f22ce0 | ||
|  | ef37a52a06 | ||
|  | 2318d615bb | ||
|  | bc14c3d665 | ||
|  | b89ea9a684 | ||
|  | 496767a52b | ||
|  | 9139c597e5 | ||
|  | 942132c01d | ||
|  | 1862acd1ff | ||
|  | ce7e18d0b0 | ||
|  | 65280d5ba3 | ||
|  | 7e3d424e23 | ||
|  | bff04c121a | ||
|  | e8903e82a1 | ||
|  | 0cfd95d9b8 | ||
|  | 1aa5349628 | ||
|  | 4e21d12202 | 
							
								
								
									
										3
									
								
								.idea/vcs.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										3
									
								
								.idea/vcs.xml
									
									
									
										generated
									
									
									
								
							| @@ -2,6 +2,5 @@ | |||||||
| <project version="4"> | <project version="4"> | ||||||
|   <component name="VcsDirectoryMappings"> |   <component name="VcsDirectoryMappings"> | ||||||
|     <mapping directory="" vcs="Git" /> |     <mapping directory="" vcs="Git" /> | ||||||
|     <mapping directory="$PROJECT_DIR$/../.." vcs="Git" /> |  | ||||||
|   </component> |   </component> | ||||||
| </project> | </project> | ||||||
|   | |||||||
| @@ -10,7 +10,7 @@ fi | |||||||
|  |  | ||||||
| cd dist | cd dist | ||||||
| wget https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.xz | wget https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.xz | ||||||
| tar xvfJ node-v${NODE_VERSION}-linux-x64.tar.xz | tar xfJ node-v${NODE_VERSION}-linux-x64.tar.xz | ||||||
| rm node-v${NODE_VERSION}-linux-x64.tar.xz | rm node-v${NODE_VERSION}-linux-x64.tar.xz | ||||||
| cd .. | cd .. | ||||||
|  |  | ||||||
|   | |||||||
| @@ -5,7 +5,7 @@ if [[ $# -eq 0 ]] ; then | |||||||
|     exit 1 |     exit 1 | ||||||
| fi | fi | ||||||
|  |  | ||||||
| npm run webpack | n exec 12 npm run webpack | ||||||
|  |  | ||||||
| DIR=$1 | DIR=$1 | ||||||
|  |  | ||||||
| @@ -27,7 +27,7 @@ cp -r electron.js $DIR/ | |||||||
| cp webpack-* $DIR/ | cp webpack-* $DIR/ | ||||||
|  |  | ||||||
| # run in subshell (so we return to original dir) | # run in subshell (so we return to original dir) | ||||||
| (cd $DIR && npm install --only=prod) | (cd $DIR && n exec 12 npm install --only=prod) | ||||||
|  |  | ||||||
| # cleanup of useless files in dependencies | # cleanup of useless files in dependencies | ||||||
| rm -r $DIR/node_modules/image-q/demo | rm -r $DIR/node_modules/image-q/demo | ||||||
|   | |||||||
| @@ -55,47 +55,20 @@ echo "Creating release in GitHub" | |||||||
| EXTRA= | EXTRA= | ||||||
|  |  | ||||||
| if [[ $TAG == *"beta"* ]]; then | if [[ $TAG == *"beta"* ]]; then | ||||||
|   EXTRA=--pre-release |   EXTRA=--prerelease | ||||||
| fi | fi | ||||||
|  |  | ||||||
| github-release release \ | echo "$GITHUB_CLI_AUTH_TOKEN" | gh auth login --with-token | ||||||
|     --tag $TAG \ |  | ||||||
|     --name "$TAG release" $EXTRA |  | ||||||
|  |  | ||||||
| echo "Uploading debian x64 package" | gh release create "$TAG" \ | ||||||
|  |     --title "$TAG release" \ | ||||||
| github-release upload \ |     --notes "" \ | ||||||
|     --tag $TAG \ |     $EXTRA \ | ||||||
|     --name "$DEBIAN_X64_BUILD" \ |     "dist/$DEBIAN_X64_BUILD" \ | ||||||
|     --file "dist/$DEBIAN_X64_BUILD" |     "dist/$LINUX_X64_BUILD" \ | ||||||
|  |     "dist/$WINDOWS_X64_BUILD" \ | ||||||
| echo "Uploading linux x64 build" |     "dist/$MAC_X64_BUILD" \ | ||||||
|  |     "dist/$SERVER_BUILD" | ||||||
| github-release upload \ |  | ||||||
|     --tag $TAG \ |  | ||||||
|     --name "$LINUX_X64_BUILD" \ |  | ||||||
|     --file "dist/$LINUX_X64_BUILD" |  | ||||||
|  |  | ||||||
| echo "Uploading windows x64 build" |  | ||||||
|  |  | ||||||
| github-release upload \ |  | ||||||
|     --tag $TAG \ |  | ||||||
|     --name "$WINDOWS_X64_BUILD" \ |  | ||||||
|     --file "dist/$WINDOWS_X64_BUILD" |  | ||||||
|  |  | ||||||
| echo "Uploading mac x64 build" |  | ||||||
|  |  | ||||||
| github-release upload \ |  | ||||||
|     --tag $TAG \ |  | ||||||
|     --name "$MAC_X64_BUILD" \ |  | ||||||
|     --file "dist/$MAC_X64_BUILD" |  | ||||||
|  |  | ||||||
| echo "Uploading linux x64 server build" |  | ||||||
|  |  | ||||||
| github-release upload \ |  | ||||||
|     --tag $TAG \ |  | ||||||
|     --name "$SERVER_BUILD" \ |  | ||||||
|     --file "dist/$SERVER_BUILD" |  | ||||||
|  |  | ||||||
| echo "Building docker image" | echo "Building docker image" | ||||||
|  |  | ||||||
| @@ -105,4 +78,4 @@ echo "Pushing docker image to dockerhub" | |||||||
|  |  | ||||||
| bin/push-docker-image.sh $VERSION | bin/push-docker-image.sh $VERSION | ||||||
|  |  | ||||||
| echo "Release finished!" | echo "Release finished!" | ||||||
|   | |||||||
							
								
								
									
										
											BIN
										
									
								
								db/demo.zip
									
									
									
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								db/demo.zip
									
									
									
									
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										2
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -1,6 +1,6 @@ | |||||||
| { | { | ||||||
|   "name": "trilium", |   "name": "trilium", | ||||||
|   "version": "0.46.4-beta", |   "version": "0.46.7", | ||||||
|   "lockfileVersion": 1, |   "lockfileVersion": 1, | ||||||
|   "requires": true, |   "requires": true, | ||||||
|   "dependencies": { |   "dependencies": { | ||||||
|   | |||||||
| @@ -2,7 +2,7 @@ | |||||||
|   "name": "trilium", |   "name": "trilium", | ||||||
|   "productName": "Trilium Notes", |   "productName": "Trilium Notes", | ||||||
|   "description": "Trilium Notes", |   "description": "Trilium Notes", | ||||||
|   "version": "0.46.5", |   "version": "0.46.9", | ||||||
|   "license": "AGPL-3.0-only", |   "license": "AGPL-3.0-only", | ||||||
|   "main": "electron.js", |   "main": "electron.js", | ||||||
|   "bin": { |   "bin": { | ||||||
| @@ -14,7 +14,7 @@ | |||||||
|   }, |   }, | ||||||
|   "scripts": { |   "scripts": { | ||||||
|     "start-server": "cross-env TRILIUM_ENV=dev node ./src/www", |     "start-server": "cross-env TRILIUM_ENV=dev node ./src/www", | ||||||
|     "start-electron": "cross-env TRILIUM_ENV=dev electron .", |     "start-electron": "cross-env TRILIUM_ENV=dev electron --inspect=5858 .", | ||||||
|     "build-backend-docs": "./node_modules/.bin/jsdoc -c jsdoc-conf.json -d ./docs/backend_api src/entities/*.js src/services/backend_script_api.js", |     "build-backend-docs": "./node_modules/.bin/jsdoc -c jsdoc-conf.json -d ./docs/backend_api src/entities/*.js src/services/backend_script_api.js", | ||||||
|     "build-frontend-docs": "./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/collapsible_widget.js", |     "build-frontend-docs": "./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/collapsible_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", | ||||||
| @@ -51,10 +51,11 @@ | |||||||
|     "is-animated": "^2.0.1", |     "is-animated": "^2.0.1", | ||||||
|     "is-svg": "4.2.1", |     "is-svg": "4.2.1", | ||||||
|     "jimp": "0.16.1", |     "jimp": "0.16.1", | ||||||
|  |     "joplin-turndown-plugin-gfm": "1.0.12", | ||||||
|     "jsdom": "16.5.0", |     "jsdom": "16.5.0", | ||||||
|     "mime-types": "2.1.29", |     "mime-types": "2.1.29", | ||||||
|     "multer": "1.4.2", |     "multer": "1.4.2", | ||||||
|     "node-abi": "2.21.0", |     "node-abi": "2.26.0", | ||||||
|     "open": "7.4.2", |     "open": "7.4.2", | ||||||
|     "portscanner": "2.2.0", |     "portscanner": "2.2.0", | ||||||
|     "rand-token": "1.0.1", |     "rand-token": "1.0.1", | ||||||
| @@ -70,7 +71,6 @@ | |||||||
|     "striptags": "3.1.1", |     "striptags": "3.1.1", | ||||||
|     "tmp": "^0.2.1", |     "tmp": "^0.2.1", | ||||||
|     "turndown": "7.0.0", |     "turndown": "7.0.0", | ||||||
|     "joplin-turndown-plugin-gfm": "1.0.12", |  | ||||||
|     "unescape": "1.0.1", |     "unescape": "1.0.1", | ||||||
|     "ws": "7.4.4", |     "ws": "7.4.4", | ||||||
|     "yauzl": "2.10.0", |     "yauzl": "2.10.0", | ||||||
|   | |||||||
| @@ -87,14 +87,16 @@ describe("Lexer expression", () => { | |||||||
|             .toEqual(["#label", "*=*", "text"]); |             .toEqual(["#label", "*=*", "text"]); | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     it("simple label operator with in quotes and without", () => { |     it("simple label operator with in quotes", () => { | ||||||
|         expect(lex("#label*=*'text'").expressionTokens) |         expect(lex("#label*=*'text'").expressionTokens) | ||||||
|             .toEqual([ |             .toEqual([ | ||||||
|                 {token: "#label", inQuotes: false, startIndex: 0, endIndex: 5}, |                 {token: "#label", inQuotes: false, startIndex: 0, endIndex: 5}, | ||||||
|                 {token: "*=*", inQuotes: false, startIndex: 6, endIndex: 8}, |                 {token: "*=*", inQuotes: false, startIndex: 6, endIndex: 8}, | ||||||
|                 {token: "text", inQuotes: true, startIndex: 10, endIndex: 13} |                 {token: "text", inQuotes: true, startIndex: 10, endIndex: 13} | ||||||
|             ]); |             ]); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     it("simple label operator with param without quotes", () => { | ||||||
|         expect(lex("#label*=*text").expressionTokens) |         expect(lex("#label*=*text").expressionTokens) | ||||||
|             .toEqual([ |             .toEqual([ | ||||||
|                 {token: "#label", inQuotes: false, startIndex: 0, endIndex: 5}, |                 {token: "#label", inQuotes: false, startIndex: 0, endIndex: 5}, | ||||||
| @@ -103,6 +105,16 @@ describe("Lexer expression", () => { | |||||||
|             ]); |             ]); | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|  |     it("simple label operator with empty string param", () => { | ||||||
|  |         expect(lex("#label = ''").expressionTokens) | ||||||
|  |             .toEqual([ | ||||||
|  |                 {token: "#label", inQuotes: false, startIndex: 0, endIndex: 5}, | ||||||
|  |                 {token: "=", inQuotes: false, startIndex: 7, endIndex: 7}, | ||||||
|  |                 // weird case for empty strings which ends up with endIndex < startIndex :-( | ||||||
|  |                 {token: "", inQuotes: true, startIndex: 10, endIndex: 9} | ||||||
|  |             ]); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|     it("note. prefix also separates fulltext from expression", () => { |     it("note. prefix also separates fulltext from expression", () => { | ||||||
|         expect(lex(`hello fulltext note.labels.capital = Prague`).expressionTokens.map(t => t.token)) |         expect(lex(`hello fulltext note.labels.capital = Prague`).expressionTokens.map(t => t.token)) | ||||||
|             .toEqual(["note", ".", "labels", ".", "capital", "=", "prague"]); |             .toEqual(["note", ".", "labels", ".", "capital", "=", "prague"]); | ||||||
|   | |||||||
| @@ -19,6 +19,13 @@ function tokens(toks, cur = 0) { | |||||||
|     }); |     }); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | function assertIsArchived(exp) { | ||||||
|  |     expect(exp.constructor.name).toEqual("PropertyComparisonExp"); | ||||||
|  |     expect(exp.propertyName).toEqual("isArchived"); | ||||||
|  |     expect(exp.operator).toEqual("="); | ||||||
|  |     expect(exp.comparedValue).toEqual("false"); | ||||||
|  | } | ||||||
|  |  | ||||||
| describe("Parser", () => { | describe("Parser", () => { | ||||||
|     it("fulltext parser without content", () => { |     it("fulltext parser without content", () => { | ||||||
|         const rootExp = parse({ |         const rootExp = parse({ | ||||||
| @@ -29,8 +36,9 @@ describe("Parser", () => { | |||||||
|  |  | ||||||
|         expect(rootExp.constructor.name).toEqual("AndExp"); |         expect(rootExp.constructor.name).toEqual("AndExp"); | ||||||
|         expect(rootExp.subExpressions[0].constructor.name).toEqual("PropertyComparisonExp"); |         expect(rootExp.subExpressions[0].constructor.name).toEqual("PropertyComparisonExp"); | ||||||
|         expect(rootExp.subExpressions[1].constructor.name).toEqual("NoteCacheFlatTextExp"); |         expect(rootExp.subExpressions[1].constructor.name).toEqual("OrExp"); | ||||||
|         expect(rootExp.subExpressions[1].tokens).toEqual(["hello", "hi"]); |         expect(rootExp.subExpressions[1].subExpressions[0].constructor.name).toEqual("NoteCacheFlatTextExp"); | ||||||
|  |         expect(rootExp.subExpressions[1].subExpressions[0].tokens).toEqual(["hello", "hi"]); | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     it("fulltext parser with content", () => { |     it("fulltext parser with content", () => { | ||||||
| @@ -40,9 +48,12 @@ describe("Parser", () => { | |||||||
|             searchContext: new SearchContext({includeNoteContent: true}) |             searchContext: new SearchContext({includeNoteContent: true}) | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         expect(rootExp.constructor.name).toEqual("OrExp"); |         expect(rootExp.constructor.name).toEqual("AndExp"); | ||||||
|  |         assertIsArchived(rootExp.subExpressions[0]); | ||||||
|  |  | ||||||
|         const subs = rootExp.subExpressions; |         expect(rootExp.subExpressions[1].constructor.name).toEqual("OrExp"); | ||||||
|  |  | ||||||
|  |         const subs = rootExp.subExpressions[1].subExpressions; | ||||||
|  |  | ||||||
|         expect(subs[0].constructor.name).toEqual("NoteCacheFlatTextExp"); |         expect(subs[0].constructor.name).toEqual("NoteCacheFlatTextExp"); | ||||||
|         expect(subs[0].tokens).toEqual(["hello", "hi"]); |         expect(subs[0].tokens).toEqual(["hello", "hi"]); | ||||||
| @@ -61,10 +72,12 @@ describe("Parser", () => { | |||||||
|             searchContext: new SearchContext() |             searchContext: new SearchContext() | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         expect(rootExp.constructor.name).toEqual("LabelComparisonExp"); |         expect(rootExp.constructor.name).toEqual("AndExp"); | ||||||
|         expect(rootExp.attributeType).toEqual("label"); |         assertIsArchived(rootExp.subExpressions[0]); | ||||||
|         expect(rootExp.attributeName).toEqual("mylabel"); |         expect(rootExp.subExpressions[1].constructor.name).toEqual("LabelComparisonExp"); | ||||||
|         expect(rootExp.comparator).toBeTruthy(); |         expect(rootExp.subExpressions[1].attributeType).toEqual("label"); | ||||||
|  |         expect(rootExp.subExpressions[1].attributeName).toEqual("mylabel"); | ||||||
|  |         expect(rootExp.subExpressions[1].comparator).toBeTruthy(); | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     it("simple attribute negation", () => { |     it("simple attribute negation", () => { | ||||||
| @@ -74,10 +87,12 @@ describe("Parser", () => { | |||||||
|             searchContext: new SearchContext() |             searchContext: new SearchContext() | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         expect(rootExp.constructor.name).toEqual("NotExp"); |         expect(rootExp.constructor.name).toEqual("AndExp"); | ||||||
|         expect(rootExp.subExpression.constructor.name).toEqual("AttributeExistsExp"); |         assertIsArchived(rootExp.subExpressions[0]); | ||||||
|         expect(rootExp.subExpression.attributeType).toEqual("label"); |         expect(rootExp.subExpressions[1].constructor.name).toEqual("NotExp"); | ||||||
|         expect(rootExp.subExpression.attributeName).toEqual("mylabel"); |         expect(rootExp.subExpressions[1].subExpression.constructor.name).toEqual("AttributeExistsExp"); | ||||||
|  |         expect(rootExp.subExpressions[1].subExpression.attributeType).toEqual("label"); | ||||||
|  |         expect(rootExp.subExpressions[1].subExpression.attributeName).toEqual("mylabel"); | ||||||
|  |  | ||||||
|         rootExp = parse({ |         rootExp = parse({ | ||||||
|             fulltextTokens: [], |             fulltextTokens: [], | ||||||
| @@ -85,10 +100,12 @@ describe("Parser", () => { | |||||||
|             searchContext: new SearchContext() |             searchContext: new SearchContext() | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         expect(rootExp.constructor.name).toEqual("NotExp"); |         expect(rootExp.constructor.name).toEqual("AndExp"); | ||||||
|         expect(rootExp.subExpression.constructor.name).toEqual("AttributeExistsExp"); |         assertIsArchived(rootExp.subExpressions[0]); | ||||||
|         expect(rootExp.subExpression.attributeType).toEqual("relation"); |         expect(rootExp.subExpressions[1].constructor.name).toEqual("NotExp"); | ||||||
|         expect(rootExp.subExpression.attributeName).toEqual("myrelation"); |         expect(rootExp.subExpressions[1].subExpression.constructor.name).toEqual("AttributeExistsExp"); | ||||||
|  |         expect(rootExp.subExpressions[1].subExpression.attributeType).toEqual("relation"); | ||||||
|  |         expect(rootExp.subExpressions[1].subExpression.attributeName).toEqual("myrelation"); | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     it("simple label AND", () => { |     it("simple label AND", () => { | ||||||
| @@ -99,7 +116,10 @@ describe("Parser", () => { | |||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         expect(rootExp.constructor.name).toEqual("AndExp"); |         expect(rootExp.constructor.name).toEqual("AndExp"); | ||||||
|         const [firstSub, secondSub] = rootExp.subExpressions; |         assertIsArchived(rootExp.subExpressions[0]); | ||||||
|  |  | ||||||
|  |         expect(rootExp.subExpressions[1].constructor.name).toEqual("AndExp"); | ||||||
|  |         const [firstSub, secondSub] = rootExp.subExpressions[1].subExpressions; | ||||||
|  |  | ||||||
|         expect(firstSub.constructor.name).toEqual("LabelComparisonExp"); |         expect(firstSub.constructor.name).toEqual("LabelComparisonExp"); | ||||||
|         expect(firstSub.attributeName).toEqual("first"); |         expect(firstSub.attributeName).toEqual("first"); | ||||||
| @@ -116,7 +136,10 @@ describe("Parser", () => { | |||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         expect(rootExp.constructor.name).toEqual("AndExp"); |         expect(rootExp.constructor.name).toEqual("AndExp"); | ||||||
|         const [firstSub, secondSub] = rootExp.subExpressions; |         assertIsArchived(rootExp.subExpressions[0]); | ||||||
|  |  | ||||||
|  |         expect(rootExp.subExpressions[1].constructor.name).toEqual("AndExp"); | ||||||
|  |         const [firstSub, secondSub] = rootExp.subExpressions[1].subExpressions; | ||||||
|  |  | ||||||
|         expect(firstSub.constructor.name).toEqual("LabelComparisonExp"); |         expect(firstSub.constructor.name).toEqual("LabelComparisonExp"); | ||||||
|         expect(firstSub.attributeName).toEqual("first"); |         expect(firstSub.attributeName).toEqual("first"); | ||||||
| @@ -132,8 +155,11 @@ describe("Parser", () => { | |||||||
|             searchContext: new SearchContext() |             searchContext: new SearchContext() | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         expect(rootExp.constructor.name).toEqual("OrExp"); |         expect(rootExp.constructor.name).toEqual("AndExp"); | ||||||
|         const [firstSub, secondSub] = rootExp.subExpressions; |         assertIsArchived(rootExp.subExpressions[0]); | ||||||
|  |  | ||||||
|  |         expect(rootExp.subExpressions[1].constructor.name).toEqual("OrExp"); | ||||||
|  |         const [firstSub, secondSub] = rootExp.subExpressions[1].subExpressions; | ||||||
|  |  | ||||||
|         expect(firstSub.constructor.name).toEqual("LabelComparisonExp"); |         expect(firstSub.constructor.name).toEqual("LabelComparisonExp"); | ||||||
|         expect(firstSub.attributeName).toEqual("first"); |         expect(firstSub.attributeName).toEqual("first"); | ||||||
| @@ -155,8 +181,9 @@ describe("Parser", () => { | |||||||
|         expect(firstSub.constructor.name).toEqual("PropertyComparisonExp"); |         expect(firstSub.constructor.name).toEqual("PropertyComparisonExp"); | ||||||
|         expect(firstSub.propertyName).toEqual('isArchived'); |         expect(firstSub.propertyName).toEqual('isArchived'); | ||||||
|  |  | ||||||
|         expect(secondSub.constructor.name).toEqual("NoteCacheFlatTextExp"); |         expect(secondSub.constructor.name).toEqual("OrExp"); | ||||||
|         expect(secondSub.tokens).toEqual(["hello"]); |         expect(secondSub.subExpressions[0].constructor.name).toEqual("NoteCacheFlatTextExp"); | ||||||
|  |         expect(secondSub.subExpressions[0].tokens).toEqual(["hello"]); | ||||||
|  |  | ||||||
|         expect(thirdSub.constructor.name).toEqual("LabelComparisonExp"); |         expect(thirdSub.constructor.name).toEqual("LabelComparisonExp"); | ||||||
|         expect(thirdSub.attributeName).toEqual("mylabel"); |         expect(thirdSub.attributeName).toEqual("mylabel"); | ||||||
| @@ -169,8 +196,11 @@ describe("Parser", () => { | |||||||
|             searchContext: new SearchContext() |             searchContext: new SearchContext() | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         expect(rootExp.constructor.name).toEqual("OrExp"); |         expect(rootExp.constructor.name).toEqual("AndExp"); | ||||||
|         const [firstSub, secondSub] = rootExp.subExpressions; |         assertIsArchived(rootExp.subExpressions[0]); | ||||||
|  |  | ||||||
|  |         expect(rootExp.subExpressions[1].constructor.name).toEqual("OrExp"); | ||||||
|  |         const [firstSub, secondSub] = rootExp.subExpressions[1].subExpressions; | ||||||
|  |  | ||||||
|         expect(firstSub.constructor.name).toEqual("LabelComparisonExp"); |         expect(firstSub.constructor.name).toEqual("LabelComparisonExp"); | ||||||
|         expect(firstSub.attributeName).toEqual("first"); |         expect(firstSub.attributeName).toEqual("first"); | ||||||
| @@ -232,10 +262,12 @@ describe("Invalid expressions", () => { | |||||||
|             searchContext: new SearchContext() |             searchContext: new SearchContext() | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         expect(rootExp.constructor.name).toEqual("LabelComparisonExp"); |         expect(rootExp.constructor.name).toEqual("AndExp"); | ||||||
|         expect(rootExp.attributeType).toEqual("label"); |         assertIsArchived(rootExp.subExpressions[0]); | ||||||
|         expect(rootExp.attributeName).toEqual("first"); |         expect(rootExp.subExpressions[1].constructor.name).toEqual("LabelComparisonExp"); | ||||||
|         expect(rootExp.comparator).toBeTruthy(); |         expect(rootExp.subExpressions[1].attributeType).toEqual("label"); | ||||||
|  |         expect(rootExp.subExpressions[1].attributeName).toEqual("first"); | ||||||
|  |         expect(rootExp.subExpressions[1].comparator).toBeTruthy(); | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     it("searching by relation without note property", () => { |     it("searching by relation without note property", () => { | ||||||
|   | |||||||
| @@ -562,8 +562,8 @@ describe("Search", () => { | |||||||
|         expect(noteCache.notes[searchResults[0].noteId].title).toEqual("Austria"); |         expect(noteCache.notes[searchResults[0].noteId].title).toEqual("Austria"); | ||||||
|         expect(noteCache.notes[searchResults[1].noteId].title).toEqual("Italy"); |         expect(noteCache.notes[searchResults[1].noteId].title).toEqual("Italy"); | ||||||
|  |  | ||||||
|         searchResults = searchService.findNotesWithQuery('# note.parents.title = Europe orderBy #capital DESC limit 0', searchContext); |         searchResults = searchService.findNotesWithQuery('# note.parents.title = Europe orderBy #capital DESC limit 1', searchContext); | ||||||
|         expect(searchResults.length).toEqual(0); |         expect(searchResults.length).toEqual(1); | ||||||
|  |  | ||||||
|         searchResults = searchService.findNotesWithQuery('# note.parents.title = Europe orderBy #capital DESC limit 1000', searchContext); |         searchResults = searchService.findNotesWithQuery('# note.parents.title = Europe orderBy #capital DESC limit 1000', searchContext); | ||||||
|         expect(searchResults.length).toEqual(4); |         expect(searchResults.length).toEqual(4); | ||||||
|   | |||||||
| @@ -1,6 +1,9 @@ | |||||||
| const {note} = require('./note_cache_mocking.js'); | const {note} = require('./note_cache_mocking.js'); | ||||||
| const ValueExtractor = require('../../src/services/search/value_extractor.js'); | const ValueExtractor = require('../../src/services/search/value_extractor.js'); | ||||||
| const noteCache = require('../../src/services/note_cache/note_cache.js'); | const noteCache = require('../../src/services/note_cache/note_cache.js'); | ||||||
|  | const SearchContext = require("../../src/services/search/search_context.js"); | ||||||
|  |  | ||||||
|  | const dsc = new SearchContext(); | ||||||
|  |  | ||||||
| describe("Value extractor", () => { | describe("Value extractor", () => { | ||||||
|     beforeEach(() => { |     beforeEach(() => { | ||||||
| @@ -10,7 +13,7 @@ describe("Value extractor", () => { | |||||||
|     it("simple title extraction", async () => { |     it("simple title extraction", async () => { | ||||||
|         const europe = note("Europe").note; |         const europe = note("Europe").note; | ||||||
|  |  | ||||||
|         const valueExtractor = new ValueExtractor(["note", "title"]); |         const valueExtractor = new ValueExtractor(dsc, ["note", "title"]); | ||||||
|  |  | ||||||
|         expect(valueExtractor.validate()).toBeFalsy(); |         expect(valueExtractor.validate()).toBeFalsy(); | ||||||
|         expect(valueExtractor.extract(europe)).toEqual("Europe"); |         expect(valueExtractor.extract(europe)).toEqual("Europe"); | ||||||
| @@ -21,12 +24,12 @@ describe("Value extractor", () => { | |||||||
|             .label("Capital", "Vienna") |             .label("Capital", "Vienna") | ||||||
|             .note; |             .note; | ||||||
|  |  | ||||||
|         let valueExtractor = new ValueExtractor(["note", "labels", "capital"]); |         let valueExtractor = new ValueExtractor(dsc, ["note", "labels", "capital"]); | ||||||
|  |  | ||||||
|         expect(valueExtractor.validate()).toBeFalsy(); |         expect(valueExtractor.validate()).toBeFalsy(); | ||||||
|         expect(valueExtractor.extract(austria)).toEqual("Vienna"); |         expect(valueExtractor.extract(austria)).toEqual("Vienna"); | ||||||
|  |  | ||||||
|         valueExtractor = new ValueExtractor(["#capital"]); |         valueExtractor = new ValueExtractor(dsc, ["#capital"]); | ||||||
|  |  | ||||||
|         expect(valueExtractor.validate()).toBeFalsy(); |         expect(valueExtractor.validate()).toBeFalsy(); | ||||||
|         expect(valueExtractor.extract(austria)).toEqual("Vienna"); |         expect(valueExtractor.extract(austria)).toEqual("Vienna"); | ||||||
| @@ -38,12 +41,12 @@ describe("Value extractor", () => { | |||||||
|             .child(note("Austria") |             .child(note("Austria") | ||||||
|                 .child(vienna)); |                 .child(vienna)); | ||||||
|  |  | ||||||
|         let valueExtractor = new ValueExtractor(["note", "children", "children", "title"]); |         let valueExtractor = new ValueExtractor(dsc, ["note", "children", "children", "title"]); | ||||||
|  |  | ||||||
|         expect(valueExtractor.validate()).toBeFalsy(); |         expect(valueExtractor.validate()).toBeFalsy(); | ||||||
|         expect(valueExtractor.extract(europe.note)).toEqual("Vienna"); |         expect(valueExtractor.extract(europe.note)).toEqual("Vienna"); | ||||||
|  |  | ||||||
|         valueExtractor = new ValueExtractor(["note", "parents", "parents", "title"]); |         valueExtractor = new ValueExtractor(dsc, ["note", "parents", "parents", "title"]); | ||||||
|  |  | ||||||
|         expect(valueExtractor.validate()).toBeFalsy(); |         expect(valueExtractor.validate()).toBeFalsy(); | ||||||
|         expect(valueExtractor.extract(vienna.note)).toEqual("Europe"); |         expect(valueExtractor.extract(vienna.note)).toEqual("Europe"); | ||||||
| @@ -56,12 +59,12 @@ describe("Value extractor", () => { | |||||||
|                 .relation('neighbor', czechRepublic.note) |                 .relation('neighbor', czechRepublic.note) | ||||||
|                 .relation('neighbor', slovakia.note); |                 .relation('neighbor', slovakia.note); | ||||||
|  |  | ||||||
|         let valueExtractor = new ValueExtractor(["note", "relations", "neighbor", "labels", "capital"]); |         let valueExtractor = new ValueExtractor(dsc, ["note", "relations", "neighbor", "labels", "capital"]); | ||||||
|  |  | ||||||
|         expect(valueExtractor.validate()).toBeFalsy(); |         expect(valueExtractor.validate()).toBeFalsy(); | ||||||
|         expect(valueExtractor.extract(austria.note)).toEqual("Prague"); |         expect(valueExtractor.extract(austria.note)).toEqual("Prague"); | ||||||
|  |  | ||||||
|         valueExtractor = new ValueExtractor(["~neighbor", "labels", "capital"]); |         valueExtractor = new ValueExtractor(dsc, ["~neighbor", "labels", "capital"]); | ||||||
|  |  | ||||||
|         expect(valueExtractor.validate()).toBeFalsy(); |         expect(valueExtractor.validate()).toBeFalsy(); | ||||||
|         expect(valueExtractor.extract(austria.note)).toEqual("Prague"); |         expect(valueExtractor.extract(austria.note)).toEqual("Prague"); | ||||||
| @@ -70,17 +73,17 @@ describe("Value extractor", () => { | |||||||
|  |  | ||||||
| describe("Invalid value extractor property path", () => { | describe("Invalid value extractor property path", () => { | ||||||
|     it('each path must start with "note" (or label/relation)', |     it('each path must start with "note" (or label/relation)', | ||||||
|         () => expect(new ValueExtractor(["neighbor"]).validate()).toBeTruthy()); |         () => expect(new ValueExtractor(dsc, ["neighbor"]).validate()).toBeTruthy()); | ||||||
|  |  | ||||||
|     it("extra path element after terminal label", |     it("extra path element after terminal label", | ||||||
|         () => expect(new ValueExtractor(["~neighbor", "labels", "capital", "noteId"]).validate()).toBeTruthy()); |         () => expect(new ValueExtractor(dsc, ["~neighbor", "labels", "capital", "noteId"]).validate()).toBeTruthy()); | ||||||
|  |  | ||||||
|     it("extra path element after terminal title", |     it("extra path element after terminal title", | ||||||
|         () => expect(new ValueExtractor(["note", "title", "isProtected"]).validate()).toBeTruthy()); |         () => expect(new ValueExtractor(dsc, ["note", "title", "isProtected"]).validate()).toBeTruthy()); | ||||||
|  |  | ||||||
|     it("relation name and note property is missing", |     it("relation name and note property is missing", | ||||||
|         () => expect(new ValueExtractor(["note", "relations"]).validate()).toBeTruthy()); |         () => expect(new ValueExtractor(dsc, ["note", "relations"]).validate()).toBeTruthy()); | ||||||
|  |  | ||||||
|     it("relation is specified but target note property is not specified", |     it("relation is specified but target note property is not specified", | ||||||
|         () => expect(new ValueExtractor(["note", "relations", "myrel"]).validate()).toBeTruthy()); |         () => expect(new ValueExtractor(dsc, ["note", "relations", "myrel"]).validate()).toBeTruthy()); | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -49,7 +49,12 @@ class Note extends Entity { | |||||||
|             this.isContentAvailable = protectedSessionService.isProtectedSessionAvailable(); |             this.isContentAvailable = protectedSessionService.isProtectedSessionAvailable(); | ||||||
|  |  | ||||||
|             if (this.isContentAvailable) { |             if (this.isContentAvailable) { | ||||||
|                 this.title = protectedSessionService.decryptString(this.title); |                 try { | ||||||
|  |                     this.title = protectedSessionService.decryptString(this.title); | ||||||
|  |                 } | ||||||
|  |                 catch (e) { | ||||||
|  |                     throw new Error(`Could not decrypt title of note ${this.noteId}: ${e.message} ${e.stack}`) | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|             else { |             else { | ||||||
|                 this.title = "[protected]"; |                 this.title = "[protected]"; | ||||||
| @@ -156,14 +161,14 @@ class Note extends Entity { | |||||||
|  |  | ||||||
|         sql.upsert("note_contents", "noteId", pojo); |         sql.upsert("note_contents", "noteId", pojo); | ||||||
|  |  | ||||||
|         const hash = utils.hash(this.noteId + "|" + content.toString()); |         const hash = utils.hash(this.noteId + "|" + pojo.content.toString()); | ||||||
|  |  | ||||||
|         entityChangesService.addEntityChange({ |         entityChangesService.addEntityChange({ | ||||||
|             entityName: 'note_contents', |             entityName: 'note_contents', | ||||||
|             entityId: this.noteId, |             entityId: this.noteId, | ||||||
|             hash: hash, |             hash: hash, | ||||||
|             isErased: false, |             isErased: false, | ||||||
|             utcDateChanged: this.getUtcDateChanged() |             utcDateChanged: pojo.utcDateModified | ||||||
|         }, null); |         }, null); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -365,7 +370,6 @@ class Note extends Entity { | |||||||
|                 WHERE attributes.isDeleted = 0 |                 WHERE attributes.isDeleted = 0 | ||||||
|                   AND attributes.type = 'relation' |                   AND attributes.type = 'relation' | ||||||
|                   AND attributes.name = 'template' |                   AND attributes.name = 'template' | ||||||
|                   AND (treeWithAttrs.level = 0 OR attributes.isInheritable = 1) |  | ||||||
|                 ) |                 ) | ||||||
|             SELECT attributes.* FROM attributes JOIN treeWithAttrs ON attributes.noteId = treeWithAttrs.noteId |             SELECT attributes.* FROM attributes JOIN treeWithAttrs ON attributes.noteId = treeWithAttrs.noteId | ||||||
|             WHERE attributes.isDeleted = 0 AND (attributes.isInheritable = 1 OR treeWithAttrs.level = 0) |             WHERE attributes.isDeleted = 0 AND (attributes.isInheritable = 1 OR treeWithAttrs.level = 0) | ||||||
|   | |||||||
| @@ -359,7 +359,7 @@ class NoteShort { | |||||||
|         const workspaceIconClass = this.getWorkspaceIconClass(); |         const workspaceIconClass = this.getWorkspaceIconClass(); | ||||||
|  |  | ||||||
|         if (iconClassLabels.length > 0) { |         if (iconClassLabels.length > 0) { | ||||||
|             return iconClassLabels.map(l => l.value).join(' '); |             return iconClassLabels[0].value; | ||||||
|         } |         } | ||||||
|         else if (workspaceIconClass) { |         else if (workspaceIconClass) { | ||||||
|             return workspaceIconClass; |             return workspaceIconClass; | ||||||
|   | |||||||
| @@ -83,6 +83,9 @@ export default class MobileLayout { | |||||||
|                     .child(new NoteTitleWidget()) |                     .child(new NoteTitleWidget()) | ||||||
|                     .child(new CloseDetailButtonWidget())) |                     .child(new CloseDetailButtonWidget())) | ||||||
|                 .child(new NoteDetailWidget() |                 .child(new NoteDetailWidget() | ||||||
|                     .css('padding', '5px 20px 10px 0'))); |                     .css('padding', '5px 20px 10px 0') | ||||||
|  |                     .css('overflow', 'auto') | ||||||
|  |                     .css('height', '100%') | ||||||
|  |                 )); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,5 +1,6 @@ | |||||||
| import utils from "./utils.js"; | import utils from "./utils.js"; | ||||||
| import options from './options.js'; | import options from './options.js'; | ||||||
|  | import server from "./server.js"; | ||||||
|  |  | ||||||
| const PROTECTED_SESSION_ID_KEY = 'protectedSessionId'; | const PROTECTED_SESSION_ID_KEY = 'protectedSessionId'; | ||||||
|  |  | ||||||
| @@ -23,11 +24,11 @@ function resetSessionCookie() { | |||||||
|     utils.setSessionCookie(PROTECTED_SESSION_ID_KEY, null); |     utils.setSessionCookie(PROTECTED_SESSION_ID_KEY, null); | ||||||
| } | } | ||||||
|  |  | ||||||
| function resetProtectedSession() { | async function resetProtectedSession() { | ||||||
|     resetSessionCookie(); |     resetSessionCookie(); | ||||||
|  |  | ||||||
|     // most secure solution - guarantees nothing remained in memory |     await server.post("logout/protected"); | ||||||
|     // since this expires because user doesn't use the app, it shouldn't be disruptive |  | ||||||
|     utils.reloadApp(); |     utils.reloadApp(); | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -36,7 +36,7 @@ export default class NoteTitleWidget extends TabAwareWidget { | |||||||
|  |  | ||||||
|             protectedSessionHolder.touchProtectedSessionIfNecessary(this.note); |             protectedSessionHolder.touchProtectedSessionIfNecessary(this.note); | ||||||
|  |  | ||||||
|             await server.put(`notes/${this.noteId}/change-title`, {title}); |             await server.put(`notes/${this.noteId}/change-title`, {title}, this.componentId); | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         appContext.addBeforeUnloadListener(this); |         appContext.addBeforeUnloadListener(this); | ||||||
|   | |||||||
| @@ -176,6 +176,15 @@ const TPL = ` | |||||||
|                       title="Images which are shown in the parent text note will not be displayed in the tree"></span> |                       title="Images which are shown in the parent text note will not be displayed in the tree"></span> | ||||||
|             </label> |             </label> | ||||||
|         </div> |         </div> | ||||||
|  |         <div class="form-check"> | ||||||
|  |             <label class="form-check-label"> | ||||||
|  |                 <input class="form-check-input auto-collapse-note-tree" type="checkbox" value=""> | ||||||
|  |                  | ||||||
|  |                 Automatically collapse notes | ||||||
|  |                 <span class="bx bx-info-circle"  | ||||||
|  |                       title="Notes will be collapsed after period of inactivity to declutter the tree."></span> | ||||||
|  |             </label> | ||||||
|  |         </div> | ||||||
|      |      | ||||||
|         <br/> |         <br/> | ||||||
|      |      | ||||||
| @@ -235,6 +244,7 @@ export default class NoteTreeWidget extends TabAwareWidget { | |||||||
|         this.$treeSettingsPopup = this.$widget.find('.tree-settings-popup'); |         this.$treeSettingsPopup = this.$widget.find('.tree-settings-popup'); | ||||||
|         this.$hideArchivedNotesCheckbox = this.$treeSettingsPopup.find('.hide-archived-notes'); |         this.$hideArchivedNotesCheckbox = this.$treeSettingsPopup.find('.hide-archived-notes'); | ||||||
|         this.$hideIncludedImages = this.$treeSettingsPopup.find('.hide-included-images'); |         this.$hideIncludedImages = this.$treeSettingsPopup.find('.hide-included-images'); | ||||||
|  |         this.$autoCollapseNoteTree = this.$treeSettingsPopup.find('.auto-collapse-note-tree'); | ||||||
|  |  | ||||||
|         this.$treeSettingsButton = this.$widget.find('.tree-settings-button'); |         this.$treeSettingsButton = this.$widget.find('.tree-settings-button'); | ||||||
|         this.$treeSettingsButton.on("click", e => { |         this.$treeSettingsButton.on("click", e => { | ||||||
| @@ -245,6 +255,7 @@ export default class NoteTreeWidget extends TabAwareWidget { | |||||||
|  |  | ||||||
|             this.$hideArchivedNotesCheckbox.prop("checked", this.hideArchivedNotes); |             this.$hideArchivedNotesCheckbox.prop("checked", this.hideArchivedNotes); | ||||||
|             this.$hideIncludedImages.prop("checked", this.hideIncludedImages); |             this.$hideIncludedImages.prop("checked", this.hideIncludedImages); | ||||||
|  |             this.$autoCollapseNoteTree.prop("checked", this.autoCollapseNoteTree); | ||||||
|  |  | ||||||
|             let top = this.$treeSettingsButton[0].offsetTop; |             let top = this.$treeSettingsButton[0].offsetTop; | ||||||
|             let left = this.$treeSettingsButton[0].offsetLeft; |             let left = this.$treeSettingsButton[0].offsetLeft; | ||||||
| @@ -272,6 +283,7 @@ export default class NoteTreeWidget extends TabAwareWidget { | |||||||
|         this.$saveTreeSettingsButton.on('click', async () => { |         this.$saveTreeSettingsButton.on('click', async () => { | ||||||
|             await this.setHideArchivedNotes(this.$hideArchivedNotesCheckbox.prop("checked")); |             await this.setHideArchivedNotes(this.$hideArchivedNotesCheckbox.prop("checked")); | ||||||
|             await this.setHideIncludedImages(this.$hideIncludedImages.prop("checked")); |             await this.setHideIncludedImages(this.$hideIncludedImages.prop("checked")); | ||||||
|  |             await this.setAutoCollapseNoteTree(this.$autoCollapseNoteTree.prop("checked")); | ||||||
|  |  | ||||||
|             this.$treeSettingsPopup.hide(); |             this.$treeSettingsPopup.hide(); | ||||||
|  |  | ||||||
| @@ -327,6 +339,14 @@ export default class NoteTreeWidget extends TabAwareWidget { | |||||||
|         await options.save("hideIncludedImages_" + this.treeName, val.toString()); |         await options.save("hideIncludedImages_" + this.treeName, val.toString()); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     get autoCollapseNoteTree() { | ||||||
|  |         return options.is("autoCollapseNoteTree"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     async setAutoCollapseNoteTree(val) { | ||||||
|  |         await options.save("autoCollapseNoteTree", val.toString()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     initFancyTree() { |     initFancyTree() { | ||||||
|         const treeData = [this.prepareRootNode()]; |         const treeData = [this.prepareRootNode()]; | ||||||
|  |  | ||||||
| @@ -362,8 +382,6 @@ export default class NoteTreeWidget extends TabAwareWidget { | |||||||
|                     } |                     } | ||||||
|                     else { |                     else { | ||||||
|                         node.setActive(); |                         node.setActive(); | ||||||
|  |  | ||||||
|                         this.clearSelectedNodes(); |  | ||||||
|                     } |                     } | ||||||
|  |  | ||||||
|                     return false; |                     return false; | ||||||
| @@ -373,6 +391,8 @@ export default class NoteTreeWidget extends TabAwareWidget { | |||||||
|                 // click event won't propagate so let's close context menu manually |                 // click event won't propagate so let's close context menu manually | ||||||
|                 contextMenu.hide(); |                 contextMenu.hide(); | ||||||
|  |  | ||||||
|  |                 this.clearSelectedNodes(); | ||||||
|  |  | ||||||
|                 const notePath = treeService.getNotePath(data.node); |                 const notePath = treeService.getNotePath(data.node); | ||||||
|  |  | ||||||
|                 const activeTabContext = appContext.tabManager.getActiveTabContext(); |                 const activeTabContext = appContext.tabManager.getActiveTabContext(); | ||||||
| @@ -797,8 +817,10 @@ export default class NoteTreeWidget extends TabAwareWidget { | |||||||
|  |  | ||||||
|             const node = await this.expandToNote(activeContext.notePath); |             const node = await this.expandToNote(activeContext.notePath); | ||||||
|  |  | ||||||
|             await node.makeVisible({scrollIntoView: true}); |             if (node) { | ||||||
|             node.setActive(true, {noEvents: true, noFocus: false}); |                 await node.makeVisible({scrollIntoView: true}); | ||||||
|  |                 node.setActive(true, {noEvents: true, noFocus: false}); | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -851,7 +873,11 @@ export default class NoteTreeWidget extends TabAwareWidget { | |||||||
|                             // these are real notes with real notePath, user can display them in a detail |                             // these are real notes with real notePath, user can display them in a detail | ||||||
|                             // but they don't have a node in the tree |                             // but they don't have a node in the tree | ||||||
|  |  | ||||||
|                             ws.logError(`Can't find node for child node of noteId=${childNoteId} for parent of noteId=${parentNode.data.noteId} and hoistedNoteId=${hoistedNoteService.getHoistedNoteId()}, requested path is ${notePath}`); |                             const childNote = await treeCache.getNote(childNoteId); | ||||||
|  |  | ||||||
|  |                             if (!childNote || childNote.type !== 'image') { | ||||||
|  |                                 ws.logError(`Can't find node for child node of noteId=${childNoteId} for parent of noteId=${parentNode.data.noteId} and hoistedNoteId=${hoistedNoteService.getHoistedNoteId()}, requested path is ${notePath}`); | ||||||
|  |                             } | ||||||
|                         } |                         } | ||||||
|  |  | ||||||
|                         return; |                         return; | ||||||
| @@ -955,6 +981,10 @@ export default class NoteTreeWidget extends TabAwareWidget { | |||||||
|         } |         } | ||||||
|  |  | ||||||
|         this.autoCollapseTimeoutId = setTimeout(() => { |         this.autoCollapseTimeoutId = setTimeout(() => { | ||||||
|  |             if (!this.autoCollapseNoteTree) { | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |  | ||||||
|             /* |             /* | ||||||
|              * We're collapsing notes after period of inactivity to "cleanup" the tree - users rarely |              * We're collapsing notes after period of inactivity to "cleanup" the tree - users rarely | ||||||
|              * collapse the notes and the tree becomes unusuably large. |              * collapse the notes and the tree becomes unusuably large. | ||||||
| @@ -1114,11 +1144,12 @@ export default class NoteTreeWidget extends TabAwareWidget { | |||||||
|             } |             } | ||||||
|  |  | ||||||
|             if (node) { |             if (node) { | ||||||
|                 node.setActive(true, {noEvents: true, noFocus: !activeNodeFocused}); |  | ||||||
|  |  | ||||||
|                 if (activeNodeFocused) { |                 if (activeNodeFocused) { | ||||||
|                     node.setFocus(true); |                     // needed by Firefox: https://github.com/zadam/trilium/issues/1865 | ||||||
|  |                     this.tree.$container.focus(); | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|  |                 await node.setActive(true, {noEvents: true, noFocus: !activeNodeFocused}); | ||||||
|             } |             } | ||||||
|             else { |             else { | ||||||
|                 // this is used when original note has been deleted and we want to move the focus to the note above/below |                 // this is used when original note has been deleted and we want to move the focus to the note above/below | ||||||
|   | |||||||
| @@ -5,7 +5,7 @@ const TPL = ` | |||||||
|     <td colspan="2"> |     <td colspan="2"> | ||||||
|         <span class="bx bx-trash"></span> |         <span class="bx bx-trash"></span> | ||||||
|      |      | ||||||
|         Delete matched note |         Delete matched notes | ||||||
|     </td> |     </td> | ||||||
|     <td class="button-column"> |     <td class="button-column"> | ||||||
|         <span class="bx bx-x icon-action action-conf-del"></span> |         <span class="bx bx-x icon-action action-conf-del"></span> | ||||||
|   | |||||||
| @@ -14,6 +14,10 @@ const TPL = ` | |||||||
|         height: 35px; |         height: 35px; | ||||||
|     } |     } | ||||||
|      |      | ||||||
|  |     .standard-top-widget > div { | ||||||
|  |         flex-shrink: 0; /* fixes https://github.com/zadam/trilium/issues/1745 */ | ||||||
|  |     } | ||||||
|  |      | ||||||
|     .standard-top-widget button.noborder { |     .standard-top-widget button.noborder { | ||||||
|         padding: 1px 5px 1px 5px; |         padding: 1px 5px 1px 5px; | ||||||
|         font-size: 90%; |         font-size: 90%; | ||||||
|   | |||||||
| @@ -5,24 +5,47 @@ import linkService from "../../services/link.js"; | |||||||
| import noteContentRenderer from "../../services/note_content_renderer.js"; | import noteContentRenderer from "../../services/note_content_renderer.js"; | ||||||
|  |  | ||||||
| export default class AbstractTextTypeWidget extends TypeWidget { | export default class AbstractTextTypeWidget extends TypeWidget { | ||||||
|     doRender() { |     setupImageOpening(singleClickOpens) { | ||||||
|         this.$widget.on("dblclick", "img", e => { |         this.$widget.on("dblclick", "img", e => this.openImageInCurrentTab($(e.target))); | ||||||
|             const $img = $(e.target); |  | ||||||
|             const src = $img.prop("src"); |  | ||||||
|  |  | ||||||
|             const match = src.match(/\/api\/images\/([A-Za-z0-9]+)\//); |         this.$widget.on("click", "img", e => { | ||||||
|  |             if ((e.which === 1 && e.ctrlKey) || e.which === 2) { | ||||||
|             if (match) { |                 this.openImageInNewTab($(e.target)); | ||||||
|                 const noteId = match[1]; |  | ||||||
|  |  | ||||||
|                 appContext.tabManager.getActiveTabContext().setNote(noteId); |  | ||||||
|             } |             } | ||||||
|             else { |             else if (e.which === 1 && singleClickOpens) { | ||||||
|                 window.open(src, '_blank'); |                 this.openImageInCurrentTab($(e.target)); | ||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     openImageInCurrentTab($img) { | ||||||
|  |         const imgSrc = $img.prop("src"); | ||||||
|  |         const noteId = this.getNoteIdFromImage(imgSrc); | ||||||
|  |  | ||||||
|  |         if (noteId) { | ||||||
|  |             appContext.tabManager.getActiveTabContext().setNote(noteId); | ||||||
|  |         } else { | ||||||
|  |             window.open(imgSrc, '_blank'); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     openImageInNewTab($img) { | ||||||
|  |         const imgSrc = $img.prop("src"); | ||||||
|  |         const noteId = this.getNoteIdFromImage(imgSrc); | ||||||
|  |  | ||||||
|  |         if (noteId) { | ||||||
|  |             appContext.tabManager.openTabWithNoteWithHoisting(noteId); | ||||||
|  |         } else { | ||||||
|  |             window.open(imgSrc, '_blank'); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     getNoteIdFromImage(imgSrc) { | ||||||
|  |         const match = imgSrc.match(/\/api\/images\/([A-Za-z0-9]+)\//); | ||||||
|  |  | ||||||
|  |         return match ? match[1] : null; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     async loadIncludedNote(noteId, $el) { |     async loadIncludedNote(noteId, $el) { | ||||||
|         const note = await treeCache.getNote(noteId); |         const note = await treeCache.getNote(noteId); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -84,6 +84,8 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget { | |||||||
|  |  | ||||||
|         keyboardActionService.setupActionsForElement('text-detail', this.$widget, this); |         keyboardActionService.setupActionsForElement('text-detail', this.$widget, this); | ||||||
|  |  | ||||||
|  |         this.setupImageOpening(false); | ||||||
|  |  | ||||||
|         super.doRender(); |         super.doRender(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -5,6 +5,7 @@ const TPL = ` | |||||||
|     <style> |     <style> | ||||||
|     .note-detail-read-only-code { |     .note-detail-read-only-code { | ||||||
|         position: relative; |         position: relative; | ||||||
|  |         min-height: 50px; | ||||||
|     } |     } | ||||||
|      |      | ||||||
|     .note-detail-read-only-code-content { |     .note-detail-read-only-code-content { | ||||||
|   | |||||||
| @@ -25,6 +25,7 @@ const TPL = ` | |||||||
|         padding-top: 10px; |         padding-top: 10px; | ||||||
|         font-family: var(--detail-text-font-family); |         font-family: var(--detail-text-font-family); | ||||||
|         position: relative; |         position: relative; | ||||||
|  |         min-height: 50px; | ||||||
|     } |     } | ||||||
|          |          | ||||||
|     .note-detail-readonly-text p:first-child, .note-detail-readonly-text::before { |     .note-detail-readonly-text p:first-child, .note-detail-readonly-text::before { | ||||||
| @@ -33,6 +34,7 @@ const TPL = ` | |||||||
|      |      | ||||||
|     .note-detail-readonly-text img { |     .note-detail-readonly-text img { | ||||||
|         max-width: 100%; |         max-width: 100%; | ||||||
|  |         cursor: pointer; | ||||||
|     } |     } | ||||||
|      |      | ||||||
|     .edit-text-note-container { |     .edit-text-note-container { | ||||||
| @@ -65,6 +67,8 @@ export default class ReadOnlyTextTypeWidget extends AbstractTextTypeWidget { | |||||||
|             this.triggerEvent('textPreviewDisabled', {tabContext: this.tabContext}); |             this.triggerEvent('textPreviewDisabled', {tabContext: this.tabContext}); | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|  |         this.setupImageOpening(true); | ||||||
|  |  | ||||||
|         super.doRender(); |         super.doRender(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -107,7 +107,11 @@ function processContent(images, note, content) { | |||||||
|             const filename = path.basename(src); |             const filename = path.basename(src); | ||||||
|  |  | ||||||
|             if (!dataUrl || !dataUrl.startsWith("data:image")) { |             if (!dataUrl || !dataUrl.startsWith("data:image")) { | ||||||
|                 log.info("Image could not be recognized as data URL:", dataUrl.substr(0, Math.min(100, dataUrl.length))); |                 const excerpt = dataUrl | ||||||
|  |                     ? dataUrl.substr(0, Math.min(100, dataUrl.length)) | ||||||
|  |                     : "null"; | ||||||
|  |  | ||||||
|  |                 log.info("Image could not be recognized as data URL: " + excerpt); | ||||||
|                 continue; |                 continue; | ||||||
|             } |             } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -78,6 +78,12 @@ function loginToProtectedSession(req) { | |||||||
|     }; |     }; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | function logoutFromProtectedSession() { | ||||||
|  |     protectedSessionService.resetDataKey(); | ||||||
|  |  | ||||||
|  |     eventService.emit(eventService.LEAVE_PROTECTED_SESSION); | ||||||
|  | } | ||||||
|  |  | ||||||
| function token(req) { | function token(req) { | ||||||
|     const username = req.body.username; |     const username = req.body.username; | ||||||
|     const password = req.body.password; |     const password = req.body.password; | ||||||
| @@ -101,5 +107,6 @@ function token(req) { | |||||||
| module.exports = { | module.exports = { | ||||||
|     loginSync, |     loginSync, | ||||||
|     loginToProtectedSession, |     loginToProtectedSession, | ||||||
|  |     logoutFromProtectedSession, | ||||||
|     token |     token | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -5,6 +5,7 @@ const noteCacheService = require('../../services/note_cache/note_cache_service') | |||||||
| const protectedSessionService = require('../../services/protected_session'); | const protectedSessionService = require('../../services/protected_session'); | ||||||
| const noteRevisionService = require('../../services/note_revisions'); | const noteRevisionService = require('../../services/note_revisions'); | ||||||
| const utils = require('../../services/utils'); | const utils = require('../../services/utils'); | ||||||
|  | const sql = require('../../services/sql'); | ||||||
| const path = require('path'); | const path = require('path'); | ||||||
|  |  | ||||||
| function getNoteRevisions(req) { | function getNoteRevisions(req) { | ||||||
|   | |||||||
| @@ -41,7 +41,8 @@ const ALLOWED_OPTIONS = new Set([ | |||||||
|     'attributeListExpanded', |     'attributeListExpanded', | ||||||
|     'promotedAttributesExpanded', |     'promotedAttributesExpanded', | ||||||
|     'similarNotesExpanded', |     'similarNotesExpanded', | ||||||
|     'headingStyle' |     'headingStyle', | ||||||
|  |     'autoCollapseNoteTree' | ||||||
| ]); | ]); | ||||||
|  |  | ||||||
| function getOptions() { | function getOptions() { | ||||||
|   | |||||||
| @@ -200,9 +200,7 @@ function queueSector(req) { | |||||||
|     const entityName = utils.sanitizeSqlIdentifier(req.params.entityName); |     const entityName = utils.sanitizeSqlIdentifier(req.params.entityName); | ||||||
|     const sector = utils.sanitizeSqlIdentifier(req.params.sector); |     const sector = utils.sanitizeSqlIdentifier(req.params.sector); | ||||||
|  |  | ||||||
|     const entityPrimaryKey = entityConstructor.getEntityFromEntityName(entityName).primaryKeyName; |     entityChangesService.addEntityChangesForSector(entityName, sector); | ||||||
|  |  | ||||||
|     entityChangesService.addEntityChangesForSector(entityName, entityPrimaryKey, sector); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|   | |||||||
| @@ -270,6 +270,8 @@ function register(app) { | |||||||
|     route(POST, '/api/login/sync', [], loginApiRoute.loginSync, apiResultHandler); |     route(POST, '/api/login/sync', [], loginApiRoute.loginSync, apiResultHandler); | ||||||
|     // this is for entering protected mode so user has to be already logged-in (that's the reason we don't require username) |     // this is for entering protected mode so user has to be already logged-in (that's the reason we don't require username) | ||||||
|     apiRoute(POST, '/api/login/protected', loginApiRoute.loginToProtectedSession); |     apiRoute(POST, '/api/login/protected', loginApiRoute.loginToProtectedSession); | ||||||
|  |     apiRoute(POST, '/api/logout/protected', loginApiRoute.logoutFromProtectedSession); | ||||||
|  |  | ||||||
|     route(POST, '/api/login/token', [], loginApiRoute.token, apiResultHandler); |     route(POST, '/api/login/token', [], loginApiRoute.token, apiResultHandler); | ||||||
|  |  | ||||||
|     // in case of local electron, local calls are allowed unauthenticated, for server they need auth |     // in case of local electron, local calls are allowed unauthenticated, for server they need auth | ||||||
|   | |||||||
| @@ -1 +1 @@ | |||||||
| module.exports = { buildDate:"2021-03-14T22:56:27+01:00", buildRevision: "6c8d20288df302f3a415bd1bdcace98bf29d4bf6" }; | module.exports = { buildDate:"2021-04-22T20:50:22+02:00", buildRevision: "c27f573eed8905fc1d958adf5bdee0efc3aff593" }; | ||||||
|   | |||||||
| @@ -53,22 +53,14 @@ function moveEntityChangeToTop(entityName, entityId) { | |||||||
|     addEntityChange(entityName, entityId, hash, null, isSynced); |     addEntityChange(entityName, entityId, hash, null, isSynced); | ||||||
| } | } | ||||||
|  |  | ||||||
| function addEntityChangesForSector(entityName, entityPrimaryKey, sector) { | function addEntityChangesForSector(entityName, sector) { | ||||||
|     const startTime = Date.now(); |     const startTime = Date.now(); | ||||||
|     const repository = require('./repository'); |  | ||||||
|  |     const entityChanges = sql.getRows(`SELECT * FROM entity_changes WHERE entityName = ? AND SUBSTR(entityId, 1, 1) = ?`, [entityName, sector]); | ||||||
|  |  | ||||||
|     sql.transactional(() => { |     sql.transactional(() => { | ||||||
|         const entityIds = sql.getColumn(`SELECT ${entityPrimaryKey} FROM ${entityName} WHERE SUBSTR(${entityPrimaryKey}, 1, 1) = ?`, [sector]); |         for (const ec of entityChanges) { | ||||||
|  |             insertEntityChange(entityName, ec.entityId, ec.hash, ec.isErased, ec.utcDateChanged, ec.sourceId, ec.isSynced); | ||||||
|         for (const entityId of entityIds) { |  | ||||||
|             // retrieving entity one by one to avoid memory issues with note_contents |  | ||||||
|             const entity = repository.getEntity(`SELECT * FROM ${entityName} WHERE ${entityPrimaryKey} = ?`, [entityId]); |  | ||||||
|  |  | ||||||
|             if (entityName === 'options' && !entity.isSynced) { |  | ||||||
|                 continue |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             insertEntityChange(entityName, entityId, entity.generateHash(), false, entity.getUtcDateChanged(), 'content-check', true); |  | ||||||
|         } |         } | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ const log = require('./log'); | |||||||
|  |  | ||||||
| const NOTE_TITLE_CHANGED = "NOTE_TITLE_CHANGED"; | const NOTE_TITLE_CHANGED = "NOTE_TITLE_CHANGED"; | ||||||
| const ENTER_PROTECTED_SESSION = "ENTER_PROTECTED_SESSION"; | const ENTER_PROTECTED_SESSION = "ENTER_PROTECTED_SESSION"; | ||||||
|  | const LEAVE_PROTECTED_SESSION = "LEAVE_PROTECTED_SESSION"; | ||||||
| const ENTITY_CREATED = "ENTITY_CREATED"; | const ENTITY_CREATED = "ENTITY_CREATED"; | ||||||
| const ENTITY_CHANGED = "ENTITY_CHANGED"; | const ENTITY_CHANGED = "ENTITY_CHANGED"; | ||||||
| const ENTITY_DELETED = "ENTITY_DELETED"; | const ENTITY_DELETED = "ENTITY_DELETED"; | ||||||
| @@ -47,6 +48,7 @@ module.exports = { | |||||||
|     // event types: |     // event types: | ||||||
|     NOTE_TITLE_CHANGED, |     NOTE_TITLE_CHANGED, | ||||||
|     ENTER_PROTECTED_SESSION, |     ENTER_PROTECTED_SESSION, | ||||||
|  |     LEAVE_PROTECTED_SESSION, | ||||||
|     ENTITY_CREATED, |     ENTITY_CREATED, | ||||||
|     ENTITY_CHANGED, |     ENTITY_CHANGED, | ||||||
|     ENTITY_DELETED, |     ENTITY_DELETED, | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| "use strict"; | "use strict"; | ||||||
|  |  | ||||||
| const protectedSessionService = require('../../protected_session'); | const protectedSessionService = require('../../protected_session'); | ||||||
|  | const log = require('../../log'); | ||||||
|  |  | ||||||
| class Note { | class Note { | ||||||
|     constructor(noteCache, row) { |     constructor(noteCache, row) { | ||||||
| @@ -405,7 +406,7 @@ class Note { | |||||||
|             return 0; |             return 0; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         let minDistance = 999_999; |         let minDistance = 999999; | ||||||
|  |  | ||||||
|         for (const parent of this.parents) { |         for (const parent of this.parents) { | ||||||
|             minDistance = Math.min(minDistance, parent.getDistanceToAncestor(ancestorNoteId) + 1); |             minDistance = Math.min(minDistance, parent.getDistanceToAncestor(ancestorNoteId) + 1); | ||||||
| @@ -416,9 +417,14 @@ class Note { | |||||||
|  |  | ||||||
|     decrypt() { |     decrypt() { | ||||||
|         if (this.isProtected && !this.isDecrypted && protectedSessionService.isProtectedSessionAvailable()) { |         if (this.isProtected && !this.isDecrypted && protectedSessionService.isProtectedSessionAvailable()) { | ||||||
|             this.title = protectedSessionService.decryptString(this.title); |             try { | ||||||
|  |                 this.title = protectedSessionService.decryptString(this.title); | ||||||
|  |  | ||||||
|             this.isDecrypted = true; |                 this.isDecrypted = true; | ||||||
|  |             } | ||||||
|  |             catch (e) { | ||||||
|  |                 log.error(`Could not decrypt note ${this.noteId}: ${e.message} ${e.stack}`); | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -177,6 +177,10 @@ eventService.subscribe(eventService.ENTER_PROTECTED_SESSION, () => { | |||||||
|     } |     } | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  | eventService.subscribe(eventService.LEAVE_PROTECTED_SESSION, () => { | ||||||
|  |     load(); | ||||||
|  | }); | ||||||
|  |  | ||||||
| module.exports = { | module.exports = { | ||||||
|     load |     load | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -233,7 +233,7 @@ async function findSimilarNotes(noteId) { | |||||||
|  |  | ||||||
|     const baseNote = noteCache.notes[noteId]; |     const baseNote = noteCache.notes[noteId]; | ||||||
|  |  | ||||||
|     if (!baseNote) { |     if (!baseNote || !baseNote.utcDateCreated) { | ||||||
|         return []; |         return []; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -86,6 +86,7 @@ const defaultOptions = [ | |||||||
|     { name: 'similarNotesExpanded', value: 'true', isSynced: true }, |     { name: 'similarNotesExpanded', value: 'true', isSynced: true }, | ||||||
|     { name: 'debugModeEnabled', value: 'false', isSynced: false }, |     { name: 'debugModeEnabled', value: 'false', isSynced: false }, | ||||||
|     { name: 'headingStyle', value: 'markdown', isSynced: true }, |     { name: 'headingStyle', value: 'markdown', isSynced: true }, | ||||||
|  |     { name: 'autoCollapseNoteTree', value: 'true', isSynced: true }, | ||||||
| ]; | ]; | ||||||
|  |  | ||||||
| function initStartupOptions() { | function initStartupOptions() { | ||||||
|   | |||||||
| @@ -5,7 +5,7 @@ const log = require('./log'); | |||||||
| const dataEncryptionService = require('./data_encryption'); | const dataEncryptionService = require('./data_encryption'); | ||||||
| const cls = require('./cls'); | const cls = require('./cls'); | ||||||
|  |  | ||||||
| const dataKeyMap = {}; | let dataKeyMap = {}; | ||||||
|  |  | ||||||
| function setDataKey(decryptedDataKey) { | function setDataKey(decryptedDataKey) { | ||||||
|     const protectedSessionId = utils.randomSecureToken(32); |     const protectedSessionId = utils.randomSecureToken(32); | ||||||
| @@ -29,6 +29,10 @@ function getDataKey() { | |||||||
|     return dataKeyMap[protectedSessionId]; |     return dataKeyMap[protectedSessionId]; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | function resetDataKey() { | ||||||
|  |     dataKeyMap = {}; | ||||||
|  | } | ||||||
|  |  | ||||||
| function isProtectedSessionAvailable() { | function isProtectedSessionAvailable() { | ||||||
|     const protectedSessionId = getProtectedSessionId(); |     const protectedSessionId = getProtectedSessionId(); | ||||||
|  |  | ||||||
| @@ -71,6 +75,7 @@ function decryptString(cipherText) { | |||||||
| module.exports = { | module.exports = { | ||||||
|     setDataKey, |     setDataKey, | ||||||
|     getDataKey, |     getDataKey, | ||||||
|  |     resetDataKey, | ||||||
|     isProtectedSessionAvailable, |     isProtectedSessionAvailable, | ||||||
|     encrypt, |     encrypt, | ||||||
|     decrypt, |     decrypt, | ||||||
|   | |||||||
| @@ -21,8 +21,8 @@ function lex(str) { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     function finishWord(endIndex) { |     function finishWord(endIndex, createAlsoForEmptyWords = false) { | ||||||
|         if (currentWord === '') { |         if (currentWord === '' && !createAlsoForEmptyWords) { | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|  |  | ||||||
| @@ -71,7 +71,7 @@ function lex(str) { | |||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|             else if (quotes === chr) { |             else if (quotes === chr) { | ||||||
|                 finishWord(i - 1); |                 finishWord(i - 1, true); | ||||||
|  |  | ||||||
|                 quotes = false; |                 quotes = false; | ||||||
|             } |             } | ||||||
|   | |||||||
| @@ -245,9 +245,7 @@ async function checkContentHash(syncContext) { | |||||||
|     const failedChecks = contentHashService.checkContentHashes(resp.entityHashes); |     const failedChecks = contentHashService.checkContentHashes(resp.entityHashes); | ||||||
|  |  | ||||||
|     for (const {entityName, sector} of failedChecks) { |     for (const {entityName, sector} of failedChecks) { | ||||||
|         const entityPrimaryKey = entityConstructor.getEntityFromEntityName(entityName).primaryKeyName; |         entityChangesService.addEntityChangesForSector(entityName, sector); | ||||||
|  |  | ||||||
|         entityChangesService.addEntityChangesForSector(entityName, entityPrimaryKey, sector); |  | ||||||
|  |  | ||||||
|         await syncRequest(syncContext, 'POST', `/api/sync/queue-sector/${entityName}/${sector}`); |         await syncRequest(syncContext, 'POST', `/api/sync/queue-sector/${entityName}/${sector}`); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -26,9 +26,7 @@ function updateEntity(entityChange, entity, sourceId) { | |||||||
|         ? updateNoteReordering(entityChange, entity, sourceId) |         ? updateNoteReordering(entityChange, entity, sourceId) | ||||||
|         : updateNormalEntity(entityChange, entity, sourceId); |         : updateNormalEntity(entityChange, entity, sourceId); | ||||||
|  |  | ||||||
|     // currently making exception for protected notes and note revisions because here |     if (updated && !entityChange.isErased) { | ||||||
|     // the title and content are not available decrypted as listeners would expect |  | ||||||
|     if (updated && !entity.isProtected && !entityChange.isErased) { |  | ||||||
|         eventService.emit(eventService.ENTITY_SYNCED, { |         eventService.emit(eventService.ENTITY_SYNCED, { | ||||||
|             entityName: entityChange.entityName, |             entityName: entityChange.entityName, | ||||||
|             entity |             entity | ||||||
| @@ -44,7 +42,7 @@ function updateNormalEntity(remoteEntityChange, entity, sourceId) { | |||||||
|  |  | ||||||
|     if (localEntityChange && !localEntityChange.isErased && remoteEntityChange.isErased) { |     if (localEntityChange && !localEntityChange.isErased && remoteEntityChange.isErased) { | ||||||
|         sql.transactional(() => { |         sql.transactional(() => { | ||||||
|             const primaryKey = entityConstructor.getEntityFromEntityName(entityName).primaryKeyName; |             const primaryKey = entityConstructor.getEntityFromEntityName(remoteEntityChange.entityName).primaryKeyName; | ||||||
|  |  | ||||||
|             sql.execute(`DELETE FROM ${remoteEntityChange.entityName} WHERE ${primaryKey} = ?`, remoteEntityChange.entityId); |             sql.execute(`DELETE FROM ${remoteEntityChange.entityName} WHERE ${primaryKey} = ?`, remoteEntityChange.entityId); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| "use strict"; | "use strict"; | ||||||
|  |  | ||||||
| const sql = require('./sql'); | const sql = require('./sql'); | ||||||
|  | const log = require('./log'); | ||||||
| const repository = require('./repository'); | const repository = require('./repository'); | ||||||
| const Branch = require('../entities/branch'); | const Branch = require('../entities/branch'); | ||||||
| const entityChangesService = require('./entity_changes.js'); | const entityChangesService = require('./entity_changes.js'); | ||||||
| @@ -139,7 +140,12 @@ function sortNotesByTitle(parentNoteId, foldersFirst = false, reverse = false) { | |||||||
|             sql.execute("UPDATE branches SET notePosition = ? WHERE branchId = ?", |             sql.execute("UPDATE branches SET notePosition = ? WHERE branchId = ?", | ||||||
|                 [position, note.branchId]); |                 [position, note.branchId]); | ||||||
|  |  | ||||||
|             noteCache.branches[note.branchId].notePosition = position; |             if (note.branchId in noteCache.branches) { | ||||||
|  |                 noteCache.branches[note.branchId].notePosition = position; | ||||||
|  |             } | ||||||
|  |             else { | ||||||
|  |                 log.info(`Branch "${note.branchId}" was not found in note cache.`); | ||||||
|  |             } | ||||||
|  |  | ||||||
|             position += 10; |             position += 10; | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -7,6 +7,9 @@ | |||||||
|       <sourceFolder url="file://$MODULE_DIR$/src/public" isTestSource="false" /> |       <sourceFolder url="file://$MODULE_DIR$/src/public" isTestSource="false" /> | ||||||
|       <excludeFolder url="file://$MODULE_DIR$/dist" /> |       <excludeFolder url="file://$MODULE_DIR$/dist" /> | ||||||
|       <excludeFolder url="file://$MODULE_DIR$/src/public/app-dist" /> |       <excludeFolder url="file://$MODULE_DIR$/src/public/app-dist" /> | ||||||
|  |       <excludeFolder url="file://$MODULE_DIR$/libraries" /> | ||||||
|  |       <excludeFolder url="file://$MODULE_DIR$/docs" /> | ||||||
|  |       <excludeFolder url="file://$MODULE_DIR$/bin/better-sqlite3" /> | ||||||
|     </content> |     </content> | ||||||
|     <orderEntry type="inheritedJdk" /> |     <orderEntry type="inheritedJdk" /> | ||||||
|     <orderEntry type="sourceFolder" forTests="false" /> |     <orderEntry type="sourceFolder" forTests="false" /> | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user