mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2024-11-21 09:16:30 -05:00
wip: range authorships backend storage
Co-authored-by: Philip Molares <philip.molares@udo.edu> Signed-off-by: Philip Molares <philip.molares@udo.edu> Signed-off-by: Erik Michelson <github@erik.michelson.eu>
This commit is contained in:
parent
c5dc671398
commit
68780f54e1
29 changed files with 316 additions and 133 deletions
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
@ -76,7 +76,8 @@ const routes: Routes = [
|
|||
detectTsNode() ? 'ts' : 'js'
|
||||
}`,
|
||||
],
|
||||
migrationsRun: true,
|
||||
migrationsRun: false,
|
||||
synchronize: true,
|
||||
};
|
||||
},
|
||||
}),
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
@ -11,7 +11,7 @@ import {
|
|||
PrimaryGeneratedColumn,
|
||||
} from 'typeorm';
|
||||
|
||||
import { Edit } from '../revisions/edit.entity';
|
||||
import { RangeAuthorship } from '../revisions/range-authorship.entity';
|
||||
import { Session } from '../sessions/session.entity';
|
||||
import { User } from '../users/user.entity';
|
||||
|
||||
|
@ -52,8 +52,8 @@ export class Author {
|
|||
* List of edits that this author created
|
||||
* All edits must belong to the same note
|
||||
*/
|
||||
@OneToMany(() => Edit, (edit) => edit.author)
|
||||
edits: Promise<Edit[]>;
|
||||
@OneToMany(() => RangeAuthorship, (edit) => edit.author)
|
||||
edits: Promise<RangeAuthorship[]>;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
private constructor() {}
|
||||
|
|
|
@ -1,14 +1,18 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { LoggerModule } from '../logger/logger.module';
|
||||
import { Author } from './author.entity';
|
||||
import { AuthorsService } from './authors.service';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([Author])],
|
||||
imports: [TypeOrmModule.forFeature([Author]), LoggerModule],
|
||||
providers: [AuthorsService],
|
||||
exports: [AuthorsService],
|
||||
})
|
||||
export class AuthorsModule {}
|
||||
|
|
42
backend/src/authors/authors.service.ts
Normal file
42
backend/src/authors/authors.service.ts
Normal file
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import type { ShortRealtimeUser } from '@hedgedoc/commons';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { ConsoleLoggerService } from '../logger/console-logger.service';
|
||||
import { Author } from './author.entity';
|
||||
|
||||
@Injectable()
|
||||
export class AuthorsService {
|
||||
constructor(
|
||||
private readonly logger: ConsoleLoggerService,
|
||||
@InjectRepository(Author) private authorsRepository: Repository<Author>,
|
||||
) {
|
||||
this.logger.setContext(AuthorsService.name);
|
||||
}
|
||||
|
||||
// /**
|
||||
// * @async
|
||||
// * Get or create the author specified by the short realtime user
|
||||
// * @param {Username} username the username by which the user is specified
|
||||
// * @param {UserRelationEnum[]} [withRelations=[]] if the returned user object should contain certain relations
|
||||
// * @return {User} the specified user
|
||||
// */
|
||||
// async findOrCreateAuthor(
|
||||
// shortRealtimeUser: ShortRealtimeUser,
|
||||
// ): Promise<Author> {
|
||||
// const author = await this.authorsRepository.findOne({
|
||||
// where: { username: username },
|
||||
// relations: withRelations,
|
||||
// });
|
||||
// if (user === null) {
|
||||
// throw new NotInDBError(`User with username '${username}' not found`);
|
||||
// }
|
||||
// return user;
|
||||
// }
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
@ -28,7 +28,7 @@ import { NotesModule } from '../notes/notes.module';
|
|||
import { Tag } from '../notes/tag.entity';
|
||||
import { NoteGroupPermission } from '../permissions/note-group-permission.entity';
|
||||
import { NoteUserPermission } from '../permissions/note-user-permission.entity';
|
||||
import { Edit } from '../revisions/edit.entity';
|
||||
import { RangeAuthorship } from '../revisions/edit.entity';
|
||||
import { Revision } from '../revisions/revision.entity';
|
||||
import { RevisionsModule } from '../revisions/revisions.module';
|
||||
import { RevisionsService } from '../revisions/revisions.service';
|
||||
|
@ -116,7 +116,7 @@ describe('HistoryService', () => {
|
|||
.useValue({})
|
||||
.overrideProvider(getRepositoryToken(Identity))
|
||||
.useValue({})
|
||||
.overrideProvider(getRepositoryToken(Edit))
|
||||
.overrideProvider(getRepositoryToken(RangeAuthorship))
|
||||
.useValue({})
|
||||
.overrideProvider(getRepositoryToken(Revision))
|
||||
.useValue({})
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
@ -28,7 +28,7 @@ import { NotesModule } from '../notes/notes.module';
|
|||
import { Tag } from '../notes/tag.entity';
|
||||
import { NoteGroupPermission } from '../permissions/note-group-permission.entity';
|
||||
import { NoteUserPermission } from '../permissions/note-user-permission.entity';
|
||||
import { Edit } from '../revisions/edit.entity';
|
||||
import { RangeAuthorship } from '../revisions/edit.entity';
|
||||
import { Revision } from '../revisions/revision.entity';
|
||||
import { Session } from '../sessions/session.entity';
|
||||
import { User } from '../users/user.entity';
|
||||
|
@ -82,7 +82,7 @@ describe('MediaService', () => {
|
|||
EventEmitterModule.forRoot(eventModuleConfig),
|
||||
],
|
||||
})
|
||||
.overrideProvider(getRepositoryToken(Edit))
|
||||
.overrideProvider(getRepositoryToken(RangeAuthorship))
|
||||
.useValue({})
|
||||
.overrideProvider(getRepositoryToken(AuthToken))
|
||||
.useValue({})
|
||||
|
|
|
@ -30,7 +30,7 @@ import { LoggerModule } from '../logger/logger.module';
|
|||
import { NoteGroupPermission } from '../permissions/note-group-permission.entity';
|
||||
import { NoteUserPermission } from '../permissions/note-user-permission.entity';
|
||||
import { RealtimeNoteModule } from '../realtime/realtime-note/realtime-note.module';
|
||||
import { Edit } from '../revisions/edit.entity';
|
||||
import { RangeAuthorship } from '../revisions/edit.entity';
|
||||
import { Revision } from '../revisions/revision.entity';
|
||||
import { RevisionsModule } from '../revisions/revisions.module';
|
||||
import { Session } from '../sessions/session.entity';
|
||||
|
@ -122,7 +122,7 @@ describe('AliasService', () => {
|
|||
.useValue({})
|
||||
.overrideProvider(getRepositoryToken(Identity))
|
||||
.useValue({})
|
||||
.overrideProvider(getRepositoryToken(Edit))
|
||||
.overrideProvider(getRepositoryToken(RangeAuthorship))
|
||||
.useValue({})
|
||||
.overrideProvider(getRepositoryToken(Revision))
|
||||
.useClass(Repository)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
@ -7,7 +7,7 @@ import { ApiProperty } from '@nestjs/swagger';
|
|||
import { Type } from 'class-transformer';
|
||||
import { IsArray, IsString, ValidateNested } from 'class-validator';
|
||||
|
||||
import { EditDto } from '../revisions/edit.dto';
|
||||
import { RangeAuthorshipDto } from '../revisions/range-authorship.dto';
|
||||
import { BaseDto } from '../utils/base.dto.';
|
||||
import { NoteMetadataDto } from './note-metadata.dto';
|
||||
|
||||
|
@ -33,7 +33,7 @@ export class NoteDto extends BaseDto {
|
|||
*/
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => EditDto)
|
||||
@ApiProperty({ isArray: true, type: EditDto })
|
||||
editedByAtPosition: EditDto[];
|
||||
@Type(() => RangeAuthorshipDto)
|
||||
@ApiProperty({ isArray: true, type: RangeAuthorshipDto })
|
||||
editedByAtPosition: RangeAuthorshipDto[];
|
||||
}
|
||||
|
|
|
@ -41,7 +41,7 @@ import { LoggerModule } from '../logger/logger.module';
|
|||
import { NoteGroupPermission } from '../permissions/note-group-permission.entity';
|
||||
import { NoteUserPermission } from '../permissions/note-user-permission.entity';
|
||||
import { RealtimeNoteModule } from '../realtime/realtime-note/realtime-note.module';
|
||||
import { Edit } from '../revisions/edit.entity';
|
||||
import { RangeAuthorship } from '../revisions/edit.entity';
|
||||
import { Revision } from '../revisions/revision.entity';
|
||||
import { RevisionsModule } from '../revisions/revisions.module';
|
||||
import { RevisionsService } from '../revisions/revisions.service';
|
||||
|
@ -198,7 +198,7 @@ describe('NotesService', () => {
|
|||
.useValue({})
|
||||
.overrideProvider(getRepositoryToken(Identity))
|
||||
.useValue({})
|
||||
.overrideProvider(getRepositoryToken(Edit))
|
||||
.overrideProvider(getRepositoryToken(RangeAuthorship))
|
||||
.useValue({})
|
||||
.overrideProvider(getRepositoryToken(Revision))
|
||||
.useClass(Repository)
|
||||
|
@ -248,13 +248,13 @@ describe('NotesService', () => {
|
|||
endPos: 1,
|
||||
updatedAt: new Date(1549312452000),
|
||||
author: Promise.resolve(author),
|
||||
} as Edit,
|
||||
} as RangeAuthorship,
|
||||
{
|
||||
startPos: 0,
|
||||
endPos: 1,
|
||||
updatedAt: new Date(1549312452001),
|
||||
author: Promise.resolve(author),
|
||||
} as Edit,
|
||||
} as RangeAuthorship,
|
||||
]),
|
||||
createdAt: new Date(1549312452000),
|
||||
tags: Promise.resolve([
|
||||
|
|
|
@ -104,6 +104,7 @@ export class NotesService {
|
|||
const newRevision = await this.revisionsService.createRevision(
|
||||
newNote,
|
||||
noteContent,
|
||||
[], // TODO Use the correct rangeAuthorships
|
||||
);
|
||||
newNote.revisions = Promise.resolve(
|
||||
newRevision === undefined ? [] : [newRevision],
|
||||
|
@ -261,7 +262,7 @@ export class NotesService {
|
|||
.createQueryBuilder('user')
|
||||
.innerJoin('user.authors', 'author')
|
||||
.innerJoin('author.edits', 'edit')
|
||||
.innerJoin('edit.revisions', 'revision')
|
||||
.innerJoin('edit.revision', 'revision')
|
||||
.innerJoin('revision.note', 'note')
|
||||
.where('note.id = :id', { id: note.id })
|
||||
.getMany();
|
||||
|
@ -351,6 +352,7 @@ export class NotesService {
|
|||
const newRevision = await this.revisionsService.createRevision(
|
||||
note,
|
||||
noteContent,
|
||||
[], // TODO Use the correct rangeAuthorships
|
||||
);
|
||||
if (newRevision !== undefined) {
|
||||
revisions.push(newRevision);
|
||||
|
@ -367,12 +369,12 @@ export class NotesService {
|
|||
*/
|
||||
async calculateUpdateUser(note: Note): Promise<User | null> {
|
||||
const lastRevision = await this.revisionsService.getLatestRevision(note);
|
||||
const edits = await lastRevision.edits;
|
||||
if (edits.length > 0) {
|
||||
const rangeAuthorships = await lastRevision.rangeAuthorships;
|
||||
if (rangeAuthorships.length > 0) {
|
||||
// Sort the last Revisions Edits by their updatedAt Date to get the latest one
|
||||
// the user of that Edit is the updateUser
|
||||
return await (
|
||||
await edits.sort(
|
||||
await rangeAuthorships.sort(
|
||||
(a, b) => b.updatedAt.getTime() - a.updatedAt.getTime(),
|
||||
)[0].author
|
||||
).user;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
@ -38,7 +38,7 @@ import {
|
|||
import { Note } from '../notes/note.entity';
|
||||
import { NotesModule } from '../notes/notes.module';
|
||||
import { Tag } from '../notes/tag.entity';
|
||||
import { Edit } from '../revisions/edit.entity';
|
||||
import { RangeAuthorship } from '../revisions/edit.entity';
|
||||
import { Revision } from '../revisions/revision.entity';
|
||||
import { Session } from '../sessions/session.entity';
|
||||
import { User } from '../users/user.entity';
|
||||
|
@ -166,7 +166,7 @@ describe('PermissionsService', () => {
|
|||
.useValue({})
|
||||
.overrideProvider(getRepositoryToken(Identity))
|
||||
.useValue({})
|
||||
.overrideProvider(getRepositoryToken(Edit))
|
||||
.overrideProvider(getRepositoryToken(RangeAuthorship))
|
||||
.useValue({})
|
||||
.overrideProvider(getRepositoryToken(Revision))
|
||||
.useValue({})
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
@ -47,6 +47,7 @@ export class RealtimeNoteService implements BeforeApplicationShutdown {
|
|||
.createAndSaveRevision(
|
||||
realtimeNote.getNote(),
|
||||
realtimeNote.getRealtimeDoc().getCurrentContent(),
|
||||
realtimeNote.getRealtimeDoc().getAbsolutePositionAuthorships(),
|
||||
realtimeNote.getRealtimeDoc().encodeStateAsUpdate(),
|
||||
)
|
||||
.catch((reason) => this.logger.error(reason));
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
@ -125,6 +125,7 @@ export class RealtimeUserStatusAdapter {
|
|||
ownUser: {
|
||||
displayName: this.realtimeUser.displayName,
|
||||
styleIndex: this.realtimeUser.styleIndex,
|
||||
username: this.realtimeUser.username,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
@ -33,7 +33,7 @@ import { NotePermission } from '../../permissions/note-permission.enum';
|
|||
import { NoteUserPermission } from '../../permissions/note-user-permission.entity';
|
||||
import { PermissionsModule } from '../../permissions/permissions.module';
|
||||
import { PermissionsService } from '../../permissions/permissions.service';
|
||||
import { Edit } from '../../revisions/edit.entity';
|
||||
import { RangeAuthorship } from '../../revisions/range-authorship.entity';
|
||||
import { Revision } from '../../revisions/revision.entity';
|
||||
import { Session } from '../../sessions/session.entity';
|
||||
import { SessionModule } from '../../sessions/session.module';
|
||||
|
@ -126,7 +126,7 @@ describe('Websocket gateway', () => {
|
|||
.useValue({})
|
||||
.overrideProvider(getRepositoryToken(Identity))
|
||||
.useValue({})
|
||||
.overrideProvider(getRepositoryToken(Edit))
|
||||
.overrideProvider(getRepositoryToken(RangeAuthorship))
|
||||
.useValue({})
|
||||
.overrideProvider(getRepositoryToken(Revision))
|
||||
.useValue({})
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { EditDto } from './edit.dto';
|
||||
import { Edit } from './edit.entity';
|
||||
|
||||
@Injectable()
|
||||
export class EditService {
|
||||
async toEditDto(edit: Edit): Promise<EditDto> {
|
||||
const authorUser = await (await edit.author).user;
|
||||
|
||||
return {
|
||||
username: authorUser ? authorUser.username : null,
|
||||
startPos: edit.startPos,
|
||||
endPos: edit.endPos,
|
||||
createdAt: edit.createdAt,
|
||||
updatedAt: edit.updatedAt,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
@ -10,7 +10,7 @@ import { IsDate, IsNumber, IsOptional, IsString, Min } from 'class-validator';
|
|||
import { UserInfoDto } from '../users/user-info.dto';
|
||||
import { BaseDto } from '../utils/base.dto.';
|
||||
|
||||
export class EditDto extends BaseDto {
|
||||
export class RangeAuthorshipDto extends BaseDto {
|
||||
/**
|
||||
* Username of the user who authored this section
|
||||
* Is `null` if the user is anonymous
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
@ -7,7 +7,6 @@ import {
|
|||
Column,
|
||||
CreateDateColumn,
|
||||
Entity,
|
||||
ManyToMany,
|
||||
ManyToOne,
|
||||
PrimaryGeneratedColumn,
|
||||
UpdateDateColumn,
|
||||
|
@ -17,18 +16,18 @@ import { Author } from '../authors/author.entity';
|
|||
import { Revision } from './revision.entity';
|
||||
|
||||
/**
|
||||
* The Edit represents a change in the content of a note by a particular {@link Author}
|
||||
* The RangeAuthorship represents a change in the content of a note by a particular {@link Author}
|
||||
*/
|
||||
@Entity()
|
||||
export class Edit {
|
||||
export class RangeAuthorship {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
/**
|
||||
* Revisions this edit appears in
|
||||
*/
|
||||
@ManyToMany((_) => Revision, (revision) => revision.edits)
|
||||
revisions: Promise<Revision[]>;
|
||||
@ManyToOne((_) => Revision, (revision) => revision.rangeAuthorships)
|
||||
revision: Promise<Revision>;
|
||||
|
||||
/**
|
||||
* Author that created the change
|
||||
|
@ -55,9 +54,8 @@ export class Edit {
|
|||
author: Author,
|
||||
startPos: number,
|
||||
endPos: number,
|
||||
): Omit<Edit, 'id' | 'createdAt' | 'updatedAt'> {
|
||||
const newEdit = new Edit();
|
||||
newEdit.revisions = Promise.resolve([]);
|
||||
): Omit<RangeAuthorship, 'id' | 'createdAt' | 'updatedAt' | 'revision'> {
|
||||
const newEdit = new RangeAuthorship();
|
||||
newEdit.author = Promise.resolve(author);
|
||||
newEdit.startPos = startPos;
|
||||
newEdit.endPos = endPos;
|
50
backend/src/revisions/range-authorship.service.ts
Normal file
50
backend/src/revisions/range-authorship.service.ts
Normal file
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { AbsolutePositionAuthorship } from '@hedgedoc/commons';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { Author } from '../authors/author.entity';
|
||||
import { RangeAuthorshipDto } from './range-authorship.dto';
|
||||
import { RangeAuthorship } from './range-authorship.entity';
|
||||
|
||||
@Injectable()
|
||||
export class RangeAuthorshipService {
|
||||
createRangeAuthorshipsFromAbsolutePositions(
|
||||
absolutePositions: AbsolutePositionAuthorship[],
|
||||
noteLength: number,
|
||||
): RangeAuthorship[] {
|
||||
return absolutePositions
|
||||
.sort(([positionA], [positionB]) => {
|
||||
return positionB - positionA;
|
||||
})
|
||||
.reduce<RangeAuthorship[]>((authorships, [startPosition, _]) => {
|
||||
const author = Author.create(1) as Author; // ToDo: use the real author here
|
||||
const endPosition = authorships[0]?.startPos - 1 ?? noteLength;
|
||||
authorships.unshift(
|
||||
RangeAuthorship.create(
|
||||
author,
|
||||
startPosition,
|
||||
endPosition,
|
||||
) as RangeAuthorship,
|
||||
);
|
||||
return authorships;
|
||||
}, []);
|
||||
}
|
||||
|
||||
async toRangeAuthorshipDto(
|
||||
rangeAuthorship: RangeAuthorship,
|
||||
): Promise<RangeAuthorshipDto> {
|
||||
const authorUser = await (await rangeAuthorship.author).user;
|
||||
|
||||
return {
|
||||
username: authorUser ? authorUser.username : null,
|
||||
startPos: rangeAuthorship.startPos,
|
||||
endPos: rangeAuthorship.endPos,
|
||||
createdAt: rangeAuthorship.createdAt,
|
||||
updatedAt: rangeAuthorship.updatedAt,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
@ -7,7 +7,7 @@ import { ApiProperty } from '@nestjs/swagger';
|
|||
import { Type } from 'class-transformer';
|
||||
import { IsString, ValidateNested } from 'class-validator';
|
||||
|
||||
import { EditDto } from './edit.dto';
|
||||
import { RangeAuthorshipDto } from './range-authorship.dto';
|
||||
import { RevisionMetadataDto } from './revision-metadata.dto';
|
||||
|
||||
export class RevisionDto extends RevisionMetadataDto {
|
||||
|
@ -27,10 +27,10 @@ export class RevisionDto extends RevisionMetadataDto {
|
|||
patch: string;
|
||||
|
||||
/**
|
||||
* All edit objects which are used in the revision.
|
||||
* All range authorship objects which are used in the revision.
|
||||
*/
|
||||
@Type(() => EditDto)
|
||||
@Type(() => RangeAuthorshipDto)
|
||||
@ValidateNested({ each: true })
|
||||
@ApiProperty({ isArray: true, type: EditDto })
|
||||
edits: EditDto[];
|
||||
@ApiProperty({ isArray: true, type: RangeAuthorshipDto })
|
||||
rangeAuthorships: RangeAuthorshipDto[];
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
@ -10,12 +10,13 @@ import {
|
|||
JoinTable,
|
||||
ManyToMany,
|
||||
ManyToOne,
|
||||
OneToMany,
|
||||
PrimaryGeneratedColumn,
|
||||
} from 'typeorm';
|
||||
|
||||
import { Note } from '../notes/note.entity';
|
||||
import { Tag } from '../notes/tag.entity';
|
||||
import { Edit } from './edit.entity';
|
||||
import { RangeAuthorship } from './range-authorship.entity';
|
||||
|
||||
/**
|
||||
* The state of a note at a particular point in time,
|
||||
|
@ -84,9 +85,12 @@ export class Revision {
|
|||
/**
|
||||
* All edit objects which are used in the revision.
|
||||
*/
|
||||
@ManyToMany((_) => Edit, (edit) => edit.revisions)
|
||||
@JoinTable()
|
||||
edits: Promise<Edit[]>;
|
||||
@OneToMany(
|
||||
(_) => RangeAuthorship,
|
||||
(rangeAuthorship) => rangeAuthorship.revision,
|
||||
{ onDelete: 'CASCADE' },
|
||||
)
|
||||
rangeAuthorships: Promise<RangeAuthorship[]>;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
private constructor() {}
|
||||
|
@ -99,6 +103,7 @@ export class Revision {
|
|||
title: string,
|
||||
description: string,
|
||||
tags: Tag[],
|
||||
rangeAuthorships: RangeAuthorship[],
|
||||
): Omit<Revision, 'id' | 'createdAt'> {
|
||||
const newRevision = new Revision();
|
||||
newRevision.patch = patch;
|
||||
|
@ -108,7 +113,7 @@ export class Revision {
|
|||
newRevision.description = description;
|
||||
newRevision.tags = Promise.resolve(tags);
|
||||
newRevision.note = Promise.resolve(note);
|
||||
newRevision.edits = Promise.resolve([]);
|
||||
newRevision.rangeAuthorships = Promise.resolve(rangeAuthorships);
|
||||
newRevision.yjsStateVector = yjsStateVector ?? null;
|
||||
return newRevision;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
@ -9,19 +9,19 @@ import { TypeOrmModule } from '@nestjs/typeorm';
|
|||
|
||||
import { AuthorsModule } from '../authors/authors.module';
|
||||
import { LoggerModule } from '../logger/logger.module';
|
||||
import { Edit } from './edit.entity';
|
||||
import { EditService } from './edit.service';
|
||||
import { RangeAuthorship } from './range-authorship.entity';
|
||||
import { RangeAuthorshipService } from './range-authorship.service';
|
||||
import { Revision } from './revision.entity';
|
||||
import { RevisionsService } from './revisions.service';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([Revision, Edit]),
|
||||
TypeOrmModule.forFeature([Revision, RangeAuthorship]),
|
||||
LoggerModule,
|
||||
ConfigModule,
|
||||
AuthorsModule,
|
||||
],
|
||||
providers: [RevisionsService, EditService],
|
||||
exports: [RevisionsService, EditService],
|
||||
providers: [RevisionsService, RangeAuthorshipService],
|
||||
exports: [RevisionsService, RangeAuthorshipService],
|
||||
})
|
||||
export class RevisionsModule {}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
@ -29,8 +29,8 @@ import { NoteGroupPermission } from '../permissions/note-group-permission.entity
|
|||
import { NoteUserPermission } from '../permissions/note-user-permission.entity';
|
||||
import { Session } from '../sessions/session.entity';
|
||||
import { User } from '../users/user.entity';
|
||||
import { Edit } from './edit.entity';
|
||||
import { EditService } from './edit.service';
|
||||
import { RangeAuthorship } from './edit.entity';
|
||||
import { RangeAuthorshipService } from './range-authorship.service';
|
||||
import { Revision } from './revision.entity';
|
||||
import { RevisionsService } from './revisions.service';
|
||||
|
||||
|
@ -42,7 +42,7 @@ describe('RevisionsService', () => {
|
|||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
RevisionsService,
|
||||
EditService,
|
||||
RangeAuthorshipService,
|
||||
{
|
||||
provide: getRepositoryToken(Revision),
|
||||
useClass: Repository,
|
||||
|
@ -63,7 +63,7 @@ describe('RevisionsService', () => {
|
|||
EventEmitterModule.forRoot(eventModuleConfig),
|
||||
],
|
||||
})
|
||||
.overrideProvider(getRepositoryToken(Edit))
|
||||
.overrideProvider(getRepositoryToken(RangeAuthorship))
|
||||
.useValue({})
|
||||
.overrideProvider(getRepositoryToken(User))
|
||||
.useValue({})
|
||||
|
@ -183,12 +183,14 @@ describe('RevisionsService', () => {
|
|||
author.user = Promise.resolve(user);
|
||||
const anonAuthor = Author.create(123) as Author;
|
||||
const anonAuthor2 = Author.create(123) as Author;
|
||||
const edits = [Edit.create(author, 12, 15) as Edit];
|
||||
edits.push(Edit.create(author, 16, 18) as Edit);
|
||||
edits.push(Edit.create(author, 29, 20) as Edit);
|
||||
edits.push(Edit.create(anonAuthor, 29, 20) as Edit);
|
||||
edits.push(Edit.create(anonAuthor, 29, 20) as Edit);
|
||||
edits.push(Edit.create(anonAuthor2, 29, 20) as Edit);
|
||||
const edits = [RangeAuthorship.create(author, 12, 15) as RangeAuthorship];
|
||||
edits.push(RangeAuthorship.create(author, 16, 18) as RangeAuthorship);
|
||||
edits.push(RangeAuthorship.create(author, 29, 20) as RangeAuthorship);
|
||||
edits.push(RangeAuthorship.create(anonAuthor, 29, 20) as RangeAuthorship);
|
||||
edits.push(RangeAuthorship.create(anonAuthor, 29, 20) as RangeAuthorship);
|
||||
edits.push(
|
||||
RangeAuthorship.create(anonAuthor2, 29, 20) as RangeAuthorship,
|
||||
);
|
||||
const revision = Mock.of<Revision>({});
|
||||
revision.edits = Promise.resolve(edits);
|
||||
|
||||
|
@ -210,7 +212,7 @@ describe('RevisionsService', () => {
|
|||
description: 'mockDescription',
|
||||
patch: 'mockPatch',
|
||||
edits: Promise.resolve([
|
||||
Mock.of<Edit>({
|
||||
Mock.of<RangeAuthorship>({
|
||||
endPos: 93,
|
||||
startPos: 34,
|
||||
createdAt: new Date('2020-03-04T20:12:00.000Z'),
|
||||
|
@ -259,7 +261,7 @@ describe('RevisionsService', () => {
|
|||
description: 'mockDescription',
|
||||
patch: 'mockPatch',
|
||||
edits: Promise.resolve([
|
||||
Mock.of<Edit>({
|
||||
Mock.of<RangeAuthorship>({
|
||||
endPos: 93,
|
||||
startPos: 34,
|
||||
createdAt: new Date('2020-03-04T22:32:00.000Z'),
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import type { AbsolutePositionAuthorship } from '@hedgedoc/commons';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { createPatch } from 'diff';
|
||||
|
@ -12,7 +13,7 @@ import { NotInDBError } from '../errors/errors';
|
|||
import { ConsoleLoggerService } from '../logger/console-logger.service';
|
||||
import { Note } from '../notes/note.entity';
|
||||
import { Tag } from '../notes/tag.entity';
|
||||
import { EditService } from './edit.service';
|
||||
import { RangeAuthorshipService } from './range-authorship.service';
|
||||
import { RevisionMetadataDto } from './revision-metadata.dto';
|
||||
import { RevisionDto } from './revision.dto';
|
||||
import { Revision } from './revision.entity';
|
||||
|
@ -29,7 +30,7 @@ export class RevisionsService {
|
|||
private readonly logger: ConsoleLoggerService,
|
||||
@InjectRepository(Revision)
|
||||
private revisionRepository: Repository<Revision>,
|
||||
private editService: EditService,
|
||||
private rangeAuthorshipService: RangeAuthorshipService,
|
||||
) {
|
||||
this.logger.setContext(RevisionsService.name);
|
||||
}
|
||||
|
@ -97,7 +98,9 @@ export class RevisionsService {
|
|||
async getRevisionUserInfo(revision: Revision): Promise<RevisionUserInfo> {
|
||||
// get a deduplicated list of all authors
|
||||
let authors = await Promise.all(
|
||||
(await revision.edits).map(async (edit) => await edit.author),
|
||||
(await revision.rangeAuthorships).map(
|
||||
async (rangeAuthorship) => await rangeAuthorship.author,
|
||||
),
|
||||
);
|
||||
authors = [...new Set(authors)]; // remove duplicates with Set
|
||||
|
||||
|
@ -142,9 +145,12 @@ export class RevisionsService {
|
|||
authorUsernames: revisionUserInfo.usernames,
|
||||
anonymousAuthorCount: revisionUserInfo.anonymousUserCount,
|
||||
patch: revision.patch,
|
||||
edits: await Promise.all(
|
||||
(await revision.edits).map(
|
||||
async (edit) => await this.editService.toEditDto(edit),
|
||||
rangeAuthorships: await Promise.all(
|
||||
(await revision.rangeAuthorships).map(
|
||||
async (rangeAuthorship) =>
|
||||
await this.rangeAuthorshipService.toRangeAuthorshipDto(
|
||||
rangeAuthorship,
|
||||
),
|
||||
),
|
||||
),
|
||||
};
|
||||
|
@ -158,12 +164,14 @@ export class RevisionsService {
|
|||
* @param note The note for which the revision should be created
|
||||
* @param newContent The new note content
|
||||
* @param yjsStateVector The yjs state vector that describes the new content
|
||||
* @param absolutePositionAuthorships The absolute positions for authorship marks
|
||||
* @return {Revision} the created revision
|
||||
* @return {undefined} if the revision couldn't be created because e.g. the content hasn't changed
|
||||
*/
|
||||
async createRevision(
|
||||
note: Note,
|
||||
newContent: string,
|
||||
absolutePositionAuthorships: AbsolutePositionAuthorship[],
|
||||
yjsStateVector?: number[],
|
||||
): Promise<Revision | undefined> {
|
||||
const latestRevision =
|
||||
|
@ -186,6 +194,12 @@ export class RevisionsService {
|
|||
return entity;
|
||||
});
|
||||
|
||||
const rangeAuthorships =
|
||||
this.rangeAuthorshipService.createRangeAuthorshipsFromAbsolutePositions(
|
||||
absolutePositionAuthorships,
|
||||
newContent.length,
|
||||
);
|
||||
|
||||
return Revision.create(
|
||||
newContent,
|
||||
patch,
|
||||
|
@ -194,6 +208,7 @@ export class RevisionsService {
|
|||
title,
|
||||
description,
|
||||
tagEntities,
|
||||
rangeAuthorships,
|
||||
) as Revision;
|
||||
}
|
||||
|
||||
|
@ -203,16 +218,19 @@ export class RevisionsService {
|
|||
* @async
|
||||
* @param note The note for which the revision should be created
|
||||
* @param newContent The new note content
|
||||
* @param absolutePositionAuthorships The absolute positions for authorship marks
|
||||
* @param yjsStateVector The yjs state vector that describes the new content
|
||||
*/
|
||||
async createAndSaveRevision(
|
||||
note: Note,
|
||||
newContent: string,
|
||||
absolutePositionAuthorships: AbsolutePositionAuthorship[],
|
||||
yjsStateVector?: number[],
|
||||
): Promise<void> {
|
||||
const revision = await this.createRevision(
|
||||
note,
|
||||
newContent,
|
||||
absolutePositionAuthorships,
|
||||
yjsStateVector,
|
||||
);
|
||||
if (revision) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
@ -17,7 +17,7 @@ import { Note } from './notes/note.entity';
|
|||
import { Tag } from './notes/tag.entity';
|
||||
import { NoteGroupPermission } from './permissions/note-group-permission.entity';
|
||||
import { NoteUserPermission } from './permissions/note-user-permission.entity';
|
||||
import { Edit } from './revisions/edit.entity';
|
||||
import { RangeAuthorship } from './revisions/range-authorship.entity';
|
||||
import { Revision } from './revisions/revision.entity';
|
||||
import { Session } from './sessions/session.entity';
|
||||
import { User } from './users/user.entity';
|
||||
|
@ -33,7 +33,7 @@ const dataSource = new DataSource({
|
|||
User,
|
||||
Note,
|
||||
Revision,
|
||||
Edit,
|
||||
RangeAuthorship,
|
||||
NoteGroupPermission,
|
||||
NoteUserPermission,
|
||||
Group,
|
||||
|
@ -81,9 +81,14 @@ dataSource
|
|||
'Test note',
|
||||
'',
|
||||
[],
|
||||
[],
|
||||
) as Revision;
|
||||
const edit = Edit.create(author, 1, 42) as Edit;
|
||||
revision.edits = Promise.resolve([edit]);
|
||||
const rangeAuthorship = RangeAuthorship.create(
|
||||
author,
|
||||
1,
|
||||
42,
|
||||
) as RangeAuthorship;
|
||||
revision.rangeAuthorships = Promise.resolve([rangeAuthorship]);
|
||||
notes[i].revisions = Promise.all([revision]);
|
||||
notes[i].userPermissions = Promise.resolve([]);
|
||||
notes[i].groupPermissions = Promise.resolve([]);
|
||||
|
@ -92,7 +97,7 @@ dataSource
|
|||
notes[i],
|
||||
user,
|
||||
revision,
|
||||
edit,
|
||||
rangeAuthorship,
|
||||
author,
|
||||
identity,
|
||||
]);
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { RealtimeUser, RemoteCursor } from './realtime-user.js'
|
||||
import {
|
||||
RealtimeUser,
|
||||
RemoteCursor,
|
||||
ShortRealtimeUser
|
||||
} from './realtime-user.js'
|
||||
|
||||
export enum MessageType {
|
||||
NOTE_CONTENT_STATE_REQUEST = 'NOTE_CONTENT_STATE_REQUEST',
|
||||
|
@ -33,10 +37,7 @@ export interface MessagePayloads {
|
|||
[MessageType.NOTE_CONTENT_UPDATE]: number[]
|
||||
[MessageType.REALTIME_USER_STATE_SET]: {
|
||||
users: RealtimeUser[]
|
||||
ownUser: {
|
||||
displayName: string
|
||||
styleIndex: number
|
||||
}
|
||||
ownUser: ShortRealtimeUser
|
||||
}
|
||||
[MessageType.REALTIME_USER_SINGLE_UPDATE]: RemoteCursor
|
||||
|
||||
|
|
|
@ -1,14 +1,11 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export interface RealtimeUser {
|
||||
displayName: string
|
||||
username: string | null
|
||||
export interface RealtimeUser extends ShortRealtimeUser {
|
||||
active: boolean
|
||||
styleIndex: number
|
||||
cursor: RemoteCursor | null
|
||||
}
|
||||
|
||||
|
@ -16,3 +13,9 @@ export interface RemoteCursor {
|
|||
from: number
|
||||
to?: number
|
||||
}
|
||||
|
||||
export interface ShortRealtimeUser {
|
||||
displayName: string
|
||||
styleIndex: number
|
||||
username: string | null
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
@ -7,3 +7,4 @@ export * from './y-doc-sync-client-adapter.js'
|
|||
export * from './y-doc-sync-server-adapter.js'
|
||||
export * from './y-doc-sync-adapter.js'
|
||||
export * from './realtime-doc.js'
|
||||
export * from './position-authorship.js'
|
||||
|
|
14
commons/src/y-doc-sync/position-authorship.ts
Normal file
14
commons/src/y-doc-sync/position-authorship.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { ShortRealtimeUser } from '../message-transporters/index.js'
|
||||
import { AbsolutePosition, RelativePosition } from 'yjs'
|
||||
|
||||
export type RelativePositionAuthorship = [RelativePosition, ShortRealtimeUser]
|
||||
export type OptionalAbsolutePositionAuthorship = [
|
||||
AbsolutePosition | null,
|
||||
ShortRealtimeUser
|
||||
]
|
||||
export type AbsolutePositionAuthorship = [number, ShortRealtimeUser]
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
@ -10,10 +10,21 @@ import {
|
|||
Doc,
|
||||
encodeStateAsUpdate,
|
||||
encodeStateVector,
|
||||
Text as YText
|
||||
Text as YText,
|
||||
Array as YArray,
|
||||
createAbsolutePositionFromRelativePosition,
|
||||
createRelativePositionFromTypeIndex,
|
||||
AbsolutePosition
|
||||
} from 'yjs'
|
||||
import {
|
||||
AbsolutePositionAuthorship,
|
||||
OptionalAbsolutePositionAuthorship,
|
||||
RelativePositionAuthorship
|
||||
} from './position-authorship.js'
|
||||
import { RealtimeUser } from '../message-transporters/index.js'
|
||||
|
||||
const MARKDOWN_CONTENT_CHANNEL_NAME = 'markdownContent'
|
||||
const RELATIVE_POSITION_AUTHORSHIPS_CHANNEL_NAME = 'relativePositionAuthorships'
|
||||
|
||||
export interface RealtimeDocEvents extends EventMap {
|
||||
update: (update: number[], origin: unknown) => void
|
||||
|
@ -36,13 +47,33 @@ export class RealtimeDoc extends EventEmitter2<RealtimeDocEvents> {
|
|||
*
|
||||
* @param initialTextContent the initial text content of the {@link Doc YDoc}
|
||||
* @param initialYjsState the initial yjs state. If provided this will be used instead of the text content
|
||||
* @param initialAbsolutePositionAuthorships the initial realtime range authorships
|
||||
*/
|
||||
constructor(initialTextContent?: string, initialYjsState?: number[]) {
|
||||
constructor(
|
||||
initialTextContent?: string,
|
||||
initialYjsState?: number[],
|
||||
initialAbsolutePositionAuthorships?: AbsolutePositionAuthorship[]
|
||||
) {
|
||||
super()
|
||||
if (initialYjsState) {
|
||||
this.applyUpdate(initialYjsState, this)
|
||||
} else if (initialTextContent) {
|
||||
this.getMarkdownContentChannel().insert(0, initialTextContent)
|
||||
} else {
|
||||
if (initialTextContent) {
|
||||
this.getMarkdownContentChannel().insert(0, initialTextContent)
|
||||
}
|
||||
|
||||
if (initialAbsolutePositionAuthorships) {
|
||||
this.getRelativePositionAuthorshipsChannel().insert(
|
||||
0,
|
||||
initialAbsolutePositionAuthorships.map(([index, user]) => [
|
||||
createRelativePositionFromTypeIndex(
|
||||
this.getMarkdownContentChannel(),
|
||||
index
|
||||
),
|
||||
user
|
||||
])
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
this.docUpdateListener = (update, origin) => {
|
||||
|
@ -60,6 +91,15 @@ export class RealtimeDoc extends EventEmitter2<RealtimeDocEvents> {
|
|||
return this.doc.getText(MARKDOWN_CONTENT_CHANNEL_NAME)
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the {@link YMap map channel} that contains the realtime range authorships.
|
||||
*
|
||||
* @return The realtime range authorships channel
|
||||
*/
|
||||
public getRelativePositionAuthorshipsChannel(): YArray<RelativePositionAuthorship> {
|
||||
return this.doc.getArray(RELATIVE_POSITION_AUTHORSHIPS_CHANNEL_NAME)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current content of the note as it's currently edited in realtime.
|
||||
*
|
||||
|
@ -72,6 +112,25 @@ export class RealtimeDoc extends EventEmitter2<RealtimeDocEvents> {
|
|||
return this.getMarkdownContentChannel().toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the current authorship positions for the realtime.
|
||||
*
|
||||
* Please be aware that the return of this method may be very quickly outdated.
|
||||
*
|
||||
* @return An array of .
|
||||
*/
|
||||
public getAbsolutePositionAuthorships(): AbsolutePositionAuthorship[] {
|
||||
return this.getRelativePositionAuthorshipsChannel()
|
||||
.map<OptionalAbsolutePositionAuthorship>(([relativePosition, user]) => [
|
||||
createAbsolutePositionFromRelativePosition(relativePosition, this.doc),
|
||||
user
|
||||
])
|
||||
.filter(
|
||||
<T, S>(value: [T | null, S]): value is [T, S] => value[0] !== null
|
||||
)
|
||||
.map(([absolutePosition, user]) => [absolutePosition.index, user])
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the current state of the doc as update so it can be applied to other y-docs.
|
||||
*
|
||||
|
|
Loading…
Reference in a new issue