diff --git a/docs/backend_api/Note.html b/docs/backend_api/Note.html
index 4ffc94575..2c8828dbb 100644
--- a/docs/backend_api/Note.html
+++ b/docs/backend_api/Note.html
@@ -1034,7 +1034,7 @@
}
*/
diff --git a/src/public/javascripts/dialogs/link_map.js b/src/public/javascripts/dialogs/link_map.js
index 6f0ca6ba4..ffc69d598 100644
--- a/src/public/javascripts/dialogs/link_map.js
+++ b/src/public/javascripts/dialogs/link_map.js
@@ -1,30 +1,15 @@
-import server from '../services/server.js';
-import noteDetailService from "../services/note_detail.js";
-import libraryLoader from "../services/library_loader.js";
-import treeCache from "../services/tree_cache.js";
-import linkService from "../services/link.js";
import utils from "../services/utils.js";
+import LinkMapService from "../services/link_map.js";
+import noteDetailService from "../services/note_detail.js";
const $linkMapContainer = $("#link-map-container");
-const linkOverlays = [
- [ "Arrow", {
- location: 1,
- id: "arrow",
- length: 10,
- width: 10,
- foldback: 0.7
- } ]
-];
-
const LINK_TYPES = [ "hyper", "image", "relation", "relation-map" ];
const $dialog = $("#link-map-dialog");
const $maxNotesInput = $("#link-map-max-notes");
-let jsPlumbInstance = null;
-let pzInstance = null;
-let renderer = null;
+let linkMapService;
export async function showDialog() {
utils.closeActiveDialog();
@@ -35,184 +20,18 @@ export async function showDialog() {
$maxNotesInput.val(10);
LINK_TYPES.forEach(lt => $("#link-map-" + lt).prop('checked', true));
- await libraryLoader.requireLibrary(libraryLoader.LINK_MAP);
+ const note = noteDetailService.getActiveNoteId();
- jsPlumb.ready(() => {
- initJsPlumbInstance();
+ if (!note) {
+ return;
+ }
- initPanZoom();
-
- loadNotesAndRelations();
- });
+ linkMapService = new LinkMapService(note, $linkMapContainer);
+ linkMapService.render();
$dialog.modal();
}
-async function loadNotesAndRelations() {
- cleanup();
+$(".link-map-settings").change(() => linkMapService.loadNotesAndRelations());
- const linkTypes = LINK_TYPES.filter(lt => $(`#link-map-${lt}:checked`).length > 0);
- const maxNotes = parseInt($maxNotesInput.val());
-
- const activeNoteId = noteDetailService.getActiveNoteId();
-
- const links = await server.post(`notes/${activeNoteId}/link-map`, {
- linkTypes,
- maxNotes
- });
-
- const noteIds = new Set(links.map(l => l.noteId).concat(links.map(l => l.targetNoteId)));
-
- if (noteIds.size === 0) {
- noteIds.add(activeNoteId);
- }
-
- // preload all notes
- const notes = await treeCache.getNotes(Array.from(noteIds));
-
- const graph = new Springy.Graph();
- graph.addNodes(...noteIds);
- graph.addEdges(...links.map(l => [l.noteId, l.targetNoteId]));
-
- const layout = new Springy.Layout.ForceDirected(
- graph,
- 400.0, // Spring stiffness
- 400.0, // Node repulsion
- 0.5 // Damping
- );
-
- function getNoteBox(noteId) {
- const noteBoxId = noteIdToId(noteId);
- const $existingNoteBox = $("#" + noteBoxId);
-
- if ($existingNoteBox.length > 0) {
- return $existingNoteBox;
- }
-
- const note = notes.find(n => n.noteId === noteId);
-
- const $noteBox = $("")
- .addClass("note-box")
- .prop("id", noteBoxId);
-
- linkService.createNoteLink(noteId, note.title).then($link => {
- $noteBox.append($("").addClass("title").append($link));
- });
-
- if (activeNoteId === noteId) {
- $noteBox.addClass("link-map-active-note");
- }
-
- jsPlumbInstance.getContainer().appendChild($noteBox[0]);
-
- jsPlumbInstance.draggable($noteBox[0], {
- start: params => {
- renderer.stop();
- },
- drag: params => {},
- stop: params => {}
- });
-
-
- return $noteBox;
- }
-
- renderer = new Springy.Renderer(
- layout,
- () => {},
- (edge, p1, p2) => {
- const connectionId = edge.source.id + '-' + edge.target.id;
-
- if ($("#" + connectionId).length > 0) {
- return;
- }
-
- getNoteBox(edge.source.id);
- getNoteBox(edge.target.id);
-
- const connection = jsPlumbInstance.connect({
- source: noteIdToId(edge.source.id),
- target: noteIdToId(edge.target.id),
- type: 'link'
- });
-
- connection.canvas.id = connectionId;
- },
- (node, p) => {
- const $noteBox = getNoteBox(node.id);
- const middleW = $linkMapContainer.width() / 2;
- const middleH = $linkMapContainer.height() / 2;
-
- $noteBox
- .css("left", (middleW + p.x * 100) + "px")
- .css("top", (middleH + p.y * 100) + "px");
- },
- () => {},
- () => {},
- () => {
- jsPlumbInstance.repaintEverything();
- }
- );
-
- renderer.start();
-}
-
-function initPanZoom() {
- if (pzInstance) {
- return;
- }
-
- pzInstance = panzoom($linkMapContainer[0], {
- maxZoom: 2,
- minZoom: 0.3,
- smoothScroll: false,
- filterKey: function (e, dx, dy, dz) {
- // if ALT is pressed then panzoom should bubble the event up
- // this is to preserve ALT-LEFT, ALT-RIGHT navigation working
- return e.altKey;
- }
- });
-}
-
-function cleanup() {
- if (renderer) {
- renderer.stop();
- }
-
- // delete all endpoints and connections
- // this is done at this point (after async operations) to reduce flicker to the minimum
- jsPlumbInstance.deleteEveryEndpoint();
-
- // without this we still end up with note boxes remaining in the canvas
- $linkMapContainer.empty();
-
- // reset zoom/pan
- pzInstance.zoomTo(0, 0, 1);
- pzInstance.moveTo(0, 0);
-}
-
-function initJsPlumbInstance() {
- if (jsPlumbInstance) {
- cleanup();
-
- return;
- }
-
- jsPlumbInstance = jsPlumb.getInstance({
- Endpoint: ["Blank", {}],
- ConnectionOverlays: linkOverlays,
- PaintStyle: { stroke: "var(--muted-text-color)", strokeWidth: 1 },
- HoverPaintStyle: { stroke: "var(--main-text-color)", strokeWidth: 1 },
- Container: $linkMapContainer.attr("id")
- });
-
- jsPlumbInstance.registerConnectionType("link", { anchor: "Continuous", connector: "Straight", overlays: linkOverlays });
-}
-
-function noteIdToId(noteId) {
- return "link-map-note-" + noteId;
-}
-
-$(".link-map-settings").change(loadNotesAndRelations);
-
-$maxNotesInput.on("input", loadNotesAndRelations);
+$maxNotesInput.on("input", () => linkMapService.loadNotesAndRelations());
diff --git a/src/public/javascripts/services/attributes.js b/src/public/javascripts/services/attributes.js
index 5a62e60f9..36c6d95bf 100644
--- a/src/public/javascripts/services/attributes.js
+++ b/src/public/javascripts/services/attributes.js
@@ -1,9 +1,7 @@
import server from "./server.js";
-import utils from "./utils.js";
import ws from "./ws.js";
import treeUtils from "./tree_utils.js";
import noteAutocompleteService from "./note_autocomplete.js";
-import linkService from "./link.js";
class Attributes {
/**
diff --git a/src/public/javascripts/services/frontend_script_api.js b/src/public/javascripts/services/frontend_script_api.js
index 846a9471f..41337f4bc 100644
--- a/src/public/javascripts/services/frontend_script_api.js
+++ b/src/public/javascripts/services/frontend_script_api.js
@@ -5,7 +5,6 @@ import infoService from './info.js';
import linkService from './link.js';
import treeCache from './tree_cache.js';
import noteDetailService from './note_detail.js';
-import noteTypeService from './note_type.js';
import noteTooltipService from './note_tooltip.js';
import protectedSessionService from './protected_session.js';
import dateNotesService from './date_notes.js';
diff --git a/src/public/javascripts/services/link_map.js b/src/public/javascripts/services/link_map.js
index 6f1522516..f74ec8763 100644
--- a/src/public/javascripts/services/link_map.js
+++ b/src/public/javascripts/services/link_map.js
@@ -55,7 +55,6 @@ export default class LinkMap {
const graph = new Springy.Graph();
graph.addNodes(...noteIds);
- graph.addEdges(...links.map(l => [l.noteId, l.targetNoteId]));
const layout = new Springy.Layout.ForceDirected(
graph,
diff --git a/src/public/javascripts/services/note_detail_relation_map.js b/src/public/javascripts/services/note_detail_relation_map.js
index fff5b3320..63dfb6d0a 100644
--- a/src/public/javascripts/services/note_detail_relation_map.js
+++ b/src/public/javascripts/services/note_detail_relation_map.js
@@ -316,14 +316,6 @@ class NoteDetailRelationMap {
connection.canvas.setAttribute("data-connection-id", connection.id);
}
-
- for (const link of data.links) {
- this.jsPlumbInstance.connect({
- source: this.noteIdToId(link.sourceNoteId),
- target: this.noteIdToId(link.targetNoteId),
- type: 'link'
- });
- }
});
}
diff --git a/src/routes/api/links.js b/src/routes/api/links.js
deleted file mode 100644
index 04a2bab36..000000000
--- a/src/routes/api/links.js
+++ /dev/null
@@ -1,28 +0,0 @@
-"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
-};
\ No newline at end of file
diff --git a/src/routes/routes.js b/src/routes/routes.js
index a5898fbcf..ce2cf85d3 100644
--- a/src/routes/routes.js
+++ b/src/routes/routes.js
@@ -33,7 +33,6 @@ 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 customCodeMimeTypesRoute = require('./api/custom_code_mime_types.js');
@@ -161,8 +160,6 @@ function register(app) {
apiRoute(GET, '/api/notes/:noteId/target-relations', attributesRoute.getTargetRelations);
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);
diff --git a/src/services/repository.js b/src/services/repository.js
index ccbebdd30..808ab6185 100644
--- a/src/services/repository.js
+++ b/src/services/repository.js
@@ -72,11 +72,6 @@ async function getOption(name) {
return await getEntity("SELECT * FROM options WHERE name = ?", [name]);
}
-/** @returns {Promise} */
-async function getLink(linkId) {
- return await getEntity("SELECT * FROM links WHERE linkId = ?", [linkId]);
-}
-
async function updateEntity(entity) {
const entityName = entity.constructor.entityName;
const primaryKeyName = entity.constructor.primaryKeyName;
@@ -144,7 +139,6 @@ module.exports = {
getBranch,
getAttribute,
getOption,
- getLink,
updateEntity,
setEntityConstructor
};
\ No newline at end of file