mirror of
				https://github.com/zadam/trilium.git
				synced 2025-11-03 20:06:08 +01:00 
			
		
		
		
	Compare commits
	
		
			33 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					265401775b | ||
| 
						 | 
					10a5773c66 | ||
| 
						 | 
					1e8472266f | ||
| 
						 | 
					b30792a3da | ||
| 
						 | 
					26602e8226 | ||
| 
						 | 
					b8eeb0371c | ||
| 
						 | 
					b1c4737e78 | ||
| 
						 | 
					e29aee1aae | ||
| 
						 | 
					1aff42f453 | ||
| 
						 | 
					a098630e09 | ||
| 
						 | 
					074eb1c02f | ||
| 
						 | 
					a81ea3771f | ||
| 
						 | 
					d9550dd59b | ||
| 
						 | 
					a810c08c02 | ||
| 
						 | 
					263b7a84bb | ||
| 
						 | 
					9d18bebb13 | ||
| 
						 | 
					26bcfe5160 | ||
| 
						 | 
					89c04e6b6b | ||
| 
						 | 
					40fb4ff56b | ||
| 
						 | 
					d64c14482b | ||
| 
						 | 
					564366861e | ||
| 
						 | 
					8c11d022fb | ||
| 
						 | 
					24210ef80c | ||
| 
						 | 
					2135aa058e | ||
| 
						 | 
					67542f448d | ||
| 
						 | 
					08e9b59696 | ||
| 
						 | 
					fe605c012a | ||
| 
						 | 
					4c7c53d8c8 | ||
| 
						 | 
					d345b7ed56 | ||
| 
						 | 
					298af217e9 | ||
| 
						 | 
					7d64f6a7dd | ||
| 
						 | 
					bc8b6284a6 | ||
| 
						 | 
					ed5eb5c6db | 
							
								
								
									
										10
									
								
								.idea/runConfigurations.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										10
									
								
								.idea/runConfigurations.xml
									
									
									
										generated
									
									
									
								
							@@ -1,10 +0,0 @@
 | 
				
			|||||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
					 | 
				
			||||||
<project version="4">
 | 
					 | 
				
			||||||
  <component name="RunConfigurationProducerService">
 | 
					 | 
				
			||||||
    <option name="ignoredProducers">
 | 
					 | 
				
			||||||
      <set>
 | 
					 | 
				
			||||||
        <option value="com.android.tools.idea.compose.preview.runconfiguration.ComposePreviewRunConfigurationProducer" />
 | 
					 | 
				
			||||||
      </set>
 | 
					 | 
				
			||||||
    </option>
 | 
					 | 
				
			||||||
  </component>
 | 
					 | 
				
			||||||
</project>
 | 
					 | 
				
			||||||
