mirror of
https://github.com/zadam/trilium.git
synced 2025-03-01 14:22:32 +01:00
Link entity migrated to Attribute, WIP
This commit is contained in:
parent
fd9b79e115
commit
3cb421143f
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]);
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user