Skip to content

Commit

Permalink
Merge pull request #32 from LighthouseBlog/feature/tags
Browse files Browse the repository at this point in the history
Finished tagging feature
  • Loading branch information
Prescientje authored Oct 4, 2017
2 parents 01a4e98 + ec9789a commit 61a9c63
Show file tree
Hide file tree
Showing 5 changed files with 143 additions and 8 deletions.
38 changes: 37 additions & 1 deletion src/app/_services/editor.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export class EditorService {

private editorUrl = environment.URL + '/blog/';
private gistUrl = environment.URL + '/gist/';
private tagUrl = environment.URL + '/tags/'
private title = '';
private description = '';
private id: string;
Expand Down Expand Up @@ -57,7 +58,7 @@ export class EditorService {
.catch(this.handleError);
}

saveArticle(edits: string): Observable<boolean> {
saveArticle(edits: string, tags: string[]): Observable<boolean> {
const headers = new Headers();
headers.append('Content-Type', 'application/json');
headers.append('Authorization', 'Bearer ' + this.auth.token);
Expand All @@ -68,6 +69,7 @@ export class EditorService {
text: edits,
title: this.title,
description: this.description,
tags,
author: author.username
};

Expand Down Expand Up @@ -108,6 +110,40 @@ export class EditorService {
.catch(this.handleError);
}

getTags(text: string): Observable<string[]> {
if (!text) {
return Observable.of([]);
}
const headers = new Headers();
headers.append('Content-Type', 'application/json');
headers.append('Authorization', 'Bearer ' + this.auth.token);

const options = new RequestOptions({ headers });

const body = {
prefix: text,
count: 50
}

return this.http.put(this.tagUrl, body, options)
.map(this.extractData)
.catch(this.handleError);
}

addTag(tag: string): Observable<Response> {
const headers = new Headers();
headers.append('Content-Type', 'application/json');
headers.append('Authorization', 'Bearer ' + this.auth.token);

const options = new RequestOptions({ headers });

const body = {
tag
}

return this.http.post(this.tagUrl, body, options);
}

private extractData(res: Response) {
const body = res.json();
return body.data || { };
Expand Down
19 changes: 18 additions & 1 deletion src/app/editor/editor.component.html
Original file line number Diff line number Diff line change
@@ -1,14 +1,31 @@
<div class="editor-form">
<form class="form-container" #f="ngForm" [formGroup]="formGroup" (ngSubmit)="saveArticle(f.value, f.valid)" novalidate>
<form class="form-container" #f="ngForm" [formGroup]="formGroup" (ngSubmit)="saveArticle(f.value, f.valid)" (keydown.enter)="$event.preventDefault()" novalidate>
<button mat-raised-button type="submit" [disabled]="!f.valid" color="accent" class="save"> Save Article </button>
<mat-input-container class="form-input">
<input matInput type="text" placeholder="Article Title" formControlName="articleTitle" required />
<mat-hint *ngIf="formGroup.hasError('required','articleTitle')" [ngStyle]="{'color': 'red'}" >Article Title is required</mat-hint>
</mat-input-container>
<span class="vertical-quarter-spacer"></span>
<mat-input-container class="form-input">
<textarea matInput type="text" placeholder="Article Description" formControlName="articleDescription" rows="5" required></textarea>
<mat-hint *ngIf="formGroup.hasError('required','articleDescription')" [ngStyle]="{'color': 'red'}" >Article Description is required</mat-hint>
</mat-input-container>
<span class="vertical-quarter-spacer"></span>
<md-chip-list class="tag-container">
<mat-chip *ngFor="let tag of selectedTags" [value]="tag" [removable]="removable" (remove)="removeTag(tag)">
{{ tag }}
<mat-icon matChipRemove *ngIf="removable">cancel</mat-icon>
</mat-chip>
</md-chip-list>
<span class="vertical-quarter-spacer"></span>
<mat-input-container class="form-input">
<input matInput type="text" placeholder="Tags" formControlName="tags" [matAutocomplete]="auto" (window:keydown)="onEnter($event)" [(ngModel)]="tagInput" />
<mat-autocomplete #auto="matAutocomplete">
<mat-option *ngFor="let tag of filteredTags | async" [value]="tag" (onSelectionChange)="tagSelected(tag)">
{{ tag }}
</mat-option>
</mat-autocomplete>
</mat-input-container>
</form>
<div matInput class="editor">
<div *ngIf="editing" (froalaInit)="initialize($event)" [froalaEditor]="options" [(froalaModel)]="editorContent"></div>
Expand Down
21 changes: 21 additions & 0 deletions src/app/editor/editor.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,25 @@
.editor-form {
margin-left: 1vw;
margin-right: 1vw;
}

.vertical-spacer {
width: 100%;
display: block;
clear: all;
padding-top: 2em;
}

.vertical-half-spacer {
width: 100%;
display: block;
clear: all;
padding-top: 1em;
}

.vertical-quarter-spacer {
width: 100%;
display: block;
clear: all;
padding-top: 0.5em;
}
69 changes: 64 additions & 5 deletions src/app/editor/editor.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { Router, ActivatedRoute } from '@angular/router';
import { FormBuilder, FormGroup, FormControl, Validators, FormsModule } from '@angular/forms';
import { MdSnackBar } from '@angular/material';

import { Observable } from 'rxjs/Observable';

import { EditorService } from '../_services/editor.service';
import { ArticleService } from '../_services/article.service';
import { ImagesService } from '../_services/images.service';
Expand Down Expand Up @@ -55,6 +57,10 @@ export class EditorComponent implements OnInit {
public editing = false;
public formGroup: FormGroup;
public initControls: any;
public filteredTags: Observable<string[]>;
public selectedTags: Set<string>;
public tagInput: string;
public removable = true;

constructor(
private editorService: EditorService,
Expand All @@ -67,7 +73,8 @@ export class EditorComponent implements OnInit {
) {
this.formGroup = this.fb.group({
'articleTitle': new FormControl('', Validators.required),
'articleDescription': new FormControl('', Validators.required)
'articleDescription': new FormControl('', Validators.required),
'tags': new FormControl('')
});
}

Expand All @@ -83,6 +90,12 @@ export class EditorComponent implements OnInit {
}

ngOnInit() {
this.selectedTags = new Set<string>();
this.formGroup.get('tags').valueChanges
.subscribe((input) => {
this.filteredTags = this.filterTags(input);
});

initializeFroalaGistPlugin(this.editorService);
this.editing = true;
this.route.params.subscribe(params => {
Expand All @@ -94,10 +107,14 @@ export class EditorComponent implements OnInit {
.subscribe(article => {
this.formGroup.setValue({
'articleTitle': article.title,
'articleDescription': article.description
'articleDescription': article.description,
'tags': ''
});
if (article.tags instanceof Array) {
this.selectedTags = new Set<string>(article.tags);
}
this.editorContent = article.text;
})
});
});
}

Expand All @@ -109,11 +126,12 @@ export class EditorComponent implements OnInit {
if (isFormValid) {
const articleTitle = formValue['articleTitle'];
const articleDescription = formValue['articleDescription'];
const tags = Array.from(this.selectedTags);

this.editorService.setArticleTitle(articleTitle);
this.editorService.setArticleDescription(articleDescription);

this.editorService.saveArticle(this.content)
this.editorService.saveArticle(this.content, tags)
.subscribe(result => {
if (result['text'] === this.content) {
this.snackBar.open('Successfully saved article', '', {
Expand All @@ -132,8 +150,49 @@ export class EditorComponent implements OnInit {
}
}

previewArticle() {
filterTags(text: string): Observable<string[]> {
return this.editorService.getTags(text);
}

removeTag(tag: string) {
this.selectedTags.delete(tag);
}

tagSelected(tag: string) {
if (this.selectedTags.has(this.tagInput)) {
this.snackBar.open('Tag already exists', '', {
duration: 2000
});
} else {
this.selectedTags.add(tag);
this.snackBar.open(`Added the tag: ${tag}`, '', {
duration: 2000
});
this.formGroup.get('tags').patchValue('');
}
}

onEnter(event: any) {
if (event.keyCode === 13) {
if (this.selectedTags.has(this.tagInput)) {
this.snackBar.open('Tag already exists', '', {
duration: 2000
});
} else {
const input = this.tagInput;
this.editorService.addTag(input)
.subscribe(result => {
this.selectedTags.add(input);
this.snackBar.open(`Added the tag: ${input}`, '', {
duration: 2000
});
this.formGroup.get('tags').patchValue('');
}, error => {
this.snackBar.open('Error adding tag', '', {
duration: 2000
});
});
}
}
}
}
4 changes: 3 additions & 1 deletion src/app/material.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { NgModule } from '@angular/core';
import { MatAutocompleteModule, MatGridListModule, MatDialogModule, MatInputModule, MatSelectModule,
MatMenuModule, MatSidenavModule, MatToolbarModule, MatListModule,
MatCardModule, MatButtonModule, MatIconModule, MatSnackBarModule, MatTableModule,
NoConflictStyleCompatibilityMode } from '@angular/material';
NoConflictStyleCompatibilityMode, MatChipsModule } from '@angular/material';

@NgModule({
imports: [
Expand All @@ -20,6 +20,7 @@ import { MatAutocompleteModule, MatGridListModule, MatDialogModule, MatInputModu
MatIconModule,
MatSnackBarModule,
MatTableModule,
MatChipsModule,
NoConflictStyleCompatibilityMode
],
exports: [
Expand All @@ -37,6 +38,7 @@ import { MatAutocompleteModule, MatGridListModule, MatDialogModule, MatInputModu
MatIconModule,
MatSnackBarModule,
MatTableModule,
MatChipsModule,
NoConflictStyleCompatibilityMode
]
})
Expand Down

0 comments on commit 61a9c63

Please sign in to comment.