Extract publication edition into a separated component.
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
<form [formGroup]="publicationEditionForm" (submit)="save()" ngNativeValidate>
|
||||
<header>
|
||||
<h1>Modification de l'article {{ publication.title }}</h1>
|
||||
</header>
|
||||
|
||||
<mat-tab-group dynamicHeight (selectedIndexChange)="onTabChange($event)">
|
||||
<mat-tab label="Edition">
|
||||
<div class="form-content">
|
||||
<div class="first-part">
|
||||
<div>
|
||||
<mat-form-field>
|
||||
<mat-label>Title</mat-label>
|
||||
<input matInput type="text" formControlName="title" />
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<mat-label>Description</mat-label>
|
||||
<input matInput type="text" formControlName="description" />
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<div class="picture-container">
|
||||
<img src="/api/pictures/{{publication.illustrationId}}" (click)="displayPictureSectionDialog()" matTooltip="Click to change illustration"/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<button type="button" matTooltip="Click to insert a title 1 section" (click)="insertTitle(1)">
|
||||
H1
|
||||
</button>
|
||||
<button type="button" matTooltip="Click to insert a title 2 section" (click)="insertTitle(2)">
|
||||
H2
|
||||
</button>
|
||||
<button type="button" matTooltip="Click to insert a title 1 section" (click)="insertTitle(3)">
|
||||
H3
|
||||
</button>
|
||||
<button type="button" matTooltip="Click to insert a picture" (click)="selectAPicture()">
|
||||
<mat-icon>image</mat-icon>
|
||||
</button>
|
||||
<button type="button" matTooltip="Click to insert a link" (click)="insertLink()">
|
||||
<mat-icon>link</mat-icon>
|
||||
</button>
|
||||
<button type="button" matTooltip="Click to insert a code block" (click)="displayCodeBlockDialog()">
|
||||
<mat-icon>code</mat-icon>
|
||||
</button>
|
||||
<button type="button" disabled matTooltip="Click to display editor help">
|
||||
<mat-icon>help</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
<mat-form-field class="example-form-field">
|
||||
<mat-label>Content</mat-label>
|
||||
<textarea
|
||||
#textArea
|
||||
matInput
|
||||
formControlName="text"
|
||||
class="text-input"
|
||||
(keyup)="updateCursorPosition($event)"
|
||||
(click)="updateCursorPosition($event)">
|
||||
</textarea>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
</mat-tab>
|
||||
|
||||
<mat-tab label="Previewing">
|
||||
<div class="preview">
|
||||
@if ((isPreviewing$ | async) === true) {
|
||||
<div class="preview-loading">
|
||||
<h2>Preview is loading...</h2>
|
||||
<mat-spinner></mat-spinner>
|
||||
</div>
|
||||
} @else {
|
||||
<img class="illustration" src="/pictures/{{ publication.illustrationId }}" />
|
||||
<header>
|
||||
<h1>{{ publication.title }}</h1>
|
||||
<h2>{{ publication.description }}</h2>
|
||||
</header>
|
||||
<main [innerHTML]="publication.parsedText"></main>
|
||||
}
|
||||
</div>
|
||||
</mat-tab>
|
||||
</mat-tab-group>
|
||||
<footer>
|
||||
<app-submit-button label="Save" [requestPending]="!!(isSaving$ | async)"></app-submit-button>
|
||||
<button type="button" class="secondary" (click)="goPreviousLocation()">
|
||||
Cancel
|
||||
</button>
|
||||
</footer>
|
||||
</form>
|
||||
@@ -0,0 +1,184 @@
|
||||
:host {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
form {
|
||||
margin: 1em;
|
||||
max-width: 80em;
|
||||
width: 90%;
|
||||
border-radius: .5em;
|
||||
box-shadow: 0 2px 5px 0 rgba(0,0,0,.16),0 2px 10px 0 rgba(0,0,0,.12);
|
||||
|
||||
& > header {
|
||||
padding: 2em;
|
||||
background-color: #3f51b5;
|
||||
color: white;
|
||||
border-radius: .5em .5em 0 0;
|
||||
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
margin-bottom: .5em;
|
||||
}
|
||||
}
|
||||
|
||||
footer {
|
||||
padding: 2em;
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.form-content {
|
||||
padding: 2em;
|
||||
padding-bottom: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: .5em;
|
||||
|
||||
mat-form-field {
|
||||
textarea {
|
||||
height: 20em;
|
||||
}
|
||||
}
|
||||
|
||||
.first-part {
|
||||
display: flex;
|
||||
flex-direction: column-reverse;
|
||||
gap: 1em;
|
||||
|
||||
@media screen and (min-width: 600px) {
|
||||
flex-direction: row;
|
||||
|
||||
div {
|
||||
flex: 1 0;
|
||||
|
||||
&.picture-container {
|
||||
max-width: 20em;
|
||||
|
||||
img {
|
||||
max-height: 15em;
|
||||
max-width: 20em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
div {
|
||||
flex: 1 0 50%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
|
||||
&.picture-container {
|
||||
img {
|
||||
flex: 1;
|
||||
object-fit: cover;
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
border-radius: 1em;
|
||||
opacity: .9;
|
||||
box-shadow: 0 2px 5px 0 rgba(0,0,0,.16),0 2px 10px 0 rgba(0,0,0,.12);
|
||||
transition: opacity .2s ease-in-out, box-shadow .2s ease-in-out;
|
||||
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
opacity: 1;
|
||||
box-shadow: 0 2px 5px 0 rgba(0,0,0,.32),0 2px 10px 0 rgba(0,0,0,.24);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: .5em;
|
||||
|
||||
button {
|
||||
padding: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 3em;
|
||||
height: 3em;
|
||||
box-shadow: 0 2px 5px 0 rgba(0,0,0,.16),0 2px 10px 0 rgba(0,0,0,.12);
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
button, a.button {
|
||||
padding: .8em 1.2em;
|
||||
border-radius: 10em;
|
||||
border: none;
|
||||
background-color: #3f51b5;
|
||||
color: white;
|
||||
transition: background-color .2s ease-in-out;
|
||||
|
||||
&:hover {
|
||||
background-color: #5b6ed8;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
background-color: #5f6aa6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
&.secondary {
|
||||
color: #3f51b5;
|
||||
background-color: white;
|
||||
|
||||
&:hover {
|
||||
background-color: #f2f4ff;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.preview {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-height: 80vh;
|
||||
overflow-y: auto;
|
||||
|
||||
.preview-loading {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.illustration {
|
||||
flex: 1;
|
||||
height: 12em;
|
||||
object-fit: cover;
|
||||
transition: height .2s ease-in-out;
|
||||
|
||||
@media screen and (min-width: 450px) {
|
||||
height: 15em;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 600px) {
|
||||
height: 20em;
|
||||
}
|
||||
|
||||
@media screen and (min-width: 750px) {
|
||||
height: 25em;
|
||||
}
|
||||
}
|
||||
|
||||
header {
|
||||
padding: 2em;
|
||||
}
|
||||
|
||||
main {
|
||||
padding: 2em;
|
||||
text-align: justify;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
import { CommonModule, Location } from "@angular/common";
|
||||
import { Component, EventEmitter, inject, Input, OnChanges, OnDestroy, Output } from "@angular/core";
|
||||
import { FormBuilder, FormControl, FormGroup, ReactiveFormsModule, Validators } from "@angular/forms";
|
||||
import { MatDialogModule } from "@angular/material/dialog";
|
||||
import { MatIconModule } from "@angular/material/icon";
|
||||
import { MatInputModule } from "@angular/material/input";
|
||||
import { MatProgressSpinnerModule } from "@angular/material/progress-spinner";
|
||||
import { MatTabsModule } from "@angular/material/tabs";
|
||||
import { MatTooltipModule } from "@angular/material/tooltip";
|
||||
import { map, Observable, of, Subscription } from "rxjs";
|
||||
import { Publication } from "../../core/rest-services/publications/model/publication";
|
||||
import { PictureSelectionDialog } from "../../pages/publication-edition/picture-selection-dialog/picture-selection-dialog.component";
|
||||
import { SubmitButtonComponent } from "../submit-button/submit-button.component";
|
||||
import { PublicationEditionService } from "./publication-edition.service";
|
||||
|
||||
@Component({
|
||||
selector: 'app-publication-edition',
|
||||
standalone: true,
|
||||
templateUrl: './publication-edition.component.html',
|
||||
styleUrl: './publication-edition.component.scss',
|
||||
imports: [
|
||||
CommonModule,
|
||||
MatDialogModule,
|
||||
MatIconModule,
|
||||
MatInputModule,
|
||||
MatProgressSpinnerModule,
|
||||
MatTabsModule,
|
||||
MatTooltipModule,
|
||||
PictureSelectionDialog,
|
||||
ReactiveFormsModule,
|
||||
SubmitButtonComponent
|
||||
],
|
||||
providers: [PublicationEditionService]
|
||||
})
|
||||
export class PublicationEditionComponent implements OnChanges, OnDestroy {
|
||||
@Input()
|
||||
publication!: Publication;
|
||||
@Input()
|
||||
isSaving$: Observable<boolean> = of(false);
|
||||
@Output()
|
||||
publicationSave = new EventEmitter<Publication>();
|
||||
|
||||
private readonly formBuilder = inject(FormBuilder);
|
||||
private readonly location = inject(Location);
|
||||
private readonly publicationEditionService = inject(PublicationEditionService);
|
||||
private publicationInEdition!: Publication;
|
||||
private subscriptions: Subscription[] = [];
|
||||
|
||||
publicationEditionForm: FormGroup = this.formBuilder.group({
|
||||
title: new FormControl<string | undefined>('', [Validators.required]),
|
||||
description: new FormControl<string | undefined>('', [Validators.required]),
|
||||
text: new FormControl<string | undefined>('', [Validators.required]),
|
||||
illustrationId: new FormControl<string | undefined>('', [Validators.required]),
|
||||
categoryId: new FormControl<string | undefined>('', [Validators.required])
|
||||
});
|
||||
|
||||
get isLoading$(): Observable<boolean> {
|
||||
return this.publicationEditionService.isLoading$;
|
||||
}
|
||||
|
||||
get isPreviewing$(): Observable<boolean> {
|
||||
return this.publicationEditionService.isPreviewing$;
|
||||
}
|
||||
|
||||
ngOnChanges(): void {
|
||||
this.ngOnDestroy();
|
||||
|
||||
this.publicationInEdition = this.publication;
|
||||
this.publicationEditionService.init(this.publicationInEdition);
|
||||
|
||||
['title', 'description', 'text'].forEach(fieldName => {
|
||||
const fieldSubscription = this.publicationEditionForm.controls[fieldName].valueChanges
|
||||
.pipe(
|
||||
map(value => value?.length ? value as string : '')
|
||||
)
|
||||
.subscribe(fieldValue => {
|
||||
switch (fieldName) {
|
||||
case 'title':
|
||||
this.publicationEditionService.editTitle(fieldValue);
|
||||
break;
|
||||
case 'description':
|
||||
this.publicationEditionService.editDescription(fieldValue);
|
||||
break;
|
||||
case 'text':
|
||||
this.publicationEditionService.editText(fieldValue);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
this.subscriptions.push(fieldSubscription);
|
||||
});
|
||||
|
||||
const publicationSubscription = this.publicationEditionService.state$.subscribe(state => {
|
||||
this.publicationInEdition = 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);
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.subscriptions.forEach(subscription => subscription?.unsubscribe());
|
||||
}
|
||||
|
||||
goPreviousLocation(): void {
|
||||
this.location.back();
|
||||
}
|
||||
|
||||
insertTitle(titleNumber: number): void {
|
||||
this.publicationEditionService.insertTitle(titleNumber);
|
||||
}
|
||||
|
||||
selectAPicture(): void {
|
||||
this.publicationEditionService.selectAPicture();
|
||||
}
|
||||
|
||||
insertLink(): void {
|
||||
this.publicationEditionService.insertLink();
|
||||
}
|
||||
|
||||
displayCodeBlockDialog(): void {
|
||||
this.publicationEditionService.displayCodeBlockDialog();
|
||||
}
|
||||
|
||||
save(): void {
|
||||
this.publicationSave.emit(this.publicationInEdition);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
onTabChange(tabSelectedIndex: number): void {
|
||||
if (tabSelectedIndex === 1) {
|
||||
this.publicationEditionService.loadPreview();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,296 @@
|
||||
import { inject, Injectable, OnDestroy } from "@angular/core";
|
||||
import { ActivatedRoute, Router } from "@angular/router";
|
||||
import { PublicationRestService } from "../../core/rest-services/publications/publication.rest-service";
|
||||
import { MatSnackBar } from "@angular/material/snack-bar";
|
||||
import { MatDialog } from "@angular/material/dialog";
|
||||
import { BehaviorSubject, Observable, Subscription } from "rxjs";
|
||||
import { copy } from "../../core/utils/ObjectUtils";
|
||||
import { Publication } from "../../core/rest-services/publications/model/publication";
|
||||
import { Location } from "@angular/common";
|
||||
import { PictureSelectionDialog } from "../../pages/publication-edition/picture-selection-dialog/picture-selection-dialog.component";
|
||||
import { CodeBlockDialog } from "../../pages/publication-edition/code-block-dialog/code-block-dialog.component";
|
||||
|
||||
declare let Prism: any;
|
||||
|
||||
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: '',
|
||||
key: '',
|
||||
title: '',
|
||||
text: '',
|
||||
parsedText: '',
|
||||
description: '',
|
||||
creationDate: new Date(),
|
||||
illustrationId: '',
|
||||
categoryId: '',
|
||||
author: {
|
||||
id: '',
|
||||
name: '',
|
||||
image: ''
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
private readonly publicationRestService = inject(PublicationRestService);
|
||||
private readonly location = inject(Location);
|
||||
private readonly router = inject(Router);
|
||||
private readonly snackBar = inject(MatSnackBar);
|
||||
private readonly dialog = inject(MatDialog);
|
||||
|
||||
private isLoadingSubject = new BehaviorSubject<boolean>(false);
|
||||
private stateSubject = new BehaviorSubject<PublicationEditionState>(copy(DEFAULT_STATE));
|
||||
private subscriptions: Subscription[] = [];
|
||||
private isSavingSubject = new BehaviorSubject<boolean>(false);
|
||||
private isPreviewingSubject = new BehaviorSubject<boolean>(false);
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.subscriptions.forEach(subscription => subscription.unsubscribe());
|
||||
}
|
||||
|
||||
private get _state(): PublicationEditionState {
|
||||
return this.stateSubject.value;
|
||||
}
|
||||
|
||||
private _save(state: PublicationEditionState): void {
|
||||
this.stateSubject.next(state);
|
||||
}
|
||||
|
||||
get isLoading$(): Observable<boolean> {
|
||||
return this.isLoadingSubject.asObservable();
|
||||
}
|
||||
|
||||
get isSaving$(): Observable<boolean> {
|
||||
return this.isSavingSubject.asObservable();
|
||||
}
|
||||
|
||||
get isPreviewing$(): Observable<boolean> {
|
||||
return this.isPreviewingSubject.asObservable();
|
||||
}
|
||||
|
||||
get state$(): Observable<PublicationEditionState> {
|
||||
return this.stateSubject.asObservable();
|
||||
}
|
||||
|
||||
loadPublication(): void {
|
||||
this.isLoadingSubject.next(true);
|
||||
|
||||
this.activatedRoute.paramMap.subscribe(params => {
|
||||
const publicationId = params.get('publicationId');
|
||||
if (publicationId == undefined) {
|
||||
this.snackBar.open('A technical error occurred while loading publication data.', 'Close', {duration: 5000});
|
||||
this.location.back();
|
||||
} else {
|
||||
this.publicationRestService.getById(publicationId)
|
||||
.then(publication => {
|
||||
const state = this._state;
|
||||
state.publication = publication;
|
||||
this.stateSubject.next(state);
|
||||
})
|
||||
.catch(error => {
|
||||
const errorMessage = 'A technical error occurred while loading publication data.';
|
||||
this.snackBar.open(errorMessage, 'Close', {duration: 5000});
|
||||
console.error(errorMessage, error)
|
||||
})
|
||||
.finally(() => this.isLoadingSubject.next(false));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
init(publication: Publication): void {
|
||||
const state = this._state;
|
||||
state.publication = publication;
|
||||
this.stateSubject.next(state);
|
||||
}
|
||||
|
||||
editTitle(title: string): void {
|
||||
const state = this._state;
|
||||
state.publication.title = title;
|
||||
this._save(state);
|
||||
}
|
||||
|
||||
editDescription(description: string): void {
|
||||
const state = this._state;
|
||||
state.publication.description = description;
|
||||
this._save(state);
|
||||
}
|
||||
|
||||
editText(text: string): void {
|
||||
const state = this._state;
|
||||
state.publication.text = text;
|
||||
this._save(state);
|
||||
}
|
||||
|
||||
editIllustrationId(pictureId: string): void {
|
||||
const state = this._state;
|
||||
state.publication.illustrationId = pictureId
|
||||
this._save(state);
|
||||
}
|
||||
|
||||
displayPictureSectionDialog(): void {
|
||||
const dialogRef = this.dialog.open(PictureSelectionDialog);
|
||||
|
||||
const afterDialogCloseSubscription = dialogRef.afterClosed()
|
||||
.subscribe(newPictureId => {
|
||||
if (newPictureId) {
|
||||
this.editIllustrationId(newPictureId);
|
||||
}
|
||||
});
|
||||
this.subscriptions.push(afterDialogCloseSubscription);
|
||||
}
|
||||
|
||||
displayCodeBlockDialog(): void {
|
||||
const dialogRef = this.dialog.open(CodeBlockDialog, { width: '60em' });
|
||||
|
||||
const afterDialogCloseSubscription = dialogRef.afterClosed()
|
||||
.subscribe(codeBlockWithLanguage => {
|
||||
if (codeBlockWithLanguage) {
|
||||
this.insertCodeBlock(codeBlockWithLanguage.programmingLanguage, codeBlockWithLanguage.codeBlock);
|
||||
}
|
||||
});
|
||||
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}'.`);
|
||||
}
|
||||
}
|
||||
|
||||
selectAPicture(): void {
|
||||
const dialogRef = this.dialog.open(PictureSelectionDialog);
|
||||
|
||||
const afterDialogCloseSubscription = dialogRef.afterClosed()
|
||||
.subscribe(newPictureId => {
|
||||
if (newPictureId) {
|
||||
this.insertPicture(newPictureId);
|
||||
}
|
||||
});
|
||||
this.subscriptions.push(afterDialogCloseSubscription);
|
||||
}
|
||||
|
||||
insertPicture(pictureId: string): void {
|
||||
const state = this._state;
|
||||
|
||||
const publication = state.publication;
|
||||
|
||||
const publicationTextLeftPart = publication.text.substring(0, state.cursorPosition.start);
|
||||
const publicationTextRightPart = publication.text.substring(state.cursorPosition.start);
|
||||
const textWithTags = `${publicationTextLeftPart}[img src="/api/pictures/${pictureId}" /]${publicationTextRightPart}`;
|
||||
|
||||
publication.text = textWithTags;
|
||||
|
||||
this._save(state);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
insertCodeBlock(programmingLanguage: string, codeBlock: string): void {
|
||||
const state = this._state;
|
||||
|
||||
const publication = state.publication;
|
||||
|
||||
const publicationTextLeftPart = publication.text.substring(0, state.cursorPosition.start);
|
||||
const publicationTextRightPart = publication.text.substring(state.cursorPosition.start);
|
||||
const codeBlockInstruction = `\n[code lg="${programmingLanguage}"]\n${codeBlock}\n[/code]\n\n`;
|
||||
const textWithTags = `${publicationTextLeftPart}${codeBlockInstruction}${publicationTextRightPart}`;
|
||||
|
||||
publication.text = textWithTags;
|
||||
|
||||
this._save(state);
|
||||
}
|
||||
|
||||
save(): void {
|
||||
const state = this._state;
|
||||
|
||||
this.isSavingSubject.next(true);
|
||||
this.publicationRestService.update(state.publication)
|
||||
.then(() => {
|
||||
this.snackBar.open('Publication updated succesfully!', 'Close', { duration: 5000 });
|
||||
this.router.navigate(['/home']);
|
||||
})
|
||||
.catch(error => {
|
||||
const errorMessage = 'An error occured while saving publication modifications.';
|
||||
console.error(errorMessage, error);
|
||||
this.snackBar.open(errorMessage, 'Close', { duration: 5000 });
|
||||
})
|
||||
.finally(() => this.isSavingSubject.next(false));
|
||||
}
|
||||
|
||||
loadPreview(): void {
|
||||
const state = this._state;
|
||||
|
||||
this.isPreviewingSubject.next(true);
|
||||
this.publicationRestService.preview(state.publication.text)
|
||||
.then(parsedText => {
|
||||
state.publication.parsedText = parsedText;
|
||||
this._save(state);
|
||||
setTimeout(() => Prism.highlightAll(), 1000);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error(error);
|
||||
})
|
||||
.finally(() => {
|
||||
this.isPreviewingSubject.next(false);
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user