mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 03:29:02 +01:00 
			
		
		
		
	Merge remote-tracking branch 'origin/develop' into feature/improved_promoted_attributes
; Conflicts: ; src/public/app/layouts/desktop_layout.js
This commit is contained in:
		
						commit
						bb4164f10f
					
				
							
								
								
									
										12
									
								
								.github/ISSUE_TEMPLATE/bug_report.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										12
									
								
								.github/ISSUE_TEMPLATE/bug_report.yml
									
									
									
									
										vendored
									
									
								
							| @ -3,6 +3,12 @@ description: Report a bug | |||||||
| title: "(Bug report) " | title: "(Bug report) " | ||||||
| labels: "Type: Bug" | labels: "Type: Bug" | ||||||
| body: | body: | ||||||
|  | - type: textarea | ||||||
|  |   attributes: | ||||||
|  |     label: Description | ||||||
|  |     description: A clear and concise description of the bug and any additional information. | ||||||
|  |   validations: | ||||||
|  |     required: true | ||||||
| - type: input | - type: input | ||||||
|   attributes: |   attributes: | ||||||
|     label: TriliumNext Version |     label: TriliumNext Version | ||||||
| @ -38,12 +44,6 @@ body: | |||||||
|     placeholder: "e.g. Windows 10 version 1909, macOS Catalina 10.15.7, or Ubuntu 20.04" |     placeholder: "e.g. Windows 10 version 1909, macOS Catalina 10.15.7, or Ubuntu 20.04" | ||||||
|   validations: |   validations: | ||||||
|     required: true |     required: true | ||||||
| - type: textarea |  | ||||||
|   attributes: |  | ||||||
|     label: Description |  | ||||||
|     description: A clear and concise description of the bug and any additional information. |  | ||||||
|   validations: |  | ||||||
|     required: true |  | ||||||
| - type: textarea | - type: textarea | ||||||
|   attributes: |   attributes: | ||||||
|     label: Error logs |     label: Error logs | ||||||
|  | |||||||
							
								
								
									
										70
									
								
								.github/workflows/dev.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										70
									
								
								.github/workflows/dev.yml
									
									
									
									
										vendored
									
									
								
							| @ -9,6 +9,12 @@ concurrency: | |||||||
|   group: ${{ github.workflow }}-${{ github.ref }} |   group: ${{ github.workflow }}-${{ github.ref }} | ||||||
|   cancel-in-progress: true |   cancel-in-progress: true | ||||||
| 
 | 
 | ||||||
|  | env: | ||||||
|  |   GHCR_REGISTRY: ghcr.io | ||||||
|  |   DOCKERHUB_REGISTRY: docker.io | ||||||
|  |   IMAGE_NAME: ${{ github.repository_owner }}/notes | ||||||
|  |   TEST_TAG: ${{ github.repository_owner }}/notes:test | ||||||
|  | 
 | ||||||
| jobs: | jobs: | ||||||
|   build_docker: |   build_docker: | ||||||
|     name: Build Docker image |     name: Build Docker image | ||||||
| @ -30,4 +36,66 @@ jobs: | |||||||
|         with: |         with: | ||||||
|           context: . |           context: . | ||||||
|           cache-from: type=gha |           cache-from: type=gha | ||||||
|           cache-to: type=gha,mode=max |           cache-to: type=gha,mode=max | ||||||
|  |   test_docker: | ||||||
|  |     name: Check Docker build | ||||||
|  |     runs-on: ubuntu-latest | ||||||
|  |     strategy: | ||||||
|  |       matrix: | ||||||
|  |         include: | ||||||
|  |           - dockerfile: Dockerfile.alpine | ||||||
|  |           - dockerfile: Dockerfile | ||||||
|  |     steps: | ||||||
|  |       - name: Checkout the repository | ||||||
|  |         uses: actions/checkout@v4 | ||||||
|  |          | ||||||
|  |       - name: Set IMAGE_NAME to lowercase | ||||||
|  |         run: echo "IMAGE_NAME=${IMAGE_NAME,,}" >> $GITHUB_ENV | ||||||
|  |       - name: Set TEST_TAG to lowercase | ||||||
|  |         run: echo "TEST_TAG=${TEST_TAG,,}" >> $GITHUB_ENV | ||||||
|  | 
 | ||||||
|  |       - name: Set up Docker Buildx | ||||||
|  |         uses: docker/setup-buildx-action@v3 | ||||||
|  | 
 | ||||||
|  |       - name: Set up node & dependencies | ||||||
|  |         uses: actions/setup-node@v4 | ||||||
|  |         with: | ||||||
|  |           node-version: 20 | ||||||
|  |           cache: "npm" | ||||||
|  |        | ||||||
|  |       - run: npm ci | ||||||
|  |        | ||||||
|  |       - name: Run the TypeScript build | ||||||
|  |         run: npx tsc | ||||||
|  |        | ||||||
|  |       - name: Create server-package.json | ||||||
|  |         run: cat package.json | grep -v electron > server-package.json | ||||||
|  | 
 | ||||||
|  |       - name: Build and export to Docker | ||||||
|  |         uses: docker/build-push-action@v6 | ||||||
|  |         with: | ||||||
|  |           context: . | ||||||
|  |           file: ${{ matrix.dockerfile }} | ||||||
|  |           load: true | ||||||
|  |           tags: ${{ env.TEST_TAG }} | ||||||
|  |           cache-from: type=gha | ||||||
|  |           cache-to: type=gha,mode=max | ||||||
|  |        | ||||||
|  |       - name: Validate container run output | ||||||
|  |         run: | | ||||||
|  |           CONTAINER_ID=$(docker run -d --log-driver=journald --rm --name trilium_local ${{ env.TEST_TAG }}) | ||||||
|  |           echo "Container ID: $CONTAINER_ID" | ||||||
|  |        | ||||||
|  |       - name: Wait for the healthchecks to pass | ||||||
|  |         uses: stringbean/docker-healthcheck-action@v1 | ||||||
|  |         with: | ||||||
|  |           container: trilium_local | ||||||
|  |           wait-time: 50 | ||||||
|  |           require-status: running | ||||||
|  |           require-healthy: true | ||||||
|  | 
 | ||||||
|  |       # Print the entire log of the container thus far, regardless if the healthcheck failed or succeeded | ||||||
|  |       - name: Print entire log | ||||||
|  |         if: always() | ||||||
|  |         run: | | ||||||
|  |           journalctl -u docker CONTAINER_NAME=trilium_local --no-pager | ||||||
|  | |||||||
| @ -17,17 +17,45 @@ | |||||||
| # | # | ||||||
| # -------------------------------------------------------------------------------------------------- | # -------------------------------------------------------------------------------------------------- | ||||||
| 
 | 
 | ||||||
|  | number_of_keys() { | ||||||
|  | 	[ -f "$1" ] && jq 'path(..) | select(length == 2) | .[1]' "$1" | wc -l || echo "0" | ||||||
|  | } | ||||||
|  | 
 | ||||||
