Skip to content

Commit cc58d99

Browse files
authored
Merge pull request #41 from LighthouseBlog/feature/layout
Feature/layout
2 parents 3e83b2e + ad793d4 commit cc58d99

26 files changed

+417
-127
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ typings/
66
npm-debug.log
77
dist/
88

9-
*.pem
9+
*.pem
10+
*.rdb

src/app/_models/Article.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1+
import { Author } from './Author';
12
export class Article {
23
_id: number;
34
title: string;
45
description: string;
5-
datePosted: string;
6+
datePosted: Date;
67
text: string;
7-
author: string;
8+
author: Author;
89
tags: Array<string>;
910
}

src/app/_models/ArticleList.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export class ArticleList {
2+
_id: number;
3+
title: string;
4+
tags: Array<string>;
5+
}

src/app/_services/article.service.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,13 @@ import 'rxjs/add/operator/map';
77

88
import { AuthenticationService } from '../_services/authentication.service';
99
import { Article } from '../_models/Article';
10+
import { ArticleList } from '../_models/ArticleList';
1011
import { environment } from '../../environments/environment';
1112

1213
@Injectable()
1314
export class ArticleService {
1415

15-
private editorUrl = environment.URL + '/blog/';
16+
private blogUrl = environment.URL + '/blog/';
1617
private title = '';
1718
private id: string;
1819

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

3637
const options = new RequestOptions({ headers });
3738

38-
return this.http.get(this.editorUrl, options)
39+
return this.http.get(this.blogUrl, options)
3940
.map(this.extractData)
4041
.catch(this.handleError);
4142
}
4243

4344
getArticle(id: number): Observable<Article> {
44-
return this.http.get(this.editorUrl + id)
45+
return this.http.get(this.blogUrl + id)
46+
.map(this.extractData)
47+
.catch(this.handleError);
48+
}
49+
50+
getArticlesByTitle(title: string): Observable<ArticleList[]> {
51+
return this.http.get(this.blogUrl + 'title/' + title)
4552
.map(this.extractData)
4653
.catch(this.handleError);
4754
}

src/app/_services/authentication.service.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ export class AuthenticationService {
7474
reject(`Error ${error}`);
7575
});
7676
} else {
77-
reject('No token available');
77+
reject();
7878
}
7979
})
8080
}

src/app/_services/editor.service.ts

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -58,26 +58,32 @@ export class EditorService {
5858
.catch(this.handleError);
5959
}
6060

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

65+
const options = new RequestOptions({ headers });
66+
6667
const author = JSON.parse(localStorage.getItem('currentUser'));
6768

6869
const post = {
6970
text: edits,
70-
title: this.title,
71-
description: this.description,
71+
title: title,
72+
description: description,
7273
tags,
7374
author: author.username
7475
};
7576

76-
const options = new RequestOptions({ headers });
77-
78-
return this.http.put(this.editorUrl + this.id, post, options)
79-
.map(this.extractData)
80-
.catch(this.handleError);
77+
if (coverPhoto) {
78+
return Observable.forkJoin(
79+
this.http.put(this.editorUrl + this.id, post, options).map(this.extractData).catch(this.handleError),
80+
this.http.post(this.editorUrl + this.id, coverPhoto, options).map(this.extractData).catch(this.handleError)
81+
);
82+
} else {
83+
return Observable.forkJoin(
84+
this.http.put(this.editorUrl + this.id, post, options).map(this.extractData).catch(this.handleError)
85+
);
86+
}
8187
}
8288

