Skip to content

Commit 391f5bd

Browse files
authored
Merge pull request #291 from devnull-1337/fix/note-Title-Composer
Fix/note title composer
2 parents 07b40e8 + 83faa86 commit 391f5bd

File tree

7 files changed

+123
-89
lines changed

7 files changed

+123
-89
lines changed

src/domain/entities/NoteHierarchy.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { NoteContent, NotePublicId } from './note.js';
1+
import type { NotePublicId } from './note.js';
22

33
/**
44
* Note Tree entity
@@ -8,12 +8,12 @@ export interface NoteHierarchy {
88
/**
99
* public note id
1010
*/
11-
id: NotePublicId;
11+
noteId: NotePublicId;
1212

1313
/**
14-
* note content
14+
* note title
1515
*/
16-
content: NoteContent;
16+
noteTitle: string;
1717

1818
/**
1919
* child notes

src/domain/entities/note.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,3 +87,28 @@ export interface Note {
8787
* Part of note entity used to create new note
8888
*/
8989
export type NoteCreationAttributes = Pick<Note, 'publicId' | 'content' | 'creatorId' | 'tools'>;
90+
91+
/**
92+
* Note preview entity used to display notes in a sidebar hierarchy
93+
*/
94+
export type NotePreview = {
95+
/**
96+
* Note id
97+
*/
98+
noteId: NoteInternalId;
99+
100+
/**
101+
* Note public id
102+
*/
103+
publicId: NotePublicId;
104+
105+
/**
106+
* Note content
107+
*/
108+
content: NoteContent;
109+
110+
/**
111+
* Parent note id
112+
*/
113+
parentId: NoteInternalId | null;
114+
};

src/domain/service/note.ts

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { Note, NoteInternalId, NotePublicId } from '@domain/entities/note.js';
1+
import type { Note, NoteContent, NoteInternalId, NotePublicId } from '@domain/entities/note.js';
22
import type NoteRepository from '@repository/note.repository.js';
33
import type NoteVisitsRepository from '@repository/noteVisits.repository.js';
44
import { createPublicId } from '@infrastructure/utils/id.js';
@@ -466,8 +466,58 @@ export default class NoteService {
466466
// If there is no ultimate parent, the provided noteId is the ultimate parent
467467
const rootNoteId = ultimateParent ?? noteId;
468468

469-
const noteHierarchy = await this.noteRepository.getNoteHierarchyByNoteId(rootNoteId);
469+
const notesRows = await this.noteRepository.getNoteTreeByNoteId(rootNoteId);
470470

471-
return noteHierarchy;
471+
const notesMap = new Map<NoteInternalId, NoteHierarchy>();
472+
473+
let root: NoteHierarchy | null = null;
474+
475+
if (!notesRows || notesRows.length === 0) {
476+
return null;
477+
}
478+
// Step 1: Parse and initialize all notes
479+
notesRows.forEach((note) => {
480+
notesMap.set(note.noteId, {
481+
noteId: note.publicId,
482+
noteTitle: this.getTitleFromContent(note.content),
483+
childNotes: null,
484+
});
485+
});
486+
487+
// Step 2: Build hierarchy
488+
notesRows.forEach((note) => {
489+
if (note.parentId === null) {
490+
root = notesMap.get(note.noteId) ?? null;
491+
} else {
492+
const parent = notesMap.get(note.parentId);
493+
494+
if (parent) {
495+
// Initialize childNotes as an array if it's null
496+
if (parent.childNotes === null) {
497+
parent.childNotes = [];
498+
}
499+
parent.childNotes?.push(notesMap.get(note.noteId)!);
500+
}
501+
}
502+
});
503+
504+
return root;
472505
}
506+
507+
/**
508+
* Get the title of the note
509+
* @param content - content of the note
510+
* @returns the title of the note
511+
*/
512+
public getTitleFromContent(content: NoteContent): string {
513+
const limitCharsForNoteTitle = 50;
514+
const firstNoteBlock = content.blocks[0];
515+
const text = (firstNoteBlock?.data as { text?: string })?.text;
516+
517+
if (text === undefined || text.trim() === '') {
518+
return 'Untitled';
519+
}
520+
521+
return text.replace(/&nbsp;/g, ' ').slice(0, limitCharsForNoteTitle);
522+
};
473523
}

