#98 Support highlighting new items on blogs
Merged a year ago by amitosh. Opened 2 years ago by amitosh.
amitosh/Fedora-app new-tracking  into  master

@@ -1,11 +1,11 @@ 

  <ion-header no-border>

    <ion-navbar>

      <ion-title>

-       <img src="./assets/img/Fedora.svg" height="24px" alt="Fedora">

+       <img src="./assets/img/Fedora.svg" height="24px" alt="Fedora" />

      </ion-title>

      <ion-buttons end>

        <button tappable (click)="openNotificationPage()">

-         <img src="./assets/img/notification.svg">

+         <img src="./assets/img/notification.svg" />

        </button>

      </ion-buttons>

    </ion-navbar>

@@ -16,24 +16,44 @@ 

      Latest Articles from Magazine

      <button (click)="presentActionSheet()">

        Sort

-       <img src='./assets/img/sort.svg'>

+       <img src="./assets/img/sort.svg" />

      </button>

    </ion-label>

    <ion-list>

-     <ion-card *ngFor="let post of posts let i = index">

+     <ion-card

+       class="post"

+       *ngFor="let post of posts; let i = index"

+       [ngClass]="{ new: post.newPost }"

+     >

        <ion-row>

          <ion-col col-7>

-           <ion-card-header [innerHTML]="post.title" class="body-title" text-wrap tappable (click)="openPost(post)"></ion-card-header>

+           <ion-card-header

+             [innerHTML]="post.title"

+             class="body-title"

+             text-wrap

+             tappable

+             (click)="openPost(post)"

+           ></ion-card-header>

            <ion-card-content>

-             <span class="body-subtitle">{{ beautify(post.publishedAt) }} — {{ post.comments }} comments</span>

-             <img [src]=bookmarkIcon[i] tappable (click)="addToBookmark(post,i)" class="bookmark">

+             <span class="body-subtitle"

+               >{{ beautify(post.publishedAt) }} —

+               {{ post.comments }} comments</span

+             >

+             <img

+               [src]="bookmarkIcon[i]"

+               tappable

+               (click)="addToBookmark(post, i)"

+               class="bookmark"

+             />

            </ion-card-content>

          </ion-col>

          <ion-col col-5 tappable (click)="openPost(post)">

-           <img src="{{post.featuredImage}}" class="card-image">

+           <img src="{{ post.featuredImage }}" class="card-image" />

          </ion-col>

        </ion-row>

      </ion-card>

    </ion-list>

-   <button (click)="loadMore()" class="load" ng-if="posts.length!==0">Load More Posts</button>

+   <button (click)="loadMore()" class="load" ng-if="posts.length!==0">

+     Load More Posts

+   </button>

  </ion-content>

@@ -9,6 +9,13 @@ 

      ion-card-header {

        padding: 0 !important;

      }

+ 

+     &.new {

+         .body-title {

+             font-weight: bold;

+         }

+     }

+ 

      ion-card-content {

        margin-top: 4px;

        padding: 0 !important;

@@ -40,9 +40,9 @@ 

  

    private blog: Blog;

  

-   private loading: boolean = false;

+   loading: boolean = false;

  

-   private error: boolean = false;

+   error: boolean = false;

  

    constructor(private browser: Browser,

      private navCtrl: NavController,

@@ -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);