server-ts: Convert routes/api/note_map

This commit is contained in:
Elian Doran 2024-04-06 21:45:58 +03:00
parent b1744c3867
commit 37697c7db7
No known key found for this signature in database
2 changed files with 46 additions and 40 deletions

View File

@ -1,18 +1,25 @@
"use strict"; "use strict";
const becca = require('../../becca/becca'); import becca = require('../../becca/becca');
const { JSDOM } = require("jsdom"); import { JSDOM } from "jsdom";
import BNote = require('../../becca/entities/bnote');
import BAttribute = require('../../becca/entities/battribute');
import { Request } from 'express';
import ValidationError = require('../../errors/validation_error');
function buildDescendantCountMap(noteIdsToCount) { function buildDescendantCountMap(noteIdsToCount: string[]) {
if (!Array.isArray(noteIdsToCount)) { if (!Array.isArray(noteIdsToCount)) {
throw new Error('noteIdsToCount: type error'); throw new Error('noteIdsToCount: type error');
} }
const noteIdToCountMap = Object.create(null); const noteIdToCountMap = Object.create(null);
function getCount(noteId) { function getCount(noteId: string) {
if (!(noteId in noteIdToCountMap)) { if (!(noteId in noteIdToCountMap)) {
const note = becca.getNote(noteId); const note = becca.getNote(noteId);
if (!note) {
return;
}
const hiddenImageNoteIds = note.getRelations('imageLink').map(rel => rel.value); const hiddenImageNoteIds = note.getRelations('imageLink').map(rel => rel.value);
const childNoteIds = note.children.map(child => child.noteId); const childNoteIds = note.children.map(child => child.noteId);
@ -33,19 +40,14 @@ function buildDescendantCountMap(noteIdsToCount) {
return noteIdToCountMap; return noteIdToCountMap;
} }
/** function getNeighbors(note: BNote, depth: number): string[] {
* @param {BNote} note
* @param {int} depth
* @returns {string[]} noteIds
*/
function getNeighbors(note, depth) {
if (depth === 0) { if (depth === 0) {
return []; return [];
} }
const retNoteIds = []; const retNoteIds = [];
function isIgnoredRelation(relation) { function isIgnoredRelation(relation: BAttribute) {
return ['relationMapLink', 'template', 'inherit', 'image', 'ancestor'].includes(relation.name); return ['relationMapLink', 'template', 'inherit', 'image', 'ancestor'].includes(relation.name);
} }
@ -90,8 +92,9 @@ function getNeighbors(note, depth) {
return retNoteIds; return retNoteIds;
} }
function getLinkMap(req) { function getLinkMap(req: Request) {
const mapRootNote = becca.getNote(req.params.noteId); const mapRootNote = becca.getNoteOrThrow(req.params.noteId);
// if the map root itself has "excludeFromNoteMap" attribute (journal typically) then there wouldn't be anything // if the map root itself has "excludeFromNoteMap" attribute (journal typically) then there wouldn't be anything
// to display, so we'll just ignore it // to display, so we'll just ignore it
const ignoreExcludeFromNoteMap = mapRootNote.isLabelTruthy('excludeFromNoteMap'); const ignoreExcludeFromNoteMap = mapRootNote.isLabelTruthy('excludeFromNoteMap');
@ -125,7 +128,7 @@ function getLinkMap(req) {
const noteIdsArray = Array.from(noteIds) const noteIdsArray = Array.from(noteIds)
const notes = noteIdsArray.map(noteId => { const notes = noteIdsArray.map(noteId => {
const note = becca.getNote(noteId); const note = becca.getNoteOrThrow(noteId);
return [ return [
note.noteId, note.noteId,
@ -144,6 +147,9 @@ function getLinkMap(req) {
} }
else if (rel.name === 'imageLink') { else if (rel.name === 'imageLink') {
const parentNote = becca.getNote(rel.noteId); const parentNote = becca.getNote(rel.noteId);
if (!parentNote) {
return false;
}
return !parentNote.getChildNotes().find(childNote => childNote.noteId === rel.value); return !parentNote.getChildNotes().find(childNote => childNote.noteId === rel.value);
} }
@ -165,8 +171,8 @@ function getLinkMap(req) {
}; };
} }
function getTreeMap(req) { function getTreeMap(req: Request) {
const mapRootNote = becca.getNote(req.params.noteId); const mapRootNote = becca.getNoteOrThrow(req.params.noteId);
// if the map root itself has "excludeFromNoteMap" (journal typically) then there wouldn't be anything to display, // if the map root itself has "excludeFromNoteMap" (journal typically) then there wouldn't be anything to display,
// so we'll just ignore it // so we'll just ignore it
const ignoreExcludeFromNoteMap = mapRootNote.isLabelTruthy('excludeFromNoteMap'); const ignoreExcludeFromNoteMap = mapRootNote.isLabelTruthy('excludeFromNoteMap');
@ -198,8 +204,8 @@ function getTreeMap(req) {
note.getLabelValue('color') note.getLabelValue('color')
]); ]);
const noteIds = new Set(); const noteIds = new Set<string>();
notes.forEach(([noteId]) => noteIds.add(noteId)); notes.forEach(([noteId]) => noteId && noteIds.add(noteId));
const links = []; const links = [];
@ -225,7 +231,7 @@ function getTreeMap(req) {
}; };
} }
function updateDescendantCountMapForSearch(noteIdToDescendantCountMap, relationships) { function updateDescendantCountMapForSearch(noteIdToDescendantCountMap: Record<string, number>, relationships: { parentNoteId: string, childNoteId: string }[]) {
for (const {parentNoteId, childNoteId} of relationships) { for (const {parentNoteId, childNoteId} of relationships) {
const parentNote = becca.notes[parentNoteId]; const parentNote = becca.notes[parentNoteId];
if (!parentNote || parentNote.type !== 'search') { if (!parentNote || parentNote.type !== 'search') {
@ -237,16 +243,17 @@ function updateDescendantCountMapForSearch(noteIdToDescendantCountMap, relations
} }
} }
function removeImages(document) { function removeImages(document: Document) {
const images = document.getElementsByTagName('img'); const images = document.getElementsByTagName('img');
while (images.length > 0) { while (images && images.length > 0) {
images[0].parentNode.removeChild(images[0]); images[0]?.parentNode?.removeChild(images[0]);
} }
} }
const EXCERPT_CHAR_LIMIT = 200; const EXCERPT_CHAR_LIMIT = 200;
type ElementOrText = (Element | Text);
function findExcerpts(sourceNote, referencedNoteId) { function findExcerpts(sourceNote: BNote, referencedNoteId: string) {
const html = sourceNote.getContent(); const html = sourceNote.getContent();
const document = new JSDOM(html).window.document; const document = new JSDOM(html).window.document;
@ -263,25 +270,24 @@ function findExcerpts(sourceNote, referencedNoteId) {
linkEl.classList.add("backlink-link"); linkEl.classList.add("backlink-link");
let centerEl = linkEl; let centerEl: HTMLElement = linkEl;
while (centerEl.tagName !== 'BODY' && centerEl.parentElement?.textContent?.length <= EXCERPT_CHAR_LIMIT) { while (centerEl.tagName !== 'BODY' && centerEl.parentElement && (centerEl.parentElement?.textContent?.length || 0) <= EXCERPT_CHAR_LIMIT) {
centerEl = centerEl.parentElement; centerEl = centerEl.parentElement;
} }
/** @var {HTMLElement[]} */ const excerptEls: ElementOrText[] = [centerEl];
const excerptEls = [centerEl]; let excerptLength = centerEl.textContent?.length || 0;
let excerptLength = centerEl.textContent.length; let left: ElementOrText = centerEl;
let left = centerEl; let right: ElementOrText = centerEl;
let right = centerEl;
while (excerptLength < EXCERPT_CHAR_LIMIT) { while (excerptLength < EXCERPT_CHAR_LIMIT) {
let added = false; let added = false;
const prev = left.previousElementSibling; const prev: Element | null = left.previousElementSibling;
if (prev) { if (prev) {
const prevText = prev.textContent; const prevText = prev.textContent || "";
if (prevText.length + excerptLength > EXCERPT_CHAR_LIMIT) { if (prevText.length + excerptLength > EXCERPT_CHAR_LIMIT) {
const prefix = prevText.substr(prevText.length - (EXCERPT_CHAR_LIMIT - excerptLength)); const prefix = prevText.substr(prevText.length - (EXCERPT_CHAR_LIMIT - excerptLength));
@ -298,12 +304,12 @@ function findExcerpts(sourceNote, referencedNoteId) {
added = true; added = true;
} }
const next = right.nextElementSibling; const next: Element | null = right.nextElementSibling;
if (next) { if (next) {
const nextText = next.textContent; const nextText = next.textContent;
if (nextText.length + excerptLength > EXCERPT_CHAR_LIMIT) { if (nextText && nextText.length + excerptLength > EXCERPT_CHAR_LIMIT) {
const suffix = nextText.substr(nextText.length - (EXCERPT_CHAR_LIMIT - excerptLength)); const suffix = nextText.substr(nextText.length - (EXCERPT_CHAR_LIMIT - excerptLength));
const textNode = document.createTextNode(`${suffix}`); const textNode = document.createTextNode(`${suffix}`);
@ -314,7 +320,7 @@ function findExcerpts(sourceNote, referencedNoteId) {
right = next; right = next;
excerptEls.push(right); excerptEls.push(right);
excerptLength += nextText.length; excerptLength += nextText?.length || 0;
added = true; added = true;
} }
@ -336,13 +342,13 @@ function findExcerpts(sourceNote, referencedNoteId) {
return excerpts; return excerpts;
} }
function getFilteredBacklinks(note) { function getFilteredBacklinks(note: BNote) {
return note.getTargetRelations() return note.getTargetRelations()
// search notes have "ancestor" relations which are not interesting // search notes have "ancestor" relations which are not interesting
.filter(relation => !!relation.getNote() && relation.getNote().type !== 'search'); .filter(relation => !!relation.getNote() && relation.getNote().type !== 'search');
} }
function getBacklinkCount(req) { function getBacklinkCount(req: Request) {
const {noteId} = req.params; const {noteId} = req.params;
const note = becca.getNoteOrThrow(noteId); const note = becca.getNoteOrThrow(noteId);
@ -352,7 +358,7 @@ function getBacklinkCount(req) {
}; };
} }
function getBacklinks(req) { function getBacklinks(req: Request) {
const {noteId} = req.params; const {noteId} = req.params;
const note = becca.getNoteOrThrow(noteId); const note = becca.getNoteOrThrow(noteId);
@ -379,7 +385,7 @@ function getBacklinks(req) {
}); });
} }
module.exports = { export = {
getLinkMap, getLinkMap,
getTreeMap, getTreeMap,
getBacklinkCount, getBacklinkCount,

View File

@ -49,7 +49,7 @@ const filesRoute = require('./api/files');
const searchRoute = require('./api/search'); const searchRoute = require('./api/search');
const bulkActionRoute = require('./api/bulk_action'); const bulkActionRoute = require('./api/bulk_action');
const specialNotesRoute = require('./api/special_notes'); const specialNotesRoute = require('./api/special_notes');
const noteMapRoute = require('./api/note_map.js'); const noteMapRoute = require('./api/note_map');
const clipperRoute = require('./api/clipper'); const clipperRoute = require('./api/clipper');
const similarNotesRoute = require('./api/similar_notes.js'); const similarNotesRoute = require('./api/similar_notes.js');
const keysRoute = require('./api/keys'); const keysRoute = require('./api/keys');