mirror of
				https://github.com/zadam/trilium.git
				synced 2025-11-04 13:39:01 +01:00 
			
		
		
		
	Merge branch 'beta'
# Conflicts: # package-lock.json
This commit is contained in:
		
						commit
						99d0f22403
					
				
							
								
								
									
										0
									
								
								bin/create-anonymization-script.js
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							
							
						
						
									
										0
									
								
								bin/create-anonymization-script.js
									
									
									
									
									
										
										
										Normal file → Executable file
									
								
							@ -1,165 +1,47 @@
 | 
				
			|||||||
 | 
					 | 
				
			||||||
UPDATE etapi_tokens SET tokenHash = 'API token hash value';
 | 
					UPDATE etapi_tokens SET tokenHash = 'API token hash value';
 | 
				
			||||||
UPDATE notes SET title = 'title' WHERE noteId != 'root' AND noteId NOT LIKE '\_%' ESCAPE '\';
 | 
					UPDATE notes SET title = 'title' WHERE title NOT IN ('root', '_hidden', '_share');
 | 
				
			||||||
UPDATE blobs SET content = 'text' WHERE content IS NOT NULL;
 | 
					UPDATE blobs SET content = 'text' WHERE content IS NOT NULL;
 | 
				
			||||||
UPDATE revisions SET title = 'title';
 | 
					UPDATE revisions SET title = 'title';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
UPDATE attributes SET name = 'name', value = 'value'
 | 
					UPDATE attributes SET name  = 'name', value = 'value' WHERE type = 'label'
 | 
				
			||||||
                  WHERE type = 'label'
 | 
					  AND name NOT IN
 | 
				
			||||||
                    AND name NOT IN ('inbox',
 | 
					      ('inbox', 'disableVersioning', 'calendarRoot', 'archived', 'excludeFromExport', 'disableInclusion', 'appCss',
 | 
				
			||||||
                                     'disableVersioning',
 | 
					       'appTheme', 'hidePromotedAttributes', 'readOnly', 'autoReadOnlyDisabled', 'cssClass', 'iconClass',
 | 
				
			||||||
                                     'calendarRoot',
 | 
					       'keyboardShortcut', 'run', 'runOnInstance', 'runAtHour', 'customRequestHandler', 'customResourceProvider',
 | 
				
			||||||
                                     'archived',
 | 
					       'widget', 'noteInfoWidgetDisabled', 'linkMapWidgetDisabled', 'revisionsWidgetDisabled',
 | 
				
			||||||
                                     'excludeFromExport',
 | 
					       'whatLinksHereWidgetDisabled', 'similarNotesWidgetDisabled', 'workspace', 'workspaceIconClass',
 | 
				
			||||||
                                     'disableInclusion',
 | 
					       'workspaceTabBackgroundColor', 'workspaceCalendarRoot', 'workspaceTemplate', 'searchHome', 'workspaceInbox',
 | 
				
			||||||
                                     'appCss',
 | 
					       'workspaceSearchHome', 'sqlConsoleHome', 'datePattern', 'pageSize', 'viewType', 'mapRootNoteId',
 | 
				
			||||||
                                     'appTheme',
 | 
					       'bookmarkFolder', 'sorted', 'sortDirection', 'sortFoldersFirst', 'sortNatural', 'sortLocale', 'top',
 | 
				
			||||||
                                     'hidePromotedAttributes',
 | 
					       'fullContentWidth', 'shareHiddenFromTree', 'shareOmitDefaultCss', 'shareRoot', 'shareDescription',
 | 
				
			||||||
                                     'readOnly',
 | 
					       'shareRaw', 'shareDisallowRobotIndexing', 'shareIndex', 'displayRelations', 'hideRelations', 'titleTemplate',
 | 
				
			||||||
                                     'autoReadOnlyDisabled',
 | 
					       'template', 'toc', 'color', 'keepCurrentHoisting', 'executeButton', 'executeDescription', 'newNotesOnTop',
 | 
				
			||||||
                                     'cssClass',
 | 
					       'clipperInbox', 'internalLink', 'imageLink', 'relationMapLink', 'includeMapLink', 'runOnNoteCreation',
 | 
				
			||||||
                                     'iconClass',
 | 
					       'runOnNoteTitleChange', 'runOnNoteChange', 'runOnNoteContentChange', 'runOnNoteDeletion', 'runOnBranchCreation',
 | 
				
			||||||
                                     'keyboardShortcut',
 | 
					       'runOnBranchDeletion', 'runOnChildNoteCreation', 'runOnAttributeCreation', 'runOnAttributeChange', 'template',
 | 
				
			||||||
                                     'run',
 | 
					       'inherit', 'widget', 'renderNote', 'shareCss', 'shareJs', 'shareFavicon');
 | 
				
			||||||
                                     'runOnInstance',
 | 
					UPDATE attributes SET name = 'name' WHERE type = 'relation'
 | 
				
			||||||
                                     'runAtHour',
 | 
					  AND name NOT IN
 | 
				
			||||||
                                     'customRequestHandler',
 | 
					      ('inbox', 'disableVersioning', 'calendarRoot', 'archived', 'excludeFromExport', 'disableInclusion', 'appCss',
 | 
				
			||||||
                                     'customResourceProvider',
 | 
					       'appTheme', 'hidePromotedAttributes', 'readOnly', 'autoReadOnlyDisabled', 'cssClass', 'iconClass',
 | 
				
			||||||
                                     'widget',
 | 
					       'keyboardShortcut', 'run', 'runOnInstance', 'runAtHour', 'customRequestHandler', 'customResourceProvider',
 | 
				
			||||||
                                     'noteInfoWidgetDisabled',
 | 
					       'widget', 'noteInfoWidgetDisabled', 'linkMapWidgetDisabled', 'revisionsWidgetDisabled',
 | 
				
			||||||
                                     'linkMapWidgetDisabled',
 | 
					       'whatLinksHereWidgetDisabled', 'similarNotesWidgetDisabled', 'workspace', 'workspaceIconClass',
 | 
				
			||||||
                                     'revisionsWidgetDisabled',
 | 
					       'workspaceTabBackgroundColor', 'workspaceCalendarRoot', 'workspaceTemplate', 'searchHome', 'workspaceInbox',
 | 
				
			||||||
                                     'whatLinksHereWidgetDisabled',
 | 
					       'workspaceSearchHome', 'sqlConsoleHome', 'datePattern', 'pageSize', 'viewType', 'mapRootNoteId',
 | 
				
			||||||
                                     'similarNotesWidgetDisabled',
 | 
					       'bookmarkFolder', 'sorted', 'sortDirection', 'sortFoldersFirst', 'sortNatural', 'sortLocale', 'top',
 | 
				
			||||||
                                     'workspace',
 | 
					       'fullContentWidth', 'shareHiddenFromTree', 'shareOmitDefaultCss', 'shareRoot', 'shareDescription',
 | 
				
			||||||
                                     'workspaceIconClass',
 | 
					       'shareRaw', 'shareDisallowRobotIndexing', 'shareIndex', 'displayRelations', 'hideRelations', 'titleTemplate',
 | 
				
			||||||
                                     'workspaceTabBackgroundColor',
 | 
					       'template', 'toc', 'color', 'keepCurrentHoisting', 'executeButton', 'executeDescription', 'newNotesOnTop',
 | 
				
			||||||
                                     'searchHome',
 | 
					       'clipperInbox', 'internalLink', 'imageLink', 'relationMapLink', 'includeMapLink', 'runOnNoteCreation',
 | 
				
			||||||
                                     'workspaceInbox',
 | 
					       'runOnNoteTitleChange', 'runOnNoteChange', 'runOnNoteContentChange', 'runOnNoteDeletion', 'runOnBranchCreation',
 | 
				
			||||||
                                     'workspaceSearchHome',
 | 
					       'runOnBranchDeletion', 'runOnChildNoteCreation', 'runOnAttributeCreation', 'runOnAttributeChange', 'template',
 | 
				
			||||||
                                     'sqlConsoleHome',
 | 
					       'inherit', 'widget', 'renderNote', 'shareCss', 'shareJs', 'shareFavicon');
 | 
				
			||||||
                                     'datePattern',
 | 
					 | 
				
			||||||
                                     'pageSize',
 | 
					 | 
				
			||||||
                                     'viewType',
 | 
					 | 
				
			||||||
                                     'mapRootNoteId',
 | 
					 | 
				
			||||||
                                     'bookmarkFolder',
 | 
					 | 
				
			||||||
                                     'sorted',
 | 
					 | 
				
			||||||
                                     'top',
 | 
					 | 
				
			||||||
                                     'fullContentWidth',
 | 
					 | 
				
			||||||
                                     'shareHiddenFromTree',
 | 
					 | 
				
			||||||
                                     'shareAlias',
 | 
					 | 
				
			||||||
                                     'shareOmitDefaultCss',
 | 
					 | 
				
			||||||
                                     'shareRoot',
 | 
					 | 
				
			||||||
                                     'internalLink',
 | 
					 | 
				
			||||||
                                     'imageLink',
 | 
					 | 
				
			||||||
                                     'relationMapLink',
 | 
					 | 
				
			||||||
                                     'includeMapLink',
 | 
					 | 
				
			||||||
                                     'runOnNoteCreation',
 | 
					 | 
				
			||||||
                                     'runOnNoteTitleChange',
 | 
					 | 
				
			||||||
                                     'runOnNoteContentChange',
 | 
					 | 
				
			||||||
                                     'runOnNoteChange',
 | 
					 | 
				
			||||||
                                     'runOnChildNoteCreation',
 | 
					 | 
				
			||||||
                                     'runOnAttributeCreation',
 | 
					 | 
				
			||||||
                                     'runOnAttributeChange',
 | 
					 | 
				
			||||||
                                     'template',
 | 
					 | 
				
			||||||
                                     'inherit',
 | 
					 | 
				
			||||||
                                     'widget',
 | 
					 | 
				
			||||||
                                     'renderNote',
 | 
					 | 
				
			||||||
                                     'shareCss',
 | 
					 | 
				
			||||||
                                     'shareJs',
 | 
					 | 
				
			||||||
                                     'shareFavicon',
 | 
					 | 
				
			||||||
                                     'executeButton',
 | 
					 | 
				
			||||||
                                     'keepCurrentHoisting',
 | 
					 | 
				
			||||||
                                     'color',
 | 
					 | 
				
			||||||
                                     'toc',
 | 
					 | 
				
			||||||
                                     'excludeFromNoteMap',
 | 
					 | 
				
			||||||
                                     'docName',
 | 
					 | 
				
			||||||
                                     'launcherType',
 | 
					 | 
				
			||||||
                                     'builtinWidget',
 | 
					 | 
				
			||||||
                                     'baseSize',
 | 
					 | 
				
			||||||
                                     'growthFactor'
 | 
					 | 
				
			||||||
                      );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
UPDATE attributes SET name = 'name'
 | 
					 | 
				
			||||||
                    AND name NOT IN ('inbox',
 | 
					 | 
				
			||||||
                                     'disableVersioning',
 | 
					 | 
				
			||||||
                                     'calendarRoot',
 | 
					 | 
				
			||||||
                                     'archived',
 | 
					 | 
				
			||||||
                                     'excludeFromExport',
 | 
					 | 
				
			||||||
                                     'disableInclusion',
 | 
					 | 
				
			||||||
                                     'appCss',
 | 
					 | 
				
			||||||
                                     'appTheme',
 | 
					 | 
				
			||||||
                                     'hidePromotedAttributes',
 | 
					 | 
				
			||||||
                                     'readOnly',
 | 
					 | 
				
			||||||
                                     'autoReadOnlyDisabled',
 | 
					 | 
				
			||||||
                                     'cssClass',
 | 
					 | 
				
			||||||
                                     'iconClass',
 | 
					 | 
				
			||||||
                                     'keyboardShortcut',
 | 
					 | 
				
			||||||
                                     'run',
 | 
					 | 
				
			||||||
                                     'runOnInstance',
 | 
					 | 
				
			||||||
                                     'runAtHour',
 | 
					 | 
				
			||||||
                                     'customRequestHandler',
 | 
					 | 
				
			||||||
                                     'customResourceProvider',
 | 
					 | 
				
			||||||
                                     'widget',
 | 
					 | 
				
			||||||
                                     'noteInfoWidgetDisabled',
 | 
					 | 
				
			||||||
                                     'linkMapWidgetDisabled',
 | 
					 | 
				
			||||||
                                     'revisionsWidgetDisabled',
 | 
					 | 
				
			||||||
                                     'whatLinksHereWidgetDisabled',
 | 
					 | 
				
			||||||
                                     'similarNotesWidgetDisabled',
 | 
					 | 
				
			||||||
                                     'workspace',
 | 
					 | 
				
			||||||
                                     'workspaceIconClass',
 | 
					 | 
				
			||||||
                                     'workspaceTabBackgroundColor',
 | 
					 | 
				
			||||||
                                     'searchHome',
 | 
					 | 
				
			||||||
                                     'workspaceInbox',
 | 
					 | 
				
			||||||
                                     'workspaceSearchHome',
 | 
					 | 
				
			||||||
                                     'sqlConsoleHome',
 | 
					 | 
				
			||||||
                                     'datePattern',
 | 
					 | 
				
			||||||
                                     'pageSize',
 | 
					 | 
				
			||||||
                                     'viewType',
 | 
					 | 
				
			||||||
                                     'mapRootNoteId',
 | 
					 | 
				
			||||||
                                     'bookmarkFolder',
 | 
					 | 
				
			||||||
                                     'sorted',
 | 
					 | 
				
			||||||
                                     'top',
 | 
					 | 
				
			||||||
                                     'fullContentWidth',
 | 
					 | 
				
			||||||
                                     'shareHiddenFromTree',
 | 
					 | 
				
			||||||
                                     'shareAlias',
 | 
					 | 
				
			||||||
                                     'shareOmitDefaultCss',
 | 
					 | 
				
			||||||
                                     'shareRoot',
 | 
					 | 
				
			||||||
                                     'internalLink',
 | 
					 | 
				
			||||||
                                     'imageLink',
 | 
					 | 
				
			||||||
                                     'relationMapLink',
 | 
					 | 
				
			||||||
                                     'includeMapLink',
 | 
					 | 
				
			||||||
                                     'runOnNoteCreation',
 | 
					 | 
				
			||||||
                                     'runOnNoteTitleChange',
 | 
					 | 
				
			||||||
                                     'runOnNoteContentChange',
 | 
					 | 
				
			||||||
                                     'runOnNoteChange',
 | 
					 | 
				
			||||||
                                     'runOnChildNoteCreation',
 | 
					 | 
				
			||||||
                                     'runOnAttributeCreation',
 | 
					 | 
				
			||||||
                                     'runOnAttributeChange',
 | 
					 | 
				
			||||||
                                     'template',
 | 
					 | 
				
			||||||
                                     'inherit',
 | 
					 | 
				
			||||||
                                     'widget',
 | 
					 | 
				
			||||||
                                     'renderNote',
 | 
					 | 
				
			||||||
                                     'shareCss',
 | 
					 | 
				
			||||||
                                     'shareJs',
 | 
					 | 
				
			||||||
                                     'shareFavicon',
 | 
					 | 
				
			||||||
                                     'executeButton',
 | 
					 | 
				
			||||||
                                     'keepCurrentHoisting',
 | 
					 | 
				
			||||||
                                     'color',
 | 
					 | 
				
			||||||
                                     'toc',
 | 
					 | 
				
			||||||
                                     'excludeFromNoteMap',
 | 
					 | 
				
			||||||
                                     'docName',
 | 
					 | 
				
			||||||
                                     'launcherType',
 | 
					 | 
				
			||||||
                                     'builtinWidget',
 | 
					 | 
				
			||||||
                                     'baseSize',
 | 
					 | 
				
			||||||
                                     'growthFactor'
 | 
					 | 
				
			||||||
                                    );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
