Implementaiton of h1, h2, h3 and link button on publication editor.

This commit is contained in:
Florian THIERRY
2024-08-30 18:01:29 +02:00
parent 51af25666d
commit b84ba15f4c
3 changed files with 132 additions and 36 deletions

View File

@@ -29,19 +29,19 @@
</div>
<div class="actions">
<button type="button" disabled matTooltip="Click to insert a title 1 section">
<button type="button" matTooltip="Click to insert a title 1 section" (click)="insertTitle(1)">
H1
</button>
<button type="button" disabled matTooltip="Click to insert a title 2 section">
<button type="button" matTooltip="Click to insert a title 2 section" (click)="insertTitle(2)">
H2
</button>
<button type="button" disabled matTooltip="Click to insert a title 1 section">
<button type="button" matTooltip="Click to insert a title 1 section" (click)="insertTitle(3)">
H3
</button>
<button type="button" disabled matTooltip="Click to insert a picture">
<mat-icon>image</mat-icon>
</button>
<button type="button" disabled matTooltip="Click to insert a link">
<button type="button" matTooltip="Click to insert a link" (click)="insertLink()">
<mat-icon>link</mat-icon>
</button>
<button type="button" disabled matTooltip="Click to insert a code block">
@@ -53,7 +53,14 @@
</div>
<mat-form-field class="example-form-field">
<mat-label>Content</mat-label>
<textarea matInput formControlName="text" class="text-input"></textarea>
<textarea
#textArea
matInput
formControlName="text"
class="text-input"
(keyup)="updateCursorPosition($event)"
(click)="updateCursorPosition($event)">
</textarea>
</mat-form-field>
</div>
</mat-tab>

View File

