Skip to content

Commit

Permalink
Merge pull request #41 from LighthouseBlog/feature/layout
Browse files Browse the repository at this point in the history
Feature/layout
  • Loading branch information
pastorsj authored Oct 22, 2017
2 parents 3e83b2e + ad793d4 commit cc58d99
Show file tree
Hide file tree
Showing 26 changed files with 417 additions and 127 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ typings/
npm-debug.log
dist/

*.pem
*.pem
*.rdb
5 changes: 3 additions & 2 deletions src/app/_models/Article.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { Author } from './Author';
export class Article {
_id: number;
title: string;
description: string;
datePosted: string;
datePosted: Date;
text: string;
author: string;
author: Author;
tags: Array<string>;
}
5 changes: 5 additions & 0 deletions src/app/_models/ArticleList.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export class ArticleList {
_id: number;
title: string;
tags: Array<string>;
}
13 changes: 10 additions & 3 deletions src/app/_services/article.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@ import 'rxjs/add/operator/map';

import { AuthenticationService } from '../_services/authentication.service';
import { Article } from '../_models/Article';
import { ArticleList } from '../_models/ArticleList';
import { environment } from '../../environments/environment';

@Injectable()
export class ArticleService {

private editorUrl = environment.URL + '/blog/';
private blogUrl = environment.URL + '/blog/';
private title = '';
private id: string;

Expand All @@ -35,13 +36,19 @@ export class ArticleService {

const options = new RequestOptions({ headers });

return this.http.get(this.editorUrl, options)
return this.http.get(this.blogUrl, options)
.map(this.extractData)
.catch(this.handleError);
}

getArticle(id: number): Observable<Article> {
return this.http.get(this.editorUrl + id)
return this.http.get(this.blogUrl + id)
.map(this.extractData)
.catch(this.handleError);
}

getArticlesByTitle(title: string): Observable<ArticleList[]> {
return this.http.get(this.blogUrl + 'title/' + title)
.map(this.extractData)
.catch(this.handleError);
}
Expand Down
2 changes: 1 addition & 1 deletion src/app/_services/authentication.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export class AuthenticationService {
reject(`Error ${error}`);
});
} else {
reject('No token available');
reject();
}
})
}
Expand Down
24 changes: 15 additions & 9 deletions src/app/_services/editor.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,26 +58,32 @@ export class EditorService {
.catch(this.handleError);
}