UPDATE branches SET prefix = 'prefix' WHERE prefix IS NOT NULL AND prefix != 'recovered';
 | 
					UPDATE branches SET prefix = 'prefix' WHERE prefix IS NOT NULL AND prefix != 'recovered';
 | 
				
			||||||
UPDATE options SET value = 'anonymized' WHERE name IN
 | 
					UPDATE options SET value = 'anonymized' WHERE name IN
 | 
				
			||||||
                    ('documentId', 'documentSecret', 'encryptedDataKey',
 | 
					      ('documentId', 'documentSecret', 'encryptedDataKey',
 | 
				
			||||||
                     'passwordVerificationHash', 'passwordVerificationSalt',
 | 
					       'passwordVerificationHash', 'passwordVerificationSalt',
 | 
				
			||||||
                     'passwordDerivedKeySalt', 'username', 'syncServerHost', 'syncProxy')
 | 
					       'passwordDerivedKeySalt', 'username', 'syncServerHost', 'syncProxy')
 | 
				
			||||||
                      AND value != '';
 | 
					  AND value != '';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
VACUUM;
 | 
					VACUUM;
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										1
									
								
								db/migrations/0226__rename_noteSize_label.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								db/migrations/0226__rename_noteSize_label.sql
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					UPDATE attributes SET value = 'contentAndAttachmentsAndRevisionsSize' WHERE name = 'orderBy' AND value = 'noteSize';
 | 
				
			||||||
@ -1,3 +1,5 @@
 | 
				
			|||||||
 | 
					# Running `docker-compose up` will create/use the "trilium-data" directory in the user home
 | 
				
			||||||
 | 
					# Run `TRILIUM_DATA_DIR=/path/of/your/choice docker-compose up` to set a different directory
 | 
				
			||||||
version: '2.1'
 | 
					version: '2.1'
 | 
				
			||||||
services:
 | 
					services:
 | 
				
			||||||
  trilium:
 | 
					  trilium:
 | 
				
			||||||
@ -8,7 +10,7 @@ services:
 | 
				
			|||||||
    ports:
 | 
					    ports:
 | 
				
			||||||
      - "8080:8080"
 | 
					      - "8080:8080"
 | 
				
			||||||
    volumes:
 | 
					    volumes:
 | 
				
			||||||
      - trilium:/home/node/trilium-data
 | 
					      - ${TRILIUM_DATA_DIR:-~/trilium-data}:/home/node/trilium-data
 | 
				
			||||||
 | 
					
 | 
				
			||||||
