Add create-update post component.

This commit is contained in:
2019-02-11 21:05:40 +01:00
parent b3603e242f
commit dbd07a3362
11 changed files with 794 additions and 16 deletions

View File

@@ -17,6 +17,9 @@ import javax.persistence.OneToMany;
import javax.persistence.Table;
import org.codiki.core.entities.dto.CategoryDTO;
import org.codiki.core.entities.dto.View;
import com.fasterxml.jackson.annotation.JsonView;
@Entity
@Table(name="category")
@@ -29,8 +32,10 @@ public class Category implements Serializable {
/* ******************* */
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@JsonView(View.PostDTO.class)
private Long id;
@JsonView(View.PostDTO.class)
private String name;
/* ******************* */

View File

@@ -120,36 +120,32 @@ public class PostController {
@JsonView(View.PostDTO.class)
@PostMapping("/preview")
public PostDTO preview(@RequestBody final PostDTO pPost) {
final PostDTO result = new PostDTO();
result.setTitle(pPost.getTitle());
result.setImage(pPost.getImage() == null || "".equals(pPost.getImage())
public Post preview(@RequestBody final Post pPost) {
pPost.setImage(pPost.getImage() == null || "".equals(pPost.getImage())
? "https://news-cdn.softpedia.com/images/news2/this-is-the-default-wallpaper-of-the-gnome-3-20-desktop-environment-500743-2.jpg"
: pPost.getImage());
result.setDescription(pPost.getDescription());
result.setText(parserService.parse(pPost.getText()));
pPost.setText(parserService.parse(pPost.getText()));
return result;
return pPost;
}
@JsonView(View.PostDTO.class)
@PostMapping("/")
public PostDTO insert(@RequestBody final PostDTO pPost, final HttpServletRequest pRequest,
public Post insert(@RequestBody final PostDTO pPost, final HttpServletRequest pRequest,
final HttpServletResponse pResponse, final Principal pPrincipal) {
PostDTO result = null;
Post result = null;
Optional<Post> postCreated = postService.insert(pPost, pRequest, pResponse, pPrincipal);
if(postCreated.isPresent()) {
result = new PostDTO(postCreated.get());
result = postCreated.get();
}
return result;
}
@PutMapping("/")
public void update(@RequestBody final PostDTO pPost, final HttpServletRequest pRequest,
public void update(@RequestBody final Post pPost, final HttpServletRequest pRequest,
final HttpServletResponse pResponse, final Principal pPrincipal) throws IOException {
postService.update(pPost, pRequest, pResponse, pPrincipal);
}

View File

@@ -65,7 +65,7 @@ public class PostService {
return result;
}
public void update(final PostDTO pPost, final HttpServletRequest pRequest,
public void update(final Post pPost, final HttpServletRequest pRequest,
final HttpServletResponse pResponse, final Principal pPrincipal) throws IOException {
final Optional<User> connectedUser = userService.getUserByPrincipal(pPrincipal);

View File

@@ -30,6 +30,7 @@ import { ProfilEditionComponent } from './account-settings/profil-edition/profil
import { PostComponent } from './posts/post.component';
import { NotFoundComponent } from './not-found/not-found.component';
import { ByCategoryComponent } from './posts/byCategory/by-category.component';
import { CreateUpdatePostComponent } from './posts/create-update/create-update-post.component';
// Reusable components
import { PostCardComponent } from './core/post-card/post-card.component';
@@ -45,6 +46,7 @@ import { ChangePasswordService } from './account-settings/change-password/change
import { ProfilEditionService } from './account-settings/profil-edition/profil-edition.service';
import { PostService } from './posts/post.service';
import { ByCategoryService } from './posts/byCategory/by-category.service';
import { CreateUpdatePostService } from './posts/create-update/create-update-post.service';
@NgModule({
declarations: [
@@ -61,7 +63,8 @@ import { ByCategoryService } from './posts/byCategory/by-category.service';
ProfilEditionComponent,
PostComponent,
NotFoundComponent,
ByCategoryComponent
ByCategoryComponent,
CreateUpdatePostComponent
],
imports: [
BrowserModule,
@@ -86,6 +89,7 @@ import { ByCategoryService } from './posts/byCategory/by-category.service';
ProfilEditionService,
PostService,
ByCategoryService,
CreateUpdatePostService,
{ provide: HTTP_INTERCEPTORS, useClass: UnauthorizedInterceptor, multi: true }
],
bootstrap: [AppComponent]

View File

@@ -11,6 +11,7 @@ import { ChangePasswordComponent } from './account-settings/change-password/chan
import { ProfilEditionComponent } from './account-settings/profil-edition/profil-edition.component';
import { PostComponent } from './posts/post.component';
import { ByCategoryComponent } from './posts/byCategory/by-category.component';
import { CreateUpdatePostComponent } from './posts/create-update/create-update-post.component';
export const appRoutes: Routes = [
{ path: '', redirectTo: '/home', pathMatch: 'full' },
@@ -21,7 +22,9 @@ export const appRoutes: Routes = [
{ path: 'accountSettings', component: AccountSettingsComponent, canActivate: [AuthGuard] },
{ path: 'changePassword', component: ChangePasswordComponent, canActivate: [AuthGuard] },
{ path: 'profilEdit', component: ProfilEditionComponent, canActivate: [AuthGuard] },
{ path: 'posts/new', component: CreateUpdatePostComponent, canActivate: [AuthGuard] },
{ path: 'posts/:postKey', component: PostComponent },
{ path: 'posts/byCategory/:categoryId', component: ByCategoryComponent},
{ path: 'posts/update/:postKey', component: CreateUpdatePostComponent, canActivate: [AuthGuard] },
{ path: '**', redirectTo: '/home' }
];

View File

@@ -0,0 +1,252 @@
<div class="card hoverable">
<div class="card-header indigo white-text" mdbRippleRadius>
<h1 *ngIf="!model.key">Création d'un article</h1>
<h1 *ngIf="model.key">Modification de l'article {{model.key}}</h1>
<div class="row">
<a class="waves-light tabs"
[ngClass]="{'active': activatedTab === 'Édition'}"
(click)="activateEdition()">
Édition
</a>
<a class="waves-light tabs"
[ngClass]="{'active': activatedTab === 'Aperçu'}"
(click)="activatePreview()">
Aperçu
</a>
</div>
</div>
<div class="card-body">
<div *ngIf="activatedTab === 'Édition'">
<div class="md-form">
<input mdbInputDirective
id="title"
name="title"
type="text"
class="form-control"
[(ngModel)]="model.title"
#title="ngModel"
data-error="Veuillez saisir un titre d'article"
[validateSuccess]="false"
required />
<label for="title">Titre de l'article</label>
</div>
<div class="md-form">
<input mdbInputDirective
id="image"
name="image"
type="text"
class="form-control"
[(ngModel)]="model.image"
#image="ngModel"
data-error="Veuillez saisir une adresse URL ou uploader une image"
[validateSuccess]="false"
required />
<label for="image">Image de l'article</label>
</div>
<div class="md-form">
<input mdbInputDirective
id="description"
name="description"
type="text"
class="form-control"
[(ngModel)]="model.description"
#description="ngModel"
data-error="Veuillez saisir la description de l'article"
[validateSuccess]="false"
required />
<label for="description">Description de l'article</label>
</div>
<div class="input-group mb-3 wrap">
<div class="select">
<select id="category" class="select-text" [(ngModel)]="model.category" [compareWith]="compareCategories" required>
<option value="" disabled selected></option>
<option *ngFor="let category of listCategories" [ngValue]="category">{{category.name}}</option>
</select>
<span class="select-highlight"></span>
<span class="select-bar"></span>
<label class="select-label">Catégorie de l'article</label>
</div>
</div>
<div id="toolbox" class="row">
<button type="button"
class="btn btn-floating waves-light"
(click)="injectHeader('h1')"
mdbTooltip="Titre 1"
placement="bottom"
mdbRippleRadius><b>H1</b></button>
<button type="button"
class="btn btn-floating waves-light"
(click)="injectHeader('h2')"
mdbTooltip="Titre 2"
placement="bottom"
mdbRippleRadius><b>H2</b></button>
<button type="button"
class="btn btn-floating waves-light"
(click)="injectHeader('h3')"
mdbTooltip="Titre 3"
placement="bottom"
mdbRippleRadius><b>H3</b></button>
<button type="button"
class="btn btn-floating waves-light"
(click)="openImagesModal()"
mdbTooltip="Image"
placement="bottom"
mdbRippleRadius>
<i class="fa fa-image"></i>
</button>
<button type="button"
class="btn btn-floating waves-light"
(click)="injectLink()"
mdbTooltip="Lien"
placement="bottom"
mdbRippleRadius>
<i class="fa fa-link"></i>
</button>
<button type="button"
class="btn btn-floating waves-light"
(click)="frameCode.show()"
mdbTooltip="Extrait de code"
placement="bottom"
mdbRippleRadius>
<i class="fa fa-code"></i>
</button>
</div>
<div class="md-form">
<textarea mdbInputDirective
id="text"
name="text"
type="text"
class="md-textarea form-control"
[(ngModel)]="model.text"
#text="ngModel"
data-error="Veuillez saisir le contenu de l'article"
[validateSuccess]="false"
required>
</textarea>
<label for="text">Contenu de l'article</label>
</div>
<!-- <div id="errorMsg" class="card red lighten-2 text-center z-depth-2">
<div class="card-body">
<p class="white-text mb-0">{{modelError}}</p>
</div>
</div>
<div id="resultMsg" class="card green lighten-2 text-center z-depth-2" >
<div class="card-body">
<p class="white-text mb-0">{{result}}</p>
</div>
</div> -->
</div>
<div *ngIf="activatedTab === 'Aperçu'">
<app-spinner *ngIf="!parsedPost"></app-spinner>
<div class="card" *ngIf="parsedPost">
<img [src]="parsedPost.image" class="img-fluid" alt="Post image">
<div class="card-body">
<h1 class="card-title">{{parsedPost.title}}</h1>
<h4>{{parsedPost.description}}</h4>
<hr/>
<div [innerHTML]="getContent()"></div>
</div>
</div>
</div>
<div id="errorMsg" class="card red lighten-2 text-center z-depth-2">
<div class="card-body">
<p class="white-text mb-0">{{modelError}}</p>
</div>
</div>
<div id="resultMsg" class="card green lighten-2 text-center z-depth-2" >
<div class="card-body">
<p class="white-text mb-0">{{result}}</p>
</div>
</div>
<div id="footer">
<a routerLink="/myPosts">Annuler</a>
<button type="button"
class="btn btn-primary waves-light float-right"
(click)="save()" mdbRippleRadius>Enregistrer</button>
</div>
</div>
</div>
<div mdbModal #frameCode="mdb-modal" class="modal fade top"
tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog modal-full-height modal-top" role="document">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title w-100" id="myModalLabel">Ajout de code</h4>
<button type="button" class="close" data-dismiss="modal" aria-label="Close" (click)="frameCode.hide()">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<div class="wrap">
<div class="select">
<select id="languageTmp" class="select-text" [(ngModel)]="languageTmp" required>
<option value="" disabled selected></option>
<option value="java">Java</option>
<option value="python">Python</option>
<option value="markup">html/xml</option>
<option value="sql">SQL</option>
<option value="bash">Bash</option>
</select>
<span class="select-highlight"></span>
<span class="select-bar"></span>
<label class="select-label">Langage de programmation</label>
</div>
</div>
<div class="md-form" style="margin-top: 15px; margin-bottom: 0;">
<textarea mdbInputDirective
id="codeTmp"
name="codeTmp"
type="text"
class="md-textarea form-control"
[(ngModel)]="codeTmp"
data-error="Veuillez écrire ou coller votre extrait de code dans cette zone de saisie"
[validateSuccess]="false"
required>
</textarea>
<label for="codeTmp">Extrait de code</label>
</div>
<div class="card red lighten-1 text-center z-depth-2" [hidden]="!codeError" style="margin-bottom: 0;">
<div class="card-body">
<p class="white-text mb-0">Le langage et l'extrait de code doivent être renseignés.</p>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-primary waves-effect waves-light"
data-dismiss="modal" (click)="frameCode.hide()">Fermer</button>
<button type="button" class="btn btn-primary waves-effect waves-light"
(click)="injectCode()">Enregistrer</button>
</div>
</div>
</div>
</div>
<div mdbModal #frameImages="mdb-modal" class="modal fade top"
tabindex="-1" role="dialog" aria-labelledby="frameImagesLabel" aria-hidden="true">
<div class="modal-dialog modal-full-height modal-top" role="document">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title w-100" id="frameImagesLabel">Ajout d'image</h4>
<button type="button" class="close" data-dismiss="modal" aria-label="Close" (click)="frameCode.hide()">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<app-spinner *ngIf="!imagesLoaded"></app-spinner>
<div id="image-div" *ngIf="imagesLoaded">
<img *ngFor="let img of listImages"
[src]="getLinkSrc(img.link)"
class="uploaded-image hoverable"
(click)="injectImage(img.link)" />
</div>
<a class="fixed-action-btn green white-text" *ngIf="imagesLoaded" (click)="openNewImageInput()">+</a>
<input id="newImageInput" type="file" (change)="uploadImage($event)" [hidden]="true">
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline-primary waves-effect waves-light"
data-dismiss="modal" (click)="frameImages.hide()">Fermer</button>
<button type="button" class="btn btn-primary waves-effect waves-light"
(click)="injectCode()">Enregistrer</button>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,234 @@
.card {
margin-bottom: 50px;
}
.card-header {
padding-top: 24px;
padding-left: 24px;
padding-right: 24px;
padding-bottom: 0px;
border-width: 0px;
}
.tabs {
width: 50%;
text-align: center;
line-height: 62px;
}
.tabs.active {
border-bottom: 4px solid white;
}
.custom-select {
border: 0px;
border-bottom: 1px #aaa solid;
border-radius: 0;
padding-left: 0;
margin-bottom: 20px;
}
.custom-select:focus {
-webkit-box-shadow: 0;
box-shadow: 0;
}
.md-form {
margin-bottom: 35px;
}
#footer {
line-height: 57px;
padding-left: 15px;
}
#toolbox {
margin-left: 5px;
margin-bottom: 15px;
}
.btn-floating {
border-radius: 50%;
padding: 0;
margin: 2px;
width: 40px;
height: 40px;
background-color: #3f51b5;
text-align: center;
box-shadow: 0 5px 11px 0 rgba(0,0,0,.18), 0 4px 15px 0 rgba(0,0,0,.15);
transition: box-shadow 0.3s ease-in-out;
}
.btn-floating:hover {
box-shadow: 0 8px 17px 0 rgba(0,0,0,.2), 0 6px 20px 0 rgba(0,0,0,.19);
}
#text {
padding: 0;
height: 250px;
overflow-y: scroll;
}
#resultMsg, #errorMsg {
max-height: 0;
overflow: hidden;
transition: max-height 0.5s ease-out;
margin: 0;
}
.wrap {
top: 40%;
width: 100%;
margin: 0 auto;
}
/* select starting stylings ------------------------------*/
.select {
position: relative;
width: 100%;
}
.select-text {
position: relative;
font-family: inherit;
background-color: transparent;
width: 100%;
padding: 10px 10px 10px 0;
font-size: 18px;
border-radius: 0;
border: none;
border-bottom: 1px solid #bdbdbd;
}
/* Remove focus */
.select-text:focus {
outline: none;
border-bottom: 1px solid rgba(0,0,0, 0);
}
/* Use custom arrow */
.select .select-text {
appearance: none;
-webkit-appearance:none
}
.select:after {
position: absolute;
top: 18px;
right: 10px;
/* Styling the down arrow */
width: 0;
height: 0;
padding: 0;
content: '';
border-left: 6px solid transparent;
border-right: 6px solid transparent;
border-top: 6px solid #bdbdbd;
pointer-events: none;
}
/* LABEL ======================================= */
.select-label {
color: #757575;
font-size: 1rem;
font-weight: normal;
position: absolute;
pointer-events: none;
left: 0;
top: -5px;
transition: 0.2s ease all;
}
/* active state */
.select-text:focus ~ .select-label {
color: #2F80ED;
}
.select-text:focus ~ .select-label, .select-text:valid ~ .select-label {
top: -20px;
transition: 0.2s ease all;
font-size: 14px;
}
/* BOTTOM BARS ================================= */
.select-bar {
position: relative;
display: block;
width: 100%;
}
.select-bar:before, .select-bar:after {
content: '';
height: 2px;
width: 0;
bottom: 1px;
position: absolute;
background: #2F80ED;
transition: 0.2s ease all;
}
.select-bar:before {
left: 50%;
}
.select-bar:after {
right: 50%;
}
/* active state */
.select-text:focus ~ .select-bar:before, .select-text:focus ~ .select-bar:after {
width: 50%;
}
/* HIGHLIGHTER ================================== */
.select-highlight {
position: absolute;
height: 60%;
width: 40%;
top: 25%;
left: 0;
pointer-events: none;
opacity: 0.5;
}
$btnSize: 45px;
.fixed-action-btn {
display: block;
position: absolute;
bottom: 10px;
right: 40px;
z-index: 997;
width: $btnSize;
height: $btnSize;
border-radius: 50%;
line-height: $btnSize;
text-align: center;
font-size: 25px;
box-shadow: 0 5px 11px 0 rgba(0,0,0,.18), 0 4px 15px 0 rgba(0,0,0,.15);
transition: box-shadow 0.3s ease-in-out;
}
.fixed-action-btn:hover {
box-shadow: 0 8px 17px 0 rgba(0,0,0,.2), 0 6px 20px 0 rgba(0,0,0,.19);
}
#image-div {
height: 60vh;
overflow-y: scroll;
}
.uploaded-image {
display: inline-flex;
position: relative;
width: 300px;
height: 300px;
background-color: #fff;
border-radius: 3px;
box-shadow: 0 2px 2px 0 rgba(0,0,0,0.14),0 3px 1px -2px rgba(0,0,0,0.12),0 1px 5px 0 rgba(0,0,0,0.2);
cursor:pointer;
margin-right: 15px;
margin-bottom: 15px;
}

View File

@@ -0,0 +1,233 @@
import { Component, OnInit, SecurityContext, ViewChild } from '@angular/core';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
import { Router, ActivatedRoute, RoutesRecognized, NavigationEnd } from '@angular/router';
import { Post, Category, Image } from '../../core/entities';
import { AuthService } from '../../core/services/auth.service';
import { CreateUpdatePostService } from './create-update-post.service';
import { filter, pairwise } from 'rxjs/operators';
import { HttpEventType, HttpResponse } from '@angular/common/http';
enum Tabs {
EDITION = 'Édition',
PREVIEW = 'Aperçu'
}
declare let Prism: any;
@Component({
selector: 'app-create-update-post',
templateUrl: './create-update-post.component.html',
styleUrls: ['./create-update-post.component.scss']
})
export class CreateUpdatePostComponent implements OnInit {
static INPUT_POST_TEXT = 'text';
@ViewChild('frameCode') public contentModal;
@ViewChild('frameImages') public imagesModal;
model: Post = new Post('', '', '', '', '', null, null, null);
parsedPost: Post;
listCategories: Array<Category>;
activatedTab: string;
modelError: string;
result: string;
// Variables for the code popup
codeTmp: string;
languageTmp: string;
codeError: string;
// Variables for the images popup
imagesLoaded: boolean;
listImages: Array<Image>;
selectedFiles: FileList;
currentFileUpload: File;
progress: { percentage: number } = { percentage: 0 };
constructor(
private createUpdatePostService: CreateUpdatePostService,
private activatedRoute: ActivatedRoute,
private sanitizer: DomSanitizer,
private router: Router,
private authService: AuthService
) {
this.imagesLoaded = false;
}
ngOnInit(): void {
this.listCategories = [];
this.activatedTab = Tabs.EDITION;
this.createUpdatePostService.getCategories().subscribe(listCategories => {
this.listCategories = listCategories.filter(category => !category.listSubCategories.length);
});
const postKey = this.activatedRoute.snapshot.paramMap.get('postKey');
if (postKey) {
this.createUpdatePostService.getPost(postKey).subscribe(post => {
if (post.author.key === this.authService.getUser().key) {
this.model = post;
} else {
this.router.navigate(['/forbidden']);
}
});
// FIXME: The message isn't shown and the method ngOnInit is too much called, also during others components navigation.
this.router.events.pipe(filter(e => e instanceof RoutesRecognized), pairwise()).subscribe((events: any) => {
if (events[0].urlAfterRedirects === '/posts/new') {
this.setMessage('Article créé.', false);
}
});
}
}
activateEdition(): void {
this.activatedTab = Tabs.EDITION;
}
activatePreview(): void {
this.activatedTab = Tabs.PREVIEW;
this.parsedPost = undefined;
this.createUpdatePostService.processPreview(this.model).subscribe(parsedPost => {
this.parsedPost = parsedPost;
setTimeout(() => {
Prism.highlightAll();
}, 100);
});
}
getContent(): string {
return this.sanitizer.sanitize(SecurityContext.HTML, this.parsedPost.text);
}
injectHeader(header: string): void {
this.injectElement('[' + header + '][/' + header + ']', 4);
}
injectLink(): void {
this.injectElement('[link href="" txt="" /]', 11);
}
injectCode(): void {
if (this.languageTmp && this.codeTmp) {
const codeExtract = '\n[code lg="' + this.languageTmp + '"]\n'
+ this.codeTmp + '\n[/code]\n\n';
this.injectElement(codeExtract, codeExtract.length);
this.contentModal.hide();
this.codeTmp = undefined;
this.languageTmp = undefined;
this.resetSelect('languageTmp');
} else {
this.codeError = 'Le langage et l\'extrait de code doivent être renseignés.';
setTimeout(() => {
this.codeError = undefined;
}, 3500);
}
}
private injectElement(elementToInject: string, lengthForCursor: number): void {
const input = <HTMLInputElement>document.getElementById(CreateUpdatePostComponent.INPUT_POST_TEXT);
const contentValue = input.value;
const cursorPosition = input.selectionStart;
this.model.text = contentValue.slice(0, cursorPosition) + elementToInject + contentValue.slice(cursorPosition);
input.focus();
const newCursor = cursorPosition + lengthForCursor;
input.selectionStart = newCursor;
input.selectionEnd = newCursor;
}
private resetSelect(selectId: string): void {
const select = <HTMLSelectElement>document.getElementById(selectId);
select.selectedIndex = 0;
}
save(): void {
if (this.model.title && this.model.image && this.model.description && this.model.category && this.model.text) {
this.model.author = this.authService.getUser();
if (this.model.key) {
this.createUpdatePostService.updatePost(this.model).subscribe(post => {
this.setMessage('Modification enregistrée', false);
});
} else {
this.createUpdatePostService.addPost(this.model).subscribe(post => {
this.router.navigate([`/posts/update/${post.key}`]);
});
}
} else {
this.setMessage('Veuillez saisir les champs obligatoires.', true);
}
}
setMessage(message: string, error: boolean): void {
this[error ? 'modelError' : 'result'] = message;
const resultMsgDiv = document.getElementById(error ? 'errorMsg' : 'resultMsg');
resultMsgDiv.style.maxHeight = '64px';
setTimeout(() => {
resultMsgDiv.style.maxHeight = '0px';
setTimeout(() => {
this[error ? 'modelError' : 'result'] = undefined;
}, 550);
}, 3000);
}
openImagesModal(): void {
this.imagesLoaded = false;
this.imagesModal.show();
this.createUpdatePostService.getImages().subscribe(listImages => {
this.listImages = listImages;
this.imagesLoaded = true;
});
}
getLinkSrc(pLink: string): string {
return `/api/images/${pLink}`;
}
openNewImageInput(): void {
document.getElementById('newImageInput').click();
}
uploadImage(event): void {
this.selectedFiles = event.target.files;
this.progress.percentage = 0;
this.currentFileUpload = this.selectedFiles.item(0);
// This prevents error 400 if user doesn't select any file to upload and close the input file.
if (this.currentFileUpload) {
this.createUpdatePostService.uploadPicture(this.currentFileUpload).subscribe(result => {
if (result.type === HttpEventType.UploadProgress) {
this.progress.percentage = Math.round(100 * result.loaded / result.total);
} else if (result instanceof HttpResponse) {
console.log('File ' + result.body + ' completely uploaded!');
this.createUpdatePostService.getImageDetails(result.body as string).subscribe(image => {
this.listImages.push(image);
});
}
this.selectedFiles = undefined;
});
}
}
injectImage(pImageLink: string): void {
const imgTag = `[img src="${this.getLinkSrc(pImageLink)}" /]`;
this.injectElement(imgTag, imgTag.length);
this.imagesModal.hide();
}
compareCategories(cat1: Category, cat2: Category): boolean {
return cat1 && cat2 ? cat1.id === cat2.id : cat1 === cat2;
}
}

View File

@@ -0,0 +1,51 @@
import { Injectable } from '@angular/core';
import { HttpClient, HttpEvent, HttpRequest } from '@angular/common/http';
import { Observable } from 'rxjs';
import { Post, Category, Image } from '../../core/entities';
@Injectable()
export class CreateUpdatePostService {
constructor(private http: HttpClient) {}
processPreview(post: Post): Observable<Post> {
return this.http.post<Post>(`/api/posts/preview`, post);
}
getCategories(): Observable<Array<Category>> {
return this.http.get<Array<Category>>(`/api/categories/`);
}
addPost(post: Post): Observable<Post> {
return this.http.post<Post>(`/api/posts/`, post);
}
updatePost(post: Post): Observable<Post> {
return this.http.put<Post>(`/api/posts/`, post);
}
getPost(postKey: string): Observable<Post> {
return this.http.get<Post>(`/api/posts/${postKey}/source`);
}
getImages(): Observable<Array<Image>> {
return this.http.get<Array<Image>>(`/api/images/myImages`);
}
uploadPicture(file: File): Observable<HttpEvent<{}>> {
const formData: FormData = new FormData();
formData.append('file', file);
return this.http.request(new HttpRequest(
'POST', '/api/images', formData, {
reportProgress: true,
responseType: 'text'
}
));
}
getImageDetails(imageLink: string): Observable<Image> {
return this.http.get<Image>(`/api/images/${imageLink}/details`);
}
}

View File

@@ -5,7 +5,7 @@
<a *ngIf="owned" class="btn-card-floating waves-light white-text"
routerLink="/posts/update/{{post.key}}">
<i class="fa fa-pencil"></i>
<i class="fa fa-pen"></i>
</a>
<div class="card-body">

View File

@@ -12,6 +12,6 @@
<body>
<app-root></app-root>
<script src="https://cdnjs.cloudflare.com/ajax/libs/clipboard.js/1.5.8/clipboard.min.js"></script>
<!-- <script type="text/javascript" src="./assets/js/prism.js"></script> -->
<script type="text/javascript" src="./assets/js/prism.js"></script>
</body>
</html>