| |
@@ -4,7 +4,9 @@
|
| |
import { DatabaseProvider } from '../database/database';
|
| |
import { Observable } from 'rxjs/Observable';
|
| |
import { from } from 'rxjs/observable/from';
|
| |
+ import { forkJoin } from 'rxjs/observable/forkJoin';
|
| |
import { chooseEndpoint } from '../../utils';
|
| |
+ import { Storage } from '@ionic/storage';
|
| |
|
| |
// Blog definitions
|
| |
export const BLOG = Object.seal({
|
| |
@@ -18,7 +20,10 @@
|
| |
id: 'communityBlog',
|
| |
title: 'Fedora Community Blog',
|
| |
originalEndpoint: 'https://communityblog.fedoraproject.org',
|
| |
- endpoint: chooseEndpoint('/community-blog', 'https://communityblog.fedoraproject.org')
|
| |
+ endpoint: chooseEndpoint(
|
| |
+ '/community-blog',
|
| |
+ 'https://communityblog.fedoraproject.org'
|
| |
+ )
|
| |
}
|
| |
});
|
| |
|
| |
@@ -29,68 +34,91 @@
|
| |
/**
|
| |
* Unique ID of the post, supplied by the CMS
|
| |
*/
|
| |
- id: number,
|
| |
+ id: number;
|
| |
|
| |
/**
|
| |
* A sluggified link to the post
|
| |
*/
|
| |
- link: string,
|
| |
+ link: string;
|
| |
|
| |
/**
|
| |
* Permalink to the post
|
| |
*/
|
| |
- permalink: string,
|
| |
+ permalink: string;
|
| |
|
| |
/**
|
| |
* Post title
|
| |
*/
|
| |
- title: string,
|
| |
+ title: string;
|
| |
|
| |
/**
|
| |
* A short excerpt of the post
|
| |
*/
|
| |
- excerpt: string,
|
| |
+ excerpt: string;
|
| |
|
| |
/**
|
| |
* The content of the post
|
| |
*/
|
| |
- content: string,
|
| |
+ content: string;
|
| |
|
| |
/**
|
| |
* Time of publication
|
| |
*/
|
| |
- publishedAt: Date
|
| |
+ publishedAt: Date;
|
| |
|
| |
/**
|
| |
* Number of comments on the article
|
| |
*/
|
| |
- comments: number,
|
| |
+ comments: number;
|
| |
|
| |
/**
|
| |
* Featured Image of article
|
| |
*/
|
| |
- featuredImage: string
|
| |
+ featuredImage: string;
|
| |
}
|
| |
|
| |
interface BlogPostFetchArgs {
|
| |
- page?: number,
|
| |
- orderBy?: 'date' | 'title',
|
| |
- order?: 'asc' | 'desc',
|
| |
- categories?: string[],
|
| |
- tags?: string[]
|
| |
+ page?: number;
|
| |
+ orderBy?: 'date' | 'title';
|
| |
+ order?: 'asc' | 'desc';
|
| |
+ categories?: string[];
|
| |
+ tags?: string[];
|
| |
}
|
| |
|
| |
/**
|
| |
* Represents a single wordpress blog
|
| |
*/
|
| |
export class Blog {
|
| |
-
|
| |
public postsPerPage = 10;
|
| |
|
| |
- public readonly posts$;
|
| |
+ private lastUpdatedStorageKey = '';
|
| |
+
|
| |
+ constructor(
|
| |
+ public readonly id: string,
|
| |
+ public readonly endpoint: string,
|
| |
+ private http: HttpClient,
|
| |
+ private db: DatabaseProvider,
|
| |
+ private storage: Storage
|
| |
+ ) {
|
| |
+ this.lastUpdatedStorageKey = `wordpress_blog__${id}__last_updated`;
|
| |
+ }
|
| |
+
|
| |
+ /**
|
| |
+ * Returns a promise yielding a date when the blog was last updated.
|
| |
+ *
|
| |
+ * The last updated time is 1st January 1970, if the blog is fetched for the
|
| |
+ * first time.
|
| |
+ *
|
| |
+ * @return Promise which yields the last updated date and time
|
| |
+ */
|
| |
+ lastUpdated(): Promise<Date> {
|
| |
+ return this.storage
|
| |
+ .get(this.lastUpdatedStorageKey)
|
| |
+ .then(d => (d ? d : new Date(0)));
|
| |
+ }
|
| |
|
| |
- constructor(public readonly id: string, public readonly endpoint: string,
|
| |
- private http: HttpClient, private db: DatabaseProvider) {
|
| |
+ private setLastUpdated(time: Date) {
|
| |
+ return this.storage.set(this.lastUpdatedStorageKey, time);
|
| |
}
|
| |
|
| |
/**
|
| |
@@ -98,7 +126,9 @@
|
| |
*
|
| |
* @returns Observable which emits an array of posts
|
| |
*/
|
| |
- public fetchPosts(funcParams: BlogPostFetchArgs = {}): Observable<BlogPost[]> {
|
| |
+ public fetchPosts(
|
| |
+ funcParams: BlogPostFetchArgs = {}
|
| |
+ ): Observable<BlogPost[]> {
|
| |
const {
|
| |
page = 1,
|
| |
orderBy = 'date',
|
| |
@@ -112,21 +142,31 @@
|
| |
page,
|
| |
orderby: orderBy,
|
| |
order,
|
| |
- per_page: this.postsPerPage,
|
| |
- }
|
| |
+ per_page: this.postsPerPage
|
| |
+ };
|
| |
|
| |
if (categories.length > 0) {
|
| |
- params.categories = categories.join(',')
|
| |
+ params.categories = categories.join(',');
|
| |
}
|
| |
|
| |
if (tags.length > 0) {
|
| |
- params.tags = tags.join(',')
|
| |
+ params.tags = tags.join(',');
|
| |
}
|
| |
|
| |
- return this.http.get(`${this.endpoint}/wp-json/wp/v2/posts`, { params })
|
| |
- .pipe(
|
| |
- map((data: any[]) => data.map((post: any) => {
|
| |
- const featuredMedia = post._embedded['wp:featuredmedia'] && post._embedded['wp:featuredmedia'][0];
|
| |
+ return forkJoin(
|
| |
+ this.http.get(`${this.endpoint}/wp-json/wp/v2/posts`, { params }),
|
| |
+ this.lastUpdated()
|
| |
+ ).pipe(
|
| |
+ map(([data, lastUpdated]: [any[], Date]) =>
|
| |
+ data.map((post: any) => {
|
| |
+ const publishedAt = new Date(post.date_gmt + 'Z');
|
| |
+
|
| |
+ // We can do lt,gt comparisons on Date objects, but not a === comparison
|
| |
+ const newPost = lastUpdated <= publishedAt;
|
| |
+
|
| |
+ const featuredMedia =
|
| |
+ post._embedded['wp:featuredmedia'] &&
|
| |
+ post._embedded['wp:featuredmedia'][0];
|
| |
return {
|
| |
id: post.id as number,
|
| |
link: post.link as string,
|
| |
@@ -135,14 +175,19 @@
|
| |
excerpt: post.excerpt.rendered as string,
|
| |
content: post.content.rendered as string,
|
| |
publishedAt: new Date(post.date_gmt + 'Z'),
|
| |
- featuredImage: featuredMedia && (
|
| |
- featuredMedia.media_details.sizes.medium ?
|
| |
- featuredMedia.media_details.sizes.medium.source_url :
|
| |
- featuredMedia.media_details.source_url
|
| |
- ),
|
| |
- comments: post._embedded['replies'] !== undefined ? post._embedded['replies']['0'].length : 0,
|
| |
+ newPost,
|
| |
+ featuredImage:
|
| |
+ featuredMedia &&
|
| |
+ (featuredMedia.media_details.sizes.medium
|
| |
+ ? featuredMedia.media_details.sizes.medium.source_url
|
| |
+ : featuredMedia.media_details.source_url),
|
| |
+ comments:
|
| |
+ post._embedded['replies'] !== undefined
|
| |
+ ? post._embedded['replies']['0'].length
|
| |
+ : 0
|
| |
};
|
| |
- })),
|
| |
+ })
|
| |
+ )
|
| |
);
|
| |
}
|
| |
|
| |
@@ -152,29 +197,34 @@
|
| |
* @returns Observable which emits the updated list of posts
|
| |
*/
|
| |
public updatePosts(): Observable<BlogPost[]> {
|
| |
- return this.fetchPosts()
|
| |
- .pipe(
|
| |
- tap(
|
| |
- async posts => {
|
| |
- // Store data in IndexedDB
|
| |
- try {
|
| |
- const table = this.db.table<BlogPost>(this.id);
|
| |
- await this.db.transaction('rw', table, async () => {
|
| |
- // We perform an upsert using bulkPut
|
| |
- await table.bulkPut(posts);
|
| |
- const count = await table.count();
|
| |
- if (count > this.postsPerPage) {
|
| |
- // Delete old posts
|
| |
- await table.orderBy('publishedAt').reverse().offset(this.postsPerPage).delete();
|
| |
- }
|
| |
- });
|
| |
- } catch (error) {
|
| |
- // We ignore this error, but log it for debugging
|
| |
- console.error('Unable to persist posts');
|
| |
- console.error(error);
|
| |
- }
|
| |
- })
|
| |
- );
|
| |
+ return this.fetchPosts().pipe(
|
| |
+ tap(async posts => {
|
| |
+ // Store data in IndexedDB
|
| |
+ try {
|
| |
+ const table = this.db.table<BlogPost>(this.id);
|
| |
+ await Promise.all([
|
| |
+ this.db.transaction('rw', table, async () => {
|
| |
+ // We perform an upsert using bulkPut
|
| |
+ await table.bulkPut(posts);
|
| |
+ const count = await table.count();
|
| |
+ if (count > this.postsPerPage) {
|
| |
+ // Delete old posts
|
| |
+ await table
|
| |
+ .orderBy('publishedAt')
|
| |
+ .reverse()
|
| |
+ .offset(this.postsPerPage)
|
| |
+ .delete();
|
| |
+ }
|
| |
+ }),
|
| |
+ this.setLastUpdated(new Date())
|
| |
+ ]);
|
| |
+ } catch (error) {
|
| |
+ // We ignore this error, but log it for debugging
|
| |
+ console.error('Unable to persist posts');
|
| |
+ console.error(error);
|
| |
+ }
|
| |
+ })
|
| |
+ );
|
| |
}
|
| |
|
| |
/**
|
| |
@@ -184,8 +234,11 @@
|
| |
*/
|
| |
public loadCachedPosts(): Observable<BlogPost[]> {
|
| |
return from(
|
| |
- this.db.table(this.id)
|
| |
- .orderBy('publishedAt').reverse().toArray()
|
| |
+ this.db
|
| |
+ .table(this.id)
|
| |
+ .orderBy('publishedAt')
|
| |
+ .reverse()
|
| |
+ .toArray()
|
| |
);
|
| |
}
|
| |
}
|
| |
@@ -195,10 +248,13 @@
|
| |
*/
|
| |
@Injectable()
|
| |
export class WordPressBlogProvider {
|
| |
+ private blogs: Map<string, Blog>;
|
| |
|
| |
- private blogs: Map<string, Blog>
|
| |
-
|
| |
- constructor(private http: HttpClient, private db: DatabaseProvider) {
|
| |
+ constructor(
|
| |
+ private http: HttpClient,
|
| |
+ private db: DatabaseProvider,
|
| |
+ private storage: Storage
|
| |
+ ) {
|
| |
this.blogs = new Map();
|
| |
}
|
| |
|
| |
@@ -207,10 +263,12 @@
|
| |
*
|
| |
* @param blog A blog definition
|
| |
*/
|
| |
- getBlog(blog: { id: string, endpoint: string }) {
|
| |
-
|
| |
+ getBlog(blog: { id: string; endpoint: string }) {
|
| |
if (!this.blogs.has(blog.id)) {
|
| |
- this.blogs.set(blog.id, new Blog(blog.id, blog.endpoint, this.http, this.db));
|
| |
+ this.blogs.set(
|
| |
+ blog.id,
|
| |
+ new Blog(blog.id, blog.endpoint, this.http, this.db, this.storage)
|
| |
+ );
|
| |
}
|
| |
|
| |
return this.blogs.get(blog.id);
|
| |