mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 02:16:05 +01:00 
			
		
		
		
	Compare commits
	
		
			11 Commits
		
	
	
		
			v0.24.1-be
			...
			v0.24.2-be
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 9c834229b9 | ||
|  | 3fd45b15e7 | ||
|  | f20ab45576 | ||
|  | 77a89d85c8 | ||
|  | 30249a353e | ||
|  | eb9bae9010 | ||
|  | 0c7ae527c5 | ||
|  | fef4705e2f | ||
|  | 568c2c997f | ||
|  | d6b5cd6ead | ||
|  | 00ce379962 | 
							
								
								
									
										12
									
								
								bin/build.sh
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								bin/build.sh
									
									
									
									
									
								
							| @@ -11,15 +11,21 @@ rm -r dist/* | ||||
| echo "Rebuilding binaries for linux-ia32" | ||||
| ./node_modules/.bin/electron-rebuild --arch=ia32 | ||||
|  | ||||
| ./node_modules/.bin/electron-packager . --out=dist --platform=linux --arch=ia32 --overwrite | ||||
| ./node_modules/.bin/electron-packager . --out=dist --executable-name=trilium --platform=linux --arch=ia32 --overwrite | ||||
|  | ||||
| ./node_modules/.bin/electron-packager . --out=dist --platform=win32 --arch=x64 --overwrite | ||||
| mv "./dist/Trilium Notes-linux-ia32" ./dist/trilium-linux-ia32 | ||||
|  | ||||
| ./node_modules/.bin/electron-packager . --out=dist --executable-name=trilium --platform=win32  --arch=x64 --overwrite --icon=src/public/images/app-icons/win/icon.ico | ||||
|  | ||||
| mv "./dist/Trilium Notes-win32-x64" ./dist/trilium-win32-x64 | ||||
|  | ||||
| # we build x64 as second so that we keep X64 binaries in node_modules for local development and server build | ||||
| echo "Rebuilding binaries for linux-x64" | ||||
| ./node_modules/.bin/electron-rebuild --arch=x64 | ||||
|  | ||||
| ./node_modules/.bin/electron-packager . --out=dist --platform=linux --arch=x64 --overwrite | ||||
| ./node_modules/.bin/electron-packager . --out=dist --executable-name=trilium --platform=linux --arch=x64 --overwrite | ||||
|  | ||||
| mv "./dist/Trilium Notes-linux-x64" ./dist/trilium-linux-x64 | ||||
|  | ||||
| echo "Copying required windows binaries" | ||||
|  | ||||
|   | ||||
							
								
								
									
										
											BIN
										
									
								
								db/demo.tar
									
									
									
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								db/demo.tar
									
									
									
									
									
								
							
										
											Binary file not shown.
										
									
								
							
							
								
								
									
										1
									
								
								db/migrations/0119__rename_mirror_to_inverse.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								db/migrations/0119__rename_mirror_to_inverse.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| UPDATE attributes SET value = replace(value, 'mirrorRelation', 'inverseRelation') WHERE type = 'relation-definition'; | ||||
| @@ -70,6 +70,8 @@ app.on('activate', () => { | ||||
| }); | ||||
|  | ||||
| app.on('ready', async () => { | ||||
|     app.setAppUserModelId('com.github.zadam.trilium'); | ||||
|  | ||||
|     mainWindow = await createMainWindow(); | ||||
|  | ||||
|     const result = globalShortcut.register('CommandOrControl+Alt+P', cls.wrap(async () => { | ||||
|   | ||||
							
								
								
									
										2
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -1,6 +1,6 @@ | ||||
| { | ||||
|   "name": "trilium", | ||||
|   "version": "0.24.0-beta", | ||||
|   "version": "0.24.1-beta", | ||||
|   "lockfileVersion": 1, | ||||
|   "requires": true, | ||||
|   "dependencies": { | ||||
|   | ||||
| @@ -1,7 +1,8 @@ | ||||
| { | ||||
|   "name": "trilium", | ||||
|   "productName": "Trilium Notes", | ||||
|   "description": "Trilium Notes", | ||||
|   "version": "0.24.1-beta", | ||||
|   "version": "0.24.2-beta", | ||||
|   "license": "AGPL-3.0-only", | ||||
|   "main": "electron.js", | ||||
|   "bin": { | ||||
| @@ -13,10 +14,7 @@ | ||||
|   }, | ||||
|   "scripts": { | ||||
|     "start": "node ./src/www", | ||||
|     "test-electron": "xo", | ||||
|     "rebuild-electron": "electron-rebuild", | ||||
|     "start-electron": "electron . --disable-gpu", | ||||
|     "build-electron": "electron-packager . --out=dist --asar --overwrite --platform=win32,linux --arch=ia32,x64 --app-version= --icon=src/public/app-icons/win/icon.ico", | ||||
|     "build-backend-docs": "jsdoc -d ./docs/backend_api src/entities/*.js src/services/backend_script_api.js", | ||||
|     "build-frontend-docs": "jsdoc -d ./docs/frontend_api src/public/javascripts/entities/*.js src/public/javascripts/services/frontend_script_api.js", | ||||
|     "build-docs": "npm run build-backend-docs && npm run build-frontend-docs" | ||||
|   | ||||
| @@ -72,7 +72,7 @@ function AttributesModel() { | ||||
|  | ||||
|             attr.relationDefinition = (attr.type === 'relation-definition' && attr.value) ? attr.value : { | ||||
|                 multiplicityType: "singlevalue", | ||||
|                 mirrorRelation: "", | ||||
|                 inverseRelation: "", | ||||
|                 isPromoted: true | ||||
|             }; | ||||
|  | ||||
| @@ -191,7 +191,7 @@ function AttributesModel() { | ||||
|                 }, | ||||
|                 relationDefinition: { | ||||
|                     multiplicityType: "singlevalue", | ||||
|                     mirrorRelation: "", | ||||
|                     inverseRelation: "", | ||||
|                     isPromoted: true | ||||
|                 } | ||||
|             })); | ||||
|   | ||||
							
								
								
									
										8
									
								
								src/public/javascripts/services/bootstrap.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								src/public/javascripts/services/bootstrap.js
									
									
									
									
										vendored
									
									
								
							| @@ -103,7 +103,13 @@ if (utils.isElectron()) { | ||||
|     }); | ||||
| } | ||||
|  | ||||
| $("#export-note-to-markdown-button").click(() => exportService.exportSubtree(noteDetailService.getCurrentNoteId(), 'markdown-single')); | ||||
| $("#export-note-to-markdown-button").click(function () { | ||||
|     if ($(this).hasClass("disabled")) { | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     exportService.exportSubtree(noteDetailService.getCurrentNoteId(), 'markdown-single') | ||||
| }); | ||||
|  | ||||
| treeService.showTree(); | ||||
|  | ||||
|   | ||||
| @@ -10,15 +10,13 @@ const dragAndDropSetup = { | ||||
|  | ||||
|         node.setSelected(true); | ||||
|  | ||||
|         const selectedNodes = treeService.getSelectedNodes().map(node => { | ||||
|             return { | ||||
|         // this is for dragging notes into relation map | ||||
|         // we allow to drag only one note at a time because it multi-drag conflicts with multiple single drags | ||||
|         // in UX and single drag is probably more useful | ||||
|         data.dataTransfer.setData("text", JSON.stringify({ | ||||
|             noteId: node.data.noteId, | ||||
|             title: node.title | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|         // this is for dragging notes into relation map | ||||
|         data.dataTransfer.setData("text", JSON.stringify(selectedNodes)); | ||||
|         })); | ||||
|  | ||||
|         // This function MUST be defined to enable dragging for the tree. | ||||
|         // Return false to cancel dragging of node. | ||||
|   | ||||
| @@ -25,9 +25,21 @@ function registerEntrypoints() { | ||||
|     $("#jump-to-note-dialog-button").click(jumpToNoteDialog.showDialog); | ||||
|     utils.bindShortcut('ctrl+j', jumpToNoteDialog.showDialog); | ||||
|  | ||||
|     $("#show-note-revisions-button").click(noteRevisionsDialog.showCurrentNoteRevisions); | ||||
|     $("#show-note-revisions-button").click(function() { | ||||
|         if ($(this).hasClass("disabled")) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|     $("#show-source-button").click(noteSourceDialog.showDialog); | ||||
|         noteRevisionsDialog.showCurrentNoteRevisions(); | ||||
|     }); | ||||
|  | ||||
|     $("#show-source-button").click(function() { | ||||
|         if ($(this).hasClass("disabled")) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         noteSourceDialog.showDialog(); | ||||
|     }); | ||||
|  | ||||
|     $("#recent-changes-button").click(recentChangesDialog.showDialog); | ||||
|  | ||||
|   | ||||
| @@ -5,6 +5,7 @@ import infoService from "./info.js"; | ||||
| import server from "./server.js"; | ||||
|  | ||||
| const $component = $('#note-detail-image'); | ||||
| const $imageWrapper = $('#note-detail-image-wrapper'); | ||||
| const $imageView = $('#note-detail-image-view'); | ||||
|  | ||||
| const $imageDownloadButton = $("#image-download"); | ||||
| @@ -39,10 +40,10 @@ function selectImage(element) { | ||||
| } | ||||
|  | ||||
| $copyToClipboardButton.click(() => { | ||||
|     $component.attr('contenteditable','true'); | ||||
|     $imageWrapper.attr('contenteditable','true'); | ||||
|  | ||||
|     try { | ||||
|         selectImage($component.get(0)); | ||||
|         selectImage($imageWrapper.get(0)); | ||||
|  | ||||
|         const success = document.execCommand('copy'); | ||||
|  | ||||
| @@ -55,7 +56,7 @@ $copyToClipboardButton.click(() => { | ||||
|     } | ||||
|     finally { | ||||
|         window.getSelection().removeAllRanges(); | ||||
|         $component.removeAttr('contenteditable'); | ||||
|         $imageWrapper.removeAttr('contenteditable'); | ||||
|     } | ||||
| }); | ||||
|  | ||||
|   | ||||
| @@ -15,7 +15,7 @@ const $relationMapContainer = $("#relation-map-container"); | ||||
| const $createChildNote = $("#relation-map-create-child-note"); | ||||
| const $zoomInButton = $("#relation-map-zoom-in"); | ||||
| const $zoomOutButton = $("#relation-map-zoom-out"); | ||||
| const $centerButton = $("#relation-map-center"); | ||||
| const $resetPanZoomButton = $("#relation-map-reset-pan-zoom"); | ||||
|  | ||||
| let mapData; | ||||
| let jsPlumbInstance; | ||||
| @@ -50,7 +50,7 @@ const biDirectionalOverlays = [ | ||||
|     } ] | ||||
| ]; | ||||
|  | ||||
| const mirrorOverlays = [ | ||||
| const inverseRelationsOverlays = [ | ||||
|     [ "Arrow", { | ||||
|         location: 1, | ||||
|         id: "arrow", | ||||
| @@ -134,12 +134,12 @@ async function loadNotesAndRelations() { | ||||
|  | ||||
|     for (const relation of data.relations) { | ||||
|         const match = relations.find(rel => | ||||
|             rel.name === data.mirrorRelations[relation.name] | ||||
|             rel.name === data.inverseRelations[relation.name] | ||||
|             && ((rel.sourceNoteId === relation.sourceNoteId && rel.targetNoteId === relation.targetNoteId) | ||||
|             || (rel.sourceNoteId === relation.targetNoteId && rel.targetNoteId === relation.sourceNoteId))); | ||||
|  | ||||
|         if (match) { | ||||
|             match.type = relation.type = relation.name === data.mirrorRelations[relation.name] ? 'biDirectional' : 'mirror'; | ||||
|             match.type = relation.type = relation.name === data.inverseRelations[relation.name] ? 'biDirectional' : 'inverse'; | ||||
|             relation.render = false; // don't render second relation | ||||
|         } else { | ||||
|             relation.type = 'uniDirectional'; | ||||
| @@ -173,9 +173,9 @@ async function loadNotesAndRelations() { | ||||
|  | ||||
|             connection.id = relation.attributeId; | ||||
|  | ||||
|             if (relation.type === 'mirror') { | ||||
|             if (relation.type === 'inverse') { | ||||
|                 connection.getOverlay("label-source").setLabel(relation.name); | ||||
|                 connection.getOverlay("label-target").setLabel(data.mirrorRelations[relation.name]); | ||||
|                 connection.getOverlay("label-target").setLabel(data.inverseRelations[relation.name]); | ||||
|             } | ||||
|             else { | ||||
|                 connection.getOverlay("label").setLabel(relation.name); | ||||
| @@ -240,6 +240,10 @@ function initPanZoom() { | ||||
|  | ||||
|         pzInstance.moveTo(mapData.transform.x, mapData.transform.y); | ||||
|     } | ||||
|     else { | ||||
|         // set to initial coordinates | ||||
|         pzInstance.moveTo(0, 0); | ||||
|     } | ||||
|  | ||||
|     $zoomInButton.click(() => pzInstance.zoomTo(0, 0, 1.2)); | ||||
|     $zoomOutButton.click(() => pzInstance.zoomTo(0, 0, 0.8)); | ||||
| @@ -286,7 +290,7 @@ function initJsPlumbInstance () { | ||||
|  | ||||
|     jsPlumbInstance.registerConnectionType("biDirectional", { anchor:"Continuous", connector:"StateMachine", overlays: biDirectionalOverlays }); | ||||
|  | ||||
|     jsPlumbInstance.registerConnectionType("mirror", { anchor:"Continuous", connector:"StateMachine", overlays: mirrorOverlays }); | ||||
|     jsPlumbInstance.registerConnectionType("inverse", { anchor:"Continuous", connector:"StateMachine", overlays: inverseRelationsOverlays }); | ||||
|  | ||||
|     jsPlumbInstance.registerConnectionType("link", { anchor:"Continuous", connector:"StateMachine", overlays: linkOverlays }); | ||||
|  | ||||
| @@ -518,43 +522,20 @@ function getZoom() { | ||||
| async function dropNoteOntoRelationMapHandler(ev) { | ||||
|     ev.preventDefault(); | ||||
|  | ||||
|     const notes = JSON.parse(ev.originalEvent.dataTransfer.getData("text")); | ||||
|     const note = JSON.parse(ev.originalEvent.dataTransfer.getData("text")); | ||||
|  | ||||
|     let {x, y} = getMousePosition(ev); | ||||
|  | ||||
|     // modifying position so that cursor is on the top-center of the box | ||||
|     const startX = x -= 80; | ||||
|     y -= 15; | ||||
|  | ||||
|     const currentNoteId = treeService.getCurrentNode().data.noteId; | ||||
|  | ||||
|     for (const note of notes) { | ||||
|         if (note.noteId === currentNoteId) { | ||||
|             // we don't allow placing current (relation map) into itself | ||||
|             // the reason is that when dragging notes from the tree, the relation map is always selected | ||||
|             // since it's focused. | ||||
|             continue; | ||||
|         } | ||||
|  | ||||
|     const exists = mapData.notes.some(n => n.noteId === note.noteId); | ||||
|  | ||||
|     if (exists) { | ||||
|         await infoDialog.info(`Note "${note.title}" is already placed into the diagram`); | ||||
|  | ||||
|             continue; | ||||
|         return; | ||||
|     } | ||||
|  | ||||
|     mapData.notes.push({noteId: note.noteId, x, y}); | ||||
|  | ||||
|         if (x - startX > 1000) { | ||||
|             x = startX; | ||||
|             y += 200; | ||||
|         } | ||||
|         else { | ||||
|             x += 200; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     saveData(); | ||||
|  | ||||
|     await refresh(); | ||||
| @@ -571,40 +552,10 @@ function getMousePosition(evt) { | ||||
|     }; | ||||
| } | ||||
|  | ||||
| $centerButton.click(() => { | ||||
|     if (mapData.notes.length === 0) { | ||||
|         return; // nothing to recenter on | ||||
|     } | ||||
|  | ||||
|     let totalX = 0, totalY = 0; | ||||
|  | ||||
|     for (const note of mapData.notes) { | ||||
|         totalX += note.x; | ||||
|         totalY += note.y; | ||||
|     } | ||||
|  | ||||
|     let averageX = totalX / mapData.notes.length; | ||||
|     let averageY = totalY / mapData.notes.length; | ||||
|  | ||||
|     // find note with smallest X, Y difference from the average (most central note) | ||||
|     const {noteId} = mapData.notes.map(note => { | ||||
|         return { | ||||
|             noteId: note.noteId, | ||||
|             diff: Math.abs(note.x - averageX) + Math.abs(note.y - averageY) | ||||
|         } | ||||
|     }).reduce((min, val) => min.diff <= val.min ? min : val, { diff: 9999999999 }); | ||||
|  | ||||
|     const $noteBox = $("#" + noteIdToId(noteId)); | ||||
|  | ||||
|     const clientRect = $noteBox[0].getBoundingClientRect(); | ||||
|     const cx = clientRect.left + clientRect.width / 2; | ||||
|     const cy = clientRect.top + clientRect.height / 2; | ||||
|  | ||||
|     const container = $component[0].getBoundingClientRect(); | ||||
|     const dx = container.width / 2 - cx; | ||||
|     const dy = container.height / 2 - cy; | ||||
|  | ||||
|     pzInstance.moveBy(dx, dy, true); | ||||
| $resetPanZoomButton.click(() => { | ||||
|     // reset to initial pan & zoom state | ||||
|     pzInstance.zoomTo(0, 0, 1 / getZoom()); | ||||
|     pzInstance.moveTo(0, 0); | ||||
| }); | ||||
|  | ||||
| $component.on("drop", dropNoteOntoRelationMapHandler); | ||||
|   | ||||
| @@ -558,6 +558,10 @@ table.promoted-attributes-in-tooltip td, table.promoted-attributes-in-tooltip th | ||||
|     max-height: 250px; | ||||
| } | ||||
|  | ||||
| .tooltip-inner figure.image-style-side { | ||||
|     float: right; | ||||
| } | ||||
|  | ||||
| .tooltip.show { | ||||
|     opacity: 1; | ||||
| } | ||||
|   | ||||
| @@ -117,8 +117,8 @@ async function getRelationMap(req) { | ||||
|         // noteId => title | ||||
|         noteTitles: {}, | ||||
|         relations: [], | ||||
|         // relation name => mirror relation name | ||||
|         mirrorRelations: {}, | ||||
|         // relation name => inverse relation name | ||||
|         inverseRelations: {}, | ||||
|         links: [] | ||||
|     }; | ||||
|  | ||||
| @@ -143,8 +143,8 @@ async function getRelationMap(req) { | ||||
|             }; })); | ||||
|  | ||||
|         for (const relationDefinition of await note.getRelationDefinitions()) { | ||||
|             if (relationDefinition.value.mirrorRelation) { | ||||
|                 resp.mirrorRelations[relationDefinition.name] = relationDefinition.value.mirrorRelation; | ||||
|             if (relationDefinition.value.inverseRelation) { | ||||
|                 resp.inverseRelations[relationDefinition.name] = relationDefinition.value.inverseRelation; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -3,7 +3,7 @@ | ||||
| const build = require('./build'); | ||||
| const packageJson = require('../../package'); | ||||
|  | ||||
| const APP_DB_VERSION = 118; | ||||
| const APP_DB_VERSION = 119; | ||||
| const SYNC_VERSION = 2; | ||||
|  | ||||
| module.exports = { | ||||
|   | ||||
| @@ -1 +1 @@ | ||||
| module.exports = { buildDate:"2018-11-19T00:06:44+01:00", buildRevision: "ad6cb6ba347f0396cbf79b76ab62ee3e4a4e8566" }; | ||||
| module.exports = { buildDate:"2018-11-19T17:17:08+01:00", buildRevision: "3fd45b15e7042c12f140524297b50677f9851044" }; | ||||
|   | ||||
| @@ -4,8 +4,20 @@ const sanitize = require("sanitize-filename"); | ||||
| const TurndownService = require('turndown'); | ||||
|  | ||||
| async function exportSingleMarkdown(note, res) { | ||||
|     if (note.type !== 'text' && note.type !== 'code') { | ||||
|         return [400, `Note type ${note.type} cannot be exported as single markdown file.`]; | ||||
|     } | ||||
|  | ||||
|     let markdown; | ||||
|  | ||||
|     if (note.type === 'code') { | ||||
|         markdown = '```\n' + note.content + "\n```"; | ||||
|     } | ||||
|     else if (note.type === 'text') { | ||||
|         const turndownService = new TurndownService(); | ||||
|     const markdown = turndownService.turndown(note.content); | ||||
|         markdown = turndownService.turndown(note.content); | ||||
|     } | ||||
|  | ||||
|     const name = sanitize(note.title); | ||||
|  | ||||
|     res.setHeader('Content-Disposition', 'file; filename="' + name + '.md"'); | ||||
|   | ||||
| @@ -25,7 +25,7 @@ async function exportToMarkdown(branch, res) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         saveDataFile(childFileName, note); | ||||
|         saveNote(childFileName, note); | ||||
|  | ||||
|         const childNotes = await note.getChildNotes(); | ||||
|  | ||||
| @@ -40,11 +40,7 @@ async function exportToMarkdown(branch, res) { | ||||
|         return childFileName; | ||||
|     } | ||||
|  | ||||
|     function saveDataFile(childFileName, note) { | ||||
|         if (note.type !== 'text' && note.type !== 'code') { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|     function saveTextNote(childFileName, note) { | ||||
|         if (note.content.trim().length === 0) { | ||||
|             return; | ||||
|         } | ||||
| @@ -65,6 +61,19 @@ async function exportToMarkdown(branch, res) { | ||||
|         pack.entry({name: childFileName + ".md", size: markdown.length}, markdown); | ||||
|     } | ||||
|  | ||||
|     function saveFileNote(childFileName, note) { | ||||
|         pack.entry({name: childFileName, size: note.content.length}, note.content); | ||||
|     } | ||||
|  | ||||
|     function saveNote(childFileName, note) { | ||||
|         if (note.type === 'text' || note.type === 'code') { | ||||
|             saveTextNote(childFileName, note); | ||||
|         } | ||||
|         else if (note.type === 'image' || note.type === 'file') { | ||||
|             saveFileNote(childFileName, note); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     function saveDirectory(childFileName) { | ||||
|         pack.entry({name: childFileName, type: 'directory'}); | ||||
|     } | ||||
|   | ||||
| @@ -12,6 +12,11 @@ async function exportToOpml(branch, res) { | ||||
|     async function exportNoteInner(branchId) { | ||||
|         const branch = await repository.getBranch(branchId); | ||||
|         const note = await branch.getNote(); | ||||
|  | ||||
|         if (await note.hasLabel('excludeFromExport')) { | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         const title = (branch.prefix ? (branch.prefix + ' - ') : '') + note.title; | ||||
|  | ||||
|         const preparedTitle = prepareText(title); | ||||
|   | ||||
| @@ -59,7 +59,7 @@ eventService.subscribe(eventService.CHILD_NOTE_CREATED, async ({ parentNote, chi | ||||
|     await runAttachedRelations(parentNote, 'runOnChildNoteCreation', childNote); | ||||
| }); | ||||
|  | ||||
| async function processMirrorRelations(entityName, entity, handler) { | ||||
| async function processInverseRelations(entityName, entity, handler) { | ||||
|     if (entityName === 'attributes' && entity.type === 'relation') { | ||||
|         const note = await entity.getNote(); | ||||
|         const attributes = (await note.getAttributes(entity.name)).filter(relation => relation.type === 'relation-definition'); | ||||
| @@ -67,7 +67,7 @@ async function processMirrorRelations(entityName, entity, handler) { | ||||
|         for (const attribute of attributes) { | ||||
|             const definition = attribute.value; | ||||
|  | ||||
|             if (definition.mirrorRelation && definition.mirrorRelation.trim()) { | ||||
|             if (definition.inverseRelation && definition.inverseRelation.trim()) { | ||||
|                 const targetNote = await entity.getTargetNote(); | ||||
|  | ||||
|                 await handler(definition, note, targetNote); | ||||
| @@ -77,17 +77,17 @@ async function processMirrorRelations(entityName, entity, handler) { | ||||
| } | ||||
|  | ||||
| eventService.subscribe(eventService.ENTITY_CHANGED, async ({ entityName, entity }) => { | ||||
|     await processMirrorRelations(entityName, entity, async (definition, note, targetNote) => { | ||||
|         // we need to make sure that also target's mirror attribute exists and if note, then create it | ||||
|         // mirror attribute has to target our note as well | ||||
|         const hasMirrorAttribute = (await targetNote.getRelations(definition.mirrorRelation)) | ||||
|     await processInverseRelations(entityName, entity, async (definition, note, targetNote) => { | ||||
|         // we need to make sure that also target's inverse attribute exists and if note, then create it | ||||
|         // inverse attribute has to target our note as well | ||||
|         const hasInverseAttribute = (await targetNote.getRelations(definition.inverseRelation)) | ||||
|             .some(attr => attr.value === note.noteId); | ||||
|  | ||||
|         if (!hasMirrorAttribute) { | ||||
|         if (!hasInverseAttribute) { | ||||
|             await new Attribute({ | ||||
|                 noteId: targetNote.noteId, | ||||
|                 type: 'relation', | ||||
|                 name: definition.mirrorRelation, | ||||
|                 name: definition.inverseRelation, | ||||
|                 value: note.noteId, | ||||
|                 isInheritable: entity.isInheritable | ||||
|             }).save(); | ||||
| @@ -98,9 +98,9 @@ eventService.subscribe(eventService.ENTITY_CHANGED, async ({ entityName, entity | ||||
| }); | ||||
|  | ||||
| eventService.subscribe(eventService.ENTITY_DELETED, async ({ entityName, entity }) => { | ||||
|     await processMirrorRelations(entityName, entity, async (definition, note, targetNote) => { | ||||
|         // if one mirror attribute is deleted then the other should be deleted as well | ||||
|         const relations = await targetNote.getRelations(definition.mirrorRelation); | ||||
|     await processInverseRelations(entityName, entity, async (definition, note, targetNote) => { | ||||
|         // if one inverse attribute is deleted then the other should be deleted as well | ||||
|         const relations = await targetNote.getRelations(definition.inverseRelation); | ||||
|         let deletedSomething = false; | ||||
|  | ||||
|         for (const relation of relations) { | ||||
|   | ||||
| @@ -177,7 +177,7 @@ async function protectNoteRevisions(note) { | ||||
| } | ||||
|  | ||||
| function findImageLinks(content, foundLinks) { | ||||
|     const re = /src="\/api\/images\/([a-zA-Z0-9]+)\//g; | ||||
|     const re = /src="[^"]*\/api\/images\/([a-zA-Z0-9]+)\//g; | ||||
|     let match; | ||||
|  | ||||
|     while (match = re.exec(content)) { | ||||
| @@ -186,11 +186,13 @@ function findImageLinks(content, foundLinks) { | ||||
|             targetNoteId: match[1] | ||||
|         }); | ||||
|     } | ||||
|     return match; | ||||
|  | ||||
|     // removing absolute references to server to keep it working between instances | ||||
|     return content.replace(/src="[^"]*\/api\/images\//g, 'src="/api/images/'); | ||||
| } | ||||
|  | ||||
| function findHyperLinks(content, foundLinks) { | ||||
|     const re = /href="#root[a-zA-Z0-9\/]*\/([a-zA-Z0-9]+)\/?"/g; | ||||
|     const re = /href="[^"]*#root[a-zA-Z0-9\/]*\/([a-zA-Z0-9]+)\/?"/g; | ||||
|     let match; | ||||
|  | ||||
|     while (match = re.exec(content)) { | ||||
| @@ -200,7 +202,8 @@ function findHyperLinks(content, foundLinks) { | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     return match; | ||||
|     // removing absolute references to server to keep it working between instances | ||||
|     return content.replace(/href="[^"]*#root/g, 'href="#root'); | ||||
| } | ||||
|  | ||||
| function findRelationMapLinks(content, foundLinks) { | ||||
| @@ -214,7 +217,7 @@ function findRelationMapLinks(content, foundLinks) { | ||||
|     } | ||||
| } | ||||
|  | ||||
| async function saveLinks(note) { | ||||
| async function saveLinks(note, content) { | ||||
|     if (note.type !== 'text' && note.type !== 'relation-map') { | ||||
|         return; | ||||
|     } | ||||
| @@ -222,11 +225,11 @@ async function saveLinks(note) { | ||||
|     const foundLinks = []; | ||||
|  | ||||
|     if (note.type === 'text') { | ||||
|         findImageLinks(note.content, foundLinks); | ||||
|         findHyperLinks(note.content, foundLinks); | ||||
|         content = findImageLinks(content, foundLinks); | ||||
|         content = findHyperLinks(content, foundLinks); | ||||
|     } | ||||
|     else if (note.type === 'relation-map') { | ||||
|         findRelationMapLinks(note.content, foundLinks); | ||||
|         findRelationMapLinks(content, foundLinks); | ||||
|     } | ||||
|     else { | ||||
|         throw new Error("Unrecognized type " + note.type); | ||||
| @@ -262,6 +265,8 @@ async function saveLinks(note) { | ||||
|         unusedLink.isDeleted = true; | ||||
|         await unusedLink.save(); | ||||
|     } | ||||
|  | ||||
|     return content; | ||||
| } | ||||
|  | ||||
| async function saveNoteRevision(note) { | ||||
| @@ -310,6 +315,8 @@ async function updateNote(noteId, noteUpdates) { | ||||
|  | ||||
|     const noteTitleChanged = note.title !== noteUpdates.title; | ||||
|  | ||||
|     noteUpdates.content = await saveLinks(note, noteUpdates.content); | ||||
|  | ||||
|     note.title = noteUpdates.title; | ||||
|     note.setContent(noteUpdates.content); | ||||
|     note.isProtected = noteUpdates.isProtected; | ||||
| @@ -319,8 +326,6 @@ async function updateNote(noteId, noteUpdates) { | ||||
|         await triggerNoteTitleChanged(note); | ||||
|     } | ||||
|  | ||||
|     await saveLinks(note); | ||||
|  | ||||
|     await protectNoteRevisions(note); | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -22,5 +22,7 @@ | ||||
|  | ||||
|     <br/><br/> | ||||
|  | ||||
|     <div id="note-detail-image-wrapper"> | ||||
|         <img id="note-detail-image-view" /> | ||||
|     </div> | ||||
| </div> | ||||
| @@ -7,9 +7,9 @@ | ||||
|     </button> | ||||
|  | ||||
|     <button type="button" | ||||
|             class="btn icon-button floating-button jam jam-align-center" | ||||
|             title="Re-center view on notes" | ||||
|             id="relation-map-center" style="right: 100px;"></button> | ||||
|             class="btn icon-button floating-button jam jam-crop" | ||||
|             title="Reset pan & zoom to initial coordinates and magnification" | ||||
|             id="relation-map-reset-pan-zoom" style="right: 100px;"></button> | ||||
|  | ||||
|     <div class="btn-group floating-button" style="right: 20px;"> | ||||
|         <button type="button" | ||||
|   | ||||
| @@ -72,9 +72,9 @@ | ||||
|                     </label> | ||||
|                     <br/> | ||||
|                     <label> | ||||
|                       Mirror relation: | ||||
|                       Inverse relation: | ||||
|  | ||||
|                       <input type="text" value="true" class="attribute-name" data-bind="value: relationDefinition.mirrorRelation"/> | ||||
|                       <input type="text" value="true" class="attribute-name" data-bind="value: relationDefinition.inverseRelation"/> | ||||
|                     </label> | ||||
|                   </div> | ||||
|                 </td> | ||||
|   | ||||
| @@ -159,7 +159,7 @@ | ||||
|                   <a class="dropdown-item show-attributes-button"><kbd>Alt+A</kbd> Attributes</a> | ||||
|                   <a class="dropdown-item" id="show-source-button" data-bind="css: { disabled: type() != 'text' }">HTML source</a> | ||||
|                   <a class="dropdown-item" id="upload-file-button">Upload file</a> | ||||
|                   <a class="dropdown-item" id="export-note-to-markdown-button" data-bind="css: { disabled: type() != 'text' }">Export as markdown</a> | ||||
|                   <a class="dropdown-item" id="export-note-to-markdown-button" data-bind="css: { disabled: type() != 'text' && type() != 'code' }">Export as markdown</a> | ||||
|                 </div> | ||||
|               </div> | ||||
|             </div> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user