| stats() { | stats() { | ||||||
| 	# Print the number of existing strings on the JSON files for each locale | 	# Print the number of existing strings on the JSON files for each locale | ||||||
| 	s=$(jq 'path(..) | select(length == 2) | .[1]' "${paths[0]}/en/server.json" | wc -l) | 	s=$(number_of_keys "${paths[0]}/en/server.json") | ||||||
| 	c=$(jq 'path(..) | select(length == 2) | .[1]' "${paths[1]}/en/translation.json" | wc -l) | 	c=$(number_of_keys "${paths[1]}/en/translation.json") | ||||||
| 	echo "|locale |server strings |client strings |" | 	echo "| locale |server strings |client strings |" | ||||||
| 	echo "|-------|---------------|---------------|" | 	echo "|--------|---------------|---------------|" | ||||||
| 	echo "|  en   |      ${s}      |     ${c}      |" | 	echo "|   en   |      ${s}      |     ${c}      |" | ||||||
| 	for locale in "${locales[@]}"; do | 	for locale in "${locales[@]}"; do | ||||||
| 		s=$(jq 'path(..) | select(length == 2) | .[1]' "${paths[0]}/${locale}/server.json" | wc -l) | 		s=$(number_of_keys "${paths[0]}/${locale}/server.json") | ||||||
| 		c=$(jq 'path(..) | select(length == 2) | .[1]' "${paths[1]}/${locale}/translation.json" | wc -l) | 		c=$(number_of_keys "${paths[1]}/${locale}/translation.json") | ||||||
| 		echo "|  ${locale}   |      ${s}      |     ${c}      |" | 		n1=$(((8 - ${#locale}) / 2)) | ||||||
|  | 		n2=$((n1 == 1 ? n1 + 1 : n1)) | ||||||
|  | 		echo "|$(printf "%${n1}s")${locale}$(printf "%${n2}s")|      ${s}      |     ${c}      |" | ||||||
|  | 	done | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | update_1() { | ||||||
|  | 	# Update PO files from English and localized JSON files as source | ||||||
|  | 	# NOTE: if you want a new language you need to first create the JSON files | ||||||
|  | 	# on their corresponding place with `{}` as content to avoid error on `json2po` | ||||||
|  | 	local locales=("$@") | ||||||
|  | 	for path in "${paths[@]}"; do | ||||||
|  | 		for locale in "${locales[@]}"; do | ||||||
|  | 			json2po -t "${path}/en" "${path}/${locale}" "${path}/po-${locale}" | ||||||
|  | 		done | ||||||
|  | 	done | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | update_2() { | ||||||
|  | 	# Recover translation from PO files to localized JSON files | ||||||
|  | 	local locales=("$@") | ||||||
|  | 	for path in "${paths[@]}"; do | ||||||
|  | 		for locale in "${locales[@]}"; do | ||||||
|  | 			po2json -t "${path}/en" "${path}/po-${locale}" "${path}/${locale}" | ||||||
|  | 		done | ||||||
| 	done | 	done | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -35,11 +63,11 @@ help() { | |||||||
| 	echo -e "\nDescription:" | 	echo -e "\nDescription:" | ||||||
| 	echo -e "\tCreate PO files to make easier the labor of translation" | 	echo -e "\tCreate PO files to make easier the labor of translation" | ||||||
| 	echo -e "\nUsage:" | 	echo -e "\nUsage:" | ||||||
| 	echo -e "\t./translation.sh [--stats] [--update <OPT_LOCALE>] [--update2 <OPT_LOCALE>]" | 	echo -e "\t./translation.sh [--stats] [--update1 <OPT_LOCALE>] [--update2 <OPT_LOCALE>]" | ||||||
| 	echo -e "\nFlags:" | 	echo -e "\nFlags:" | ||||||
| 	echo -e "  --clear\n\tClear all po-* directories" | 	echo -e "  --clear\n\tClear all po-* directories" | ||||||
| 	echo -e "  --stats\n\tPrint the number of existing strings on the JSON files for each locale" | 	echo -e "  --stats\n\tPrint the number of existing strings on the JSON files for each locale" | ||||||
| 	echo -e "  --update <LOCALE>\n\tUpdate PO files from English and localized JSON files as source" | 	echo -e "  --update1 <LOCALE>\n\tUpdate PO files from English and localized JSON files as source" | ||||||
| 	echo -e "  --update2 <LOCALE>\n\tRecover translation from PO files to localized JSON files" | 	echo -e "  --update2 <LOCALE>\n\tRecover translation from PO files to localized JSON files" | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -51,7 +79,7 @@ file_path="$( | |||||||
| 	pwd -P | 	pwd -P | ||||||
| )" | )" | ||||||
| paths=("${file_path}/../translations/" "${file_path}/../src/public/translations/") | paths=("${file_path}/../translations/" "${file_path}/../src/public/translations/") | ||||||
| locales=(cn es fr ro) | locales=(cn de es fr pt_br ro tw) | ||||||
| 
 | 
 | ||||||
| if [ $# -eq 1 ]; then | if [ $# -eq 1 ]; then | ||||||
| 	if [ "$1" == "--clear" ]; then | 	if [ "$1" == "--clear" ]; then | ||||||
| @ -62,34 +90,18 @@ if [ $# -eq 1 ]; then | |||||||
| 		done | 		done | ||||||
| 	elif [ "$1" == "--stats" ]; then | 	elif [ "$1" == "--stats" ]; then | ||||||
| 		stats | 		stats | ||||||
| 	elif [ "$1" == "--update" ]; then | 	elif [ "$1" == "--update1" ]; then | ||||||
| 		# Update PO files from English and localized JSON files as source | 		update_1 "${locales[@]}" | ||||||
| 		for path in "${paths[@]}"; do |  | ||||||
| 			for locale in "${locales[@]}"; do |  | ||||||
| 				json2po -t "${path}/en" "${path}/${locale}" "${path}/po-${locale}" |  | ||||||
| 			done |  | ||||||
| 		done |  | ||||||
| 	elif [ "$1" == "--update2" ]; then | 	elif [ "$1" == "--update2" ]; then | ||||||
| 		# Recover translation from PO files to localized JSON files | 		update_2 "${locales[@]}" | ||||||
| 		for path in "${paths[@]}"; do |  | ||||||
| 			for locale in "${locales[@]}"; do |  | ||||||
| 				po2json -t "${path}/en" "${path}/po-${locale}" "${path}/${locale}" |  | ||||||
| 			done |  | ||||||
| 		done |  | ||||||
| 	else | 	else | ||||||
| 		help | 		help | ||||||
| 	fi | 	fi | ||||||
| elif [ $# -eq 2 ]; then | elif [ $# -eq 2 ]; then | ||||||
| 	if [ "$1" == "--update" ]; then | 	if [ "$1" == "--update1" ]; then | ||||||
| 		locale="$2" | 		update_1 "$2" | ||||||
| 		for path in "${paths[@]}"; do |  | ||||||
| 			json2po -t "${path}/en" "${path}/${locale}" "${path}/po-${locale}" |  | ||||||
| 		done |  | ||||||
| 	elif [ "$1" == "--update2" ]; then | 	elif [ "$1" == "--update2" ]; then | ||||||
| 		locale="$2" | 		update_2 "$2" | ||||||
| 		for path in "${paths[@]}"; do |  | ||||||
| 			po2json -t "${path}/en" "${path}/po-${locale}" "${path}/${locale}" |  | ||||||
| 		done |  | ||||||
| 	else | 	else | ||||||
| 		help | 		help | ||||||
| 	fi | 	fi | ||||||
|  | |||||||
							
								
								
									
										
											BIN
										
									
								
								db/demo.zip
									
									
									
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								db/demo.zip
									
									
									
									
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										1
									
								
								libraries/mermaid-elk/elk.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								libraries/mermaid-elk/elk.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										13
									
								
								libraries/mermaid-elk/package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								libraries/mermaid-elk/package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @ -0,0 +1,13 @@ | |||||||
|  | { | ||||||
|  |   "name": "mermaid-elk", | ||||||
|  |   "version": "1.0.0", | ||||||
|  |   "lockfileVersion": 3, | ||||||
|  |   "requires": true, | ||||||
|  |   "packages": { | ||||||
|  |     "": { | ||||||
|  |       "name": "mermaid-elk", | ||||||
|  |       "version": "1.0.0", | ||||||
|  |       "license": "ISC" | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										13
									
								
								libraries/mermaid-elk/package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								libraries/mermaid-elk/package.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,13 @@ | |||||||
|  | { | ||||||
|  |   "name": "mermaid-elk", | ||||||
|  |   "version": "1.0.0", | ||||||
|  |   "main": "index.js", | ||||||
|  |   "scripts": { | ||||||
|  |     "build": "cross-env node --import ../../loader-register.js ../../node_modules/webpack/bin/webpack.js -c webpack.config.cjs" | ||||||
|  |   }, | ||||||
|  |   "keywords": [], | ||||||
|  |   "author": "", | ||||||
|  |   "license": "ISC", | ||||||
|  |   "description": "", | ||||||
|  |   "dependencies": {} | ||||||
|  | } | ||||||
							
								
								
									
										19
									
								
								libraries/mermaid-elk/webpack.config.cjs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								libraries/mermaid-elk/webpack.config.cjs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,19 @@ | |||||||
|  | const path = require("path"); | ||||||
|  | const webpack = require("webpack"); | ||||||
|  | 
 | ||||||
|  | module.exports = { | ||||||
|  |     mode: "production", | ||||||
|  |     entry: "../../node_modules/@mermaid-js/layout-elk/dist/mermaid-layout-elk.esm.min.mjs", | ||||||
|  |     output: { | ||||||
|  |         library: "MERMAID_ELK", | ||||||
|  |         filename: "elk.min.js", | ||||||
|  |         path: path.resolve(__dirname), | ||||||
|  |         libraryTarget: "umd", | ||||||
|  |         libraryExport: "default" | ||||||
|  |     }, | ||||||
|  |     plugins: [ | ||||||
|  |         new webpack.optimize.LimitChunkCountPlugin({ | ||||||
|  |             maxChunks: 1 | ||||||
|  |         }) | ||||||
|  |     ] | ||||||
|  | } | ||||||
							
								
								
									
										31
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										31
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @ -1,18 +1,19 @@ | |||||||
| { | { | ||||||
|   "name": "trilium", |   "name": "trilium", | ||||||
|   "version": "0.90.11-beta", |   "version": "0.90.12", | ||||||
|   "lockfileVersion": 3, |   "lockfileVersion": 3, | ||||||
|   "requires": true, |   "requires": true, | ||||||
|   "packages": { |   "packages": { | ||||||
|     "": { |     "": { | ||||||
|       "name": "trilium", |       "name": "trilium", | ||||||
|       "version": "0.90.11-beta", |       "version": "0.90.12", | ||||||
|       "license": "AGPL-3.0-only", |       "license": "AGPL-3.0-only", | ||||||
|       "dependencies": { |       "dependencies": { | ||||||
|         "@braintree/sanitize-url": "7.1.0", |         "@braintree/sanitize-url": "7.1.0", | ||||||
|         "@electron/remote": "2.1.2", |         "@electron/remote": "2.1.2", | ||||||
|         "@excalidraw/excalidraw": "0.17.6", |         "@excalidraw/excalidraw": "0.17.6", | ||||||
|         "@highlightjs/cdn-assets": "11.10.0", |         "@highlightjs/cdn-assets": "11.10.0", | ||||||
|  |         "@mermaid-js/layout-elk": "0.1.5", | ||||||
|         "archiver": "7.0.1", |         "archiver": "7.0.1", | ||||||
|         "async-mutex": "0.5.0", |         "async-mutex": "0.5.0", | ||||||
|         "autocomplete.js": "0.38.1", |         "autocomplete.js": "0.38.1", | ||||||
| @ -47,7 +48,7 @@ | |||||||
|         "html2plaintext": "2.1.4", |         "html2plaintext": "2.1.4", | ||||||
|         "http-proxy-agent": "7.0.2", |         "http-proxy-agent": "7.0.2", | ||||||
|         "https-proxy-agent": "7.0.5", |         "https-proxy-agent": "7.0.5", | ||||||
|         "i18next": "23.16.4", |         "i18next": "23.16.8", | ||||||
|         "i18next-fs-backend": "2.3.2", |         "i18next-fs-backend": "2.3.2", | ||||||
|         "i18next-http-backend": "2.6.2", |         "i18next-http-backend": "2.6.2", | ||||||
|         "image-type": "4.1.0", |         "image-type": "4.1.0", | ||||||
| @ -3072,6 +3073,18 @@ | |||||||
|         "url": "https://github.com/malept/cross-spawn-promise?sponsor=1" |         "url": "https://github.com/malept/cross-spawn-promise?sponsor=1" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|  |     "node_modules/@mermaid-js/layout-elk": { | ||||||
|  |       "version": "0.1.5", | ||||||
|  |       "resolved": "https://registry.npmjs.org/@mermaid-js/layout-elk/-/layout-elk-0.1.5.tgz", | ||||||
|  |       "integrity": "sha512-6ML4iWdVdyIkSW47KiID9runHzaomLxdMfNo9U60LJvfcQkB/FAjg0Vjc4AZEQnnBq7ibAoAknAWlT1XetwXSg==", | ||||||
|  |       "dependencies": { | ||||||
|  |         "d3": "^7.9.0", | ||||||
|  |         "elkjs": "^0.9.3" | ||||||
|  |       }, | ||||||
|  |       "peerDependencies": { | ||||||
|  |         "mermaid": "^11.0.0" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|     "node_modules/@mermaid-js/parser": { |     "node_modules/@mermaid-js/parser": { | ||||||
|       "version": "0.3.0", |       "version": "0.3.0", | ||||||
|       "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-0.3.0.tgz", |       "resolved": "https://registry.npmjs.org/@mermaid-js/parser/-/parser-0.3.0.tgz", | ||||||
| @ -7904,6 +7917,11 @@ | |||||||
|       "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", |       "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", | ||||||
|       "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" |       "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" | ||||||
|     }, |     }, | ||||||
|  |     "node_modules/elkjs": { | ||||||
|  |       "version": "0.9.3", | ||||||
|  |       "resolved": "https://registry.npmjs.org/elkjs/-/elkjs-0.9.3.tgz", | ||||||
|  |       "integrity": "sha512-f/ZeWvW/BCXbhGEf1Ujp29EASo/lk1FDnETgNKwJrsVvGZhUWCZyg3xLJjAsxfOmt8KjswHmI5EwCQcPMpOYhQ==" | ||||||
|  |     }, | ||||||
|     "node_modules/emitter-listener": { |     "node_modules/emitter-listener": { | ||||||
|       "version": "1.1.2", |       "version": "1.1.2", | ||||||
|       "resolved": "https://registry.npmjs.org/emitter-listener/-/emitter-listener-1.1.2.tgz", |       "resolved": "https://registry.npmjs.org/emitter-listener/-/emitter-listener-1.1.2.tgz", | ||||||
| @ -10024,9 +10042,9 @@ | |||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|     "node_modules/i18next": { |     "node_modules/i18next": { | ||||||
|       "version": "23.16.4", |       "version": "23.16.8", | ||||||
|       "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.16.4.tgz", |       "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.16.8.tgz", | ||||||
|       "integrity": "sha512-9NIYBVy9cs4wIqzurf7nLXPyf3R78xYbxExVqHLK9od3038rjpyOEzW+XB130kZ1N4PZ9inTtJ471CRJ4Ituyg==", |       "integrity": "sha512-06r/TitrM88Mg5FdUXAKL96dJMzgqLE5dv3ryBAra4KCwD9mJ4ndOTS95ZuymIGoE+2hzfdaMak2X11/es7ZWg==", | ||||||
|       "funding": [ |       "funding": [ | ||||||
|         { |         { | ||||||
|           "type": "individual", |           "type": "individual", | ||||||
| @ -10041,6 +10059,7 @@ | |||||||
|           "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" |           "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" | ||||||
|         } |         } | ||||||
|       ], |       ], | ||||||
|  |       "license": "MIT", | ||||||
|       "dependencies": { |       "dependencies": { | ||||||
|         "@babel/runtime": "^7.23.2" |         "@babel/runtime": "^7.23.2" | ||||||
|       } |       } | ||||||
|  | |||||||
| @ -2,7 +2,7 @@ | |||||||
|   "name": "trilium", |   "name": "trilium", | ||||||
|   "productName": "TriliumNext Notes", |   "productName": "TriliumNext Notes", | ||||||
|   "description": "Build your personal knowledge base with TriliumNext Notes", |   "description": "Build your personal knowledge base with TriliumNext Notes", | ||||||
|   "version": "0.90.11-beta", |   "version": "0.90.12", | ||||||
|   "license": "AGPL-3.0-only", |   "license": "AGPL-3.0-only", | ||||||
|   "main": "./dist/electron-main.js", |   "main": "./dist/electron-main.js", | ||||||
|   "author": { |   "author": { | ||||||
| @ -54,6 +54,7 @@ | |||||||
|     "@electron/remote": "2.1.2", |     "@electron/remote": "2.1.2", | ||||||
|     "@excalidraw/excalidraw": "0.17.6", |     "@excalidraw/excalidraw": "0.17.6", | ||||||
|     "@highlightjs/cdn-assets": "11.10.0", |     "@highlightjs/cdn-assets": "11.10.0", | ||||||
|  |     "@mermaid-js/layout-elk": "0.1.5", | ||||||
|     "archiver": "7.0.1", |     "archiver": "7.0.1", | ||||||
|     "async-mutex": "0.5.0", |     "async-mutex": "0.5.0", | ||||||
|     "autocomplete.js": "0.38.1", |     "autocomplete.js": "0.38.1", | ||||||
| @ -88,7 +89,7 @@ | |||||||
|     "html2plaintext": "2.1.4", |     "html2plaintext": "2.1.4", | ||||||
|     "http-proxy-agent": "7.0.2", |     "http-proxy-agent": "7.0.2", | ||||||
|     "https-proxy-agent": "7.0.5", |     "https-proxy-agent": "7.0.5", | ||||||
|     "i18next": "23.16.4", |     "i18next": "23.16.8", | ||||||
|     "i18next-fs-backend": "2.3.2", |     "i18next-fs-backend": "2.3.2", | ||||||
|     "i18next-http-backend": "2.6.2", |     "i18next-http-backend": "2.6.2", | ||||||
|     "image-type": "4.1.0", |     "image-type": "4.1.0", | ||||||
|  | |||||||
							
								
								
									
										10
									
								
								renovate.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								renovate.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,10 @@ | |||||||
|  | { | ||||||
|  |   "$schema": "https://docs.renovatebot.com/renovate-schema.json", | ||||||
|  |   "extends": ["config:recommended"], | ||||||
|  |   "repositories": ["TriliumNext/Notes"], | ||||||
|  |   "schedule": ["before 3am"], | ||||||
|  |   "labels": ["dependencies", "renovate"], | ||||||
|  |   "prHourlyLimit": 0, | ||||||
|  |   "prConcurrentLimit": 0, | ||||||
|  |   "branchConcurrentLimit": 0 | ||||||
|  | } | ||||||
| @ -88,8 +88,11 @@ export default class Component { | |||||||
| 
 | 
 | ||||||
|         if (fun) { |         if (fun) { | ||||||
|             return this.callMethod(fun, data); |             return this.callMethod(fun, data); | ||||||
|         } |         } else { | ||||||
|         else { |             if (!this.parent) { | ||||||
|  |                 throw new Error(`Component "${this.componentId}" does not have a parent attached to propagate a command.`); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|             return this.parent.triggerCommand(name, data); |             return this.parent.triggerCommand(name, data); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -586,6 +586,11 @@ export default class TabManager extends Component { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     async copyTabToNewWindowCommand({ntxId}) { | ||||||
|  |         const {notePath, hoistedNoteId} = this.getNoteContextById(ntxId); | ||||||
|  |         this.triggerCommand('openInWindow', {notePath, hoistedNoteId}); | ||||||
|  |     }  | ||||||
|  | 
 | ||||||
|     async reopenLastTabCommand() { |     async reopenLastTabCommand() { | ||||||
|         let closeLastEmptyTab = null; |         let closeLastEmptyTab = null; | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										3
									
								
								src/public/app/doc_notes/cn/hidden.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								src/public/app/doc_notes/cn/hidden.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,3 @@ | |||||||
|  | <p>隐藏树用于记录各种应用层数据,这些数据大部分时间可能对用户不可见。</p> | ||||||
|  | 
 | ||||||
|  | <p>确保你知道自己在做什么。对这个子树的错误更改可能会导致应用程序崩溃。</p> | ||||||
| @ -0,0 +1 @@ | |||||||
|  | <p>此启动器操作的键盘快捷键可以在“选项”->“快捷键”中进行配置。</p> | ||||||
| @ -0,0 +1,3 @@ | |||||||
|  | <p>“后退”和“前进”按钮允许您在导航历史中移动。</p> | ||||||
|  | 
 | ||||||
|  | <p>这些启动器仅在桌面版本中有效,在服务器版本中将被忽略,您可以使用浏览器的原生导航按钮代替。</p> | ||||||
							
								
								
									
										11
									
								
								src/public/app/doc_notes/cn/launchbar_intro.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/public/app/doc_notes/cn/launchbar_intro.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,11 @@ | |||||||
|  | <p>欢迎来到启动栏配置界面。</p> | ||||||
|  | 
 | ||||||
|  | <p>您可以在此处执行以下操作:</p> | ||||||
|  | 
 | ||||||
|  | <ul> | ||||||
|  |     <li>通过拖动将可用的启动器移动到可见列表中(从而将它们放入启动栏)</li> | ||||||
|  |     <li>通过拖动将可见的启动器移动到可用列表中(从而将它们从启动栏中隐藏)</li> | ||||||
|  |     <li>您可以通过拖动重新排列列表中的项目</li> | ||||||
|  |     <li>通过右键点击“可见启动器”文件夹来创建新的启动器</li> | ||||||
|  |     <li>如果您想恢复默认设置,可以在右键菜单中找到“重置”选项。</li> | ||||||
|  | </ul> | ||||||
							
								
								
									
										9
									
								
								src/public/app/doc_notes/cn/launchbar_note_launcher.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/public/app/doc_notes/cn/launchbar_note_launcher.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,9 @@ | |||||||
|  | <p>您可以定义以下属性:</p> | ||||||
|  | 
 | ||||||
|  | <ol> | ||||||
|  |     <li><code>target</code> - 激活启动器时应打开的笔记</li> | ||||||
|  |     <li><code>hoistedNote</code> - 可选,在打开目标笔记之前将更改提升的笔记</li> | ||||||
|  |     <li><code>keyboardShortcut</code> - 可选,按下键盘快捷键将打开该笔记</li> | ||||||
|  | </ol> | ||||||
|  | 
 | ||||||
|  | <p>启动栏显示来自启动器的标题/图标,这不一定与目标笔记的标题/图标一致。</p> | ||||||
							
								
								
									
										12
									
								
								src/public/app/doc_notes/cn/launchbar_script_launcher.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								src/public/app/doc_notes/cn/launchbar_script_launcher.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | |||||||
|  | <p>脚本启动器可以执行通过 <code>~script</code> 关系连接的脚本(代码笔记)。</p> | ||||||
|  | 
 | ||||||
|  | <ol> | ||||||
|  |     <li><code>script</code> - 与应在启动器激活时执行的脚本笔记的关系</li> | ||||||
|  |     <li><code>keyboardShortcut</code> - 可选,按下键盘快捷键将激活启动器</li> | ||||||
|  | </ol> | ||||||
|  | 
 | ||||||
|  | <h4>示例脚本</h4> | ||||||
|  | 
 | ||||||
|  | <pre> | ||||||
|  | api.showMessage("当前笔记是 " + api.getActiveContextNote().title); | ||||||
|  | </pre> | ||||||
							
								
								
									
										6
									
								
								src/public/app/doc_notes/cn/launchbar_spacer.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								src/public/app/doc_notes/cn/launchbar_spacer.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,6 @@ | |||||||
|  | <p>间隔器允许您在视觉上将启动器分组。您可以在提升的属性中进行配置:</p> | ||||||
|  | 
 | ||||||
|  | <ul> | ||||||
|  |     <li><code>baseSize</code> - 定义以像素为单位的大小(如果有足够的空间)</li> | ||||||
|  |     <li><code>growthFactor</code> - 如果您希望间隔器保持恒定的 <code>baseSize</code>,则设置为 0;如果设置为正值,它将增长。</li> | ||||||
|  | </ul> | ||||||
							
								
								
									
										34
									
								
								src/public/app/doc_notes/cn/launchbar_widget_launcher.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/public/app/doc_notes/cn/launchbar_widget_launcher.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,34 @@ | |||||||
|  | <p>请在提升的属性中定义目标小部件笔记。该小部件将用于渲染启动栏图标。</p> | ||||||
|  | 
 | ||||||
|  | <h4>示例启动栏小部件</h4> | ||||||
|  | 
 | ||||||
|  | <pre> | ||||||
|  | const TPL = `<div style="height: 53px; width: 53px;"></div>`; | ||||||
|  | 
 | ||||||
|  | class ExampleLaunchbarWidget extends api.NoteContextAwareWidget { | ||||||
|  |     doRender() { | ||||||
|  |         this.$widget = $(TPL); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     async refreshWithNote(note) { | ||||||
|  |         this.$widget.css("background-color", this.stringToColor(note.title)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     stringToColor(str) { | ||||||
|  |         let hash = 0; | ||||||
|  |         for (let i = 0; i < str.length; i++) { | ||||||
|  |             hash = str.charCodeAt(i) + ((hash << 5) - hash); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         let color = '#'; | ||||||
|  |         for (let i = 0; i < 3; i++) { | ||||||
|  |             const value = (hash >> (i * 8)) & 0xFF; | ||||||
|  |             color += ('00' + value.toString(16)).substr(-2); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return color; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | module.exports = new ExampleLaunchbarWidget(); | ||||||
|  | </pre> | ||||||
							
								
								
									
										1
									
								
								src/public/app/doc_notes/cn/share.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/public/app/doc_notes/cn/share.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | |||||||
|  | <p>在这里您可以找到所有分享的笔记。</p> | ||||||
							
								
								
									
										1
									
								
								src/public/app/doc_notes/cn/user_hidden.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/public/app/doc_notes/cn/user_hidden.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | |||||||
|  | <p>此笔记作为一个子树,用于存储由用户脚本生成的数据,这些数据本应避免在隐藏子树中随意创建。</p> | ||||||
| @ -83,6 +83,7 @@ import UploadAttachmentsDialog from "../widgets/dialogs/upload_attachments.js"; | |||||||
| import CopyImageReferenceButton from "../widgets/floating_buttons/copy_image_reference_button.js"; | import CopyImageReferenceButton from "../widgets/floating_buttons/copy_image_reference_button.js"; | ||||||
| import ScrollPaddingWidget from "../widgets/scroll_padding.js"; | import ScrollPaddingWidget from "../widgets/scroll_padding.js"; | ||||||
| import ClassicEditorToolbar from "../widgets/ribbon_widgets/classic_editor_toolbar.js"; | import ClassicEditorToolbar from "../widgets/ribbon_widgets/classic_editor_toolbar.js"; | ||||||
|  | import options from "../services/options.js"; | ||||||
| 
 | 
 | ||||||
| export default class DesktopLayout { | export default class DesktopLayout { | ||||||
|     constructor(customWidgets) { |     constructor(customWidgets) { | ||||||
| @ -92,112 +93,120 @@ export default class DesktopLayout { | |||||||
|     getRootWidget(appContext) { |     getRootWidget(appContext) { | ||||||
|         appContext.noteTreeWidget = new NoteTreeWidget(); |         appContext.noteTreeWidget = new NoteTreeWidget(); | ||||||
| 
 | 
 | ||||||
|         return new RootContainer() |         const launcherPaneIsHorizontal = (options.get("layoutOrientation") === "horizontal"); | ||||||
|  |         const launcherPane = this.#buildLauncherPane(launcherPaneIsHorizontal); | ||||||
|  | 
 | ||||||
|  |         return new RootContainer(launcherPaneIsHorizontal) | ||||||
|             .setParent(appContext) |             .setParent(appContext) | ||||||
|             .child(new FlexContainer("column") |             .optChild(launcherPaneIsHorizontal, new FlexContainer('row')                | ||||||
|                 .id("launcher-pane") |                 .child(new TabRowWidget().class("full-width")) | ||||||
|                 .css("width", "53px") |                 .child(new TitleBarButtonsWidget()) | ||||||
|                 .child(new GlobalMenuWidget()) |                 .css('height', '40px') | ||||||
|                 .child(new LauncherContainer()) |                 .css('background-color', 'var(--launcher-pane-background-color)') | ||||||
|                 .child(new LeftPaneToggleWidget()) |                 .setParent(appContext) | ||||||
|             ) |             ) | ||||||
|             .child(new LeftPaneContainer() |             .optChild(launcherPaneIsHorizontal, launcherPane) | ||||||
|                 .child(new QuickSearchWidget()) |             .child(new FlexContainer('row') | ||||||
|                 .child(appContext.noteTreeWidget) |  | ||||||
|                 .child(...this.customWidgets.get('left-pane')) |  | ||||||
|             ) |  | ||||||
|             .child(new FlexContainer('column') |  | ||||||
|                 .id('rest-pane') |  | ||||||
|                 .css("flex-grow", "1") |                 .css("flex-grow", "1") | ||||||
|                 .child(new FlexContainer('row') |                 .optChild(!launcherPaneIsHorizontal, launcherPane) | ||||||
|                     .child(new TabRowWidget()) |                 .child(new LeftPaneContainer() | ||||||
|                     .child(new TitleBarButtonsWidget()) |                     .optChild(!launcherPaneIsHorizontal, new QuickSearchWidget()) | ||||||
|                     .css('height', '40px') |                     .child(appContext.noteTreeWidget) | ||||||
|  |                     .child(...this.customWidgets.get('left-pane')) | ||||||
|                 ) |                 ) | ||||||
|                 .child(new FlexContainer('row') |                 .child(new FlexContainer('column') | ||||||
|                     .filling() |                     .id('rest-pane') | ||||||
|                     .collapsible() |                     .css("flex-grow", "1") | ||||||
|                     .child(new FlexContainer('column') |                     .optChild(!launcherPaneIsHorizontal, new FlexContainer('row') | ||||||
|  |                         .child(new TabRowWidget()) | ||||||
|  |                         .child(new TitleBarButtonsWidget()) | ||||||
|  |                         .css('height', '40px') | ||||||
|  |                     ) | ||||||
|  |                     .child(new FlexContainer('row') | ||||||
|                         .filling() |                         .filling() | ||||||
|                         .collapsible() |                         .collapsible() | ||||||
|                         .id('center-pane') |                         .child(new FlexContainer('column') | ||||||
|                         .child(new SplitNoteContainer(() => |                             .filling() | ||||||
|                                 new NoteWrapperWidget() |                             .collapsible() | ||||||
|                                     .child(new FlexContainer('row').class('title-row') |                             .id('center-pane') | ||||||
|                                         .css("height", "50px") |                             .child(new SplitNoteContainer(() => | ||||||
|                                         .css("min-height", "50px") |                                     new NoteWrapperWidget() | ||||||
|                                         .css('align-items', "center") |                                         .child(new FlexContainer('row').class('title-row') | ||||||
|                                         .cssBlock('.title-row > * { margin: 5px; }') |                                             .css("height", "50px") | ||||||
|                                         .child(new NoteIconWidget()) |                                             .css("min-height", "50px") | ||||||
|                                         .child(new NoteTitleWidget()) |                                             .css('align-items', "center") | ||||||
|                                         .child(new SpacerWidget(0, 1)) |                                             .cssBlock('.title-row > * { margin: 5px; }') | ||||||
|                                         .child(new MovePaneButton(true)) |                                             .child(new NoteIconWidget()) | ||||||
|                                         .child(new MovePaneButton(false)) |                                             .child(new NoteTitleWidget()) | ||||||
|                                         .child(new ClosePaneButton()) |                                             .child(new SpacerWidget(0, 1)) | ||||||
|                                         .child(new CreatePaneButton()) |                                             .child(new MovePaneButton(true)) | ||||||
|                                     ) |                                             .child(new MovePaneButton(false)) | ||||||
|                                     .child( |                                             .child(new ClosePaneButton()) | ||||||
|                                         new RibbonContainer() |                                             .child(new CreatePaneButton()) | ||||||
|                                             // the order of the widgets matter. Some of these want to "activate" themselves
 |                                         ) | ||||||
|                                             // when visible. When this happens to multiple of them, the first one "wins".
 |                                         .child( | ||||||
|                                             // promoted attributes should always win.
 |                                             new RibbonContainer() | ||||||
|                                             .ribbon(new ClassicEditorToolbar()) |                                                 // the order of the widgets matter. Some of these want to "activate" themselves
 | ||||||
|                                             .ribbon(new ScriptExecutorWidget()) |                                                 // when visible. When this happens to multiple of them, the first one "wins".
 | ||||||
|                                             .ribbon(new SearchDefinitionWidget()) |                                                 // promoted attributes should always win.
 | ||||||
|                                             .ribbon(new EditedNotesWidget()) |                                                 .ribbon(new ClassicEditorToolbar())                                                 | ||||||
|                                             .ribbon(new BookPropertiesWidget()) |                                                 .ribbon(new ScriptExecutorWidget()) | ||||||
|                                             .ribbon(new NotePropertiesWidget()) |                                                 .ribbon(new SearchDefinitionWidget()) | ||||||
|                                             .ribbon(new FilePropertiesWidget()) |                                                 .ribbon(new EditedNotesWidget()) | ||||||
|                                             .ribbon(new ImagePropertiesWidget()) |                                                 .ribbon(new BookPropertiesWidget()) | ||||||
|                                             .ribbon(new BasicPropertiesWidget()) |                                                 .ribbon(new NotePropertiesWidget()) | ||||||
|                                             .ribbon(new OwnedAttributeListWidget()) |                                                 .ribbon(new FilePropertiesWidget()) | ||||||
|                                             .ribbon(new InheritedAttributesWidget()) |                                                 .ribbon(new ImagePropertiesWidget()) | ||||||
|                                             .ribbon(new NotePathsWidget()) |                                                 .ribbon(new BasicPropertiesWidget()) | ||||||
|                                             .ribbon(new NoteMapRibbonWidget()) |                                                 .ribbon(new OwnedAttributeListWidget()) | ||||||
|                                             .ribbon(new SimilarNotesWidget()) |                                                 .ribbon(new InheritedAttributesWidget()) | ||||||
|                                             .ribbon(new NoteInfoWidget()) |                                                 .ribbon(new NotePathsWidget()) | ||||||
|                                             .button(new RevisionsButton()) |                                                 .ribbon(new NoteMapRibbonWidget()) | ||||||
|                                             .button(new NoteActionsWidget()) |                                                 .ribbon(new SimilarNotesWidget()) | ||||||
|                                         )                                     |                                                 .ribbon(new NoteInfoWidget()) | ||||||
|                                     .child(new SharedInfoWidget()) |                                                 .button(new RevisionsButton()) | ||||||
|                                     .child(new WatchedFileUpdateStatusWidget()) |                                                 .button(new NoteActionsWidget()) | ||||||
|                                     .child(new FloatingButtons() |                                         ) | ||||||
|                                         .child(new EditButton()) |                                         .child(new SharedInfoWidget()) | ||||||
|                                         .child(new ShowTocWidgetButton()) |                                         .child(new WatchedFileUpdateStatusWidget()) | ||||||
|                                         .child(new ShowHighlightsListWidgetButton()) |                                         .child(new FloatingButtons() | ||||||
|                                         .child(new CodeButtonsWidget()) |                                             .child(new EditButton()) | ||||||
|                                         .child(new RelationMapButtons()) |                                             .child(new ShowTocWidgetButton()) | ||||||
|                                         .child(new CopyImageReferenceButton()) |                                             .child(new ShowHighlightsListWidgetButton()) | ||||||
|                                         .child(new SvgExportButton()) |                                             .child(new CodeButtonsWidget()) | ||||||
|                                         .child(new BacklinksWidget()) |                                             .child(new RelationMapButtons()) | ||||||
|                                         .child(new HideFloatingButtonsButton()) |                                             .child(new CopyImageReferenceButton()) | ||||||
|                                     ) |                                             .child(new SvgExportButton()) | ||||||
|                                     .child(new MermaidWidget()) |                                             .child(new BacklinksWidget()) | ||||||
|                                     .child(                                         |                                             .child(new HideFloatingButtonsButton()) | ||||||
|                                         new ScrollingContainer() |                                         ) | ||||||
|                                             .filling()                                             |                                         .child(new MermaidWidget()) | ||||||
|                                             .child(new PromotedAttributesWidget()) |                                         .child( | ||||||
|                                             .child(new SqlTableSchemasWidget()) |                                             new ScrollingContainer() | ||||||
|                                             .child(new NoteDetailWidget()) |                                                 .filling() | ||||||
|                                             .child(new NoteListWidget()) |                                                 .child(new PromotedAttributesWidget()) | ||||||
|                                             .child(new SearchResultWidget()) |                                                 .child(new SqlTableSchemasWidget()) | ||||||
|                                             .child(new SqlResultWidget()) |                                                 .child(new NoteDetailWidget()) | ||||||
|                                             .child(new ScrollPaddingWidget()) |                                                 .child(new NoteListWidget()) | ||||||
|                                     ) |                                                 .child(new SearchResultWidget()) | ||||||
|                                     .child(new ApiLogWidget()) |                                                 .child(new SqlResultWidget()) | ||||||
|                                     .child(new FindWidget()) |                                                 .child(new ScrollPaddingWidget()) | ||||||
|                                     .child( |                                         ) | ||||||
|                                         ...this.customWidgets.get('node-detail-pane'), // typo, let's keep it for a while as BC
 |                                         .child(new ApiLogWidget()) | ||||||
|                                         ...this.customWidgets.get('note-detail-pane') |                                         .child(new FindWidget()) | ||||||
|                                     ) |                                         .child( | ||||||
|  |                                             ...this.customWidgets.get('node-detail-pane'), // typo, let's keep it for a while as BC
 | ||||||
|  |                                             ...this.customWidgets.get('note-detail-pane') | ||||||
|  |                                         ) | ||||||
|  |                                 ) | ||||||
|                             ) |                             ) | ||||||
|  |                             .child(...this.customWidgets.get('center-pane')) | ||||||
|  |                         ) | ||||||
|  |                         .child(new RightPaneContainer() | ||||||
|  |                             .child(new TocWidget()) | ||||||
|  |                             .child(new HighlightsListWidget()) | ||||||
|  |                             .child(...this.customWidgets.get('right-pane')) | ||||||
|                         ) |                         ) | ||||||
|                         .child(...this.customWidgets.get('center-pane')) |  | ||||||
|                     ) |  | ||||||
|                     .child(new RightPaneContainer() |  | ||||||
|                         .child(new TocWidget()) |  | ||||||
|                         .child(new HighlightsListWidget()) |  | ||||||
|                         .child(...this.customWidgets.get('right-pane')) |  | ||||||
|                     ) |                     ) | ||||||
|                 ) |                 ) | ||||||
|             ) |             ) | ||||||
| @ -225,4 +234,27 @@ export default class DesktopLayout { | |||||||
|             .child(new ConfirmDialog()) |             .child(new ConfirmDialog()) | ||||||
|             .child(new PromptDialog()); |             .child(new PromptDialog()); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     #buildLauncherPane(isHorizontal) { | ||||||
|  |         let launcherPane;         | ||||||
|  | 
 | ||||||
|  |         if (isHorizontal) { | ||||||
|  |             launcherPane = new FlexContainer("row") | ||||||
|  |                 .css("height", "53px") | ||||||
|  |                 .class("horizontal") | ||||||
|  |                 .child(new LeftPaneToggleWidget(true)) | ||||||
|  |                 .child(new LauncherContainer(true)) | ||||||
|  |                 .child(new GlobalMenuWidget(true)) | ||||||
|  |         } else { | ||||||
|  |             launcherPane = new FlexContainer("column") | ||||||
|  |                 .css("width", "53px") | ||||||
|  |                 .class("vertical") | ||||||
|  |                 .child(new GlobalMenuWidget(false)) | ||||||
|  |                 .child(new LauncherContainer(false)) | ||||||
|  |                 .child(new LeftPaneToggleWidget(false)); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         launcherPane.id("launcher-pane"); | ||||||
|  |         return launcherPane; | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -24,6 +24,7 @@ import RootContainer from "../widgets/containers/root_container.js"; | |||||||
| import SharedInfoWidget from "../widgets/shared_info.js"; | import SharedInfoWidget from "../widgets/shared_info.js"; | ||||||
| import PromotedAttributesWidget from "../widgets/ribbon_widgets/promoted_attributes.js"; | import PromotedAttributesWidget from "../widgets/ribbon_widgets/promoted_attributes.js"; | ||||||
| import ClassicEditorToolbar from "../widgets/ribbon_widgets/classic_editor_toolbar.js"; | import ClassicEditorToolbar from "../widgets/ribbon_widgets/classic_editor_toolbar.js"; | ||||||
|  | import options from "../services/options.js"; | ||||||
| 
 | 
 | ||||||
| const MOBILE_CSS = ` | const MOBILE_CSS = ` | ||||||
| <style> | <style> | ||||||
| @ -112,15 +113,12 @@ span.fancytree-expander { | |||||||
| 
 | 
 | ||||||
| export default class MobileLayout { | export default class MobileLayout { | ||||||
|     getRootWidget(appContext) { |     getRootWidget(appContext) { | ||||||
|         return new RootContainer() |         const launcherPaneIsHorizontal = (options.get("layoutOrientation") === "horizontal"); | ||||||
|  | 
 | ||||||
|  |         return new RootContainer(launcherPaneIsHorizontal) | ||||||
|             .setParent(appContext) |             .setParent(appContext) | ||||||
|             .cssBlock(MOBILE_CSS) |             .cssBlock(MOBILE_CSS) | ||||||
|             .child(new FlexContainer("column") |             .child(this.#buildLauncherPane(launcherPaneIsHorizontal)) | ||||||
|                 .id("launcher-pane") |  | ||||||
|                 .css("width", "53px") |  | ||||||
|                 .child(new GlobalMenuWidget()) |  | ||||||
|                 .child(new LauncherContainer()) |  | ||||||
|             ) |  | ||||||
|             .child(new FlexContainer("row") |             .child(new FlexContainer("row") | ||||||
|                 .filling() |                 .filling() | ||||||
|                 .child(new ScreenContainer("tree", 'column') |                 .child(new ScreenContainer("tree", 'column') | ||||||
| @ -140,12 +138,14 @@ export default class MobileLayout { | |||||||
|                     .child(new FlexContainer('row').contentSized() |                     .child(new FlexContainer('row').contentSized() | ||||||
|                         .css('font-size', 'larger') |                         .css('font-size', 'larger') | ||||||
|                         .css('align-items', 'center') |                         .css('align-items', 'center') | ||||||
|                         .child(new MobileDetailMenuWidget().contentSized()) |                         .optChild(!launcherPaneIsHorizontal, new MobileDetailMenuWidget(false).contentSized()) | ||||||
|                         .child(new NoteTitleWidget() |                         .child(new NoteTitleWidget() | ||||||
|                             .contentSized() |                             .contentSized() | ||||||
|                             .css("position: relative;") |                             .css("position: relative;") | ||||||
|                             .css("top: 5px;") |                             .css("top: 5px;") | ||||||
|  |                             .optCss(launcherPaneIsHorizontal, "padding-left", "0.5em") | ||||||
|                         ) |                         ) | ||||||
|  |                         .optChild(launcherPaneIsHorizontal, new MobileDetailMenuWidget(true).contentSized()) | ||||||
|                         .child(new CloseDetailButtonWidget().contentSized())) |                         .child(new CloseDetailButtonWidget().contentSized())) | ||||||
|                     .child(new SharedInfoWidget()) |                     .child(new SharedInfoWidget()) | ||||||
|                     .child(new FloatingButtons() |                     .child(new FloatingButtons() | ||||||
| @ -174,4 +174,25 @@ export default class MobileLayout { | |||||||
|                 .child(new ConfirmDialog()) |                 .child(new ConfirmDialog()) | ||||||
|             ); |             ); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     #buildLauncherPane(isHorizontal) { | ||||||
|  |         let launcherPane; | ||||||
|  | 
 | ||||||
|  |         if (isHorizontal) { | ||||||
|  |             launcherPane = new FlexContainer("row") | ||||||
|  |                 .class("horizontal") | ||||||
|  |                 .css("height", "53px") | ||||||
|  |                 .child(new LauncherContainer(true)) | ||||||
|  |                 .child(new GlobalMenuWidget(true)); | ||||||
|  |         } else { | ||||||
|  |             launcherPane = new FlexContainer("column")                 | ||||||
|  |                 .class("vertical") | ||||||
|  |                 .css("width", "53px") | ||||||
|  |                 .child(new GlobalMenuWidget(false)) | ||||||
|  |                 .child(new LauncherContainer(false)); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         launcherPane.id("launcher-pane"); | ||||||
|  |         return launcherPane; | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -3,6 +3,7 @@ import keyboardActionService from '../services/keyboard_actions.js'; | |||||||
| class ContextMenu { | class ContextMenu { | ||||||
|     constructor() { |     constructor() { | ||||||
|         this.$widget = $("#context-menu-container"); |         this.$widget = $("#context-menu-container"); | ||||||
|  |         this.$widget.addClass("dropend"); | ||||||
|         this.dateContextMenuOpenedMs = 0; |         this.dateContextMenuOpenedMs = 0; | ||||||
| 
 | 
 | ||||||
|         $(document).on('click', () => this.hide()); |         $(document).on('click', () => this.hide()); | ||||||
| @ -11,6 +12,12 @@ class ContextMenu { | |||||||
|     async show(options) { |     async show(options) { | ||||||
|         this.options = options; |         this.options = options; | ||||||
| 
 | 
 | ||||||
|  |         if (this.$widget.hasClass("show")) { | ||||||
|  |             // The menu is already visible. Hide the menu then open it again
 | ||||||
|  |             // at the new location to re-trigger the opening animation.
 | ||||||
|  |             await this.hide(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         this.$widget.empty(); |         this.$widget.empty(); | ||||||
| 
 | 
 | ||||||
|         this.addItems(this.$widget, options.items); |         this.addItems(this.$widget, options.items); | ||||||
| @ -96,6 +103,10 @@ class ContextMenu { | |||||||
|                     .append("   ") // some space between icon and text
 |                     .append("   ") // some space between icon and text
 | ||||||
|                     .append(item.title); |                     .append(item.title); | ||||||
| 
 | 
 | ||||||
|  |                 if (item.shortcut) { | ||||||
|  |                     $link.append($("<kbd>").text(item.shortcut)); | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|                 const $item = $("<li>") |                 const $item = $("<li>") | ||||||
|                     .addClass("dropdown-item") |                     .addClass("dropdown-item") | ||||||
|                     .append($link) |                     .append($link) | ||||||
| @ -142,18 +153,26 @@ class ContextMenu { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     hide() { |     async hide() { | ||||||
|         // this date checking comes from change in FF66 - https://github.com/zadam/trilium/issues/468
 |         // this date checking comes from change in FF66 - https://github.com/zadam/trilium/issues/468
 | ||||||
|         // "contextmenu" event also triggers "click" event which depending on the timing can close the just opened context menu
 |         // "contextmenu" event also triggers "click" event which depending on the timing can close the just opened context menu
 | ||||||
|         // we might filter out right clicks, but then it's better if even right clicks close the context menu
 |         // we might filter out right clicks, but then it's better if even right clicks close the context menu
 | ||||||
|         if (Date.now() - this.dateContextMenuOpenedMs > 300) { |         if (Date.now() - this.dateContextMenuOpenedMs > 300) { | ||||||
|             // seems like if we hide the menu immediately, some clicks can get propagated to the underlying component
 |             // seems like if we hide the menu immediately, some clicks can get propagated to the underlying component
 | ||||||
|             // see https://github.com/zadam/trilium/pull/3805 for details
 |             // see https://github.com/zadam/trilium/pull/3805 for details
 | ||||||
|             setTimeout(() => this.$widget.hide(), 100); |             await timeout(100); | ||||||
|  |             this.$widget.removeClass("show"); | ||||||
|  |             this.$widget.hide() | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | function timeout(ms) { | ||||||
|  |     return new Promise((accept, reject) => { | ||||||
|  |         setTimeout(accept, ms); | ||||||
|  |     }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| const contextMenu = new ContextMenu(); | const contextMenu = new ContextMenu(); | ||||||
| 
 | 
 | ||||||
| export default contextMenu; | export default contextMenu; | ||||||
|  | |||||||
| @ -2,6 +2,7 @@ import utils from "../services/utils.js"; | |||||||
| import options from "../services/options.js"; | import options from "../services/options.js"; | ||||||
| import zoomService from "../components/zoom.js"; | import zoomService from "../components/zoom.js"; | ||||||
| import contextMenu from "./context_menu.js"; | import contextMenu from "./context_menu.js"; | ||||||
|  | import { t } from "../services/i18n.js"; | ||||||
| 
 | 
 | ||||||
| function setupContextMenu() { | function setupContextMenu() { | ||||||
|     const electron = utils.dynamicRequire('electron'); |     const electron = utils.dynamicRequire('electron'); | ||||||
| @ -28,7 +29,7 @@ function setupContextMenu() { | |||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             items.push({ |             items.push({ | ||||||
|                 title: `Add "${params.misspelledWord}" to dictionary`, |                 title: t("electron_context_menu.add-term-to-dictionary", { term: params.misspelledWord }), | ||||||
|                 uiIcon: "bx bx-plus", |                 uiIcon: "bx bx-plus", | ||||||
|                 handler: () => webContents.session.addWordToSpellCheckerDictionary(params.misspelledWord) |                 handler: () => webContents.session.addWordToSpellCheckerDictionary(params.misspelledWord) | ||||||
|             }); |             }); | ||||||
| @ -39,7 +40,8 @@ function setupContextMenu() { | |||||||
|         if (params.isEditable) { |         if (params.isEditable) { | ||||||
|             items.push({ |             items.push({ | ||||||
|                 enabled: editFlags.canCut && hasText, |                 enabled: editFlags.canCut && hasText, | ||||||
|                 title: `Cut <kbd>${platformModifier}+X`, |                 title: t("electron_context_menu.cut"), | ||||||
|  |                 shortcut: `${platformModifier}+X`, | ||||||
|                 uiIcon: "bx bx-cut", |                 uiIcon: "bx bx-cut", | ||||||
|                 handler: () => webContents.cut() |                 handler: () => webContents.cut() | ||||||
|             }); |             }); | ||||||
| @ -48,7 +50,8 @@ function setupContextMenu() { | |||||||
|         if (params.isEditable || hasText) { |         if (params.isEditable || hasText) { | ||||||
|             items.push({ |             items.push({ | ||||||
|                 enabled: editFlags.canCopy && hasText, |                 enabled: editFlags.canCopy && hasText, | ||||||
|                 title: `Copy <kbd>${platformModifier}+C`, |                 title: t("electron_context_menu.copy"), | ||||||
|  |                 shortcut: `${platformModifier}+C`, | ||||||
|                 uiIcon: "bx bx-copy", |                 uiIcon: "bx bx-copy", | ||||||
|                 handler: () => webContents.copy() |                 handler: () => webContents.copy() | ||||||
|             }); |             }); | ||||||
| @ -56,7 +59,7 @@ function setupContextMenu() { | |||||||
| 
 | 
 | ||||||
|         if (!["", "javascript:", "about:blank#blocked"].includes(params.linkURL) && params.mediaType === 'none') { |         if (!["", "javascript:", "about:blank#blocked"].includes(params.linkURL) && params.mediaType === 'none') { | ||||||
|             items.push({ |             items.push({ | ||||||
|                 title: `Copy link`, |                 title: t("electron_context_menu.copy-link"), | ||||||
|                 uiIcon: "bx bx-copy", |                 uiIcon: "bx bx-copy", | ||||||
|                 handler: () => { |                 handler: () => { | ||||||
|                     electron.clipboard.write({ |                     electron.clipboard.write({ | ||||||
| @ -70,7 +73,8 @@ function setupContextMenu() { | |||||||
|         if (params.isEditable) { |         if (params.isEditable) { | ||||||
|             items.push({ |             items.push({ | ||||||
|                 enabled: editFlags.canPaste, |                 enabled: editFlags.canPaste, | ||||||
|                 title: `Paste <kbd>${platformModifier}+V`, |                 title: t("electron_context_menu.paste"), | ||||||
|  |                 shortcut: `${platformModifier}+V`, | ||||||
|                 uiIcon: "bx bx-paste", |                 uiIcon: "bx bx-paste", | ||||||
|                 handler: () => webContents.paste() |                 handler: () => webContents.paste() | ||||||
|             }); |             }); | ||||||
| @ -79,7 +83,8 @@ function setupContextMenu() { | |||||||
|         if (params.isEditable) { |         if (params.isEditable) { | ||||||
|             items.push({ |             items.push({ | ||||||
|                 enabled: editFlags.canPaste, |                 enabled: editFlags.canPaste, | ||||||
|                 title: `Paste as plain text <kbd>${platformModifier}+Shift+V`, |                 title: t("electron_context_menu.paste-as-plain-text"), | ||||||
|  |                 shortcut: `${platformModifier}+Shift+V`, | ||||||
|                 uiIcon: "bx bx-paste", |                 uiIcon: "bx bx-paste", | ||||||
|                 handler: () => webContents.pasteAndMatchStyle() |                 handler: () => webContents.pasteAndMatchStyle() | ||||||
|             }); |             }); | ||||||
| @ -106,9 +111,11 @@ function setupContextMenu() { | |||||||
|             // Replace the placeholder with the real search keyword.
 |             // Replace the placeholder with the real search keyword.
 | ||||||
|             let searchUrl = searchEngineUrl.replace("{keyword}", encodeURIComponent(params.selectionText)); |             let searchUrl = searchEngineUrl.replace("{keyword}", encodeURIComponent(params.selectionText)); | ||||||
| 
 | 
 | ||||||
|  |             items.push({title: "----"}); | ||||||
|  | 
 | ||||||
|             items.push({ |             items.push({ | ||||||
|                 enabled: editFlags.canPaste, |                 enabled: editFlags.canPaste, | ||||||
|                 title: `Search for "${shortenedSelection}" with ${searchEngineName}`, |                 title: t("electron_context_menu.search_online", { term: shortenedSelection, searchEngine: searchEngineName }), | ||||||
|                 uiIcon: "bx bx-search-alt", |                 uiIcon: "bx bx-search-alt", | ||||||
|                 handler: () => electron.shell.openExternal(searchUrl) |                 handler: () => electron.shell.openExternal(searchUrl) | ||||||
|             }); |             }); | ||||||
|  | |||||||
| @ -1,3 +1,4 @@ | |||||||
|  | import { t } from '../services/i18n.js'; | ||||||
| import utils from "../services/utils.js"; | import utils from "../services/utils.js"; | ||||||
| import contextMenu from "./context_menu.js"; | import contextMenu from "./context_menu.js"; | ||||||
| import imageService from "../services/image.js"; | import imageService from "../services/image.js"; | ||||||
| @ -18,11 +19,15 @@ function setupContextMenu($image) { | |||||||
|             y: e.pageY, |             y: e.pageY, | ||||||
|             items: [ |             items: [ | ||||||
|                 { |                 { | ||||||
|                     title: "Copy reference to clipboard", |                     title: t("image_context_menu.copy_reference_to_clipboard"), | ||||||
|                     command: "copyImageReferenceToClipboard", |                     command: "copyImageReferenceToClipboard", | ||||||
|                     uiIcon: "bx bx-empty" |                     uiIcon: "bx bx-directions" | ||||||
|  |                 }, | ||||||
|  |                 { | ||||||
|  |                     title: t("image_context_menu.copy_image_to_clipboard"), | ||||||
|  |                     command: "copyImageToClipboard", | ||||||
|  |                     uiIcon: "bx bx-copy" | ||||||
|                 }, |                 }, | ||||||
|                 { title: "Copy image to clipboard", command: "copyImageToClipboard", uiIcon: "bx bx-empty" }, |  | ||||||
|             ], |             ], | ||||||
|             selectMenuItemHandler: async ({ command }) => { |             selectMenuItemHandler: async ({ command }) => { | ||||||
|                 if (command === 'copyImageReferenceToClipboard') { |                 if (command === 'copyImageReferenceToClipboard') { | ||||||
| @ -57,4 +62,4 @@ function setupContextMenu($image) { | |||||||
| 
 | 
 | ||||||
| export default { | export default { | ||||||
|     setupContextMenu |     setupContextMenu | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -37,18 +37,22 @@ export default class LauncherContextMenu { | |||||||
|         const canBeReset = !canBeDeleted && note.isLaunchBarConfig(); |         const canBeReset = !canBeDeleted && note.isLaunchBarConfig(); | ||||||
| 
 | 
 | ||||||
|         return [ |         return [ | ||||||
|             (isVisibleRoot || isAvailableRoot) ? { title: t("launcher_context_menu.add-note-launcher"), command: 'addNoteLauncher', uiIcon: "bx bx-plus" } : null, |             (isVisibleRoot || isAvailableRoot) ? { title: t("launcher_context_menu.add-note-launcher"), command: 'addNoteLauncher', uiIcon: "bx bx-note" } : null, | ||||||
|             (isVisibleRoot || isAvailableRoot) ? { title: t("launcher_context_menu.add-script-launcher"), command: 'addScriptLauncher', uiIcon: "bx bx-plus" } : null, |             (isVisibleRoot || isAvailableRoot) ? { title: t("launcher_context_menu.add-script-launcher"), command: 'addScriptLauncher', uiIcon: "bx bx-code-curly" } : null, | ||||||
|             (isVisibleRoot || isAvailableRoot) ? { title: t("launcher_context_menu.add-custom-widget"), command: 'addWidgetLauncher', uiIcon: "bx bx-plus" } : null, |             (isVisibleRoot || isAvailableRoot) ? { title: t("launcher_context_menu.add-custom-widget"), command: 'addWidgetLauncher', uiIcon: "bx bx-customize" } : null, | ||||||
|             (isVisibleRoot || isAvailableRoot) ? { title: t("launcher_context_menu.add-spacer"), command: 'addSpacerLauncher', uiIcon: "bx bx-plus" } : null, |             (isVisibleRoot || isAvailableRoot) ? { title: t("launcher_context_menu.add-spacer"), command: 'addSpacerLauncher', uiIcon: "bx bx-dots-horizontal" } : null, | ||||||
|             (isVisibleRoot || isAvailableRoot) ? { title: "----" } : null, |             (isVisibleRoot || isAvailableRoot) ? { title: "----" } : null, | ||||||
|             { title: `${t("launcher_context_menu.delete")} <kbd data-command="deleteNotes"></kbd>`, command: "deleteNotes", uiIcon: "bx bx-trash", enabled: canBeDeleted }, | 
 | ||||||
|             { title: t("launcher_context_menu.reset"), command: "resetLauncher", uiIcon: "bx bx-empty", enabled: canBeReset}, |  | ||||||
|             { title: "----" }, |  | ||||||
|             isAvailableItem ? { title: t("launcher_context_menu.move-to-visible-launchers"), command: "moveLauncherToVisible", uiIcon: "bx bx-show", enabled: true } : null, |             isAvailableItem ? { title: t("launcher_context_menu.move-to-visible-launchers"), command: "moveLauncherToVisible", uiIcon: "bx bx-show", enabled: true } : null, | ||||||
|             isVisibleItem ? { title: t("launcher_context_menu.move-to-available-launchers"), command: "moveLauncherToAvailable", uiIcon: "bx bx-hide", enabled: true } : null, |             isVisibleItem ? { title: t("launcher_context_menu.move-to-available-launchers"), command: "moveLauncherToAvailable", uiIcon: "bx bx-hide", enabled: true } : null, | ||||||
|             { title: `${t("launcher_context_menu.duplicate-launcher")} <kbd data-command="duplicateSubtree">`, command: "duplicateSubtree", uiIcon: "bx bx-empty", |             (isVisibleItem || isAvailableItem) ? { title: "----" } : null, | ||||||
|                 enabled: isItem } | 
 | ||||||
|  |             { title: `${t("launcher_context_menu.duplicate-launcher")} <kbd data-command="duplicateSubtree">`, command: "duplicateSubtree", uiIcon: "bx bx-outline", enabled: isItem }, | ||||||
|  |             { title: `${t("launcher_context_menu.delete")} <kbd data-command="deleteNotes"></kbd>`, command: "deleteNotes", uiIcon: "bx bx-trash destructive-action-icon", enabled: canBeDeleted }, | ||||||
|  |             | ||||||
|  |             { title: "----" }, | ||||||
|  |             | ||||||
|  |             { title: t("launcher_context_menu.reset"), command: "resetLauncher", uiIcon: "bx bx-reset destructive-action-icon", enabled: canBeReset} | ||||||
|         ].filter(row => row !== null); |         ].filter(row => row !== null); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,3 +1,4 @@ | |||||||
|  | import { t } from "../services/i18n.js"; | ||||||
| import contextMenu from "./context_menu.js"; | import contextMenu from "./context_menu.js"; | ||||||
| import appContext from "../components/app_context.js"; | import appContext from "../components/app_context.js"; | ||||||
| 
 | 
 | ||||||
| @ -6,9 +7,9 @@ function openContextMenu(notePath, e, viewScope = {}, hoistedNoteId = null) { | |||||||
|         x: e.pageX, |         x: e.pageX, | ||||||
|         y: e.pageY, |         y: e.pageY, | ||||||
|         items: [ |         items: [ | ||||||
|             {title: "Open note in a new tab", command: "openNoteInNewTab", uiIcon: "bx bx-empty"}, |             {title: t("link_context_menu.open_note_in_new_tab"), command: "openNoteInNewTab", uiIcon: "bx bx-link-external"}, | ||||||
|             {title: "Open note in a new split", command: "openNoteInNewSplit", uiIcon: "bx bx-dock-right"}, |             {title: t("link_context_menu.open_note_in_new_split"), command: "openNoteInNewSplit", uiIcon: "bx bx-dock-right"}, | ||||||
|             {title: "Open note in a new window", command: "openNoteInNewWindow", uiIcon: "bx bx-window-open"} |             {title: t("link_context_menu.open_note_in_new_window"), command: "openNoteInNewWindow", uiIcon: "bx bx-window-open"} | ||||||
|         ], |         ], | ||||||
|         selectMenuItemHandler: ({command}) => { |         selectMenuItemHandler: ({command}) => { | ||||||
|             if (!hoistedNoteId) { |             if (!hoistedNoteId) { | ||||||
|  | |||||||
| @ -49,56 +49,98 @@ export default class TreeContextMenu { | |||||||
|         const insertNoteAfterEnabled = isNotRoot && !isHoisted && parentNotSearch; |         const insertNoteAfterEnabled = isNotRoot && !isHoisted && parentNotSearch; | ||||||
| 
 | 
 | ||||||
|         return [ |         return [ | ||||||
|             { title: `${t("tree-context-menu.open-in-a-new-tab")} <kbd>Ctrl+Click</kbd>`, command: "openInTab", uiIcon: "bx bx-empty", enabled: noSelectedNotes }, |             { title: `${t("tree-context-menu.open-in-a-new-tab")} <kbd>Ctrl+Click</kbd>`, command: "openInTab", uiIcon: "bx bx-link-external", enabled: noSelectedNotes }, | ||||||
|  |              | ||||||
|             { title: t("tree-context-menu.open-in-a-new-split"), command: "openNoteInSplit", uiIcon: "bx bx-dock-right", enabled: noSelectedNotes }, |             { title: t("tree-context-menu.open-in-a-new-split"), command: "openNoteInSplit", uiIcon: "bx bx-dock-right", enabled: noSelectedNotes }, | ||||||
|             { title: `${t("tree-context-menu.insert-note-after")} <kbd data-command="createNoteAfter"></kbd>`, command: "insertNoteAfter", uiIcon: "bx bx-plus", |              | ||||||
|  |             isHoisted ? null : { title: `${t("tree-context-menu.hoist-note")} <kbd data-command="toggleNoteHoisting"></kbd>`, command: "toggleNoteHoisting", uiIcon: "bx bxs-chevrons-up", enabled: noSelectedNotes && notSearch }, | ||||||
|  |             !isHoisted || !isNotRoot ? null : { title: `${t("tree-context-menu.unhoist-note")} <kbd data-command="toggleNoteHoisting"></kbd>`, command: "toggleNoteHoisting", uiIcon: "bx bx-door-open" }, | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |             { title: "----" }, | ||||||
|  |              | ||||||
|  | 
 | ||||||
|  |             { title: `${t("tree-context-menu.insert-note-after")}<kbd data-command="createNoteAfter"></kbd>`, command: "insertNoteAfter", uiIcon: "bx bx-plus", | ||||||
|                 items: insertNoteAfterEnabled ? await noteTypesService.getNoteTypeItems("insertNoteAfter") : null, |                 items: insertNoteAfterEnabled ? await noteTypesService.getNoteTypeItems("insertNoteAfter") : null, | ||||||
|                 enabled: insertNoteAfterEnabled && noSelectedNotes && notOptions }, |                 enabled: insertNoteAfterEnabled && noSelectedNotes && notOptions }, | ||||||
|             { title: `${t("tree-context-menu.insert-child-note")} <kbd data-command="createNoteInto"></kbd>`, command: "insertChildNote", uiIcon: "bx bx-plus", | 
 | ||||||
|  |             { title: `${t("tree-context-menu.insert-child-note")}<kbd data-command="createNoteInto"></kbd>`, command: "insertChildNote", uiIcon: "bx bx-plus", | ||||||
|                 items: notSearch ? await noteTypesService.getNoteTypeItems("insertChildNote") : null, |                 items: notSearch ? await noteTypesService.getNoteTypeItems("insertChildNote") : null, | ||||||
|                 enabled: notSearch && noSelectedNotes && notOptions }, |                 enabled: notSearch && noSelectedNotes && notOptions }, | ||||||
|             { title: `${t("tree-context-menu.delete")} <kbd data-command="deleteNotes"></kbd>`, command: "deleteNotes", uiIcon: "bx bx-trash", |              | ||||||
|                 enabled: isNotRoot && !isHoisted && parentNotSearch && notOptions }, | 
 | ||||||
|             { title: "----" }, |  | ||||||
|             { title: `${t("tree-context-menu.search-in-subtree")} <kbd data-command="searchInSubtree"></kbd>`, command: "searchInSubtree", uiIcon: "bx bx-search", |  | ||||||
|                 enabled: notSearch && noSelectedNotes }, |  | ||||||
|             isHoisted ? null : { title: `${t("tree-context-menu.hoist-note")} <kbd data-command="toggleNoteHoisting"></kbd>`, command: "toggleNoteHoisting", uiIcon: "bx bx-empty", enabled: noSelectedNotes && notSearch }, |  | ||||||
|             !isHoisted || !isNotRoot ? null : { title: `${t("tree-context-menu.unhoist-note")} <kbd data-command="toggleNoteHoisting"></kbd>`, command: "toggleNoteHoisting", uiIcon: "bx bx-door-open" }, |  | ||||||
|             { title: `${t("tree-context-menu.edit-branch-prefix")} <kbd data-command="editBranchPrefix"></kbd>`, command: "editBranchPrefix", uiIcon: "bx bx-empty", |  | ||||||
|                 enabled: isNotRoot && parentNotSearch && noSelectedNotes && notOptions }, |  | ||||||
|             { title: t("tree-context-menu.advanced"), uiIcon: "bx bx-empty", enabled: true, items: [ |  | ||||||
|                     { title: `${t("tree-context-menu.expand-subtree")} <kbd data-command="expandSubtree"></kbd>`, command: "expandSubtree", uiIcon: "bx bx-expand", enabled: noSelectedNotes }, |  | ||||||
|                     { title: `${t("tree-context-menu.collapse-subtree")} <kbd data-command="collapseSubtree"></kbd>`, command: "collapseSubtree", uiIcon: "bx bx-collapse", enabled: noSelectedNotes }, |  | ||||||
|                     { title: `${t("tree-context-menu.sort-by")} <kbd data-command="sortChildNotes"></kbd>`, command: "sortChildNotes", uiIcon: "bx bx-empty", enabled: noSelectedNotes && notSearch }, |  | ||||||
|                     { title: t("tree-context-menu.recent-changes-in-subtree"), command: "recentChangesInSubtree", uiIcon: "bx bx-history", enabled: noSelectedNotes && notOptions }, |  | ||||||
|                     { title: t("tree-context-menu.convert-to-attachment"), command: "convertNoteToAttachment", uiIcon: "bx bx-empty", enabled: isNotRoot && !isHoisted && notOptions }, |  | ||||||
|                     { title: t("tree-context-menu.copy-note-path-to-clipboard"), command: "copyNotePathToClipboard", uiIcon: "bx bx-empty", enabled: true } |  | ||||||
|                 ] }, |  | ||||||
|             { title: "----" }, |             { title: "----" }, | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|             { title: t("tree-context-menu.protect-subtree"), command: "protectSubtree", uiIcon: "bx bx-check-shield", enabled: noSelectedNotes }, |             { title: t("tree-context-menu.protect-subtree"), command: "protectSubtree", uiIcon: "bx bx-check-shield", enabled: noSelectedNotes }, | ||||||
|  | 
 | ||||||
|             { title: t("tree-context-menu.unprotect-subtree"), command: "unprotectSubtree", uiIcon: "bx bx-shield", enabled: noSelectedNotes }, |             { title: t("tree-context-menu.unprotect-subtree"), command: "unprotectSubtree", uiIcon: "bx bx-shield", enabled: noSelectedNotes }, | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|             { title: "----" }, |             { title: "----" }, | ||||||
|             { title: `${t("tree-context-menu.copy-clone")} <kbd data-command="copyNotesToClipboard"></kbd>`, command: "copyNotesToClipboard", uiIcon: "bx bx-copy", | 
 | ||||||
|                 enabled: isNotRoot && !isHoisted }, | 
 | ||||||
|             { title: `${t("tree-context-menu.clone-to")} <kbd data-command="cloneNotesTo"></kbd>`, command: "cloneNotesTo", uiIcon: "bx bx-empty", |             { title: t("tree-context-menu.advanced"), uiIcon: "bx bxs-wrench", enabled: true, items: [ | ||||||
|                 enabled: isNotRoot && !isHoisted }, |                 { title: t("tree-context-menu.apply-bulk-actions"), command: "openBulkActionsDialog", uiIcon: "bx bx-list-plus", enabled: true }, | ||||||
|  | 
 | ||||||
|  |                 { title: "----" }, | ||||||
|  | 
 | ||||||
|  |                 { title: `${t("tree-context-menu.edit-branch-prefix")} <kbd data-command="editBranchPrefix"></kbd>`, command: "editBranchPrefix", uiIcon: "bx bx-rename", enabled: isNotRoot && parentNotSearch && noSelectedNotes && notOptions }, | ||||||
|  |                 { title: t("tree-context-menu.convert-to-attachment"), command: "convertNoteToAttachment", uiIcon: "bx bx-paperclip", enabled: isNotRoot && !isHoisted && notOptions }, | ||||||
|  |                 { title: `${t("tree-context-menu.duplicate-subtree")} <kbd data-command="duplicateSubtree">`, command: "duplicateSubtree", uiIcon: "bx bx-outline", enabled: parentNotSearch && isNotRoot && !isHoisted && notOptions }, | ||||||
|  | 
 | ||||||
|  |                 { title: "----" }, | ||||||
|  | 
 | ||||||
|  |                 { title: `${t("tree-context-menu.expand-subtree")} <kbd data-command="expandSubtree"></kbd>`, command: "expandSubtree", uiIcon: "bx bx-expand", enabled: noSelectedNotes }, | ||||||
|  |                 { title: `${t("tree-context-menu.collapse-subtree")} <kbd data-command="collapseSubtree"></kbd>`, command: "collapseSubtree", uiIcon: "bx bx-collapse", enabled: noSelectedNotes }, | ||||||
|  |                 { title: `${t("tree-context-menu.sort-by")} <kbd data-command="sortChildNotes"></kbd>`, command: "sortChildNotes", uiIcon: "bx bx-sort-down", enabled: noSelectedNotes && notSearch }, | ||||||
|  | 
 | ||||||
|  |                 { title: "----" }, | ||||||
|  | 
 | ||||||
|  |                 { title: t("tree-context-menu.copy-note-path-to-clipboard"), command: "copyNotePathToClipboard", uiIcon: "bx bx-directions", enabled: true }, | ||||||
|  |                 { title: t("tree-context-menu.recent-changes-in-subtree"), command: "recentChangesInSubtree", uiIcon: "bx bx-history", enabled: noSelectedNotes && notOptions }, | ||||||
|  |             ] }, | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |             { title: "----" }, | ||||||
|  |              | ||||||
|  | 
 | ||||||
|             { title: `${t("tree-context-menu.cut")} <kbd data-command="cutNotesToClipboard"></kbd>`, command: "cutNotesToClipboard", uiIcon: "bx bx-cut", |             { title: `${t("tree-context-menu.cut")} <kbd data-command="cutNotesToClipboard"></kbd>`, command: "cutNotesToClipboard", uiIcon: "bx bx-cut", | ||||||
|                 enabled: isNotRoot && !isHoisted && parentNotSearch }, |                 enabled: isNotRoot && !isHoisted && parentNotSearch }, | ||||||
|             { title: `${t("tree-context-menu.move-to")} <kbd data-command="moveNotesTo"></kbd>`, command: "moveNotesTo", uiIcon: "bx bx-empty", | 
 | ||||||
|                 enabled: isNotRoot && !isHoisted && parentNotSearch }, |             { title: `${t("tree-context-menu.copy-clone")} <kbd data-command="copyNotesToClipboard"></kbd>`, command: "copyNotesToClipboard", uiIcon: "bx bx-copy", | ||||||
|  |                 enabled: isNotRoot && !isHoisted }, | ||||||
|  | 
 | ||||||
|             { title: `${t("tree-context-menu.paste-into")} <kbd data-command="pasteNotesFromClipboard"></kbd>`, command: "pasteNotesFromClipboard", uiIcon: "bx bx-paste", |             { title: `${t("tree-context-menu.paste-into")} <kbd data-command="pasteNotesFromClipboard"></kbd>`, command: "pasteNotesFromClipboard", uiIcon: "bx bx-paste", | ||||||
|                 enabled: !clipboard.isClipboardEmpty() && notSearch && noSelectedNotes }, |                 enabled: !clipboard.isClipboardEmpty() && notSearch && noSelectedNotes }, | ||||||
|  |              | ||||||
|             { title: t("tree-context-menu.paste-after"), command: "pasteNotesAfterFromClipboard", uiIcon: "bx bx-paste", |             { title: t("tree-context-menu.paste-after"), command: "pasteNotesAfterFromClipboard", uiIcon: "bx bx-paste", | ||||||
|                 enabled: !clipboard.isClipboardEmpty() && isNotRoot && !isHoisted && parentNotSearch && noSelectedNotes }, |                 enabled: !clipboard.isClipboardEmpty() && isNotRoot && !isHoisted && parentNotSearch && noSelectedNotes }, | ||||||
|             { title: `${t("tree-context-menu.duplicate-subtree")} <kbd data-command="duplicateSubtree">`, command: "duplicateSubtree", uiIcon: "bx bx-empty", | 
 | ||||||
|                 enabled: parentNotSearch && isNotRoot && !isHoisted && notOptions }, |             { title: `${t("tree-context-menu.move-to")} <kbd data-command="moveNotesTo"></kbd>`, command: "moveNotesTo", uiIcon: "bx bx-transfer", | ||||||
|  |                 enabled: isNotRoot && !isHoisted && parentNotSearch }, | ||||||
|  |              | ||||||
|  |             { title: `${t("tree-context-menu.clone-to")} <kbd data-command="cloneNotesTo"></kbd>`, command: "cloneNotesTo", uiIcon: "bx bx-duplicate", | ||||||
|  |                 enabled: isNotRoot && !isHoisted }, | ||||||
|  | 
 | ||||||
|  |             { title: `${t("tree-context-menu.delete")} <kbd data-command="deleteNotes"></kbd>`, command: "deleteNotes", uiIcon: "bx bx-trash destructive-action-icon", | ||||||
|  |             enabled: isNotRoot && !isHoisted && parentNotSearch && notOptions }, | ||||||
|  | 
 | ||||||
|             { title: "----" }, |             { title: "----" }, | ||||||
|             { title: t("tree-context-menu.export"), command: "exportNote", uiIcon: "bx bx-empty", | 
 | ||||||
|  |             { title: t("tree-context-menu.import-into-note"), command: "importIntoNote", uiIcon: "bx bx-import", | ||||||
|                 enabled: notSearch && noSelectedNotes && notOptions }, |                 enabled: notSearch && noSelectedNotes && notOptions }, | ||||||
|             { title: t("tree-context-menu.import-into-note"), command: "importIntoNote", uiIcon: "bx bx-empty", | 
 | ||||||
|  |             { title: t("tree-context-menu.export"), command: "exportNote", uiIcon: "bx bx-export", | ||||||
|                 enabled: notSearch && noSelectedNotes && notOptions }, |                 enabled: notSearch && noSelectedNotes && notOptions }, | ||||||
|             { title: t("tree-context-menu.apply-bulk-actions"), command: "openBulkActionsDialog", uiIcon: "bx bx-list-plus", | 
 | ||||||
|                 enabled: true } | 
 | ||||||
|  |             { title: "----" }, | ||||||
|  | 
 | ||||||
|  |              | ||||||
|  |             { title: `${t("tree-context-menu.search-in-subtree")} <kbd data-command="searchInSubtree"></kbd>`, command: "searchInSubtree", uiIcon: "bx bx-search", | ||||||
|  |             enabled: notSearch && noSelectedNotes }, | ||||||
|  | 
 | ||||||
|         ].filter(row => row !== null); |         ].filter(row => row !== null); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -13,22 +13,23 @@ import ExecuteScriptBulkAction from "../widgets/bulk_actions/execute_script.js"; | |||||||
| import AddLabelBulkAction from "../widgets/bulk_actions/label/add_label.js"; | import AddLabelBulkAction from "../widgets/bulk_actions/label/add_label.js"; | ||||||
| import AddRelationBulkAction from "../widgets/bulk_actions/relation/add_relation.js"; | import AddRelationBulkAction from "../widgets/bulk_actions/relation/add_relation.js"; | ||||||
| import RenameNoteBulkAction from "../widgets/bulk_actions/note/rename_note.js"; | import RenameNoteBulkAction from "../widgets/bulk_actions/note/rename_note.js"; | ||||||
|  | import { t } from "./i18n.js"; | ||||||
| 
 | 
 | ||||||
| const ACTION_GROUPS = [ | const ACTION_GROUPS = [ | ||||||
|     { |     { | ||||||
|         title: 'Labels', |         title: t("bulk_actions.labels"), | ||||||
|         actions: [AddLabelBulkAction, UpdateLabelValueBulkAction, RenameLabelBulkAction, DeleteLabelBulkAction] |         actions: [AddLabelBulkAction, UpdateLabelValueBulkAction, RenameLabelBulkAction, DeleteLabelBulkAction] | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|         title: 'Relations', |         title: t("bulk_actions.relations"), | ||||||
|         actions: [AddRelationBulkAction, UpdateRelationTargetBulkAction, RenameRelationBulkAction, DeleteRelationBulkAction] |         actions: [AddRelationBulkAction, UpdateRelationTargetBulkAction, RenameRelationBulkAction, DeleteRelationBulkAction] | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|         title: 'Notes', |         title: t("bulk_actions.notes"), | ||||||
|         actions: [RenameNoteBulkAction, MoveNoteBulkAction, DeleteNoteBulkAction, DeleteRevisionsBulkAction], |         actions: [RenameNoteBulkAction, MoveNoteBulkAction, DeleteNoteBulkAction, DeleteRevisionsBulkAction], | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|         title: 'Other', |         title: t("bulk_actions.other"), | ||||||
|         actions: [ExecuteScriptBulkAction] |         actions: [ExecuteScriptBulkAction] | ||||||
|     } |     } | ||||||
| ]; | ]; | ||||||
|  | |||||||
| @ -12,6 +12,7 @@ import FAttachment from "../entities/fattachment.js"; | |||||||
| import imageContextMenuService from "../menus/image_context_menu.js"; | import imageContextMenuService from "../menus/image_context_menu.js"; | ||||||
| import { applySingleBlockSyntaxHighlight, applySyntaxHighlight } from "./syntax_highlight.js"; | import { applySingleBlockSyntaxHighlight, applySyntaxHighlight } from "./syntax_highlight.js"; | ||||||
| import mime_types from "./mime_types.js"; | import mime_types from "./mime_types.js"; | ||||||
|  | import { loadElkIfNeeded } from "./mermaid.js"; | ||||||
| 
 | 
 | ||||||
| let idCounter = 1; | let idCounter = 1; | ||||||
| 
 | 
 | ||||||
| @ -237,6 +238,7 @@ async function renderMermaid(note, $renderedContent) { | |||||||
|     mermaid.mermaidAPI.initialize({startOnLoad: false, theme: mermaidTheme.trim(), securityLevel: 'antiscript'}); |     mermaid.mermaidAPI.initialize({startOnLoad: false, theme: mermaidTheme.trim(), securityLevel: 'antiscript'}); | ||||||
| 
 | 
 | ||||||
|     try { |     try { | ||||||
|  |         await loadElkIfNeeded(content); | ||||||
|         const {svg} = await mermaid.mermaidAPI.render("in-mermaid-graph-" + idCounter++, content); |         const {svg} = await mermaid.mermaidAPI.render("in-mermaid-graph-" + idCounter++, content); | ||||||
| 
 | 
 | ||||||
|         $renderedContent.append($(svg)); |         $renderedContent.append($(svg)); | ||||||
|  | |||||||
| @ -58,7 +58,19 @@ const FORCE_GRAPH = { | |||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const MERMAID = { | const MERMAID = { | ||||||
|     js: [ "node_modules/mermaid/dist/mermaid.min.js" ] |     js: [ | ||||||
|  |         "node_modules/mermaid/dist/mermaid.min.js"         | ||||||
|  |     ] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * The ELK extension of Mermaid.js, which supports more advanced layouts. | ||||||
|  |  * See https://www.npmjs.com/package/@mermaid-js/layout-elk for more information.
 | ||||||
|  |  */ | ||||||
|  | const MERMAID_ELK = { | ||||||
|  |     js: [ | ||||||
|  |         "libraries/mermaid-elk/elk.min.js" | ||||||
|  |     ] | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const EXCALIDRAW = { | const EXCALIDRAW = { | ||||||
| @ -197,6 +209,7 @@ export default { | |||||||
|     WHEEL_ZOOM, |     WHEEL_ZOOM, | ||||||
|     FORCE_GRAPH, |     FORCE_GRAPH, | ||||||
|     MERMAID, |     MERMAID, | ||||||
|  |     MERMAID_ELK, | ||||||
|     EXCALIDRAW, |     EXCALIDRAW, | ||||||
|     MARKJS, |     MARKJS, | ||||||
|     I18NEXT, |     I18NEXT, | ||||||
|  | |||||||
| @ -254,8 +254,15 @@ function goToLinkExt(evt, hrefLink, $link) { | |||||||
|                 window.open(hrefLink, '_blank'); |                 window.open(hrefLink, '_blank'); | ||||||
|             } else if (hrefLink.toLowerCase().startsWith('file:') && utils.isElectron()) { |             } else if (hrefLink.toLowerCase().startsWith('file:') && utils.isElectron()) { | ||||||
|                 const electron = utils.dynamicRequire('electron'); |                 const electron = utils.dynamicRequire('electron'); | ||||||
| 
 |  | ||||||
|                 electron.shell.openPath(hrefLink); |                 electron.shell.openPath(hrefLink); | ||||||
|  |             } else { | ||||||
|  |                 // Enable protocols supported by CKEditor 5 to be clickable. 
 | ||||||
|  |                 // Refer to `allowedProtocols` in https://github.com/TriliumNext/trilium-ckeditor5/blob/main/packages/ckeditor5-build-balloon-block/src/ckeditor.ts.
 | ||||||
|  |                 // Adding `:` to these links might be safer.
 | ||||||
|  |                 const otherAllowedProtocols = ['mailto:', 'tel:', 'sms:', 'sftp:', 'smb:', 'slack:', 'zotero:']; | ||||||
|  |                 if (otherAllowedProtocols.some(protocol => hrefLink.toLowerCase().startsWith(protocol))){ | ||||||
|  |                     window.open(hrefLink, '_blank'); | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | |||||||
							
								
								
									
										28
									
								
								src/public/app/services/mermaid.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								src/public/app/services/mermaid.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,28 @@ | |||||||
|  | import library_loader from "./library_loader.js"; | ||||||
|  | 
 | ||||||
|  | let elkLoaded = false; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Determines whether the ELK extension of Mermaid.js needs to be loaded (which is a relatively large library), based on the | ||||||
|  |  * front-matter of the diagram and loads the library if needed. | ||||||
|  |  *  | ||||||
|  |  * <p> | ||||||
|  |  * If the library has already been loaded or the diagram does not require it, the method will exit immediately. | ||||||
|  |  *  | ||||||
|  |  * @param mermaidContent the plain text of the mermaid diagram, potentially including a frontmatter. | ||||||
|  |  */ | ||||||
|  | export async function loadElkIfNeeded(mermaidContent) { | ||||||
|  |     if (elkLoaded) { | ||||||
|  |         // Exit immediately since the ELK library is already loaded.
 | ||||||
|  |         return; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const parsedContent = await mermaid.parse(mermaidContent, { | ||||||
|  |         suppressErrors: true  | ||||||
|  |     }); | ||||||
|  |     if (parsedContent?.config?.layout === "elk") { | ||||||
|  |         elkLoaded = true; | ||||||
|  |         await library_loader.requireLibrary(library_loader.MERMAID_ELK); | ||||||
|  |         mermaid.registerLayoutLoaders(MERMAID_ELK); | ||||||
|  |     }     | ||||||
|  | } | ||||||
| @ -45,6 +45,16 @@ async function autocompleteSource(term, cb, options = {}) { | |||||||
|         ].concat(results); |         ].concat(results); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     if (term.trim().length >= 1 && options.allowSearchNotes) { | ||||||
|  |         results = results.concat([ | ||||||
|  |             { | ||||||
|  |                 action: 'search-notes', | ||||||
|  |                 noteTitle: term, | ||||||
|  |                 highlightedNotePathTitle: `Search for "${utils.escapeHtml(term)}" <kbd style='color: var(--muted-text-color); background-color: transparent; float: right;'>Ctrl+Enter</kbd>` | ||||||
|  |             } | ||||||
|  |         ]); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     if (term.match(/^[a-z]+:\/\/.+/i) && options.allowExternalLinks) { |     if (term.match(/^[a-z]+:\/\/.+/i) && options.allowExternalLinks) { | ||||||
|         results = [ |         results = [ | ||||||
|             { |             { | ||||||
| @ -138,6 +148,17 @@ function initNoteAutocomplete($el, options) { | |||||||
|         autocompleteOptions.debug = true;   // don't close on blur
 |         autocompleteOptions.debug = true;   // don't close on blur
 | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     if (options.allowSearchNotes) { | ||||||
|  |         $el.on('keydown', (event) => { | ||||||
|  |             if (event.ctrlKey && event.key === 'Enter') { | ||||||
|  |                 // Prevent Ctrl + Enter from triggering autoComplete.
 | ||||||
|  |                 event.stopImmediatePropagation(); | ||||||
|  |                 event.preventDefault(); | ||||||
|  |                 $el.trigger('autocomplete:selected', { action: 'search-notes', noteTitle: $el.autocomplete("val")}); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     $el.autocomplete({ |     $el.autocomplete({ | ||||||
|         ...autocompleteOptions, |         ...autocompleteOptions, | ||||||
|         appendTo: document.querySelector('body'), |         appendTo: document.querySelector('body'), | ||||||
| @ -192,6 +213,12 @@ function initNoteAutocomplete($el, options) { | |||||||
|             suggestion.notePath = note.getBestNotePathString(hoistedNoteId); |             suggestion.notePath = note.getBestNotePathString(hoistedNoteId); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |         if (suggestion.action === 'search-notes') { | ||||||
|  |             const searchString = suggestion.noteTitle; | ||||||
|  |             appContext.triggerCommand('searchNotes', { searchString }); | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |          | ||||||
|         $el.setSelectedNotePath(suggestion.notePath); |         $el.setSelectedNotePath(suggestion.notePath); | ||||||
|         $el.setSelectedExternalLink(null); |         $el.setSelectedExternalLink(null); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -6,7 +6,7 @@ function parse(value) { | |||||||
|         if (token === 'promoted') { |         if (token === 'promoted') { | ||||||
|             defObj.isPromoted = true; |             defObj.isPromoted = true; | ||||||
|         } |         } | ||||||
|         else if (['text', 'number', 'boolean', 'date', 'datetime', 'url'].includes(token)) { |         else if (['text', 'number', 'boolean', 'date', 'datetime', 'time', 'url'].includes(token)) { | ||||||
|             defObj.labelType = token; |             defObj.labelType = token; | ||||||
|         } |         } | ||||||
|         else if (['single', 'multi'].includes(token)) { |         else if (['single', 'multi'].includes(token)) { | ||||||
|  | |||||||
| @ -125,6 +125,7 @@ const TPL = ` | |||||||
|                   <option value="boolean">${t('attribute_detail.boolean')}</option> |                   <option value="boolean">${t('attribute_detail.boolean')}</option> | ||||||
|                   <option value="date">${t('attribute_detail.date')}</option> |                   <option value="date">${t('attribute_detail.date')}</option> | ||||||
|                   <option value="datetime">${t('attribute_detail.date_time')}</option> |                   <option value="datetime">${t('attribute_detail.date_time')}</option> | ||||||
|  |                   <option value="time">${t('attribute_detail.time')}</option> | ||||||
|                   <option value="url">${t('attribute_detail.url')}</option> |                   <option value="url">${t('attribute_detail.url')}</option> | ||||||
|                 </select> |                 </select> | ||||||
|             </td> |             </td> | ||||||
|  | |||||||
| @ -40,6 +40,21 @@ class BasicWidget extends Component { | |||||||
|         return this; |         return this; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * Conditionally adds the given components as children to this component. | ||||||
|  |      *  | ||||||
|  |      * @param {boolean} condition whether to add the components. | ||||||
|  |      * @param  {...any} components the components to be added as children to this component provided the condition is truthy.  | ||||||
|  |      * @returns self for chaining. | ||||||
|  |      */ | ||||||
|  |     optChild(condition, ...components) { | ||||||
|  |         if (condition) { | ||||||
|  |             return this.child(...components); | ||||||
|  |         } else { | ||||||
|  |             return this; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     id(id) { |     id(id) { | ||||||
|         this.attrs.id = id; |         this.attrs.id = id; | ||||||
|         return this; |         return this; | ||||||
| @ -50,11 +65,34 @@ class BasicWidget extends Component { | |||||||
|         return this; |         return this; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * Sets the CSS attribute of the given name to the given value. | ||||||
|  |      *  | ||||||
|  |      * @param {string} name the name of the CSS attribute to set (e.g. `padding-left`). | ||||||
|  |      * @param {string} value the value of the CSS attribute to set (e.g. `12px`). | ||||||
|  |      * @returns self for chaining. | ||||||
|  |      */ | ||||||
|     css(name, value) { |     css(name, value) { | ||||||
|         this.attrs.style += `${name}: ${value};`; |         this.attrs.style += `${name}: ${value};`; | ||||||
|         return this; |         return this; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * Sets the CSS attribute of the given name to the given value, but only if the condition provided is truthy. | ||||||
|  |      *  | ||||||
|  |      * @param {boolean} condition `true` in order to apply the CSS, `false` to ignore it. | ||||||
|  |      * @param {string} name the name of the CSS attribute to set (e.g. `padding-left`). | ||||||
|  |      * @param {string} value the value of the CSS attribute to set (e.g. `12px`). | ||||||
|  |      * @returns self for chaining. | ||||||
|  |      */ | ||||||
|  |     optCss(condition, name, value) { | ||||||
|  |         if (condition) { | ||||||
|  |             return this.css(name, value); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return this; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     contentSized() { |     contentSized() { | ||||||
|         this.css("contain", "none"); |         this.css("contain", "none"); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -4,8 +4,8 @@ import BookmarkFolderWidget from "./buttons/bookmark_folder.js"; | |||||||
| import froca from "../services/froca.js"; | import froca from "../services/froca.js"; | ||||||
| 
 | 
 | ||||||
| export default class BookmarkButtons extends FlexContainer { | export default class BookmarkButtons extends FlexContainer { | ||||||
|     constructor() { |     constructor(isHorizontalLayout) { | ||||||
|         super("column"); |         super(isHorizontalLayout ? "row" : "column"); | ||||||
| 
 | 
 | ||||||
|         this.contentSized(); |         this.contentSized(); | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -23,7 +23,11 @@ export default class AbstractButtonWidget extends NoteContextAwareWidget { | |||||||
|     doRender() { |     doRender() { | ||||||
|         this.$widget = $(TPL); |         this.$widget = $(TPL); | ||||||
|         this.tooltip = new bootstrap.Tooltip(this.$widget, { |         this.tooltip = new bootstrap.Tooltip(this.$widget, { | ||||||
|             html: true, title: () => this.getTitle(), trigger: 'hover' |             html: true, | ||||||
|  |             title: () => this.getTitle(), | ||||||
|  |             trigger: 'hover', | ||||||
|  |             placement: this.settings.titlePlacement, | ||||||
|  |             fallbackPlacements: [ this.settings.titlePlacement ] | ||||||
|         }) |         }) | ||||||
| 
 | 
 | ||||||
|         if (this.settings.onContextMenu) { |         if (this.settings.onContextMenu) { | ||||||
| @ -36,8 +40,6 @@ export default class AbstractButtonWidget extends NoteContextAwareWidget { | |||||||
|             }); |             }); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         this.$widget.attr("data-placement", this.settings.titlePlacement); |  | ||||||
| 
 |  | ||||||
|         super.doRender(); |         super.doRender(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -20,6 +20,13 @@ const TPL = ` | |||||||
|         width: 20em; |         width: 20em; | ||||||
|     } |     } | ||||||
|      |      | ||||||
|  |     .attachment-actions .dropdown-item .bx { | ||||||
|  |         position: relative; | ||||||
|  |         top: 3px; | ||||||
|  |         font-size: 120%; | ||||||
|  |         margin-right: 5px; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     .attachment-actions .dropdown-item[disabled], .attachment-actions .dropdown-item[disabled]:hover { |     .attachment-actions .dropdown-item[disabled], .attachment-actions .dropdown-item[disabled]:hover { | ||||||
|         color: var(--muted-text-color) !important; |         color: var(--muted-text-color) !important; | ||||||
|         background-color: transparent !important; |         background-color: transparent !important; | ||||||
| @ -32,16 +39,39 @@ const TPL = ` | |||||||
|         style="position: relative; top: 3px;"></button> |         style="position: relative; top: 3px;"></button> | ||||||
| 
 | 
 | ||||||
|     <div class="dropdown-menu dropdown-menu-right"> |     <div class="dropdown-menu dropdown-menu-right"> | ||||||
|         <a data-trigger-command="openAttachment" class="dropdown-item" | 
 | ||||||
|             title="${t('attachments_actions.open_externally_title')}">${t('attachments_actions.open_externally')}</a> |         <li data-trigger-command="openAttachment" class="dropdown-item" | ||||||
|         <a data-trigger-command="openAttachmentCustom" class="dropdown-item" |             title="${t('attachments_actions.open_externally_title')}"><span class="bx bx-file-find"></span> ${t('attachments_actions.open_externally')}</li> | ||||||
|             title="${t('attachments_actions.open_custom_title')}">${t('attachments_actions.open_custom')}</a> |          | ||||||
|         <a data-trigger-command="downloadAttachment" class="dropdown-item">${t('attachments_actions.download')}</a> |         <li data-trigger-command="openAttachmentCustom" class="dropdown-item" | ||||||
|         <a data-trigger-command="renameAttachment" class="dropdown-item">${t('attachments_actions.rename_attachment')}</a> |             title="${t('attachments_actions.open_custom_title')}"><span class="bx bx-customize"></span> ${t('attachments_actions.open_custom')}</li> | ||||||
|         <a data-trigger-command="uploadNewAttachmentRevision" class="dropdown-item">${t('attachments_actions.upload_new_revision')}</a> |          | ||||||
|         <a data-trigger-command="copyAttachmentLinkToClipboard" class="dropdown-item">${t('attachments_actions.copy_link_to_clipboard')}</a> |         <li data-trigger-command="downloadAttachment" class="dropdown-item"> | ||||||
|         <a data-trigger-command="convertAttachmentIntoNote" class="dropdown-item">${t('attachments_actions.convert_attachment_into_note')}</a> |             <span class="bx bx-download"></span> ${t('attachments_actions.download')}</li> | ||||||
|         <a data-trigger-command="deleteAttachment" class="dropdown-item">${t('attachments_actions.delete_attachment')}</a> | 
 | ||||||
|  |         <li data-trigger-command="copyAttachmentLinkToClipboard" class="dropdown-item"><span class="bx bx-link"> | ||||||
|  |             </span> ${t('attachments_actions.copy_link_to_clipboard')}</li> | ||||||
|  | 
 | ||||||
|  |          | ||||||
|  |         <div class="dropdown-divider"></div> | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |         <li data-trigger-command="uploadNewAttachmentRevision" class="dropdown-item"><span class="bx bx-upload"> | ||||||
|  |             </span> ${t('attachments_actions.upload_new_revision')}</li> | ||||||
|  | 
 | ||||||
|  |         <li data-trigger-command="renameAttachment" class="dropdown-item"> | ||||||
|  |             <span class="bx bx-rename"></span> ${t('attachments_actions.rename_attachment')}</li> | ||||||
|  | 
 | ||||||
|  |         <li data-trigger-command="deleteAttachment" class="dropdown-item"> | ||||||
|  |             <span class="bx bx-trash destructive-action-icon"></span> ${t('attachments_actions.delete_attachment')}</li> | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |         <div class="dropdown-divider"></div> | ||||||
|  |              | ||||||
|  | 
 | ||||||
|  |         <li data-trigger-command="convertAttachmentIntoNote" class="dropdown-item"><span class="bx bx-note"> | ||||||
|  |             </span> ${t('attachments_actions.convert_attachment_into_note')}</li> | ||||||
|  |          | ||||||
|     </div> |     </div> | ||||||
|      |      | ||||||
|     <input type="file" class="attachment-upload-new-revision-input" style="display: none"> |     <input type="file" class="attachment-upload-new-revision-input" style="display: none"> | ||||||
| @ -83,14 +113,14 @@ export default class AttachmentActionsWidget extends BasicWidget { | |||||||
|             const $openAttachmentButton = this.$widget.find("[data-trigger-command='openAttachment']"); |             const $openAttachmentButton = this.$widget.find("[data-trigger-command='openAttachment']"); | ||||||
|             $openAttachmentButton |             $openAttachmentButton | ||||||
|                 .addClass("disabled") |                 .addClass("disabled") | ||||||
|                 .append($('<span class="disabled-tooltip"> (?)</span>') |                 .append($('<span class="bx bx-info-circle disabled-tooltip" />') | ||||||
|                     .attr("title", t('attachments_actions.open_externally_detail_page')) |                     .attr("title", t('attachments_actions.open_externally_detail_page')) | ||||||
|                 ); |                 ); | ||||||
|             if (isElectron) { |             if (isElectron) { | ||||||
|                 const $openAttachmentCustomButton = this.$widget.find("[data-trigger-command='openAttachmentCustom']"); |                 const $openAttachmentCustomButton = this.$widget.find("[data-trigger-command='openAttachmentCustom']"); | ||||||
|                 $openAttachmentCustomButton |                 $openAttachmentCustomButton | ||||||
|                     .addClass("disabled") |                     .addClass("disabled") | ||||||
|                     .append($('<span class="disabled-tooltip"> (?)</span>') |                     .append($('<span class="bx bx-info-circle disabled-tooltip" />') | ||||||
|                         .attr("title", t('attachments_actions.open_externally_detail_page')) |                         .attr("title", t('attachments_actions.open_externally_detail_page')) | ||||||
|                     ); |                     ); | ||||||
|             } |             } | ||||||
| @ -99,7 +129,7 @@ export default class AttachmentActionsWidget extends BasicWidget { | |||||||
|             const $openAttachmentCustomButton = this.$widget.find("[data-trigger-command='openAttachmentCustom']"); |             const $openAttachmentCustomButton = this.$widget.find("[data-trigger-command='openAttachmentCustom']"); | ||||||
|             $openAttachmentCustomButton |             $openAttachmentCustomButton | ||||||
|                 .addClass("disabled") |                 .addClass("disabled") | ||||||
|                 .append($('<span class="disabled-tooltip"> (?)</span>') |                 .append($('<span class="bx bx-info-circle disabled-tooltip" />') | ||||||
|                     .attr("title", t('attachments_actions.open_custom_client_only')) |                     .attr("title", t('attachments_actions.open_custom_client_only')) | ||||||
|                 ); |                 ); | ||||||
|         } |         } | ||||||
|  | |||||||
| @ -5,7 +5,7 @@ import UpdateAvailableWidget from "./update_available.js"; | |||||||
| import options from "../../services/options.js"; | import options from "../../services/options.js"; | ||||||
| 
 | 
 | ||||||
| const TPL = ` | const TPL = ` | ||||||
| <div class="dropdown global-menu dropend"> | <div class="dropdown global-menu"> | ||||||
|     <style> |     <style> | ||||||
|     .global-menu { |     .global-menu { | ||||||
|         width: 53px; |         width: 53px; | ||||||
| @ -100,53 +100,31 @@ const TPL = ` | |||||||
|         position: relative; |         position: relative; | ||||||
|         left: 0; |         left: 0; | ||||||
|         top: 5px; |         top: 5px; | ||||||
|  |         --dropdown-shadow-opacity: 0; | ||||||
|  |         --submenu-opening-delay: 0; | ||||||
|     } |     } | ||||||
|     </style>     |     </style>     | ||||||
| 
 | 
 | ||||||
|     <button type="button" data-bs-toggle="dropdown" aria-haspopup="true" |     <button type="button" data-bs-toggle="dropdown" aria-haspopup="true" | ||||||
|             aria-expanded="false" class="icon-action global-menu-button"> |             aria-expanded="false" class="icon-action global-menu-button"> | ||||||
|         <svg viewBox="0 0 256 256" data-bs-toggle="tooltip" title="${t('global_menu.menu')}"> |  | ||||||
|             <g> |  | ||||||
|                 <path class="st0" d="m202.9 112.7c-22.5 16.1-54.5 12.8-74.9 6.3l14.8-11.8 14.1-11.3 49.1-39.3-51.2 35.9-14.3 10-14.9 10.5c0.7-21.2 7-49.9 28.6-65.4 1.8-1.3 3.9-2.6 6.1-3.8 2.7-1.5 5.7-2.9 8.8-4.1 27.1-11.1 68.5-15.3 85.2-9.5 0.1 16.2-15.9 45.4-33.9 65.9-2.4 2.8-4.9 5.4-7.4 7.8-3.4 3.5-6.8 6.4-10.1 8.8z"/> |  | ||||||
|                 <path class="st1" d="m213.1 104c-22.2 12.6-51.4 9.3-70.3 3.2l14.1-11.3 49.1-39.3-51.2 35.9-14.3 10c0.5-18.1 4.9-42.1 19.7-58.6 2.7-1.5 5.7-2.9 8.8-4.1 27.1-11.1 68.5-15.3 85.2-9.5 0.1 16.2-15.9 45.4-33.9 65.9-2.3 2.8-4.8 5.4-7.2 7.8z"/> |  | ||||||
|                 <path class="st2" d="m220.5 96.2c-21.1 8.6-46.6 5.3-63.7-0.2l49.2-39.4-51.2 35.9c0.3-15.8 3.5-36.6 14.3-52.8 27.1-11.1 68.5-15.3 85.2-9.5 0.1 16.2-15.9 45.4-33.8 66z"/> |  | ||||||
|              |  | ||||||
|                 <path class="st3" d="m106.7 179c-5.8-21 5.2-43.8 15.5-57.2l4.8 14.2 4.5 13.4 15.9 47-12.8-47.6-3.6-13.2-3.7-13.9c15.5 6.2 35.1 18.6 40.7 38.8 0.5 1.7 0.9 3.6 1.2 5.5 0.4 2.4 0.6 5 0.7 7.7 0.9 23.1-7.1 54.9-15.9 65.7-12-4.3-29.3-24-39.7-42.8-1.4-2.6-2.7-5.1-3.8-7.6-1.6-3.5-2.9-6.8-3.8-10z"/> |  | ||||||
|                 <path class="st4" d="m110.4 188.9c-3.4-19.8 6.9-40.5 16.6-52.9l4.5 13.4 15.9 47-12.8-47.6-3.6-13.2c13.3 5.2 29.9 15 38.1 30.4 0.4 2.4 0.6 5 0.7 7.7 0.9 23.1-7.1 54.9-15.9 65.7-12-4.3-29.3-24-39.7-42.8-1.4-2.6-2.7-5.2-3.8-7.7z"/> |  | ||||||
|                 <path class="st5" d="m114.2 196.5c-0.7-18 8.6-35.9 17.3-47.1l15.9 47-12.8-47.6c11.6 4.4 26.1 12.4 35.2 24.8 0.9 23.1-7.1 54.9-15.9 65.7-12-4.3-29.3-24-39.7-42.8z"/> |  | ||||||
|      |  | ||||||
|                 <path class="st6" d="m86.3 59.1c21.7 10.9 32.4 36.6 35.8 54.9l-15.2-6.6-14.5-6.3-50.6-22 48.8 24.9 13.6 6.9 14.3 7.3c-16.6 7.9-41.3 14.5-62.1 4.1-1.8-0.9-3.6-1.9-5.4-3.2-2.3-1.5-4.5-3.2-6.8-5.1-19.9-16.4-40.3-46.4-42.7-61.5 12.4-6.5 41.5-5.8 64.8-0.3 3.2 0.8 6.2 1.6 9.1 2.5 4 1.3 7.6 2.8 10.9 4.4z"/> |  | ||||||
|                 <path class="st7" d="m75.4 54.8c18.9 12 28.4 35.6 31.6 52.6l-14.5-6.3-50.6-22 48.7 24.9 13.6 6.9c-14.1 6.8-34.5 13-53.3 8.2-2.3-1.5-4.5-3.2-6.8-5.1-19.8-16.4-40.2-46.4-42.6-61.5 12.4-6.5 41.5-5.8 64.8-0.3 3.1 0.8 6.2 1.6 9.1 2.6z"/> |  | ||||||
|                 <path class="st8" d="m66.3 52.2c15.3 12.8 23.3 33.6 26.1 48.9l-50.6-22 48.8 24.9c-12.2 6-29.6 11.8-46.5 10-19.8-16.4-40.2-46.4-42.6-61.5 12.4-6.5 41.5-5.8 64.8-0.3z"/> |  | ||||||
|             </g> |  | ||||||
|         </svg> |  | ||||||
| 
 |  | ||||||
|         <div class="global-menu-button-update-available"></div> |         <div class="global-menu-button-update-available"></div> | ||||||
|     </button> |     </button> | ||||||
| 
 | 
 | ||||||
|     <ul class="dropdown-menu dropdown-menu-right"> |     <ul class="dropdown-menu dropdown-menu-right"> | ||||||
|         <li class="dropdown-item" data-trigger-command="showOptions"> |  | ||||||
|             <span class="bx bx-cog"></span> |  | ||||||
|             ${t('global_menu.options')} |  | ||||||
|         </li> |  | ||||||
| 
 |  | ||||||
|         <li class="dropdown-item" data-trigger-command="openNewWindow"> |         <li class="dropdown-item" data-trigger-command="openNewWindow"> | ||||||
|             <span class="bx bx-window-open"></span> |             <span class="bx bx-window-open"></span> | ||||||
|             ${t('global_menu.open_new_window')} |             ${t('global_menu.open_new_window')} | ||||||
|             <kbd data-command="openNewWindow"></kbd> |             <kbd data-command="openNewWindow"></kbd> | ||||||
|         </li> |         </li> | ||||||
| 
 | 
 | ||||||
|         <li class="dropdown-item switch-to-mobile-version-button" data-trigger-command="switchToMobileVersion"> |         <li class="dropdown-item" data-trigger-command="showShareSubtree"> | ||||||
|             <span class="bx bx-mobile"></span> |             <span class="bx bx-share-alt"></span> | ||||||
|             ${t('global_menu.switch_to_mobile_version')} |             ${t('global_menu.show_shared_notes_subtree')} | ||||||
|         </li> |         </li> | ||||||
|          | 
 | ||||||
|         <li class="dropdown-item switch-to-desktop-version-button" data-trigger-command="switchToDesktopVersion"> |         <div class="dropdown-divider"></div> | ||||||
|             <span class="bx bx-desktop"></span> | 
 | ||||||
|             ${t('global_menu.switch_to_desktop_version')} |         <span class="zoom-container dropdown-item dropdown-item-container"> | ||||||
|         </li> |  | ||||||
|          |  | ||||||
|         <span class="zoom-container dropdown-item"> |  | ||||||
|             <div> |             <div> | ||||||
|                 <span class="bx bx-empty"></span> |                 <span class="bx bx-empty"></span> | ||||||
|                 ${t('global_menu.zoom')} |                 ${t('global_menu.zoom')} | ||||||
| @ -165,16 +143,23 @@ const TPL = ` | |||||||
|             </div> |             </div> | ||||||
|         </span> |         </span> | ||||||
| 
 | 
 | ||||||
|  |         <div class="dropdown-divider zoom-container-separator"></div> | ||||||
|  | 
 | ||||||
|  |         <li class="dropdown-item switch-to-mobile-version-button" data-trigger-command="switchToMobileVersion"> | ||||||
|  |             <span class="bx bx-mobile"></span> | ||||||
|  |             ${t('global_menu.switch_to_mobile_version')} | ||||||
|  |         </li> | ||||||
|  | 
 | ||||||
|  |         <li class="dropdown-item switch-to-desktop-version-button" data-trigger-command="switchToDesktopVersion"> | ||||||
|  |             <span class="bx bx-desktop"></span> | ||||||
|  |             ${t('global_menu.switch_to_desktop_version')} | ||||||
|  |         </li> | ||||||
|  | 
 | ||||||
|         <li class="dropdown-item" data-trigger-command="showLaunchBarSubtree"> |         <li class="dropdown-item" data-trigger-command="showLaunchBarSubtree"> | ||||||
|             <span class="bx bx-sidebar"></span> |             <span class="bx bx-sidebar"></span> | ||||||
|             ${t('global_menu.configure_launchbar')} |             ${t('global_menu.configure_launchbar')} | ||||||
|         </li> |         </li> | ||||||
|          |          | ||||||
|         <li class="dropdown-item" data-trigger-command="showShareSubtree"> |  | ||||||
|             <span class="bx bx-share-alt"></span> |  | ||||||
|             ${t('global_menu.show_shared_notes_subtree')} |  | ||||||
|         </li> |  | ||||||
|          |  | ||||||
|         <li class="dropdown-item dropdown-submenu"> |         <li class="dropdown-item dropdown-submenu"> | ||||||
|             <span class="dropdown-toggle"> |             <span class="dropdown-toggle"> | ||||||
|                 <span class="bx bx-chip"></span> |                 <span class="bx bx-chip"></span> | ||||||
| @ -182,10 +167,22 @@ const TPL = ` | |||||||
|             </span> |             </span> | ||||||
|              |              | ||||||
|             <ul class="dropdown-menu"> |             <ul class="dropdown-menu"> | ||||||
|                 <li class="dropdown-item open-dev-tools-button" data-trigger-command="openDevTools"> |                 <li class="dropdown-item" data-trigger-command="showHiddenSubtree"> | ||||||
|                     <span class="bx bx-bug-alt"></span> |                     <span class="bx bx-hide"></span> | ||||||
|                     ${t('global_menu.open_dev_tools')} |                     ${t('global_menu.show_hidden_subtree')} | ||||||
|                     <kbd data-command="openDevTools"></kbd> |                 </li> | ||||||
|  | 
 | ||||||
|  |                 <li class="dropdown-item" data-trigger-command="showSearchHistory"> | ||||||
|  |                     <span class="bx bx-search-alt"></span> | ||||||
|  |                     ${t('global_menu.open_search_history')} | ||||||
|  |                 </li> | ||||||
|  | 
 | ||||||
|  |                 <div class="dropdown-divider"></div> | ||||||
|  | 
 | ||||||
|  |                 <li class="dropdown-item" data-trigger-command="showBackendLog"> | ||||||
|  |                     <span class="bx bx-detail"></span> | ||||||
|  |                     ${t('global_menu.show_backend_log')} | ||||||
|  |                     <kbd data-command="showBackendLog"></kbd> | ||||||
|                 </li> |                 </li> | ||||||
|          |          | ||||||
|                 <li class="dropdown-item" data-trigger-command="showSQLConsole"> |                 <li class="dropdown-item" data-trigger-command="showSQLConsole"> | ||||||
| @ -198,16 +195,13 @@ const TPL = ` | |||||||
|                     <span class="bx bx-data"></span> |                     <span class="bx bx-data"></span> | ||||||
|                     ${t('global_menu.open_sql_console_history')} |                     ${t('global_menu.open_sql_console_history')} | ||||||
|                 </li> |                 </li> | ||||||
|                  | 
 | ||||||
|                 <li class="dropdown-item" data-trigger-command="showSearchHistory"> |                 <div class="dropdown-divider"></div> | ||||||
|                     <span class="bx bx-search-alt"></span> | 
 | ||||||
|                     ${t('global_menu.open_search_history')} |                 <li class="dropdown-item open-dev-tools-button" data-trigger-command="openDevTools"> | ||||||
|                 </li> |                     <span class="bx bx-bug-alt"></span> | ||||||
|          |                     ${t('global_menu.open_dev_tools')} | ||||||
|                 <li class="dropdown-item" data-trigger-command="showBackendLog"> |                     <kbd data-command="openDevTools"></kbd> | ||||||
|                     <span class="bx bx-detail"></span> |  | ||||||
|                     ${t('global_menu.show_backend_log')} |  | ||||||
|                     <kbd data-command="showBackendLog"></kbd> |  | ||||||
|                 </li> |                 </li> | ||||||
|                  |                  | ||||||
|                 <li class="dropdown-item" data-trigger-command="reloadFrontendApp"  |                 <li class="dropdown-item" data-trigger-command="reloadFrontendApp"  | ||||||
| @ -217,13 +211,16 @@ const TPL = ` | |||||||
|                     <kbd data-command="reloadFrontendApp"></kbd> |                     <kbd data-command="reloadFrontendApp"></kbd> | ||||||
|                 </li> |                 </li> | ||||||
|                  |                  | ||||||
|                 <li class="dropdown-item" data-trigger-command="showHiddenSubtree"> |  | ||||||
|                     <span class="bx bx-hide"></span> |  | ||||||
|                     ${t('global_menu.show_hidden_subtree')} |  | ||||||
|                 </li> |  | ||||||
|             </ul> |             </ul> | ||||||
|         </li> |         </li> | ||||||
| 
 | 
 | ||||||
|  |         <li class="dropdown-item" data-trigger-command="showOptions"> | ||||||
|  |             <span class="bx bx-cog"></span> | ||||||
|  |             ${t('global_menu.options')} | ||||||
|  |         </li> | ||||||
|  | 
 | ||||||
|  |         <div class="dropdown-divider desktop-only"></div> | ||||||
|  | 
 | ||||||
|         <li class="dropdown-item show-help-button" data-trigger-command="showHelp"> |         <li class="dropdown-item show-help-button" data-trigger-command="showHelp"> | ||||||
|             <span class="bx bx-help-circle"></span> |             <span class="bx bx-help-circle"></span> | ||||||
|             ${t('global_menu.show_help')} |             ${t('global_menu.show_help')} | ||||||
| @ -241,6 +238,8 @@ const TPL = ` | |||||||
|             <span class="version-text"></span> |             <span class="version-text"></span> | ||||||
|         </li> |         </li> | ||||||
| 
 | 
 | ||||||
|  |         <div class="dropdown-divider logout-button-separator"></div> | ||||||
|  | 
 | ||||||
|         <li class="dropdown-item logout-button" data-trigger-command="logout"> |         <li class="dropdown-item logout-button" data-trigger-command="logout"> | ||||||
|             <span class="bx bx-log-out"></span> |             <span class="bx bx-log-out"></span> | ||||||
|             ${t('global_menu.logout')} |             ${t('global_menu.logout')} | ||||||
| @ -250,24 +249,54 @@ const TPL = ` | |||||||
| `;
 | `;
 | ||||||
| 
 | 
 | ||||||
| export default class GlobalMenuWidget extends BasicWidget { | export default class GlobalMenuWidget extends BasicWidget { | ||||||
|     constructor() { |     constructor(isHorizontalLayout) { | ||||||
|         super(); |         super(); | ||||||
| 
 | 
 | ||||||
|         this.updateAvailableWidget = new UpdateAvailableWidget(); |         this.updateAvailableWidget = new UpdateAvailableWidget(); | ||||||
|  |         this.isHorizontalLayout = isHorizontalLayout;         | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     doRender() { |     doRender() { | ||||||
|         this.$widget = $(TPL); |         this.$widget = $(TPL); | ||||||
| 
 | 
 | ||||||
|         this.dropdown = bootstrap.Dropdown.getOrCreateInstance(this.$widget.find("[data-bs-toggle='dropdown']")); |         if (!this.isHorizontalLayout) { | ||||||
|  |             this.$widget.addClass("dropend"); | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|         this.tooltip = new bootstrap.Tooltip(this.$widget.find("[data-bs-toggle='tooltip']"), { trigger: "hover" }); |         const $globalMenuButton = this.$widget.find(".global-menu-button") | ||||||
|  |         if (!this.isHorizontalLayout) { | ||||||
|  |             $globalMenuButton.prepend($(`\
 | ||||||
|  |                 <svg viewBox="0 0 256 256" data-bs-toggle="tooltip" title="${t('global_menu.menu')}"> | ||||||
|  |                     <g> | ||||||
|  |                         <path class="st0" d="m202.9 112.7c-22.5 16.1-54.5 12.8-74.9 6.3l14.8-11.8 14.1-11.3 49.1-39.3-51.2 35.9-14.3 10-14.9 10.5c0.7-21.2 7-49.9 28.6-65.4 1.8-1.3 3.9-2.6 6.1-3.8 2.7-1.5 5.7-2.9 8.8-4.1 27.1-11.1 68.5-15.3 85.2-9.5 0.1 16.2-15.9 45.4-33.9 65.9-2.4 2.8-4.9 5.4-7.4 7.8-3.4 3.5-6.8 6.4-10.1 8.8z"/> | ||||||
|  |                         <path class="st1" d="m213.1 104c-22.2 12.6-51.4 9.3-70.3 3.2l14.1-11.3 49.1-39.3-51.2 35.9-14.3 10c0.5-18.1 4.9-42.1 19.7-58.6 2.7-1.5 5.7-2.9 8.8-4.1 27.1-11.1 68.5-15.3 85.2-9.5 0.1 16.2-15.9 45.4-33.9 65.9-2.3 2.8-4.8 5.4-7.2 7.8z"/> | ||||||
|  |                         <path class="st2" d="m220.5 96.2c-21.1 8.6-46.6 5.3-63.7-0.2l49.2-39.4-51.2 35.9c0.3-15.8 3.5-36.6 14.3-52.8 27.1-11.1 68.5-15.3 85.2-9.5 0.1 16.2-15.9 45.4-33.8 66z"/> | ||||||
|  |                      | ||||||
|  |                         <path class="st3" d="m106.7 179c-5.8-21 5.2-43.8 15.5-57.2l4.8 14.2 4.5 13.4 15.9 47-12.8-47.6-3.6-13.2-3.7-13.9c15.5 6.2 35.1 18.6 40.7 38.8 0.5 1.7 0.9 3.6 1.2 5.5 0.4 2.4 0.6 5 0.7 7.7 0.9 23.1-7.1 54.9-15.9 65.7-12-4.3-29.3-24-39.7-42.8-1.4-2.6-2.7-5.1-3.8-7.6-1.6-3.5-2.9-6.8-3.8-10z"/> | ||||||
|  |                         <path class="st4" d="m110.4 188.9c-3.4-19.8 6.9-40.5 16.6-52.9l4.5 13.4 15.9 47-12.8-47.6-3.6-13.2c13.3 5.2 29.9 15 38.1 30.4 0.4 2.4 0.6 5 0.7 7.7 0.9 23.1-7.1 54.9-15.9 65.7-12-4.3-29.3-24-39.7-42.8-1.4-2.6-2.7-5.2-3.8-7.7z"/> | ||||||
|  |                         <path class="st5" d="m114.2 196.5c-0.7-18 8.6-35.9 17.3-47.1l15.9 47-12.8-47.6c11.6 4.4 26.1 12.4 35.2 24.8 0.9 23.1-7.1 54.9-15.9 65.7-12-4.3-29.3-24-39.7-42.8z"/> | ||||||
|  |              | ||||||
|  |                         <path class="st6" d="m86.3 59.1c21.7 10.9 32.4 36.6 35.8 54.9l-15.2-6.6-14.5-6.3-50.6-22 48.8 24.9 13.6 6.9 14.3 7.3c-16.6 7.9-41.3 14.5-62.1 4.1-1.8-0.9-3.6-1.9-5.4-3.2-2.3-1.5-4.5-3.2-6.8-5.1-19.9-16.4-40.3-46.4-42.7-61.5 12.4-6.5 41.5-5.8 64.8-0.3 3.2 0.8 6.2 1.6 9.1 2.5 4 1.3 7.6 2.8 10.9 4.4z"/> | ||||||
|  |                         <path class="st7" d="m75.4 54.8c18.9 12 28.4 35.6 31.6 52.6l-14.5-6.3-50.6-22 48.7 24.9 13.6 6.9c-14.1 6.8-34.5 13-53.3 8.2-2.3-1.5-4.5-3.2-6.8-5.1-19.8-16.4-40.2-46.4-42.6-61.5 12.4-6.5 41.5-5.8 64.8-0.3 3.1 0.8 6.2 1.6 9.1 2.6z"/> | ||||||
|  |                         <path class="st8" d="m66.3 52.2c15.3 12.8 23.3 33.6 26.1 48.9l-50.6-22 48.8 24.9c-12.2 6-29.6 11.8-46.5 10-19.8-16.4-40.2-46.4-42.6-61.5 12.4-6.5 41.5-5.8 64.8-0.3z"/> | ||||||
|  |                     </g> | ||||||
|  |                 </svg>`)); | ||||||
|  |             this.tooltip = new bootstrap.Tooltip(this.$widget.find("[data-bs-toggle='tooltip']"), { trigger: "hover" }); | ||||||
|  |         } else { | ||||||
|  |             $globalMenuButton.toggleClass("bx bx-menu"); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         this.dropdown = bootstrap.Dropdown.getOrCreateInstance(this.$widget.find("[data-bs-toggle='dropdown']"), { | ||||||
|  |             alignment: "bottom" | ||||||
|  |         }); | ||||||
| 
 | 
 | ||||||
|         this.$widget.find(".show-about-dialog-button").on('click', () => this.triggerCommand("openAboutDialog")); |         this.$widget.find(".show-about-dialog-button").on('click', () => this.triggerCommand("openAboutDialog")); | ||||||
| 
 | 
 | ||||||
|         const isElectron = utils.isElectron(); |         const isElectron = utils.isElectron(); | ||||||
| 
 | 
 | ||||||
|         this.$widget.find(".logout-button").toggle(!isElectron); |         this.$widget.find(".logout-button").toggle(!isElectron); | ||||||
|  |         this.$widget.find(".logout-button-separator").toggle(!isElectron); | ||||||
|  | 
 | ||||||
|         this.$widget.find(".open-dev-tools-button").toggle(isElectron); |         this.$widget.find(".open-dev-tools-button").toggle(isElectron); | ||||||
|         this.$widget.find(".switch-to-mobile-version-button").toggle(!isElectron && utils.isDesktop()); |         this.$widget.find(".switch-to-mobile-version-button").toggle(!isElectron && utils.isDesktop()); | ||||||
|         this.$widget.find(".switch-to-desktop-version-button").toggle(!isElectron && utils.isMobile()); |         this.$widget.find(".switch-to-desktop-version-button").toggle(!isElectron && utils.isMobile()); | ||||||
| @ -283,7 +312,7 @@ export default class GlobalMenuWidget extends BasicWidget { | |||||||
|             if ($(e.target).children(".dropdown-menu").length === 1 || $(e.target).hasClass('dropdown-toggle')) { |             if ($(e.target).children(".dropdown-menu").length === 1 || $(e.target).hasClass('dropdown-toggle')) { | ||||||
|                 e.stopPropagation(); |                 e.stopPropagation(); | ||||||
|             } |             } | ||||||
|         }) |         })         | ||||||
| 
 | 
 | ||||||
|         this.$widget.find(".global-menu-button-update-available").append( |         this.$widget.find(".global-menu-button-update-available").append( | ||||||
|             this.updateAvailableWidget.render() |             this.updateAvailableWidget.render() | ||||||
| @ -293,15 +322,20 @@ export default class GlobalMenuWidget extends BasicWidget { | |||||||
| 
 | 
 | ||||||
|         if (!utils.isElectron()) { |         if (!utils.isElectron()) { | ||||||
|             this.$widget.find(".zoom-container").hide(); |             this.$widget.find(".zoom-container").hide(); | ||||||
|  |             this.$widget.find(".zoom-container-separator").hide(); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         this.$zoomState = this.$widget.find(".zoom-state"); |         this.$zoomState = this.$widget.find(".zoom-state"); | ||||||
|         this.$widget.on('show.bs.dropdown', () => { |         this.$widget.on('show.bs.dropdown', () => { | ||||||
|             this.updateZoomState(); |             this.updateZoomState(); | ||||||
|             this.tooltip.hide(); |             if (this.tooltip) { | ||||||
|             this.tooltip.disable(); |                 this.tooltip.hide(); | ||||||
|  |                 this.tooltip.disable(); | ||||||
|  |             } | ||||||
|         }); |         }); | ||||||
|         this.$widget.on('hide.bs.dropdown', () => this.tooltip.enable()); |         if (this.tooltip) { | ||||||
|  |             this.$widget.on('hide.bs.dropdown', () => this.tooltip.enable()); | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|         this.$widget.find(".zoom-buttons").on("click", |         this.$widget.find(".zoom-buttons").on("click", | ||||||
|             // delay to wait for the actual zoom change
 |             // delay to wait for the actual zoom change
 | ||||||
|  | |||||||
| @ -4,7 +4,7 @@ import CommandButtonWidget from "./command_button.js"; | |||||||
| import { t } from "../../services/i18n.js"; | import { t } from "../../services/i18n.js"; | ||||||
| 
 | 
 | ||||||
| export default class LeftPaneToggleWidget extends CommandButtonWidget { | export default class LeftPaneToggleWidget extends CommandButtonWidget { | ||||||
|     constructor() { |     constructor(isHorizontalLayout) { | ||||||
|         super(); |         super(); | ||||||
| 
 | 
 | ||||||
|         this.class("launcher-button"); |         this.class("launcher-button"); | ||||||
| @ -20,6 +20,10 @@ export default class LeftPaneToggleWidget extends CommandButtonWidget { | |||||||
|         this.settings.command = () => options.is('leftPaneVisible') |         this.settings.command = () => options.is('leftPaneVisible') | ||||||
|             ? "hideLeftPane" |             ? "hideLeftPane" | ||||||
|             : "showLeftPane"; |             : "showLeftPane"; | ||||||
|  | 
 | ||||||
|  |         if (isHorizontalLayout) { | ||||||
|  |             this.settings.titlePlacement = "bottom"; | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     refreshIcon() { |     refreshIcon() { | ||||||
|  | |||||||
| @ -11,42 +11,91 @@ import { t } from "../../services/i18n.js"; | |||||||
| const TPL = ` | const TPL = ` | ||||||
| <div class="dropdown note-actions"> | <div class="dropdown note-actions"> | ||||||
|     <style> |     <style> | ||||||
|     .note-actions { |         .note-actions { | ||||||
|         width: 35px; |             width: 35px; | ||||||
|         height: 35px; |             height: 35px; | ||||||
|     }   |         } | ||||||
|      | 
 | ||||||
|     .note-actions .dropdown-menu { |         .note-actions .dropdown-menu { | ||||||
|         min-width: 15em; |             min-width: 15em; | ||||||
|     } |         } | ||||||
|      | 
 | ||||||
|     .note-actions .dropdown-item[disabled], .note-actions .dropdown-item[disabled]:hover { |         .note-actions .dropdown-item .bx { | ||||||
|         color: var(--muted-text-color) !important; |             position: relative; | ||||||
|         background-color: transparent !important; |             top: 3px; | ||||||
|         pointer-events: none; /* makes it unclickable */ |             font-size: 120%; | ||||||
|     } |             margin-right: 5px; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         .note-actions .dropdown-item[disabled], .note-actions .dropdown-item[disabled]:hover { | ||||||
|  |             color: var(--muted-text-color) !important; | ||||||
|  |             background-color: transparent !important; | ||||||
|  |             pointer-events: none; /* makes it unclickable */ | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|     </style> |     </style> | ||||||
| 
 | 
 | ||||||
|     <button type="button" data-bs-toggle="dropdown" aria-haspopup="true"  |     <button type="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false" | ||||||
|         aria-expanded="false" class="icon-action bx bx-dots-vertical-rounded"></button> |       class="icon-action bx bx-dots-vertical-rounded"></button> | ||||||
| 
 | 
 | ||||||
|     <div class="dropdown-menu dropdown-menu-right"> |     <div class="dropdown-menu dropdown-menu-right"> | ||||||
|         <a data-trigger-command="convertNoteIntoAttachment" class="dropdown-item">${t('note_actions.convert_into_attachment')}</a> |         <li data-trigger-command="convertNoteIntoAttachment" class="dropdown-item"> | ||||||
|         <a data-trigger-command="renderActiveNote" class="dropdown-item render-note-button"><kbd data-command="renderActiveNote"></kbd> ${t('note_actions.re_render_note')}</a> |             <span class="bx bx-paperclip"></span> ${t('note_actions.convert_into_attachment')} | ||||||
|         <a data-trigger-command="findInText" class="dropdown-item find-in-text-button">${t('note_actions.search_in_note')} <kbd data-command="findInText"></kbd></a> |         </li> | ||||||
|         <a data-trigger-command="showNoteSource" class="dropdown-item show-source-button"><kbd data-command="showNoteSource"></kbd> ${t('note_actions.note_source')}</a> |          | ||||||
|         <a data-trigger-command="showAttachments" class="dropdown-item show-attachments-button"><kbd data-command="showAttachments"></kbd> ${t('note_actions.note_attachments')}</a> |         <li data-trigger-command="renderActiveNote" class="dropdown-item render-note-button"> | ||||||
|         <a data-trigger-command="openNoteExternally" class="dropdown-item open-note-externally-button" |             <span class="bx bx-extension"></span> ${t('note_actions.re_render_note')}<kbd data-command="renderActiveNote"></kbd> | ||||||
|            title="${t('note_actions.open_note_externally_title')}"> |         </li> | ||||||
|             <kbd data-command="openNoteExternally"></kbd>  | 
 | ||||||
|             ${t('note_actions.open_note_externally')} |         <li data-trigger-command="findInText" class="dropdown-item find-in-text-button"> | ||||||
|         </a> |             <span class='bx bx-search'></span> ${t('note_actions.search_in_note')}<kbd data-command="findInText"></kbd> | ||||||
|         <a data-trigger-command="openNoteCustom" class="dropdown-item open-note-custom-button"><kbd data-command="openNoteCustom"></kbd> ${t('note_actions.open_note_custom')}</a> |         </li> | ||||||
|         <a class="dropdown-item import-files-button">${t('note_actions.import_files')}</a> | 
 | ||||||
|         <a class="dropdown-item export-note-button">${t('note_actions.export_note')}</a> |         <li data-trigger-command="printActiveNote" class="dropdown-item print-active-note-button"> | ||||||
|         <a class="dropdown-item delete-note-button">${t('note_actions.delete_note')}</a> |             <span class="bx bx-printer"></span> ${t('note_actions.print_note')}<kbd data-command="printActiveNote"></kbd></li> | ||||||
|         <a data-trigger-command="printActiveNote" class="dropdown-item print-active-note-button"><kbd data-command="printActiveNote"></kbd> ${t('note_actions.print_note')}</a> | 
 | ||||||
|         <a data-trigger-command="forceSaveRevision" class="dropdown-item save-revision-button"><kbd data-command="forceSaveRevision"></kbd> ${t('note_actions.save_revision')}</a> |          | ||||||
|  |         <div class="dropdown-divider"></div> | ||||||
|  | 
 | ||||||
|  |          | ||||||
|  |         <li class="dropdown-item import-files-button"><span class="bx bx-import"></span> ${t('note_actions.import_files')}</li> | ||||||
|  | 
 | ||||||
|  |         <li class="dropdown-item export-note-button"><span class="bx bx-export"></span> ${t('note_actions.export_note')}</li> | ||||||
|  | 
 | ||||||
|  |          | ||||||
|  |         <div class="dropdown-divider"></div> | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |         <li data-trigger-command="openNoteExternally" class="dropdown-item open-note-externally-button" title="${t('note_actions.open_note_externally_title')}"> | ||||||
|  |             <span class="bx bx-file-find"></span> ${t('note_actions.open_note_externally')}<kbd data-command="openNoteExternally"></kbd> | ||||||
|  |         </li> | ||||||
|  | 
 | ||||||
|  |         <li data-trigger-command="openNoteCustom" class="dropdown-item open-note-custom-button"> | ||||||
|  |             <span class="bx bx-customize"></span> ${t('note_actions.open_note_custom')}<kbd data-command="openNoteCustom"></kbd> | ||||||
|  |         </li> | ||||||
|  | 
 | ||||||
|  |         <li data-trigger-command="showNoteSource" class="dropdown-item show-source-button"> | ||||||
|  |             <span class="bx bx-code"></span> ${t('note_actions.note_source')}<kbd data-command="showNoteSource"></kbd> | ||||||
|  |         </li> | ||||||
|  | 
 | ||||||
|  |          | ||||||
|  |         <div class="dropdown-divider"></div> | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |         <li data-trigger-command="forceSaveRevision" class="dropdown-item save-revision-button"> | ||||||
|  |             <span class="bx bx-save"></span> ${t('note_actions.save_revision')}<kbd data-command="forceSaveRevision"></kbd> | ||||||
|  |         </li> | ||||||
|  | 
 | ||||||
|  |         <li class="dropdown-item delete-note-button"><span class="bx bx-trash destructive-action-icon"></span> ${t('note_actions.delete_note')}</li> | ||||||
|  | 
 | ||||||
|  |          | ||||||
|  |         <div class="dropdown-divider"></div> | ||||||
|  | 
 | ||||||
|  |          | ||||||
|  |         <li data-trigger-command="showAttachments" class="dropdown-item show-attachments-button"> | ||||||
|  |             <span class="bx bx-paperclip"></span> ${t('note_actions.note_attachments')}<kbd data-command="showAttachments"></kbd> | ||||||
|  |         </li> | ||||||
|     </div> |     </div> | ||||||
| </div>`; | </div>`; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -2,13 +2,7 @@ import BasicWidget from "../basic_widget.js"; | |||||||
| 
 | 
 | ||||||
| const TPL = ` | const TPL = ` | ||||||
| <div class="dropdown right-dropdown-widget dropend"> | <div class="dropdown right-dropdown-widget dropend"> | ||||||
|     <style> |     <button type="button" data-bs-toggle="dropdown" | ||||||
|     .right-dropdown-widget { |  | ||||||
|         height: 53px; |  | ||||||
|     } |  | ||||||
|     </style> |  | ||||||
| 
 |  | ||||||
|     <button type="button" data-bs-toggle="dropdown" data-placement="right" |  | ||||||
|             aria-haspopup="true" aria-expanded="false"  |             aria-haspopup="true" aria-expanded="false"  | ||||||
|             class="bx right-dropdown-button launcher-button"></button> |             class="bx right-dropdown-button launcher-button"></button> | ||||||
|      |      | ||||||
| @ -25,6 +19,10 @@ export default class RightDropdownButtonWidget extends BasicWidget { | |||||||
|         this.iconClass = iconClass; |         this.iconClass = iconClass; | ||||||
|         this.title = title; |         this.title = title; | ||||||
|         this.dropdownTpl = dropdownTpl; |         this.dropdownTpl = dropdownTpl; | ||||||
|  | 
 | ||||||
|  |         this.settings = { | ||||||
|  |             titlePlacement: "right"     | ||||||
|  |         }; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     doRender() { |     doRender() { | ||||||
| @ -33,7 +31,10 @@ export default class RightDropdownButtonWidget extends BasicWidget { | |||||||
|         this.dropdown = bootstrap.Dropdown.getOrCreateInstance(this.$widget.find("[data-bs-toggle='dropdown']")); |         this.dropdown = bootstrap.Dropdown.getOrCreateInstance(this.$widget.find("[data-bs-toggle='dropdown']")); | ||||||
| 
 | 
 | ||||||
|         this.$tooltip = this.$widget.find(".tooltip-trigger").attr("title", this.title); |         this.$tooltip = this.$widget.find(".tooltip-trigger").attr("title", this.title); | ||||||
|         this.tooltip = new bootstrap.Tooltip(this.$tooltip); |         this.tooltip = new bootstrap.Tooltip(this.$tooltip, { | ||||||
|  |             placement: this.settings.titlePlacement, | ||||||
|  |             fallbackPlacements: [ this.settings.titlePlacement ] | ||||||
|  |         }); | ||||||
| 
 | 
 | ||||||
|         this.$widget.find(".right-dropdown-button") |         this.$widget.find(".right-dropdown-button") | ||||||
|             .addClass(this.iconClass) |             .addClass(this.iconClass) | ||||||
|  | |||||||
| @ -10,12 +10,14 @@ import CommandButtonWidget from "../buttons/command_button.js"; | |||||||
| import utils from "../../services/utils.js"; | import utils from "../../services/utils.js"; | ||||||
| import TodayLauncher from "../buttons/launcher/today_launcher.js"; | import TodayLauncher from "../buttons/launcher/today_launcher.js"; | ||||||
| import HistoryNavigationButton from "../buttons/history_navigation.js"; | import HistoryNavigationButton from "../buttons/history_navigation.js"; | ||||||
|  | import QuickSearchLauncherWidget from "../quick_search_launcher.js"; | ||||||
| 
 | 
 | ||||||
| export default class LauncherWidget extends BasicWidget { | export default class LauncherWidget extends BasicWidget { | ||||||
|     constructor() { |     constructor(isHorizontalLayout) { | ||||||
|         super(); |         super(); | ||||||
| 
 | 
 | ||||||
|         this.innerWidget = null; |         this.innerWidget = null; | ||||||
|  |         this.isHorizontalLayout = isHorizontalLayout; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     isEnabled() { |     isEnabled() { | ||||||
| @ -63,6 +65,9 @@ export default class LauncherWidget extends BasicWidget { | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         this.child(this.innerWidget); |         this.child(this.innerWidget); | ||||||
|  |         if (this.isHorizontalLayout && this.innerWidget.settings) { | ||||||
|  |             this.innerWidget.settings.titlePlacement = "bottom"; | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|         return true; |         return true; | ||||||
|     } |     } | ||||||
| @ -86,29 +91,31 @@ export default class LauncherWidget extends BasicWidget { | |||||||
| 
 | 
 | ||||||
|     initBuiltinWidget(note) { |     initBuiltinWidget(note) { | ||||||
|         const builtinWidget = note.getLabelValue("builtinWidget"); |         const builtinWidget = note.getLabelValue("builtinWidget"); | ||||||
| 
 |         switch (builtinWidget) { | ||||||
|         if (builtinWidget === 'calendar') { |             case "calendar": | ||||||
|             return new CalendarWidget(note.title, note.getIcon()); |                 return new CalendarWidget(note.title, note.getIcon()); | ||||||
|         } else if (builtinWidget === 'spacer') { |             case "spacer": | ||||||
|             // || has to be inside since 0 is a valid value
 |                 // || has to be inside since 0 is a valid value
 | ||||||
|             const baseSize = parseInt(note.getLabelValue("baseSize") || "40"); |                 const baseSize = parseInt(note.getLabelValue("baseSize") || "40"); | ||||||
|             const growthFactor = parseInt(note.getLabelValue("growthFactor") || "100"); |                 const growthFactor = parseInt(note.getLabelValue("growthFactor") || "100"); | ||||||
| 
 |          | ||||||
|             return new SpacerWidget(baseSize, growthFactor); |                 return new SpacerWidget(baseSize, growthFactor); | ||||||
|         } else if (builtinWidget === 'bookmarks') { |             case "bookmarks": | ||||||
|             return new BookmarkButtons(); |                 return new BookmarkButtons(this.isHorizontalLayout); | ||||||
|         } else if (builtinWidget === 'protectedSession') { |             case "protectedSession": | ||||||
|             return new ProtectedSessionStatusWidget(); |                 return new ProtectedSessionStatusWidget(); | ||||||
|         } else if (builtinWidget === 'syncStatus') { |             case "syncStatus": | ||||||
|             return new SyncStatusWidget(); |                 return new SyncStatusWidget(); | ||||||
|         } else if (builtinWidget === 'backInHistoryButton') { |             case "backInHistoryButton": | ||||||
|             return new HistoryNavigationButton(note, "backInNoteHistory"); |                 return new HistoryNavigationButton(note, "backInNoteHistory"); | ||||||
|         } else if (builtinWidget === 'forwardInHistoryButton') { |             case "forwardInHistoryButton": | ||||||
|             return new HistoryNavigationButton(note, "forwardInNoteHistory"); |                 return new HistoryNavigationButton(note, "forwardInNoteHistory"); | ||||||
|         } else if (builtinWidget === 'todayInJournal') { |             case "todayInJournal": | ||||||
|             return new TodayLauncher(note); |                 return new TodayLauncher(note); | ||||||
|         } else { |             case "quickSearch": | ||||||
|             throw new Error(`Unrecognized builtin widget ${builtinWidget} for launcher ${note.noteId} "${note.title}"`); |                 return new QuickSearchLauncherWidget(this.isHorizontalLayout); | ||||||
|  |             default: | ||||||
|  |                 throw new Error(`Unrecognized builtin widget ${builtinWidget} for launcher ${note.noteId} "${note.title}"`); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -4,12 +4,13 @@ import appContext from "../../components/app_context.js"; | |||||||
| import LauncherWidget from "./launcher.js"; | import LauncherWidget from "./launcher.js"; | ||||||
| 
 | 
 | ||||||
| export default class LauncherContainer extends FlexContainer { | export default class LauncherContainer extends FlexContainer { | ||||||
|     constructor() { |     constructor(isHorizontalLayout) { | ||||||
|         super('column'); |         super(isHorizontalLayout ? "row" : "column"); | ||||||
| 
 | 
 | ||||||
|         this.id('launcher-container'); |         this.id('launcher-container'); | ||||||
|         this.css('height', '100%'); |         this.css(isHorizontalLayout ? "width" : 'height', '100%'); | ||||||
|         this.filling(); |         this.filling(); | ||||||
|  |         this.isHorizontalLayout = isHorizontalLayout; | ||||||
| 
 | 
 | ||||||
|         this.load(); |         this.load(); | ||||||
|     } |     } | ||||||
| @ -29,7 +30,7 @@ export default class LauncherContainer extends FlexContainer { | |||||||
| 
 | 
 | ||||||
|         for (const launcherNote of await visibleLaunchersRoot.getChildNotes()) { |         for (const launcherNote of await visibleLaunchersRoot.getChildNotes()) { | ||||||
|             try { |             try { | ||||||
|                 const launcherWidget = new LauncherWidget(); |                 const launcherWidget = new LauncherWidget(this.isHorizontalLayout); | ||||||
|                 const success = await launcherWidget.initLauncher(launcherNote); |                 const success = await launcherWidget.initLauncher(launcherNote); | ||||||
| 
 | 
 | ||||||
|                 if (success) { |                 if (success) { | ||||||
|  | |||||||
| @ -351,6 +351,11 @@ export default class RibbonContainer extends NoteContextAwareWidget { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     noteTypeMimeChangedEvent() { | ||||||
|  |         // We are ignoring the event which triggers a refresh since it is usually already done by a different
 | ||||||
|  |         // event and causing a race condition in which the items appear twice.
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /** |     /** | ||||||
|      * Executed as soon as the user presses the "Edit" floating button in a read-only text note. |      * Executed as soon as the user presses the "Edit" floating button in a read-only text note. | ||||||
|      *  |      *  | ||||||
|  | |||||||
| @ -1,8 +1,8 @@ | |||||||
| import FlexContainer from "./flex_container.js"; | import FlexContainer from "./flex_container.js"; | ||||||
| 
 | 
 | ||||||
| export default class RootContainer extends FlexContainer { | export default class RootContainer extends FlexContainer { | ||||||
|     constructor() { |     constructor(isHorizontalLayout) { | ||||||
|         super('row'); |         super(isHorizontalLayout ? "column" : "row"); | ||||||
| 
 | 
 | ||||||
|         this.id('root-widget'); |         this.id('root-widget'); | ||||||
|         this.css('height', '100%'); |         this.css('height', '100%'); | ||||||
|  | |||||||
| @ -58,6 +58,7 @@ export default class JumpToNoteDialog extends BasicWidget { | |||||||
|         noteAutocompleteService.initNoteAutocomplete(this.$autoComplete, { |         noteAutocompleteService.initNoteAutocomplete(this.$autoComplete, { | ||||||
|             allowCreatingNotes: true, |             allowCreatingNotes: true, | ||||||
|             hideGoToSelectedNoteButton: true, |             hideGoToSelectedNoteButton: true, | ||||||
|  |             allowSearchNotes: true, | ||||||
|             container: this.$results |             container: this.$results | ||||||
|         }) |         }) | ||||||
|             // clear any event listener added in previous invocation of this function
 |             // clear any event listener added in previous invocation of this function
 | ||||||
|  | |||||||
| @ -54,7 +54,7 @@ const TPL = ` | |||||||
|                             data-bs-toggle="dropdown" data-bs-display="static"> |                             data-bs-toggle="dropdown" data-bs-display="static"> | ||||||
|                     </button> |                     </button> | ||||||
| 
 | 
 | ||||||
|                     <div class="revision-list dropdown-menu" style="position: static; height: 100%; overflow: auto;"></div> |                     <div class="revision-list dropdown-menu static" style="position: static; height: 100%; overflow: auto;"></div> | ||||||
|                 </div> |                 </div> | ||||||
| 
 | 
 | ||||||
|                 <div class="revision-content-wrapper"> |                 <div class="revision-content-wrapper"> | ||||||
|  | |||||||
| @ -5,6 +5,7 @@ | |||||||
| 
 | 
 | ||||||
| import { t } from "../services/i18n.js"; | import { t } from "../services/i18n.js"; | ||||||
| import NoteContextAwareWidget from "./note_context_aware_widget.js"; | import NoteContextAwareWidget from "./note_context_aware_widget.js"; | ||||||
|  | import attributeService from "../services/attributes.js"; | ||||||
| import FindInText from "./find_in_text.js"; | import FindInText from "./find_in_text.js"; | ||||||
| import FindInCode from "./find_in_code.js"; | import FindInCode from "./find_in_code.js"; | ||||||
| import FindInHtml from "./find_in_html.js"; | import FindInHtml from "./find_in_html.js"; | ||||||
| @ -16,27 +17,26 @@ const waitForEnter = (findWidgetDelayMillis < 0); | |||||||
| // the focusout handler is called with relatedTarget equal to the label instead
 | // the focusout handler is called with relatedTarget equal to the label instead
 | ||||||
| // of undefined. It's -1 instead of > 0, so they don't tabstop
 | // of undefined. It's -1 instead of > 0, so they don't tabstop
 | ||||||
| const TPL = ` | const TPL = ` | ||||||
| <div style="contain: none;"> | <div class='find-replace-widget' style="contain: none; border-top: 1px solid var(--main-border-color);"> | ||||||
|     <style> |     <style> | ||||||
|         .find-widget-box { |         .find-widget-box, .replace-widget-box { | ||||||
|             padding: 10px; |             padding: 2px 10px 2px 10px; | ||||||
|             border-top: 1px solid var(--main-border-color);  |  | ||||||
|             align-items: center; |             align-items: center; | ||||||
|         } |         } | ||||||
|          |          | ||||||
|         .find-widget-box > * { |         .find-widget-box > *, .replace-widget-box > *{ | ||||||
|             margin-right: 15px; |             margin-right: 15px; | ||||||
|         } |         } | ||||||
|          |          | ||||||
|         .find-widget-box { |         .find-widget-box, .replace-widget-box { | ||||||
|             display: flex; |             display: flex; | ||||||
|         } |         } | ||||||
|          | 
 | ||||||
|         .find-widget-found-wrapper { |         .find-widget-found-wrapper { | ||||||
|             font-weight: bold; |             font-weight: bold; | ||||||
|         } |         } | ||||||
|          |          | ||||||
|         .find-widget-search-term-input-group { |         .find-widget-search-term-input-group, .replace-widget-replacetext-input { | ||||||
|             max-width: 300px; |             max-width: 300px; | ||||||
|         } |         } | ||||||
|          |          | ||||||
| @ -47,19 +47,23 @@ const TPL = ` | |||||||
| 
 | 
 | ||||||
|     <div class="find-widget-box"> |     <div class="find-widget-box"> | ||||||
|         <div class="input-group find-widget-search-term-input-group"> |         <div class="input-group find-widget-search-term-input-group"> | ||||||
|             <input type="text" class="form-control find-widget-search-term-input"> |             <input type="text" class="form-control find-widget-search-term-input" placeholder="${t('find.find_placeholder')}"> | ||||||
|             <button class="btn btn-outline-secondary bx bxs-chevron-up find-widget-previous-button" type="button"></button> |             <button class="btn btn-outline-secondary bx bxs-chevron-up find-widget-previous-button" type="button"></button> | ||||||
|             <button class="btn btn-outline-secondary bx bxs-chevron-down find-widget-next-button" type="button"></button> |             <button class="btn btn-outline-secondary bx bxs-chevron-down find-widget-next-button" type="button"></button> | ||||||
|         </div> |         </div> | ||||||
|          |          | ||||||
|         <div class="form-check"> |         <div class="form-check"> | ||||||
|             <input type="checkbox" class="form-check-input find-widget-case-sensitive-checkbox">  |             <label tabIndex="-1" class="form-check-label"> | ||||||
|             <label tabIndex="-1" class="form-check-label">${t('find.case_sensitive')}</label> |                 <input type="checkbox" class="form-check-input find-widget-case-sensitive-checkbox">  | ||||||
|  |                 ${t('find.case_sensitive')} | ||||||
|  |             </label> | ||||||
|         </div> |         </div> | ||||||
| 
 | 
 | ||||||
|         <div class="form-check"> |         <div class="form-check"> | ||||||
|             <input type="checkbox" class="form-check-input find-widget-match-words-checkbox"> |             <label tabIndex="-1" class="form-check-label"> | ||||||
|             <label tabIndex="-1" class="form-check-label">${t('find.match_words')}</label> |                 <input type="checkbox" class="form-check-input find-widget-match-words-checkbox"> | ||||||
|  |                 ${t('find.match_words')} | ||||||
|  |             </label> | ||||||
|         </div> |         </div> | ||||||
|          |          | ||||||
|         <div class="find-widget-found-wrapper"> |         <div class="find-widget-found-wrapper"> | ||||||
| @ -72,6 +76,12 @@ const TPL = ` | |||||||
|          |          | ||||||
|         <div class="find-widget-close-button"><button class="btn icon-action bx bx-x"></button></div> |         <div class="find-widget-close-button"><button class="btn icon-action bx bx-x"></button></div> | ||||||
|     </div> |     </div> | ||||||
|  | 
 | ||||||
|  |     <div class="replace-widget-box" style='display: none'> | ||||||
|  |         <input type="text" class="form-control replace-widget-replacetext-input" placeholder="${t('find.replace_placeholder')}"> | ||||||
|  |         <button class="btn btn-sm replace-widget-replaceall-button" type="button">${t('find.replace_all')}</button> | ||||||
|  |         <button class="btn btn-sm  replace-widget-replace-button" type="button">${t('find.replace')}</button> | ||||||
|  |     </div> | ||||||
| </div>`; | </div>`; | ||||||
| 
 | 
 | ||||||
| export default class FindWidget extends NoteContextAwareWidget { | export default class FindWidget extends NoteContextAwareWidget { | ||||||
| @ -93,8 +103,7 @@ export default class FindWidget extends NoteContextAwareWidget { | |||||||
| 
 | 
 | ||||||
|     doRender() { |     doRender() { | ||||||
|         this.$widget = $(TPL); |         this.$widget = $(TPL); | ||||||
|         this.$findBox = this.$widget.find('.find-widget-box'); |         this.$widget.hide(); | ||||||
|         this.$findBox.hide(); |  | ||||||
|         this.$input = this.$widget.find('.find-widget-search-term-input'); |         this.$input = this.$widget.find('.find-widget-search-term-input'); | ||||||
|         this.$currentFound = this.$widget.find('.find-widget-current-found'); |         this.$currentFound = this.$widget.find('.find-widget-current-found'); | ||||||
|         this.$totalFound = this.$widget.find('.find-widget-total-found'); |         this.$totalFound = this.$widget.find('.find-widget-total-found'); | ||||||
| @ -109,6 +118,13 @@ export default class FindWidget extends NoteContextAwareWidget { | |||||||
|         this.$closeButton = this.$widget.find(".find-widget-close-button"); |         this.$closeButton = this.$widget.find(".find-widget-close-button"); | ||||||
|         this.$closeButton.on("click", () => this.closeSearch()); |         this.$closeButton.on("click", () => this.closeSearch()); | ||||||
| 
 | 
 | ||||||
|  |         this.$replaceWidgetBox = this.$widget.find(".replace-widget-box"); | ||||||
|  |         this.$replaceTextInput = this.$widget.find(".replace-widget-replacetext-input"); | ||||||
|  |         this.$replaceAllButton = this.$widget.find(".replace-widget-replaceall-button"); | ||||||
|  |         this.$replaceAllButton.on("click", () => this.replaceAll()); | ||||||
|  |         this.$replaceButton = this.$widget.find(".replace-widget-replace-button"); | ||||||
|  |         this.$replaceButton.on("click", () => this.replace()); | ||||||
|  | 
 | ||||||
|         this.$input.keydown(async e => { |         this.$input.keydown(async e => { | ||||||
|             if ((e.metaKey || e.ctrlKey) && (e.key === 'F' || e.key === 'f')) { |             if ((e.metaKey || e.ctrlKey) && (e.key === 'F' || e.key === 'f')) { | ||||||
|                 // If ctrl+f is pressed when the findbox is shown, select the
 |                 // If ctrl+f is pressed when the findbox is shown, select the
 | ||||||
| @ -121,7 +137,7 @@ export default class FindWidget extends NoteContextAwareWidget { | |||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         this.$findBox.keydown(async e => { |         this.$widget.keydown(async e => { | ||||||
|             if (e.key === 'Escape') { |             if (e.key === 'Escape') { | ||||||
|                 await this.closeSearch(); |                 await this.closeSearch(); | ||||||
|             } |             } | ||||||
| @ -142,13 +158,25 @@ export default class FindWidget extends NoteContextAwareWidget { | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         this.handler = await this.getHandler(); |         this.handler = await this.getHandler(); | ||||||
|  |          | ||||||
|  |         const isReadOnly = await this.noteContext.isReadOnly(); | ||||||
| 
 | 
 | ||||||
|         const selectedText = window.getSelection().toString() || ""; |         let selectedText = ''; | ||||||
| 
 |         if (this.note.type === 'code' && !isReadOnly){ | ||||||
|         this.$findBox.show(); |             const codeEditor = await this.noteContext.getCodeEditor(); | ||||||
|  |             selectedText = codeEditor.getSelection(); | ||||||
|  |         }else{ | ||||||
|  |             selectedText = window.getSelection().toString() || ""; | ||||||
|  |         } | ||||||
|  |         this.$widget.show(); | ||||||
|         this.$input.focus(); |         this.$input.focus(); | ||||||
|  |         if (['text', 'code'].includes(this.note.type) && !isReadOnly) { | ||||||
|  |             this.$replaceWidgetBox.show(); | ||||||
|  |         }else{ | ||||||
|  |             this.$replaceWidgetBox.hide(); | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|         const isAlreadyVisible = this.$findBox.is(":visible"); |         const isAlreadyVisible = this.$widget.is(":visible"); | ||||||
| 
 | 
 | ||||||
|         if (isAlreadyVisible) { |         if (isAlreadyVisible) { | ||||||
|             if (selectedText) { |             if (selectedText) { | ||||||
| @ -254,8 +282,8 @@ export default class FindWidget extends NoteContextAwareWidget { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async closeSearch() { |     async closeSearch() { | ||||||
|         if (this.$findBox.is(":visible")) { |         if (this.$widget.is(":visible")) { | ||||||
|             this.$findBox.hide(); |             this.$widget.hide(); | ||||||
| 
 | 
 | ||||||
|             // Restore any state, if there's a current occurrence clear markers
 |             // Restore any state, if there's a current occurrence clear markers
 | ||||||
|             // and scroll to and select the last occurrence
 |             // and scroll to and select the last occurrence
 | ||||||
| @ -268,13 +296,27 @@ export default class FindWidget extends NoteContextAwareWidget { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     async replace() { | ||||||
|  |         const replaceText = this.$replaceTextInput.val(); | ||||||
|  |         await this.handler.replace(replaceText); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     async replaceAll() { | ||||||
|  |         const replaceText = this.$replaceTextInput.val(); | ||||||
|  |         await this.handler.replaceAll(replaceText); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     isEnabled() { |     isEnabled() { | ||||||
|         return super.isEnabled() && ['text', 'code', 'render'].includes(this.note.type); |         return super.isEnabled() && ['text', 'code', 'render'].includes(this.note.type); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async entitiesReloadedEvent({loadResults}) { |     async entitiesReloadedEvent({ loadResults }) { | ||||||
|         if (loadResults.isNoteContentReloaded(this.noteId)) { |         if (loadResults.isNoteContentReloaded(this.noteId)) { | ||||||
|             this.$totalFound.text("?") |             this.$totalFound.text("?") | ||||||
|  |         } else if (loadResults.getAttributeRows().find(attr => attr.type === 'label' | ||||||
|  |             && (attr.name.toLowerCase().includes('readonly')) | ||||||
|  |             && attributeService.isAffecting(attr, this.note))) { | ||||||
|  |             this.closeSearch(); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -170,4 +170,55 @@ export default class FindInCode { | |||||||
| 
 | 
 | ||||||
|         codeEditor.focus(); |         codeEditor.focus(); | ||||||
|     } |     } | ||||||
|  |     async replace(replaceText) { | ||||||
|  |         // this.findResult may be undefined and null
 | ||||||
|  |         if (!this.findResult || this.findResult.length===0){ | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |         let currentFound = -1; | ||||||
|  |         this.findResult.forEach((marker, index) => { | ||||||
|  |             const pos = marker.find(); | ||||||
|  |             if (pos) { | ||||||
|  |                 if (marker.className === FIND_RESULT_SELECTED_CSS_CLASSNAME) { | ||||||
|  |                     currentFound = index; | ||||||
|  |                     return; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |         if (currentFound >= 0) { | ||||||
|  |             let marker = this.findResult[currentFound]; | ||||||
|  |             let pos = marker.find(); | ||||||
|  |             const codeEditor = await this.getCodeEditor(); | ||||||
|  |             const doc = codeEditor.doc; | ||||||
|  |             doc.replaceRange(replaceText, pos.from, pos.to); | ||||||
|  |             marker.clear(); | ||||||
|  | 
 | ||||||
|  |             let nextFound; | ||||||
|  |             if (currentFound === this.findResult.length - 1) { | ||||||
|  |                 nextFound = 0; | ||||||
|  |             } else { | ||||||
|  |                 nextFound = currentFound; | ||||||
|  |             } | ||||||
|  |             this.findResult.splice(currentFound, 1); | ||||||
|  |             if (this.findResult.length > 0) { | ||||||
|  |                 this.findNext(0, nextFound, nextFound); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |     async replaceAll(replaceText) { | ||||||
|  |         if (!this.findResult || this.findResult.length===0){ | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  |         const codeEditor = await this.getCodeEditor(); | ||||||
|  |         const doc = codeEditor.doc; | ||||||
|  |         codeEditor.operation(() => { | ||||||
|  |             for (let currentFound = 0; currentFound < this.findResult.length; currentFound++) { | ||||||
|  |                 let marker = this.findResult[currentFound]; | ||||||
|  |                 let pos = marker.find(); | ||||||
|  |                 doc.replaceRange(replaceText, pos.from, pos.to); | ||||||
|  |                 marker.clear(); | ||||||
|  |             } | ||||||
|  |         }); | ||||||
|  |         this.findResult = []; | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -21,6 +21,7 @@ export default class FindInText { | |||||||
|         const findAndReplaceEditing = textEditor.plugins.get('FindAndReplaceEditing'); |         const findAndReplaceEditing = textEditor.plugins.get('FindAndReplaceEditing'); | ||||||
|         findAndReplaceEditing.state.clear(model); |         findAndReplaceEditing.state.clear(model); | ||||||
|         findAndReplaceEditing.stop(); |         findAndReplaceEditing.stop(); | ||||||
|  |         this.editingState = findAndReplaceEditing.state; | ||||||
|         if (searchTerm !== "") { |         if (searchTerm !== "") { | ||||||
|             // Parameters are callback/text, options.matchCase=false, options.wholeWords=false
 |             // Parameters are callback/text, options.matchCase=false, options.wholeWords=false
 | ||||||
|             // See https://github.com/ckeditor/ckeditor5/blob/b95e2faf817262ac0e1e21993d9c0bde3f1be594/packages/ckeditor5-find-and-replace/src/findcommand.js#L44
 |             // See https://github.com/ckeditor/ckeditor5/blob/b95e2faf817262ac0e1e21993d9c0bde3f1be594/packages/ckeditor5-find-and-replace/src/findcommand.js#L44
 | ||||||
| @ -29,7 +30,7 @@ export default class FindInText { | |||||||
|             // let re = new RegExp(searchTerm, 'gi');
 |             // let re = new RegExp(searchTerm, 'gi');
 | ||||||
|             // let m = text.match(re);
 |             // let m = text.match(re);
 | ||||||
|             // totalFound = m ? m.length : 0;
 |             // totalFound = m ? m.length : 0;
 | ||||||
|             const options = { "matchCase" : matchCase, "wholeWords" : wholeWord }; |             const options = { "matchCase": matchCase, "wholeWords": wholeWord }; | ||||||
|             findResult = textEditor.execute('find', searchTerm, options); |             findResult = textEditor.execute('find', searchTerm, options); | ||||||
|             totalFound = findResult.results.length; |             totalFound = findResult.results.length; | ||||||
|             // Find the result beyond the cursor
 |             // Find the result beyond the cursor
 | ||||||
| @ -102,4 +103,18 @@ export default class FindInText { | |||||||
| 
 | 
 | ||||||
|         textEditor.focus(); |         textEditor.focus(); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     async replace(replaceText) { | ||||||
|  |         if (this.editingState !== undefined && this.editingState.highlightedResult !== null) { | ||||||
|  |             const textEditor = await this.getTextEditor(); | ||||||
|  |             textEditor.execute('replace', replaceText, this.editingState.highlightedResult); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     async replaceAll(replaceText) { | ||||||
|  |         if (this.editingState !== undefined  && this.editingState.results.length > 0) { | ||||||
|  |             const textEditor = await this.getTextEditor(); | ||||||
|  |             textEditor.execute('replaceAll', replaceText, this.editingState.results); | ||||||
|  |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -5171,7 +5171,7 @@ const icons = [ | |||||||
|         "type_of_icon": "REGULAR" |         "type_of_icon": "REGULAR" | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|         "name": '_share', |         "name": "share", | ||||||
|         "slug": "share-regular", |         "slug": "share-regular", | ||||||
|         "category_id": 101, |         "category_id": 101, | ||||||
|         "type_of_icon": "REGULAR" |         "type_of_icon": "REGULAR" | ||||||
| @ -6826,7 +6826,7 @@ const icons = [ | |||||||
|         "type_of_icon": "SOLID" |         "type_of_icon": "SOLID" | ||||||
|     }, |     }, | ||||||
|     { |     { | ||||||
|         "name": '_share', |         "name": "share", | ||||||
|         "slug": "share-solid", |         "slug": "share-solid", | ||||||
|         "category_id": 101, |         "category_id": 101, | ||||||
|         "type_of_icon": "SOLID" |         "type_of_icon": "SOLID" | ||||||
|  | |||||||
| @ -3,6 +3,7 @@ import libraryLoader from "../services/library_loader.js"; | |||||||
| import NoteContextAwareWidget from "./note_context_aware_widget.js"; | import NoteContextAwareWidget from "./note_context_aware_widget.js"; | ||||||
| import server from "../services/server.js"; | import server from "../services/server.js"; | ||||||
| import utils from "../services/utils.js"; | import utils from "../services/utils.js"; | ||||||
|  | import { loadElkIfNeeded } from "../services/mermaid.js"; | ||||||
| 
 | 
 | ||||||
| const TPL = `<div class="mermaid-widget">
 | const TPL = `<div class="mermaid-widget">
 | ||||||
|     <style> |     <style> | ||||||
| @ -57,10 +58,10 @@ export default class MermaidWidget extends NoteContextAwareWidget { | |||||||
|         this.$errorContainer.hide(); |         this.$errorContainer.hide(); | ||||||
| 
 | 
 | ||||||
|         await libraryLoader.requireLibrary(libraryLoader.MERMAID); |         await libraryLoader.requireLibrary(libraryLoader.MERMAID); | ||||||
| 
 |          | ||||||
|         const documentStyle = window.getComputedStyle(document.documentElement); |         const documentStyle = window.getComputedStyle(document.documentElement); | ||||||
|         const mermaidTheme = documentStyle.getPropertyValue('--mermaid-theme'); |         const mermaidTheme = documentStyle.getPropertyValue('--mermaid-theme');         | ||||||
| 
 |          | ||||||
|         mermaid.mermaidAPI.initialize({ |         mermaid.mermaidAPI.initialize({ | ||||||
|             startOnLoad: false, |             startOnLoad: false, | ||||||
|             theme: mermaidTheme.trim(), |             theme: mermaidTheme.trim(), | ||||||
| @ -111,6 +112,7 @@ export default class MermaidWidget extends NoteContextAwareWidget { | |||||||
|                 zoomOnClick: false |                 zoomOnClick: false | ||||||
|             }); |             }); | ||||||
|         } catch (e) { |         } catch (e) { | ||||||
|  |             console.warn(e); | ||||||
|             this.$errorMessage.text(e.message); |             this.$errorMessage.text(e.message); | ||||||
|             this.$errorContainer.show(); |             this.$errorContainer.show(); | ||||||
|         } |         } | ||||||
| @ -122,6 +124,7 @@ export default class MermaidWidget extends NoteContextAwareWidget { | |||||||
|         const blob = await this.note.getBlob(); |         const blob = await this.note.getBlob(); | ||||||
|         const content = blob.content || ""; |         const content = blob.content || ""; | ||||||
| 
 | 
 | ||||||
|  |         await loadElkIfNeeded(content); | ||||||
|         const {svg} = await mermaid.mermaidAPI.render(`mermaid-graph-${idCounter}`, content); |         const {svg} = await mermaid.mermaidAPI.render(`mermaid-graph-${idCounter}`, content); | ||||||
|         return svg; |         return svg; | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -6,12 +6,20 @@ import branchService from "../../services/branches.js"; | |||||||
| import treeService from "../../services/tree.js"; | import treeService from "../../services/tree.js"; | ||||||
| import { t } from "../../services/i18n.js"; | import { t } from "../../services/i18n.js"; | ||||||
| 
 | 
 | ||||||
| const TPL = `<button type="button" class="action-button bx bx-menu" style="padding-top: 10px;"></button>`; | const TPL = `<button type="button" class="action-button bx" style="padding-top: 10px;"></button>`; | ||||||
| 
 | 
 | ||||||
| class MobileDetailMenuWidget extends BasicWidget { | class MobileDetailMenuWidget extends BasicWidget { | ||||||
|  | 
 | ||||||
|  |     constructor(isHorizontalLayout) { | ||||||
|  |         super(); | ||||||
|  |         this.isHorizontalLayout = isHorizontalLayout; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     doRender() { |     doRender() { | ||||||
|         this.$widget = $(TPL); |         this.$widget = $(TPL); | ||||||
| 
 | 
 | ||||||
|  |         this.$widget.addClass(this.isHorizontalLayout ? "bx-dots-vertical-rounded" : "bx-menu"); | ||||||
|  | 
 | ||||||
|         this.$widget.on("click", async e => { |         this.$widget.on("click", async e => { | ||||||
|             const note = appContext.tabManager.getActiveContextNote(); |             const note = appContext.tabManager.getActiveContextNote(); | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										33
									
								
								src/public/app/widgets/quick_search_launcher.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/public/app/widgets/quick_search_launcher.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,33 @@ | |||||||
|  | import utils from "../services/utils.js"; | ||||||
|  | import QuickSearchWidget from "./quick_search.js"; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Similar to the {@link QuickSearchWidget} but meant to be included inside the launcher bar. | ||||||
|  |  *  | ||||||
|  |  * <p> | ||||||
|  |  * Adds specific tweaks such as: | ||||||
|  |  *  | ||||||
|  |  * - Hiding the widget on mobile. | ||||||
|  |  */ | ||||||
|  | export default class QuickSearchLauncherWidget extends QuickSearchWidget { | ||||||
|  | 
 | ||||||
|  |     constructor(isHorizontalLayout) { | ||||||
|  |         super(); | ||||||
|  |         this.isHorizontalLayout = isHorizontalLayout; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     isEnabled() { | ||||||
|  |         if (!this.isHorizontalLayout) { | ||||||
|  |             // The quick search widget is added somewhere else on the vertical layout.
 | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if (utils.isMobile()) { | ||||||
|  |             // The widget takes too much spaces to be included in the mobile layout.
 | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return super.isEnabled(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | } | ||||||
| @ -60,7 +60,7 @@ export default class ClassicEditorToolbar extends NoteContextAwareWidget { | |||||||
|             show: await this.#shouldDisplay(), |             show: await this.#shouldDisplay(), | ||||||
|             activate: true, |             activate: true, | ||||||
|             title: t("classic_editor_toolbar.title"), |             title: t("classic_editor_toolbar.title"), | ||||||
|             icon: "bx bx-edit-alt" |             icon: "bx bx-text" | ||||||
|         }; |         }; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -233,6 +233,9 @@ export default class PromotedAttributesWidget extends NoteContextAwareWidget { | |||||||
|             else if (definition.labelType === 'datetime') { |             else if (definition.labelType === 'datetime') { | ||||||
|                 $input.prop('type', 'datetime-local') |                 $input.prop('type', 'datetime-local') | ||||||
|             } |             } | ||||||
|  |             else if (definition.labelType === 'time') { | ||||||
|  |                 $input.prop('type', 'time') | ||||||
|  |             } | ||||||
|             else if (definition.labelType === 'url') { |             else if (definition.labelType === 'url') { | ||||||
|                 $input.prop("placeholder", t("promoted_attributes.url_placeholder")); |                 $input.prop("placeholder", t("promoted_attributes.url_placeholder")); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -55,6 +55,10 @@ const TAB_ROW_TPL = ` | |||||||
|         background: var(--main-background-color); |         background: var(--main-background-color); | ||||||
|         overflow: hidden; |         overflow: hidden; | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     .tab-row-widget.full-width { | ||||||
|  |         background: var(--launcher-pane-background-color); | ||||||
|  |     } | ||||||
|      |      | ||||||
|     .tab-row-widget * { |     .tab-row-widget * { | ||||||
|         box-sizing: inherit; |         box-sizing: inherit; | ||||||
| @ -263,8 +267,15 @@ export default class TabRowWidget extends BasicWidget { | |||||||
|                     {title: t('tab_row.close_other_tabs'), command: "closeOtherTabs", uiIcon: "bx bx-empty", enabled: appContext.tabManager.noteContexts.length !== 1}, |                     {title: t('tab_row.close_other_tabs'), command: "closeOtherTabs", uiIcon: "bx bx-empty", enabled: appContext.tabManager.noteContexts.length !== 1}, | ||||||
|                     {title: t('tab_row.close_right_tabs'), command: "closeRightTabs", uiIcon: "bx bx-empty", enabled: appContext.tabManager.noteContexts.at(-1).ntxId !== ntxId}, |                     {title: t('tab_row.close_right_tabs'), command: "closeRightTabs", uiIcon: "bx bx-empty", enabled: appContext.tabManager.noteContexts.at(-1).ntxId !== ntxId}, | ||||||
|                     {title: t('tab_row.close_all_tabs'), command: "closeAllTabs", uiIcon: "bx bx-empty"}, |                     {title: t('tab_row.close_all_tabs'), command: "closeAllTabs", uiIcon: "bx bx-empty"}, | ||||||
|                     { title: "----" }, | 
 | ||||||
|                     {title: t('tab_row.move_tab_to_new_window'), command: "moveTabToNewWindow", uiIcon: "bx bx-window-open"} |                     {title: "----"}, | ||||||
|  | 
 | ||||||
|  |                     {title: t('tab_row.reopen_last_tab'), command: "reopenLastTab", uiIcon: "bx bx-undo", enabled: appContext.tabManager.recentlyClosedTabs.length !== 0}, | ||||||
|  | 
 | ||||||
|  |                     {title: "----"}, | ||||||
|  |                      | ||||||
|  |                     {title: t('tab_row.move_tab_to_new_window'), command: "moveTabToNewWindow", uiIcon: "bx bx-window-open"}, | ||||||
|  |                     {title: t('tab_row.copy_tab_to_new_window'), command: "copyTabToNewWindow", uiIcon: "bx bx-empty"} | ||||||
|                 ], |                 ], | ||||||
|                 selectMenuItemHandler: ({command}) => { |                 selectMenuItemHandler: ({command}) => { | ||||||
|                     this.triggerCommand(command, {ntxId}); |                     this.triggerCommand(command, {ntxId}); | ||||||
|  | |||||||
| @ -1,45 +1,53 @@ | |||||||
| import NoteContextAwareWidget from "../../note_context_aware_widget.js"; |  | ||||||
| import server from "../../../services/server.js"; | import server from "../../../services/server.js"; | ||||||
| import { t } from "../../../services/i18n.js"; | import { t } from "../../../services/i18n.js"; | ||||||
|  | import AbstractCodeTypeWidget from "../abstract_code_type_widget.js"; | ||||||
| 
 | 
 | ||||||
| const TPL = `<div style="height: 100%; display: flex; flex-direction: column;">
 | const TPL = `<div style="height: 100%; display: flex; flex-direction: column;">
 | ||||||
|     <style> |     <style> | ||||||
|         .backend-log-textarea { |         .backend-log-editor { | ||||||
|             flex-grow: 1;  |             flex-grow: 1;  | ||||||
|             width: 100%; |             width: 100%; | ||||||
|             border: none; |             border: none; | ||||||
|  |             resize: none; | ||||||
|         }    |         }    | ||||||
|     </style> |     </style> | ||||||
| 
 | 
 | ||||||
|     <textarea class="backend-log-textarea" readonly="readonly"></textarea> |     <pre class="backend-log-editor"></pre> | ||||||
|      |      | ||||||
|     <div style="display: flex; justify-content: space-around; margin-top: 10px;"> |     <div style="display: flex; justify-content: space-around; margin-top: 10px;"> | ||||||
|         <button class="refresh-backend-log-button btn btn-primary">${t("backend_log.refresh")}</button> |         <button class="refresh-backend-log-button btn btn-primary">${t("backend_log.refresh")}</button> | ||||||
|     </div> |     </div> | ||||||
| </div>`; | </div>`; | ||||||
| 
 | 
 | ||||||
| export default class BackendLogWidget extends NoteContextAwareWidget { | export default class BackendLogWidget extends AbstractCodeTypeWidget { | ||||||
|     doRender() { |     doRender() { | ||||||
|  |         super.doRender(); | ||||||
|         this.$widget = $(TPL); |         this.$widget = $(TPL); | ||||||
|         this.$backendLogTextArea = this.$widget.find(".backend-log-textarea"); |         this.$editor = this.$widget.find(".backend-log-editor"); | ||||||
|  | 
 | ||||||
|         this.$refreshBackendLog = this.$widget.find(".refresh-backend-log-button"); |         this.$refreshBackendLog = this.$widget.find(".refresh-backend-log-button"); | ||||||
| 
 |  | ||||||
|         this.$refreshBackendLog.on('click', () => this.load()); |         this.$refreshBackendLog.on('click', () => this.load()); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     scrollToBottom() { |  | ||||||
|         this.$backendLogTextArea.scrollTop(this.$backendLogTextArea[0].scrollHeight); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     async refresh() { |     async refresh() { | ||||||
|         await this.load(); |         await this.load(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     getExtraOpts() { | ||||||
|  |         return { | ||||||
|  |             lineWrapping: false, | ||||||
|  |             readOnly: true | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     async load() { |     async load() { | ||||||
|         const backendLog = await server.get('backend-log'); |         const content = await server.get('backend-log'); | ||||||
|  |         await this.initialized; | ||||||
| 
 | 
 | ||||||
|         this.$backendLogTextArea.text(backendLog); |         this._update({ | ||||||
| 
 |             mime: "text/plain"             | ||||||
|         this.scrollToBottom(); |         }, content); | ||||||
|  |         this.show(); | ||||||
|  |         this.scrollToEnd(); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -31,7 +31,14 @@ export default class DocTypeWidget extends TypeWidget { | |||||||
|         const docName = note.getLabelValue('docName'); |         const docName = note.getLabelValue('docName'); | ||||||
| 
 | 
 | ||||||
|         if (docName) { |         if (docName) { | ||||||
|             this.$content.load(`${window.glob.appPath}/doc_notes/${docName}.html`); |             // find doc based on language
 | ||||||
|  |             const lng = i18next.language; | ||||||
|  |             this.$content.load(`${window.glob.appPath}/doc_notes/${lng}/${docName}.html`, (response, status) => { | ||||||
|  |                 // fallback to english doc if no translation available
 | ||||||
|  |                 if (status === 'error') { | ||||||
|  |                     this.$content.load(`${window.glob.appPath}/doc_notes/en/${docName}.html`); | ||||||
|  |                 } | ||||||
|  |             }); | ||||||
|         } else { |         } else { | ||||||
|             this.$content.empty(); |             this.$content.empty(); | ||||||
|         } |         } | ||||||
|  | |||||||
| @ -70,6 +70,7 @@ export default class EmptyTypeWidget extends TypeWidget { | |||||||
|         noteAutocompleteService.initNoteAutocomplete(this.$autoComplete, { |         noteAutocompleteService.initNoteAutocomplete(this.$autoComplete, { | ||||||
|             hideGoToSelectedNoteButton: true, |             hideGoToSelectedNoteButton: true, | ||||||
|             allowCreatingNotes: true, |             allowCreatingNotes: true, | ||||||
|  |             allowSearchNotes: true, | ||||||
|             container: this.$results |             container: this.$results | ||||||
|         }) |         }) | ||||||
|             .on('autocomplete:noteselected', function(event, suggestion, dataset) { |             .on('autocomplete:noteselected', function(event, suggestion, dataset) { | ||||||
|  | |||||||
| @ -2,6 +2,8 @@ import OptionsWidget from "../options_widget.js"; | |||||||
| import utils from "../../../../services/utils.js"; | import utils from "../../../../services/utils.js"; | ||||||
| import { t } from "../../../../services/i18n.js"; | import { t } from "../../../../services/i18n.js"; | ||||||
| 
 | 
 | ||||||
|  | const MIN_VALUE = 640; | ||||||
|  | 
 | ||||||
| const TPL = ` | const TPL = ` | ||||||
| <div class="options-section"> | <div class="options-section"> | ||||||
|     <h4>${t("max_content_width.title")}</h4> |     <h4>${t("max_content_width.title")}</h4> | ||||||
| @ -11,7 +13,7 @@ const TPL = ` | |||||||
|     <div class="form-group row"> |     <div class="form-group row"> | ||||||
|         <div class="col-6"> |         <div class="col-6"> | ||||||
|             <label>${t("max_content_width.max_width_label")}</label> |             <label>${t("max_content_width.max_width_label")}</label> | ||||||
|             <input type="number" min="200" step="10" class="max-content-width form-control options-number-input"> |             <input type="number" min="${MIN_VALUE}" step="10" class="max-content-width form-control options-number-input"> | ||||||
|         </div> |         </div> | ||||||
|     </div> |     </div> | ||||||
|      |      | ||||||
| @ -34,6 +36,6 @@ export default class MaxContentWidthOptions extends OptionsWidget { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async optionsLoaded(options) { |     async optionsLoaded(options) { | ||||||
|         this.$maxContentWidth.val(options.maxContentWidth); |         this.$maxContentWidth.val(Math.max(MIN_VALUE, options.maxContentWidth)); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -5,6 +5,26 @@ import { t } from "../../../../services/i18n.js"; | |||||||
| 
 | 
 | ||||||
| const TPL = ` | const TPL = ` | ||||||
| <div class="options-section"> | <div class="options-section"> | ||||||
|  |     <h4>${t("theme.layout")}</h4> | ||||||
|  | 
 | ||||||
|  |     <div class="form-group row"> | ||||||
|  |         <div> | ||||||
|  |             <label> | ||||||
|  |                 <input type="radio" name="layout-orientation" value="vertical" /> | ||||||
|  |                 <strong>${t("theme.layout-vertical-title")}</strong> | ||||||
|  |                 - ${t("theme.layout-vertical-description")} | ||||||
|  |             </label> | ||||||
|  |         </div> | ||||||
|  | 
 | ||||||
|  |         <div> | ||||||
|  |             <label> | ||||||
|  |                 <input type="radio" name="layout-orientation" value="horizontal" /> | ||||||
|  |                 <strong>${t("theme.layout-horizontal-title")}</strong> | ||||||
|  |                 - ${t("theme.layout-horizontal-description")} | ||||||
|  |             </label> | ||||||
|  |         </div> | ||||||
|  |     </div> | ||||||
|  | 
 | ||||||
|     <h4>${t("theme.title")}</h4> |     <h4>${t("theme.title")}</h4> | ||||||
|      |      | ||||||
|     <div class="form-group row"> |     <div class="form-group row"> | ||||||
| @ -19,7 +39,7 @@ const TPL = ` | |||||||
|                 ${t("theme.override_theme_fonts_label")} |                 ${t("theme.override_theme_fonts_label")} | ||||||
|             </label> |             </label> | ||||||
|         </div> |         </div> | ||||||
|     </div> |     </div>     | ||||||
| </div>`; | </div>`; | ||||||
| 
 | 
 | ||||||
| export default class ThemeOptions extends OptionsWidget { | export default class ThemeOptions extends OptionsWidget { | ||||||
| @ -27,6 +47,11 @@ export default class ThemeOptions extends OptionsWidget { | |||||||
|         this.$widget = $(TPL); |         this.$widget = $(TPL); | ||||||
|         this.$themeSelect = this.$widget.find(".theme-select"); |         this.$themeSelect = this.$widget.find(".theme-select"); | ||||||
|         this.$overrideThemeFonts = this.$widget.find(".override-theme-fonts"); |         this.$overrideThemeFonts = this.$widget.find(".override-theme-fonts"); | ||||||
|  |         this.$layoutOrientation = this.$widget.find(`input[name="layout-orientation"]`).on("change", async () => { | ||||||
|  |             const newLayoutOrientation = this.$widget.find(`input[name="layout-orientation"]:checked`).val(); | ||||||
|  |             await this.updateOption("layoutOrientation", newLayoutOrientation); | ||||||
|  |             utils.reloadFrontendApp("layout orientation change"); | ||||||
|  |         }); | ||||||
| 
 | 
 | ||||||
|         this.$themeSelect.on('change', async () => { |         this.$themeSelect.on('change', async () => { | ||||||
|             const newTheme = this.$themeSelect.val(); |             const newTheme = this.$themeSelect.val(); | ||||||
| @ -57,5 +82,8 @@ export default class ThemeOptions extends OptionsWidget { | |||||||
|         this.$themeSelect.val(options.theme); |         this.$themeSelect.val(options.theme); | ||||||
| 
 | 
 | ||||||
|         this.setCheckboxState(this.$overrideThemeFonts, options.overrideThemeFonts); |         this.setCheckboxState(this.$overrideThemeFonts, options.overrideThemeFonts); | ||||||
|  | 
 | ||||||
|  |         this.$widget.find(`input[name="layout-orientation"][value="${options.layoutOrientation}"]`) | ||||||
|  |             .prop("checked", "true"); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -42,7 +42,21 @@ const TPL = ` | |||||||
| <div class="options-section"> | <div class="options-section"> | ||||||
|     <h4>${t('backup.existing_backups')}</h4> |     <h4>${t('backup.existing_backups')}</h4> | ||||||
|      |      | ||||||
|     <ul class="existing-backup-list"></ul> |     <table class="table table-stripped"> | ||||||
|  |         <colgroup> | ||||||
|  |             <col width="33%" /> | ||||||
|  |             <col /> | ||||||
|  |         </colgroup> | ||||||
|  |         <thead> | ||||||
|  |             <tr> | ||||||
|  |                 <th>${t("backup.date-and-time")}</th> | ||||||
|  |                 <th>${t("backup.path")}</th> | ||||||
|  |             </tr> | ||||||
|  |         </thead> | ||||||
|  |         <tbody class="existing-backup-list-items"> | ||||||
|  |         </tbody> | ||||||
|  |     </table> | ||||||
|  | 
 | ||||||
| </div> | </div> | ||||||
| `;
 | `;
 | ||||||
| 
 | 
 | ||||||
| @ -73,7 +87,7 @@ export default class BackupOptions extends OptionsWidget { | |||||||
|         this.$monthlyBackupEnabled.on('change', () => |         this.$monthlyBackupEnabled.on('change', () => | ||||||
|             this.updateCheckboxOption('monthlyBackupEnabled', this.$monthlyBackupEnabled)); |             this.updateCheckboxOption('monthlyBackupEnabled', this.$monthlyBackupEnabled)); | ||||||
| 
 | 
 | ||||||
|         this.$existingBackupList = this.$widget.find(".existing-backup-list"); |         this.$existingBackupList = this.$widget.find(".existing-backup-list-items"); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     optionsLoaded(options) { |     optionsLoaded(options) { | ||||||
| @ -85,11 +99,34 @@ export default class BackupOptions extends OptionsWidget { | |||||||
|             this.$existingBackupList.empty(); |             this.$existingBackupList.empty(); | ||||||
| 
 | 
 | ||||||
|             if (!backupFiles.length) { |             if (!backupFiles.length) { | ||||||
|                 backupFiles = [{filePath: t('backup.no_backup_yet'), mtime: ''}]; |                 this.$existingBackupList.append($(` | ||||||
|  |                     <tr> | ||||||
|  |                         <td class="empty-table-placeholder" colspan="2">${t('backup.no_backup_yet')}</td> | ||||||
|  |                     </tr> | ||||||
|  |                 `));
 | ||||||
|  | 
 | ||||||
|  |                 return; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|  |             // Sort the backup files by modification date & time in a desceding order
 | ||||||
|  |             backupFiles.sort((a, b) => { | ||||||
|  |                 if (a.mtime < b.mtime) return 1; | ||||||
|  |                 if (a.mtime > b.mtime) return -1; | ||||||
|  |                 return 0; | ||||||
|  |             }); | ||||||
|  | 
 | ||||||
|  |             const dateTimeFormatter = new Intl.DateTimeFormat(navigator.language, { | ||||||
|  |                 dateStyle: "medium", | ||||||
|  |                 timeStyle: "medium" | ||||||
|  |             }); | ||||||
|  | 
 | ||||||
|             for (const {filePath, mtime} of backupFiles) { |             for (const {filePath, mtime} of backupFiles) { | ||||||
|                 this.$existingBackupList.append($("<li>").text(`${filePath} ${mtime ? ` - ${mtime}` : ''}`)); |                 this.$existingBackupList.append($(` | ||||||
|  |                     <tr> | ||||||
|  |                         <td>${(mtime) ? dateTimeFormatter.format(new Date(mtime)) : "-"}</td> | ||||||
|  |                         <td>${filePath}</td> | ||||||
|  |                     </tr> | ||||||
|  |                 `));
 | ||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -95,9 +95,9 @@ export default class EtapiOptions extends OptionsWidget { | |||||||
|                     .append($("<td>").text(token.name)) |                     .append($("<td>").text(token.name)) | ||||||
|                     .append($("<td>").text(token.utcDateCreated)) |                     .append($("<td>").text(token.utcDateCreated)) | ||||||
|                     .append($("<td>").append( |                     .append($("<td>").append( | ||||||
|                         $('<span class="bx bx-pen token-table-button" title="${t("etapi.rename_token")}"></span>') |                         $(`<span class="bx bx-pen token-table-button" title="${t("etapi.rename_token")}"></span>`) | ||||||
|                             .on("click", () => this.renameToken(token.etapiTokenId, token.name)), |                             .on("click", () => this.renameToken(token.etapiTokenId, token.name)), | ||||||
|                         $('<span class="bx bx-trash token-table-button" title="${t("etapi.delete_token")}"></span>') |                         $(`<span class="bx bx-trash token-table-button" title="${t("etapi.delete_token")}"></span>`) | ||||||
|                             .on("click", () => this.deleteToken(token.etapiTokenId, token.name)) |                             .on("click", () => this.deleteToken(token.etapiTokenId, token.name)) | ||||||
|                     )) |                     )) | ||||||
|             ); |             ); | ||||||
|  | |||||||
| @ -6,25 +6,37 @@ const TPL = ` | |||||||
| <div class="options-section"> | <div class="options-section"> | ||||||
|     <h4>${t("editing.editor_type.label")}</h4> |     <h4>${t("editing.editor_type.label")}</h4> | ||||||
|      |      | ||||||
|     <select class="editor-type-select form-select"> |     <div> | ||||||
|         <option value="ckeditor-balloon">${t("editing.editor_type.floating")}</option> |         <label> | ||||||
|         <option value="ckeditor-classic">${t("editing.editor_type.fixed")}</option> |             <input type="radio" name="editor-type" value="ckeditor-balloon" /> | ||||||
|     </select> |             <strong>${t("editing.editor_type.floating.title")}</strong> | ||||||
|  |             - ${t("editing.editor_type.floating.description")} | ||||||
|  |         </label> | ||||||
|  |     </div> | ||||||
|  | 
 | ||||||
|  |     <div> | ||||||
|  |         <label> | ||||||
|  |             <input type="radio" name="editor-type" value="ckeditor-classic" /> | ||||||
|  |             <strong>${t("editing.editor_type.fixed.title")}</strong> | ||||||
|  |             - ${t("editing.editor_type.fixed.description")} | ||||||
|  |         </label> | ||||||
|  |     </div> | ||||||
|  | 
 | ||||||
| </div>`; | </div>`; | ||||||
| 
 | 
 | ||||||
| export default class EditorOptions extends OptionsWidget { | export default class EditorOptions extends OptionsWidget { | ||||||
|     doRender() { |     doRender() { | ||||||
|         this.$widget = $(TPL); |         this.$widget = $(TPL); | ||||||
|         this.$body = $("body"); |         this.$body = $("body"); | ||||||
|         this.$editorType = this.$widget.find(".editor-type-select"); |         this.$widget.find(`input[name="editor-type"]`).on('change', async () => { | ||||||
|         this.$editorType.on('change', async () => { |             const newEditorType = this.$widget.find(`input[name="editor-type"]:checked`).val(); | ||||||
|             const newEditorType = this.$editorType.val(); |  | ||||||
|             await this.updateOption('textNoteEditorType', newEditorType); |             await this.updateOption('textNoteEditorType', newEditorType); | ||||||
|             utils.reloadFrontendApp("editor type change"); |             utils.reloadFrontendApp("editor type change"); | ||||||
|         }); |         }); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async optionsLoaded(options) { |     async optionsLoaded(options) { | ||||||
|         this.$editorType.val(options.textNoteEditorType); |         this.$widget.find(`input[name="editor-type"][value="${options.textNoteEditorType}"]`) | ||||||
|  |                     .prop("checked", "true"); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -51,7 +51,7 @@ export default class ReadOnlyCodeTypeWidget extends AbstractCodeTypeWidget { | |||||||
| 
 | 
 | ||||||
|         await this.initialized; |         await this.initialized; | ||||||
| 
 | 
 | ||||||
|         resolve(this.$content); |         resolve(this.$editor); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     format(html) { |     format(html) { | ||||||
|  | |||||||
| @ -19,6 +19,10 @@ | |||||||
|     --bs-table-bg: transparent !important; |     --bs-table-bg: transparent !important; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | :root { | ||||||
|  |     --submenu-opening-delay: 300ms; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| html { | html { | ||||||
|     /* this fixes FF filter vs. position fixed bug: https://github.com/zadam/trilium/issues/233 */ |     /* this fixes FF filter vs. position fixed bug: https://github.com/zadam/trilium/issues/233 */ | ||||||
|     height: 100%; |     height: 100%; | ||||||
| @ -36,6 +40,10 @@ body { | |||||||
|     font-size: var(--main-font-size); |     font-size: var(--main-font-size); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | body.mobile .desktop-only { | ||||||
|  |     display: none !important; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| a { | a { | ||||||
|     text-decoration: none; |     text-decoration: none; | ||||||
| } | } | ||||||
| @ -229,7 +237,15 @@ div.ui-tooltip { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .dropdown-divider { | .dropdown-divider { | ||||||
|     background-color: var(--menu-text-color); |     border-color: var(--dropdown-border-color); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @keyframes dropdown-menu-opening { | ||||||
|  |     from { | ||||||
|  |         opacity: 0; | ||||||
|  |     } to { | ||||||
|  |         opacity: 1; | ||||||
|  |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .dropdown-menu { | .dropdown-menu { | ||||||
| @ -237,6 +253,26 @@ div.ui-tooltip { | |||||||
|     color: var(--menu-text-color) !important; |     color: var(--menu-text-color) !important; | ||||||
|     background-color: var(--menu-background-color) !important; |     background-color: var(--menu-background-color) !important; | ||||||
|     font-size: inherit; |     font-size: inherit; | ||||||
|  |     box-shadow: 0px 10px 20px rgba(0, 0, 0, var(--dropdown-shadow-opacity)); | ||||||
|  |     animation: dropdown-menu-opening 100ms ease-in; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | @supports(animation-fill-mode: forwards) { | ||||||
|  |     /* Delay the opening of submenus */ | ||||||
|  |     .dropdown-submenu .dropdown-menu { | ||||||
|  |         opacity: 0; | ||||||
|  |         animation-fill-mode: forwards; | ||||||
|  |         animation-delay: var(--submenu-opening-delay); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .dropdown-menu.static { | ||||||
|  |     box-shadow: unset; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .dropend .dropdown-toggle::after { | ||||||
|  |     margin-left: .5em; | ||||||
|  |     color: var(--muted-text-color); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .dropdown-menu .disabled { | .dropdown-menu .disabled { | ||||||
| @ -246,17 +282,29 @@ div.ui-tooltip { | |||||||
| 
 | 
 | ||||||
| .dropdown-menu .disabled .disabled-tooltip { | .dropdown-menu .disabled .disabled-tooltip { | ||||||
|     pointer-events: all; |     pointer-events: all; | ||||||
|     color: var(--menu-text-color); |     margin-left: 8px; | ||||||
|  |     font-size: .5em; | ||||||
|  |     color: var(--disabled-tooltip-icon-color); | ||||||
|     cursor: help; |     cursor: help; | ||||||
|  |     opacity: .75; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| .dropdown-menu a:hover:not(.disabled), .dropdown-item:hover:not(.disabled) { | .dropdown-menu .disabled .disabled-tooltip:hover { | ||||||
|  |     opacity: 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | .dropdown-menu a:hover:not(.disabled), .dropdown-item:hover:not(.disabled, .dropdown-item-container) { | ||||||
|     color: var(--hover-item-text-color) !important; |     color: var(--hover-item-text-color) !important; | ||||||
|     background-color: var(--hover-item-background-color) !important; |     background-color: var(--hover-item-background-color) !important; | ||||||
|     border-color: var(--hover-item-border-color) !important; |     border-color: var(--hover-item-border-color) !important; | ||||||
|     cursor: pointer; |     cursor: pointer; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | .dropdown-item-container, .dropdown-item-container:hover, .dropdown-item-container:active { | ||||||
|  |     background: transparent; | ||||||
|  |     cursor: default; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| .dropdown-menu a:not(.selected) .check { | .dropdown-menu a:not(.selected) .check { | ||||||
|     visibility: hidden; |     visibility: hidden; | ||||||
| } | } | ||||||
| @ -288,6 +336,10 @@ div.ui-tooltip { | |||||||
|     outline: none; |     outline: none; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | .dropdown-item .destructive-action-icon { | ||||||
|  |     color: var(--dropdown-item-icon-destructive-color); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| .CodeMirror { | .CodeMirror { | ||||||
|     height: 100%; |     height: 100%; | ||||||
|     background: inherit; |     background: inherit; | ||||||
| @ -974,6 +1026,18 @@ li.dropdown-submenu:hover > ul.dropdown-menu { | |||||||
|     overflow: auto; |     overflow: auto; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | body:not(.mobile) #launcher-pane.horizontal .dropdown-submenu > .dropdown-menu { | ||||||
|  |     left: calc(-100% + 10px); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #launcher-pane.horizontal .right-dropdown-widget { | ||||||
|  |     width: 53px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #launcher-pane.vertical .right-dropdown-widget { | ||||||
|  |     height: 53px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| /* rotate caret on hover */ | /* rotate caret on hover */ | ||||||
| .dropdown-menu > li > a:hover:after { | .dropdown-menu > li > a:hover:after { | ||||||
|     text-decoration: underline; |     text-decoration: underline; | ||||||
| @ -1075,9 +1139,21 @@ li.dropdown-submenu:hover > ul.dropdown-menu { | |||||||
|     cursor: pointer; |     cursor: pointer; | ||||||
|     border: none; |     border: none; | ||||||
|     color: var(--launcher-pane-text-color); |     color: var(--launcher-pane-text-color); | ||||||
|     background-color: var(--launcher-pane-background-color); |     background-color: var(--launcher-pane-background-color);     | ||||||
|     height: 53px; | } | ||||||
|  | 
 | ||||||
|  | #launcher-pane.vertical .launcher-button { | ||||||
|     width: 100%; |     width: 100%; | ||||||
|  |     height: 53px; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #launcher-pane.horizontal .launcher-button { | ||||||
|  |     width: 53px; | ||||||
|  |     height: 100%; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | #launcher-pane.horizontal .quick-search { | ||||||
|  |     width: 350px; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| #launcher-pane .icon-action:hover { | #launcher-pane .icon-action:hover { | ||||||
| @ -1238,3 +1314,8 @@ textarea { | |||||||
|     padding: 1rem; |     padding: 1rem; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | .empty-table-placeholder { | ||||||
|  |     text-align: center; | ||||||
|  |     color: var(--muted-text-color); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | |||||||
| @ -17,6 +17,10 @@ | |||||||
|     --main-text-color: #ccc; |     --main-text-color: #ccc; | ||||||
|     --main-border-color: #aaa; |     --main-border-color: #aaa; | ||||||
|     --dropdown-border-color: #555; |     --dropdown-border-color: #555; | ||||||
|  |     --dropdown-shadow-opacity: .4; | ||||||
|  |     --dropdown-item-icon-destructive-color: #de6e5b; | ||||||
|  |     --disabled-tooltip-icon-color: #7fd2ef; | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
|     --accented-background-color: #555; |     --accented-background-color: #555; | ||||||
|     --more-accented-background-color: #777; |     --more-accented-background-color: #777; | ||||||
|  | |||||||
| @ -21,6 +21,9 @@ html { | |||||||
|     --main-text-color: black; |     --main-text-color: black; | ||||||
|     --main-border-color: #ccc; |     --main-border-color: #ccc; | ||||||
|     --dropdown-border-color: #ccc; |     --dropdown-border-color: #ccc; | ||||||
|  |     --dropdown-shadow-opacity: .2; | ||||||
|  |     --dropdown-item-icon-destructive-color: #ec5138; | ||||||
|  |     --disabled-tooltip-icon-color: #004382; | ||||||
| 
 | 
 | ||||||
|     --accented-background-color: #f5f5f5; |     --accented-background-color: #f5f5f5; | ||||||
|     --more-accented-background-color: #ddd; |     --more-accented-background-color: #ddd; | ||||||
|  | |||||||
| @ -138,13 +138,13 @@ span.fancytree-node.protected > span.fancytree-custom-icon { | |||||||
| span.fancytree-node.multiple-parents.shared .fancytree-title::after { | span.fancytree-node.multiple-parents.shared .fancytree-title::after { | ||||||
|     font-family: 'boxicons' !important; |     font-family: 'boxicons' !important; | ||||||
|     font-size: smaller; |     font-size: smaller; | ||||||
|     content: " \ec27  \ec03"; |     content: " \eb3d  \ec03"; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| span.fancytree-node.multiple-parents .fancytree-title::after { | span.fancytree-node.multiple-parents .fancytree-title::after { | ||||||
|     font-family: 'boxicons' !important; |     font-family: 'boxicons' !important; | ||||||
|     font-size: smaller; |     font-size: smaller; | ||||||
|     content: " \ec27"; /* lookup code for "star" in boxicons.css */ |     content: " \eb3d"; /* lookup code for "link-alt" in boxicons.css */ | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| span.fancytree-node.shared .fancytree-title::after { | span.fancytree-node.shared .fancytree-title::after { | ||||||
|  | |||||||
| @ -15,7 +15,13 @@ | |||||||
|       "message": "发生了严重错误,导致客户端应用程序无法启动:\n\n{{message}}\n\n这很可能是由于脚本以意外的方式失败引起的。请尝试以安全模式启动应用程序并解决问题。" |       "message": "发生了严重错误,导致客户端应用程序无法启动:\n\n{{message}}\n\n这很可能是由于脚本以意外的方式失败引起的。请尝试以安全模式启动应用程序并解决问题。" | ||||||
|     }, |     }, | ||||||
|     "widget-error": { |     "widget-error": { | ||||||
|       "title": "小部件初始化失败" |       "title": "小部件初始化失败", | ||||||
|  |       "message-custom": "来自 ID 为 \"{{id}}\"、标题为 \"{{title}}\" 的笔记的自定义小部件因以下原因无法初始化:\n\n{{message}}", | ||||||
|  |       "message-unknown": "未知小部件因以下原因无法初始化:\n\n{{message}}" | ||||||
|  |     }, | ||||||
|  |     "bundle-error": { | ||||||
|  |       "title": "加载自定义脚本失败", | ||||||
|  |       "message": "来自 ID 为 \"{{id}}\"、标题为 \"{{title}}\" 的笔记的脚本因以下原因无法执行:\n\n{{message}}" | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|   "add_link": { |   "add_link": { | ||||||
| @ -45,7 +51,11 @@ | |||||||
|     "chosen_actions": "选择的操作", |     "chosen_actions": "选择的操作", | ||||||
|     "execute_bulk_actions": "执行批量操作", |     "execute_bulk_actions": "执行批量操作", | ||||||
|     "bulk_actions_executed": "批量操作已成功执行。", |     "bulk_actions_executed": "批量操作已成功执行。", | ||||||
|     "none_yet": "暂无操作 ... 通过点击上方的可用操作添加一个操作。" |     "none_yet": "暂无操作 ... 通过点击上方的可用操作添加一个操作。", | ||||||
|  |     "labels": "标签", | ||||||
|  |     "relations": "关联关系", | ||||||
|  |     "notes": "笔记", | ||||||
|  |     "other": "其它" | ||||||
|   }, |   }, | ||||||
|   "clone_to": { |   "clone_to": { | ||||||
|     "clone_notes_to": "克隆笔记到...", |     "clone_notes_to": "克隆笔记到...", | ||||||
| @ -164,7 +174,8 @@ | |||||||
|     "textImportedAsText": "如果元数据不明确,将HTML、Markdown和TXT导入为文本笔记", |     "textImportedAsText": "如果元数据不明确,将HTML、Markdown和TXT导入为文本笔记", | ||||||
|     "codeImportedAsCode": "如果元数据不明确,将识别的代码文件(例如<code>.json</code>)导入为代码笔记", |     "codeImportedAsCode": "如果元数据不明确,将识别的代码文件(例如<code>.json</code>)导入为代码笔记", | ||||||
|     "replaceUnderscoresWithSpaces": "在导入的笔记名称中将下划线替换为空格", |     "replaceUnderscoresWithSpaces": "在导入的笔记名称中将下划线替换为空格", | ||||||
|     "import": "导入" |     "import": "导入", | ||||||
|  |     "failed": "导入失败: {{message}}." | ||||||
|   }, |   }, | ||||||
|   "include_note": { |   "include_note": { | ||||||
|     "dialog_title": "包含笔记", |     "dialog_title": "包含笔记", | ||||||
| @ -304,6 +315,7 @@ | |||||||
|     "boolean": "布尔值", |     "boolean": "布尔值", | ||||||
|     "date": "日期", |     "date": "日期", | ||||||
|     "date_time": "日期和时间", |     "date_time": "日期和时间", | ||||||
|  |     "time": "时间", | ||||||
|     "url": "网址", |     "url": "网址", | ||||||
|     "precision_title": "值设置界面中浮点数后的位数。", |     "precision_title": "值设置界面中浮点数后的位数。", | ||||||
|     "precision": "精度", |     "precision": "精度", | ||||||
| @ -630,7 +642,10 @@ | |||||||
|     "export_note": "导出笔记", |     "export_note": "导出笔记", | ||||||
|     "delete_note": "删除笔记", |     "delete_note": "删除笔记", | ||||||
|     "print_note": "打印笔记", |     "print_note": "打印笔记", | ||||||
|     "save_revision": "保存笔记历史" |     "save_revision": "保存笔记历史", | ||||||
|  |     "convert_into_attachment_failed": "笔记 '{{title}}' 转换失败。", | ||||||
|  |     "convert_into_attachment_successful": "笔记 '{{title}}' 已成功转换为附件。", | ||||||
|  |     "convert_into_attachment_prompt": "确定要将笔记 '{{title}}' 转换为父笔记的附件吗?" | ||||||
|   }, |   }, | ||||||
|   "onclick_button": { |   "onclick_button": { | ||||||
|     "no_click_handler": "按钮组件'{{componentId}}'没有定义点击处理程序" |     "no_click_handler": "按钮组件'{{componentId}}'没有定义点击处理程序" | ||||||
| @ -885,7 +900,8 @@ | |||||||
|     "label_rock_or_pop": "只需一个标签存在即可", |     "label_rock_or_pop": "只需一个标签存在即可", | ||||||
|     "label_year_comparison": "数字比较(也包括>,>=,<)。", |     "label_year_comparison": "数字比较(也包括>,>=,<)。", | ||||||
|     "label_date_created": "上个月创建的笔记", |     "label_date_created": "上个月创建的笔记", | ||||||
|     "error": "搜索错误:{{error}}" |     "error": "搜索错误:{{error}}", | ||||||
|  |     "search_prefix": "搜索:" | ||||||
|   }, |   }, | ||||||
|   "attachment_detail": { |   "attachment_detail": { | ||||||
|     "open_help_page": "打开附件帮助页面", |     "open_help_page": "打开附件帮助页面", | ||||||
| @ -919,7 +935,15 @@ | |||||||
|   }, |   }, | ||||||
|   "protected_session": { |   "protected_session": { | ||||||
|     "enter_password_instruction": "显示受保护的笔记需要输入您的密码:", |     "enter_password_instruction": "显示受保护的笔记需要输入您的密码:", | ||||||
|     "start_session_button": "开始受保护的会话" |     "start_session_button": "开始受保护的会话", | ||||||
|  |     "started": "受保护的会话已启动。", | ||||||
|  |     "wrong_password": "密码错误。", | ||||||
|  |     "protecting-finished-successfully": "保护操作已成功完成。", | ||||||
|  |     "unprotecting-finished-successfully": "解除保护操作已成功完成。", | ||||||
|  |     "protecting-in-progress": "保护进行中:{{count}}", | ||||||
|  |     "unprotecting-in-progress-count": "解除保护进行中:{{count}}", | ||||||
|  |     "protecting-title": "保护状态", | ||||||
|  |     "unprotecting-title": "解除保护状态" | ||||||
|   }, |   }, | ||||||
|   "relation_map": { |   "relation_map": { | ||||||
|     "open_in_new_tab": "在新标签页中打开", |     "open_in_new_tab": "在新标签页中打开", | ||||||
| @ -990,7 +1014,9 @@ | |||||||
|     "fill_entity_changes_button": "填充实体变更记录", |     "fill_entity_changes_button": "填充实体变更记录", | ||||||
|     "full_sync_triggered": "全量同步已触发", |     "full_sync_triggered": "全量同步已触发", | ||||||
|     "filling_entity_changes": "正在填充实体变更行...", |     "filling_entity_changes": "正在填充实体变更行...", | ||||||
|     "sync_rows_filled_successfully": "同步行填充成功" |     "sync_rows_filled_successfully": "同步行填充成功", | ||||||
|  |     "finished-successfully": "同步已完成。", | ||||||
|  |     "failed": "同步失败:{{message}}" | ||||||
|   }, |   }, | ||||||
|   "vacuum_database": { |   "vacuum_database": { | ||||||
|     "title": "数据库清理", |     "title": "数据库清理", | ||||||
| @ -1036,8 +1062,13 @@ | |||||||
|     "theme_label": "主题", |     "theme_label": "主题", | ||||||
|     "override_theme_fonts_label": "覆盖主题字体", |     "override_theme_fonts_label": "覆盖主题字体", | ||||||
|     "light_theme": "浅色", |     "light_theme": "浅色", | ||||||
|     "dark_theme": "深色" |     "dark_theme": "深色", | ||||||
|   }, |     "layout": "布局", | ||||||
|  |     "layout-vertical-title": "垂直", | ||||||
|  |     "layout-horizontal-title": "水平", | ||||||
|  |     "layout-vertical-description": "启动栏位于左侧(默认)", | ||||||
|  |     "layout-horizontal-description": "启动栏位于标签栏下方,标签栏现在是全宽的。" | ||||||
|  |  }, | ||||||
|   "zoom_factor": { |   "zoom_factor": { | ||||||
|     "title": "缩放系数(仅桌面客户端有效)", |     "title": "缩放系数(仅桌面客户端有效)", | ||||||
|     "description": "缩放也可以通过 CTRL+- 和 CTRL+= 快捷键进行控制。" |     "description": "缩放也可以通过 CTRL+- 和 CTRL+= 快捷键进行控制。" | ||||||
| @ -1162,6 +1193,8 @@ | |||||||
|     "backup_now": "立即备份", |     "backup_now": "立即备份", | ||||||
|     "backup_database_now": "立即备份数据库", |     "backup_database_now": "立即备份数据库", | ||||||
|     "existing_backups": "现有备份", |     "existing_backups": "现有备份", | ||||||
|  |     "date-and-time": "日期和时间", | ||||||
|  |     "path": "路径", | ||||||
|     "database_backed_up_to": "数据库已备份到", |     "database_backed_up_to": "数据库已备份到", | ||||||
|     "no_backup_yet": "尚无备份" |     "no_backup_yet": "尚无备份" | ||||||
|   }, |   }, | ||||||
| @ -1309,7 +1342,9 @@ | |||||||
|     "duplicate-subtree": "复制子树", |     "duplicate-subtree": "复制子树", | ||||||
|     "export": "导出", |     "export": "导出", | ||||||
|     "import-into-note": "导入到笔记", |     "import-into-note": "导入到笔记", | ||||||
|     "apply-bulk-actions": "应用批量操作" |     "apply-bulk-actions": "应用批量操作", | ||||||
|  |     "converted-to-attachments": "{{count}} 个笔记已被转换为附件。", | ||||||
|  |     "convert-to-attachment-confirm": "确定要将选中的笔记转换为其父笔记的附件吗?" | ||||||
|   }, |   }, | ||||||
|   "shared_info": { |   "shared_info": { | ||||||
|     "shared_publicly": "此笔记已公开分享在", |     "shared_publicly": "此笔记已公开分享在", | ||||||
| @ -1332,7 +1367,8 @@ | |||||||
|     "image": "图片", |     "image": "图片", | ||||||
|     "launcher": "启动器", |     "launcher": "启动器", | ||||||
|     "doc": "文档", |     "doc": "文档", | ||||||
|     "widget": "小部件" |     "widget": "小部件", | ||||||
|  |     "confirm-change": "当笔记内容不为空时,不建议更改笔记类型。您仍然要继续吗?" | ||||||
|   }, |   }, | ||||||
|   "protect_note": { |   "protect_note": { | ||||||
|     "toggle-on": "保护笔记", |     "toggle-on": "保护笔记", | ||||||
| @ -1355,7 +1391,11 @@ | |||||||
|   "open-help-page": "打开帮助页面", |   "open-help-page": "打开帮助页面", | ||||||
|   "find": { |   "find": { | ||||||
|     "case_sensitive": "区分大小写", |     "case_sensitive": "区分大小写", | ||||||
|     "match_words": "匹配单词" |     "match_words": "匹配单词", | ||||||
|  |     "find_placeholder": "在文本中查找...", | ||||||
|  |     "replace_placeholder": "替换为...", | ||||||
|  |     "replace": "替换", | ||||||
|  |     "replace_all": "全部替换" | ||||||
|   }, |   }, | ||||||
|   "highlights_list_2": { |   "highlights_list_2": { | ||||||
|     "title": "高亮列表", |     "title": "高亮列表", | ||||||
| @ -1378,7 +1418,9 @@ | |||||||
|     "hide-archived-notes": "隐藏已归档笔记", |     "hide-archived-notes": "隐藏已归档笔记", | ||||||
|     "automatically-collapse-notes": "自动折叠笔记", |     "automatically-collapse-notes": "自动折叠笔记", | ||||||
|     "automatically-collapse-notes-title": "笔记在一段时间内未使用将被折叠,以减少树形结构的杂乱。", |     "automatically-collapse-notes-title": "笔记在一段时间内未使用将被折叠,以减少树形结构的杂乱。", | ||||||
|     "save-changes": "保存并应用更改" |     "save-changes": "保存并应用更改", | ||||||
|  |     "auto-collapsing-notes-after-inactivity": "在不活动后自动折叠笔记...", | ||||||
|  |     "saved-search-note-refreshed": "已保存的搜索笔记已刷新。" | ||||||
|   }, |   }, | ||||||
|   "title_bar_buttons": { |   "title_bar_buttons": { | ||||||
|     "window-on-top": "保持此窗口置顶" |     "window-on-top": "保持此窗口置顶" | ||||||
| @ -1407,8 +1449,11 @@ | |||||||
|     "add_new_tab": "添加新标签页", |     "add_new_tab": "添加新标签页", | ||||||
|     "close": "关闭", |     "close": "关闭", | ||||||
|     "close_other_tabs": "关闭其他标签页", |     "close_other_tabs": "关闭其他标签页", | ||||||
|  |     "close_right_tabs": "关闭右侧标签页", | ||||||
|     "close_all_tabs": "关闭所有标签页", |     "close_all_tabs": "关闭所有标签页", | ||||||
|  |     "reopen_last_tab": "重新打开最后一个关闭的标签页", | ||||||
|     "move_tab_to_new_window": "将此标签页移动到新窗口", |     "move_tab_to_new_window": "将此标签页移动到新窗口", | ||||||
|  |     "copy_tab_to_new_window": "将此标签页复制到新窗口", | ||||||
|     "new_tab": "新标签页" |     "new_tab": "新标签页" | ||||||
|   }, |   }, | ||||||
|   "toc": { |   "toc": { | ||||||
| @ -1422,5 +1467,101 @@ | |||||||
|   }, |   }, | ||||||
|   "app_context": { |   "app_context": { | ||||||
|     "please_wait_for_save": "请等待几秒钟以完成保存,然后您可以尝试再操作一次。" |     "please_wait_for_save": "请等待几秒钟以完成保存,然后您可以尝试再操作一次。" | ||||||
|  |   }, | ||||||
|  |   "note_create": { | ||||||
|  |     "duplicated": "笔记 \"{{title}}\" 已被复制。" | ||||||
|  |   }, | ||||||
|  |   "image": { | ||||||
|  |     "copied-to-clipboard": "图片的引用已复制到剪贴板,可以粘贴到任何文本笔记中。", | ||||||
|  |     "cannot-copy": "无法将图片引用复制到剪贴板。" | ||||||
|  |   }, | ||||||
|  |   "clipboard": { | ||||||
|  |     "cut": "笔记已剪切到剪贴板。", | ||||||
|  |     "copied": "笔记已复制到剪贴板。" | ||||||
|  |   }, | ||||||
|  |   "entrypoints": { | ||||||
|  |     "note-revision-created": "笔记修订已创建。", | ||||||
|  |     "note-executed": "笔记已执行。", | ||||||
|  |     "sql-error": "执行 SQL 查询时发生错误:{{message}}" | ||||||
|  |   }, | ||||||
|  |   "branches": { | ||||||
|  |     "cannot-move-notes-here": "无法将笔记移动到这里。", | ||||||
|  |     "delete-status": "删除状态", | ||||||
|  |     "delete-notes-in-progress": "正在删除笔记:{{count}}", | ||||||
|  |     "delete-finished-successfully": "删除成功完成。", | ||||||
|  |     "undeleting-notes-in-progress": "正在恢复删除的笔记:{{count}}", | ||||||
|  |     "undeleting-notes-finished-successfully": "恢复删除的笔记已成功完成。" | ||||||
|  |   }, | ||||||
|  |   "frontend_script_api": { | ||||||
|  |     "async_warning": "您正在将一个异步函数传递给 `api.runOnBackend()`,这可能无法按预期工作。\\n要么使该函数同步(通过移除 `async` 关键字),要么使用 `api.runAsyncOnBackendWithManualTransactionHandling()`。", | ||||||
|  |     "sync_warning": "您正在将一个同步函数传递给 `api.runAsyncOnBackendWithManualTransactionHandling()`,\\n而您可能应该使用 `api.runOnBackend()`。" | ||||||
|  |   }, | ||||||
|  |   "ws": { | ||||||
|  |     "sync-check-failed": "同步检查失败!", | ||||||
|  |     "consistency-checks-failed": "一致性检查失败!请查看日志了解详细信息。", | ||||||
|  |     "encountered-error": "遇到错误 \"{{message}}\",请查看控制台。" | ||||||
|  |   }, | ||||||
|  |   "hoisted_note": { | ||||||
|  |     "confirm_unhoisting": "请求的笔记 '{{requestedNote}}' 位于提升的笔记 '{{hoistedNote}}' 的子树之外,您必须取消提升才能访问该笔记。是否继续取消提升?" | ||||||
|  |   }, | ||||||
|  |   "launcher_context_menu": { | ||||||
|  |     "reset_launcher_confirm": "您确定要重置 \"{{title}}\" 吗?此笔记(及其子项)中的所有数据/设置将丢失,且启动器将恢复到其原始位置。", | ||||||
|  |     "add-note-launcher": "添加笔记启动器", | ||||||
|  |     "add-script-launcher": "添加脚本启动器", | ||||||
|  |     "add-custom-widget": "添加自定义小部件", | ||||||
|  |     "add-spacer": "添加间隔", | ||||||
|  |     "delete": "删除", | ||||||
|  |     "reset": "重置", | ||||||
|  |     "move-to-visible-launchers": "移动到可见启动器", | ||||||
|  |     "move-to-available-launchers": "移动到可用启动器", | ||||||
|  |     "duplicate-launcher": "复制启动器" | ||||||
|  |   }, | ||||||
|  |   "editable-text": { | ||||||
|  |     "auto-detect-language": "自动检测" | ||||||
|  |   }, | ||||||
|  |   "highlighting": { | ||||||
|  |     "title": "文本笔记的代码语法高亮", | ||||||
|  |     "description": "控制文本笔记中代码块的语法高亮,代码笔记不会受到影响。", | ||||||
|  |     "color-scheme": "颜色方案" | ||||||
|  |   }, | ||||||
|  |   "code_block": { | ||||||
|  |     "word_wrapping": "自动换行" | ||||||
|  |   }, | ||||||
|  |   "classic_editor_toolbar": { | ||||||
|  |     "title": "格式化" | ||||||
|  |   }, | ||||||
|  |   "editor": { | ||||||
|  |     "title": "编辑器" | ||||||
|  |   }, | ||||||
|  |   "editing": { | ||||||
|  |     "editor_type": { | ||||||
|  |       "label": "格式化工具栏", | ||||||
|  |       "floating": { | ||||||
|  |         "title": "浮动", | ||||||
|  |         "description": "编辑工具出现在光标附近;" | ||||||
|  |       }, | ||||||
|  |       "fixed": { | ||||||
|  |         "title": "固定", | ||||||
|  |         "description": "编辑工具出现在 \"格式化\" 功能区标签中。" | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   }, | ||||||
|  |   "electron_context_menu": { | ||||||
|  |     "add-term-to-dictionary": "将 \"{{term}}\" 添加到字典", | ||||||
|  |     "cut": "剪切", | ||||||
|  |     "copy": "复制", | ||||||
|  |     "copy-link": "复制链接", | ||||||
|  |     "paste": "粘贴", | ||||||
|  |     "paste-as-plain-text": "以纯文本粘贴", | ||||||
|  |     "search_online": "用 {{searchEngine}} 搜索 \"{{term}}\"" | ||||||
|  |   }, | ||||||
|  |   "image_context_menu": { | ||||||
|  |     "copy_reference_to_clipboard": "复制引用到剪贴板", | ||||||
|  |     "copy_image_to_clipboard": "复制图片到剪贴板" | ||||||
|  |   }, | ||||||
|  |   "link_context_menu": { | ||||||
|  |     "open_note_in_new_tab": "在新标签页中打开笔记", | ||||||
|  |     "open_note_in_new_split": "在新分屏中打开笔记", | ||||||
|  |     "open_note_in_new_window": "在新窗口中打开笔记" | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | |||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -51,7 +51,11 @@ | |||||||
|     "chosen_actions": "Chosen actions", |     "chosen_actions": "Chosen actions", | ||||||
|     "execute_bulk_actions": "Execute bulk actions", |     "execute_bulk_actions": "Execute bulk actions", | ||||||
|     "bulk_actions_executed": "Bulk actions have been executed successfully.", |     "bulk_actions_executed": "Bulk actions have been executed successfully.", | ||||||
|     "none_yet": "None yet... add an action by clicking one of the available ones above." |     "none_yet": "None yet... add an action by clicking one of the available ones above.", | ||||||
|  |     "labels": "Labels", | ||||||
|  |     "relations": "Relations", | ||||||
|  |     "notes": "Notes", | ||||||
|  |     "other": "Other" | ||||||
|   }, |   }, | ||||||
|   "clone_to": { |   "clone_to": { | ||||||
|     "clone_notes_to": "Clone notes to...", |     "clone_notes_to": "Clone notes to...", | ||||||
| @ -238,23 +242,23 @@ | |||||||
|     "confirm_undelete": "Do you want to undelete this note and its sub-notes?" |     "confirm_undelete": "Do you want to undelete this note and its sub-notes?" | ||||||
|   }, |   }, | ||||||
|   "revisions": { |   "revisions": { | ||||||
|     "note_revisions": "Note revisions", |     "note_revisions": "Note Revisions", | ||||||
|     "delete_all_revisions": "Delete all revisions of this note", |     "delete_all_revisions": "Delete all revisions of this note", | ||||||
|     "delete_all_button": "Delete all revisions", |     "delete_all_button": "Delete all revisions", | ||||||
|     "help_title": "Help on Note revisions", |     "help_title": "Help on Note Revisions", | ||||||
|     "revision_last_edited": "This revision was last edited on {{date}}", |     "revision_last_edited": "This revision was last edited on {{date}}", | ||||||
|     "confirm_delete_all": "Do you want to delete all revisions of this note? This action will erase revision title and content, but still preserve revision metadata.", |     "confirm_delete_all": "Do you want to delete all revisions of this note? This action will erase the revision title and content, but still preserve the revision metadata.", | ||||||
|     "no_revisions": "No revisions for this note yet...", |     "no_revisions": "No revisions for this note yet...", | ||||||
|     "restore_button": "Restore this revision", |     "restore_button": "Restore this revision", | ||||||
|     "confirm_restore": "Do you want to restore this revision? This will overwrite current title and content of the note with this revision.", |     "confirm_restore": "Do you want to restore this revision? This will overwrite the current title and content of the note with this revision.", | ||||||
|     "delete_button": "Delete this revision", |     "delete_button": "Delete this revision", | ||||||
|     "confirm_delete": "Do you want to delete this revision? This action will delete revision title and content, but still preserve revision metadata.", |     "confirm_delete": "Do you want to delete this revision? This action will delete the revision title and content, but still preserve the revision metadata.", | ||||||
|     "revisions_deleted": "Note revisions has been deleted.", |     "revisions_deleted": "Note revisions have been deleted.", | ||||||
|     "revision_restored": "Note revision has been restored.", |     "revision_restored": "Note revision has been restored.", | ||||||
|     "revision_deleted": "Note revision has been deleted.", |     "revision_deleted": "Note revision has been deleted.", | ||||||
|     "snapshot_interval": "Note Revisions Snapshot Interval: {{seconds}}s.", |     "snapshot_interval": "Note Revision Snapshot Interval: {{seconds}}s.", | ||||||
|     "maximum_revisions": "Maximum revisions for current note: {{number}}.", |     "maximum_revisions": "Note Revision Snapshot Limit: {{number}}.", | ||||||
|     "settings": "Settings for Note revisions", |     "settings": "Note Revision Settings", | ||||||
|     "download_button": "Download", |     "download_button": "Download", | ||||||
|     "mime": "MIME: ", |     "mime": "MIME: ", | ||||||
|     "file_size": "File size:", |     "file_size": "File size:", | ||||||
| @ -311,6 +315,7 @@ | |||||||
|     "boolean": "Boolean", |     "boolean": "Boolean", | ||||||
|     "date": "Date", |     "date": "Date", | ||||||
|     "date_time": "Date & Time", |     "date_time": "Date & Time", | ||||||
|  |     "time": "Time", | ||||||
|     "url": "URL", |     "url": "URL", | ||||||
|     "precision_title": "What number of digits after floating point should be available in the value setting interface.", |     "precision_title": "What number of digits after floating point should be available in the value setting interface.", | ||||||
|     "precision": "Precision", |     "precision": "Precision", | ||||||
| @ -1057,7 +1062,12 @@ | |||||||
|     "theme_label": "Theme", |     "theme_label": "Theme", | ||||||
|     "override_theme_fonts_label": "Override theme fonts", |     "override_theme_fonts_label": "Override theme fonts", | ||||||
|     "light_theme": "Light", |     "light_theme": "Light", | ||||||
|     "dark_theme": "Dark" |     "dark_theme": "Dark", | ||||||
|  |     "layout": "Layout", | ||||||
|  |     "layout-vertical-title": "Vertical", | ||||||
|  |     "layout-horizontal-title": "Horizontal", | ||||||
|  |     "layout-vertical-description": "launcher bar is on the left (default)", | ||||||
|  |     "layout-horizontal-description": "launcher bar is underneath the tab bar, the tab bar is now full width." | ||||||
|   }, |   }, | ||||||
|   "zoom_factor": { |   "zoom_factor": { | ||||||
|     "title": "Zoom Factor (desktop build only)", |     "title": "Zoom Factor (desktop build only)", | ||||||
| @ -1108,12 +1118,12 @@ | |||||||
|     "deleted_notes_erased": "Deleted notes have been erased." |     "deleted_notes_erased": "Deleted notes have been erased." | ||||||
|   }, |   }, | ||||||
|   "revisions_snapshot_interval": { |   "revisions_snapshot_interval": { | ||||||
|     "note_revisions_snapshot_interval_title": "Note Revisions Snapshot Interval", |     "note_revisions_snapshot_interval_title": "Note Revision Snapshot Interval", | ||||||
|     "note_revisions_snapshot_description": "Note revision snapshot time interval is time in seconds after which a new note revision will be created for the note. See <a href=\"https://triliumnext.github.io/Docs/Wiki/note-revisions.html\" class=\"external\">wiki</a> for more info.", |     "note_revisions_snapshot_description": "The Note revision snapshot interval is the time in seconds after which a new note revision will be created for the note. See <a href=\"https://triliumnext.github.io/Docs/Wiki/note-revisions.html\" class=\"external\">wiki</a> for more info.", | ||||||
|     "snapshot_time_interval_label": "Note revision snapshot time interval (in seconds):" |     "snapshot_time_interval_label": "Note revision snapshot time interval (in seconds):" | ||||||
|   }, |   }, | ||||||
|   "revisions_snapshot_limit": { |   "revisions_snapshot_limit": { | ||||||
|     "note_revisions_snapshot_limit_title": "Note Revision Snapshots Limit", |     "note_revisions_snapshot_limit_title": "Note Revision Snapshot Limit", | ||||||
|     "note_revisions_snapshot_limit_description": "The note revision snapshot number limit refers to the maximum number of revisions that can be saved for each note. Where -1 means no limit, 0 means delete all revisions. You can set the maximum revisions for a single note through the #versioningLimit label.", |     "note_revisions_snapshot_limit_description": "The note revision snapshot number limit refers to the maximum number of revisions that can be saved for each note. Where -1 means no limit, 0 means delete all revisions. You can set the maximum revisions for a single note through the #versioningLimit label.", | ||||||
|     "snapshot_number_limit_label": "Note revision snapshot number limit:", |     "snapshot_number_limit_label": "Note revision snapshot number limit:", | ||||||
|     "erase_excess_revision_snapshots": "Erase excess revision snapshots now", |     "erase_excess_revision_snapshots": "Erase excess revision snapshots now", | ||||||
| @ -1183,6 +1193,8 @@ | |||||||
|     "backup_now": "Backup now", |     "backup_now": "Backup now", | ||||||
|     "backup_database_now": "Backup database now", |     "backup_database_now": "Backup database now", | ||||||
|     "existing_backups": "Existing backups", |     "existing_backups": "Existing backups", | ||||||
|  |     "date-and-time": "Date & time", | ||||||
|  |     "path": "Path", | ||||||
|     "database_backed_up_to": "Database has been backed up to", |     "database_backed_up_to": "Database has been backed up to", | ||||||
|     "no_backup_yet": "no backup yet" |     "no_backup_yet": "no backup yet" | ||||||
|   }, |   }, | ||||||
| @ -1378,8 +1390,12 @@ | |||||||
|   }, |   }, | ||||||
|   "open-help-page": "Open help page", |   "open-help-page": "Open help page", | ||||||
|   "find": { |   "find": { | ||||||
|     "case_sensitive": "case sensitive", |     "case_sensitive": "Case sensitive", | ||||||
|     "match_words": "match words" |     "match_words": "Match words", | ||||||
|  |     "find_placeholder": "Find in text...", | ||||||
|  |     "replace_placeholder": "Replace with...", | ||||||
|  |     "replace": "Replace", | ||||||
|  |     "replace_all": "Replace all" | ||||||
|   }, |   }, | ||||||
|   "highlights_list_2": { |   "highlights_list_2": { | ||||||
|     "title": "Highlights List", |     "title": "Highlights List", | ||||||
| @ -1435,7 +1451,9 @@ | |||||||
|     "close_other_tabs": "Close other tabs", |     "close_other_tabs": "Close other tabs", | ||||||
|     "close_right_tabs": "Close tabs to the right", |     "close_right_tabs": "Close tabs to the right", | ||||||
|     "close_all_tabs": "Close all tabs", |     "close_all_tabs": "Close all tabs", | ||||||
|  |     "reopen_last_tab": "Reopen last closed tab", | ||||||
|     "move_tab_to_new_window": "Move this tab to a new window", |     "move_tab_to_new_window": "Move this tab to a new window", | ||||||
|  |     "copy_tab_to_new_window": "Copy this tab to a new window", | ||||||
|     "new_tab": "New tab" |     "new_tab": "New tab" | ||||||
|   }, |   }, | ||||||
|   "toc": { |   "toc": { | ||||||
| @ -1518,8 +1536,32 @@ | |||||||
|   "editing": { |   "editing": { | ||||||
|     "editor_type": { |     "editor_type": { | ||||||
|       "label": "Formatting toolbar", |       "label": "Formatting toolbar", | ||||||
|       "floating": "Floating (editing tools appear near the cursor)", |       "floating": { | ||||||
|       "fixed": "Fixed (editing tools appear in the \"Formatting\" ribbon tab)" |         "title": "Floating", | ||||||
|  |         "description": "editing tools appear near the cursor;" | ||||||
|  |       }, | ||||||
|  |       "fixed": { | ||||||
|  |         "title": "Fixed", | ||||||
|  |         "description": "editing tools appear in the \"Formatting\" ribbon tab." | ||||||
|  |       } | ||||||
|     } |     } | ||||||
|  |   }, | ||||||
|  |   "electron_context_menu": { | ||||||
|  |     "add-term-to-dictionary": "Add \"{{term}}\" to dictionary", | ||||||
|  |     "cut": "Cut", | ||||||
|  |     "copy": "Copy", | ||||||
|  |     "copy-link": "Copy link", | ||||||
|  |     "paste": "Paste", | ||||||
|  |     "paste-as-plain-text": "Paste as plain text", | ||||||
|  |     "search_online": "Search for \"{{term}}\" with {{searchEngine}}" | ||||||
|  |   }, | ||||||
|  |   "image_context_menu": { | ||||||
|  |     "copy_reference_to_clipboard": "Copy reference to clipboard", | ||||||
|  |     "copy_image_to_clipboard": "Copy image to clipboard" | ||||||
|  |   }, | ||||||
|  |   "link_context_menu": { | ||||||
|  |     "open_note_in_new_tab": "Open note in a new tab", | ||||||
|  |     "open_note_in_new_split": "Open note in a new split", | ||||||
|  |     "open_note_in_new_window": "Open note in a new window" | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | |||||||
| @ -51,7 +51,11 @@ | |||||||
|     "chosen_actions": "Acciones elegidas", |     "chosen_actions": "Acciones elegidas", | ||||||
|     "execute_bulk_actions": "Ejecutar acciones en bloque", |     "execute_bulk_actions": "Ejecutar acciones en bloque", | ||||||
|     "bulk_actions_executed": "Las acciones en bloque se han ejecutado con éxito.", |     "bulk_actions_executed": "Las acciones en bloque se han ejecutado con éxito.", | ||||||
|     "none_yet": "Ninguna todavía... agrega una acción haciendo clic en una de las disponibles arriba." |     "none_yet": "Ninguna todavía... agregue una acción haciendo clic en una de las disponibles arriba.", | ||||||
|  |     "labels": "Etiquetas", | ||||||
|  |     "relations": "Relaciones", | ||||||
|  |     "notes": "Notas", | ||||||
|  |     "other": "Otro" | ||||||
|   }, |   }, | ||||||
|   "clone_to": { |   "clone_to": { | ||||||
|     "clone_notes_to": "Clonar notas a...", |     "clone_notes_to": "Clonar notas a...", | ||||||
| @ -311,6 +315,7 @@ | |||||||
|     "boolean": "Booleano", |     "boolean": "Booleano", | ||||||
|     "date": "Fecha", |     "date": "Fecha", | ||||||
|     "date_time": "Fecha y hora", |     "date_time": "Fecha y hora", | ||||||
|  |     "time": "Hora", | ||||||
|     "url": "URL", |     "url": "URL", | ||||||
|     "precision_title": "Cantidad de dígitos después del punto flotante que deben estar disponibles en la interfaz de configuración del valor.", |     "precision_title": "Cantidad de dígitos después del punto flotante que deben estar disponibles en la interfaz de configuración del valor.", | ||||||
|     "precision": "Precisión", |     "precision": "Precisión", | ||||||
| @ -1057,7 +1062,12 @@ | |||||||
|     "theme_label": "Tema", |     "theme_label": "Tema", | ||||||
|     "override_theme_fonts_label": "Sobreescribir fuentes de tema", |     "override_theme_fonts_label": "Sobreescribir fuentes de tema", | ||||||
|     "light_theme": "Claro", |     "light_theme": "Claro", | ||||||
|     "dark_theme": "Oscuro" |     "dark_theme": "Oscuro", | ||||||
|  |     "layout": "Disposición", | ||||||
|  |     "layout-vertical-title": "Vertical", | ||||||
|  |     "layout-horizontal-title": "Horizontal", | ||||||
|  |     "layout-vertical-description": "la barra del lanzador está en la izquierda (por defecto)", | ||||||
|  |     "layout-horizontal-description": "la barra de lanzamiento está debajo de la barra de pestañas, la barra de pestañas ahora tiene ancho completo." | ||||||
|   }, |   }, | ||||||
|   "zoom_factor": { |   "zoom_factor": { | ||||||
|     "title": "Factor de zoom (solo versión de escritorio)", |     "title": "Factor de zoom (solo versión de escritorio)", | ||||||
| @ -1183,6 +1193,8 @@ | |||||||
|     "backup_now": "Realizar copia de seguridad ahora", |     "backup_now": "Realizar copia de seguridad ahora", | ||||||
|     "backup_database_now": "Realizar copia de seguridad de la base de datos ahora", |     "backup_database_now": "Realizar copia de seguridad de la base de datos ahora", | ||||||
|     "existing_backups": "Copias de seguridad existentes", |     "existing_backups": "Copias de seguridad existentes", | ||||||
|  |     "date-and-time": "Fecha y hora", | ||||||
|  |     "path": "Ruta", | ||||||
|     "database_backed_up_to": "Se ha realizado una copia de seguridad de la base de datos en", |     "database_backed_up_to": "Se ha realizado una copia de seguridad de la base de datos en", | ||||||
|     "no_backup_yet": "no hay copia de seguridad todavía" |     "no_backup_yet": "no hay copia de seguridad todavía" | ||||||
|   }, |   }, | ||||||
| @ -1378,8 +1390,12 @@ | |||||||
|   }, |   }, | ||||||
|   "open-help-page": "Abrir página de ayuda", |   "open-help-page": "Abrir página de ayuda", | ||||||
|   "find": { |   "find": { | ||||||
|     "case_sensitive": "distingue entre mayúsculas y minúsculas", |     "case_sensitive": "Distingue entre mayúsculas y minúsculas", | ||||||
|     "match_words": "coincidir palabras" |     "match_words": "Coincidir palabras", | ||||||
|  |     "find_placeholder": "Encontrar en texto...", | ||||||
|  |     "replace_placeholder": "Reemplazar con...", | ||||||
|  |     "replace": "Reemplazar", | ||||||
|  |     "replace_all": "Reemplazar todo" | ||||||
|   }, |   }, | ||||||
|   "highlights_list_2": { |   "highlights_list_2": { | ||||||
|     "title": "Lista de destacados", |     "title": "Lista de destacados", | ||||||
| @ -1435,7 +1451,9 @@ | |||||||
|     "close_other_tabs": "Cerrar otras pestañas", |     "close_other_tabs": "Cerrar otras pestañas", | ||||||
|     "close_right_tabs": "Cerrar pestañas a la derecha", |     "close_right_tabs": "Cerrar pestañas a la derecha", | ||||||
|     "close_all_tabs": "Cerras todas las pestañas", |     "close_all_tabs": "Cerras todas las pestañas", | ||||||
|  |     "reopen_last_tab": "Reabrir última pestaña cerrada", | ||||||
|     "move_tab_to_new_window": "Mover esta pestaña a una nueva ventana", |     "move_tab_to_new_window": "Mover esta pestaña a una nueva ventana", | ||||||
|  |     "copy_tab_to_new_window": "Copiar esta pestaña a una ventana nueva", | ||||||
|     "new_tab": "Nueva pestaña" |     "new_tab": "Nueva pestaña" | ||||||
|   }, |   }, | ||||||
|   "toc": { |   "toc": { | ||||||
| @ -1518,8 +1536,23 @@ | |||||||
|   "editing": { |   "editing": { | ||||||
|     "editor_type": { |     "editor_type": { | ||||||
|       "label": "Barra de herramientas de formato", |       "label": "Barra de herramientas de formato", | ||||||
|       "floating": "Flotante (las herramientas de edición aparecen cerca del cursor)", |       "floating": { | ||||||
|       "fixed": "Fijo (las herramientas de edición aparecen en la pestaña de la cinta \"Formato\")" |         "title": "Flotante", | ||||||
|  |         "description": "las herramientas de edición aparecen cerca del cursor;" | ||||||
|  |       }, | ||||||
|  |       "fixed": { | ||||||
|  |         "title": "Fijo", | ||||||
|  |         "description": "las herramientas de edición aparecen en la pestaña de la cinta \"Formato\")." | ||||||
|  |       } | ||||||
|     } |     } | ||||||
|  |   }, | ||||||
|  |   "electron_context_menu": { | ||||||
|  |     "add-term-to-dictionary": "Agregar \"{{term}}\" al diccionario.", | ||||||
|  |     "cut": "Cortar", | ||||||
|  |     "copy": "Copiar", | ||||||
|  |     "copy-link": "Copiar enlace", | ||||||
|  |     "paste": "Pegar", | ||||||
|  |     "paste-as-plain-text": "Pegar como texto plano", | ||||||
|  |     "search_online": "Buscar \"{{term}}\" con {{searchEngine}}" | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | |||||||
| @ -311,6 +311,7 @@ | |||||||
|     "boolean": "Booléen", |     "boolean": "Booléen", | ||||||
|     "date": "Date", |     "date": "Date", | ||||||
|     "date_time": "Date et heure", |     "date_time": "Date et heure", | ||||||
|  |     "time": "Heure", | ||||||
|     "url": "URL", |     "url": "URL", | ||||||
|     "precision_title": "Nombre de chiffres après la virgule devant être disponible dans l'interface définissant la valeur.", |     "precision_title": "Nombre de chiffres après la virgule devant être disponible dans l'interface définissant la valeur.", | ||||||
|     "precision": "Précision", |     "precision": "Précision", | ||||||
|  | |||||||
							
								
								
									
										1
									
								
								src/public/translations/pt_br/translation.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/public/translations/pt_br/translation.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | |||||||
|  | {} | ||||||
| @ -127,6 +127,7 @@ | |||||||
|     "custom_resource_provider": "a se vedea <a href=\"javascript:\" data-help-page=\"custom-request-handler.html\">Custom request handler</a>", |     "custom_resource_provider": "a se vedea <a href=\"javascript:\" data-help-page=\"custom-request-handler.html\">Custom request handler</a>", | ||||||
|     "date": "Dată", |     "date": "Dată", | ||||||
|     "date_time": "Dată și timp", |     "date_time": "Dată și timp", | ||||||
|  |     "time": "Timp", | ||||||
|     "delete": "Șterge", |     "delete": "Șterge", | ||||||
|     "digits": "număr de zecimale", |     "digits": "număr de zecimale", | ||||||
|     "disable_inclusion": "script-urile cu această etichetă nu vor fi incluse în execuția scriptului părinte.", |     "disable_inclusion": "script-urile cu această etichetă nu vor fi incluse în execuția scriptului părinte.", | ||||||
| @ -254,6 +255,8 @@ | |||||||
|     "enable_monthly_backup": "Activează copia de siguranță lunară", |     "enable_monthly_backup": "Activează copia de siguranță lunară", | ||||||
|     "enable_weekly_backup": "Activează copia de siguranță săptămânală", |     "enable_weekly_backup": "Activează copia de siguranță săptămânală", | ||||||
|     "existing_backups": "Copii de siguranță existente", |     "existing_backups": "Copii de siguranță existente", | ||||||
|  |     "date-and-time": "Data și ora", | ||||||
|  |     "path": "Calea fișierului", | ||||||
|     "no_backup_yet": "nu există încă nicio copie de siguranță" |     "no_backup_yet": "nu există încă nicio copie de siguranță" | ||||||
|   }, |   }, | ||||||
|   "basic_properties": { |   "basic_properties": { | ||||||
| @ -297,7 +300,11 @@ | |||||||
|     "close": "Închide", |     "close": "Închide", | ||||||
|     "execute_bulk_actions": "Execută acțiunile în masă", |     "execute_bulk_actions": "Execută acțiunile în masă", | ||||||
|     "include_descendants": "Include descendenții notiței selectate", |     "include_descendants": "Include descendenții notiței selectate", | ||||||
|     "none_yet": "Nicio acțiune... adaugați una printr-un click pe cele disponibile mai jos." |     "none_yet": "Nicio acțiune... adăugați una printr-un click pe cele disponibile mai jos.", | ||||||
|  |     "labels": "Etichete", | ||||||
|  |     "notes": "Notițe", | ||||||
|  |     "other": "Altele", | ||||||
|  |     "relations": "Relații" | ||||||
|   }, |   }, | ||||||
|   "calendar": { |   "calendar": { | ||||||
|     "april": "Aprilie", |     "april": "Aprilie", | ||||||
| @ -1349,7 +1356,11 @@ | |||||||
|   "open-help-page": "Deschide pagina de informații", |   "open-help-page": "Deschide pagina de informații", | ||||||
|   "find": { |   "find": { | ||||||
|     "match_words": "doar cuvinte întregi", |     "match_words": "doar cuvinte întregi", | ||||||
|     "case_sensitive": "ține cont de majuscule" |     "case_sensitive": "ține cont de majuscule", | ||||||
|  |     "replace_all": "Înlocuiește totul", | ||||||
|  |     "replace_placeholder": "Înlocuiește cu...", | ||||||
|  |     "replace": "Înlocuiește", | ||||||
|  |     "find_placeholder": "Căutați în text..." | ||||||
|   }, |   }, | ||||||
|   "highlights_list_2": { |   "highlights_list_2": { | ||||||
|     "options": "Setări", |     "options": "Setări", | ||||||
| @ -1439,7 +1450,9 @@ | |||||||
|     "close_tab": "Închide tab", |     "close_tab": "Închide tab", | ||||||
|     "move_tab_to_new_window": "Mută acest tab în altă fereastră", |     "move_tab_to_new_window": "Mută acest tab în altă fereastră", | ||||||
|     "new_tab": "Tab nou", |     "new_tab": "Tab nou", | ||||||
|     "close_right_tabs": "Închide taburile din dreapta" |     "close_right_tabs": "Închide taburile din dreapta", | ||||||
|  |     "copy_tab_to_new_window": "Copiază tab-ul într-o fereastră nouă", | ||||||
|  |     "reopen_last_tab": "Redeschide ultimul tab închis" | ||||||
|   }, |   }, | ||||||
|   "toc": { |   "toc": { | ||||||
|     "options": "Setări", |     "options": "Setări", | ||||||
| @ -1514,12 +1527,27 @@ | |||||||
|   }, |   }, | ||||||
|   "editing": { |   "editing": { | ||||||
|     "editor_type": { |     "editor_type": { | ||||||
|       "fixed": "Editor cu bară fixă (uneltele de editare vor apărea în tab-ul „Formatare” din panglică)", |       "label": "Bară de formatare", | ||||||
|       "floating": "Editor cu bară flotantă (uneltele de editare vor apărea lângă cursor)", |       "floating": { | ||||||
|       "label": "Bară de formatare" |         "title": "Editor cu bară flotantă", | ||||||
|  |         "description": "uneltele de editare vor apărea lângă cursor;" | ||||||
|  |       }, | ||||||
|  |       "fixed": { | ||||||
|  |         "title": "Editor cu bară fixă", | ||||||
|  |         "description": "uneltele de editare vor apărea în tab-ul „Formatare” din panglică." | ||||||
|  |       } | ||||||
|     } |     } | ||||||
|   }, |   }, | ||||||
|   "editor": { |   "editor": { | ||||||
|     "title": "Editor" |     "title": "Editor" | ||||||
|  |   }, | ||||||
|  |   "electron_context_menu": { | ||||||
|  |     "add-term-to-dictionary": "Adaugă „{{term}}” în dicționar", | ||||||
|  |     "copy": "Copiază", | ||||||
|  |     "copy-link": "Copiază legătura", | ||||||
|  |     "cut": "Decupează", | ||||||
|  |     "paste": "Lipește", | ||||||
|  |     "paste-as-plain-text": "Lipește doar textul", | ||||||
|  |     "search_online": "Caută „{{term}}” cu {{searchEngine}}" | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										1
									
								
								src/public/translations/tw/translation.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								src/public/translations/tw/translation.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1 @@ | |||||||
|  | {} | ||||||
| @ -66,7 +66,8 @@ const ALLOWED_OPTIONS = new Set([ | |||||||
|     'editedNotesOpenInRibbon', |     'editedNotesOpenInRibbon', | ||||||
|     'locale', |     'locale', | ||||||
|     'firstDayOfWeek', |     'firstDayOfWeek', | ||||||
|     'textNoteEditorType' |     'textNoteEditorType', | ||||||
|  |     'layoutOrientation' | ||||||
| ]); | ]); | ||||||
| 
 | 
 | ||||||
| function getOptions() { | function getOptions() { | ||||||
|  | |||||||
| @ -42,7 +42,7 @@ function index(req: Request, res: Response) { | |||||||
|         isDev: env.isDev(), |         isDev: env.isDev(), | ||||||
|         isMainWindow: !req.query.extraWindow, |         isMainWindow: !req.query.extraWindow, | ||||||
|         isProtectedSessionAvailable: protectedSessionService.isProtectedSessionAvailable(), |         isProtectedSessionAvailable: protectedSessionService.isProtectedSessionAvailable(), | ||||||
|         maxContentWidth: parseInt(options.maxContentWidth), |         maxContentWidth: Math.max(640, parseInt(options.maxContentWidth)), | ||||||
|         triliumVersion: packageJson.version, |         triliumVersion: packageJson.version, | ||||||
|         assetPath: assetPath, |         assetPath: assetPath, | ||||||
|         appPath: appPath |         appPath: appPath | ||||||
|  | |||||||
| @ -34,8 +34,17 @@ interface Item { | |||||||
|     baseSize?: string; |     baseSize?: string; | ||||||
|     growthFactor?: string; |     growthFactor?: string; | ||||||
|     targetNoteId?: "_backendLog" | "_globalNoteMap"; |     targetNoteId?: "_backendLog" | "_globalNoteMap"; | ||||||
|     builtinWidget?: "bookmarks" | "spacer" | "backInHistoryButton" | "forwardInHistoryButton" | "syncStatus" | "protectedSession" | "todayInJournal" | "calendar"; |     builtinWidget?: "bookmarks" | "spacer" | "backInHistoryButton" | "forwardInHistoryButton" | "syncStatus" | "protectedSession" | "todayInJournal" | "calendar" | "quickSearch"; | ||||||
|     command?: "jumpToNote" | "searchNotes" | "createNoteIntoInbox" | "showRecentChanges"; |     command?: keyof typeof Command; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // TODO: Move this into a commons project once the client/server architecture is well split.
 | ||||||
|  | enum Command { | ||||||
|  |     jumpToNote, | ||||||
|  |     searchNotes, | ||||||
|  |     createNoteIntoInbox, | ||||||
|  |     showRecentChanges, | ||||||
|  |     showOptions | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /* | /* | ||||||
| @ -94,7 +103,8 @@ const HIDDEN_SUBTREE_DEFINITION: Item = { | |||||||
|             type: 'contentWidget', |             type: 'contentWidget', | ||||||
|             icon: 'bx-terminal', |             icon: 'bx-terminal', | ||||||
|             attributes: [ |             attributes: [ | ||||||
|                 { type: 'label', name: 'keepCurrentHoisting' } |                 { type: 'label', name: 'keepCurrentHoisting' }, | ||||||
|  |                 { type: 'label', name: 'fullContentWidth' } | ||||||
|             ] |             ] | ||||||
|         }, |         }, | ||||||
|         { |         { | ||||||
| @ -231,8 +241,10 @@ const HIDDEN_SUBTREE_DEFINITION: Item = { | |||||||
|                         { id: '_lbBookmarks', title: 'Bookmarks', type: 'launcher', builtinWidget: 'bookmarks', icon: 'bx bx-bookmark' }, |                         { id: '_lbBookmarks', title: 'Bookmarks', type: 'launcher', builtinWidget: 'bookmarks', icon: 'bx bx-bookmark' }, | ||||||
|                         { id: '_lbToday', title: "Open Today's Journal Note", type: 'launcher', builtinWidget: 'todayInJournal', icon: 'bx bx-calendar-star' }, |                         { id: '_lbToday', title: "Open Today's Journal Note", type: 'launcher', builtinWidget: 'todayInJournal', icon: 'bx bx-calendar-star' }, | ||||||
|                         { id: '_lbSpacer2', title: 'Spacer', type: 'launcher', builtinWidget: 'spacer', baseSize: "0", growthFactor: "1" }, |                         { id: '_lbSpacer2', title: 'Spacer', type: 'launcher', builtinWidget: 'spacer', baseSize: "0", growthFactor: "1" }, | ||||||
|  |                         { id: '_lbQuickSearch', title: "Quick Search", type: "launcher", builtinWidget: "quickSearch", icon: "bx bx-rectangle" }, | ||||||
|                         { id: '_lbProtectedSession', title: 'Protected Session', type: 'launcher', builtinWidget: 'protectedSession', icon: 'bx bx bx-shield-quarter' }, |                         { id: '_lbProtectedSession', title: 'Protected Session', type: 'launcher', builtinWidget: 'protectedSession', icon: 'bx bx bx-shield-quarter' }, | ||||||
|                         { id: '_lbSyncStatus', title: 'Sync Status', type: 'launcher', builtinWidget: 'syncStatus', icon: 'bx bx-wifi' } |                         { id: '_lbSyncStatus', title: 'Sync Status', type: 'launcher', builtinWidget: 'syncStatus', icon: 'bx bx-wifi' }, | ||||||
|  |                         { id: '_lbSettings', title: 'Settings', type: 'launcher', command: 'showOptions', icon: 'bx bx-cog' } | ||||||
|                     ] |                     ] | ||||||
|                 } |                 } | ||||||
|             ] |             ] | ||||||
|  | |||||||
| @ -134,7 +134,9 @@ const defaultOptions: DefaultOption[] = [ | |||||||
|     { name: "codeBlockWordWrap", value: "false", isSynced: true }, |     { name: "codeBlockWordWrap", value: "false", isSynced: true }, | ||||||
| 
 | 
 | ||||||
|     // Text note configuration
 |     // Text note configuration
 | ||||||
|     { name: "textNoteEditorType", value: "ckeditor-balloon", isSynced: true } |     { name: "textNoteEditorType", value: "ckeditor-balloon", isSynced: true }, | ||||||
|  | 
 | ||||||
|  |     { name: "layoutOrientation", value: "vertical", isSynced: false } | ||||||
| ]; | ]; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  | |||||||
| @ -8,7 +8,7 @@ function parse(value: string): DefinitionObject { | |||||||
|         if (token === 'promoted') { |         if (token === 'promoted') { | ||||||
|             defObj.isPromoted = true; |             defObj.isPromoted = true; | ||||||
|         } |         } | ||||||
|         else if (['text', 'number', 'boolean', 'date', 'datetime', 'url'].includes(token)) { |         else if (['text', 'number', 'boolean', 'date', 'datetime', 'time', 'url'].includes(token)) { | ||||||
|             defObj.labelType = token; |             defObj.labelType = token; | ||||||
|         } |         } | ||||||
|         else if (['single', 'multi'].includes(token)) { |         else if (['single', 'multi'].includes(token)) { | ||||||
|  | |||||||
| @ -27,43 +27,52 @@ class SearchResult { | |||||||
|         this.score = 0; |         this.score = 0; | ||||||
| 
 | 
 | ||||||
|         const note = becca.notes[this.noteId]; |         const note = becca.notes[this.noteId]; | ||||||
|  |         const normalizedQuery = fulltextQuery.toLowerCase(); | ||||||
|  |         const normalizedTitle = note.title.toLowerCase(); | ||||||
| 
 | 
 | ||||||
|  |         // Note ID exact match, much higher score
 | ||||||
|         if (note.noteId.toLowerCase() === fulltextQuery) { |         if (note.noteId.toLowerCase() === fulltextQuery) { | ||||||
|             this.score += 100; |             this.score += 1000; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if (note.title.toLowerCase() === fulltextQuery) { |         // Title matching scores, make sure to always win
 | ||||||
|             this.score += 100; // high reward for exact match #3470
 |         if (normalizedTitle === normalizedQuery) { | ||||||
|  |             this.score += 2000; // Increased from 1000 to ensure exact matches always win
 | ||||||
|  |         } | ||||||
|  |         else if (normalizedTitle.startsWith(normalizedQuery)) { | ||||||
|  |             this.score += 500;  // Increased to give more weight to prefix matches
 | ||||||
|  |         } | ||||||
|  |         else if (normalizedTitle.includes(` ${normalizedQuery} `) ||  | ||||||
|  |                 normalizedTitle.startsWith(`${normalizedQuery} `) ||  | ||||||
|  |                 normalizedTitle.endsWith(` ${normalizedQuery}`)) { | ||||||
|  |             this.score += 300;  // Increased to better distinguish word matches
 | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         // notes with matches on its own note title as opposed to ancestors or descendants
 |         // Add scores for partial matches with adjusted weights
 | ||||||
|         this.addScoreForStrings(tokens, note.title, 1.5); |         this.addScoreForStrings(tokens, note.title, 2.0);  // Increased to give more weight to title matches
 | ||||||
| 
 |         this.addScoreForStrings(tokens, this.notePathTitle, 0.3); // Reduced to further de-emphasize path matches
 | ||||||
|         // matches in attributes don't get extra points and thus are implicitly valued less than note path matches
 |  | ||||||
| 
 |  | ||||||
|         this.addScoreForStrings(tokens, this.notePathTitle, 1); |  | ||||||
| 
 | 
 | ||||||
|         if (note.isInHiddenSubtree()) { |         if (note.isInHiddenSubtree()) { | ||||||
|             this.score = this.score / 2; |             this.score = this.score / 3; // Increased penalty for hidden notes
 | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     addScoreForStrings(tokens: string[], str: string, factor: number) { |     addScoreForStrings(tokens: string[], str: string, factor: number) { | ||||||
|         const chunks = str.toLowerCase().split(" "); |         const chunks = str.toLowerCase().split(" "); | ||||||
| 
 | 
 | ||||||
|         this.score = 0; |         let tokenScore = 0; | ||||||
| 
 |  | ||||||
|         for (const chunk of chunks) { |         for (const chunk of chunks) { | ||||||
|             for (const token of tokens) { |             for (const token of tokens) { | ||||||
|                 if (chunk === token) { |                 if (chunk === token) { | ||||||
|                     this.score += 4 * token.length * factor; |                     tokenScore += 4 * token.length * factor; | ||||||
|                 } else if (chunk.startsWith(token)) { |                 } else if (chunk.startsWith(token)) { | ||||||
|                     this.score += 2 * token.length * factor; |                     tokenScore += 2 * token.length * factor; | ||||||
|                 } else if (chunk.includes(token)) { |                 } else if (chunk.includes(token)) { | ||||||
|                     this.score += token.length * factor; |                     tokenScore += token.length * factor; | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |         this.score += tokenScore; | ||||||
|     } |     } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Elian Doran
						Elian Doran