volumes:
 | 
					volumes:
 | 
				
			||||||
  trilium:
 | 
					  trilium:
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										2
									
								
								libraries/codemirror/addon/lint/eslint.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								libraries/codemirror/addon/lint/eslint.js
									
									
									
									
										vendored
									
									
								
							@ -46,7 +46,7 @@
 | 
				
			|||||||
        const errors = new eslint().verify(text, {
 | 
					        const errors = new eslint().verify(text, {
 | 
				
			||||||
            root: true,
 | 
					            root: true,
 | 
				
			||||||
            parserOptions: {
 | 
					            parserOptions: {
 | 
				
			||||||
                ecmaVersion: 2022
 | 
					                ecmaVersion: "latest"
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            extends: ['eslint:recommended', 'airbnb-base'],
 | 
					            extends: ['eslint:recommended', 'airbnb-base'],
 | 
				
			||||||
            env: {
 | 
					            env: {
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										13
									
								
								libraries/codemirror/addon/lint/lint.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										13
									
								
								libraries/codemirror/addon/lint/lint.js
									
									
									
									
										vendored
									
									
								
							@ -24,8 +24,10 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    function position(e) {
 | 
					    function position(e) {
 | 
				
			||||||
      if (!tt.parentNode) return CodeMirror.off(document, "mousemove", position);
 | 
					      if (!tt.parentNode) return CodeMirror.off(document, "mousemove", position);
 | 
				
			||||||
      tt.style.top = Math.max(0, e.clientY - tt.offsetHeight - 5) + "px";
 | 
					      var top = Math.max(0, e.clientY - tt.offsetHeight - 5);
 | 
				
			||||||
      tt.style.left = (e.clientX + 5) + "px";
 | 
					      var left = Math.max(0, Math.min(e.clientX + 5, tt.ownerDocument.defaultView.innerWidth - tt.offsetWidth));
 | 
				
			||||||
 | 
					      tt.style.top = top + "px"
 | 
				
			||||||
 | 
					      tt.style.left = left + "px";
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    CodeMirror.on(document, "mousemove", position);
 | 
					    CodeMirror.on(document, "mousemove", position);
 | 
				
			||||||
    position(e);
 | 
					    position(e);
 | 
				
			||||||
@ -199,10 +201,6 @@
 | 
				
			|||||||
      var anns = annotations[line];
 | 
					      var anns = annotations[line];
 | 
				
			||||||
      if (!anns) continue;
 | 
					      if (!anns) continue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // filter out duplicate messages
 | 
					 | 
				
			||||||
      var message = [];
 | 
					 | 
				
			||||||
      anns = anns.filter(function(item) { return message.indexOf(item.message) > -1 ? false : message.push(item.message) });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      var maxSeverity = null;
 | 
					      var maxSeverity = null;
 | 
				
			||||||
      var tipLabel = state.hasGutter && document.createDocumentFragment();
 | 
					      var tipLabel = state.hasGutter && document.createDocumentFragment();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -220,9 +218,8 @@
 | 
				
			|||||||
          __annotation: ann
 | 
					          __annotation: ann
 | 
				
			||||||
        }));
 | 
					        }));
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      // use original annotations[line] to show multiple messages
 | 
					 | 
				
			||||||
      if (state.hasGutter)
 | 
					      if (state.hasGutter)
 | 
				
			||||||
        cm.setGutterMarker(line, GUTTER_ID, makeMarker(cm, tipLabel, maxSeverity, annotations[line].length > 1,
 | 
					        cm.setGutterMarker(line, GUTTER_ID, makeMarker(cm, tipLabel, maxSeverity, anns.length > 1,
 | 
				
			||||||
                                                       options.tooltips));
 | 
					                                                       options.tooltips));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (options.highlightLines)
 | 
					      if (options.highlightLines)
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										136
									
								
								libraries/codemirror/addon/mode/multiplex.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										136
									
								
								libraries/codemirror/addon/mode/multiplex.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,136 @@
 | 
				
			|||||||
 | 
					// CodeMirror, copyright (c) by Marijn Haverbeke and others
 | 
				
			||||||
 | 
					// Distributed under an MIT license: https://codemirror.net/5/LICENSE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(function(mod) {
 | 
				
			||||||
 | 
					  if (typeof exports == "object" && typeof module == "object") // CommonJS
 | 
				
			||||||
 | 
					    mod(require("../../lib/codemirror"));
 | 
				
			||||||
 | 
					  else if (typeof define == "function" && define.amd) // AMD
 | 
				
			||||||
 | 
					    define(["../../lib/codemirror"], mod);
 | 
				
			||||||
 | 
					  else // Plain browser env
 | 
				
			||||||
 | 
					    mod(CodeMirror);
 | 
				
			||||||
 | 
					})(function(CodeMirror) {
 | 
				
			||||||
 | 
					"use strict";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CodeMirror.multiplexingMode = function(outer /*, others */) {
 | 
				
			||||||
 | 
					  // Others should be {open, close, mode [, delimStyle] [, innerStyle] [, parseDelimiters]} objects
 | 
				
			||||||
 | 
					  var others = Array.prototype.slice.call(arguments, 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  function indexOf(string, pattern, from, returnEnd) {
 | 
				
			||||||
 | 
					    if (typeof pattern == "string") {
 | 
				
			||||||
 | 
					      var found = string.indexOf(pattern, from);
 | 
				
			||||||
 | 
					      return returnEnd && found > -1 ? found + pattern.length : found;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    var m = pattern.exec(from ? string.slice(from) : string);
 | 
				
			||||||
 | 
					    return m ? m.index + from + (returnEnd ? m[0].length : 0) : -1;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    startState: function() {
 | 
				
			||||||
 | 
					      return {
 | 
				
			||||||
 | 
					        outer: CodeMirror.startState(outer),
 | 
				
			||||||
 | 
					        innerActive: null,
 | 
				
			||||||
 | 
					        inner: null,
 | 
				
			||||||
 | 
					        startingInner: false
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    copyState: function(state) {
 | 
				
			||||||
 | 
					      return {
 | 
				
			||||||
 | 
					        outer: CodeMirror.copyState(outer, state.outer),
 | 
				
			||||||
 | 
					        innerActive: state.innerActive,
 | 
				
			||||||
 | 
					        inner: state.innerActive && CodeMirror.copyState(state.innerActive.mode, state.inner),
 | 
				
			||||||
 | 
					        startingInner: state.startingInner
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    token: function(stream, state) {
 | 
				
			||||||
 | 
					      if (!state.innerActive) {
 | 
				
			||||||
 | 
					        var cutOff = Infinity, oldContent = stream.string;
 | 
				
			||||||
 | 
					        for (var i = 0; i < others.length; ++i) {
 | 
				
			||||||
 | 
					          var other = others[i];
 | 
				
			||||||
 | 
					          var found = indexOf(oldContent, other.open, stream.pos);
 | 
				
			||||||
 | 
					          if (found == stream.pos) {
 | 
				
			||||||
 | 
					            if (!other.parseDelimiters) stream.match(other.open);
 | 
				
			||||||
 | 
					            state.startingInner = !!other.parseDelimiters
 | 
				
			||||||
 | 
					            state.innerActive = other;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            // Get the outer indent, making sure to handle CodeMirror.Pass
 | 
				
			||||||
 | 
					            var outerIndent = 0;
 | 
				
			||||||
 | 
					            if (outer.indent) {
 | 
				
			||||||
 | 
					              var possibleOuterIndent = outer.indent(state.outer, "", "");
 | 
				
			||||||
 | 
					              if (possibleOuterIndent !== CodeMirror.Pass) outerIndent = possibleOuterIndent;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            state.inner = CodeMirror.startState(other.mode, outerIndent);
 | 
				
			||||||
 | 
					            return other.delimStyle && (other.delimStyle + " " + other.delimStyle + "-open");
 | 
				
			||||||
 | 
					          } else if (found != -1 && found < cutOff) {
 | 
				
			||||||
 | 
					            cutOff = found;
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (cutOff != Infinity) stream.string = oldContent.slice(0, cutOff);
 | 
				
			||||||
 | 
					        var outerToken = outer.token(stream, state.outer);
 | 
				
			||||||
 | 
					        if (cutOff != Infinity) stream.string = oldContent;
 | 
				
			||||||
 | 
					        return outerToken;
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        var curInner = state.innerActive, oldContent = stream.string;
 | 
				
			||||||
 | 
					        if (!curInner.close && stream.sol()) {
 | 
				
			||||||
 | 
					          state.innerActive = state.inner = null;
 | 
				
			||||||
 | 
					          return this.token(stream, state);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        var found = curInner.close && !state.startingInner ?
 | 
				
			||||||
 | 
					            indexOf(oldContent, curInner.close, stream.pos, curInner.parseDelimiters) : -1;
 | 
				
			||||||
 | 
					        if (found == stream.pos && !curInner.parseDelimiters) {
 | 
				
			||||||
 | 
					          stream.match(curInner.close);
 | 
				
			||||||
 | 
					          state.innerActive = state.inner = null;
 | 
				
			||||||
 | 
					          return curInner.delimStyle && (curInner.delimStyle + " " + curInner.delimStyle + "-close");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        if (found > -1) stream.string = oldContent.slice(0, found);
 | 
				
			||||||
 | 
					        var innerToken = curInner.mode.token(stream, state.inner);
 | 
				
			||||||
 | 
					        if (found > -1) stream.string = oldContent;
 | 
				
			||||||
 | 
					        else if (stream.pos > stream.start) state.startingInner = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (found == stream.pos && curInner.parseDelimiters)
 | 
				
			||||||
 | 
					          state.innerActive = state.inner = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (curInner.innerStyle) {
 | 
				
			||||||
 | 
					          if (innerToken) innerToken = innerToken + " " + curInner.innerStyle;
 | 
				
			||||||
 | 
					          else innerToken = curInner.innerStyle;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return innerToken;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    indent: function(state, textAfter, line) {
 | 
				
			||||||
 | 
					      var mode = state.innerActive ? state.innerActive.mode : outer;
 | 
				
			||||||
 | 
					      if (!mode.indent) return CodeMirror.Pass;
 | 
				
			||||||
 | 
					      return mode.indent(state.innerActive ? state.inner : state.outer, textAfter, line);
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    blankLine: function(state) {
 | 
				
			||||||
 | 
					      var mode = state.innerActive ? state.innerActive.mode : outer;
 | 
				
			||||||
 | 
					      if (mode.blankLine) {
 | 
				
			||||||
 | 
					        mode.blankLine(state.innerActive ? state.inner : state.outer);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (!state.innerActive) {
 | 
				
			||||||
 | 
					        for (var i = 0; i < others.length; ++i) {
 | 
				
			||||||
 | 
					          var other = others[i];
 | 
				
			||||||
 | 
					          if (other.open === "\n") {
 | 
				
			||||||
 | 
					            state.innerActive = other;
 | 
				
			||||||
 | 
					            state.inner = CodeMirror.startState(other.mode, mode.indent ? mode.indent(state.outer, "", "") : 0);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      } else if (state.innerActive.close === "\n") {
 | 
				
			||||||
 | 
					        state.innerActive = state.inner = null;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    electricChars: outer.electricChars,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    innerMode: function(state) {
 | 
				
			||||||
 | 
					      return state.inner ? {state: state.inner, mode: state.innerActive.mode} : {state: state.outer, mode: outer};
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										90
									
								
								libraries/codemirror/addon/mode/overlay.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								libraries/codemirror/addon/mode/overlay.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,90 @@
 | 
				
			|||||||
 | 
					// CodeMirror, copyright (c) by Marijn Haverbeke and others
 | 
				
			||||||
 | 
					// Distributed under an MIT license: https://codemirror.net/5/LICENSE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Utility function that allows modes to be combined. The mode given
 | 
				
			||||||
 | 
					// as the base argument takes care of most of the normal mode
 | 
				
			||||||
 | 
					// functionality, but a second (typically simple) mode is used, which
 | 
				
			||||||
 | 
					// can override the style of text. Both modes get to parse all of the
 | 
				
			||||||
 | 
					// text, but when both assign a non-null style to a piece of code, the
 | 
				
			||||||
 | 
					// overlay wins, unless the combine argument was true and not overridden,
 | 
				
			||||||
 | 
					// or state.overlay.combineTokens was true, in which case the styles are
 | 
				
			||||||
 | 
					// combined.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					(function(mod) {
 | 
				
			||||||
 | 
					  if (typeof exports == "object" && typeof module == "object") // CommonJS
 | 
				
			||||||
 | 
					    mod(require("../../lib/codemirror"));
 | 
				
			||||||
 | 
					  else if (typeof define == "function" && define.amd) // AMD
 | 
				
			||||||
 | 
					    define(["../../lib/codemirror"], mod);
 | 
				
			||||||
 | 
					  else // Plain browser env
 | 
				
			||||||
 | 
					    mod(CodeMirror);
 | 
				
			||||||
 | 
					})(function(CodeMirror) {
 | 
				
			||||||
 | 
					"use strict";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CodeMirror.overlayMode = function(base, overlay, combine) {
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    startState: function() {
 | 
				
			||||||
 | 
					      return {
 | 
				
			||||||
 | 
					        base: CodeMirror.startState(base),
 | 
				
			||||||
 | 
					        overlay: CodeMirror.startState(overlay),
 | 
				
			||||||
 | 
					        basePos: 0, baseCur: null,
 | 
				
			||||||
 | 
					        overlayPos: 0, overlayCur: null,
 | 
				
			||||||
 | 
					        streamSeen: null
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    copyState: function(state) {
 | 
				
			||||||
 | 
					      return {
 | 
				
			||||||
 | 
					        base: CodeMirror.copyState(base, state.base),
 | 
				
			||||||
 | 
					        overlay: CodeMirror.copyState(overlay, state.overlay),
 | 
				
			||||||
 | 
					        basePos: state.basePos, baseCur: null,
 | 
				
			||||||
 | 
					        overlayPos: state.overlayPos, overlayCur: null
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    token: function(stream, state) {
 | 
				
			||||||
 | 
					      if (stream != state.streamSeen ||
 | 
				
			||||||
 | 
					          Math.min(state.basePos, state.overlayPos) < stream.start) {
 | 
				
			||||||
 | 
					        state.streamSeen = stream;
 | 
				
			||||||
 | 
					        state.basePos = state.overlayPos = stream.start;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (stream.start == state.basePos) {
 | 
				
			||||||
 | 
					        state.baseCur = base.token(stream, state.base);
 | 
				
			||||||
 | 
					        state.basePos = stream.pos;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (stream.start == state.overlayPos) {
 | 
				
			||||||
 | 
					        stream.pos = stream.start;
 | 
				
			||||||
 | 
					        state.overlayCur = overlay.token(stream, state.overlay);
 | 
				
			||||||
 | 
					        state.overlayPos = stream.pos;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      stream.pos = Math.min(state.basePos, state.overlayPos);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // state.overlay.combineTokens always takes precedence over combine,
 | 
				
			||||||
 | 
					      // unless set to null
 | 
				
			||||||
 | 
					      if (state.overlayCur == null) return state.baseCur;
 | 
				
			||||||
 | 
					      else if (state.baseCur != null &&
 | 
				
			||||||
 | 
					               state.overlay.combineTokens ||
 | 
				
			||||||
 | 
					               combine && state.overlay.combineTokens == null)
 | 
				
			||||||
 | 
					        return state.baseCur + " " + state.overlayCur;
 | 
				
			||||||
 | 
					      else return state.overlayCur;
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    indent: base.indent && function(state, textAfter, line) {
 | 
				
			||||||
 | 
					      return base.indent(state.base, textAfter, line);
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    electricChars: base.electricChars,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    innerMode: function(state) { return {state: state.base, mode: base}; },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    blankLine: function(state) {
 | 
				
			||||||
 | 
					      var baseToken, overlayToken;
 | 
				
			||||||
 | 
					      if (base.blankLine) baseToken = base.blankLine(state.base);
 | 
				
			||||||
 | 
					      if (overlay.blankLine) overlayToken = overlay.blankLine(state.overlay);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return overlayToken == null ?
 | 
				
			||||||
 | 
					        baseToken :
 | 
				
			||||||
 | 
					        (combine && baseToken != null ? baseToken + " " + overlayToken : overlayToken);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
@ -8259,8 +8259,8 @@
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  function disableBrowserMagic(field, spellcheck, autocorrect, autocapitalize) {
 | 
					  function disableBrowserMagic(field, spellcheck, autocorrect, autocapitalize) {
 | 
				
			||||||
    field.setAttribute("autocorrect", autocorrect ? "" : "off");
 | 
					    field.setAttribute("autocorrect", autocorrect ? "on" : "off");
 | 
				
			||||||
    field.setAttribute("autocapitalize", autocapitalize ? "" : "off");
 | 
					    field.setAttribute("autocapitalize", autocapitalize ? "on" : "off");
 | 
				
			||||||
    field.setAttribute("spellcheck", !!spellcheck);
 | 
					    field.setAttribute("spellcheck", !!spellcheck);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -8275,7 +8275,6 @@
 | 
				
			|||||||
    else { te.setAttribute("wrap", "off"); }
 | 
					    else { te.setAttribute("wrap", "off"); }
 | 
				
			||||||
    // If border: 0; -- iOS fails to open keyboard (issue #1287)
 | 
					    // If border: 0; -- iOS fails to open keyboard (issue #1287)
 | 
				
			||||||
    if (ios) { te.style.border = "1px solid black"; }
 | 
					    if (ios) { te.style.border = "1px solid black"; }
 | 
				
			||||||
    disableBrowserMagic(te);
 | 
					 | 
				
			||||||
    return div
 | 
					    return div
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -8897,6 +8896,7 @@
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
      // Old-fashioned briefly-focus-a-textarea hack
 | 
					      // Old-fashioned briefly-focus-a-textarea hack
 | 
				
			||||||
      var kludge = hiddenTextarea(), te = kludge.firstChild;
 | 
					      var kludge = hiddenTextarea(), te = kludge.firstChild;
 | 
				
			||||||
 | 
					      disableBrowserMagic(te);
 | 
				
			||||||
      cm.display.lineSpace.insertBefore(kludge, cm.display.lineSpace.firstChild);
 | 
					      cm.display.lineSpace.insertBefore(kludge, cm.display.lineSpace.firstChild);
 | 
				
			||||||
      te.value = lastCopied.text.join("\n");
 | 
					      te.value = lastCopied.text.join("\n");
 | 
				
			||||||
      var hadFocus = activeElt(div.ownerDocument);
 | 
					      var hadFocus = activeElt(div.ownerDocument);
 | 
				
			||||||
@ -9461,6 +9461,8 @@
 | 
				
			|||||||
    // The semihidden textarea that is focused when the editor is
 | 
					    // The semihidden textarea that is focused when the editor is
 | 
				
			||||||
    // focused, and receives input.
 | 
					    // focused, and receives input.
 | 
				
			||||||
    this.textarea = this.wrapper.firstChild;
 | 
					    this.textarea = this.wrapper.firstChild;
 | 
				
			||||||
 | 
					    var opts = this.cm.options;
 | 
				
			||||||
 | 
					    disableBrowserMagic(this.textarea, opts.spellcheck, opts.autocorrect, opts.autocapitalize);
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  TextareaInput.prototype.screenReaderLabelChanged = function (label) {
 | 
					  TextareaInput.prototype.screenReaderLabelChanged = function (label) {
 | 
				
			||||||
@ -9865,7 +9867,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  addLegacyProps(CodeMirror);
 | 
					  addLegacyProps(CodeMirror);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  CodeMirror.version = "5.65.9";
 | 
					  CodeMirror.version = "5.65.15";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return CodeMirror;
 | 
					  return CodeMirror;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										10
									
								
								libraries/codemirror/mode/clike/clike.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										10
									
								
								libraries/codemirror/mode/clike/clike.js
									
									
									
									
										vendored
									
									
								
							@ -218,7 +218,8 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
 | 
				
			|||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    indent: function(state, textAfter) {
 | 
					    indent: function(state, textAfter) {
 | 
				
			||||||
      if (state.tokenize != tokenBase && state.tokenize != null || state.typeAtEndOfLine) return CodeMirror.Pass;
 | 
					      if (state.tokenize != tokenBase && state.tokenize != null || state.typeAtEndOfLine && isTopScope(state.context))
 | 
				
			||||||
 | 
					        return CodeMirror.Pass;
 | 
				
			||||||
      var ctx = state.context, firstChar = textAfter && textAfter.charAt(0);
 | 
					      var ctx = state.context, firstChar = textAfter && textAfter.charAt(0);
 | 
				
			||||||
      var closing = firstChar == ctx.type;
 | 
					      var closing = firstChar == ctx.type;
 | 
				
			||||||
      if (ctx.type == "statement" && firstChar == "}") ctx = ctx.prev;
 | 
					      if (ctx.type == "statement" && firstChar == "}") ctx = ctx.prev;
 | 
				
			||||||
@ -512,8 +513,8 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
 | 
				
			|||||||
    name: "clike",
 | 
					    name: "clike",
 | 
				
			||||||
    keywords: words("abstract as async await base break case catch checked class const continue" +
 | 
					    keywords: words("abstract as async await base break case catch checked class const continue" +
 | 
				
			||||||
                    " default delegate do else enum event explicit extern finally fixed for" +
 | 
					                    " default delegate do else enum event explicit extern finally fixed for" +
 | 
				
			||||||
                    " foreach goto if implicit in interface internal is lock namespace new" +
 | 
					                    " foreach goto if implicit in init interface internal is lock namespace new" +
 | 
				
			||||||
                    " operator out override params private protected public readonly ref return sealed" +
 | 
					                    " operator out override params private protected public readonly record ref required return sealed" +
 | 
				
			||||||
                    " sizeof stackalloc static struct switch this throw try typeof unchecked" +
 | 
					                    " sizeof stackalloc static struct switch this throw try typeof unchecked" +
 | 
				
			||||||
                    " unsafe using virtual void volatile while add alias ascending descending dynamic from get" +
 | 
					                    " unsafe using virtual void volatile while add alias ascending descending dynamic from get" +
 | 
				
			||||||
                    " global group into join let orderby partial remove select set value var yield"),
 | 
					                    " global group into join let orderby partial remove select set value var yield"),
 | 
				
			||||||
@ -522,7 +523,7 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
 | 
				
			|||||||
                 " UInt64 bool byte char decimal double short int long object"  +
 | 
					                 " UInt64 bool byte char decimal double short int long object"  +
 | 
				
			||||||
                 " sbyte float string ushort uint ulong"),
 | 
					                 " sbyte float string ushort uint ulong"),
 | 
				
			||||||
    blockKeywords: words("catch class do else finally for foreach if struct switch try while"),
 | 
					    blockKeywords: words("catch class do else finally for foreach if struct switch try while"),
 | 
				
			||||||
    defKeywords: words("class interface namespace struct var"),
 | 
					    defKeywords: words("class interface namespace record struct var"),
 | 
				
			||||||
    typeFirstDefinitions: true,
 | 
					    typeFirstDefinitions: true,
 | 
				
			||||||
    atoms: words("true false null"),
 | 
					    atoms: words("true false null"),
 | 
				
			||||||
    hooks: {
 | 
					    hooks: {
 | 
				
			||||||
@ -613,6 +614,7 @@ CodeMirror.defineMode("clike", function(config, parserConfig) {
 | 
				
			|||||||
        return state.tokenize(stream, state);
 | 
					        return state.tokenize(stream, state);
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
      "'": function(stream) {
 | 
					      "'": function(stream) {
 | 
				
			||||||
 | 
					        if (stream.match(/^(\\[^'\s]+|[^\\'])'/)) return "string-2"
 | 
				
			||||||
        stream.eatWhile(/[\w\$_\xa1-\uffff]/);
 | 
					        stream.eatWhile(/[\w\$_\xa1-\uffff]/);
 | 
				
			||||||
        return "atom";
 | 
					        return "atom";
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										8
									
								
								libraries/codemirror/mode/dart/dart.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								libraries/codemirror/mode/dart/dart.js
									
									
									
									
										vendored
									
									
								
							@ -12,10 +12,10 @@
 | 
				
			|||||||
  "use strict";
 | 
					  "use strict";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  var keywords = ("this super static final const abstract class extends external factory " +
 | 
					  var keywords = ("this super static final const abstract class extends external factory " +
 | 
				
			||||||
    "implements mixin get native set typedef with enum throw rethrow " +
 | 
					    "implements mixin get native set typedef with enum throw rethrow assert break case " +
 | 
				
			||||||
    "assert break case continue default in return new deferred async await covariant " +
 | 
					    "continue default in return new deferred async await covariant try catch finally " +
 | 
				
			||||||
    "try catch finally do else for if switch while import library export " +
 | 
					    "do else for if switch while import library export part of show hide is as extension " +
 | 
				
			||||||
    "part of show hide is as extension on yield late required").split(" ");
 | 
					    "on yield late required sealed base interface when inline").split(" ");
 | 
				
			||||||
  var blockKeywords = "try catch finally do else for if switch while".split(" ");
 | 
					  var blockKeywords = "try catch finally do else for if switch while".split(" ");
 | 
				
			||||||
  var atoms = "true false null".split(" ");
 | 
					  var atoms = "true false null".split(" ");
 | 
				
			||||||
  var builtins = "void bool num int double dynamic var String Null Never".split(" ");
 | 
					  var builtins = "void bool num int double dynamic var String Null Never".split(" ");
 | 
				
			||||||
 | 
				
			|||||||
@ -779,7 +779,7 @@ CodeMirror.defineMode("javascript", function(config, parserConfig) {
 | 
				
			|||||||
    if (type == "async" ||
 | 
					    if (type == "async" ||
 | 
				
			||||||
        (type == "variable" &&
 | 
					        (type == "variable" &&
 | 
				
			||||||
         (value == "static" || value == "get" || value == "set" || (isTS && isModifier(value))) &&
 | 
					         (value == "static" || value == "get" || value == "set" || (isTS && isModifier(value))) &&
 | 
				
			||||||
         cx.stream.match(/^\s+[\w$\xa1-\uffff]/, false))) {
 | 
					         cx.stream.match(/^\s+#?[\w$\xa1-\uffff]/, false))) {
 | 
				
			||||||
      cx.marked = "keyword";
 | 
					      cx.marked = "keyword";
 | 
				
			||||||
      return cont(classBody);
 | 
					      return cont(classBody);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										2
									
								
								libraries/codemirror/mode/nsis/nsis.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								libraries/codemirror/mode/nsis/nsis.js
									
									
									
									
										vendored
									
									
								
							@ -24,7 +24,7 @@ CodeMirror.defineSimpleMode("nsis",{
 | 
				
			|||||||
    { regex: /`(?:[^\\`]|\\.)*`?/, token: "string" },
 | 
					    { regex: /`(?:[^\\`]|\\.)*`?/, token: "string" },
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Compile Time Commands
 | 
					    // Compile Time Commands
 | 
				
			||||||
    {regex: /^\s*(?:\!(addincludedir|addplugindir|appendfile|cd|define|delfile|echo|error|execute|finalize|getdllversion|gettlbversion|include|insertmacro|macro|macroend|makensis|packhdr|pragma|searchparse|searchreplace|system|tempfile|undef|uninstfinalize|verbose|warning))\b/i, token: "keyword"},
 | 
					    {regex: /^\s*(?:\!(addincludedir|addplugindir|appendfile|assert|cd|define|delfile|echo|error|execute|finalize|getdllversion|gettlbversion|include|insertmacro|macro|macroend|makensis|packhdr|pragma|searchparse|searchreplace|system|tempfile|undef|uninstfinalize|verbose|warning))\b/i, token: "keyword"},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Conditional Compilation
 | 
					    // Conditional Compilation
 | 
				
			||||||
    {regex: /^\s*(?:\!(if(?:n?def)?|ifmacron?def|macro))\b/i, token: "keyword", indent: true},
 | 
					    {regex: /^\s*(?:\!(if(?:n?def)?|ifmacron?def|macro))\b/i, token: "keyword", indent: true},
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										3
									
								
								libraries/codemirror/mode/pegjs/pegjs.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								libraries/codemirror/mode/pegjs/pegjs.js
									
									
									
									
										vendored
									
									
								
							@ -31,8 +31,6 @@ CodeMirror.defineMode("pegjs", function (config) {
 | 
				
			|||||||
      };
 | 
					      };
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    token: function (stream, state) {
 | 
					    token: function (stream, state) {
 | 
				
			||||||
      if (stream)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      //check for state changes
 | 
					      //check for state changes
 | 
				
			||||||
      if (!state.inString && !state.inComment && ((stream.peek() == '"') || (stream.peek() == "'"))) {
 | 
					      if (!state.inString && !state.inComment && ((stream.peek() == '"') || (stream.peek() == "'"))) {
 | 
				
			||||||
        state.stringType = stream.peek();
 | 
					        state.stringType = stream.peek();
 | 
				
			||||||
@ -43,7 +41,6 @@ CodeMirror.defineMode("pegjs", function (config) {
 | 
				
			|||||||
        state.inComment = true;
 | 
					        state.inComment = true;
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      //return state
 | 
					 | 
				
			||||||
      if (state.inString) {
 | 
					      if (state.inString) {
 | 
				
			||||||
        while (state.inString && !stream.eol()) {
 | 
					        while (state.inString && !stream.eol()) {
 | 
				
			||||||
          if (stream.peek() === state.stringType) {
 | 
					          if (stream.peek() === state.stringType) {
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										6
									
								
								libraries/codemirror/mode/python/python.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								libraries/codemirror/mode/python/python.js
									
									
									
									
										vendored
									
									
								
							@ -20,7 +20,7 @@
 | 
				
			|||||||
                        "def", "del", "elif", "else", "except", "finally",
 | 
					                        "def", "del", "elif", "else", "except", "finally",
 | 
				
			||||||
                        "for", "from", "global", "if", "import",
 | 
					                        "for", "from", "global", "if", "import",
 | 
				
			||||||
                        "lambda", "pass", "raise", "return",
 | 
					                        "lambda", "pass", "raise", "return",
 | 
				
			||||||
                        "try", "while", "with", "yield", "in"];
 | 
					                        "try", "while", "with", "yield", "in", "False", "True"];
 | 
				
			||||||
  var commonBuiltins = ["abs", "all", "any", "bin", "bool", "bytearray", "callable", "chr",
 | 
					  var commonBuiltins = ["abs", "all", "any", "bin", "bool", "bytearray", "callable", "chr",
 | 
				
			||||||
                        "classmethod", "compile", "complex", "delattr", "dict", "dir", "divmod",
 | 
					                        "classmethod", "compile", "complex", "delattr", "dict", "dir", "divmod",
 | 
				
			||||||
                        "enumerate", "eval", "filter", "float", "format", "frozenset",
 | 
					                        "enumerate", "eval", "filter", "float", "format", "frozenset",
 | 
				
			||||||
@ -60,7 +60,7 @@
 | 
				
			|||||||
    if (py3) {
 | 
					    if (py3) {
 | 
				
			||||||
      // since http://legacy.python.org/dev/peps/pep-0465/ @ is also an operator
 | 
					      // since http://legacy.python.org/dev/peps/pep-0465/ @ is also an operator
 | 
				
			||||||
      var identifiers = parserConf.identifiers|| /^[_A-Za-z\u00A1-\uFFFF][_A-Za-z0-9\u00A1-\uFFFF]*/;
 | 
					      var identifiers = parserConf.identifiers|| /^[_A-Za-z\u00A1-\uFFFF][_A-Za-z0-9\u00A1-\uFFFF]*/;
 | 
				
			||||||
      myKeywords = myKeywords.concat(["nonlocal", "False", "True", "None", "async", "await"]);
 | 
					      myKeywords = myKeywords.concat(["nonlocal", "None", "aiter", "anext", "async", "await", "breakpoint", "match", "case"]);
 | 
				
			||||||
      myBuiltins = myBuiltins.concat(["ascii", "bytes", "exec", "print"]);
 | 
					      myBuiltins = myBuiltins.concat(["ascii", "bytes", "exec", "print"]);
 | 
				
			||||||
      var stringPrefixes = new RegExp("^(([rbuf]|(br)|(rb)|(fr)|(rf))?('{3}|\"{3}|['\"]))", "i");
 | 
					      var stringPrefixes = new RegExp("^(([rbuf]|(br)|(rb)|(fr)|(rf))?('{3}|\"{3}|['\"]))", "i");
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
@ -68,7 +68,7 @@
 | 
				
			|||||||
      myKeywords = myKeywords.concat(["exec", "print"]);
 | 
					      myKeywords = myKeywords.concat(["exec", "print"]);
 | 
				
			||||||
      myBuiltins = myBuiltins.concat(["apply", "basestring", "buffer", "cmp", "coerce", "execfile",
 | 
					      myBuiltins = myBuiltins.concat(["apply", "basestring", "buffer", "cmp", "coerce", "execfile",
 | 
				
			||||||
                                      "file", "intern", "long", "raw_input", "reduce", "reload",
 | 
					                                      "file", "intern", "long", "raw_input", "reduce", "reload",
 | 
				
			||||||
                                      "unichr", "unicode", "xrange", "False", "True", "None"]);
 | 
					                                      "unichr", "unicode", "xrange", "None"]);
 | 
				
			||||||
      var stringPrefixes = new RegExp("^(([rubf]|(ur)|(br))?('{3}|\"{3}|['\"]))", "i");
 | 
					      var stringPrefixes = new RegExp("^(([rubf]|(ur)|(br))?('{3}|\"{3}|['\"]))", "i");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    var keywords = wordRegexp(myKeywords);
 | 
					    var keywords = wordRegexp(myKeywords);
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										23
									
								
								libraries/codemirror/mode/sparql/sparql.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										23
									
								
								libraries/codemirror/mode/sparql/sparql.js
									
									
									
									
										vendored
									
									
								
							@ -33,6 +33,9 @@ CodeMirror.defineMode("sparql", function(config) {
 | 
				
			|||||||
                             "true", "false", "with",
 | 
					                             "true", "false", "with",
 | 
				
			||||||
                             "data", "copy", "to", "move", "add", "create", "drop", "clear", "load", "into"]);
 | 
					                             "data", "copy", "to", "move", "add", "create", "drop", "clear", "load", "into"]);
 | 
				
			||||||
  var operatorChars = /[*+\-<>=&|\^\/!\?]/;
 | 
					  var operatorChars = /[*+\-<>=&|\^\/!\?]/;
 | 
				
			||||||
 | 
					  var PN_CHARS = "[A-Za-z_\\-0-9]";
 | 
				
			||||||
 | 
					  var PREFIX_START = new RegExp("[A-Za-z]");
 | 
				
			||||||
 | 
					  var PREFIX_REMAINDER = new RegExp("((" + PN_CHARS + "|\\.)*(" + PN_CHARS + "))?:");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  function tokenBase(stream, state) {
 | 
					  function tokenBase(stream, state) {
 | 
				
			||||||
    var ch = stream.next();
 | 
					    var ch = stream.next();
 | 
				
			||||||
@ -71,20 +74,18 @@ CodeMirror.defineMode("sparql", function(config) {
 | 
				
			|||||||
      stream.eatWhile(/[a-z\d\-]/i);
 | 
					      stream.eatWhile(/[a-z\d\-]/i);
 | 
				
			||||||
      return "meta";
 | 
					      return "meta";
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    else {
 | 
					    else if (PREFIX_START.test(ch) && stream.match(PREFIX_REMAINDER)) {
 | 
				
			||||||
      stream.eatWhile(/[_\w\d]/);
 | 
					 | 
				
			||||||
      if (stream.eat(":")) {
 | 
					 | 
				
			||||||
        eatPnLocal(stream);
 | 
					        eatPnLocal(stream);
 | 
				
			||||||
        return "atom";
 | 
					        return "atom";
 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      var word = stream.current();
 | 
					 | 
				
			||||||
      if (ops.test(word))
 | 
					 | 
				
			||||||
        return "builtin";
 | 
					 | 
				
			||||||
      else if (keywords.test(word))
 | 
					 | 
				
			||||||
        return "keyword";
 | 
					 | 
				
			||||||
      else
 | 
					 | 
				
			||||||
        return "variable";
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					    stream.eatWhile(/[_\w\d]/);
 | 
				
			||||||
 | 
					    var word = stream.current();
 | 
				
			||||||
 | 
					    if (ops.test(word))
 | 
				
			||||||
 | 
					      return "builtin";
 | 
				
			||||||
 | 
					    else if (keywords.test(word))
 | 
				
			||||||
 | 
					      return "keyword";
 | 
				
			||||||
 | 
					    else
 | 
				
			||||||
 | 
					      return "variable";
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  function eatPnLocal(stream) {
 | 
					  function eatPnLocal(stream) {
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										26
									
								
								libraries/codemirror/mode/sql/sql.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										26
									
								
								libraries/codemirror/mode/sql/sql.js
									
									
									
									
										vendored
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							
							
								
								
									
										2
									
								
								libraries/codemirror/mode/yaml/yaml.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								libraries/codemirror/mode/yaml/yaml.js
									
									
									
									
										vendored
									
									
								
							@ -85,7 +85,7 @@ CodeMirror.defineMode("yaml", function() {
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      /* pairs (associative arrays) -> key */
 | 
					      /* pairs (associative arrays) -> key */
 | 
				
			||||||
      if (!state.pair && stream.match(/^\s*(?:[,\[\]{}&*!|>'"%@`][^\s'":]|[^,\[\]{}#&*!|>'"%@`])[^#]*?(?=\s*:($|\s))/)) {
 | 
					      if (!state.pair && stream.match(/^\s*(?:[,\[\]{}&*!|>'"%@`][^\s'":]|[^\s,\[\]{}#&*!|>'"%@`])[^#:]*(?=:($|\s))/)) {
 | 
				
			||||||
        state.pair = true;
 | 
					        state.pair = true;
 | 
				
			||||||
        state.keyCol = stream.indentation();
 | 
					        state.keyCol = stream.indentation();
 | 
				
			||||||
        return "atom";
 | 
					        return "atom";
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										14
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								package.json
									
									
									
									
									
								
							@ -2,7 +2,7 @@
 | 
				
			|||||||
  "name": "trilium",
 | 
					  "name": "trilium",
 | 
				
			||||||
  "productName": "Trilium Notes",
 | 
					  "productName": "Trilium Notes",
 | 
				
			||||||
  "description": "Trilium Notes",
 | 
					  "description": "Trilium Notes",
 | 
				
			||||||
  "version": "0.61.7-beta",
 | 
					  "version": "0.61.8-beta",
 | 
				
			||||||
  "license": "AGPL-3.0-only",
 | 
					  "license": "AGPL-3.0-only",
 | 
				
			||||||
  "main": "electron.js",
 | 
					  "main": "electron.js",
 | 
				
			||||||
  "bin": {
 | 
					  "bin": {
 | 
				
			||||||
@ -36,7 +36,7 @@
 | 
				
			|||||||
    "@excalidraw/excalidraw": "0.16.1",
 | 
					    "@excalidraw/excalidraw": "0.16.1",
 | 
				
			||||||
    "archiver": "5.3.1",
 | 
					    "archiver": "5.3.1",
 | 
				
			||||||
    "async-mutex": "0.4.0",
 | 
					    "async-mutex": "0.4.0",
 | 
				
			||||||
    "axios": "1.5.0",
 | 
					    "axios": "1.5.1",
 | 
				
			||||||
    "better-sqlite3": "8.4.0",
 | 
					    "better-sqlite3": "8.4.0",
 | 
				
			||||||
    "chokidar": "3.5.3",
 | 
					    "chokidar": "3.5.3",
 | 
				
			||||||
    "cls-hooked": "4.2.2",
 | 
					    "cls-hooked": "4.2.2",
 | 
				
			||||||
@ -68,7 +68,7 @@
 | 
				
			|||||||
    "jimp": "0.22.10",
 | 
					    "jimp": "0.22.10",
 | 
				
			||||||
    "joplin-turndown-plugin-gfm": "1.0.12",
 | 
					    "joplin-turndown-plugin-gfm": "1.0.12",
 | 
				
			||||||
    "jsdom": "22.1.0",
 | 
					    "jsdom": "22.1.0",
 | 
				
			||||||
    "marked": "9.0.3",
 | 
					    "marked": "9.1.0",
 | 
				
			||||||
    "mime-types": "2.1.35",
 | 
					    "mime-types": "2.1.35",
 | 
				
			||||||
    "multer": "1.4.5-lts.1",
 | 
					    "multer": "1.4.5-lts.1",
 | 
				
			||||||
    "node-abi": "3.47.0",
 | 
					    "node-abi": "3.47.0",
 | 
				
			||||||
@ -78,11 +78,11 @@
 | 
				
			|||||||
    "react": "18.2.0",
 | 
					    "react": "18.2.0",
 | 
				
			||||||
    "react-dom": "18.2.0",
 | 
					    "react-dom": "18.2.0",
 | 
				
			||||||
    "request": "2.88.2",
 | 
					    "request": "2.88.2",
 | 
				
			||||||
    "rimraf": "5.0.1",
 | 
					    "rimraf": "5.0.5",
 | 
				
			||||||
    "safe-compare": "1.1.4",
 | 
					    "safe-compare": "1.1.4",
 | 
				
			||||||
    "sanitize-filename": "1.6.3",
 | 
					    "sanitize-filename": "1.6.3",
 | 
				
			||||||
    "sanitize-html": "2.11.0",
 | 
					    "sanitize-html": "2.11.0",
 | 
				
			||||||
    "sax": "1.2.4",
 | 
					    "sax": "1.3.0",
 | 
				
			||||||
    "semver": "7.5.4",
 | 
					    "semver": "7.5.4",
 | 
				
			||||||
    "serve-favicon": "2.5.0",
 | 
					    "serve-favicon": "2.5.0",
 | 
				
			||||||
    "session-file-store": "1.5.0",
 | 
					    "session-file-store": "1.5.0",
 | 
				
			||||||
@ -97,11 +97,11 @@
 | 
				
			|||||||
  },
 | 
					  },
 | 
				
			||||||
  "devDependencies": {
 | 
					  "devDependencies": {
 | 
				
			||||||
    "cross-env": "7.0.3",
 | 
					    "cross-env": "7.0.3",
 | 
				
			||||||
    "electron": "25.8.2",
 | 
					    "electron": "25.9.0",
 | 
				
			||||||
    "electron-builder": "24.6.4",
 | 
					    "electron-builder": "24.6.4",
 | 
				
			||||||
    "electron-packager": "17.1.2",
 | 
					    "electron-packager": "17.1.2",
 | 
				
			||||||
    "electron-rebuild": "3.2.9",
 | 
					    "electron-rebuild": "3.2.9",
 | 
				
			||||||
    "eslint": "8.49.0",
 | 
					    "eslint": "8.50.0",
 | 
				
			||||||
    "eslint-config-airbnb-base": "15.0.0",
 | 
					    "eslint-config-airbnb-base": "15.0.0",
 | 
				
			||||||
    "eslint-config-prettier": "9.0.0",
 | 
					    "eslint-config-prettier": "9.0.0",
 | 
				
			||||||
    "eslint-plugin-import": "2.28.1",
 | 
					    "eslint-plugin-import": "2.28.1",
 | 
				
			||||||
 | 
				
			|||||||
@ -208,6 +208,7 @@ class Becca {
 | 
				
			|||||||
        return this.etapiTokens[etapiTokenId];
 | 
					        return this.etapiTokens[etapiTokenId];
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** @returns {AbstractBeccaEntity|null} */
 | 
				
			||||||
    getEntity(entityName, entityId) {
 | 
					    getEntity(entityName, entityId) {
 | 
				
			||||||
        if (!entityName || !entityId) {
 | 
					        if (!entityName || !entityId) {
 | 
				
			||||||
            return null;
 | 
					            return null;
 | 
				
			||||||
 | 
				
			|||||||
@ -63,10 +63,10 @@ function load() {
 | 
				
			|||||||
    log.info(`Becca (note cache) load took ${Date.now() - start}ms`);
 | 
					    log.info(`Becca (note cache) load took ${Date.now() - start}ms`);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function reload() {
 | 
					function reload(reason) {
 | 
				
			||||||
    load();
 | 
					    load();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    require('../services/ws').reloadFrontend();
 | 
					    require('../services/ws').reloadFrontend(reason || "becca reloaded");
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
eventService.subscribeBeccaLoader([eventService.ENTITY_CHANGE_SYNCED],  ({entityName, entityRow}) => {
 | 
					eventService.subscribeBeccaLoader([eventService.ENTITY_CHANGE_SYNCED],  ({entityName, entityRow}) => {
 | 
				
			||||||
 | 
				
			|||||||
@ -18,31 +18,11 @@ let becca = null;
 | 
				
			|||||||
class AbstractBeccaEntity {
 | 
					class AbstractBeccaEntity {
 | 
				
			||||||
    /** @protected */
 | 
					    /** @protected */
 | 
				
			||||||
    beforeSaving() {
 | 
					    beforeSaving() {
 | 
				
			||||||
        this.generateIdIfNecessary();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /** @protected */
 | 
					 | 
				
			||||||
    generateIdIfNecessary() {
 | 
					 | 
				
			||||||
        if (!this[this.constructor.primaryKeyName]) {
 | 
					        if (!this[this.constructor.primaryKeyName]) {
 | 
				
			||||||
            this[this.constructor.primaryKeyName] = utils.newEntityId();
 | 
					            this[this.constructor.primaryKeyName] = utils.newEntityId();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /** @protected */
 | 
					 | 
				
			||||||
    generateHash(isDeleted = false) {
 | 
					 | 
				
			||||||
        let contentToHash = "";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        for (const propertyName of this.constructor.hashedProperties) {
 | 
					 | 
				
			||||||
            contentToHash += `|${this[propertyName]}`;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (isDeleted) {
 | 
					 | 
				
			||||||
            contentToHash += "|deleted";
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return utils.hash(contentToHash).substr(0, 10);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    /** @protected */
 | 
					    /** @protected */
 | 
				
			||||||
    getUtcDateChanged() {
 | 
					    getUtcDateChanged() {
 | 
				
			||||||
        return this.utcDateModified || this.utcDateCreated;
 | 
					        return this.utcDateModified || this.utcDateCreated;
 | 
				
			||||||
@ -61,7 +41,7 @@ class AbstractBeccaEntity {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /** @protected */
 | 
					    /** @protected */
 | 
				
			||||||
    putEntityChange(isDeleted = false) {
 | 
					    putEntityChange(isDeleted) {
 | 
				
			||||||
        entityChangesService.putEntityChange({
 | 
					        entityChangesService.putEntityChange({
 | 
				
			||||||
            entityName: this.constructor.entityName,
 | 
					            entityName: this.constructor.entityName,
 | 
				
			||||||
            entityId: this[this.constructor.primaryKeyName],
 | 
					            entityId: this[this.constructor.primaryKeyName],
 | 
				
			||||||
@ -72,11 +52,37 @@ class AbstractBeccaEntity {
 | 
				
			|||||||
        });
 | 
					        });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @protected
 | 
				
			||||||
 | 
					     * @returns {string}
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    generateHash(isDeleted) {
 | 
				
			||||||
 | 
					        let contentToHash = "";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for (const propertyName of this.constructor.hashedProperties) {
 | 
				
			||||||
 | 
					            contentToHash += `|${this[propertyName]}`;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (isDeleted) {
 | 
				
			||||||
 | 
					            contentToHash += "|deleted";
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return utils.hash(contentToHash).substr(0, 10);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /** @protected */
 | 
					    /** @protected */
 | 
				
			||||||
    getPojoToSave() {
 | 
					    getPojoToSave() {
 | 
				
			||||||
        return this.getPojo();
 | 
					        return this.getPojo();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @protected
 | 
				
			||||||
 | 
					     * @abstract
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    getPojo() {
 | 
				
			||||||
 | 
					        throw new Error(`Unimplemented getPojo() for entity '${this.constructor.name}'`)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * Saves entity - executes SQL, but doesn't commit the transaction on its own
 | 
					     * Saves entity - executes SQL, but doesn't commit the transaction on its own
 | 
				
			||||||
     *
 | 
					     *
 | 
				
			||||||
@ -88,9 +94,7 @@ class AbstractBeccaEntity {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        const isNewEntity = !this[primaryKeyName];
 | 
					        const isNewEntity = !this[primaryKeyName];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (this.beforeSaving) {
 | 
					        this.beforeSaving(opts);
 | 
				
			||||||
            this.beforeSaving(opts);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const pojo = this.getPojoToSave();
 | 
					        const pojo = this.getPojoToSave();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -101,7 +105,7 @@ class AbstractBeccaEntity {
 | 
				
			|||||||
                return;
 | 
					                return;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            this.putEntityChange(false);
 | 
					            this.putEntityChange(!!this.isDeleted);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (!cls.isEntityEventsDisabled()) {
 | 
					            if (!cls.isEntityEventsDisabled()) {
 | 
				
			||||||
                const eventPayload = {
 | 
					                const eventPayload = {
 | 
				
			||||||
 | 
				
			|||||||
@ -20,8 +20,7 @@ const attachmentRoleToNoteTypeMapping = {
 | 
				
			|||||||
class BAttachment extends AbstractBeccaEntity {
 | 
					class BAttachment extends AbstractBeccaEntity {
 | 
				
			||||||
    static get entityName() { return "attachments"; }
 | 
					    static get entityName() { return "attachments"; }
 | 
				
			||||||
    static get primaryKeyName() { return "attachmentId"; }
 | 
					    static get primaryKeyName() { return "attachmentId"; }
 | 
				
			||||||
    static get hashedProperties() { return ["attachmentId", "ownerId", "role", "mime", "title", "blobId",
 | 
					    static get hashedProperties() { return ["attachmentId", "ownerId", "role", "mime", "title", "blobId", "utcDateScheduledForErasureSince"]; }
 | 
				
			||||||
                                            "utcDateScheduledForErasureSince", "utcDateModified"]; }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    constructor(row) {
 | 
					    constructor(row) {
 | 
				
			||||||
        super();
 | 
					        super();
 | 
				
			||||||
 | 
				
			|||||||
@ -150,11 +150,17 @@ class BNote extends AbstractBeccaEntity {
 | 
				
			|||||||
         */
 | 
					         */
 | 
				
			||||||
        this.contentSize = null;
 | 
					        this.contentSize = null;
 | 
				
			||||||
        /**
 | 
					        /**
 | 
				
			||||||
         * size of the content and note revision contents in bytes
 | 
					         * size of the note content, attachment contents in bytes
 | 
				
			||||||
         * @type {int|null}
 | 
					         * @type {int|null}
 | 
				
			||||||
         * @private
 | 
					         * @private
 | 
				
			||||||
         */
 | 
					         */
 | 
				
			||||||
        this.noteSize = null;
 | 
					        this.contentAndAttachmentsSize = null;
 | 
				
			||||||
 | 
					        /**
 | 
				
			||||||
 | 
					         * size of the note content, attachment contents and revision contents in bytes
 | 
				
			||||||
 | 
					         * @type {int|null}
 | 
				
			||||||
 | 
					         * @private
 | 
				
			||||||
 | 
					         */
 | 
				
			||||||
 | 
					        this.contentAndAttachmentsAndRevisionsSize = null;
 | 
				
			||||||
        /**
 | 
					        /**
 | 
				
			||||||
         * number of note revisions for this note
 | 
					         * number of note revisions for this note
 | 
				
			||||||
         * @type {int|null}
 | 
					         * @type {int|null}
 | 
				
			||||||
@ -1625,16 +1631,12 @@ class BNote extends AbstractBeccaEntity {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            revision.save(); // to generate revisionId, which is then used to save attachments
 | 
					            revision.save(); // to generate revisionId, which is then used to save attachments
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (this.type === 'text') {
 | 
					            for (const noteAttachment of this.getAttachments()) {
 | 
				
			||||||
                for (const noteAttachment of this.getAttachments()) {
 | 
					                const revisionAttachment = noteAttachment.copy();
 | 
				
			||||||
                    if (noteAttachment.utcDateScheduledForErasureSince) {
 | 
					                revisionAttachment.ownerId = revision.revisionId;
 | 
				
			||||||
                        continue;
 | 
					                revisionAttachment.setContent(noteAttachment.getContent(), {forceSave: true});
 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                    const revisionAttachment = noteAttachment.copy();
 | 
					 | 
				
			||||||
                    revisionAttachment.ownerId = revision.revisionId;
 | 
					 | 
				
			||||||
                    revisionAttachment.setContent(noteAttachment.getContent(), {forceSave: true});
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (this.type === 'text') {
 | 
				
			||||||
                    // content is rewritten to point to the revision attachments
 | 
					                    // content is rewritten to point to the revision attachments
 | 
				
			||||||
                    noteContent = noteContent.replaceAll(`attachments/${noteAttachment.attachmentId}`,
 | 
					                    noteContent = noteContent.replaceAll(`attachments/${noteAttachment.attachmentId}`,
 | 
				
			||||||
                        `attachments/${revisionAttachment.attachmentId}`);
 | 
					                        `attachments/${revisionAttachment.attachmentId}`);
 | 
				
			||||||
 | 
				
			|||||||
@ -86,6 +86,29 @@ class BRevision extends AbstractBeccaEntity {
 | 
				
			|||||||
        return this._getContent();
 | 
					        return this._getContent();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * @returns {*}
 | 
				
			||||||
 | 
					     * @throws Error in case of invalid JSON */
 | 
				
			||||||
 | 
					    getJsonContent() {
 | 
				
			||||||
 | 
					        const content = this.getContent();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!content || !content.trim()) {
 | 
				
			||||||
 | 
					            return null;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return JSON.parse(content);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** @returns {*|null} valid object or null if the content cannot be parsed as JSON */
 | 
				
			||||||
 | 
					    getJsonContentSafely() {
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            return this.getJsonContent();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        catch (e) {
 | 
				
			||||||
 | 
					            return null;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * @param content
 | 
					     * @param content
 | 
				
			||||||
     * @param {object} [opts]
 | 
					     * @param {object} [opts]
 | 
				
			||||||
@ -105,6 +128,45 @@ class BRevision extends AbstractBeccaEntity {
 | 
				
			|||||||
            .map(row => new BAttachment(row));
 | 
					            .map(row => new BAttachment(row));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** @returns {BAttachment|null} */
 | 
				
			||||||
 | 
					    getAttachmentById(attachmentId, opts = {}) {
 | 
				
			||||||
 | 
					        opts.includeContentLength = !!opts.includeContentLength;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const query = opts.includeContentLength
 | 
				
			||||||
 | 
					            ? `SELECT attachments.*, LENGTH(blobs.content) AS contentLength
 | 
				
			||||||
 | 
					               FROM attachments 
 | 
				
			||||||
 | 
					               JOIN blobs USING (blobId) 
 | 
				
			||||||
 | 
					               WHERE ownerId = ? AND attachmentId = ? AND isDeleted = 0`
 | 
				
			||||||
 | 
					            : `SELECT * FROM attachments WHERE ownerId = ? AND attachmentId = ? AND isDeleted = 0`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return sql.getRows(query, [this.revisionId, attachmentId])
 | 
				
			||||||
 | 
					            .map(row => new BAttachment(row))[0];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** @returns {BAttachment[]} */
 | 
				
			||||||
 | 
					    getAttachmentsByRole(role) {
 | 
				
			||||||
 | 
					        return sql.getRows(`
 | 
				
			||||||
 | 
					                SELECT attachments.*
 | 
				
			||||||
 | 
					                FROM attachments 
 | 
				
			||||||
 | 
					                WHERE ownerId = ? 
 | 
				
			||||||
 | 
					                  AND role = ?
 | 
				
			||||||
 | 
					                  AND isDeleted = 0
 | 
				
			||||||
 | 
					                ORDER BY position`, [this.revisionId, role])
 | 
				
			||||||
 | 
					            .map(row => new BAttachment(row));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /** @returns {BAttachment} */
 | 
				
			||||||
 | 
					    getAttachmentByTitle(title) {
 | 
				
			||||||
 | 
					        return sql.getRows(`
 | 
				
			||||||
 | 
					                SELECT attachments.*
 | 
				
			||||||
 | 
					                FROM attachments 
 | 
				
			||||||
 | 
					                WHERE ownerId = ? 
 | 
				
			||||||
 | 
					                  AND title = ?
 | 
				
			||||||
 | 
					                  AND isDeleted = 0
 | 
				
			||||||
 | 
					                ORDER BY position`, [this.revisionId, title])
 | 
				
			||||||
 | 
					            .map(row => new BAttachment(row))[0];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    beforeSaving() {
 | 
					    beforeSaving() {
 | 
				
			||||||
        super.beforeSaving();
 | 
					        super.beforeSaving();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -127,7 +127,8 @@ paths:
 | 
				
			|||||||
            - targetRelationCount
 | 
					            - targetRelationCount
 | 
				
			||||||
            - targetRelationCountIncludingLinks
 | 
					            - targetRelationCountIncludingLinks
 | 
				
			||||||
            - contentSize
 | 
					            - contentSize
 | 
				
			||||||
            - noteSize
 | 
					            - contentAndAttachmentsSize
 | 
				
			||||||
 | 
					            - contentAndAttachmentsAndRevisionsSize
 | 
				
			||||||
            - revisionCount
 | 
					            - revisionCount
 | 
				
			||||||
        - name: orderDirection
 | 
					        - name: orderDirection
 | 
				
			||||||
          in: query
 | 
					          in: query
 | 
				
			||||||
 | 
				
			|||||||
@ -32,8 +32,8 @@ export default class LauncherContextMenu {
 | 
				
			|||||||
        const isVisibleItem = parentNoteId === '_lbVisibleLaunchers';
 | 
					        const isVisibleItem = parentNoteId === '_lbVisibleLaunchers';
 | 
				
			||||||
        const isAvailableItem = parentNoteId === '_lbAvailableLaunchers';
 | 
					        const isAvailableItem = parentNoteId === '_lbAvailableLaunchers';
 | 
				
			||||||
        const isItem = isVisibleItem || isAvailableItem;
 | 
					        const isItem = isVisibleItem || isAvailableItem;
 | 
				
			||||||
        const canBeDeleted = !note.isLaunchBarConfig();
 | 
					        const canBeDeleted = !note.noteId.startsWith("_"); // fixed notes can't be deleted
 | 
				
			||||||
        const canBeReset = note.isLaunchBarConfig();
 | 
					        const canBeReset = !canBeDeleted && note.isLaunchBarConfig();;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return [
 | 
					        return [
 | 
				
			||||||
            (isVisibleRoot || isAvailableRoot) ? { title: 'Add a note launcher', command: 'addNoteLauncher', uiIcon: "bx bx-plus" } : null,
 | 
					            (isVisibleRoot || isAvailableRoot) ? { title: 'Add a note launcher', command: 'addNoteLauncher', uiIcon: "bx bx-plus" } : null,
 | 
				
			||||||
 | 
				
			|||||||
@ -10,6 +10,8 @@ const CODE_MIRROR = {
 | 
				
			|||||||
        "libraries/codemirror/addon/lint/lint.js",
 | 
					        "libraries/codemirror/addon/lint/lint.js",
 | 
				
			||||||
        "libraries/codemirror/addon/lint/eslint.js",
 | 
					        "libraries/codemirror/addon/lint/eslint.js",
 | 
				
			||||||
        "libraries/codemirror/addon/mode/loadmode.js",
 | 
					        "libraries/codemirror/addon/mode/loadmode.js",
 | 
				
			||||||
 | 
					        "libraries/codemirror/addon/mode/multiplex.js",
 | 
				
			||||||
 | 
					        "libraries/codemirror/addon/mode/overlay.js",
 | 
				
			||||||
        "libraries/codemirror/addon/mode/simple.js",
 | 
					        "libraries/codemirror/addon/mode/simple.js",
 | 
				
			||||||
        "libraries/codemirror/addon/search/match-highlighter.js",
 | 
					        "libraries/codemirror/addon/search/match-highlighter.js",
 | 
				
			||||||
        "libraries/codemirror/mode/meta.js",
 | 
					        "libraries/codemirror/mode/meta.js",
 | 
				
			||||||
 | 
				
			|||||||
@ -252,11 +252,15 @@ class NoteListRenderer {
 | 
				
			|||||||
            if (pageCount < 20 || i <= 5 || pageCount - i <= 5 || Math.abs(this.page - i) <= 2) {
 | 
					            if (pageCount < 20 || i <= 5 || pageCount - i <= 5 || Math.abs(this.page - i) <= 2) {
 | 
				
			||||||
                lastPrinted = true;
 | 
					                lastPrinted = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                const startIndex = (i - 1) * this.pageSize + 1;
 | 
				
			||||||
 | 
					                const endIndex = Math.min(this.noteIds.length, i * this.pageSize);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                $pager.append(
 | 
					                $pager.append(
 | 
				
			||||||
                    i === this.page
 | 
					                    i === this.page
 | 
				
			||||||
                        ? $('<span>').text(i).css('text-decoration', 'underline').css('font-weight', "bold")
 | 
					                        ? $('<span>').text(i).css('text-decoration', 'underline').css('font-weight', "bold")
 | 
				
			||||||
                        : $('<a href="javascript:">')
 | 
					                        : $('<a href="javascript:">')
 | 
				
			||||||
                            .text(i)
 | 
					                            .text(i)
 | 
				
			||||||
 | 
					                            .attr("title", `Page of ${startIndex} - ${endIndex}`)
 | 
				
			||||||
                            .on('click', () => {
 | 
					                            .on('click', () => {
 | 
				
			||||||
                                this.page = i;
 | 
					                                this.page = i;
 | 
				
			||||||
                                this.renderList();
 | 
					                                this.renderList();
 | 
				
			||||||
@ -270,6 +274,9 @@ class NoteListRenderer {
 | 
				
			|||||||
                lastPrinted = false;
 | 
					                lastPrinted = false;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // no need to distinguish "note" vs "notes" since in case of one result, there's no paging at all
 | 
				
			||||||
 | 
					        $pager.append(`<span class="note-list-pager-total-count">(${this.noteIds.length} notes)</span>`);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async renderNote(note, expand = false) {
 | 
					    async renderNote(note, expand = false) {
 | 
				
			||||||
 | 
				
			|||||||
@ -45,6 +45,7 @@ const TPL = `
 | 
				
			|||||||
        <a class="dropdown-item export-note-button">Export note</a>
 | 
					        <a class="dropdown-item export-note-button">Export note</a>
 | 
				
			||||||
        <a class="dropdown-item delete-note-button">Delete note</a>
 | 
					        <a class="dropdown-item delete-note-button">Delete note</a>
 | 
				
			||||||
        <a data-trigger-command="printActiveNote" class="dropdown-item print-active-note-button"><kbd data-command="printActiveNote"></kbd> Print note</a>
 | 
					        <a data-trigger-command="printActiveNote" class="dropdown-item print-active-note-button"><kbd data-command="printActiveNote"></kbd> Print note</a>
 | 
				
			||||||
 | 
					        <a data-trigger-command="forceSaveRevision" class="dropdown-item save-revision-button"><kbd data-command="forceSaveRevision"></kbd> Save revision</a>
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
</div>`;
 | 
					</div>`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -274,26 +274,11 @@ export default class RevisionsDialog extends BasicWidget {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            this.$content.html($table);
 | 
					            this.$content.html($table);
 | 
				
			||||||
        } else if (revisionItem.type === 'canvas') {
 | 
					        } else if (revisionItem.type === 'canvas') {
 | 
				
			||||||
            /**
 | 
					            const sanitizedTitle = revisionItem.title.replace(/[^a-z0-9-.]/gi, "");
 | 
				
			||||||
             * FIXME: We load a font called Virgil.wof2, which originates from excalidraw.com
 | 
					 | 
				
			||||||
             *        REMOVE external dependency!!!! This is defined in the svg in defs.style
 | 
					 | 
				
			||||||
             */
 | 
					 | 
				
			||||||
            const content = fullRevision.content;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            try {
 | 
					            this.$content.html($("<img>")
 | 
				
			||||||
                const data = JSON.parse(content)
 | 
					                .attr("src", `api/revisions/${revisionItem.revisionId}/image/${sanitizedTitle}?${Math.random()}`)
 | 
				
			||||||
                const svg = data.svg || "no svg present."
 | 
					                .css("max-width", "100%"));
 | 
				
			||||||
 | 
					 | 
				
			||||||
                /**
 | 
					 | 
				
			||||||
                 * maxWidth: 100% use full width of container but do not enlarge!
 | 
					 | 
				
			||||||
                 * height:auto to ensure that height scales with width
 | 
					 | 
				
			||||||
                 */
 | 
					 | 
				
			||||||
                const $svgHtml = $(svg).css({maxWidth: "100%", height: "auto"});
 | 
					 | 
				
			||||||
                this.$content.html($('<div>').append($svgHtml));
 | 
					 | 
				
			||||||
            } catch (err) {
 | 
					 | 
				
			||||||
                console.error("error parsing fullRevision.content as JSON", fullRevision.content, err);
 | 
					 | 
				
			||||||
                this.$content.html($("<div>").text("Error parsing content. Please check console.error() for more details."));
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            this.$content.text("Preview isn't available for this note type.");
 | 
					            this.$content.text("Preview isn't available for this note type.");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
				
			|||||||
@ -14,7 +14,8 @@ const TPL = `
 | 
				
			|||||||
            <option value="dateCreated">Date created</option>
 | 
					            <option value="dateCreated">Date created</option>
 | 
				
			||||||
            <option value="dateModified">Date of last modification</option>
 | 
					            <option value="dateModified">Date of last modification</option>
 | 
				
			||||||
            <option value="contentSize">Note content size</option>
 | 
					            <option value="contentSize">Note content size</option>
 | 
				
			||||||
            <option value="noteSize">Note content size including revisions</option>
 | 
					            <option value="contentAndAttachmentsSize">Note content size including attachments</option>
 | 
				
			||||||
 | 
					            <option value="contentAndAttachmentsAndRevisionsSize">Note content size including attachments and revisions</option>
 | 
				
			||||||
            <option value="revisionCount">Number of revisions</option>
 | 
					            <option value="revisionCount">Number of revisions</option>
 | 
				
			||||||
            <option value="childrenCount">Number of children notes</option>
 | 
					            <option value="childrenCount">Number of children notes</option>
 | 
				
			||||||
            <option value="parentCount">Number of clones</option>
 | 
					            <option value="parentCount">Number of clones</option>
 | 
				
			||||||
 | 
				
			|||||||
@ -5,14 +5,27 @@ const becca = require('../../becca/becca');
 | 
				
			|||||||
const RESOURCE_DIR = require('../../services/resource_dir').RESOURCE_DIR;
 | 
					const RESOURCE_DIR = require('../../services/resource_dir').RESOURCE_DIR;
 | 
				
			||||||
const fs = require('fs');
 | 
					const fs = require('fs');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function returnImage(req, res) {
 | 
					function returnImageFromNote(req, res) {
 | 
				
			||||||
    const image = becca.getNote(req.params.noteId);
 | 
					    const image = becca.getNote(req.params.noteId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return returnImageInt(image, res);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function returnImageFromRevision(req, res) {
 | 
				
			||||||
 | 
					    const image = becca.getRevision(req.params.revisionId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return returnImageInt(image, res);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * @param {BNote|BRevision} image
 | 
				
			||||||
 | 
					 * @param res
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					function returnImageInt(image, res) {
 | 
				
			||||||
    if (!image) {
 | 
					    if (!image) {
 | 
				
			||||||
        res.set('Content-Type', 'image/png');
 | 
					        res.set('Content-Type', 'image/png');
 | 
				
			||||||
        return res.send(fs.readFileSync(`${RESOURCE_DIR}/db/image-deleted.png`));
 | 
					        return res.send(fs.readFileSync(`${RESOURCE_DIR}/db/image-deleted.png`));
 | 
				
			||||||
    }
 | 
					    } else if (!["image", "canvas"].includes(image.type)) {
 | 
				
			||||||
    else if (!["image", "canvas"].includes(image.type)){
 | 
					 | 
				
			||||||
        return res.sendStatus(400);
 | 
					        return res.sendStatus(400);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -84,7 +97,8 @@ function updateImage(req) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports = {
 | 
					module.exports = {
 | 
				
			||||||
    returnImage,
 | 
					    returnImageFromNote,
 | 
				
			||||||
 | 
					    returnImageFromRevision,
 | 
				
			||||||
    returnAttachedImage,
 | 
					    returnAttachedImage,
 | 
				
			||||||
    updateImage
 | 
					    updateImage
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
				
			|||||||
@ -181,6 +181,8 @@ function register(app) {
 | 
				
			|||||||
    apiRoute(GET, '/api/revisions/:revisionId/blob', revisionsApiRoute.getRevisionBlob);
 | 
					    apiRoute(GET, '/api/revisions/:revisionId/blob', revisionsApiRoute.getRevisionBlob);
 | 
				
			||||||
    apiRoute(DEL, '/api/revisions/:revisionId', revisionsApiRoute.eraseRevision);
 | 
					    apiRoute(DEL, '/api/revisions/:revisionId', revisionsApiRoute.eraseRevision);
 | 
				
			||||||
    apiRoute(PST, '/api/revisions/:revisionId/restore', revisionsApiRoute.restoreRevision);
 | 
					    apiRoute(PST, '/api/revisions/:revisionId/restore', revisionsApiRoute.restoreRevision);
 | 
				
			||||||
 | 
					    route(GET, '/api/revisions/:revisionId/image/:filename', [auth.checkApiAuthOrElectron], imageRoute.returnImageFromRevision);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    route(GET, '/api/revisions/:revisionId/download', [auth.checkApiAuthOrElectron], revisionsApiRoute.downloadRevision);
 | 
					    route(GET, '/api/revisions/:revisionId/download', [auth.checkApiAuthOrElectron], revisionsApiRoute.downloadRevision);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -200,7 +202,7 @@ function register(app) {
 | 
				
			|||||||
    apiRoute(GET, '/api/attribute-values/:attributeName', attributesRoute.getValuesForAttribute);
 | 
					    apiRoute(GET, '/api/attribute-values/:attributeName', attributesRoute.getValuesForAttribute);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // :filename is not used by trilium, but instead used for "save as" to assign a human-readable filename
 | 
					    // :filename is not used by trilium, but instead used for "save as" to assign a human-readable filename
 | 
				
			||||||
    route(GET, '/api/images/:noteId/:filename', [auth.checkApiAuthOrElectron], imageRoute.returnImage);
 | 
					    route(GET, '/api/images/:noteId/:filename', [auth.checkApiAuthOrElectron], imageRoute.returnImageFromNote);
 | 
				
			||||||
    route(PUT, '/api/images/:noteId', [auth.checkApiAuthOrElectron, uploadMiddlewareWithErrorHandling, csrfMiddleware], imageRoute.updateImage, apiResultHandler);
 | 
					    route(PUT, '/api/images/:noteId', [auth.checkApiAuthOrElectron, uploadMiddlewareWithErrorHandling, csrfMiddleware], imageRoute.updateImage, apiResultHandler);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    apiRoute(GET, '/api/options', optionsApiRoute.getOptions);
 | 
					    apiRoute(GET, '/api/options', optionsApiRoute.getOptions);
 | 
				
			||||||
 | 
				
			|||||||
@ -7,8 +7,9 @@ const sql = require("./sql");
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
function getFullAnonymizationScript() {
 | 
					function getFullAnonymizationScript() {
 | 
				
			||||||
    // we want to delete all non-builtin attributes because they can contain sensitive names and values
 | 
					    // we want to delete all non-builtin attributes because they can contain sensitive names and values
 | 
				
			||||||
// on the other hand builtin/system attrs should not contain any sensitive info
 | 
					    // on the other hand builtin/system attrs should not contain any sensitive info
 | 
				
			||||||
    const builtinAttrNames = BUILTIN_ATTRIBUTES
 | 
					    const builtinAttrNames = BUILTIN_ATTRIBUTES
 | 
				
			||||||
 | 
					        .filter(attr => !["shareCredentials", "shareAlias"].includes(attr.name))
 | 
				
			||||||
        .map(attr => `'${attr.name}'`).join(', ');
 | 
					        .map(attr => `'${attr.name}'`).join(', ');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const anonymizeScript = `
 | 
					    const anonymizeScript = `
 | 
				
			||||||
 | 
				
			|||||||
@ -4,7 +4,7 @@ const build = require('./build');
 | 
				
			|||||||
const packageJson = require('../../package');
 | 
					const packageJson = require('../../package');
 | 
				
			||||||
const {TRILIUM_DATA_DIR} = require('./data_dir');
 | 
					const {TRILIUM_DATA_DIR} = require('./data_dir');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const APP_DB_VERSION = 225;
 | 
					const APP_DB_VERSION = 226;
 | 
				
			||||||
const SYNC_VERSION = 31;
 | 
					const SYNC_VERSION = 31;
 | 
				
			||||||
const CLIPPER_PROTOCOL_VERSION = "1.0";
 | 
					const CLIPPER_PROTOCOL_VERSION = "1.0";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1 +1 @@
 | 
				
			|||||||
module.exports = { buildDate:"2023-09-21T23:38:18+02:00", buildRevision: "79e5e3b65ff613cdb81e2afaa832037ccf06d7b8" };
 | 
					module.exports = { buildDate:"2023-09-29T00:54:45+02:00", buildRevision: "e5555beea9a1638fefa218118e0596f4cfc1f4d0" };
 | 
				
			||||||
 | 
				
			|||||||
@ -763,7 +763,7 @@ class ConsistencyChecks {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (this.reloadNeeded) {
 | 
					        if (this.reloadNeeded) {
 | 
				
			||||||
            require("../becca/becca_loader").reload();
 | 
					            require("../becca/becca_loader").reload("consistency checks need becca reload");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return !this.unrecoveredConsistencyErrors;
 | 
					        return !this.unrecoveredConsistencyErrors;
 | 
				
			||||||
 | 
				
			|||||||
@ -148,12 +148,13 @@ function fillEntityChanges(entityName, entityPrimaryKey, condition = '') {
 | 
				
			|||||||
                const entity = becca.getEntity(entityName, entityId);
 | 
					                const entity = becca.getEntity(entityName, entityId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if (entity) {
 | 
					                if (entity) {
 | 
				
			||||||
                    ec.hash = entity.generateHash() || "|deleted";
 | 
					                    ec.hash = entity.generateHash();
 | 
				
			||||||
                    ec.utcDateChanged = entity.getUtcDateChanged() || dateUtils.utcNowDateTime();
 | 
					                    ec.utcDateChanged = entity.getUtcDateChanged() || dateUtils.utcNowDateTime();
 | 
				
			||||||
                    ec.isSynced = entityName !== 'options' || !!entity.isSynced;
 | 
					                    ec.isSynced = entityName !== 'options' || !!entity.isSynced;
 | 
				
			||||||
                } else {
 | 
					                } else {
 | 
				
			||||||
                    // entity might be null (not present in becca) when it's deleted
 | 
					                    // entity might be null (not present in becca) when it's deleted
 | 
				
			||||||
                    // FIXME: hacky, not sure if it might cause some problems
 | 
					                    // this will produce different hash value than when entity is being deleted since then
 | 
				
			||||||
 | 
					                    // all normal hashed attributes are being used. Sync should recover from that, though.
 | 
				
			||||||
                    ec.hash = "deleted";
 | 
					                    ec.hash = "deleted";
 | 
				
			||||||
                    ec.utcDateChanged = dateUtils.utcNowDateTime();
 | 
					                    ec.utcDateChanged = dateUtils.utcNowDateTime();
 | 
				
			||||||
                    ec.isSynced = true; // deletable (the ones with isDeleted) entities are synced
 | 
					                    ec.isSynced = true; // deletable (the ones with isDeleted) entities are synced
 | 
				
			||||||
 | 
				
			|||||||
@ -327,6 +327,11 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
 | 
				
			|||||||
        content = content.replace(/<\/body>.*<\/html>/gis, "");
 | 
					        content = content.replace(/<\/body>.*<\/html>/gis, "");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        content = content.replace(/src="([^"]*)"/g, (match, url) => {
 | 
					        content = content.replace(/src="([^"]*)"/g, (match, url) => {
 | 
				
			||||||
 | 
					            if (url.startsWith("data:image")) {
 | 
				
			||||||
 | 
					                // inline images are parsed and saved into attachments in the note service
 | 
				
			||||||
 | 
					                return match;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            try {
 | 
					            try {
 | 
				
			||||||
                url = decodeURIComponent(url).trim();
 | 
					                url = decodeURIComponent(url).trim();
 | 
				
			||||||
            } catch (e) {
 | 
					            } catch (e) {
 | 
				
			||||||
@ -456,7 +461,7 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
 | 
				
			|||||||
                position: attachmentMeta.position
 | 
					                position: attachmentMeta.position
 | 
				
			||||||
            });
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            attachment.setContent(content);
 | 
					            attachment.setContent(content, { forceSave: true });
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -60,7 +60,7 @@ function checkProtectedSessionExpiration() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        log.info("Expiring protected session");
 | 
					        log.info("Expiring protected session");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        require('./ws').reloadFrontend();
 | 
					        require('./ws').reloadFrontend("leaving protected session");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -56,9 +56,9 @@ class OrderByAndLimitExp extends Expression {
 | 
				
			|||||||
                if (!valA && !valB) {
 | 
					                if (!valA && !valB) {
 | 
				
			||||||
                    // the attribute value is empty/zero in both notes so continue to the next order definition
 | 
					                    // the attribute value is empty/zero in both notes so continue to the next order definition
 | 
				
			||||||
                    continue;
 | 
					                    continue;
 | 
				
			||||||
                } else if (!valB || valA < valB) {
 | 
					                } else if (valA < valB) {
 | 
				
			||||||
                    return smaller;
 | 
					                    return smaller;
 | 
				
			||||||
                } else if (!valA || valA > valB) {
 | 
					                } else if (valA > valB) {
 | 
				
			||||||
                    return larger;
 | 
					                    return larger;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                // else the values are equal and continue to next order definition
 | 
					                // else the values are equal and continue to next order definition
 | 
				
			||||||
 | 
				
			|||||||
@ -31,7 +31,8 @@ const PROP_MAPPING = {
 | 
				
			|||||||
    "targetrelationcount": "targetRelationCount",
 | 
					    "targetrelationcount": "targetRelationCount",
 | 
				
			||||||
    "targetrelationcountincludinglinks": "targetRelationCountIncludingLinks",
 | 
					    "targetrelationcountincludinglinks": "targetRelationCountIncludingLinks",
 | 
				
			||||||
    "contentsize": "contentSize",
 | 
					    "contentsize": "contentSize",
 | 
				
			||||||
    "notesize": "noteSize",
 | 
					    "contentandattachmentssize": "contentAndAttachmentsSize",
 | 
				
			||||||
 | 
					    "contentandattachmentsandrevisionssize": "contentAndAttachmentsAndRevisionsSize",
 | 
				
			||||||
    "revisioncount": "revisionCount"
 | 
					    "revisioncount": "revisionCount"
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -48,7 +49,7 @@ class PropertyComparisonExp extends Expression {
 | 
				
			|||||||
        this.comparedValue = comparedValue; // for DEBUG mode
 | 
					        this.comparedValue = comparedValue; // for DEBUG mode
 | 
				
			||||||
        this.comparator = buildComparator(operator, comparedValue);
 | 
					        this.comparator = buildComparator(operator, comparedValue);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (['contentsize', 'notesize', 'revisioncount'].includes(this.propertyName)) {
 | 
					        if (['contentsize', 'contentandattachmentssize', 'contentandattachmentsandrevisionssize', 'revisioncount'].includes(this.propertyName)) {
 | 
				
			||||||
            searchContext.dbLoadNeeded = true;
 | 
					            searchContext.dbLoadNeeded = true;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -92,47 +92,107 @@ function searchFromRelation(note, relationName) {
 | 
				
			|||||||
function loadNeededInfoFromDatabase() {
 | 
					function loadNeededInfoFromDatabase() {
 | 
				
			||||||
    const sql = require('../../sql');
 | 
					    const sql = require('../../sql');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for (const noteId in becca.notes) {
 | 
					    /**
 | 
				
			||||||
        becca.notes[noteId].contentSize = 0;
 | 
					     * This complex structure is needed to calculate total occupied space by a note. Several object instances
 | 
				
			||||||
        becca.notes[noteId].noteSize = 0;
 | 
					     * (note, revisions, attachments) can point to a single blobId, and thus the blob size should count towards the total
 | 
				
			||||||
        becca.notes[noteId].revisionCount = 0;
 | 
					     * only once.
 | 
				
			||||||
    }
 | 
					     *
 | 
				
			||||||
 | 
					     * @var {Object.<string, Object.<string, int>>} - noteId => { blobId => blobSize }
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    const noteBlobs = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const noteContentLengths = sql.getRows(`
 | 
					    const noteContentLengths = sql.getRows(`
 | 
				
			||||||
        SELECT 
 | 
					        SELECT 
 | 
				
			||||||
            noteId, 
 | 
					            noteId, 
 | 
				
			||||||
 | 
					            blobId,
 | 
				
			||||||
            LENGTH(content) AS length 
 | 
					            LENGTH(content) AS length 
 | 
				
			||||||
        FROM notes
 | 
					        FROM notes
 | 
				
			||||||
             JOIN blobs USING(blobId) 
 | 
					             JOIN blobs USING(blobId) 
 | 
				
			||||||
        WHERE notes.isDeleted = 0`);
 | 
					        WHERE notes.isDeleted = 0`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for (const {noteId, length} of noteContentLengths) {
 | 
					    for (const {noteId, blobId, length} of noteContentLengths) {
 | 
				
			||||||
        if (!(noteId in becca.notes)) {
 | 
					        if (!(noteId in becca.notes)) {
 | 
				
			||||||
            log.error(`Note ${noteId} not found in becca.`);
 | 
					            log.error(`Note '${noteId}' not found in becca.`);
 | 
				
			||||||
            continue;
 | 
					            continue;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        becca.notes[noteId].contentSize = length;
 | 
					        becca.notes[noteId].contentSize = length;
 | 
				
			||||||
        becca.notes[noteId].noteSize = length;
 | 
					        becca.notes[noteId].revisionCount = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        noteBlobs[noteId] = { [blobId]: length };
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const revisionContentLengths = sql.getRows(`
 | 
					    const attachmentContentLengths = sql.getRows(`
 | 
				
			||||||
        SELECT 
 | 
					        SELECT
 | 
				
			||||||
            noteId, 
 | 
					            ownerId AS noteId,
 | 
				
			||||||
            LENGTH(content) AS length 
 | 
					            attachments.blobId,
 | 
				
			||||||
        FROM notes
 | 
					            LENGTH(content) AS length
 | 
				
			||||||
             JOIN revisions USING(noteId) 
 | 
					        FROM attachments
 | 
				
			||||||
             JOIN blobs USING(blobId) 
 | 
					            JOIN notes ON attachments.ownerId = notes.noteId
 | 
				
			||||||
        WHERE notes.isDeleted = 0`);
 | 
					            JOIN blobs ON attachments.blobId = blobs.blobId
 | 
				
			||||||
 | 
					        WHERE attachments.isDeleted = 0 
 | 
				
			||||||
 | 
					            AND notes.isDeleted = 0`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for (const {noteId, length} of revisionContentLengths) {
 | 
					    for (const {noteId, blobId, length} of attachmentContentLengths) {
 | 
				
			||||||
        if (!(noteId in becca.notes)) {
 | 
					        if (!(noteId in becca.notes)) {
 | 
				
			||||||
            log.error(`Note ${noteId} not found in becca.`);
 | 
					            log.error(`Note '${noteId}' not found in becca.`);
 | 
				
			||||||
            continue;
 | 
					            continue;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        becca.notes[noteId].noteSize += length;
 | 
					        if (!(noteId in noteBlobs)) {
 | 
				
			||||||
        becca.notes[noteId].revisionCount++;
 | 
					            log.error(`Did not find a '${noteId}' in the noteBlobs.`);
 | 
				
			||||||
 | 
					            continue;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        noteBlobs[noteId][blobId] = length;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (const noteId in noteBlobs) {
 | 
				
			||||||
 | 
					        becca.notes[noteId].contentAndAttachmentsSize = Object.values(noteBlobs[noteId]).reduce((acc, size) => acc + size, 0);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const revisionContentLengths = sql.getRows(`
 | 
				
			||||||
 | 
					            SELECT 
 | 
				
			||||||
 | 
					                noteId, 
 | 
				
			||||||
 | 
					                revisions.blobId,
 | 
				
			||||||
 | 
					                LENGTH(content) AS length,
 | 
				
			||||||
 | 
					                1 AS isNoteRevision
 | 
				
			||||||
 | 
					            FROM notes
 | 
				
			||||||
 | 
					                JOIN revisions USING(noteId) 
 | 
				
			||||||
 | 
					                JOIN blobs ON revisions.blobId = blobs.blobId
 | 
				
			||||||
 | 
					            WHERE notes.isDeleted = 0
 | 
				
			||||||
 | 
					        UNION ALL
 | 
				
			||||||
 | 
					            SELECT
 | 
				
			||||||
 | 
					                noteId,
 | 
				
			||||||
 | 
					                revisions.blobId,
 | 
				
			||||||
 | 
					                LENGTH(content) AS length,
 | 
				
			||||||
 | 
					                0 AS isNoteRevision -- it's attachment not counting towards revision count
 | 
				
			||||||
 | 
					            FROM notes
 | 
				
			||||||
 | 
					                JOIN revisions USING(noteId)
 | 
				
			||||||
 | 
					                JOIN attachments ON attachments.ownerId = revisions.revisionId
 | 
				
			||||||
 | 
					                JOIN blobs ON attachments.blobId = blobs.blobId
 | 
				
			||||||
 | 
					            WHERE notes.isDeleted = 0`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (const {noteId, blobId, length, isNoteRevision} of revisionContentLengths) {
 | 
				
			||||||
 | 
					        if (!(noteId in becca.notes)) {
 | 
				
			||||||
 | 
					            log.error(`Note '${noteId}' not found in becca.`);
 | 
				
			||||||
 | 
					            continue;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (!(noteId in noteBlobs)) {
 | 
				
			||||||
 | 
					            log.error(`Did not find a '${noteId}' in the noteBlobs.`);
 | 
				
			||||||
 | 
					            continue;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        noteBlobs[noteId][blobId] = length;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (isNoteRevision) {
 | 
				
			||||||
 | 
					            becca.notes[noteId].revisionCount++;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (const noteId in noteBlobs) {
 | 
				
			||||||
 | 
					        becca.notes[noteId].contentAndAttachmentsAndRevisionsSize = Object.values(noteBlobs[noteId]).reduce((acc, size) => acc + size, 0);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -27,7 +27,8 @@ const PROP_MAPPING = {
 | 
				
			|||||||
    "targetrelationcount": "targetRelationCount",
 | 
					    "targetrelationcount": "targetRelationCount",
 | 
				
			||||||
    "targetrelationcountincludinglinks": "targetRelationCountIncludingLinks",
 | 
					    "targetrelationcountincludinglinks": "targetRelationCountIncludingLinks",
 | 
				
			||||||
    "contentsize": "contentSize",
 | 
					    "contentsize": "contentSize",
 | 
				
			||||||
    "notesize": "noteSize",
 | 
					    "contentandattachmentssize": "contentAndAttachmentsSize",
 | 
				
			||||||
 | 
					    "contentandattachmentsandrevisionssize": "contentAndAttachmentsAndRevisionsSize",
 | 
				
			||||||
    "revisioncount": "revisionCount"
 | 
					    "revisioncount": "revisionCount"
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -42,7 +43,7 @@ class ValueExtractor {
 | 
				
			|||||||
            this.propertyPath = ['note', 'relations', this.propertyPath[0].substr(1), ...this.propertyPath.slice(1, this.propertyPath.length)];
 | 
					            this.propertyPath = ['note', 'relations', this.propertyPath[0].substr(1), ...this.propertyPath.slice(1, this.propertyPath.length)];
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (['contentsize', 'notesize', 'revisioncount'].includes(this.propertyPath[this.propertyPath.length - 1])) {
 | 
					        if (['contentsize', 'contentandattachmentssize', 'contentandattachmentsandrevisionssize', 'revisioncount'].includes(this.propertyPath[this.propertyPath.length - 1])) {
 | 
				
			||||||
            searchContext.dbLoadNeeded = true;
 | 
					            searchContext.dbLoadNeeded = true;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
				
			|||||||
@ -151,9 +151,14 @@ async function pullChanges(syncContext) {
 | 
				
			|||||||
        if (entityChanges.length === 0) {
 | 
					        if (entityChanges.length === 0) {
 | 
				
			||||||
            break;
 | 
					            break;
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
            const sizeInKb = Math.round(JSON.stringify(resp).length / 1024);
 | 
					            try { // https://github.com/zadam/trilium/issues/4310
 | 
				
			||||||
 | 
					                const sizeInKb = Math.round(JSON.stringify(resp).length / 1024);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            log.info(`Sync ${logMarkerId}: Pulled ${entityChanges.length} changes in ${sizeInKb} KB, starting at entityChangeId=${lastSyncedPull} in ${pulledDate - startDate}ms and applied them in ${Date.now() - pulledDate}ms, ${outstandingPullCount} outstanding pulls`);
 | 
					                log.info(`Sync ${logMarkerId}: Pulled ${entityChanges.length} changes in ${sizeInKb} KB, starting at entityChangeId=${lastSyncedPull} in ${pulledDate - startDate}ms and applied them in ${Date.now() - pulledDate}ms, ${outstandingPullCount} outstanding pulls`);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            catch (e) {
 | 
				
			||||||
 | 
					                log.error(`Error occurred ${e.message} ${e.stack}`);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -106,7 +106,7 @@ function updateNormalEntity(remoteEC, remoteEntityRow, instanceId, updateContext
 | 
				
			|||||||
        updateContext.updated[remoteEC.entityName] = updateContext.updated[remoteEC.entityName] || [];
 | 
					        updateContext.updated[remoteEC.entityName] = updateContext.updated[remoteEC.entityName] || [];
 | 
				
			||||||
        updateContext.updated[remoteEC.entityName].push(remoteEC.entityId);
 | 
					        updateContext.updated[remoteEC.entityName].push(remoteEC.entityId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (!localEC || localEC.utcDateChanged < remoteEC.utcDateChanged) {
 | 
					        if (!localEC || localEC.utcDateChanged < remoteEC.utcDateChanged || localEC.hash !== remoteEC.hash) {
 | 
				
			||||||
            entityChangesService.putEntityChangeWithInstanceId(remoteEC, instanceId);
 | 
					            entityChangesService.putEntityChangeWithInstanceId(remoteEC, instanceId);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -13,7 +13,7 @@ const env = require('./env');
 | 
				
			|||||||
if (env.isDev()) {
 | 
					if (env.isDev()) {
 | 
				
			||||||
    const chokidar = require('chokidar');
 | 
					    const chokidar = require('chokidar');
 | 
				
			||||||
    const debounce = require('debounce');
 | 
					    const debounce = require('debounce');
 | 
				
			||||||
    const debouncedReloadFrontend = debounce(reloadFrontend, 200);
 | 
					    const debouncedReloadFrontend = debounce(() => reloadFrontend("source code change"), 200);
 | 
				
			||||||
    chokidar
 | 
					    chokidar
 | 
				
			||||||
        .watch('src/public')
 | 
					        .watch('src/public')
 | 
				
			||||||
        .on('add', debouncedReloadFrontend)
 | 
					        .on('add', debouncedReloadFrontend)
 | 
				
			||||||
@ -230,8 +230,8 @@ function syncFailed() {
 | 
				
			|||||||
    sendMessageToAllClients({ type: 'sync-failed', lastSyncedPush });
 | 
					    sendMessageToAllClients({ type: 'sync-failed', lastSyncedPush });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function reloadFrontend() {
 | 
					function reloadFrontend(reason) {
 | 
				
			||||||
    sendMessageToAllClients({ type: 'reload-frontend' });
 | 
					    sendMessageToAllClients({ type: 'reload-frontend', reason });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function setLastSyncedPush(entityChangeId) {
 | 
					function setLastSyncedPush(entityChangeId) {
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user