src/presentation/http/router/note.test.ts

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2290,7 +2290,10 @@ describe('Note API', () => {
22902290
{
22912291
description: 'Should get note hierarchy with no parent or child when noteId passed has no relations',
22922292
setup: async () => {
2293-
const note = await global.db.insertNote({ creatorId: user.id });
2293+
const note = await global.db.insertNote({
2294+
creatorId: user.id,
2295+
content: DEFAULT_NOTE_CONTENT,
2296+
});
22942297

22952298
await global.db.insertNoteSetting({
22962299
noteId: note.id,
@@ -2304,8 +2307,8 @@ describe('Note API', () => {
23042307
},
23052308

23062309
expected: (note: Note, childNote: Note | null) => ({
2307-
id: note.publicId,
2308-
content: note.content,
2310+
noteId: note.publicId,
2311+
noteTitle: 'text',
23092312
childNotes: childNote,
23102313
}),
23112314
},
@@ -2314,8 +2317,14 @@ describe('Note API', () => {
23142317
{
23152318
description: 'Should get note hierarchy with child when noteId passed has relations',
23162319
setup: async () => {
2317-
const childNote = await global.db.insertNote({ creatorId: user.id });
2318-
const parentNote = await global.db.insertNote({ creatorId: user.id });
2320+
const childNote = await global.db.insertNote({
2321+
creatorId: user.id,
2322+
content: DEFAULT_NOTE_CONTENT,
2323+
});
2324+
const parentNote = await global.db.insertNote({
2325+
creatorId: user.id,
2326+
content: DEFAULT_NOTE_CONTENT,
2327+
});
23192328

23202329
await global.db.insertNoteSetting({
23212330
noteId: childNote.id,
@@ -2336,12 +2345,12 @@ describe('Note API', () => {
23362345
};
23372346
},
23382347
expected: (note: Note, childNote: Note | null) => ({
2339-
id: note.publicId,
2340-
content: note.content,
2348+
noteId: note.publicId,
2349+
noteTitle: 'text',
23412350
childNotes: [
23422351
{
2343-
id: childNote?.publicId,
2344-
content: childNote?.content,
2352+
noteId: childNote?.publicId,
2353+
noteTitle: 'text',
23452354
childNotes: null,
23462355
},
23472356
],

src/presentation/http/schema/NoteHierarchy.ts

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,15 @@
11
export const NoteHierarchySchema = {
22
$id: 'NoteHierarchySchema',
33
properties: {
4-
id: {
4+
noteId: {
55
type: 'string',
66
pattern: '[a-zA-Z0-9-_]+',
77
maxLength: 10,
88
minLength: 10,
99
},
10-
content: {
11-
type: 'object',
12-
properties: {
13-
time: {
14-
type: 'number',
15-
},
16-
blocks: {
17-
type: 'array',
18-
},
19-
version: {
20-
type: 'string',
21-
},
22-
},
10+
noteTitle: {
11+
type: 'string',
12+
maxLength: 50,
2313
},
2414
childNotes: {
2515
type: 'array',

src/repository/note.repository.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import type { Note, NoteCreationAttributes, NoteInternalId, NotePublicId } from '@domain/entities/note.js';
2-
import type { NoteHierarchy } from '@domain/entities/NoteHierarchy.js';
1+
import type { Note, NoteCreationAttributes, NoteInternalId, NotePublicId, NotePreview } from '@domain/entities/note.js';
32
import type NoteStorage from '@repository/storage/note.storage.js';
43

54
/**
@@ -93,11 +92,11 @@ export default class NoteRepository {
9392
}
9493

9594
/**
96-
* Gets the Note tree by note id
95+
* Get note and all of its children recursively
9796
* @param noteId - note id
98-
* @returns NoteHierarchy structure
97+
* @returns an array of NotePreview
9998
*/
100-
public async getNoteHierarchyByNoteId(noteId: NoteInternalId): Promise<NoteHierarchy | null> {
101-
return await this.storage.getNoteHierarchybyNoteId(noteId);
99+
public async getNoteTreeByNoteId(noteId: NoteInternalId): Promise<NotePreview[] | null> {
100+
return await this.storage.getNoteTreebyNoteId(noteId);
102101
}
103102
}

src/repository/storage/postgres/orm/sequelize/note.ts

Lines changed: 14 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
import type { CreationOptional, InferAttributes, InferCreationAttributes, ModelStatic, NonAttribute, Sequelize } from 'sequelize';
22
import { DataTypes, Model, Op, QueryTypes } from 'sequelize';
33
import type Orm from '@repository/storage/postgres/orm/sequelize/index.js';
4-
import type { Note, NoteContent, NoteCreationAttributes, NoteInternalId, NotePublicId } from '@domain/entities/note.js';
4+
import type { Note, NoteCreationAttributes, NoteInternalId, NotePublicId, NotePreview } from '@domain/entities/note.js';
55
import { UserModel } from '@repository/storage/postgres/orm/sequelize/user.js';
66
import type { NoteSettingsModel } from './noteSettings.js';
77
import type { NoteVisitsModel } from './noteVisits.js';
88
import type { NoteHistoryModel } from './noteHistory.js';
9-
import type { NoteHierarchy } from '@domain/entities/NoteHierarchy.js';
109

1110
/* eslint-disable @typescript-eslint/naming-convention */
1211

@@ -349,33 +348,33 @@ export default class NoteSequelizeStorage {
349348
}
350349

351350
/**
352-
* Creates a tree of notes
353-
* @param noteId - public note id
354-
* @returns NoteHierarchy
351+
* Get note and all of its children recursively
352+
* @param noteId - note id
353+
* @returns an array of NotePreview
355354
*/
356-
public async getNoteHierarchybyNoteId(noteId: NoteInternalId): Promise<NoteHierarchy | null> {
355+
public async getNoteTreebyNoteId(noteId: NoteInternalId): Promise<NotePreview[] | null> {
357356
// Fetch all notes and relations in a recursive query
358357
const query = `
359358
WITH RECURSIVE note_tree AS (
360359
SELECT
361-
n.id AS noteId,
360+
n.id AS "noteId",
362361
n.content,
363-
n.public_id,
364-
nr.parent_id
362+
n.public_id AS "publicId",
363+
nr.parent_id AS "parentId"
365364
FROM ${String(this.database.literal(this.tableName).val)} n
366365
LEFT JOIN ${String(this.database.literal('note_relations').val)} nr ON n.id = nr.note_id
367366
WHERE n.id = :startNoteId
368367
369368
UNION ALL
370369
371370
SELECT
372-
n.id AS noteId,
371+
n.id AS "noteId",
373372
n.content,
374-
n.public_id,
375-
nr.parent_id
373+
n.public_id AS "publicId",
374+
nr.parent_id AS "parentId"
376375
FROM ${String(this.database.literal(this.tableName).val)} n
377376
INNER JOIN ${String(this.database.literal('note_relations').val)} nr ON n.id = nr.note_id
378-
INNER JOIN note_tree nt ON nr.parent_id = nt.noteId
377+
INNER JOIN note_tree nt ON nr.parent_id = nt."noteId"
379378
)
380379
SELECT * FROM note_tree;
381380
`;
@@ -388,46 +387,8 @@ export default class NoteSequelizeStorage {
388387
if (!result || result.length === 0) {
389388
return null; // No data found
390389
}
390+
const notes = result as NotePreview[];
391391

392-
type NoteRow = {
393-
noteid: NoteInternalId;
394-
public_id: NotePublicId;
395-
content: NoteContent;
396-
parent_id: NoteInternalId | null;
397-
};
398-
399-
const notes = result as NoteRow[];
400-
401-
const notesMap = new Map<NoteInternalId, NoteHierarchy>();
402-
403-
let root: NoteHierarchy | null = null;
404-
405-
// Step 1: Parse and initialize all notes
406-
notes.forEach((note) => {
407-
notesMap.set(note.noteid, {
408-
id: note.public_id,
409-
content: note.content,
410-
childNotes: null,
411-
});
412-
});
413-
414-
// Step 2: Build hierarchy
415-
notes.forEach((note) => {
416-
if (note.parent_id === null) {
417-
root = notesMap.get(note.noteid) ?? null;
418-
} else {
419-
const parent = notesMap.get(note.parent_id);
420-
421-
if (parent) {
422-
// Initialize childNotes as an array if it's null
423-
if (parent.childNotes === null) {
424-
parent.childNotes = [];
425-
}
426-
parent.childNotes?.push(notesMap.get(note.noteid)!);
427-
}
428-
}
429-
});
430-
431-
return root;
392+
return notes;
432393
}
433394
}

0 commit comments

Comments
 (0)