Skip to content

Commit c84e77a

Browse files
authored
Merge pull request #267 from codex-team/note-history
feat(note-history): added note-history page
2 parents 4ed579e + 9df8c99 commit c84e77a

File tree

14 files changed

+360
-10
lines changed

14 files changed

+360
-10
lines changed

src/application/i18n/messages/en.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,10 @@
8181
},
8282
"lastEdit": "Last edit"
8383
},
84+
"history": {
85+
"title": "Versions history",
86+
"view": "View"
87+
},
8488
"noteList": {
8589
"emptyNoteList": "No Notes yet. Make your first note",
8690
"noteListItem": {
@@ -170,6 +174,7 @@
170174
"addTool": "Add tool",
171175
"notFound": "Not found",
172176
"joinTeam": "Join",
173-
"authorization": "Authorize"
177+
"authorization": "Authorize",
178+
"history": "History"
174179
}
175180
}

src/application/router/routes.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import AuthorizationPage from '@/presentation/pages/AuthorizationPage.vue';
99
import type { RouteRecordRaw } from 'vue-router';
1010
import AddTool from '@/presentation/pages/marketplace/AddTool.vue';
1111
import MarketplacePage from '@/presentation/pages/marketplace/MarketplacePage.vue';
12+
import History from '@/presentation/pages/History.vue';
1213
import MarketplaceTools from '@/presentation/pages/marketplace/MarketplaceTools.vue';
1314

