Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Finished tagging feature #32

Merged
merged 1 commit into from
Oct 4, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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