saveArticle(edits: string, tags: string[]): Observable<boolean> {
saveArticle(edits: string, title: string, description: string, tags: string[], coverPhoto?: FormData): Observable<any> {
const headers = new Headers();
headers.append('Content-Type', 'application/json');
headers.append('Authorization', 'Bearer ' + this.auth.token);

const options = new RequestOptions({ headers });

const author = JSON.parse(localStorage.getItem('currentUser'));

const post = {
text: edits,
title: this.title,
description: this.description,
title: title,
description: description,
tags,
author: author.username
};

const options = new RequestOptions({ headers });

return this.http.put(this.editorUrl + this.id, post, options)
.map(this.extractData)
.catch(this.handleError);
if (coverPhoto) {
return Observable.forkJoin(
this.http.put(this.editorUrl + this.id, post, options).map(this.extractData).catch(this.handleError),
this.http.post(this.editorUrl + this.id, coverPhoto, options).map(this.extractData).catch(this.handleError)
);
} else {
return Observable.forkJoin(
this.http.put(this.editorUrl + this.id, post, options).map(this.extractData).catch(this.handleError)
);
}
}

deleteArticle(article): Observable<boolean> {
Expand Down
53 changes: 53 additions & 0 deletions src/app/_services/tags.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { Injectable } from '@angular/core';
import { Http, Response, RequestOptions, Headers } from '@angular/http';

import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/map';

import { AuthenticationService } from '../_services/authentication.service';
import { Article } from '../_models/Article';
import { environment } from '../../environments/environment';

@Injectable()
export class TagService {

private tagUrl = environment.URL + '/tags/';

constructor(
private http: Http,
private auth: AuthenticationService
) { }

getAllTags(): Observable<Array<String>> {
return this.http.get(this.tagUrl)
.map(this.extractData)
.catch(this.handleError);
}

getArticlesByTag(tag: string): Observable<Article> {
return this.http.get(this.tagUrl + tag)
.map(this.extractData)
.catch(this.handleError);
}

private extractData(res: Response) {
const body = res.json();
return body.data || { };
}

private handleError (error: Response | any) {
// In a real world app, you might use a remote logging infrastructure
let errMsg: string;
if (error instanceof Response) {
const body = error.json() || '';
const err = body.error || JSON.stringify(body);
errMsg = `${error.status} - ${error.statusText || ''} ${err}`;
} else {
errMsg = error.message ? error.message : error.toString();
}
console.error(errMsg);
return Observable.throw(errMsg);
}

}
7 changes: 6 additions & 1 deletion src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,16 @@ import {Router} from './app.routing';
import { EditorComponent } from './editor/editor.component';
import { UserArticlesComponent } from './user-articles/user-articles.component';
import { CreateArticleModalComponent } from './create-article-modal/create-article-modal.component';
import { TagComponent } from './articles/tag/tag.component';

import { AuthGuard } from './_guards/auth.guard';
import { AuthenticationService } from './_services/authentication.service';
import { EditorService } from './_services/editor.service';
import { ArticleService } from './_services/article.service';
import { AuthorService } from './_services/author.service';
import { ImagesService } from './_services/images.service';
import { TagService } from './_services/tags.service';

import { FileValidator } from './_directives/fileValidator.directive';
import { FileValueAccessor } from './_directives/fileValueAccessor.directive';

Expand All @@ -49,7 +52,8 @@ import { MaterialModule } from './material.module';
DeleteArticleModalComponent,
SettingsModalComponent,
FileValidator,
FileValueAccessor
FileValueAccessor,
TagComponent
],
imports: [
BrowserAnimationsModule,
Expand Down Expand Up @@ -78,6 +82,7 @@ import { MaterialModule } from './material.module';
ArticleService,
AuthorService,
ImagesService,
TagService,
BaseRequestOptions
],
bootstrap: [
Expand Down
102 changes: 85 additions & 17 deletions src/app/articles/articles.component.html
Original file line number Diff line number Diff line change
@@ -1,19 +1,87 @@
<div class="articles">
<mat-grid-list cols="4">
<mat-grid-tile *ngFor="let article of articles | async">
<mat-card class="example-card">
<mat-card-header>
<mat-card-title>{{article?.title}}</mat-card-title>
</mat-card-header>
<mat-card-content>
<p>
{{article?.description}}
</p>
</mat-card-content>
<mat-card-actions>
<button mat-button (click)="selectedArticle(article)"> READ </button>
</mat-card-actions>
</mat-card>
</mat-grid-tile>
</mat-grid-list>
<section class="section">
<div class="container">
<div class="columns">
<div class="column is-two-thirds">
<mat-card class="article" *ngFor="let article of articles | async">
<mat-card-header>
<mat-card-title>
{{article?.title}}
</mat-card-title>
<mat-card-subtitle>
By {{article?.author?.name}}, {{article?.datePosted | date:'longDate'}}
</mat-card-subtitle>
</mat-card-header>
<img class="cover-photo" mat-card-image *ngIf="article?.coverPhoto" src="{{article?.coverPhoto}}" />
<mat-card-content>
{{article?.description}}
</mat-card-content>
<mat-card-actions align="end">
<button mat-button (click)="selectedArticle(article)"> Continue Reading... </button>
</mat-card-actions>
</mat-card>
</div>
<div class="column">
<mat-card class="card-sections">
<mat-card-header>
<mat-card-title>Search Articles By Title</mat-card-title>
</mat-card-header>
<mat-card-content>
<mat-input-container class="title-search">
<input matInput type="text" placeholder="Articles" [matAutocomplete]="auto" #title (keyup)="filterArticles(title.value)"/>
<mat-autocomplete #auto="matAutocomplete">
<mat-option class="article-option" *ngFor="let article of filteredArticles | async" [value]="article.title" (onSelectionChange)="articleSelected(article)">
{{ article.title }}
</mat-option>
</mat-autocomplete>
</mat-input-container>
</mat-card-content>
</mat-card>
<mat-card class="card-sections">
<mat-card-header>
<mat-card-title>About</mat-card-title>
</mat-card-header>
<mat-card-content>
<p>
This site was developed by <a href="https://github.com/pastorsj" target="_blank">Sam Pastoriza</a>
and <a href="https://github.com/Prescientje" target="_blank">James Edwards</a>.
We wanted a blog that was self sustaining and easily modifiable.
We are using the Angular framework developed by Google and a combination of
Angular Material and Bulma to style and layout the site respectively. An express based
backend application utilizes MongoDb and Redis to store data on users and articles. The
blog is completely open-sourced on GitHub and contributers are more than welcome. The end goal
is a fully functional blog mainly covering technical topics, but can be reused
as a base for other blogs.
</p>
</mat-card-content>
<mat-card-footer>
<mat-list>
<mat-list-item class="repo">
<mat-icon mat-list-icon>developer_board</mat-icon>
<a mat-list-item href="https://github.com/LighthouseBlog/Blog"> Blog </a>
</mat-list-item>
<mat-list-item class="repo">
<mat-icon mat-list-icon>developer_board</mat-icon>
<a mat-list-item href="https://github.com/pastorsj/blog-api"> Backend Api </a>
</mat-list-item>
</mat-list>
</mat-card-footer>
</mat-card>
<mat-card class="card-sections">
<mat-card-header>
<mat-card-title>Tags</mat-card-title>
</mat-card-header>
<mat-card-content>
<tag *ngFor="let tag of tags | async"
(click)="getArticlesByTag(tag)"
[tag]="tag"
[fontSize]="tagData[tag]"
[maxSize]="maxSize">
</tag>
</mat-card-content>
</mat-card>
</div>
</div>
</div>
</section>
</div>
22 changes: 22 additions & 0 deletions src/app/articles/articles.component.scss
Original file line number Diff line number Diff line change
@@ -1,4 +1,26 @@
.articles {
margin-left: 3vw;
margin-top: 2vh;
}

.repo:hover {
background-color: #bdbdbd;
}

.card-sections {
margin-bottom: 20px;
}

.title-search {
width: 100%;
max-height: 100px;
}

.article-option {
font-size: 10pt;
}

.cover-photo {
max-width: 100%;
height: auto;
}
32 changes: 32 additions & 0 deletions src/app/articles/articles.component.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { Component, OnInit } from '@angular/core';

import { Observable } from 'rxjs/Observable';

import { ArticleService } from '../_services/article.service';
import { Router } from '@angular/router';
import { TagService } from '../_services/tags.service';
import { TagComponent } from './tag/tag.component';
import { ArticleList } from '../_models/ArticleList';

@Component({
selector: 'app-articles',
Expand All @@ -11,19 +16,46 @@ import { Router } from '@angular/router';
export class ArticlesComponent implements OnInit {

public articles;
public tags: Promise<Array<String>>;
public tagData: Object;
public maxSize: Number
public filteredArticles: Observable<ArticleList[]>;

constructor(
private articleService: ArticleService,
private tagService: TagService,
private router: Router
) { }

ngOnInit() {
this.articles = this.articleService.getAllArticles();
this.tagService.getAllTags()
.subscribe((tags) => {
this.tagData = tags;
this.tags = Promise.resolve(Object.keys(tags));
this.maxSize = Object.keys(tags).map((tag) => {
return parseInt(tags[tag], 10);
}).reduce((accumulator, currentValue) => {
return Math.max(accumulator, currentValue);
}, 0);
});
}

selectedArticle(e) {
this.articleService.setArticleId(e._id);
this.router.navigate(['article', e._id]);
}

getArticlesByTag(tag: string) {
this.articles = this.tagService.getArticlesByTag(tag);
}

filterArticles(title: string) {
this.filteredArticles = this.articleService.getArticlesByTitle(title);
}

articleSelected(article: ArticleList) {
this.router.navigate(['article', article._id]);
}

}
Loading

0 comments on commit cc58d99

Please sign in to comment.