8389
deleteArticle(article): Observable<boolean> {

src/app/_services/tags.service.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { Injectable } from '@angular/core';
2+
import { Http, Response, RequestOptions, Headers } from '@angular/http';
3+
4+
import { Observable } from 'rxjs/Observable';
5+
import 'rxjs/add/operator/catch';
6+
import 'rxjs/add/operator/map';
7+
8+
import { AuthenticationService } from '../_services/authentication.service';
9+
import { Article } from '../_models/Article';
10+
import { environment } from '../../environments/environment';
11+
12+
@Injectable()
13+
export class TagService {
14+
15+
private tagUrl = environment.URL + '/tags/';
16+
17+
constructor(
18+
private http: Http,
19+
private auth: AuthenticationService
20+
) { }
21+
22+
getAllTags(): Observable<Array<String>> {
23+
return this.http.get(this.tagUrl)
24+
.map(this.extractData)
25+
.catch(this.handleError);
26+
}
27+
28+
getArticlesByTag(tag: string): Observable<Article> {
29+
return this.http.get(this.tagUrl + tag)
30+
.map(this.extractData)
31+
.catch(this.handleError);
32+
}
33+
34+
private extractData(res: Response) {
35+
const body = res.json();
36+
return body.data || { };
37+
}
38+
39+
private handleError (error: Response | any) {
40+
// In a real world app, you might use a remote logging infrastructure
41+
let errMsg: string;
42+
if (error instanceof Response) {
43+
const body = error.json() || '';
44+
const err = body.error || JSON.stringify(body);
45+
errMsg = `${error.status} - ${error.statusText || ''} ${err}`;
46+
} else {
47+
errMsg = error.message ? error.message : error.toString();
48+
}
49+
console.error(errMsg);
50+
return Observable.throw(errMsg);
51+
}
52+
53+
}

src/app/app.module.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,16 @@ import {Router} from './app.routing';
1818
import { EditorComponent } from './editor/editor.component';
1919
import { UserArticlesComponent } from './user-articles/user-articles.component';
2020
import { CreateArticleModalComponent } from './create-article-modal/create-article-modal.component';
21+
import { TagComponent } from './articles/tag/tag.component';
2122

2223
import { AuthGuard } from './_guards/auth.guard';
2324
import { AuthenticationService } from './_services/authentication.service';
2425
import { EditorService } from './_services/editor.service';
2526
import { ArticleService } from './_services/article.service';
2627
import { AuthorService } from './_services/author.service';
2728
import { ImagesService } from './_services/images.service';
29+
import { TagService } from './_services/tags.service';
30+
2831
import { FileValidator } from './_directives/fileValidator.directive';
2932
import { FileValueAccessor } from './_directives/fileValueAccessor.directive';
3033

@@ -49,7 +52,8 @@ import { MaterialModule } from './material.module';
4952
DeleteArticleModalComponent,
5053
SettingsModalComponent,
5154
FileValidator,
52-
FileValueAccessor
55+
FileValueAccessor,
56+
TagComponent
5357
],
5458
imports: [
5559
BrowserAnimationsModule,
@@ -78,6 +82,7 @@ import { MaterialModule } from './material.module';
7882
ArticleService,
7983
AuthorService,
8084
ImagesService,
85+
TagService,
8186
BaseRequestOptions
8287
],
8388
bootstrap: [
Lines changed: 85 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,87 @@
11
<div class="articles">
2-
<mat-grid-list cols="4">
3-
<mat-grid-tile *ngFor="let article of articles | async">
4-
<mat-card class="example-card">
5-
<mat-card-header>
6-
<mat-card-title>{{article?.title}}</mat-card-title>
7-
</mat-card-header>
8-
<mat-card-content>
9-
<p>
10-
{{article?.description}}
11-
</p>
12-
</mat-card-content>
13-
<mat-card-actions>
14-
<button mat-button (click)="selectedArticle(article)"> READ </button>
15-
</mat-card-actions>
16-
</mat-card>
17-
</mat-grid-tile>
18-
</mat-grid-list>
2+
<section class="section">
3+
<div class="container">
4+
<div class="columns">
5+
<div class="column is-two-thirds">
6+
<mat-card class="article" *ngFor="let article of articles | async">
7+
<mat-card-header>
8+
<mat-card-title>
9+
{{article?.title}}
10+
</mat-card-title>
11+
<mat-card-subtitle>
12+
By {{article?.author?.name}}, {{article?.datePosted | date:'longDate'}}
13+
</mat-card-subtitle>
14+
</mat-card-header>
15+
<img class="cover-photo" mat-card-image *ngIf="article?.coverPhoto" src="{{article?.coverPhoto}}" />
16+
<mat-card-content>
17+
{{article?.description}}
18+
</mat-card-content>
19+
<mat-card-actions align="end">
20+
<button mat-button (click)="selectedArticle(article)"> Continue Reading... </button>
21+
</mat-card-actions>
22+
</mat-card>
23+
</div>
24+
<div class="column">
25+
<mat-card class="card-sections">
26+
<mat-card-header>
27+
<mat-card-title>Search Articles By Title</mat-card-title>
28+
</mat-card-header>
29+
<mat-card-content>
30+
<mat-input-container class="title-search">
31+
<input matInput type="text" placeholder="Articles" [matAutocomplete]="auto" #title (keyup)="filterArticles(title.value)"/>
32+
<mat-autocomplete #auto="matAutocomplete">
33+
<mat-option class="article-option" *ngFor="let article of filteredArticles | async" [value]="article.title" (onSelectionChange)="articleSelected(article)">
34+
{{ article.title }}
35+
</mat-option>
36+
</mat-autocomplete>
37+
</mat-input-container>
38+
</mat-card-content>
39+
</mat-card>
40+
<mat-card class="card-sections">
41+
<mat-card-header>
42+
<mat-card-title>About</mat-card-title>
43+
</mat-card-header>
44+
<mat-card-content>
45+
<p>
46+
This site was developed by <a href="https://github.com/pastorsj" target="_blank">Sam Pastoriza</a>
47+
and <a href="https://github.com/Prescientje" target="_blank">James Edwards</a>.
48+
We wanted a blog that was self sustaining and easily modifiable.
49+
We are using the Angular framework developed by Google and a combination of
50+
Angular Material and Bulma to style and layout the site respectively. An express based
51+
backend application utilizes MongoDb and Redis to store data on users and articles. The
52+
blog is completely open-sourced on GitHub and contributers are more than welcome. The end goal
53+
is a fully functional blog mainly covering technical topics, but can be reused
54+
as a base for other blogs.
55+
</p>
56+
</mat-card-content>
57+
<mat-card-footer>
58+
<mat-list>
59+
<mat-list-item class="repo">
60+
<mat-icon mat-list-icon>developer_board</mat-icon>
61+
<a mat-list-item href="https://github.com/LighthouseBlog/Blog"> Blog </a>
62+
</mat-list-item>
63+
<mat-list-item class="repo">
64+
<mat-icon mat-list-icon>developer_board</mat-icon>
65+
<a mat-list-item href="https://github.com/pastorsj/blog-api"> Backend Api </a>
66+
</mat-list-item>
67+
</mat-list>
68+
</mat-card-footer>
69+
</mat-card>
70+
<mat-card class="card-sections">
71+
<mat-card-header>
72+
<mat-card-title>Tags</mat-card-title>
73+
</mat-card-header>
74+
<mat-card-content>
75+
<tag *ngFor="let tag of tags | async"
76+
(click)="getArticlesByTag(tag)"
77+
[tag]="tag"
78+
[fontSize]="tagData[tag]"
79+
[maxSize]="maxSize">
80+
</tag>
81+
</mat-card-content>
82+
</mat-card>
83+
</div>
84+
</div>
85+
</div>
86+
</section>
1987
</div>
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,26 @@
11
.articles {
22
margin-left: 3vw;
33
margin-top: 2vh;
4+
}
5+
6+
.repo:hover {
7+
background-color: #bdbdbd;
8+
}
9+
10+
.card-sections {
11+
margin-bottom: 20px;
12+
}
13+
14+
.title-search {
15+
width: 100%;
16+
max-height: 100px;
17+
}
18+
19+
.article-option {
20+
font-size: 10pt;
21+
}
22+
23+
.cover-photo {
24+
max-width: 100%;
25+
height: auto;
426
}

0 commit comments

Comments
 (0)