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>
<div class="actions"> <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 H1
</button> </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 H2
</button> </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 H3
</button> </button>
<button type="button" disabled matTooltip="Click to insert a picture"> <button type="button" disabled matTooltip="Click to insert a picture">
<mat-icon>image</mat-icon> <mat-icon>image</mat-icon>
</button> </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> <mat-icon>link</mat-icon>
</button> </button>
<button type="button" disabled matTooltip="Click to insert a code block"> <button type="button" disabled matTooltip="Click to insert a code block">
@@ -53,7 +53,14 @@
</div> </div>
<mat-form-field class="example-form-field"> <mat-form-field class="example-form-field">
<mat-label>Content</mat-label> <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> </mat-form-field>
</div> </div>
</mat-tab> </mat-tab>

View File

@@ -59,7 +59,6 @@ export class PublicationEditionComponent implements OnInit, OnDestroy {
['title', 'description', 'text'].forEach(fieldName => { ['title', 'description', 'text'].forEach(fieldName => {
const fieldSubscription = this.publicationEditionForm.controls[fieldName].valueChanges const fieldSubscription = this.publicationEditionForm.controls[fieldName].valueChanges
.pipe( .pipe(
debounceTime(300),
map(value => value?.length ? value as string : '') map(value => value?.length ? value as string : '')
) )
.subscribe(fieldValue => { .subscribe(fieldValue => {
@@ -76,18 +75,17 @@ export class PublicationEditionComponent implements OnInit, OnDestroy {
default: default:
break; break;
} }
this.publicationEditionService
}); });
this.subscriptions.push(fieldSubscription); this.subscriptions.push(fieldSubscription);
}); });
const publicationSubscription = this.publicationEditionService.publication$.subscribe(publication => { const publicationSubscription = this.publicationEditionService.state$.subscribe(state => {
this.publication = publication; this.publication = state.publication;
this.publicationEditionForm.controls['title'].setValue(publication.title, { emitEvent: false }); this.publicationEditionForm.controls['title'].setValue(this.publication.title, { emitEvent: false });
this.publicationEditionForm.controls['description'].setValue(publication.description, { emitEvent: false }); this.publicationEditionForm.controls['description'].setValue(this.publication.description, { emitEvent: false });
this.publicationEditionForm.controls['text'].setValue(publication.text, { emitEvent: false }); this.publicationEditionForm.controls['text'].setValue(this.publication.text, { emitEvent: false });
this.publicationEditionForm.controls['illustrationId'].setValue(publication.illustrationId, { emitEvent: false }); this.publicationEditionForm.controls['illustrationId'].setValue(this.publication.illustrationId, { emitEvent: false });
this.publicationEditionForm.controls['categoryId'].setValue(publication.categoryId, { emitEvent: false }); this.publicationEditionForm.controls['categoryId'].setValue(this.publication.categoryId, { emitEvent: false });
}); });
this.subscriptions.push(publicationSubscription); this.subscriptions.push(publicationSubscription);
@@ -102,6 +100,14 @@ export class PublicationEditionComponent implements OnInit, OnDestroy {
this.location.back(); this.location.back();
} }
insertTitle(titleNumber: number): void {
this.publicationEditionService.insertTitle(titleNumber);
}
insertLink(): void {
this.publicationEditionService.insertLink();
}
save(): void { save(): void {
this.publicationEditionService.save(); this.publicationEditionService.save();
} }
@@ -109,4 +115,18 @@ export class PublicationEditionComponent implements OnInit, OnDestroy {
displayPictureSectionDialog(): void { displayPictureSectionDialog(): void {
this.publicationEditionService.displayPictureSectionDialog(); 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 { MatDialog } from "@angular/material/dialog";
import { PictureSelectionDialog } from "./picture-selection-dialog/picture-selection-dialog.component"; 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 = { const DEFAULT_PUBLICATION: Publication = {
id: '', 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() @Injectable()
export class PublicationEditionService implements OnDestroy { export class PublicationEditionService implements OnDestroy {
private readonly activatedRoute = inject(ActivatedRoute); private readonly activatedRoute = inject(ActivatedRoute);
@@ -37,7 +60,7 @@ export class PublicationEditionService implements OnDestroy {
private readonly dialog = inject(MatDialog); private readonly dialog = inject(MatDialog);
private isLoadingSubject = new BehaviorSubject<boolean>(false); 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 subscriptions: Subscription[] = [];
private isSavingSubject = new BehaviorSubject<boolean>(false); private isSavingSubject = new BehaviorSubject<boolean>(false);
@@ -45,12 +68,12 @@ export class PublicationEditionService implements OnDestroy {
this.subscriptions.forEach(subscription => subscription.unsubscribe()); this.subscriptions.forEach(subscription => subscription.unsubscribe());
} }
private get _publication(): Publication { private get _state(): PublicationEditionState {
return this.publicationSubject.value; return this.stateSubject.value;
} }
private _save(publication: Publication): void { private _save(state: PublicationEditionState): void {
this.publicationSubject.next(publication); this.stateSubject.next(state);
} }
get isLoading$(): Observable<boolean> { get isLoading$(): Observable<boolean> {
@@ -61,8 +84,8 @@ export class PublicationEditionService implements OnDestroy {
return this.isSavingSubject.asObservable(); return this.isSavingSubject.asObservable();
} }
get publication$(): Observable<Publication> { get state$(): Observable<PublicationEditionState> {
return this.publicationSubject.asObservable(); return this.stateSubject.asObservable();
} }
loadPublication(): void { loadPublication(): void {
@@ -76,7 +99,9 @@ export class PublicationEditionService implements OnDestroy {
} else { } else {
this.publicationRestService.getById(publicationId) this.publicationRestService.getById(publicationId)
.then(publication => { .then(publication => {
this.publicationSubject.next(publication); const state = this._state;
state.publication = publication;
this.stateSubject.next(state);
}) })
.catch(error => { .catch(error => {
const errorMessage = 'A technical error occurred while loading publication data.'; const errorMessage = 'A technical error occurred while loading publication data.';
@@ -89,27 +114,27 @@ export class PublicationEditionService implements OnDestroy {
} }
editTitle(title: string): void { editTitle(title: string): void {
const publication = this._publication; const state = this._state;
publication.title = title; state.publication.title = title;
this._save(publication); this._save(state);
} }
editDescription(description: string): void { editDescription(description: string): void {
const publication = this._publication; const state = this._state;
publication.description = description; state.publication.description = description;
this._save(publication); this._save(state);
} }
editText(text: string): void { editText(text: string): void {
const publication = this._publication; const state = this._state;
publication.text = text; state.publication.text = text;
this._save(publication); this._save(state);
} }
editIllustrationId(pictureId: string): void { editIllustrationId(pictureId: string): void {
const publication = this._publication; const state = this._state;
publication.illustrationId = pictureId state.publication.illustrationId = pictureId
this._save(publication); this._save(state);
} }
displayPictureSectionDialog(): void { displayPictureSectionDialog(): void {
@@ -124,11 +149,55 @@ export class PublicationEditionService implements OnDestroy {
this.subscriptions.push(afterDialogCloseSubscription); 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 { save(): void {
const publication = this._publication; const state = this._state;
this.isSavingSubject.next(true); this.isSavingSubject.next(true);
this.publicationRestService.update(publication) this.publicationRestService.update(state.publication)
.then(() => { .then(() => {
this.snackBar.open('Publication updated succesfully!', 'Close', { duration: 5000 }); this.snackBar.open('Publication updated succesfully!', 'Close', { duration: 5000 });
this.router.navigate(['/home']); this.router.navigate(['/home']);