1415
// Default production hostname for homepage. If different, then custom hostname used
@@ -43,6 +44,19 @@ const routes: RouteRecordRaw[] = [
4344
id: String(route.params.id),
4445
}),
4546
},
47+
{
48+
name: 'history',
49+
path: '/note/:noteId/history',
50+
component: History,
51+
meta: {
52+
layout: 'fullpage',
53+
pageTitleI18n: 'pages.history',
54+
authRequired: true,
55+
},
56+
props: route => ({
57+
noteId: String(route.params.noteId),
58+
}),
59+
},
4660
{
4761
name: 'new',
4862
path: '/new',
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import type { MaybeRefOrGetter, Ref } from 'vue';
2+
import { computed, onMounted, ref, toValue } from 'vue';
3+
import type { NoteHistoryRecord, NoteHistoryMeta } from '@/domain/entities/History';
4+
import type { Note } from '@/domain/entities/Note';
5+
import { noteHistoryService } from '@/domain';
6+
7+
interface UseNoteHistoryComposableState {
8+
/**
9+
* Note hisotry is array of the history meta used for history preview
10+
*/
11+
noteHistory: Ref<NoteHistoryMeta[] | null>;
12+
}
13+
14+
interface UseNoteHistoryComposableOptions {
15+
/**
16+
* Id of the note
17+
*/
18+
noteId: MaybeRefOrGetter<NoteHistoryRecord['noteId'] | null>;
19+
}
20+
21+
export default function useNoteHistory(options: UseNoteHistoryComposableOptions): UseNoteHistoryComposableState {
22+
const noteHistory = ref<NoteHistoryMeta[] | null>(null);
23+
24+
const currentNoteId = computed(() => toValue(options.noteId));
25+
26+
async function loadNoteHistory(noteId: Note['id']): Promise<void> {
27+
noteHistory.value = await noteHistoryService.loadNoteHistory(noteId);
28+
}
29+
30+
/**
31+
* When page is mounted, we should load note history
32+
*/
33+
onMounted(() => {
34+
if (currentNoteId.value !== null) {
35+
void loadNoteHistory(currentNoteId.value);
36+
}
37+
});
38+
39+
return {
40+
noteHistory: noteHistory,
41+
};
42+
}

src/domain/entities/History.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import type EditorTool from './EditorTool';
2+
import type { Note } from './Note';
3+
import type { User } from './User';
4+
5+
interface UserMeta {
6+
/**
7+
* Name of the user
8+
*/
9+
name: User['name'];
10+
11+
/**
12+
* Photo of the user
13+
*/
14+
photo: User['photo'];
15+
};
16+
17+
export interface NoteHistoryRecord {
18+
/**
19+
* Unique identified of note history record
20+
*/
21+
id: number;
22+
23+
/**
24+
* Id of the note whose content history is stored
25+
*/
26+
noteId: Note['id'];
27+
28+
/**
29+
* User that updated note content
30+
*/
31+
userId: User['id'];
32+
33+
/**
34+
* Timestamp of note update
35+
*/
36+
createdAt: string;
37+
38+
/**
39+
* Version of note content
40+
*/
41+
content: Note['content'];
42+
43+
/**
44+
* Note tools of current version of note content
45+
*/
46+
tools: EditorTool[];
47+
}
48+
49+
/**
50+
* Meta data of the note history record
51+
* Used for presentation of the note history record in web
52+
*/
53+
export type NoteHistoryMeta = Omit<NoteHistoryRecord, 'content' | 'noteId' | 'tools'> & {
54+
/**
55+
* Meta data of the user who did changes
56+
* Used for note history metadata presentation
57+
*/
58+
user: UserMeta;
59+
};

src/domain/index.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import NoteListService from './noteList.service';
99
import EditorToolsService from '@/domain/editorTools.service';
1010
import WorkspaceService from './workspace.service';
1111
import TeamService from './team.service';
12+
import NoteHistoryService from './noteHistory.service';
1213
/**
1314
* Get API url from environment
1415
*/
@@ -36,6 +37,7 @@ const authService = new AuthService(eventBus, repositories.auth);
3637
const userService = new UserService(eventBus, repositories.user);
3738
const marketplaceService = new MarketplaceService(repositories.marketplace);
3839
const teamService = new TeamService(repositories.team);
40+
const noteHistoryService = new NoteHistoryService(repositories.noteHistory);
3941

4042
/**
4143
* App State — is a read-only combination of app Stores.
@@ -60,5 +62,6 @@ export {
6062
userService,
6163
marketplaceService,
6264
workspaceService,
63-
teamService
65+
teamService,
66+
noteHistoryService
6467
};
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import type { NoteHistoryMeta } from './entities/History';
2+
import type { Note } from './entities/Note';
3+
4+
/**
5+
* Interface of the note history repository
6+
*/
7+
export default interface NoteHistoryRepositoryInterface {
8+
/**
9+
* Loads note history meta for note history preview
10+
* @param noteId - id of the note
11+
*/
12+
loadNoteHistory(noteId: Note['id']): Promise<NoteHistoryMeta[]>;
13+
}

src/domain/noteHistory.service.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import type { NoteHistoryMeta } from './entities/History';
2+
import type { Note } from './entities/Note';
3+
import type NoteHistoryRepository from './noteHistory.repository.interface';
4+
5+
/**
6+
* Note history service class, used for logic handling of the data,
7+
* Also used for data delivery from repository to application service
8+
*/
9+
export default class NoteHistoryService {
10+
/**
11+
* Note history repository instance
12+
*/
13+
private readonly noteHistoryRepository: NoteHistoryRepository;
14+
15+
constructor(historyRepository: NoteHistoryRepository) {
16+
this.noteHistoryRepository = historyRepository;
17+
}
18+
19+
/**
20+
* Loads note history meta for note history preview
21+
* @param noteId - id of the note
22+
*/
23+
public async loadNoteHistory(noteId: Note['id']): Promise<NoteHistoryMeta[]> {
24+
return await this.noteHistoryRepository.loadNoteHistory(noteId);
25+
}
26+
}

src/infrastructure/index.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import EditorToolsRepository from '@/infrastructure/editorTools.repository';
1717
import EditorToolsTransport from '@/infrastructure/transport/editorTools.transport';
1818
import NoteAttachmentUploaderRepository from './noteAttachmentUploader.repository';
1919
import TeamRepository from '@/infrastructure/team.repository';
20+
import NoteHistoryRepository from './noteHistory.repository';
2021

2122
/**
2223
* Repositories
@@ -66,6 +67,11 @@ export interface Repositories {
6667
* Working with teams
6768
*/
6869
team: TeamRepository;
70+
71+
/**
72+
* Working with note history
73+
*/
74+
noteHistory: NoteHistoryRepository;
6975
}
7076

