mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-30 09:56:36 +01:00 
			
		
		
		
	Link entity migrated to Attribute, WIP
This commit is contained in:
		
							
								
								
									
										10
									
								
								db/migrations/0137__links_to_attributes.sql
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								db/migrations/0137__links_to_attributes.sql
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | ||||
| UPDATE links SET type = 'internal-link' WHERE type = 'hyper'; | ||||
| UPDATE links SET type = 'image-link' WHERE type = 'image'; | ||||
| UPDATE links SET type = 'relation-map-link' WHERE type = 'relation-map'; | ||||
|  | ||||
| INSERT INTO attributes (attributeId, noteId, type, name, value, position, utcDateCreated, utcDateModified, isDeleted, hash, isInheritable) | ||||
| SELECT linkId, noteId, 'relation', type, targetNoteId, 0, utcDateCreated, utcDateModified, isDeleted, hash, 0 FROM links; | ||||
|  | ||||
| UPDATE sync SET entityName = 'attributes' WHERE entityName = 'links'; | ||||
|  | ||||
| DROP TABLE links; | ||||
| @@ -1,6 +1,5 @@ | ||||
| const Note = require('../entities/note'); | ||||
| const NoteRevision = require('../entities/note_revision'); | ||||
| const Link = require('../entities/link'); | ||||
| const Branch = require('../entities/branch'); | ||||
| const Attribute = require('../entities/attribute'); | ||||
| const RecentNote = require('../entities/recent_note'); | ||||
| @@ -16,7 +15,6 @@ const ENTITY_NAME_TO_ENTITY = { | ||||
|     "recent_notes": RecentNote, | ||||
|     "options": Option, | ||||
|     "api_tokens": ApiToken, | ||||
|     "links": Link | ||||
| }; | ||||
|  | ||||
| function getEntityFromEntityName(entityName) { | ||||
| @@ -36,9 +34,6 @@ function createEntityFromRow(row) { | ||||
|     else if (row.noteRevisionId) { | ||||
|         entity = new NoteRevision(row); | ||||
|     } | ||||
|     else if (row.linkId) { | ||||
|         entity = new Link(row); | ||||
|     } | ||||
|     else if (row.branchId && row.notePath) { | ||||
|         entity = new RecentNote(row); | ||||
|     } | ||||
|   | ||||
| @@ -1,51 +0,0 @@ | ||||
| "use strict"; | ||||
|  | ||||
| const Entity = require('./entity'); | ||||
| const repository = require('../services/repository'); | ||||
| const dateUtils = require('../services/date_utils'); | ||||
|  | ||||
| /** | ||||
|  * This class represents link from one note to another in the form of hyperlink or image reference. Note that | ||||
|  * this is different concept than attribute/relation. | ||||
|  * | ||||
|  * @param {string} linkId | ||||
|  * @param {string} noteId | ||||
|  * @param {string} targetNoteId | ||||
|  * @param {string} type | ||||
|  * @param {boolean} isDeleted | ||||
|  * @param {string} utcDateModified | ||||
|  * @param {string} utcDateCreated | ||||
|  * | ||||
|  * @extends Entity | ||||
|  */ | ||||
| class Link extends Entity { | ||||
|     static get entityName() { return "links"; } | ||||
|     static get primaryKeyName() { return "linkId"; } | ||||
|     static get hashedProperties() { return ["linkId", "noteId", "targetNoteId", "type", "isDeleted", "utcDateCreated", "utcDateModified"]; } | ||||
|  | ||||
|     async getNote() { | ||||
|         return await repository.getEntity("SELECT * FROM notes WHERE noteId = ?", [this.noteId]); | ||||
|     } | ||||
|  | ||||
|     async getTargetNote() { | ||||
|         return await repository.getEntity("SELECT * FROM notes WHERE noteId = ?", [this.targetNoteId]); | ||||
|     } | ||||
|  | ||||
|     beforeSaving() { | ||||
|         if (!this.isDeleted) { | ||||
|             this.isDeleted = false; | ||||
|         } | ||||
|  | ||||
|         if (!this.utcDateCreated) { | ||||
|             this.utcDateCreated = dateUtils.utcNowDateTime(); | ||||
|         } | ||||
|  | ||||
|         super.beforeSaving(); | ||||
|  | ||||
|         if (this.isChanged) { | ||||
|             this.utcDateModified = dateUtils.utcNowDateTime(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| module.exports = Link; | ||||
| @@ -628,10 +628,17 @@ class Note extends Entity { | ||||
|     /** | ||||
|      * Get list of links coming out of this note. | ||||
|      * | ||||
|      * @returns {Promise<Link[]>} | ||||
|      * @deprecated - not intended for general use | ||||
|      * @returns {Promise<Attribute[]>} | ||||
|      */ | ||||
|     async getLinks() { | ||||
|         return await repository.getEntities("SELECT * FROM links WHERE noteId = ? AND isDeleted = 0", [this.noteId]); | ||||
|         return await repository.getEntities(` | ||||
|             SELECT *  | ||||
|             FROM attributes  | ||||
|             WHERE noteId = ? AND  | ||||
|                   isDeleted = 0 AND  | ||||
|                   type = 'relation' AND  | ||||
|                   name IN ('internal-link', 'image-link', 'relation-map-link')`, [this.noteId]); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|   | ||||
							
								
								
									
										33
									
								
								src/public/javascripts/entities/link.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/public/javascripts/entities/link.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,33 @@ | ||||
| class Link { | ||||
|     constructor(treeCache, row) { | ||||
|         this.treeCache = treeCache; | ||||
|         /** @param {string} linkId */ | ||||
|         this.linkId = row.linkId; | ||||
|         /** @param {string} noteId */ | ||||
|         this.noteId = row.noteId; | ||||
|         /** @param {string} type */ | ||||
|         this.type = row.type; | ||||
|         /** @param {string} targetNoteId */ | ||||
|         this.targetNoteId = row.targetNoteId; | ||||
|         /** @param {string} utcDateCreated */ | ||||
|         this.utcDateCreated = row.utcDateCreated; | ||||
|         /** @param {string} utcDateModified */ | ||||
|         this.utcDateModified = row.utcDateModified; | ||||
|     } | ||||
|  | ||||
|     /** @returns {NoteShort} */ | ||||
|     async getNote() { | ||||
|         return await this.treeCache.getNote(this.noteId); | ||||
|     } | ||||
|  | ||||
|     /** @returns {NoteShort} */ | ||||
|     async getTargetNote() { | ||||
|         return await this.treeCache.getNote(this.targetNoteId); | ||||
|     } | ||||
|  | ||||
|     get toString() { | ||||
|         return `Link(linkId=${this.linkId}, type=${this.type}, note=${this.noteId}, targetNoteId=${this.targetNoteId})`; | ||||
|     } | ||||
| } | ||||
|  | ||||
| export default Link; | ||||
| @@ -1,4 +1,6 @@ | ||||
| import server from '../services/server.js'; | ||||
| import Attribute from './attribute.js'; | ||||
| import Link from './link.js'; | ||||
|  | ||||
| const LABEL = 'label'; | ||||
| const LABEL_DEFINITION = 'label-definition'; | ||||
| @@ -84,7 +86,8 @@ class NoteShort { | ||||
|      */ | ||||
|     async getAttributes(name) { | ||||
|         if (!this.attributeCache) { | ||||
|             this.attributeCache = await server.get('notes/' + this.noteId + '/attributes'); | ||||
|             this.attributeCache = (await server.get('notes/' + this.noteId + '/attributes')) | ||||
|                 .map(attrRow => new Attribute(this.treeCache, attrRow)); | ||||
|         } | ||||
|  | ||||
|         if (name) { | ||||
| @@ -227,6 +230,14 @@ class NoteShort { | ||||
|         this.attributeCache = null; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @return {Promise<Link[]>} | ||||
|      */ | ||||
|     async getLinks() { | ||||
|         return (await server.get('notes/' + this.noteId + '/links')) | ||||
|             .map(linkRow => new Link(this.treeCache, linkRow)); | ||||
|     } | ||||
|  | ||||
|     get toString() { | ||||
|         return `Note(noteId=${this.noteId}, title=${this.title})`; | ||||
|     } | ||||
|   | ||||
| @@ -58,9 +58,9 @@ class LinkMapWidget extends StandardWidget { | ||||
|         const linkTypes = [ "hyper", "image", "relation", "relation-map" ]; | ||||
|         const maxNotes = 50; | ||||
|  | ||||
|         const noteId = this.ctx.note.noteId; | ||||
|         const currentNoteId = this.ctx.note.noteId; | ||||
|  | ||||
|         const links = await server.post(`notes/${noteId}/link-map`, { | ||||
|         const links = await server.post(`notes/${currentNoteId}/link-map`, { | ||||
|             linkTypes, | ||||
|             maxNotes, | ||||
|             maxDepth: 1 | ||||
| @@ -69,7 +69,7 @@ class LinkMapWidget extends StandardWidget { | ||||
|         const noteIds = new Set(links.map(l => l.noteId).concat(links.map(l => l.targetNoteId))); | ||||
|  | ||||
|         if (noteIds.size === 0) { | ||||
|             noteIds.add(noteId); | ||||
|             noteIds.add(currentNoteId); | ||||
|         } | ||||
|  | ||||
|         // preload all notes | ||||
| @@ -104,7 +104,7 @@ class LinkMapWidget extends StandardWidget { | ||||
|                 $noteBox.append($("<span>").addClass("title").append($link)); | ||||
|             }); | ||||
|  | ||||
|             if (noteId === noteId) { | ||||
|             if (noteId === currentNoteId) { | ||||
|                 $noteBox.addClass("link-map-active-note"); | ||||
|             } | ||||
|  | ||||
|   | ||||
							
								
								
									
										31
									
								
								src/public/javascripts/widgets/what_links_here.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								src/public/javascripts/widgets/what_links_here.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,31 @@ | ||||
| import StandardWidget from "./standard_widget.js"; | ||||
|  | ||||
| class WhatLinksHereWidget extends StandardWidget { | ||||
|     getWidgetTitle() { return "What links here"; } | ||||
|  | ||||
|     async doRenderBody() { | ||||
|  | ||||
|  | ||||
|         const $noteId = this.$body.find(".note-info-note-id"); | ||||
|         const $dateCreated = this.$body.find(".note-info-date-created"); | ||||
|         const $dateModified = this.$body.find(".note-info-date-modified"); | ||||
|         const $type = this.$body.find(".note-info-type"); | ||||
|         const $mime = this.$body.find(".note-info-mime"); | ||||
|  | ||||
|         const note = this.ctx.note; | ||||
|  | ||||
|         $noteId.text(note.noteId); | ||||
|         $dateCreated.text(note.dateCreated); | ||||
|         $dateModified.text(note.dateModified); | ||||
|         $type.text(note.type); | ||||
|         $mime.text(note.mime); | ||||
|     } | ||||
|  | ||||
|     syncDataReceived(syncData) { | ||||
|         if (syncData.find(sd => sd.entityName === 'notes' && sd.entityId === this.ctx.note.noteId)) { | ||||
|             this.doRenderBody(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| export default WhatLinksHereWidget; | ||||
| @@ -39,5 +39,4 @@ | ||||
|  | ||||
| .link-map-active-note { | ||||
|     background-color: var(--more-accented-background-color) !important; | ||||
|     border-width: 3px !important; | ||||
| } | ||||
| @@ -9,7 +9,7 @@ const messagingService = require('../../services/messaging'); | ||||
| const log = require('../../services/log'); | ||||
| const utils = require('../../services/utils'); | ||||
| const path = require('path'); | ||||
| const Link = require('../../entities/link'); | ||||
| const Attribute = require('../../entities/attribute'); | ||||
|  | ||||
| async function findClippingNote(todayNote, pageUrl) { | ||||
|     const notes = await todayNote.getDescendantNotesWithLabel('pageUrl', pageUrl); | ||||
| @@ -84,10 +84,11 @@ async function addImagesToNote(images, note, content) { | ||||
|  | ||||
|             const {note: imageNote, url} = await imageService.saveImage(buffer, filename, note.noteId, true); | ||||
|  | ||||
|             await new Link({ | ||||
|             await new Attribute({ | ||||
|                 noteId: note.noteId, | ||||
|                 targetNoteId: imageNote.noteId, | ||||
|                 type: 'image' | ||||
|                 type: 'relation', | ||||
|                 value: imageNote.noteId, | ||||
|                 name: 'image-link' | ||||
|             }).save(); | ||||
|  | ||||
|             console.log(`Replacing ${imageId} with ${url}`); | ||||
|   | ||||
| @@ -2,38 +2,34 @@ | ||||
|  | ||||
| const sql = require('../../services/sql'); | ||||
|  | ||||
| async function getLinks(noteIds, linkTypes) { | ||||
| async function getRelations(noteIds, relationNames) { | ||||
|     return (await sql.getManyRows(` | ||||
|         SELECT noteId, targetNoteId, type | ||||
|         FROM links | ||||
|         WHERE (noteId IN (???) OR targetNoteId IN (???)) | ||||
|           AND isDeleted = 0 | ||||
|         UNION | ||||
|         SELECT noteId, value, 'relation' | ||||
|         SELECT noteId, name, value AS targetNoteId | ||||
|         FROM attributes | ||||
|         WHERE (noteId IN (???) OR value IN (???)) | ||||
|           AND type = 'relation' | ||||
|           AND isDeleted = 0 | ||||
|     `, Array.from(noteIds))).filter(l => linkTypes.includes(l.type)); | ||||
|     `, Array.from(noteIds))).filter(l => relationNames.includes(l.name)); | ||||
| } | ||||
|  | ||||
| async function getLinkMap(req) { | ||||
|     const {noteId} = req.params; | ||||
|     const {linkTypes, maxNotes, maxDepth} = req.body; | ||||
|     const {relationNames, maxNotes, maxDepth} = req.body; | ||||
|  | ||||
|     let noteIds = new Set([noteId]); | ||||
|     let links = []; | ||||
|     let relations; | ||||
|  | ||||
|     let depth = 0; | ||||
|  | ||||
|     while (true) { | ||||
|         links = await getLinks(noteIds, linkTypes); | ||||
|         relations = await getRelations(noteIds, relationNames); | ||||
|  | ||||
|         if (depth === maxDepth) { | ||||
|             break; | ||||
|         } | ||||
|  | ||||
|         const newNoteIds = new Set(links.map(l => l.noteId).concat(links.map(l => l.targetNoteId))); | ||||
|         const newNoteIds = new Set(relations.map(rel => rel.noteId) | ||||
|                                             .concat(relations.map(rel => rel.targetNoteId))); | ||||
|  | ||||
|         if (newNoteIds.size === noteIds.size) { | ||||
|             // no new note discovered, no need to search any further | ||||
| @@ -51,9 +47,9 @@ async function getLinkMap(req) { | ||||
|     } | ||||
|  | ||||
|     // keep only links coming from and targetting some note in the noteIds set | ||||
|     links = links.filter(l => noteIds.has(l.noteId) && noteIds.has(l.targetNoteId)); | ||||
|     relations = relations.filter(rel => noteIds.has(rel.noteId) && noteIds.has(rel.targetNoteId)); | ||||
|  | ||||
|     return links; | ||||
|     return relations; | ||||
| } | ||||
|  | ||||
| module.exports = { | ||||
|   | ||||
							
								
								
									
										28
									
								
								src/routes/api/links.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								src/routes/api/links.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | ||||
| "use strict"; | ||||
|  | ||||
| const repository = require('../../services/repository'); | ||||
|  | ||||
| async function getLinks(req) { | ||||
|     const note = await repository.getNote(req.params.noteId); | ||||
|  | ||||
|     if (!note) { | ||||
|         return [404, `Note ${req.params.noteId} not found`]; | ||||
|     } | ||||
|  | ||||
|     return await note.getLinks(); | ||||
| } | ||||
|  | ||||
| async function getIncomingLinks(req) { | ||||
|     const note = await repository.getNote(req.params.noteId); | ||||
|  | ||||
|     if (!note) { | ||||
|         return [404, `Note ${req.params.noteId} not found`]; | ||||
|     } | ||||
|  | ||||
|     note.getTargetRelations() | ||||
| } | ||||
|  | ||||
| module.exports = { | ||||
|     getLinks, | ||||
|     getIncomingLinks | ||||
| }; | ||||
| @@ -114,8 +114,7 @@ async function getRelationMap(req) { | ||||
|         noteTitles: {}, | ||||
|         relations: [], | ||||
|         // relation name => inverse relation name | ||||
|         inverseRelations: {}, | ||||
|         links: [] | ||||
|         inverseRelations: {} | ||||
|     }; | ||||
|  | ||||
|     if (noteIds.length === 0) { | ||||
| @@ -145,16 +144,6 @@ async function getRelationMap(req) { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     resp.links = (await repository.getEntities(`SELECT * FROM links WHERE isDeleted = 0 AND noteId IN (${questionMarks})`, noteIds)) | ||||
|         .filter(link => noteIds.includes(link.targetNoteId)) | ||||
|         .map(link => { | ||||
|             return { | ||||
|                 linkId: link.linkId, | ||||
|                 sourceNoteId: link.noteId, | ||||
|                 targetNoteId: link.targetNoteId | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|     return resp; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -33,6 +33,7 @@ const filesRoute = require('./api/file_upload'); | ||||
| const searchRoute = require('./api/search'); | ||||
| const dateNotesRoute = require('./api/date_notes'); | ||||
| const linkMapRoute = require('./api/link_map'); | ||||
| const linksRoute = require('./api/links'); | ||||
| const clipperRoute = require('./api/clipper'); | ||||
|  | ||||
| const log = require('../services/log'); | ||||
| @@ -158,6 +159,8 @@ function register(app) { | ||||
|     apiRoute(GET, '/api/attributes/values/:attributeName', attributesRoute.getValuesForAttribute); | ||||
|  | ||||
|     apiRoute(POST, '/api/notes/:noteId/link-map', linkMapRoute.getLinkMap); | ||||
|     apiRoute(GET, '/api/notes/:noteId/links', linksRoute.getLinks); | ||||
|     apiRoute(GET, '/api/notes/:noteId/incoming-links', linksRoute.getIncomingLinks); | ||||
|  | ||||
|     apiRoute(GET, '/api/date-notes/date/:date', dateNotesRoute.getDateNote); | ||||
|     apiRoute(GET, '/api/date-notes/month/:month', dateNotesRoute.getMonthNote); | ||||
|   | ||||
| @@ -4,8 +4,8 @@ const build = require('./build'); | ||||
| const packageJson = require('../../package'); | ||||
| const {TRILIUM_DATA_DIR} = require('./data_dir'); | ||||
|  | ||||
| const APP_DB_VERSION = 136; | ||||
| const SYNC_VERSION = 9; | ||||
| const APP_DB_VERSION = 137; | ||||
| const SYNC_VERSION = 10; | ||||
| const CLIPPER_PROTOCOL_VERSION = "1.0"; | ||||
|  | ||||
| module.exports = { | ||||
|   | ||||
| @@ -12,7 +12,6 @@ const Attribute = require('../entities/attribute'); | ||||
| const NoteRevision = require('../entities/note_revision'); | ||||
| const RecentNote = require('../entities/recent_note'); | ||||
| const Option = require('../entities/option'); | ||||
| const Link = require('../entities/link'); | ||||
|  | ||||
| async function getHash(tableName, primaryKeyName, whereBranch) { | ||||
|     // subselect is necessary to have correct ordering in GROUP_CONCAT | ||||
| @@ -40,7 +39,6 @@ async function getHashes() { | ||||
|         options: await getHash(Option.entityName, Option.primaryKeyName, "isSynced = 1"), | ||||
|         attributes: await getHash(Attribute.entityName, Attribute.primaryKeyName), | ||||
|         api_tokens: await getHash(ApiToken.entityName, ApiToken.primaryKeyName), | ||||
|         links: await getHash(Link.entityName, Link.primaryKeyName) | ||||
|     }; | ||||
|  | ||||
|     const elapseTimeMs = Date.now() - startTime.getTime(); | ||||
|   | ||||
| @@ -115,12 +115,6 @@ async function exportToTar(exportContext, branch, format, res) { | ||||
|                     isInheritable: attribute.isInheritable, | ||||
|                     position: attribute.position | ||||
|                 }; | ||||
|             }), | ||||
|             links: (await note.getLinks()).map(link => { | ||||
|                 return { | ||||
|                     type: link.type, | ||||
|                     targetNoteId: link.targetNoteId | ||||
|                 } | ||||
|             }) | ||||
|         }; | ||||
|  | ||||
| @@ -220,9 +214,8 @@ async function exportToTar(exportContext, branch, format, res) { | ||||
|     }; | ||||
|  | ||||
|     for (const noteMeta of Object.values(noteIdToMeta)) { | ||||
|         // filter out relations and links which are not inside this export | ||||
|         // filter out relations which are not inside this export | ||||
|         noteMeta.attributes = noteMeta.attributes.filter(attr => attr.type !== 'relation' || attr.value in noteIdToMeta); | ||||
|         noteMeta.links = noteMeta.links.filter(link => link.targetNoteId in noteIdToMeta); | ||||
|     } | ||||
|  | ||||
|     if (!metaFile.files[0]) { // corner case of disabled export for exported note | ||||
|   | ||||
| @@ -1,7 +1,6 @@ | ||||
| "use strict"; | ||||
|  | ||||
| const Attribute = require('../../entities/attribute'); | ||||
| const Link = require('../../entities/link'); | ||||
| const utils = require('../../services/utils'); | ||||
| const log = require('../../services/log'); | ||||
| const repository = require('../../services/repository'); | ||||
| @@ -26,7 +25,6 @@ async function importTar(importContext, fileBuffer, importRootNote) { | ||||
|     // maps from original noteId (in tar file) to newly generated noteId | ||||
|     const noteIdMap = {}; | ||||
|     const attributes = []; | ||||
|     const links = []; | ||||
|     // path => noteId | ||||
|     const createdPaths = { '/': importRootNote.noteId, '\\': importRootNote.noteId }; | ||||
|     const mdReader = new commonmark.Parser(); | ||||
| @@ -146,7 +144,7 @@ async function importTar(importContext, fileBuffer, importRootNote) { | ||||
|         return { type, mime }; | ||||
|     } | ||||
|  | ||||
|     async function saveAttributesAndLinks(note, noteMeta) { | ||||
|     async function saveAttributes(note, noteMeta) { | ||||
|         if (!noteMeta) { | ||||
|             return; | ||||
|         } | ||||
| @@ -169,13 +167,6 @@ async function importTar(importContext, fileBuffer, importRootNote) { | ||||
|  | ||||
|             attributes.push(attr); | ||||
|         } | ||||
|  | ||||
|         for (const link of noteMeta.links) { | ||||
|             link.noteId = note.noteId; | ||||
|             link.targetNoteId = getNewNoteId(link.targetNoteId); | ||||
|  | ||||
|             links.push(link); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     async function saveDirectory(filePath) { | ||||
| @@ -200,7 +191,7 @@ async function importTar(importContext, fileBuffer, importRootNote) { | ||||
|             isProtected: importRootNote.isProtected && protectedSessionService.isProtectedSessionAvailable(), | ||||
|         })); | ||||
|  | ||||
|         await saveAttributesAndLinks(note, noteMeta); | ||||
|         await saveAttributes(note, noteMeta); | ||||
|  | ||||
|         if (!firstNote) { | ||||
|             firstNote = note; | ||||
| @@ -246,9 +237,11 @@ async function importTar(importContext, fileBuffer, importRootNote) { | ||||
|             content = content.toString("UTF-8"); | ||||
|  | ||||
|             if (noteMeta) { | ||||
|                 const internalLinks = (noteMeta.attributes || []).find(attr => attr.type === 'relation' && attr.name === 'internal-link'); | ||||
|  | ||||
|                 // this will replace all internal links (<a> and <img>) inside the body | ||||
|                 // links pointing outside the export will be broken and changed (ctx.getNewNoteId() will still assign new noteId) | ||||
|                 for (const link of noteMeta.links || []) { | ||||
|                 for (const link of internalLinks) { | ||||
|                     // no need to escape the regexp find string since it's a noteId which doesn't contain any special characters | ||||
|                     content = content.replace(new RegExp(link.targetNoteId, "g"), getNewNoteId(link.targetNoteId)); | ||||
|                 } | ||||
| @@ -278,7 +271,7 @@ async function importTar(importContext, fileBuffer, importRootNote) { | ||||
|                 isProtected: importRootNote.isProtected && protectedSessionService.isProtectedSessionAvailable(), | ||||
|             })); | ||||
|  | ||||
|             await saveAttributesAndLinks(note, noteMeta); | ||||
|             await saveAttributes(note, noteMeta); | ||||
|  | ||||
|             if (!noteMeta && (type === 'file' || type === 'image')) { | ||||
|                 attributes.push({ | ||||
| @@ -379,15 +372,6 @@ async function importTar(importContext, fileBuffer, importRootNote) { | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             for (const link of links) { | ||||
|                 if (link.targetNoteId in createdNoteIds) { | ||||
|                     await new Link(link).save(); | ||||
|                 } | ||||
|                 else { | ||||
|                     log.info("Link not imported since target note doesn't exist: " + JSON.stringify(link)); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             resolve(firstNote); | ||||
|         }); | ||||
|  | ||||
|   | ||||
| @@ -8,7 +8,6 @@ const eventService = require('./events'); | ||||
| const repository = require('./repository'); | ||||
| const cls = require('../services/cls'); | ||||
| const Note = require('../entities/note'); | ||||
| const Link = require('../entities/link'); | ||||
| const NoteRevision = require('../entities/note_revision'); | ||||
| const Branch = require('../entities/branch'); | ||||
| const Attribute = require('../entities/attribute'); | ||||
| @@ -215,8 +214,8 @@ function findImageLinks(content, foundLinks) { | ||||
|  | ||||
|     while (match = re.exec(content)) { | ||||
|         foundLinks.push({ | ||||
|             type: 'image', | ||||
|             targetNoteId: match[1] | ||||
|             type: 'image-link', | ||||
|             value: match[1] | ||||
|         }); | ||||
|     } | ||||
|  | ||||
| @@ -225,14 +224,14 @@ function findImageLinks(content, foundLinks) { | ||||
|     return content.replace(/src="[^"]*\/api\/images\//g, 'src="api/images/'); | ||||
| } | ||||
|  | ||||
| function findHyperLinks(content, foundLinks) { | ||||
| function findInternalLinks(content, foundLinks) { | ||||
|     const re = /href="[^"]*#root[a-zA-Z0-9\/]*\/([a-zA-Z0-9]+)\/?"/g; | ||||
|     let match; | ||||
|  | ||||
|     while (match = re.exec(content)) { | ||||
|         foundLinks.push({ | ||||
|             type: 'hyper', | ||||
|             targetNoteId: match[1] | ||||
|             name: 'internal-link', | ||||
|             value: match[1] | ||||
|         }); | ||||
|     } | ||||
|  | ||||
| @@ -245,8 +244,8 @@ function findRelationMapLinks(content, foundLinks) { | ||||
|  | ||||
|     for (const note of obj.notes) { | ||||
|         foundLinks.push({ | ||||
|             type: 'relation-map', | ||||
|             targetNoteId: note.noteId | ||||
|             type: 'relation-map-link', | ||||
|             value: note.noteId | ||||
|         }) | ||||
|     } | ||||
| } | ||||
| @@ -260,7 +259,7 @@ async function saveLinks(note, content) { | ||||
|  | ||||
|     if (note.type === 'text') { | ||||
|         content = findImageLinks(content, foundLinks); | ||||
|         content = findHyperLinks(content, foundLinks); | ||||
|         content = findInternalLinks(content, foundLinks); | ||||
|     } | ||||
|     else if (note.type === 'relation-map') { | ||||
|         findRelationMapLinks(content, foundLinks); | ||||
| @@ -273,14 +272,15 @@ async function saveLinks(note, content) { | ||||
|  | ||||
|     for (const foundLink of foundLinks) { | ||||
|         const existingLink = existingLinks.find(existingLink => | ||||
|             existingLink.targetNoteId === foundLink.targetNoteId | ||||
|             && existingLink.type === foundLink.type); | ||||
|             existingLink.value === foundLink.value | ||||
|             && existingLink.name === foundLink.name); | ||||
|  | ||||
|         if (!existingLink) { | ||||
|             await new Link({ | ||||
|             await new Attribute({ | ||||
|                 noteId: note.noteId, | ||||
|                 targetNoteId: foundLink.targetNoteId, | ||||
|                 type: foundLink.type | ||||
|                 type: 'relation', | ||||
|                 name: foundLink.name, | ||||
|                 value: foundLink.targetNoteId, | ||||
|             }).save(); | ||||
|         } | ||||
|         else if (existingLink.isDeleted) { | ||||
| @@ -292,8 +292,8 @@ async function saveLinks(note, content) { | ||||
|  | ||||
|     // marking links as deleted if they are not present on the page anymore | ||||
|     const unusedLinks = existingLinks.filter(existingLink => !foundLinks.some(foundLink => | ||||
|                                     existingLink.targetNoteId === foundLink.targetNoteId | ||||
|                                     && existingLink.type === foundLink.type)); | ||||
|                                     existingLink.value === foundLink.value | ||||
|                                     && existingLink.name === foundLink.name)); | ||||
|  | ||||
|     for (const unusedLink of unusedLinks) { | ||||
|         unusedLink.isDeleted = true; | ||||
| @@ -415,11 +415,6 @@ async function deleteNote(branch) { | ||||
|             await relation.save(); | ||||
|         } | ||||
|  | ||||
|         for (const link of await note.getLinks()) { | ||||
|             link.isDeleted = true; | ||||
|             await link.save(); | ||||
|         } | ||||
|  | ||||
|         for (const link of await note.getTargetLinks()) { | ||||
|             link.isDeleted = true; | ||||
|             await link.save(); | ||||
|   | ||||
| @@ -249,8 +249,7 @@ const primaryKeys = { | ||||
|     "recent_notes": "noteId", | ||||
|     "api_tokens": "apiTokenId", | ||||
|     "options": "name", | ||||
|     "attributes": "attributeId", | ||||
|     "links": "linkId" | ||||
|     "attributes": "attributeId" | ||||
| }; | ||||
|  | ||||
| async function getEntityRow(entityName, entityId) { | ||||
|   | ||||
| @@ -32,10 +32,6 @@ async function addRecentNoteSync(noteId, sourceId) { | ||||
|     await addEntitySync("recent_notes", noteId, sourceId); | ||||
| } | ||||
|  | ||||
| async function addLinkSync(linkId, sourceId) { | ||||
|     await addEntitySync("links", linkId, sourceId); | ||||
| } | ||||
|  | ||||
| async function addAttributeSync(attributeId, sourceId) { | ||||
|     await addEntitySync("attributes", attributeId, sourceId); | ||||
| } | ||||
| @@ -101,7 +97,6 @@ async function fillAllSyncRows() { | ||||
|     await fillSyncRows("recent_notes", "noteId"); | ||||
|     await fillSyncRows("attributes", "attributeId"); | ||||
|     await fillSyncRows("api_tokens", "apiTokenId"); | ||||
|     await fillSyncRows("links", "linkId"); | ||||
|     await fillSyncRows("options", "name", 'isSynced = 1'); | ||||
| } | ||||
|  | ||||
| @@ -115,7 +110,6 @@ module.exports = { | ||||
|     addRecentNoteSync, | ||||
|     addAttributeSync, | ||||
|     addApiTokenSync, | ||||
|     addLinkSync, | ||||
|     addEntitySync, | ||||
|     fillAllSyncRows | ||||
| }; | ||||
| @@ -28,9 +28,6 @@ async function updateEntity(sync, entity, sourceId) { | ||||
|     else if (entityName === 'recent_notes') { | ||||
|         await updateRecentNotes(entity, sourceId); | ||||
|     } | ||||
|     else if (entityName === 'links') { | ||||
|         await updateLink(entity, sourceId); | ||||
|     } | ||||
|     else if (entityName === 'attributes') { | ||||
|         await updateAttribute(entity, sourceId); | ||||
|     } | ||||
| @@ -159,20 +156,6 @@ async function updateRecentNotes(entity, sourceId) { | ||||
|     } | ||||
| } | ||||
|  | ||||
| async function updateLink(entity, sourceId) { | ||||
|     const origLink = await sql.getRow("SELECT * FROM links WHERE linkId = ?", [entity.linkId]); | ||||
|  | ||||
|     if (!origLink || origLink.utcDateModified <= entity.utcDateModified) { | ||||
|         await sql.transactional(async () => { | ||||
|             await sql.replace("links", entity); | ||||
|  | ||||
|             await syncTableService.addLinkSync(entity.linkId, sourceId); | ||||
|         }); | ||||
|  | ||||
|         log.info("Update/sync link " + entity.linkId); | ||||
|     } | ||||
| } | ||||
|  | ||||
| async function updateAttribute(entity, sourceId) { | ||||
|     const origAttribute = await sql.getRow("SELECT * FROM attributes WHERE attributeId = ?", [entity.attributeId]); | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user