@@ -16,7 +16,7 @@ RUN set -x \
 | 
				
			|||||||
        make \
 | 
					        make \
 | 
				
			||||||
        nasm \
 | 
					        nasm \
 | 
				
			||||||
        libpng-dev \
 | 
					        libpng-dev \
 | 
				
			||||||
        python \
 | 
					        python3 \
 | 
				
			||||||
    && npm install --production \
 | 
					    && npm install --production \
 | 
				
			||||||
    && apk del .build-dependencies
 | 
					    && apk del .build-dependencies
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										11078
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										11078
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -2,7 +2,7 @@
 | 
				
			|||||||
  "name": "trilium",
 | 
					  "name": "trilium",
 | 
				
			||||||
  "productName": "Trilium Notes",
 | 
					  "productName": "Trilium Notes",
 | 
				
			||||||
  "description": "Trilium Notes",
 | 
					  "description": "Trilium Notes",
 | 
				
			||||||
  "version": "0.48.6",
 | 
					  "version": "0.48.9",
 | 
				
			||||||
  "license": "AGPL-3.0-only",
 | 
					  "license": "AGPL-3.0-only",
 | 
				
			||||||
  "main": "electron.js",
 | 
					  "main": "electron.js",
 | 
				
			||||||
  "bin": {
 | 
					  "bin": {
 | 
				
			||||||
@@ -81,7 +81,7 @@
 | 
				
			|||||||
  },
 | 
					  },
 | 
				
			||||||
  "devDependencies": {
 | 
					  "devDependencies": {
 | 
				
			||||||
    "cross-env": "7.0.3",
 | 
					    "cross-env": "7.0.3",
 | 
				
			||||||
    "electron": "13.6.1",
 | 
					    "electron": "13.6.3",
 | 
				
			||||||
    "electron-builder": "22.13.1",
 | 
					    "electron-builder": "22.13.1",
 | 
				
			||||||
    "electron-packager": "15.4.0",
 | 
					    "electron-packager": "15.4.0",
 | 
				
			||||||
    "electron-rebuild": "3.2.3",
 | 
					    "electron-rebuild": "3.2.3",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -42,7 +42,7 @@ class NoteBuilder {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    child(childNoteBuilder, prefix = "") {
 | 
					    child(childNoteBuilder, prefix = "") {
 | 
				
			||||||
        new Branch(becca, {
 | 
					        new Branch({
 | 
				
			||||||
            branchId: id(),
 | 
					            branchId: id(),
 | 
				
			||||||
            noteId: childNoteBuilder.note.noteId,
 | 
					            noteId: childNoteBuilder.note.noteId,
 | 
				
			||||||
            parentNoteId: this.note.noteId,
 | 
					            parentNoteId: this.note.noteId,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -37,7 +37,7 @@ describe("Parser", () => {
 | 
				
			|||||||
        expect(rootExp.constructor.name).toEqual("AndExp");
 | 
					        expect(rootExp.constructor.name).toEqual("AndExp");
 | 
				
			||||||
        expect(rootExp.subExpressions[0].constructor.name).toEqual("PropertyComparisonExp");
 | 
					        expect(rootExp.subExpressions[0].constructor.name).toEqual("PropertyComparisonExp");
 | 
				
			||||||
        expect(rootExp.subExpressions[1].constructor.name).toEqual("OrExp");
 | 
					        expect(rootExp.subExpressions[1].constructor.name).toEqual("OrExp");
 | 
				
			||||||
        expect(rootExp.subExpressions[1].subExpressions[0].constructor.name).toEqual("BeccaFlatTextExp");
 | 
					        expect(rootExp.subExpressions[1].subExpressions[0].constructor.name).toEqual("NoteFlatTextExp");
 | 
				
			||||||
        expect(rootExp.subExpressions[1].subExpressions[0].tokens).toEqual(["hello", "hi"]);
 | 
					        expect(rootExp.subExpressions[1].subExpressions[0].tokens).toEqual(["hello", "hi"]);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -55,7 +55,7 @@ describe("Parser", () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        const subs = rootExp.subExpressions[1].subExpressions;
 | 
					        const subs = rootExp.subExpressions[1].subExpressions;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        expect(subs[0].constructor.name).toEqual("BeccaFlatTextExp");
 | 
					        expect(subs[0].constructor.name).toEqual("NoteFlatTextExp");
 | 
				
			||||||
        expect(subs[0].tokens).toEqual(["hello", "hi"]);
 | 
					        expect(subs[0].tokens).toEqual(["hello", "hi"]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        expect(subs[1].constructor.name).toEqual("NoteContentProtectedFulltextExp");
 | 
					        expect(subs[1].constructor.name).toEqual("NoteContentProtectedFulltextExp");
 | 
				
			||||||
@@ -182,7 +182,7 @@ describe("Parser", () => {
 | 
				
			|||||||
        expect(firstSub.propertyName).toEqual('isArchived');
 | 
					        expect(firstSub.propertyName).toEqual('isArchived');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        expect(secondSub.constructor.name).toEqual("OrExp");
 | 
					        expect(secondSub.constructor.name).toEqual("OrExp");
 | 
				
			||||||
        expect(secondSub.subExpressions[0].constructor.name).toEqual("BeccaFlatTextExp");
 | 
					        expect(secondSub.subExpressions[0].constructor.name).toEqual("NoteFlatTextExp");
 | 
				
			||||||
        expect(secondSub.subExpressions[0].tokens).toEqual(["hello"]);
 | 
					        expect(secondSub.subExpressions[0].tokens).toEqual(["hello"]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        expect(thirdSub.constructor.name).toEqual("LabelComparisonExp");
 | 
					        expect(thirdSub.constructor.name).toEqual("LabelComparisonExp");
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,7 +13,7 @@ describe("Search", () => {
 | 
				
			|||||||
        becca.reset();
 | 
					        becca.reset();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        rootNote = new NoteBuilder(new Note({noteId: 'root', title: 'root', type: 'text'}));
 | 
					        rootNote = new NoteBuilder(new Note({noteId: 'root', title: 'root', type: 'text'}));
 | 
				
			||||||
        new Branch(becca, {branchId: 'root', noteId: 'root', parentNoteId: 'none', notePosition: 10});
 | 
					        new Branch({branchId: 'root', noteId: 'root', parentNoteId: 'none', notePosition: 10});
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it("simple path match", () => {
 | 
					    it("simple path match", () => {
 | 
				
			||||||
@@ -157,6 +157,21 @@ describe("Search", () => {
 | 
				
			|||||||
        expect(findNoteByTitle(searchResults, "Czech Republic")).toBeTruthy();
 | 
					        expect(findNoteByTitle(searchResults, "Czech Republic")).toBeTruthy();
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it("inherited label comparison", () => {
 | 
				
			||||||
 | 
					        rootNote
 | 
				
			||||||
 | 
					            .child(note("Europe")
 | 
				
			||||||
 | 
					                .label('country', '', true)
 | 
				
			||||||
 | 
					                .child(note("Austria"))
 | 
				
			||||||
 | 
					                .child(note("Czech Republic"))
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const searchContext = new SearchContext();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const searchResults = searchService.findResultsWithQuery('austria #country', searchContext);
 | 
				
			||||||
 | 
					        expect(searchResults.length).toEqual(1);
 | 
				
			||||||
 | 
					        expect(findNoteByTitle(searchResults, "Austria")).toBeTruthy();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it("numeric label comparison fallback to string comparison", () => {
 | 
					    it("numeric label comparison fallback to string comparison", () => {
 | 
				
			||||||
        // dates should not be coerced into numbers which would then give wrong numbers
 | 
					        // dates should not be coerced into numbers which would then give wrong numbers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -218,7 +233,7 @@ describe("Search", () => {
 | 
				
			|||||||
        test("#month = month", 1);
 | 
					        test("#month = month", 1);
 | 
				
			||||||
        test("#month = 'MONTH'", 0);
 | 
					        test("#month = 'MONTH'", 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        test("note.dateCreated =* month", 1);
 | 
					        test("note.dateCreated =* month", 2);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        test("#date = TODAY", 1);
 | 
					        test("#date = TODAY", 1);
 | 
				
			||||||
        test("#date = today", 1);
 | 
					        test("#date = today", 1);
 | 
				
			||||||
@@ -337,11 +352,11 @@ describe("Search", () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        const searchContext = new SearchContext();
 | 
					        const searchContext = new SearchContext();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        let searchResults = searchService.findResultsWithQuery('#city AND note.getAncestors().title = Europe', searchContext);
 | 
					        let searchResults = searchService.findResultsWithQuery('#city AND note.ancestors.title = Europe', searchContext);
 | 
				
			||||||
        expect(searchResults.length).toEqual(1);
 | 
					        expect(searchResults.length).toEqual(1);
 | 
				
			||||||
        expect(findNoteByTitle(searchResults, "Prague")).toBeTruthy();
 | 
					        expect(findNoteByTitle(searchResults, "Prague")).toBeTruthy();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        searchResults = searchService.findResultsWithQuery('#city AND note.getAncestors().title = Asia', searchContext);
 | 
					        searchResults = searchService.findResultsWithQuery('#city AND note.ancestors.title = Asia', searchContext);
 | 
				
			||||||
        expect(searchResults.length).toEqual(1);
 | 
					        expect(searchResults.length).toEqual(1);
 | 
				
			||||||
        expect(findNoteByTitle(searchResults, "Taipei")).toBeTruthy();
 | 
					        expect(findNoteByTitle(searchResults, "Taipei")).toBeTruthy();
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -58,6 +58,9 @@ class Branch extends AbstractEntity {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    init() {
 | 
					    init() {
 | 
				
			||||||
 | 
					        this.becca.branches[this.branchId] = this;
 | 
				
			||||||
 | 
					        this.becca.childParentToBranch[`${this.noteId}-${this.parentNoteId}`] = this;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (this.branchId === 'root') {
 | 
					        if (this.branchId === 'root') {
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@@ -76,15 +79,12 @@ class Branch extends AbstractEntity {
 | 
				
			|||||||
        if (!parentNote.children.includes(childNote)) {
 | 
					        if (!parentNote.children.includes(childNote)) {
 | 
				
			||||||
            parentNote.children.push(childNote);
 | 
					            parentNote.children.push(childNote);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					 | 
				
			||||||
        this.becca.branches[this.branchId] = this;
 | 
					 | 
				
			||||||
        this.becca.childParentToBranch[`${this.noteId}-${this.parentNoteId}`] = this;
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /** @returns {Note} */
 | 
					    /** @returns {Note} */
 | 
				
			||||||
    get childNote() {
 | 
					    get childNote() {
 | 
				
			||||||
        if (!(this.noteId in this.becca.notes)) {
 | 
					        if (!(this.noteId in this.becca.notes)) {
 | 
				
			||||||
            // entities can come out of order in sync, create skeleton which will be filled later
 | 
					            // entities can come out of order in sync/import, create skeleton which will be filled later
 | 
				
			||||||
            this.becca.addNote(this.noteId, new Note({noteId: this.noteId}));
 | 
					            this.becca.addNote(this.noteId, new Note({noteId: this.noteId}));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -98,7 +98,7 @@ class Branch extends AbstractEntity {
 | 
				
			|||||||
    /** @returns {Note} */
 | 
					    /** @returns {Note} */
 | 
				
			||||||
    get parentNote() {
 | 
					    get parentNote() {
 | 
				
			||||||
        if (!(this.parentNoteId in this.becca.notes)) {
 | 
					        if (!(this.parentNoteId in this.becca.notes)) {
 | 
				
			||||||
            // entities can come out of order in sync, create skeleton which will be filled later
 | 
					            // entities can come out of order in sync/import, create skeleton which will be filled later
 | 
				
			||||||
            this.becca.addNote(this.parentNoteId, new Note({noteId: this.parentNoteId}));
 | 
					            this.becca.addNote(this.parentNoteId, new Note({noteId: this.parentNoteId}));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -30,12 +30,12 @@ const TPL = `
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    <div class="form-group">
 | 
					    <div class="form-group">
 | 
				
			||||||
        <label for="image-max-width-height">Max width / height of an image in pixels (image will be resized if it exceeds this setting).</label>
 | 
					        <label for="image-max-width-height">Max width / height of an image in pixels (image will be resized if it exceeds this setting).</label>
 | 
				
			||||||
        <input class="form-control" id="image-max-width-height" type="number">
 | 
					        <input class="form-control" id="image-max-width-height" type="number" min="1">
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <div class="form-group">
 | 
					    <div class="form-group">
 | 
				
			||||||
        <label for="image-jpeg-quality">JPEG quality (0 - worst quality, 100 best quality, 50 - 85 is recommended)</label>
 | 
					        <label for="image-jpeg-quality">JPEG quality (10 - worst quality, 100 best quality, 50 - 85 is recommended)</label>
 | 
				
			||||||
        <input class="form-control" id="image-jpeg-quality" min="0" max="100" type="number">
 | 
					        <input class="form-control" id="image-jpeg-quality" min="10" max="100" type="number">
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -67,7 +67,7 @@ const TPL = `
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    <div class="form-group">
 | 
					    <div class="form-group">
 | 
				
			||||||
        <label for="protected-session-timeout-in-seconds">Protected session timeout (in seconds)</label>
 | 
					        <label for="protected-session-timeout-in-seconds">Protected session timeout (in seconds)</label>
 | 
				
			||||||
        <input class="form-control" id="protected-session-timeout-in-seconds" type="number">
 | 
					        <input class="form-control" id="protected-session-timeout-in-seconds" type="number" min="60">
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -78,7 +78,7 @@ const TPL = `
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    <div class="form-group">
 | 
					    <div class="form-group">
 | 
				
			||||||
        <label for="note-revision-snapshot-time-interval-in-seconds">Note revision snapshot time interval (in seconds)</label>
 | 
					        <label for="note-revision-snapshot-time-interval-in-seconds">Note revision snapshot time interval (in seconds)</label>
 | 
				
			||||||
        <input class="form-control" id="note-revision-snapshot-time-interval-in-seconds" type="number">
 | 
					        <input class="form-control" id="note-revision-snapshot-time-interval-in-seconds" type="number" min="10">
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
</div>
 | 
					</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -89,12 +89,12 @@ const TPL = `
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    <div class="form-group">
 | 
					    <div class="form-group">
 | 
				
			||||||
        <label for="auto-readonly-size-text">Automatic readonly size (text notes)</label>
 | 
					        <label for="auto-readonly-size-text">Automatic readonly size (text notes)</label>
 | 
				
			||||||
        <input class="form-control" id="auto-readonly-size-text" type="number">
 | 
					        <input class="form-control" id="auto-readonly-size-text" type="number" min="0">
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <div class="form-group">
 | 
					    <div class="form-group">
 | 
				
			||||||
        <label for="auto-readonly-size-code">Automatic readonly size (code notes)</label>
 | 
					        <label for="auto-readonly-size-code">Automatic readonly size (code notes)</label>
 | 
				
			||||||
        <input class="form-control" id="auto-readonly-size-code" type="number">
 | 
					        <input class="form-control" id="auto-readonly-size-code" type="number" min="0">
 | 
				
			||||||
    </div>
 | 
					    </div>
 | 
				
			||||||
</div>`;
 | 
					</div>`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -88,7 +88,7 @@ export default class MobileLayout {
 | 
				
			|||||||
        return new FlexContainer('row').cssBlock(MOBILE_CSS)
 | 
					        return new FlexContainer('row').cssBlock(MOBILE_CSS)
 | 
				
			||||||
            .setParent(appContext)
 | 
					            .setParent(appContext)
 | 
				
			||||||
            .id('root-widget')
 | 
					            .id('root-widget')
 | 
				
			||||||
            .css('height', '100vh')
 | 
					            .css('height', '100%')
 | 
				
			||||||
            .child(new ScreenContainer("tree", 'column')
 | 
					            .child(new ScreenContainer("tree", 'column')
 | 
				
			||||||
                .class("d-sm-flex d-md-flex d-lg-flex d-xl-flex col-12 col-sm-5 col-md-4 col-lg-4 col-xl-4")
 | 
					                .class("d-sm-flex d-md-flex d-lg-flex d-xl-flex col-12 col-sm-5 col-md-4 col-lg-4 col-xl-4")
 | 
				
			||||||
                .css("max-height", "100%")
 | 
					                .css("max-height", "100%")
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -213,9 +213,13 @@ export default class Entrypoints extends Component {
 | 
				
			|||||||
        } else if (note.mime.endsWith("env=backend")) {
 | 
					        } else if (note.mime.endsWith("env=backend")) {
 | 
				
			||||||
            await server.post('script/run/' + note.noteId);
 | 
					            await server.post('script/run/' + note.noteId);
 | 
				
			||||||
        } else if (note.mime === 'text/x-sqlite;schema=trilium') {
 | 
					        } else if (note.mime === 'text/x-sqlite;schema=trilium') {
 | 
				
			||||||
            const {results} = await server.post("sql/execute/" + note.noteId);
 | 
					            const resp = await server.post("sql/execute/" + note.noteId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            await appContext.triggerEvent('sqlQueryResults', {ntxId: ntxId, results: results});
 | 
					            if (!resp.success) {
 | 
				
			||||||
 | 
					                alert("Error occurred while executing SQL query: " + resp.message);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            await appContext.triggerEvent('sqlQueryResults', {ntxId: ntxId, results: resp.results});
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        toastService.showMessage("Note executed");
 | 
					        toastService.showMessage("Note executed");
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -83,6 +83,10 @@ async function resolveNotePathToSegments(notePath, hoistedNoteId = 'root', logEr
 | 
				
			|||||||
                if (someNotePath) { // in case it's root the path may be empty
 | 
					                if (someNotePath) { // in case it's root the path may be empty
 | 
				
			||||||
                    const pathToRoot = someNotePath.split("/").reverse().slice(1);
 | 
					                    const pathToRoot = someNotePath.split("/").reverse().slice(1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if (!pathToRoot.includes("root")) {
 | 
				
			||||||
 | 
					                        pathToRoot.push('root');
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    for (const noteId of pathToRoot) {
 | 
					                    for (const noteId of pathToRoot) {
 | 
				
			||||||
                        effectivePathSegments.push(noteId);
 | 
					                        effectivePathSegments.push(noteId);
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -181,9 +181,9 @@ async function consumeFrontendUpdateData() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        for (const entityChange of nonProcessedEntityChanges) {
 | 
					        for (const entityChange of nonProcessedEntityChanges) {
 | 
				
			||||||
            processedEntityChangeIds.add(entityChange.id);
 | 
					            processedEntityChangeIds.add(entityChange.id);
 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        lastProcessedEntityChangeId = Math.max(lastProcessedEntityChangeId, allEntityChanges[allEntityChanges.length - 1].id);
 | 
					            lastProcessedEntityChangeId = Math.max(lastProcessedEntityChangeId, entityChange.id);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    checkEntityChangeIdListeners();
 | 
					    checkEntityChangeIdListeners();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -44,7 +44,10 @@ export default class ButtonWidget extends NoteContextAwareWidget {
 | 
				
			|||||||
        this.$widget.tooltip({
 | 
					        this.$widget.tooltip({
 | 
				
			||||||
            html: true,
 | 
					            html: true,
 | 
				
			||||||
            title: () => {
 | 
					            title: () => {
 | 
				
			||||||
                const title = this.settings.title;
 | 
					                const title = typeof this.settings.title === "function"
 | 
				
			||||||
 | 
					                    ? this.settings.title()
 | 
				
			||||||
 | 
					                    : this.settings.title;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                const action = actions.find(act => act.actionName === this.settings.command);
 | 
					                const action = actions.find(act => act.actionName === this.settings.command);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if (action && action.effectiveShortcuts.length > 0) {
 | 
					                if (action && action.effectiveShortcuts.length > 0) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,8 +5,25 @@ import froca from "../../services/froca.js";
 | 
				
			|||||||
export default class OpenNoteButtonWidget extends ButtonWidget {
 | 
					export default class OpenNoteButtonWidget extends ButtonWidget {
 | 
				
			||||||
    targetNote(noteId) {
 | 
					    targetNote(noteId) {
 | 
				
			||||||
        froca.getNote(noteId).then(note => {
 | 
					        froca.getNote(noteId).then(note => {
 | 
				
			||||||
 | 
					            if (!note) {
 | 
				
			||||||
 | 
					                console.log(`Note ${noteId} has not been found. This might happen on the first run before the target note is created.`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (!this.retried) {
 | 
				
			||||||
 | 
					                    this.retried = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    setTimeout(() => this.targetNote(noteId), 15000); // should be higher than timeout for createMissingSpecialNotes
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                return;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            this.icon(note.getIcon());
 | 
					            this.icon(note.getIcon());
 | 
				
			||||||
            this.title(note.title);
 | 
					            this.title(() => {
 | 
				
			||||||
 | 
					                const n = froca.getNoteFromCache(noteId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // always fresh, always decoded (when protected session is available)
 | 
				
			||||||
 | 
					                return n.title;
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            this.refreshIcon();
 | 
					            this.refreshIcon();
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,7 +7,7 @@ export default class RootContainer extends FlexContainer {
 | 
				
			|||||||
        super('row');
 | 
					        super('row');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.id('root-widget');
 | 
					        this.id('root-widget');
 | 
				
			||||||
        this.css('height', '100vh');
 | 
					        this.css('height', '100%');
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    refresh() {
 | 
					    refresh() {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -150,6 +150,7 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
 | 
				
			|||||||
    doRender() {
 | 
					    doRender() {
 | 
				
			||||||
        this.$widget = $(TPL);
 | 
					        this.$widget = $(TPL);
 | 
				
			||||||
        this.$tree = this.$widget.find('.tree');
 | 
					        this.$tree = this.$widget.find('.tree');
 | 
				
			||||||
 | 
					        this.$treeActions = this.$widget.find(".tree-actions");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.$tree.on("mousedown", ".unhoist-button", () => hoistedNoteService.unhoist());
 | 
					        this.$tree.on("mousedown", ".unhoist-button", () => hoistedNoteService.unhoist());
 | 
				
			||||||
        this.$tree.on("mousedown", ".refresh-search-button", e => this.refreshSearch(e));
 | 
					        this.$tree.on("mousedown", ".refresh-search-button", e => this.refreshSearch(e));
 | 
				
			||||||
@@ -200,20 +201,16 @@ export default class NoteTreeWidget extends NoteContextAwareWidget {
 | 
				
			|||||||
            this.$hideIncludedImages.prop("checked", this.hideIncludedImages);
 | 
					            this.$hideIncludedImages.prop("checked", this.hideIncludedImages);
 | 
				
			||||||
            this.$autoCollapseNoteTree.prop("checked", this.autoCollapseNoteTree);
 | 
					            this.$autoCollapseNoteTree.prop("checked", this.autoCollapseNoteTree);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            let top = this.$treeSettingsButton[0].offsetTop;
 | 
					            const top = this.$treeActions[0].offsetTop - (this.$treeSettingsPopup.outerHeight());
 | 
				
			||||||
            let left = this.$treeSettingsButton[0].offsetLeft;
 | 
					            const left = Math.max(
 | 
				
			||||||
            top -= this.$treeSettingsPopup.outerHeight() + 10;
 | 
					                0,
 | 
				
			||||||
            left += this.$treeSettingsButton.outerWidth() - this.$treeSettingsPopup.outerWidth();
 | 
					                this.$treeActions[0].offsetLeft - this.$treeSettingsPopup.outerWidth() + this.$treeActions.outerWidth()
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
            if (left < 0) {
 | 
					 | 
				
			||||||
                left = 0;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            this.$treeSettingsPopup.css({
 | 
					            this.$treeSettingsPopup.css({
 | 
				
			||||||
                display: "block",
 | 
					                top,
 | 
				
			||||||
                top: top,
 | 
					                left
 | 
				
			||||||
                left: left
 | 
					            }).show();
 | 
				
			||||||
            }).addClass("show");
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return false;
 | 
					            return false;
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -134,6 +134,8 @@ export default class QuickSearchWidget extends BasicWidget {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async showInFullSearch() {
 | 
					    async showInFullSearch() {
 | 
				
			||||||
 | 
					        this.$dropdownToggle.dropdown("hide");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const searchNote = await dateNotesService.createSearchNote({searchString: this.$searchString.val()});
 | 
					        const searchNote = await dateNotesService.createSearchNote({searchString: this.$searchString.val()});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        await froca.loadSearchNote(searchNote.noteId);
 | 
					        await froca.loadSearchNote(searchNote.noteId);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -69,7 +69,10 @@ export default class NotePathsWidget extends NoteContextAwareWidget {
 | 
				
			|||||||
        this.$notePathList.empty();
 | 
					        this.$notePathList.empty();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (this.noteId === 'root') {
 | 
					        if (this.noteId === 'root') {
 | 
				
			||||||
            await this.getRenderedPath('root');
 | 
					            this.$notePathList.empty().append(
 | 
				
			||||||
 | 
					                await this.getRenderedPath('root')
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -94,7 +97,7 @@ export default class NotePathsWidget extends NoteContextAwareWidget {
 | 
				
			|||||||
        this.$notePathList.empty().append(...renderedPaths);
 | 
					        this.$notePathList.empty().append(...renderedPaths);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async getRenderedPath(notePath, notePathRecord) {
 | 
					    async getRenderedPath(notePath, notePathRecord = null) {
 | 
				
			||||||
        const title = await treeService.getNotePathTitle(notePath);
 | 
					        const title = await treeService.getNotePathTitle(notePath);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const $noteLink = await linkService.createNoteLink(notePath, {title});
 | 
					        const $noteLink = await linkService.createNoteLink(notePath, {title});
 | 
				
			||||||
@@ -109,20 +112,20 @@ export default class NotePathsWidget extends NoteContextAwareWidget {
 | 
				
			|||||||
            $noteLink.addClass("path-current");
 | 
					            $noteLink.addClass("path-current");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (notePathRecord.isInHoistedSubTree) {
 | 
					        if (!notePathRecord || notePathRecord.isInHoistedSubTree) {
 | 
				
			||||||
            $noteLink.addClass("path-in-hoisted-subtree");
 | 
					            $noteLink.addClass("path-in-hoisted-subtree");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        else {
 | 
					        else {
 | 
				
			||||||
            icons.push(`<span class="bx bx-trending-up" title="This path is outside of hoisted note and you would have to unhoist."></span>`);
 | 
					            icons.push(`<span class="bx bx-trending-up" title="This path is outside of hoisted note and you would have to unhoist."></span>`);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (notePathRecord.isArchived) {
 | 
					        if (notePathRecord?.isArchived) {
 | 
				
			||||||
            $noteLink.addClass("path-archived");
 | 
					            $noteLink.addClass("path-archived");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            icons.push(`<span class="bx bx-archive" title="Archived"></span>`);
 | 
					            icons.push(`<span class="bx bx-archive" title="Archived"></span>`);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (notePathRecord.isSearch) {
 | 
					        if (notePathRecord?.isSearch) {
 | 
				
			||||||
            $noteLink.addClass("path-search");
 | 
					            $noteLink.addClass("path-search");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            icons.push(`<span class="bx bx-search" title="Search"></span>`);
 | 
					            icons.push(`<span class="bx bx-search" title="Search"></span>`);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -115,9 +115,9 @@ export default class PromotedAttributesWidget extends NoteContextAwareWidget {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        const $input = $("<input>")
 | 
					        const $input = $("<input>")
 | 
				
			||||||
            .prop("tabindex", 200 + definitionAttr.position)
 | 
					            .prop("tabindex", 200 + definitionAttr.position)
 | 
				
			||||||
            .prop("attribute-id", valueAttr.noteId === this.noteId ? valueAttr.attributeId : '') // if not owned, we'll force creation of a new attribute instead of updating the inherited one
 | 
					            .attr("data-attribute-id", valueAttr.noteId === this.noteId ? valueAttr.attributeId : '') // if not owned, we'll force creation of a new attribute instead of updating the inherited one
 | 
				
			||||||
            .prop("attribute-type", valueAttr.type)
 | 
					            .attr("data-attribute-type", valueAttr.type)
 | 
				
			||||||
            .prop("attribute-name", valueAttr.name)
 | 
					            .attr("data-attribute-name", valueAttr.name)
 | 
				
			||||||
            .prop("value", valueAttr.value)
 | 
					            .prop("value", valueAttr.value)
 | 
				
			||||||
            .addClass("form-control")
 | 
					            .addClass("form-control")
 | 
				
			||||||
            .addClass("promoted-attribute-input")
 | 
					            .addClass("promoted-attribute-input")
 | 
				
			||||||
@@ -230,7 +230,7 @@ export default class PromotedAttributesWidget extends NoteContextAwareWidget {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (definition.multiplicity === "multi") {
 | 
					        if (definition.multiplicity === "multi") {
 | 
				
			||||||
            const addButton = $("<span>")
 | 
					            const $addButton = $("<span>")
 | 
				
			||||||
                .addClass("bx bx-plus pointer")
 | 
					                .addClass("bx bx-plus pointer")
 | 
				
			||||||
                .prop("title", "Add new attribute")
 | 
					                .prop("title", "Add new attribute")
 | 
				
			||||||
                .on('click', async () => {
 | 
					                .on('click', async () => {
 | 
				
			||||||
@@ -246,12 +246,28 @@ export default class PromotedAttributesWidget extends NoteContextAwareWidget {
 | 
				
			|||||||
                    $new.find('input').trigger('focus');
 | 
					                    $new.find('input').trigger('focus');
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            const removeButton = $("<span>")
 | 
					            const $removeButton = $("<span>")
 | 
				
			||||||
                .addClass("bx bx-trash pointer")
 | 
					                .addClass("bx bx-trash pointer")
 | 
				
			||||||
                .prop("title", "Remove this attribute")
 | 
					                .prop("title", "Remove this attribute")
 | 
				
			||||||
                .on('click', async () => {
 | 
					                .on('click', async () => {
 | 
				
			||||||
                    if (valueAttr.attributeId) {
 | 
					                    const attributeId = $input.attr("data-attribute-id");
 | 
				
			||||||
                        await server.remove("notes/" + this.noteId + "/attributes/" + valueAttr.attributeId, this.componentId);
 | 
					
 | 
				
			||||||
 | 
					                    if (attributeId) {
 | 
				
			||||||
 | 
					                        await server.remove("notes/" + this.noteId + "/attributes/" + attributeId, this.componentId);
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    // if it's the last one the create new empty form immediately
 | 
				
			||||||
 | 
					                    const sameAttrSelector = `input[data-attribute-type='${valueAttr.type}'][data-attribute-name='${valueName}']`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    if (this.$widget.find(sameAttrSelector).length <= 1) {
 | 
				
			||||||
 | 
					                        const $new = await this.createPromotedAttributeCell(definitionAttr, {
 | 
				
			||||||
 | 
					                            attributeId: "",
 | 
				
			||||||
 | 
					                            type: valueAttr.type,
 | 
				
			||||||
 | 
					                            name: valueName,
 | 
				
			||||||
 | 
					                            value: ""
 | 
				
			||||||
 | 
					                        }, valueName);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        $wrapper.after($new);
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    $wrapper.remove();
 | 
					                    $wrapper.remove();
 | 
				
			||||||
@@ -259,9 +275,9 @@ export default class PromotedAttributesWidget extends NoteContextAwareWidget {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            $multiplicityCell
 | 
					            $multiplicityCell
 | 
				
			||||||
                .append("  ")
 | 
					                .append("  ")
 | 
				
			||||||
                .append(addButton)
 | 
					                .append($addButton)
 | 
				
			||||||
                .append("  ")
 | 
					                .append("  ")
 | 
				
			||||||
                .append(removeButton);
 | 
					                .append($removeButton);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return $wrapper;
 | 
					        return $wrapper;
 | 
				
			||||||
@@ -275,7 +291,7 @@ export default class PromotedAttributesWidget extends NoteContextAwareWidget {
 | 
				
			|||||||
        if ($attr.prop("type") === "checkbox") {
 | 
					        if ($attr.prop("type") === "checkbox") {
 | 
				
			||||||
            value = $attr.is(':checked') ? "true" : "false";
 | 
					            value = $attr.is(':checked') ? "true" : "false";
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        else if ($attr.prop("attribute-type") === "relation") {
 | 
					        else if ($attr.attr("data-attribute-type") === "relation") {
 | 
				
			||||||
            const selectedPath = $attr.getSelectedNotePath();
 | 
					            const selectedPath = $attr.getSelectedNotePath();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            value = selectedPath ? treeService.getNoteIdFromNotePath(selectedPath) : "";
 | 
					            value = selectedPath ? treeService.getNoteIdFromNotePath(selectedPath) : "";
 | 
				
			||||||
@@ -285,13 +301,13 @@ export default class PromotedAttributesWidget extends NoteContextAwareWidget {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const result = await server.put(`notes/${this.noteId}/attribute`, {
 | 
					        const result = await server.put(`notes/${this.noteId}/attribute`, {
 | 
				
			||||||
            attributeId: $attr.prop("attribute-id"),
 | 
					            attributeId: $attr.attr("data-attribute-id"),
 | 
				
			||||||
            type: $attr.prop("attribute-type"),
 | 
					            type: $attr.attr("data-attribute-type"),
 | 
				
			||||||
            name: $attr.prop("attribute-name"),
 | 
					            name: $attr.attr("data-attribute-name"),
 | 
				
			||||||
            value: value
 | 
					            value: value
 | 
				
			||||||
        }, this.componentId);
 | 
					        }, this.componentId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        $attr.prop("attribute-id", result.attributeId);
 | 
					        $attr.attr("data-attribute-id", result.attributeId);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    entitiesReloadedEvent({loadResults}) {
 | 
					    entitiesReloadedEvent({loadResults}) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -84,5 +84,11 @@ export default class EmptyTypeWidget extends TypeWidget {
 | 
				
			|||||||
                    .on('click', () => this.triggerCommand('hoistNote', {noteId: workspaceNote.noteId}))
 | 
					                    .on('click', () => this.triggerCommand('hoistNote', {noteId: workspaceNote.noteId}))
 | 
				
			||||||
            );
 | 
					            );
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (workspaceNotes.length === 0) {
 | 
				
			||||||
 | 
					            this.$autoComplete
 | 
				
			||||||
 | 
					                .trigger('focus')
 | 
				
			||||||
 | 
					                .trigger('select');
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -57,9 +57,7 @@ class ImageTypeWidget extends TypeWidget {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async doRefresh(note) {
 | 
					    async doRefresh(note) {
 | 
				
			||||||
        const imageHash = utils.randomString(10);
 | 
					        this.$imageView.prop("src", `api/images/${note.noteId}/${note.title}`);
 | 
				
			||||||
 | 
					 | 
				
			||||||
        this.$imageView.prop("src", `api/images/${note.noteId}/${note.title}?${imageHash}`);
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    copyImageToClipboardEvent({ntxId}) {
 | 
					    copyImageToClipboardEvent({ntxId}) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -18,9 +18,11 @@ body {
 | 
				
			|||||||
       on the last line of the editor. */
 | 
					       on the last line of the editor. */
 | 
				
			||||||
    position: fixed;
 | 
					    position: fixed;
 | 
				
			||||||
    width: 100%;
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					    height: 100%;
 | 
				
			||||||
    background-color: var(--main-background-color);
 | 
					    background-color: var(--main-background-color);
 | 
				
			||||||
    color: var(--main-text-color);
 | 
					    color: var(--main-text-color);
 | 
				
			||||||
    font-family: var(--main-font-family);
 | 
					    font-family: var(--main-font-family);
 | 
				
			||||||
 | 
					    font-size: var(--main-font-size);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
a, a:visited, a:hover {
 | 
					a, a:visited, a:hover {
 | 
				
			||||||
@@ -58,7 +60,7 @@ table td, table th {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
code, kbd, pre, samp {
 | 
					code, kbd, pre, samp {
 | 
				
			||||||
    font-family: var(--monospace-font-family);
 | 
					    font-family: var(--monospace-font-family) !important;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.input-group-text {
 | 
					.input-group-text {
 | 
				
			||||||
@@ -714,10 +716,6 @@ a.external:not(.no-arrow):after, a[href^="http://"]:not(.no-arrow):after, a[href
 | 
				
			|||||||
    border-color: var(--main-border-color) !important;
 | 
					    border-color: var(--main-border-color) !important;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
body {
 | 
					 | 
				
			||||||
    font-size: var(--main-font-size);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
.gutter {
 | 
					.gutter {
 | 
				
			||||||
    background: linear-gradient(to bottom, transparent, var(--accented-background-color), transparent);
 | 
					    background: linear-gradient(to bottom, transparent, var(--accented-background-color), transparent);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -60,6 +60,7 @@ function downloadNoteFile(noteId, res, contentDisposition = true) {
 | 
				
			|||||||
        res.setHeader('Content-Disposition', utils.getContentDisposition(filename));
 | 
					        res.setHeader('Content-Disposition', utils.getContentDisposition(filename));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    res.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
 | 
				
			||||||
    res.setHeader('Content-Type', note.mime);
 | 
					    res.setHeader('Content-Type', note.mime);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    res.send(note.getContent());
 | 
					    res.send(note.getContent());
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,6 +20,7 @@ function returnImage(req, res) {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    res.set('Content-Type', image.mime);
 | 
					    res.set('Content-Type', image.mime);
 | 
				
			||||||
 | 
					    res.set("Cache-Control", "no-cache, no-store, must-revalidate");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    res.send(image.getContent());
 | 
					    res.send(image.getContent());
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -42,6 +42,11 @@ function getNeighbors(note, depth) {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const targetNote = relation.getTargetNote();
 | 
					        const targetNote = relation.getTargetNote();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (targetNote.hasLabel('excludeFromNoteMap')) {
 | 
				
			||||||
 | 
					            continue;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        retNoteIds.push(targetNote.noteId);
 | 
					        retNoteIds.push(targetNote.noteId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for (const noteId of getNeighbors(targetNote, depth - 1)) {
 | 
					        for (const noteId of getNeighbors(targetNote, depth - 1)) {
 | 
				
			||||||
@@ -56,6 +61,11 @@ function getNeighbors(note, depth) {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const sourceNote = relation.getNote();
 | 
					        const sourceNote = relation.getNote();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (sourceNote.hasLabel('excludeFromNoteMap')) {
 | 
				
			||||||
 | 
					            continue;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        retNoteIds.push(sourceNote.noteId);
 | 
					        retNoteIds.push(sourceNote.noteId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for (const noteId of getNeighbors(sourceNote, depth - 1)) {
 | 
					        for (const noteId of getNeighbors(sourceNote, depth - 1)) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -203,6 +203,10 @@ function changeTitle(req) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    const noteTitleChanged = note.title !== title;
 | 
					    const noteTitleChanged = note.title !== title;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (noteTitleChanged) {
 | 
				
			||||||
 | 
					        noteService.saveNoteRevision(note);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    note.title = title;
 | 
					    note.title = title;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    note.save();
 | 
					    note.save();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -204,6 +204,11 @@ function queueSector(req) {
 | 
				
			|||||||
    entityChangesService.addEntityChangesForSector(entityName, sector);
 | 
					    entityChangesService.addEntityChangesForSector(entityName, sector);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function checkEntityChanges() {
 | 
				
			||||||
 | 
					    const consistencyChecks = require("../../services/consistency_checks");
 | 
				
			||||||
 | 
					    consistencyChecks.runEntityChangesChecks();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports = {
 | 
					module.exports = {
 | 
				
			||||||
    testSync,
 | 
					    testSync,
 | 
				
			||||||
    checkSync,
 | 
					    checkSync,
 | 
				
			||||||
@@ -215,5 +220,6 @@ module.exports = {
 | 
				
			|||||||
    update,
 | 
					    update,
 | 
				
			||||||
    getStats,
 | 
					    getStats,
 | 
				
			||||||
    syncFinished,
 | 
					    syncFinished,
 | 
				
			||||||
    queueSector
 | 
					    queueSector,
 | 
				
			||||||
 | 
					    checkEntityChanges
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -294,6 +294,7 @@ function register(app) {
 | 
				
			|||||||
    route(GET, '/api/sync/changed', [auth.checkApiAuth], syncApiRoute.getChanged, apiResultHandler);
 | 
					    route(GET, '/api/sync/changed', [auth.checkApiAuth], syncApiRoute.getChanged, apiResultHandler);
 | 
				
			||||||
    route(PUT, '/api/sync/update', [auth.checkApiAuth], syncApiRoute.update, apiResultHandler);
 | 
					    route(PUT, '/api/sync/update', [auth.checkApiAuth], syncApiRoute.update, apiResultHandler);
 | 
				
			||||||
    route(POST, '/api/sync/finished', [auth.checkApiAuth], syncApiRoute.syncFinished, apiResultHandler);
 | 
					    route(POST, '/api/sync/finished', [auth.checkApiAuth], syncApiRoute.syncFinished, apiResultHandler);
 | 
				
			||||||
 | 
					    route(POST, '/api/sync/check-entity-changes', [auth.checkApiAuth], syncApiRoute.checkEntityChanges, apiResultHandler);
 | 
				
			||||||
    route(POST, '/api/sync/queue-sector/:entityName/:sector', [auth.checkApiAuth], syncApiRoute.queueSector, apiResultHandler);
 | 
					    route(POST, '/api/sync/queue-sector/:entityName/:sector', [auth.checkApiAuth], syncApiRoute.queueSector, apiResultHandler);
 | 
				
			||||||
    route(GET, '/api/sync/stats', [], syncApiRoute.getStats, apiResultHandler);
 | 
					    route(GET, '/api/sync/stats', [], syncApiRoute.getStats, apiResultHandler);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,7 +5,7 @@ const packageJson = require('../../package');
 | 
				
			|||||||
const {TRILIUM_DATA_DIR} = require('./data_dir');
 | 
					const {TRILIUM_DATA_DIR} = require('./data_dir');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const APP_DB_VERSION = 185;
 | 
					const APP_DB_VERSION = 185;
 | 
				
			||||||
const SYNC_VERSION = 21;
 | 
					const SYNC_VERSION = 22;
 | 
				
			||||||
const CLIPPER_PROTOCOL_VERSION = "1.0";
 | 
					const CLIPPER_PROTOCOL_VERSION = "1.0";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports = {
 | 
					module.exports = {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -62,6 +62,7 @@ const BUILTIN_ATTRIBUTES = [
 | 
				
			|||||||
    { type: 'relation', name: 'renderNote', isDangerous: true }
 | 
					    { type: 'relation', name: 'renderNote', isDangerous: true }
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/** @returns {Note[]} */
 | 
				
			||||||
function getNotesWithLabel(name, value) {
 | 
					function getNotesWithLabel(name, value) {
 | 
				
			||||||
    const query = formatAttrForSearch({type: 'label', name, value}, true);
 | 
					    const query = formatAttrForSearch({type: 'label', name, value}, true);
 | 
				
			||||||
    return searchService.searchNotes(query, {
 | 
					    return searchService.searchNotes(query, {
 | 
				
			||||||
@@ -71,6 +72,7 @@ function getNotesWithLabel(name, value) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// TODO: should be in search service
 | 
					// TODO: should be in search service
 | 
				
			||||||
 | 
					/** @returns {Note|null} */
 | 
				
			||||||
function getNoteWithLabel(name, value) {
 | 
					function getNoteWithLabel(name, value) {
 | 
				
			||||||
    // optimized version (~20 times faster) without using normal search, useful for e.g. finding date notes
 | 
					    // optimized version (~20 times faster) without using normal search, useful for e.g. finding date notes
 | 
				
			||||||
    const attrs = becca.findAttributes('label', name);
 | 
					    const attrs = becca.findAttributes('label', name);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1 +1 @@
 | 
				
			|||||||
module.exports = { buildDate:"2021-11-13T22:49:58+01:00", buildRevision: "c94603010630cfafe64575ab378c482bb39fb083" };
 | 
					module.exports = { buildDate:"2021-12-22T22:39:24+01:00", buildRevision: "10a5773c66e678c6d2bd3bd9dc9952d5cd76b795" };
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -258,7 +258,8 @@ class ConsistencyChecks {
 | 
				
			|||||||
                             FROM branches
 | 
					                             FROM branches
 | 
				
			||||||
                             WHERE noteId = ?
 | 
					                             WHERE noteId = ?
 | 
				
			||||||
                               and parentNoteId = ?
 | 
					                               and parentNoteId = ?
 | 
				
			||||||
                               and isDeleted = 0`, [noteId, parentNoteId]);
 | 
					                               and isDeleted = 0
 | 
				
			||||||
 | 
					                             ORDER BY utcDateCreated`, [noteId, parentNoteId]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    const branches = branchIds.map(branchId => becca.getBranch(branchId));
 | 
					                    const branches = branchIds.map(branchId => becca.getBranch(branchId));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -537,6 +538,27 @@ class ConsistencyChecks {
 | 
				
			|||||||
                        logError(`Unrecognized entity change id=${id}, entityName=${entityName}, entityId=${entityId}`);
 | 
					                        logError(`Unrecognized entity change id=${id}, entityName=${entityName}, entityId=${entityId}`);
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                });
 | 
					                });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.findAndFixIssues(`
 | 
				
			||||||
 | 
					            SELECT 
 | 
				
			||||||
 | 
					              id, entityId
 | 
				
			||||||
 | 
					            FROM 
 | 
				
			||||||
 | 
					              entity_changes 
 | 
				
			||||||
 | 
					              JOIN ${entityName} ON entityId = ${key} 
 | 
				
			||||||
 | 
					            WHERE
 | 
				
			||||||
 | 
					              entity_changes.isErased = 1
 | 
				
			||||||
 | 
					              AND entity_changes.entityName = '${entityName}'`,
 | 
				
			||||||
 | 
					            ({id, entityId}) => {
 | 
				
			||||||
 | 
					                if (this.autoFix) {
 | 
				
			||||||
 | 
					                    sql.execute(`DELETE FROM ${entityName} WHERE ${key} = ?`, [entityId]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    this.reloadNeeded = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                    logFix(`Erasing entityName=${entityName}, entityId=${entityId} since entity change id=${id} has it as erased.`);
 | 
				
			||||||
 | 
					                } else {
 | 
				
			||||||
 | 
					                    logError(`Entity change id=${id} has entityName=${entityName}, entityId=${entityId} as erased, but it's not.`);
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    findEntityChangeIssues() {
 | 
					    findEntityChangeIssues() {
 | 
				
			||||||
@@ -603,14 +625,14 @@ class ConsistencyChecks {
 | 
				
			|||||||
        this.fixedIssues = false;
 | 
					        this.fixedIssues = false;
 | 
				
			||||||
        this.reloadNeeded = false;
 | 
					        this.reloadNeeded = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.findEntityChangeIssues();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.findBrokenReferenceIssues();
 | 
					        this.findBrokenReferenceIssues();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.findExistencyIssues();
 | 
					        this.findExistencyIssues();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.findLogicIssues();
 | 
					        this.findLogicIssues();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.findEntityChangeIssues();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        this.findWronglyNamedAttributes();
 | 
					        this.findWronglyNamedAttributes();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.findSyncIssues();
 | 
					        this.findSyncIssues();
 | 
				
			||||||
@@ -701,6 +723,11 @@ function runOnDemandChecks(autoFix) {
 | 
				
			|||||||
    consistencyChecks.runChecks();
 | 
					    consistencyChecks.runChecks();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function runEntityChangesChecks() {
 | 
				
			||||||
 | 
					    const consistencyChecks = new ConsistencyChecks(true);
 | 
				
			||||||
 | 
					    consistencyChecks.findEntityChangeIssues();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
sqlInit.dbReady.then(() => {
 | 
					sqlInit.dbReady.then(() => {
 | 
				
			||||||
    setInterval(cls.wrap(runPeriodicChecks), 60 * 60 * 1000);
 | 
					    setInterval(cls.wrap(runPeriodicChecks), 60 * 60 * 1000);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -709,5 +736,6 @@ sqlInit.dbReady.then(() => {
 | 
				
			|||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports = {
 | 
					module.exports = {
 | 
				
			||||||
    runOnDemandChecks
 | 
					    runOnDemandChecks,
 | 
				
			||||||
 | 
					    runEntityChangesChecks
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -25,15 +25,6 @@ function createNote(parentNote, noteTitle) {
 | 
				
			|||||||
    }).note;
 | 
					    }).note;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function getNoteStartingWith(parentNoteId, startsWith) {
 | 
					 | 
				
			||||||
    const noteId = sql.getValue(`SELECT notes.noteId FROM notes JOIN branches USING(noteId) 
 | 
					 | 
				
			||||||
                                    WHERE parentNoteId = ? AND title LIKE '${startsWith}%'
 | 
					 | 
				
			||||||
                                    AND notes.isDeleted = 0 AND isProtected = 0 
 | 
					 | 
				
			||||||
                                    AND branches.isDeleted = 0`, [parentNoteId]);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return becca.getNote(noteId);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/** @returns {Note} */
 | 
					/** @returns {Note} */
 | 
				
			||||||
function getRootCalendarNote() {
 | 
					function getRootCalendarNote() {
 | 
				
			||||||
    let rootNote = attributeService.getNoteWithLabel(CALENDAR_ROOT_LABEL);
 | 
					    let rootNote = attributeService.getNoteWithLabel(CALENDAR_ROOT_LABEL);
 | 
				
			||||||
@@ -65,8 +56,7 @@ function getYearNote(dateStr, rootNote) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    const yearStr = dateStr.substr(0, 4);
 | 
					    const yearStr = dateStr.substr(0, 4);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let yearNote = attributeService.getNoteWithLabel(YEAR_LABEL, yearStr)
 | 
					    let yearNote = attributeService.getNoteWithLabel(YEAR_LABEL, yearStr);
 | 
				
			||||||
        || getNoteStartingWith(rootNote.noteId, yearStr);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (yearNote) {
 | 
					    if (yearNote) {
 | 
				
			||||||
        return yearNote;
 | 
					        return yearNote;
 | 
				
			||||||
@@ -112,18 +102,12 @@ function getMonthNote(dateStr, rootNote) {
 | 
				
			|||||||
        return monthNote;
 | 
					        return monthNote;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const yearNote = getYearNote(dateStr, rootNote);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    monthNote = getNoteStartingWith(yearNote.noteId, monthNumber);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (monthNote) {
 | 
					 | 
				
			||||||
        return monthNote;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const dateObj = dateUtils.parseLocalDate(dateStr);
 | 
					    const dateObj = dateUtils.parseLocalDate(dateStr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const noteTitle = getMonthNoteTitle(rootNote, monthNumber, dateObj);
 | 
					    const noteTitle = getMonthNoteTitle(rootNote, monthNumber, dateObj);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const yearNote = getYearNote(dateStr, rootNote);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    sql.transactional(() => {
 | 
					    sql.transactional(() => {
 | 
				
			||||||
        monthNote = createNote(yearNote, noteTitle);
 | 
					        monthNote = createNote(yearNote, noteTitle);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -164,12 +148,6 @@ function getDateNote(dateStr) {
 | 
				
			|||||||
    const monthNote = getMonthNote(dateStr, rootNote);
 | 
					    const monthNote = getMonthNote(dateStr, rootNote);
 | 
				
			||||||
    const dayNumber = dateStr.substr(8, 2);
 | 
					    const dayNumber = dateStr.substr(8, 2);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    dateNote = getNoteStartingWith(monthNote.noteId, dayNumber);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (dateNote) {
 | 
					 | 
				
			||||||
        return dateNote;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const dateObj = dateUtils.parseLocalDate(dateStr);
 | 
					    const dateObj = dateUtils.parseLocalDate(dateStr);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const noteTitle = getDateNoteTitle(rootNote, dayNumber, dateObj);
 | 
					    const noteTitle = getDateNoteTitle(rootNote, dayNumber, dateObj);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,12 +7,10 @@ const becca = require("../becca/becca");
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
let maxEntityChangeId = 0;
 | 
					let maxEntityChangeId = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function addEntityChange(origEntityChange, keepOriginalId = false) {
 | 
					function addEntityChange(origEntityChange) {
 | 
				
			||||||
    const ec = {...origEntityChange};
 | 
					    const ec = {...origEntityChange};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!keepOriginalId) {
 | 
					 | 
				
			||||||
    delete ec.id;
 | 
					    delete ec.id;
 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ec.sourceId = ec.sourceId || cls.getSourceId() || sourceIdService.getCurrentSourceId();
 | 
					    ec.sourceId = ec.sourceId || cls.getSourceId() || sourceIdService.getCurrentSourceId();
 | 
				
			||||||
    ec.isSynced = ec.isSynced ? 1 : 0;
 | 
					    ec.isSynced = ec.isSynced ? 1 : 0;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -50,7 +50,13 @@ function exportToZip(taskContext, branch, format, res) {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    function getDataFileName(note, baseFileName, existingFileNames) {
 | 
					    function getDataFileName(note, baseFileName, existingFileNames) {
 | 
				
			||||||
        const existingExtension = path.extname(baseFileName).toLowerCase();
 | 
					        let fileName = baseFileName;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (fileName.length > 30) {
 | 
				
			||||||
 | 
					            fileName = fileName.substr(0, 30);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let existingExtension = path.extname(fileName).toLowerCase();
 | 
				
			||||||
        let newExtension;
 | 
					        let newExtension;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // following two are handled specifically since we always want to have these extensions no matter the automatic detection
 | 
					        // following two are handled specifically since we always want to have these extensions no matter the automatic detection
 | 
				
			||||||
@@ -67,14 +73,13 @@ function exportToZip(taskContext, branch, format, res) {
 | 
				
			|||||||
        else if (existingExtension.length > 0) { // if the page already has an extension, then we'll just keep it
 | 
					        else if (existingExtension.length > 0) { // if the page already has an extension, then we'll just keep it
 | 
				
			||||||
            newExtension = null;
 | 
					            newExtension = null;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        else {
 | 
				
			||||||
 | 
					            if (note.mime?.toLowerCase()?.trim() === "image/jpg") {
 | 
				
			||||||
 | 
					                newExtension = 'jpg';
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
            else {
 | 
					            else {
 | 
				
			||||||
                newExtension = mimeTypes.extension(note.mime) || "dat";
 | 
					                newExtension = mimeTypes.extension(note.mime) || "dat";
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					 | 
				
			||||||
        let fileName = baseFileName;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (fileName.length > 30) {
 | 
					 | 
				
			||||||
            fileName = fileName.substr(0, 30);
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // if the note is already named with extension (e.g. "jquery"), then it's silly to append exact same extension again
 | 
					        // if the note is already named with extension (e.g. "jquery"), then it's silly to append exact same extension again
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -122,7 +122,11 @@ function saveImage(parentNoteId, uploadBuffer, originalName, shrinkImageSwitch,
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function shrinkImage(buffer, originalName) {
 | 
					async function shrinkImage(buffer, originalName) {
 | 
				
			||||||
    const jpegQuality = optionService.getOptionInt('imageJpegQuality');
 | 
					    let jpegQuality = optionService.getOptionInt('imageJpegQuality');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (jpegQuality < 10 || jpegQuality > 100) {
 | 
				
			||||||
 | 
					        jpegQuality = 75;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let finalImageBuffer;
 | 
					    let finalImageBuffer;
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -351,7 +351,19 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        let note = becca.getNote(noteId);
 | 
					        let note = becca.getNote(noteId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const isProtected = importRootNote.isProtected && protectedSessionService.isProtectedSessionAvailable();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if (note) {
 | 
					        if (note) {
 | 
				
			||||||
 | 
					            // only skeleton was created because of altered order of cloned notes in ZIP, we need to update
 | 
				
			||||||
 | 
					            // https://github.com/zadam/trilium/issues/2440
 | 
				
			||||||
 | 
					            if (note.type === undefined) {
 | 
				
			||||||
 | 
					                note.type = type;
 | 
				
			||||||
 | 
					                note.mime = mime;
 | 
				
			||||||
 | 
					                note.title = noteTitle;
 | 
				
			||||||
 | 
					                note.isProtected = isProtected;
 | 
				
			||||||
 | 
					                note.save();
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            note.setContent(content);
 | 
					            note.setContent(content);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        else {
 | 
					        else {
 | 
				
			||||||
@@ -367,7 +379,7 @@ async function importZip(taskContext, fileBuffer, importRootNote) {
 | 
				
			|||||||
                // root notePosition should be ignored since it relates to original document
 | 
					                // root notePosition should be ignored since it relates to original document
 | 
				
			||||||
                // now import root should be placed after existing notes into new parent
 | 
					                // now import root should be placed after existing notes into new parent
 | 
				
			||||||
                notePosition: (noteMeta && firstNote) ? noteMeta.notePosition : undefined,
 | 
					                notePosition: (noteMeta && firstNote) ? noteMeta.notePosition : undefined,
 | 
				
			||||||
                isProtected: importRootNote.isProtected && protectedSessionService.isProtectedSessionAvailable(),
 | 
					                isProtected: isProtected,
 | 
				
			||||||
            }));
 | 
					            }));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            createdNoteIds[note.noteId] = true;
 | 
					            createdNoteIds[note.noteId] = true;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -732,6 +732,8 @@ function eraseAttributes(attributeIdsToErase) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function eraseDeletedEntities(eraseEntitiesAfterTimeInSeconds = null) {
 | 
					function eraseDeletedEntities(eraseEntitiesAfterTimeInSeconds = null) {
 | 
				
			||||||
 | 
					    // this is important also so that the erased entity changes are sent to the connected clients
 | 
				
			||||||
 | 
					    sql.transactional(() => {
 | 
				
			||||||
        if (eraseEntitiesAfterTimeInSeconds === null) {
 | 
					        if (eraseEntitiesAfterTimeInSeconds === null) {
 | 
				
			||||||
            eraseEntitiesAfterTimeInSeconds = optionService.getOptionInt('eraseEntitiesAfterTimeInSeconds');
 | 
					            eraseEntitiesAfterTimeInSeconds = optionService.getOptionInt('eraseEntitiesAfterTimeInSeconds');
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
@@ -749,6 +751,7 @@ function eraseDeletedEntities(eraseEntitiesAfterTimeInSeconds = null) {
 | 
				
			|||||||
        const attributeIdsToErase = sql.getColumn("SELECT attributeId FROM attributes WHERE isDeleted = 1 AND utcDateModified <= ?", [dateUtils.utcDateTimeStr(cutoffDate)]);
 | 
					        const attributeIdsToErase = sql.getColumn("SELECT attributeId FROM attributes WHERE isDeleted = 1 AND utcDateModified <= ?", [dateUtils.utcDateTimeStr(cutoffDate)]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        eraseAttributes(attributeIdsToErase);
 | 
					        eraseAttributes(attributeIdsToErase);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function eraseNotesWithDeleteId(deleteId) {
 | 
					function eraseNotesWithDeleteId(deleteId) {
 | 
				
			||||||
@@ -918,5 +921,6 @@ module.exports = {
 | 
				
			|||||||
    getUndeletedParentBranchIds,
 | 
					    getUndeletedParentBranchIds,
 | 
				
			||||||
    triggerNoteTitleChanged,
 | 
					    triggerNoteTitleChanged,
 | 
				
			||||||
    eraseDeletedNotesNow,
 | 
					    eraseDeletedNotesNow,
 | 
				
			||||||
    eraseNotesWithDeleteId
 | 
					    eraseNotesWithDeleteId,
 | 
				
			||||||
 | 
					    saveNoteRevision
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -23,7 +23,6 @@ class AttributeExistsExp extends Expression {
 | 
				
			|||||||
        for (const attr of attrs) {
 | 
					        for (const attr of attrs) {
 | 
				
			||||||
            const note = attr.note;
 | 
					            const note = attr.note;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (inputNoteSet.hasNoteId(note.noteId)) {
 | 
					 | 
				
			||||||
            if (attr.isInheritable) {
 | 
					            if (attr.isInheritable) {
 | 
				
			||||||
                resultNoteSet.addAll(note.getSubtreeNotesIncludingTemplated());
 | 
					                resultNoteSet.addAll(note.getSubtreeNotesIncludingTemplated());
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
@@ -34,9 +33,8 @@ class AttributeExistsExp extends Expression {
 | 
				
			|||||||
                resultNoteSet.add(note);
 | 
					                resultNoteSet.add(note);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return resultNoteSet;
 | 
					        return resultNoteSet.intersection(inputNoteSet);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,6 +8,7 @@ const protectedSessionService = require('../../protected_session');
 | 
				
			|||||||
const striptags = require('striptags');
 | 
					const striptags = require('striptags');
 | 
				
			||||||
const utils = require("../../utils");
 | 
					const utils = require("../../utils");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// FIXME: create common subclass with NoteContentUnprotectedFulltextExp to avoid duplication
 | 
				
			||||||
class NoteContentProtectedFulltextExp extends Expression {
 | 
					class NoteContentProtectedFulltextExp extends Expression {
 | 
				
			||||||
    constructor(operator, tokens, raw) {
 | 
					    constructor(operator, tokens, raw) {
 | 
				
			||||||
        super();
 | 
					        super();
 | 
				
			||||||
@@ -46,15 +47,7 @@ class NoteContentProtectedFulltextExp extends Expression {
 | 
				
			|||||||
                continue;
 | 
					                continue;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            content = utils.normalize(content);
 | 
					            content = this.preprocessContent(content, type, mime);
 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (type === 'text' && mime === 'text/html') {
 | 
					 | 
				
			||||||
                if (!this.raw && content.length < 20000) { // striptags is slow for very large notes
 | 
					 | 
				
			||||||
                    content = striptags(content);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                content = content.replace(/ /g, ' ');
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (!this.tokens.find(token => !content.includes(token))) {
 | 
					            if (!this.tokens.find(token => !content.includes(token))) {
 | 
				
			||||||
                resultNoteSet.add(becca.notes[noteId]);
 | 
					                resultNoteSet.add(becca.notes[noteId]);
 | 
				
			||||||
@@ -63,6 +56,23 @@ class NoteContentProtectedFulltextExp extends Expression {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        return resultNoteSet;
 | 
					        return resultNoteSet;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    preprocessContent(content, type, mime) {
 | 
				
			||||||
 | 
					        content = utils.normalize(content.toString());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (type === 'text' && mime === 'text/html') {
 | 
				
			||||||
 | 
					            if (!this.raw && content.length < 20000) { // striptags is slow for very large notes
 | 
				
			||||||
 | 
					                // allow link to preserve URLs: https://github.com/zadam/trilium/issues/2412
 | 
				
			||||||
 | 
					                content = striptags(content, ['a']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // at least the closing tag can be easily stripped
 | 
				
			||||||
 | 
					                content = content.replace(/<\/a>/ig, "");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            content = content.replace(/ /g, ' ');
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return content;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports = NoteContentProtectedFulltextExp;
 | 
					module.exports = NoteContentProtectedFulltextExp;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,6 +6,7 @@ const becca = require('../../../becca/becca');
 | 
				
			|||||||
const striptags = require('striptags');
 | 
					const striptags = require('striptags');
 | 
				
			||||||
const utils = require("../../utils");
 | 
					const utils = require("../../utils");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// FIXME: create common subclass with NoteContentProtectedFulltextExp to avoid duplication
 | 
				
			||||||
class NoteContentUnprotectedFulltextExp extends Expression {
 | 
					class NoteContentUnprotectedFulltextExp extends Expression {
 | 
				
			||||||
    constructor(operator, tokens, raw) {
 | 
					    constructor(operator, tokens, raw) {
 | 
				
			||||||
        super();
 | 
					        super();
 | 
				
			||||||
@@ -32,15 +33,7 @@ class NoteContentUnprotectedFulltextExp extends Expression {
 | 
				
			|||||||
                continue;
 | 
					                continue;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            content = utils.normalize(content.toString());
 | 
					            content = this.preprocessContent(content, type, mime);
 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (type === 'text' && mime === 'text/html') {
 | 
					 | 
				
			||||||
                if (!this.raw && content.length < 20000) { // striptags is slow for very large notes
 | 
					 | 
				
			||||||
                    content = striptags(content);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                content = content.replace(/ /g, ' ');
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (!this.tokens.find(token => !content.includes(token))) {
 | 
					            if (!this.tokens.find(token => !content.includes(token))) {
 | 
				
			||||||
                resultNoteSet.add(becca.notes[noteId]);
 | 
					                resultNoteSet.add(becca.notes[noteId]);
 | 
				
			||||||
@@ -49,6 +42,23 @@ class NoteContentUnprotectedFulltextExp extends Expression {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        return resultNoteSet;
 | 
					        return resultNoteSet;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    preprocessContent(content, type, mime) {
 | 
				
			||||||
 | 
					        content = utils.normalize(content.toString());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (type === 'text' && mime === 'text/html') {
 | 
				
			||||||
 | 
					            if (!this.raw && content.length < 20000) { // striptags is slow for very large notes
 | 
				
			||||||
 | 
					                // allow link to preserve URLs: https://github.com/zadam/trilium/issues/2412
 | 
				
			||||||
 | 
					                content = striptags(content, ['a']);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                // at least the closing tag can be easily stripped
 | 
				
			||||||
 | 
					                content = content.replace(/<\/a>/ig, "");
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            content = content.replace(/ /g, ' ');
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return content;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports = NoteContentUnprotectedFulltextExp;
 | 
					module.exports = NoteContentUnprotectedFulltextExp;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,7 +11,7 @@ const RelationWhereExp = require('../expressions/relation_where');
 | 
				
			|||||||
const PropertyComparisonExp = require('../expressions/property_comparison');
 | 
					const PropertyComparisonExp = require('../expressions/property_comparison');
 | 
				
			||||||
const AttributeExistsExp = require('../expressions/attribute_exists');
 | 
					const AttributeExistsExp = require('../expressions/attribute_exists');
 | 
				
			||||||
const LabelComparisonExp = require('../expressions/label_comparison');
 | 
					const LabelComparisonExp = require('../expressions/label_comparison');
 | 
				
			||||||
const BeccaFlatTextExp = require('../expressions/note_cache_flat_text');
 | 
					const NoteFlatTextExp = require('../expressions/note_flat_text.js');
 | 
				
			||||||
const NoteContentProtectedFulltextExp = require('../expressions/note_content_protected_fulltext');
 | 
					const NoteContentProtectedFulltextExp = require('../expressions/note_content_protected_fulltext');
 | 
				
			||||||
const NoteContentUnprotectedFulltextExp = require('../expressions/note_content_unprotected_fulltext');
 | 
					const NoteContentUnprotectedFulltextExp = require('../expressions/note_content_unprotected_fulltext');
 | 
				
			||||||
const OrderByAndLimitExp = require('../expressions/order_by_and_limit');
 | 
					const OrderByAndLimitExp = require('../expressions/order_by_and_limit');
 | 
				
			||||||
@@ -31,13 +31,13 @@ function getFulltext(tokens, searchContext) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    if (!searchContext.fastSearch) {
 | 
					    if (!searchContext.fastSearch) {
 | 
				
			||||||
        return new OrExp([
 | 
					        return new OrExp([
 | 
				
			||||||
            new BeccaFlatTextExp(tokens),
 | 
					            new NoteFlatTextExp(tokens),
 | 
				
			||||||
            new NoteContentProtectedFulltextExp('*=*', tokens),
 | 
					            new NoteContentProtectedFulltextExp('*=*', tokens),
 | 
				
			||||||
            new NoteContentUnprotectedFulltextExp('*=*', tokens)
 | 
					            new NoteContentUnprotectedFulltextExp('*=*', tokens)
 | 
				
			||||||
        ]);
 | 
					        ]);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    else {
 | 
					    else {
 | 
				
			||||||
        return new BeccaFlatTextExp(tokens);
 | 
					        return new NoteFlatTextExp(tokens);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -83,8 +83,12 @@ function findResultsWithExpression(expression, searchContext) {
 | 
				
			|||||||
                throw new Error(`Can't find note path for note ${JSON.stringify(note.getPojo())}`);
 | 
					                throw new Error(`Can't find note path for note ${JSON.stringify(note.getPojo())}`);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (notePathArray.includes("hidden")) {
 | 
				
			||||||
 | 
					                return null;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            return new SearchResult(notePathArray);
 | 
					            return new SearchResult(notePathArray);
 | 
				
			||||||
        });
 | 
					        }).filter(Boolean);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for (const res of searchResults) {
 | 
					    for (const res of searchResults) {
 | 
				
			||||||
        res.computeScore(searchContext.highlightedTokens);
 | 
					        res.computeScore(searchContext.highlightedTokens);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -34,6 +34,7 @@ function getHiddenRoot() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    if (!hidden) {
 | 
					    if (!hidden) {
 | 
				
			||||||
        hidden = noteService.createNewNote({
 | 
					        hidden = noteService.createNewNote({
 | 
				
			||||||
 | 
					            branchId: 'hidden',
 | 
				
			||||||
            noteId: 'hidden',
 | 
					            noteId: 'hidden',
 | 
				
			||||||
            title: 'hidden',
 | 
					            title: 'hidden',
 | 
				
			||||||
            type: 'text',
 | 
					            type: 'text',
 | 
				
			||||||
@@ -44,6 +45,7 @@ function getHiddenRoot() {
 | 
				
			|||||||
        // isInheritable: false means that this notePath is automatically not preffered but at the same time
 | 
					        // isInheritable: false means that this notePath is automatically not preffered but at the same time
 | 
				
			||||||
        // the flag is not inherited to the children
 | 
					        // the flag is not inherited to the children
 | 
				
			||||||
        hidden.addLabel('archived', "", false);
 | 
					        hidden.addLabel('archived', "", false);
 | 
				
			||||||
 | 
					        hidden.addLabel('excludeFromNoteMap', "", true);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return hidden;
 | 
					    return hidden;
 | 
				
			||||||
@@ -206,6 +208,12 @@ function createMissingSpecialNotes() {
 | 
				
			|||||||
    getSinglesNoteRoot();
 | 
					    getSinglesNoteRoot();
 | 
				
			||||||
    getSinglesNoteRoot();
 | 
					    getSinglesNoteRoot();
 | 
				
			||||||
    getGlobalNoteMap();
 | 
					    getGlobalNoteMap();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const hidden = getHiddenRoot();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!hidden.hasOwnedLabel('excludeFromNoteMap')) {
 | 
				
			||||||
 | 
					        hidden.addLabel('excludeFromNoteMap', "", true);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports = {
 | 
					module.exports = {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -149,7 +149,10 @@ async function pullChanges(syncContext) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        sql.transactional(() => {
 | 
					        sql.transactional(() => {
 | 
				
			||||||
            for (const {entityChange, entity} of entityChanges) {
 | 
					            for (const {entityChange, entity} of entityChanges) {
 | 
				
			||||||
                if (!sourceIdService.isLocalSourceId(entityChange.sourceId)) {
 | 
					                // FIXME: temporary fix
 | 
				
			||||||
 | 
					                const existsAlready = !!sql.getValue("SELECT id FROM entity_changes WHERE entityName = ? AND entityId = ? AND utcDateChanged = ? AND hash = ?", [entityChange.entityName, entityChange.entityId, entityChange.utcDateChanged, entityChange.hash]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (!existsAlready && !sourceIdService.isLocalSourceId(entityChange.sourceId)) {
 | 
				
			||||||
                    if (!atLeastOnePullApplied) { // send only for first
 | 
					                    if (!atLeastOnePullApplied) { // send only for first
 | 
				
			||||||
                        ws.syncPullInProgress();
 | 
					                        ws.syncPullInProgress();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -249,6 +252,14 @@ async function checkContentHash(syncContext) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    const failedChecks = contentHashService.checkContentHashes(resp.entityHashes);
 | 
					    const failedChecks = contentHashService.checkContentHashes(resp.entityHashes);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (failedChecks.length > 0) {
 | 
				
			||||||
 | 
					        // before requeuing sectors make sure the entity changes are correct
 | 
				
			||||||
 | 
					        const consistencyChecks = require("./consistency_checks");
 | 
				
			||||||
 | 
					        consistencyChecks.runEntityChangesChecks();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        await syncRequest(syncContext, 'POST', `/api/sync/check-entity-changes`);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for (const {entityName, sector} of failedChecks) {
 | 
					    for (const {entityName, sector} of failedChecks) {
 | 
				
			||||||
        entityChangesService.addEntityChangesForSector(entityName, sector);
 | 
					        entityChangesService.addEntityChangesForSector(entityName, sector);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -54,7 +54,7 @@ function updateNormalEntity(remoteEntityChange, entity) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
            sql.execute(`DELETE FROM ${remoteEntityChange.entityName} WHERE ${primaryKey} = ?`, remoteEntityChange.entityId);
 | 
					            sql.execute(`DELETE FROM ${remoteEntityChange.entityName} WHERE ${primaryKey} = ?`, remoteEntityChange.entityId);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            entityChangesService.addEntityChange(remoteEntityChange, true);
 | 
					            entityChangesService.addEntityChange(remoteEntityChange);
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return true;
 | 
					        return true;
 | 
				
			||||||
@@ -71,7 +71,7 @@ function updateNormalEntity(remoteEntityChange, entity) {
 | 
				
			|||||||
        sql.transactional(() => {
 | 
					        sql.transactional(() => {
 | 
				
			||||||
            sql.replace(remoteEntityChange.entityName, entity);
 | 
					            sql.replace(remoteEntityChange.entityName, entity);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            entityChangesService.addEntityChange(remoteEntityChange, true);
 | 
					            entityChangesService.addEntityChange(remoteEntityChange);
 | 
				
			||||||
        });
 | 
					        });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return true;
 | 
					        return true;
 | 
				
			||||||
@@ -86,7 +86,7 @@ function updateNoteReordering(entityChange, entity) {
 | 
				
			|||||||
            sql.execute("UPDATE branches SET notePosition = ? WHERE branchId = ?", [entity[key], key]);
 | 
					            sql.execute("UPDATE branches SET notePosition = ? WHERE branchId = ?", [entity[key], key]);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        entityChangesService.addEntityChange(entityChange, true);
 | 
					        entityChangesService.addEntityChange(entityChange);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return true;
 | 
					    return true;
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user