7177
/**
@@ -135,6 +141,7 @@ export function init(noteApiUrl: string, eventBus: EventBus): Repositories {
135141
const noteAttachmentUploaderRepository = new NoteAttachmentUploaderRepository(notesApiTransport);
136142
const workspaceRepository = new WorkspaceRepository(openedPagesStore);
137143
const teamRepository = new TeamRepository(notesApiTransport);
144+
const noteHistoryRepository = new NoteHistoryRepository(notesApiTransport);
138145

139146
return {
140147
note: noteRepository,
@@ -146,5 +153,6 @@ export function init(noteApiUrl: string, eventBus: EventBus): Repositories {
146153
noteAttachmentUploader: noteAttachmentUploaderRepository,
147154
workspace: workspaceRepository,
148155
team: teamRepository,
156+
noteHistory: noteHistoryRepository,
149157
};
150158
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import type NoteHistoryRepositoryInterface from '@/domain/noteHistory.repository.interface';
2+
import type NotesApiTransport from './transport/notes-api';
3+
import type { Note } from '@/domain/entities/Note';
4+
import type { NoteHistoryMeta } from '@/domain/entities/History';
5+
6+
/**
7+
* Note history repository class used for data delivery from transport to service
8+
*/
9+
export default class NoteHistoryRepository implements NoteHistoryRepositoryInterface {
10+
/**
11+
* Notes api transport instance
12+
*/
13+
private readonly transport: NotesApiTransport;
14+
15+
constructor(notesApiTransport: NotesApiTransport) {
16+
this.transport = notesApiTransport;
17+
}
18+
19+
/**
20+
* Loads note history meta for note history preview
21+
* @param noteId - id of the note
22+
*/
23+
public async loadNoteHistory(noteId: Note['id']): Promise<NoteHistoryMeta[]> {
24+
const response = await this.transport.get<{ noteHistoryMeta: NoteHistoryMeta[] }>(`/note/${noteId}/history`);
25+
26+
return response.noteHistoryMeta;
27+
}
28+
}

src/infrastructure/utils/date.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,27 @@
11
import dayjs from 'dayjs';
22
import relativeTime from 'dayjs/plugin/relativeTime';
33
/**
4-
* Formats the date in a human-readable format
4+
* Returns time, that has been passed from updated at timestamp
55
* @param updatedAt - the date the note was last updated
66
* @returns the last updated time
77
*/
8-
export function formatShortDate(updatedAt: string): string {
8+
export function getTimeFromNow(updatedAt: string): string {
99
dayjs.extend(relativeTime);
1010
const formattedUpdatedAt = dayjs(updatedAt).fromNow();
1111

1212
return formattedUpdatedAt;
1313
}
14+
15+
/**
16+
* Converts timestamp to readable time format
17+
* @param date - date instance from timestamp
18+
* @returns - string with formatted date and time
19+
*/
20+
export function parseDate(date: Date): string {
21+
return new Intl.DateTimeFormat('en-GB', {
22+
month: 'short',
23+
day: 'numeric',
24+
hour: 'numeric',
25+
minute: 'numeric',
26+
}).format(date);
27+
}

src/presentation/components/note-list/NoteList.vue

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
<script setup lang="ts">
4242
import useNoteList from '@/application/services/useNoteList';
4343
import type { Note } from '@/domain/entities/Note';
44-
import { formatShortDate } from '@/infrastructure/utils/date';
44+
import { getTimeFromNow } from '@/infrastructure/utils/date';
4545
import { getTitle } from '@/infrastructure/utils/note';
4646
import { useI18n } from 'vue-i18n';
4747
import { Card, CardSkeleton, Button } from 'codex-ui/vue';
@@ -66,7 +66,7 @@ function getSubtitle(note: Note): string | undefined {
6666
return;
6767
}
6868
69-
return `${t('home.updated')} ${formatShortDate(note.updatedAt)}`;
69+
return `${t('home.updated')} ${getTimeFromNow(note.updatedAt)}`;
7070
}
7171
7272
</script>

0 commit comments

Comments
 (0)