@@ -59,7 +59,6 @@ export class PublicationEditionComponent implements OnInit, OnDestroy {
['title', 'description', 'text'].forEach(fieldName => {
const fieldSubscription = this.publicationEditionForm.controls[fieldName].valueChanges
.pipe(
debounceTime(300),
map(value => value?.length ? value as string : '')
)
.subscribe(fieldValue => {
@@ -76,18 +75,17 @@ export class PublicationEditionComponent implements OnInit, OnDestroy {
default:
break;
}
this.publicationEditionService
});
this.subscriptions.push(fieldSubscription);
});
const publicationSubscription = this.publicationEditionService.publication$.subscribe(publication => {
this.publication = publication;
this.publicationEditionForm.controls['title'].setValue(publication.title, { emitEvent: false });
this.publicationEditionForm.controls['description'].setValue(publication.description, { emitEvent: false });
this.publicationEditionForm.controls['text'].setValue(publication.text, { emitEvent: false });
this.publicationEditionForm.controls['illustrationId'].setValue(publication.illustrationId, { emitEvent: false });
this.publicationEditionForm.controls['categoryId'].setValue(publication.categoryId, { emitEvent: false });
const publicationSubscription = this.publicationEditionService.state$.subscribe(state => {
this.publication = state.publication;
this.publicationEditionForm.controls['title'].setValue(this.publication.title, { emitEvent: false });
this.publicationEditionForm.controls['description'].setValue(this.publication.description, { emitEvent: false });
this.publicationEditionForm.controls['text'].setValue(this.publication.text, { emitEvent: false });
this.publicationEditionForm.controls['illustrationId'].setValue(this.publication.illustrationId, { emitEvent: false });
this.publicationEditionForm.controls['categoryId'].setValue(this.publication.categoryId, { emitEvent: false });
});
this.subscriptions.push(publicationSubscription);
@@ -102,6 +100,14 @@ export class PublicationEditionComponent implements OnInit, OnDestroy {
this.location.back();
}
insertTitle(titleNumber: number): void {
this.publicationEditionService.insertTitle(titleNumber);
}
insertLink(): void {
this.publicationEditionService.insertLink();
}
save(): void {
this.publicationEditionService.save();
}
@@ -109,4 +115,18 @@ export class PublicationEditionComponent implements OnInit, OnDestroy {
displayPictureSectionDialog(): void {
this.publicationEditionService.displayPictureSectionDialog();
}
updateCursorPosition(event: KeyboardEvent | MouseEvent): void {
if (event.target) {
const textarea = event.target as HTMLTextAreaElement;
const positionStart = textarea.selectionStart;
const positionEnd = textarea.selectionEnd;
const selectedCharacterCount = positionEnd - positionStart;
console.log(`cursor position updated: [${positionStart}, ${positionEnd}] (${selectedCharacterCount})`);
this.publicationEditionService.editCursorPosition(positionStart, positionEnd);
}
}
}

View File

@@ -9,6 +9,22 @@ import { copy } from '../../core/utils/ObjectUtils';
import { MatDialog } from "@angular/material/dialog";
import { PictureSelectionDialog } from "./picture-selection-dialog/picture-selection-dialog.component";
export class CursorPosition {
start: number;
end: number;
selectedCharacters: number;
constructor(start: number, end: number) {
this.start = start;
this.end = end;
this.selectedCharacters = end - start;
}
}
export interface PublicationEditionState {
publication: Publication;
cursorPosition: CursorPosition;
}
const DEFAULT_PUBLICATION: Publication = {
id: '',
@@ -27,6 +43,13 @@ const DEFAULT_PUBLICATION: Publication = {
}
};
const DEFAULT_CURSOR_POSITION = new CursorPosition(0, 0);
const DEFAULT_STATE: PublicationEditionState = {
publication: DEFAULT_PUBLICATION,
cursorPosition: DEFAULT_CURSOR_POSITION
};
@Injectable()
export class PublicationEditionService implements OnDestroy {
private readonly activatedRoute = inject(ActivatedRoute);
@@ -37,7 +60,7 @@ export class PublicationEditionService implements OnDestroy {
private readonly dialog = inject(MatDialog);
private isLoadingSubject = new BehaviorSubject<boolean>(false);
private publicationSubject = new BehaviorSubject<Publication>(copy(DEFAULT_PUBLICATION));
private stateSubject = new BehaviorSubject<PublicationEditionState>(copy(DEFAULT_STATE));
private subscriptions: Subscription[] = [];
private isSavingSubject = new BehaviorSubject<boolean>(false);
@@ -45,12 +68,12 @@ export class PublicationEditionService implements OnDestroy {
this.subscriptions.forEach(subscription => subscription.unsubscribe());
}
private get _publication(): Publication {
return this.publicationSubject.value;
private get _state(): PublicationEditionState {
return this.stateSubject.value;
}
private _save(publication: Publication): void {
this.publicationSubject.next(publication);
private _save(state: PublicationEditionState): void {
this.stateSubject.next(state);
}
get isLoading$(): Observable<boolean> {
@@ -61,8 +84,8 @@ export class PublicationEditionService implements OnDestroy {
return this.isSavingSubject.asObservable();
}
get publication$(): Observable<Publication> {
return this.publicationSubject.asObservable();
get state$(): Observable<PublicationEditionState> {
return this.stateSubject.asObservable();
}
loadPublication(): void {
@@ -76,7 +99,9 @@ export class PublicationEditionService implements OnDestroy {
} else {
this.publicationRestService.getById(publicationId)
.then(publication => {
this.publicationSubject.next(publication);
const state = this._state;
state.publication = publication;
this.stateSubject.next(state);
})
.catch(error => {
const errorMessage = 'A technical error occurred while loading publication data.';
@@ -89,27 +114,27 @@ export class PublicationEditionService implements OnDestroy {
}
editTitle(title: string): void {
const publication = this._publication;
publication.title = title;
this._save(publication);
const state = this._state;
state.publication.title = title;
this._save(state);
}
editDescription(description: string): void {
const publication = this._publication;
publication.description = description;
this._save(publication);
const state = this._state;
state.publication.description = description;
this._save(state);
}
editText(text: string): void {
const publication = this._publication;
publication.text = text;
this._save(publication);
const state = this._state;
state.publication.text = text;
this._save(state);
}
editIllustrationId(pictureId: string): void {
const publication = this._publication;
publication.illustrationId = pictureId
this._save(publication);
const state = this._state;
state.publication.illustrationId = pictureId
this._save(state);
}
displayPictureSectionDialog(): void {
@@ -124,11 +149,55 @@ export class PublicationEditionService implements OnDestroy {
this.subscriptions.push(afterDialogCloseSubscription);
}
editCursorPosition(positionStart: number, positionEnd: number): void {
const state = this._state;
state.cursorPosition.start = positionStart;
state.cursorPosition.end = positionEnd;
this._save(state);
}
insertTitle(titleNumber: number): void {
if (titleNumber >= 1 && titleNumber <= 3) {
const state = this._state;
const publication = state.publication;
const publicationTextLeftPart = publication.text.substring(0, state.cursorPosition.start);
const publicationTextMiddlePart = publication.text.substring(state.cursorPosition.start, state.cursorPosition.end);
const publicationTextRightPart = publication.text.substring(state.cursorPosition.end);
const textWithTags = `${publicationTextLeftPart}[h${titleNumber}]${publicationTextMiddlePart}[/h${titleNumber}]${publicationTextRightPart}`;
publication.text = textWithTags;
this._save(state);
} else {
console.error(`Bad value for parameter of function 'insertTitle': '${titleNumber}'.`);
}
}
insertLink(): void {
const state = this._state;
const publication = state.publication;
const publicationTextLeftPart = publication.text.substring(0, state.cursorPosition.start);
const publicationTextMiddlePart = publication.text.substring(state.cursorPosition.start, state.cursorPosition.end);
const publicationTextRightPart = publication.text.substring(state.cursorPosition.end);
const textWithTags = `${publicationTextLeftPart}[link href="" txt="${publicationTextMiddlePart}" /]${publicationTextRightPart}`;
publication.text = textWithTags;
this._save(state);
}
save(): void {
const publication = this._publication;
const state = this._state;
this.isSavingSubject.next(true);
this.publicationRestService.update(publication)
this.publicationRestService.update(state.publication)
.then(() => {
this.snackBar.open('Publication updated succesfully!', 'Close', { duration: 5000 });
this.router.navigate(['/home']);