From 824f8c35ef8d43c4bbfdc89ed068799e37079c9b Mon Sep 17 00:00:00 2001 From: Amitosh Swain Mahapatra Date: May 30 2018 07:00:27 +0000 Subject: [PATCH 1/4] Add type declarations to Http and Social providers --- diff --git a/package-lock.json b/package-lock.json index 719245a..90db4c2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4123,6 +4123,11 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.5.tgz", "integrity": "sha512-svL3uiZf1RwhH+cWrfZn3A4+U58wbP0tGVTLQPbjplZxZ8ROD9VLuNgsRniTlLe7OlSqR79RUehXgpBW/s0IQw==" }, + "lodash-es": { + "version": "4.17.10", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.10.tgz", + "integrity": "sha512-iesFYPmxYYGTcmQK0sL8bX3TGHyM6b2qREaB4kamHfQyfPJP0xgoGxp19nsH16nsfquLdiyKyX3mQkfiSGV8Rg==" + }, "lodash.assign": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz", diff --git a/package.json b/package.json index c529928..eea4ccf 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,7 @@ "ionic-plugin-keyboard": "^2.2.1", "ionicons": "^3.0.0", "lodash": "^4.14.0", + "lodash-es": "^4.17.10", "moment": "^2.14.1", "moment-timezone": "^0.5.5", "query-string": "^4.2.2", diff --git a/src/app/app.module.ts b/src/app/app.module.ts index b5f9309..1378f59 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -18,8 +18,10 @@ import { SocialPage } from '../pages/social/social'; import { WomenPage } from '../pages/women/women'; import { Browser } from '../providers/browser/browser'; -import { Request } from '../providers/request/request'; +import { Http } from '../providers/http/http'; import { IonicConfig } from '../providers/ionic-config/ionic-config'; +import { FacebookProvider } from '../providers/social/facebook'; +import { TwitterProvider } from '../providers/social/twitter'; @NgModule({ declarations: [ @@ -50,13 +52,15 @@ import { IonicConfig } from '../providers/ionic-config/ionic-config'; Browser, IonicConfig, InAppBrowser, - Request, + Http, SocialSharing, SplashScreen, SpinnerDialog, StatusBar, Toast, - { provide: ErrorHandler, useClass: IonicErrorHandler } + FacebookProvider, + TwitterProvider, + { provide: ErrorHandler, useClass: IonicErrorHandler }, ] }) export class AppModule { } diff --git a/src/pages/ask/ask.ts b/src/pages/ask/ask.ts index 95433d0..7cf52af 100644 --- a/src/pages/ask/ask.ts +++ b/src/pages/ask/ask.ts @@ -2,7 +2,7 @@ import { Component } from '@angular/core'; import { SocialSharing } from '@ionic-native/social-sharing'; import { Browser } from '../../providers/browser/browser'; -import { AskFedora } from '../../providers/ask-fedora/ask-fedora'; +import { AskFedoraService } from '../../providers/ask-fedora/ask-fedora'; /* Generated class for the AskPage page. @@ -12,13 +12,13 @@ import { AskFedora } from '../../providers/ask-fedora/ask-fedora'; */ @Component({ templateUrl: 'ask.html', - providers: [Browser, AskFedora], + providers: [Browser, AskFedoraService], }) export class AskPage { private questions: any; constructor(private browser: Browser, - private askFedora: AskFedora, private socialSharing: SocialSharing) { + private askFedora: AskFedoraService, private socialSharing: SocialSharing) { this.questions = []; } diff --git a/src/pages/social/social.ts b/src/pages/social/social.ts index 7219971..0b30a6f 100644 --- a/src/pages/social/social.ts +++ b/src/pages/social/social.ts @@ -2,8 +2,9 @@ import { Component } from '@angular/core'; import { SocialSharing } from '@ionic-native/social-sharing'; import { Browser } from '../../providers/browser/browser'; -import { FB } from '../../providers/fb/fb'; -import { Tw } from '../../providers/tw/tw'; +import { FacebookProvider } from '../../providers/social/facebook'; +import { TwitterProvider } from '../../providers/social/twitter'; +import { Post } from '../../providers/social/social'; /* Generated class for the SocialPage page. @@ -13,18 +14,16 @@ import { Tw } from '../../providers/tw/tw'; */ @Component({ templateUrl: 'social.html', - providers: [FB, Tw], + providers: [FacebookProvider, TwitterProvider], }) export class SocialPage { - // TODO: Refactor, we are not supposed to use `any`, rather, ww should define an - // interface Post { content:string, date:Date } - private posts:Array; - private tweets:Array; + private posts:Post[]; + private tweets:Post[]; private updates:Array; private USER:any; - constructor(private browser:Browser, private fb:FB, private tw:Tw, + constructor(private browser:Browser, private fb:FacebookProvider, private tw:TwitterProvider, private socialSharing:SocialSharing) { this.posts = []; this.tweets = []; @@ -42,14 +41,14 @@ export class SocialPage { updateUpdates() { this.fb - .getPagePosts(this.USER.FB) + .getPosts(this.USER.FB) .then((posts:Array) => { this.posts = posts; this.mergeUpdates(); }); this.tw - .getTimelineTweets(this.USER.TW) + .getPosts(this.USER.TW) .then((tweets:Array) => { this.tweets = tweets; this.mergeUpdates(); diff --git a/src/providers/ask-fedora/ask-fedora.ts b/src/providers/ask-fedora/ask-fedora.ts index 7929f70..f32e1cf 100644 --- a/src/providers/ask-fedora/ask-fedora.ts +++ b/src/providers/ask-fedora/ask-fedora.ts @@ -1,12 +1,23 @@ import { Injectable } from '@angular/core'; import 'rxjs/add/operator/map'; -import * as _ from 'lodash'; - -import { Request } from '../request/request'; +import { Http } from '../http/http'; const API_ENDPOINT = 'https://ask.fedoraproject.org/en/api/v1'; +export interface Question { + id: string, + title: string, + link: string, + answers: number, + content: string, + timestamp: Date, + tags: string[], + view: number, + vote: number, +} + + /* Generated class for the AskFedora provider. @@ -14,11 +25,11 @@ const API_ENDPOINT = 'https://ask.fedoraproject.org/en/api/v1'; for more info on providers and Angular 2 DI. */ @Injectable() -export class AskFedora { - private API:any; - private questions:Array; +export class AskFedoraService { + private API: any; + private questions: Question[]; - constructor(private request:Request) { + constructor(private request: Http) { this.API = { base: [API_ENDPOINT], questions: [API_ENDPOINT, 'questions'], @@ -28,30 +39,29 @@ export class AskFedora { } getQuestions() { - if (!_.isEmpty(this.questions)) { + if (this.questions.length) { return Promise.resolve(this.questions); } - return new Promise((resolve, reject) => { - this.request.get(this.API.questions) - .then((data:any) => { - this.questions = _.map(data.questions, q => { - const question = { - id: q.id, - title: q.title, - link: q.url, - answers: q.answer_count, - content: q.summary, - timestamp: q.added_at, - tags:q.tags, - view:q.view_count, - vote:q.score, - }; - return question; - }); - - return resolve(this.questions); - }).catch(reject); - }); + return this.request.get(this.API.questions) + .toPromise() + .then((data: any) => { + this.questions = data.questions.map(q => { + const question = { + id: q.id, + title: q.title, + link: q.url, + answers: q.answer_count, + content: q.summary, + timestamp: q.added_at, + tags: q.tags, + view: q.view_count, + vote: q.score, + }; + return question; + }); + + return this.questions; + }); } } diff --git a/src/providers/fb/fb.ts b/src/providers/fb/fb.ts deleted file mode 100644 index 4a12aa8..0000000 --- a/src/providers/fb/fb.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { Injectable } from '@angular/core'; -import 'rxjs/add/operator/map'; -import ENV from '@environment'; - -import { Facebook } from 'fb'; -import * as _ from 'lodash'; - - -/* - Generated class for the Fb provider. - - See https://angular.io/docs/ts/latest/guide/dependency-injection.html - for more info on providers and Angular 2 DI. -*/ -@Injectable() -export class FB { - private fb:Facebook; - constructor() { - this.fb = new Facebook(ENV.FB_CONFIG); - } - - api(urlParts) { - return new Promise((resolve, reject) => { - this.fb.api(urlParts.join('/'), res => { - if (!res || res.error) { - return reject(res); - } else { - return resolve(res); - } - }); - }); - } - - getPagePosts(page) { - return new Promise((resolve, reject) => { - this.api([page, 'posts']).then((res:any) => { - var posts = _.compact(_.map(res.data, p => { - var post = { - id: p.id, - link: 'https://facebook.com/' + p.id, - content: p.message, - origin: 'facebook', - }; - - if (_.isEmpty(post.content)) { - return null; - } else { - return post; - } - })); - - return resolve(posts); - }).catch(reject); - }); - } -} diff --git a/src/providers/fedo-cal/fedo-cal.ts b/src/providers/fedo-cal/fedo-cal.ts index 3a6288a..cfcc03d 100644 --- a/src/providers/fedo-cal/fedo-cal.ts +++ b/src/providers/fedo-cal/fedo-cal.ts @@ -4,7 +4,7 @@ import 'rxjs/add/operator/map'; import * as _ from 'lodash'; import * as moment from 'moment-timezone'; -import { Request } from '../request/request'; +import { Http } from '../http/http'; const API_ENDPOINT = 'https://apps.fedoraproject.org/calendar/api/'; const API = { @@ -19,8 +19,8 @@ const TIME_FORMAT = 'h:mm a z'; /* Convert title to capital case, but skip special names like FESCo, FAmSCo, i18n */ -function calendarNameToDisplayName(name:string) :string { - switch(name) { +function calendarNameToDisplayName(name: string): string { + switch (name) { case 'i18n': return 'i18n'; case 'fesco': @@ -39,10 +39,10 @@ function calendarNameToDisplayName(name:string) :string { */ @Injectable() export class FedoCal { - private calendars:any; - private meetings:any; + private calendars: any; + private meetings: any; - constructor(private request:Request) { + constructor(private request: Http) { this.calendars = []; this.meetings = []; } @@ -52,21 +52,20 @@ export class FedoCal { return Promise.resolve(this.calendars); } - return new Promise((resolve, reject) => { - this.request.get(API.calendars) - .then((data:any) => { - this.calendars = _.map(data.calendars, c => { - return { - real_name: c.calendar_name, - display_name: calendarNameToDisplayName(c.calendar_name), - description: c.calendar_description, - contact: c.calendar_contact, - }; - }); - - return resolve(this.calendars); - }).catch(reject); - }); + return this.request.get(API.calendars) + .toPromise() + .then((data: any) => { + this.calendars = _.map(data.calendars, c => { + return { + real_name: c.calendar_name, + display_name: calendarNameToDisplayName(c.calendar_name), + description: c.calendar_description, + contact: c.calendar_contact, + }; + }); + + return this.calendars + }); } getMeetings(calendar) { @@ -74,42 +73,41 @@ export class FedoCal { return Promise.resolve(this.meetings[calendar]); } - return new Promise((resolve, reject) => { - this.request.get(API.meetings, { calendar: calendar }) - .then((data:any) => { - this.meetings[calendar] = _.map(data.meetings, m => { - const meeting:any = { - name: m.meeting_name, - real_description: m.meeting_information, - display_description: _.truncate( - m.meeting_information, - { length: 120, separator: ' ' } - ), - date_start: m.meeting_date, - time_start: m.meeting_time_start, - date_end: m.meeting_date_end, - time_end: m.meeting_time_end, - timezone: m.meeting_timezone, - location: m.meeting_location, - }; - - const start = dateToMoment(meeting.date_start, meeting.time_start, meeting.timezone); - meeting.moment_start = start; - meeting.datetime_start = start.toDate(); - - const end = dateToMoment(meeting.date_end, meeting.time_end, meeting.timezone); - meeting.moment_end = end; - meeting.datetime_end = end.toDate(); - - meeting.display_date = start.format(DATE_FORMAT); - meeting.display_time = start.format(TIME_FORMAT); - - return meeting; - }); - - return resolve(this.meetings[calendar]); - }).catch(reject); - }); + return this.request.get(API.meetings, { calendar: calendar }) + .toPromise() + .then((data: any) => { + this.meetings[calendar] = _.map(data.meetings, m => { + const meeting: any = { + name: m.meeting_name, + real_description: m.meeting_information, + display_description: _.truncate( + m.meeting_information, + { length: 120, separator: ' ' } + ), + date_start: m.meeting_date, + time_start: m.meeting_time_start, + date_end: m.meeting_date_end, + time_end: m.meeting_time_end, + timezone: m.meeting_timezone, + location: m.meeting_location, + }; + + const start = dateToMoment(meeting.date_start, meeting.time_start, meeting.timezone); + meeting.moment_start = start; + meeting.datetime_start = start.toDate(); + + const end = dateToMoment(meeting.date_end, meeting.time_end, meeting.timezone); + meeting.moment_end = end; + meeting.datetime_end = end.toDate(); + + meeting.display_date = start.format(DATE_FORMAT); + meeting.display_time = start.format(TIME_FORMAT); + + return meeting; + }); + + return this.meetings[calendar]; + }); } } @@ -117,4 +115,4 @@ export class FedoCal { function dateToMoment(date, time, timezone) { return moment.tz(date + 'T' + time + 'Z', timezone).tz('Etc/UTC'); } - + diff --git a/src/providers/fedora-mag/fedora-mag.ts b/src/providers/fedora-mag/fedora-mag.ts index 196fc80..fa41391 100644 --- a/src/providers/fedora-mag/fedora-mag.ts +++ b/src/providers/fedora-mag/fedora-mag.ts @@ -3,13 +3,13 @@ import 'rxjs/add/operator/map'; import * as _ from 'lodash'; -import { Request } from '../request/request'; +import { Http } from '../http/http'; const API_ENDPOINT = 'https://fedoramagazine.org/wp-json/wp/v2'; const API = { base: [API_ENDPOINT], posts: [API_ENDPOINT, 'posts'], -// media_url: [API_ENDPOINT, 'media'], + // media_url: [API_ENDPOINT, 'media'], }; /* @@ -20,9 +20,9 @@ const API = { */ @Injectable() export class FedoraMag { - private posts:any; - private media_url:any; - constructor(private request:Request) { + private posts: any; + private media_url: any; + constructor(private request: Http) { this.posts = []; this.media_url = []; } @@ -32,53 +32,53 @@ export class FedoraMag { return Promise.resolve(this.posts); } - return new Promise((resolve, reject) => { - this.request.get(API.posts) - .then(data => { - this.posts = _.map(data, p => { - var excerpt = p.excerpt.rendered; - var post = { - id: p.id, - link: p.guid.rendered, - title: p.title.rendered, - image: p.featured_media, - excerpt: _.truncate( - _.truncate(excerpt, { - length: excerpt.length, - separator: ' { + this.posts = _.map(data, p => { + var excerpt = p.excerpt.rendered; + var post = { + id: p.id, + link: p.guid.rendered, + title: p.title.rendered, + image: p.featured_media, + excerpt: _.truncate( + _.truncate(excerpt, { + length: excerpt.length, + separator: ' { - this.request.get(this.API.media_url,{ posts : posts }) - .then(data => { - this.media_url[posts] = _.map(data, i => { - // var excerpt = p.excerpt.rendered; - var img_url = { - img_id: i.id, - img_link: i.guid.rendered, - }; - //image_url = func_url(post.image); - return post; - }); + /* getMedia_url(posts) { + if (!_.isEmpty(this.media_url[posts])) { + return Promise.resolve(this.media_url[posts]); + } + + return new Promise((resolve, reject) => { + this.request.get(this.API.media_url,{ posts : posts }) + .then(data => { + this.media_url[posts] = _.map(data, i => { + // var excerpt = p.excerpt.rendered; + var img_url = { + img_id: i.id, + img_link: i.guid.rendered, + }; + //image_url = func_url(post.image); + return post; + }); - return resolve(this.posts); - }).catch(reject); - }); - }*/ + return resolve(this.posts); + }).catch(reject); + }); + }*/ } diff --git a/src/providers/http/http.ts b/src/providers/http/http.ts new file mode 100644 index 0000000..f3f4402 --- /dev/null +++ b/src/providers/http/http.ts @@ -0,0 +1,129 @@ +import 'rxjs/add/operator/finally'; +import 'rxjs/add/operator/map'; + +import { Injectable } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { SpinnerDialog } from '@ionic-native/spinner-dialog'; +import { Toast } from '@ionic-native/toast'; +import { IonicConfig } from '../ionic-config/ionic-config'; + +import * as _ from 'lodash'; +import * as querystring from 'query-string'; +import { Observable } from 'rxjs/Observable'; + +/* + Generated class for the Http provider. + + See https://angular.io/docs/ts/latest/guide/dependency-injection.html + for more info on providers and Angular 2 DI. +*/ +@Injectable() +export class Http { + private proxies:any; + private isCordova:boolean; + private isLiveReload:boolean; + + constructor(private httpClient:HttpClient, config:IonicConfig, + /* private spinnerDialog:SpinnerDialog, */ private toast:Toast) { + this.proxies = config.get('proxies'); + + this.isCordova = window.hasOwnProperty('cordova'); + this.isLiveReload = false; + + // this.http + // .get('http://localhost:8100') + // .subscribe( + // res => this.isLiveReload = !_.isEmpty(res), + // err => console.log(this.useProxy()) + // ); + } + + // private startHandler = () => { + // this.spinnerDialog.show(); + // } + + // private stopHandler = () => { + // this.spinnerDialog.hide(); + // } + + // private errorHandler = (error, reject) => { + // this.toast.showShortBottom('Something went wrong.'); + // this.spinnerDialog.hide(); + // return reject(error); + // } + + private useProxy() { + if (this.isLiveReload || !this.isCordova) { + return true; + } else { + return false; + } + } + + private buildURL(parts:Array):string { + return _.map( + parts, part => _.trim(part, '/') + ).join('/'); + } + + private processURL(urlParts) { + const url = this.buildURL(urlParts); + + let finalURL = url; + + if (this.useProxy()) { + this.proxies.forEach(proxy => { + if (url.startsWith(proxy.proxyUrl)) { + finalURL = _.replace( + url, proxy.proxyUrl, proxy.path + ); + return false; + } + }); + + finalURL = _.trim(finalURL + '/'); + } + + return finalURL; + } + + get(urlParts, query?:any, headers = {}): Observable { + let url = this.processURL(urlParts); + + if (!_.isEmpty(query)) { + url = _.trimEnd(url, '/'); + var query = querystring.stringify(query); + url += '?' + query; + } + + // this.startHandler(); + // return new Promise((resolve, reject) => { + return this.httpClient.get(url, { headers }); + // .subscribe( + // response => resolve(response), + // error => this.errorHandler.bind(this, error, reject), + // this.stopHandler + // ); + // }); + } + + post(urlParts, data = {}, headers = {}) { + const url = this.processURL(urlParts); + const body = JSON.stringify(data); + + _.defaults(headers, { + 'Content-Type': 'application/json' + }); + + // this.startHandler(); + // return new Promise((resolve, reject) => { + return this.httpClient.post(url, body, { headers }); + // .subscribe( + // response => resolve(response), + // error => this.errorHandler.bind(this, error, reject), + // this.stopHandler + // ); + // }); + } +} + diff --git a/src/providers/request/request.ts b/src/providers/request/request.ts deleted file mode 100644 index 3a41e5f..0000000 --- a/src/providers/request/request.ts +++ /dev/null @@ -1,130 +0,0 @@ -import 'rxjs/add/operator/finally'; -import 'rxjs/add/operator/map'; - -import { Injectable } from '@angular/core'; -import { HttpClient } from '@angular/common/http'; -import { SpinnerDialog } from '@ionic-native/spinner-dialog'; -import { Toast } from '@ionic-native/toast'; -import { IonicConfig } from '../ionic-config/ionic-config'; - -import * as _ from 'lodash'; -import * as querystring from 'query-string'; - -/* - Generated class for the Request provider. - - See https://angular.io/docs/ts/latest/guide/dependency-injection.html - for more info on providers and Angular 2 DI. -*/ -@Injectable() -export class Request { - private proxies:any; - private isCordova:boolean; - private isLiveReload:boolean; - - constructor(private http:HttpClient, config:IonicConfig, - private spinnerDialog:SpinnerDialog, private toast:Toast) { - this.proxies = config.get('proxies'); - - this.isCordova = window.hasOwnProperty('cordova'); - this.isLiveReload = false; - - // this.http - // .get('http://localhost:8100') - // .subscribe( - // res => this.isLiveReload = !_.isEmpty(res), - // err => console.log(this.useProxy()) - // ); - } - - private startHandler = () => { - this.spinnerDialog.show(); - } - - private stopHandler = () => { - this.spinnerDialog.hide(); - } - - private errorHandler = (error, reject) => { - this.toast.showShortBottom('Something went wrong.'); - this.spinnerDialog.hide(); - return reject(error); - } - - private useProxy() { - if (this.isLiveReload || !this.isCordova) { - return true; - } else { - return false; - } - } - - private buildURL(parts:Array):string { - return _.map( - parts, part => _.trim(part, '/') - ).join('/'); - } - - private processURL(urlParts) { - const url = this.buildURL(urlParts); - - let finalURL = url; - - if (this.useProxy()) { - this.proxies.forEach(proxy => { - if (url.startsWith(proxy.proxyUrl)) { - finalURL = _.replace( - url, proxy.proxyUrl, proxy.path - ); - return false; - } - }); - - finalURL = _.trim(finalURL + '/'); - } - - return finalURL; - } - - get(urlParts, query?:any, headers = {}) { - let url = this.processURL(urlParts); - - if (!_.isEmpty(query)) { - url = _.trimEnd(url, '/'); - var query = querystring.stringify(query); - url += '?' + query; - } - - this.startHandler(); - return new Promise((resolve, reject) => { - this.http - .get(url, { headers }) - .subscribe( - response => resolve(response), - error => this.errorHandler.bind(this, error, reject), - this.stopHandler - ); - }); - } - - post(urlParts, data = {}, headers = {}) { - const url = this.processURL(urlParts); - const body = JSON.stringify(data); - - _.defaults(headers, { - 'Content-Type': 'application/json' - }); - - this.startHandler(); - return new Promise((resolve, reject) => { - this.http - .post(url, body, { headers }) - .subscribe( - response => resolve(response), - error => this.errorHandler.bind(this, error, reject), - this.stopHandler - ); - }); - } -} - diff --git a/src/providers/social/facebook.ts b/src/providers/social/facebook.ts new file mode 100644 index 0000000..a5056e9 --- /dev/null +++ b/src/providers/social/facebook.ts @@ -0,0 +1,48 @@ +import { Injectable } from '@angular/core'; +import 'rxjs/add/operator/map'; +import ENV from '@environment'; +import { Facebook } from 'fb'; +import { compact } from 'lodash-es'; +import { SocialProvider, Post } from './social'; + +/* + Generated class for the Fb provider. + + See https://angular.io/docs/ts/latest/guide/dependency-injection.html + for more info on providers and Angular 2 DI. +*/ +@Injectable() +export class FacebookProvider implements SocialProvider { + private fb: Facebook; + constructor() { + this.fb = new Facebook(ENV.FB_CONFIG); + } + + private api(uri) { + return new Promise((resolve, reject) => { + this.fb.api(uri, res => { + if (!res || res.error) { + return reject(res); + } else { + return resolve(res); + } + }); + }); + } + + public async getPosts(page:string, args?): Promise { + const res:any = await this.api(`${page}/posts`); + const posts = compact(res.data.map(p => { + const post = { + id: p.id, + link: 'https://facebook.com/' + p.id, + content: p.message, + origin: 'facebook', + }; + + return post.content ? post : null; + })); + + return posts; + } +} diff --git a/src/providers/social/social.ts b/src/providers/social/social.ts new file mode 100644 index 0000000..b8d9482 --- /dev/null +++ b/src/providers/social/social.ts @@ -0,0 +1,18 @@ + +export const FACEBOOK = 'facebook'; +export const TWITTER = 'twitter'; + +export interface Post { + id:string, + origin:string, + link:string, + // date: Date, +} + +export interface GetPostExtraArgs { + offset?: string, +} + +export interface SocialProvider { + getPosts(resID: string, args?:GetPostExtraArgs): Promise +} diff --git a/src/providers/social/twitter.ts b/src/providers/social/twitter.ts new file mode 100644 index 0000000..af1bd6b --- /dev/null +++ b/src/providers/social/twitter.ts @@ -0,0 +1,47 @@ +import { Injectable } from '@angular/core'; +import 'rxjs/add/operator/map'; +import * as _ from 'lodash'; + +import ENV from '@environment'; + +import { Http } from '../http/http'; +import { SocialProvider, Post } from './social'; + + +const API_ENDPOINT = 'https://api.twitter.com/1.1/'; + +const API: any = { + base: [API_ENDPOINT], + statuses: [API_ENDPOINT, 'statuses'], +} +API.timeline = API.statuses.concat(['user_timeline.json']); + + +/* + Generated class for the Twitter provider. + + See https://angular.io/docs/ts/latest/guide/dependency-injection.html + for more info on providers and Angular 2 DI. +*/ +@Injectable() +export class TwitterProvider implements SocialProvider { + + constructor(private http: Http) { } + + public async getPosts(handle:string, args?): Promise { + const response = (await this.http.get( + API.timeline, { screen_name: handle }, + { 'Authorization': 'Bearer ' + ENV.TWITTER_CONFIG.BEARER_TOKEN } + ).toPromise() as any[]); + + return response.map(t => { + return { + id: t.id, + link: 'https://twitter.com/statuses/' + t.id_str, + content: t.text, + origin: 'twitter', + }; + }); + } +} + diff --git a/src/providers/tw/tw.ts b/src/providers/tw/tw.ts deleted file mode 100644 index 5cbb6b2..0000000 --- a/src/providers/tw/tw.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { Injectable } from '@angular/core'; -import 'rxjs/add/operator/map'; -import ENV from '@environment'; - -import * as _ from 'lodash'; -import { Request } from '../request/request'; - - -const API_ENDPOINT = 'https://api.twitter.com/1.1/'; - -const API:any = { - base: [API_ENDPOINT], - statuses: [API_ENDPOINT, 'statuses'], -} -API.timeline = API.statuses.concat(['user_timeline.json']); - -/* - Generated class for the Tw provider. - - See https://angular.io/docs/ts/latest/guide/dependency-injection.html - for more info on providers and Angular 2 DI. -*/ -@Injectable() -export class Tw { - - constructor(private request:Request) { } - - getTimelineTweets(user) { - return new Promise((resolve, reject) => { - this.request.get( - API.timeline, { screen_name: user }, - { 'Authorization': 'Bearer ' + ENV.TWITTER_CONFIG.BEARER_TOKEN } - ).then(data => { - var tweets = _.map(data, t => { - return { - id: t.id, - link: 'https://twitter.com/statuses/' + t.id_str, - content: t.text, - origin: 'twitter', - }; - }); - return resolve(tweets); - }).catch(reject); - }); - } -} - diff --git a/src/utils.ts b/src/utils.ts new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/utils.ts From 6c79e17ce524d0525ccd5bf279608dd5f2fba781 Mon Sep 17 00:00:00 2001 From: Amitosh Swain Mahapatra Date: May 30 2018 07:00:27 +0000 Subject: [PATCH 2/4] Use Observables instead of Promises to match with Angular API --- diff --git a/ionic.config.json b/ionic.config.json index 5b16c8f..12b679c 100644 --- a/ionic.config.json +++ b/ionic.config.json @@ -3,23 +3,19 @@ "app_id": "", "proxies": [ { - "path": "/facebook/", - "proxyUrl": "https://graph.facebook.com/v2.6/" - }, - { - "path": "/twitter/", + "path": "/twitter", "proxyUrl": "https://api.twitter.com/1.1/" }, { - "path": "/fedocal/", + "path": "/fedocal", "proxyUrl": "https://apps.fedoraproject.org/calendar/api/" }, { - "path": "/fedoramag/", + "path": "/fedora-magazine", "proxyUrl": "https://fedoramagazine.org/wp-json/wp/v2/" }, { - "path": "/askfedora/", + "path": "/ask-fedora", "proxyUrl": "https://ask.fedoraproject.org/en/api/v1/" } ], diff --git a/src/app/app.component.ts b/src/app/app.component.ts index df270df..d3a02b7 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -1,25 +1,25 @@ -import {Component, ViewChild} from '@angular/core'; +import { Component, ViewChild } from '@angular/core'; import { Platform, NavController } from 'ionic-angular'; -import {MagazinePage} from '../pages/magazine/magazine'; -import {AskPage} from '../pages/ask/ask'; -import {CalendarPage} from '../pages/calendar/calendar'; -import {SocialPage} from '../pages/social/social'; -import {WomenPage} from '../pages/women/women'; -import {FirstPage} from '../pages/first/first'; +import { MagazinePage } from '../pages/magazine/magazine'; +import { AskPage } from '../pages/ask/ask'; +import { CalendarPage } from '../pages/calendar/calendar'; +import { SocialPage } from '../pages/social/social'; +import { WomenPage } from '../pages/women/women'; +import { FirstPage } from '../pages/first/first'; import { SplashScreen } from '@ionic-native/splash-screen'; @Component({ templateUrl: 'app.html', }) export class MyApp { - pages:Array<{title:string, component:any}>; - rootPage:any; + pages: Array<{ title: string, component: any }>; + rootPage: any; - @ViewChild('content') nav:NavController; + @ViewChild('content') nav: NavController; - constructor(platform:Platform, splashScreen:SplashScreen) { + constructor(platform: Platform, splashScreen: SplashScreen) { // used for an example of ngFor and navigation this.pages = [ { title: 'Home', component: FirstPage }, diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 1378f59..3c5e34c 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -18,10 +18,6 @@ import { SocialPage } from '../pages/social/social'; import { WomenPage } from '../pages/women/women'; import { Browser } from '../providers/browser/browser'; -import { Http } from '../providers/http/http'; -import { IonicConfig } from '../providers/ionic-config/ionic-config'; -import { FacebookProvider } from '../providers/social/facebook'; -import { TwitterProvider } from '../providers/social/twitter'; @NgModule({ declarations: [ @@ -50,16 +46,12 @@ import { TwitterProvider } from '../providers/social/twitter'; ], providers: [ Browser, - IonicConfig, InAppBrowser, - Http, SocialSharing, SplashScreen, SpinnerDialog, StatusBar, Toast, - FacebookProvider, - TwitterProvider, { provide: ErrorHandler, useClass: IonicErrorHandler }, ] }) diff --git a/src/pages/ask/ask.ts b/src/pages/ask/ask.ts index 7cf52af..e61edc1 100644 --- a/src/pages/ask/ask.ts +++ b/src/pages/ask/ask.ts @@ -29,7 +29,7 @@ export class AskPage { updateQuestions() { this.askFedora .getQuestions() - .then(questions => { + .subscribe(questions => { this.questions = questions; }); } diff --git a/src/pages/calendar/calendar.html b/src/pages/calendar/calendar.html index 9e5eb6b..b612521 100644 --- a/src/pages/calendar/calendar.html +++ b/src/pages/calendar/calendar.html @@ -16,8 +16,8 @@ - - {{ calendar.display_name }} + + {{ calendar.displayName }} @@ -27,12 +27,12 @@ {{ meeting.name }} - {{ meeting.display_description }} + {{ meeting.description }} -

{{ meeting.display_date }}

-

{{ meeting.display_time }}

+

{{ meeting.displayTime.dateString }}

+

{{ meeting.displayTime.timeString }}

- diff --git a/src/pages/social/social.ts b/src/pages/social/social.ts index 0b30a6f..185dd8d 100644 --- a/src/pages/social/social.ts +++ b/src/pages/social/social.ts @@ -5,6 +5,12 @@ import { Browser } from '../../providers/browser/browser'; import { FacebookProvider } from '../../providers/social/facebook'; import { TwitterProvider } from '../../providers/social/twitter'; import { Post } from '../../providers/social/social'; +import { forkJoin } from 'rxjs/observable/forkJoin'; + +const HANDLE = { + FB: 'fedoraqa', + TWITTER: 'fedora_qa', +}; /* Generated class for the SocialPage page. @@ -18,53 +24,28 @@ import { Post } from '../../providers/social/social'; }) export class SocialPage { - private posts:Post[]; - private tweets:Post[]; - private updates:Array; - private USER:any; - - constructor(private browser:Browser, private fb:FacebookProvider, private tw:TwitterProvider, - private socialSharing:SocialSharing) { + private posts: Post[]; + constructor(private browser: Browser, private fb: FacebookProvider, private twitter: TwitterProvider, + private socialSharing: SocialSharing) { this.posts = []; - this.tweets = []; - this.updates = []; - - this.USER = { - FB: 'fedoraqa', - TW: 'fedora_qa', - }; } ngOnInit() { - this.updateUpdates(); + this.updatePosts(); } - updateUpdates() { - this.fb - .getPosts(this.USER.FB) - .then((posts:Array) => { - this.posts = posts; - this.mergeUpdates(); + private updatePosts() { + forkJoin(this.fb.getPosts(HANDLE.FB), this.twitter.getPosts(HANDLE.TWITTER)) + .subscribe(values => { + this.posts = [...values[0], ...values[1]] as Post[]; }); - - this.tw - .getPosts(this.USER.TW) - .then((tweets:Array) => { - this.tweets = tweets; - this.mergeUpdates(); - }); - } - - mergeUpdates() { - // TODO: Merge as per ascending order of timestamps? - this.updates = [ ...this.posts, ...this.tweets ]; } - openUpdate(event) { + openPost(event) { this.browser.open(event.link); } - shareUpdate(event) { + sharePost(event) { this.socialSharing.share( event.title, event.title, diff --git a/src/pages/women/women.ts b/src/pages/women/women.ts index 31282e2..7ef83df 100644 --- a/src/pages/women/women.ts +++ b/src/pages/women/women.ts @@ -1,13 +1,4 @@ import { Component } from '@angular/core'; -import { NavController } from 'ionic-angular'; -import { SocialSharing } from '@ionic-native/social-sharing'; - -import { Browser } from '../../providers/browser/browser'; -import { FedoraMag } from '../../providers/fedora-mag/fedora-mag'; -import { MagazinePage } from '../magazine/magazine'; -import { AskPage } from '../ask/ask'; -import { CalendarPage } from '../calendar/calendar'; -import { SocialPage } from '../social/social'; /* Generated class for the MagazinePage page. @@ -17,51 +8,6 @@ import { SocialPage } from '../social/social'; */ @Component({ templateUrl: 'women.html', - providers: [Browser, FedoraMag], }) export class WomenPage { - posts: Array; - - constructor(private nav: NavController, private browser: Browser, - private fedoraMag: FedoraMag, private socialSharing: SocialSharing) { - this.posts = []; - } - - ngOnInit() { - this.updatePosts(); - } - - updatePosts() { - this.fedoraMag.getPosts().then(posts => { - this.posts = posts; - }); - } - - openPost(event) { - this.browser.open(event.link); - } - - openMag() { - this.nav.push(MagazinePage); - } - openAsk() { - this.nav.push(AskPage); - } - openSocial() { - this.nav.push(SocialPage); - } - openCal() { - this.nav.push(CalendarPage); - } - - login(event) { - - } - - sharePost(event) { - this.socialSharing.share( - event.title, event.title, - null, event.link - ); - } } diff --git a/src/providers/ask-fedora/ask-fedora.ts b/src/providers/ask-fedora/ask-fedora.ts index f32e1cf..9775aff 100644 --- a/src/providers/ask-fedora/ask-fedora.ts +++ b/src/providers/ask-fedora/ask-fedora.ts @@ -1,9 +1,12 @@ -import { Injectable } from '@angular/core'; import 'rxjs/add/operator/map'; +import { Injectable } from '@angular/core'; -import { Http } from '../http/http'; +import { HttpClient } from '@angular/common/http'; +import { Platform } from 'ionic-angular'; -const API_ENDPOINT = 'https://ask.fedoraproject.org/en/api/v1'; +import { chooseEndpoint } from '../../utils'; + +const ENDPOINT = chooseEndpoint('/ask-fedora', 'https://ask.fedoraproject.org/en/api/v1'); export interface Question { id: string, @@ -26,42 +29,22 @@ export interface Question { */ @Injectable() export class AskFedoraService { - private API: any; - private questions: Question[]; - - constructor(private request: Http) { - this.API = { - base: [API_ENDPOINT], - questions: [API_ENDPOINT, 'questions'], - }; - - this.questions = []; + private endpoint:string; + constructor(private http: HttpClient, private platform: Platform) { } getQuestions() { - if (this.questions.length) { - return Promise.resolve(this.questions); - } - - return this.request.get(this.API.questions) - .toPromise() - .then((data: any) => { - this.questions = data.questions.map(q => { - const question = { - id: q.id, - title: q.title, - link: q.url, - answers: q.answer_count, - content: q.summary, - timestamp: q.added_at, - tags: q.tags, - view: q.view_count, - vote: q.score, - }; - return question; - }); - - return this.questions; - }); + return this.http.get(`${ENDPOINT}/questions/`) + .map((data: any) => data.questions.map(q => ({ + id: q.id, + title: q.title, + link: q.url, + answers: q.answer_count, + content: q.summary, + timestamp: q.added_at, + tags: q.tags, + view: q.view_count, + vote: q.score, + }))); } } diff --git a/src/providers/fedo-cal/fedo-cal.ts b/src/providers/fedo-cal/fedo-cal.ts index cfcc03d..bb24f93 100644 --- a/src/providers/fedo-cal/fedo-cal.ts +++ b/src/providers/fedo-cal/fedo-cal.ts @@ -4,17 +4,16 @@ import 'rxjs/add/operator/map'; import * as _ from 'lodash'; import * as moment from 'moment-timezone'; -import { Http } from '../http/http'; +import { HttpClient } from '@angular/common/http'; +import { Platform } from 'ionic-angular'; +import { Observable } from 'rxjs/Observable'; +import { chooseEndpoint } from '../../utils'; -const API_ENDPOINT = 'https://apps.fedoraproject.org/calendar/api/'; -const API = { - base: [API_ENDPOINT], - calendars: [API_ENDPOINT, 'calendars'], - meetings: [API_ENDPOINT, 'meetings'], -}; + +const ENDPOINT = chooseEndpoint('/fedocal', 'https://apps.fedoraproject.org/calendar/api'); const DATE_FORMAT = 'dddd, MMMM Do YYYY'; -const TIME_FORMAT = 'h:mm a z'; +const TIME_FORMAT = 'h:mm A z'; /* Convert title to capital case, but skip special names like FESCo, FAmSCo, i18n @@ -28,7 +27,32 @@ function calendarNameToDisplayName(name: string): string { default: return /^[A-Z][^\d-]*.*$/.test(name) ? name : _.startCase(name); } +} + +export interface Calendar { + realName: string, + displayName: string, + description: string, + adminGroup: string, + editorGroup: string, + contact: string, + enabled: boolean, +} +export interface Meeting { + name: string, + description: string, + location: string, + time: Date, + timeEnd: Date, + displayTime: { + dateString: string, + timeString: string, + }, + displayTimeEnd: { + dateString: string, + timeString: string, + } } /* @@ -38,81 +62,54 @@ function calendarNameToDisplayName(name: string): string { for more info on providers and Angular 2 DI. */ @Injectable() -export class FedoCal { - private calendars: any; - private meetings: any; - - constructor(private request: Http) { - this.calendars = []; - this.meetings = []; +export class FedoCalService { + private endpoint: string; + constructor(private http: HttpClient, private platform: Platform) { } - getCalendars() { - if (!_.isEmpty(this.calendars)) { - return Promise.resolve(this.calendars); - } - - return this.request.get(API.calendars) - .toPromise() - .then((data: any) => { - this.calendars = _.map(data.calendars, c => { - return { - real_name: c.calendar_name, - display_name: calendarNameToDisplayName(c.calendar_name), - description: c.calendar_description, - contact: c.calendar_contact, - }; - }); - - return this.calendars - }); + getCalendars(): Observable { + return this.http.get(`${ENDPOINT}/calendars/`) + .map((data: any) => + data.calendars.map((c: any) => ({ + realName: c.calendar_name, + displayName: calendarNameToDisplayName(c.calendar_name), + description: c.calendar_description, + contact: c.calendar_contact, + adminGroup: c.calendar_admin_group, + editorGroup: c.calendar_editor_group, + enabled: c.calendar_status === 'Enabled' + }))); } - getMeetings(calendar) { - if (!_.isEmpty(this.meetings[calendar])) { - return Promise.resolve(this.meetings[calendar]); - } - - return this.request.get(API.meetings, { calendar: calendar }) - .toPromise() - .then((data: any) => { - this.meetings[calendar] = _.map(data.meetings, m => { - const meeting: any = { - name: m.meeting_name, - real_description: m.meeting_information, - display_description: _.truncate( - m.meeting_information, - { length: 120, separator: ' ' } - ), - date_start: m.meeting_date, - time_start: m.meeting_time_start, - date_end: m.meeting_date_end, - time_end: m.meeting_time_end, - timezone: m.meeting_timezone, - location: m.meeting_location, - }; - - const start = dateToMoment(meeting.date_start, meeting.time_start, meeting.timezone); - meeting.moment_start = start; - meeting.datetime_start = start.toDate(); - - const end = dateToMoment(meeting.date_end, meeting.time_end, meeting.timezone); - meeting.moment_end = end; - meeting.datetime_end = end.toDate(); - - meeting.display_date = start.format(DATE_FORMAT); - meeting.display_time = start.format(TIME_FORMAT); - - return meeting; - }); - - return this.meetings[calendar]; - }); + getMeetings(calendar): Observable { + + return this.http.get(`${ENDPOINT}/meetings/`, { params: { calendar: calendar } }) + .map((data: any) => data.meetings.map(m => { + const mTime = dateToMoment(m.meeting_date, m.meeting_time_start, m.meeting_timezone); + const mTimeEnd = dateToMoment(m.meeting_date_end, m.meeting_time_stop, m.meeting_timezone); + + return { + name: m.meeting_name, + description: m.meeting_information, + time: mTime.toDate(), + timeEnd: mTimeEnd.toDate(), + displayTime: { + dateString: mTime.format(DATE_FORMAT), + timeString: mTime.format(TIME_FORMAT) + }, + displayTimeEnd: { + dateString: mTimeEnd.format(DATE_FORMAT), + timeString: mTimeEnd.format(TIME_FORMAT), + }, + location: m.meeting_location, + }; + })); } } function dateToMoment(date, time, timezone) { - return moment.tz(date + 'T' + time + 'Z', timezone).tz('Etc/UTC'); + const m = moment.tz(`${date} ${time}`, timezone).tz('UTC'); + return m; } diff --git a/src/providers/fedora-mag/fedora-mag.ts b/src/providers/fedora-mag/fedora-mag.ts deleted file mode 100644 index fa41391..0000000 --- a/src/providers/fedora-mag/fedora-mag.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { Injectable } from '@angular/core'; -import 'rxjs/add/operator/map'; - -import * as _ from 'lodash'; - -import { Http } from '../http/http'; - -const API_ENDPOINT = 'https://fedoramagazine.org/wp-json/wp/v2'; -const API = { - base: [API_ENDPOINT], - posts: [API_ENDPOINT, 'posts'], - // media_url: [API_ENDPOINT, 'media'], -}; - -/* - Generated class for the FedoraMag provider. - - See https://angular.io/docs/ts/latest/guide/dependency-injection.html - for more info on providers and Angular 2 DI. -*/ -@Injectable() -export class FedoraMag { - private posts: any; - private media_url: any; - constructor(private request: Http) { - this.posts = []; - this.media_url = []; - } - - getPosts() { - if (!_.isEmpty(this.posts)) { - return Promise.resolve(this.posts); - } - - return this.request.get(API.posts) - .toPromise() - .then(data => { - this.posts = _.map(data, p => { - var excerpt = p.excerpt.rendered; - var post = { - id: p.id, - link: p.guid.rendered, - title: p.title.rendered, - image: p.featured_media, - excerpt: _.truncate( - _.truncate(excerpt, { - length: excerpt.length, - separator: ' { - this.request.get(this.API.media_url,{ posts : posts }) - .then(data => { - this.media_url[posts] = _.map(data, i => { - // var excerpt = p.excerpt.rendered; - var img_url = { - img_id: i.id, - img_link: i.guid.rendered, - }; - //image_url = func_url(post.image); - return post; - }); - - return resolve(this.posts); - }).catch(reject); - }); - }*/ -} diff --git a/src/providers/fedora-magazine/fedora-magazine.ts b/src/providers/fedora-magazine/fedora-magazine.ts new file mode 100644 index 0000000..000b79b --- /dev/null +++ b/src/providers/fedora-magazine/fedora-magazine.ts @@ -0,0 +1,68 @@ +import { Injectable } from '@angular/core'; +import 'rxjs/add/operator/map'; + +import * as _ from 'lodash'; + +import { HttpClient } from '@angular/common/http'; +import { Observable } from 'rxjs/Observable'; +import { chooseEndpoint } from '../../utils'; + +const ENDPOINT = chooseEndpoint('/fedora-magazine', 'https://fedoramagazine.org/wp-json/wp/v2'); + +export interface Post { + id: number, + link: string, + title: string, + image: any, + excerpt: string, + content: string, + date: Date +} + +/* + Generated class for the FedoraMag provider. + + See https://angular.io/docs/ts/latest/guide/dependency-injection.html + for more info on providers and Angular 2 DI. +*/ +@Injectable() +export class FedoraMagazineService { + constructor(private http: HttpClient) { + } + + getPosts(): Observable { + return this.http.get(`${ENDPOINT}/posts`) + .map((data: any[]) => data.map((post: any) => ({ + id: post.id, + link: post.link, + title: post.title.rendered, + image: post.featured_media, + excerpt: post.excerpt.rendered, + content: post.content.rendered, + date: new Date(post.date_gmt+'Z'), + }))); + } +} + + /* getMedia_url(posts) { + if (!_.isEmpty(this.media_url[posts])) { + return Promise.resolve(this.media_url[posts]); + } + + return new Promise((resolve, reject) => { + this.request.get(this.API.media_url,{ posts : posts }) + .then(data => { + this.media_url[posts] = _.map(data, i => { + // var excerpt = p.excerpt.rendered; + var img_url = { + img_id: i.id, + img_link: i.guid.rendered, + }; + //image_url = func_url(post.image); + return post; + }); + + return resolve(this.posts); + }).catch(reject); + }); + }*/ diff --git a/src/providers/http/http.ts b/src/providers/http/http.ts deleted file mode 100644 index f3f4402..0000000 --- a/src/providers/http/http.ts +++ /dev/null @@ -1,129 +0,0 @@ -import 'rxjs/add/operator/finally'; -import 'rxjs/add/operator/map'; - -import { Injectable } from '@angular/core'; -import { HttpClient } from '@angular/common/http'; -import { SpinnerDialog } from '@ionic-native/spinner-dialog'; -import { Toast } from '@ionic-native/toast'; -import { IonicConfig } from '../ionic-config/ionic-config'; - -import * as _ from 'lodash'; -import * as querystring from 'query-string'; -import { Observable } from 'rxjs/Observable'; - -/* - Generated class for the Http provider. - - See https://angular.io/docs/ts/latest/guide/dependency-injection.html - for more info on providers and Angular 2 DI. -*/ -@Injectable() -export class Http { - private proxies:any; - private isCordova:boolean; - private isLiveReload:boolean; - - constructor(private httpClient:HttpClient, config:IonicConfig, - /* private spinnerDialog:SpinnerDialog, */ private toast:Toast) { - this.proxies = config.get('proxies'); - - this.isCordova = window.hasOwnProperty('cordova'); - this.isLiveReload = false; - - // this.http - // .get('http://localhost:8100') - // .subscribe( - // res => this.isLiveReload = !_.isEmpty(res), - // err => console.log(this.useProxy()) - // ); - } - - // private startHandler = () => { - // this.spinnerDialog.show(); - // } - - // private stopHandler = () => { - // this.spinnerDialog.hide(); - // } - - // private errorHandler = (error, reject) => { - // this.toast.showShortBottom('Something went wrong.'); - // this.spinnerDialog.hide(); - // return reject(error); - // } - - private useProxy() { - if (this.isLiveReload || !this.isCordova) { - return true; - } else { - return false; - } - } - - private buildURL(parts:Array):string { - return _.map( - parts, part => _.trim(part, '/') - ).join('/'); - } - - private processURL(urlParts) { - const url = this.buildURL(urlParts); - - let finalURL = url; - - if (this.useProxy()) { - this.proxies.forEach(proxy => { - if (url.startsWith(proxy.proxyUrl)) { - finalURL = _.replace( - url, proxy.proxyUrl, proxy.path - ); - return false; - } - }); - - finalURL = _.trim(finalURL + '/'); - } - - return finalURL; - } - - get(urlParts, query?:any, headers = {}): Observable { - let url = this.processURL(urlParts); - - if (!_.isEmpty(query)) { - url = _.trimEnd(url, '/'); - var query = querystring.stringify(query); - url += '?' + query; - } - - // this.startHandler(); - // return new Promise((resolve, reject) => { - return this.httpClient.get(url, { headers }); - // .subscribe( - // response => resolve(response), - // error => this.errorHandler.bind(this, error, reject), - // this.stopHandler - // ); - // }); - } - - post(urlParts, data = {}, headers = {}) { - const url = this.processURL(urlParts); - const body = JSON.stringify(data); - - _.defaults(headers, { - 'Content-Type': 'application/json' - }); - - // this.startHandler(); - // return new Promise((resolve, reject) => { - return this.httpClient.post(url, body, { headers }); - // .subscribe( - // response => resolve(response), - // error => this.errorHandler.bind(this, error, reject), - // this.stopHandler - // ); - // }); - } -} - diff --git a/src/providers/ionic-config/ionic-config.ts b/src/providers/ionic-config/ionic-config.ts deleted file mode 100644 index a6a4e98..0000000 --- a/src/providers/ionic-config/ionic-config.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { Injectable } from '@angular/core'; - -// this no longer works -// import * as CONFIG from '../../../ionic.config.json'; -// WORKAROUND: manually set proxy config here - -const CONFIG = { - proxies: [ - { - path: '/facebook/', - proxyUrl: 'https://graph.facebook.com/v2.6/' - }, - { - path: '/twitter/', - proxyUrl: 'https://api.twitter.com/1.1/' - }, - { - path: '/fedocal/', - proxyUrl: 'https://apps.fedoraproject.org/calendar/api/' - }, - { - path: '/fedoramag/', - proxyUrl: 'https://fedoramagazine.org/wp-json/wp/v2/' - }, - { - path: '/askfedora/', - proxyUrl: 'https://ask.fedoraproject.org/en/api/v1/' - } - ], -}; - -@Injectable() -export class IonicConfig { - private config:any; - constructor() { - this.config = CONFIG; - } - - get(key) { - return this.config[key]; - } -} diff --git a/src/providers/social/facebook.ts b/src/providers/social/facebook.ts index a5056e9..48e4ea0 100644 --- a/src/providers/social/facebook.ts +++ b/src/providers/social/facebook.ts @@ -3,7 +3,9 @@ import 'rxjs/add/operator/map'; import ENV from '@environment'; import { Facebook } from 'fb'; import { compact } from 'lodash-es'; -import { SocialProvider, Post } from './social'; +import { SocialProvider, Post, FACEBOOK } from './social'; +import { Observable } from 'rxjs/Observable'; +import { Observer } from 'rxjs/Observer'; /* Generated class for the Fb provider. @@ -19,30 +21,33 @@ export class FacebookProvider implements SocialProvider { } private api(uri) { - return new Promise((resolve, reject) => { + return Observable.create((emitter: Observer) => { this.fb.api(uri, res => { if (!res || res.error) { - return reject(res); + emitter.error(res.error); } else { - return resolve(res); + emitter.next(res); } + emitter.complete(); }); }); } - public async getPosts(page:string, args?): Promise { - const res:any = await this.api(`${page}/posts`); - const posts = compact(res.data.map(p => { + public getPosts(page: string, args?): Observable { + return this.api(`${page}/posts`) + .map(res => { + const posts = compact(res.data.map(p => { const post = { id: p.id, link: 'https://facebook.com/' + p.id, content: p.message, - origin: 'facebook', + origin: FACEBOOK, }; return post.content ? post : null; })); return posts; + }); } } diff --git a/src/providers/social/social.ts b/src/providers/social/social.ts index b8d9482..8832ffe 100644 --- a/src/providers/social/social.ts +++ b/src/providers/social/social.ts @@ -1,3 +1,4 @@ +import { Observable } from "rxjs/Observable"; export const FACEBOOK = 'facebook'; export const TWITTER = 'twitter'; @@ -6,7 +7,7 @@ export interface Post { id:string, origin:string, link:string, - // date: Date, + content:string, } export interface GetPostExtraArgs { @@ -14,5 +15,5 @@ export interface GetPostExtraArgs { } export interface SocialProvider { - getPosts(resID: string, args?:GetPostExtraArgs): Promise + getPosts(resID: string, args?:GetPostExtraArgs): Observable } diff --git a/src/providers/social/twitter.ts b/src/providers/social/twitter.ts index af1bd6b..59bf6d2 100644 --- a/src/providers/social/twitter.ts +++ b/src/providers/social/twitter.ts @@ -1,21 +1,18 @@ import { Injectable } from '@angular/core'; import 'rxjs/add/operator/map'; +import 'rxjs/add/operator/do'; import * as _ from 'lodash'; import ENV from '@environment'; -import { Http } from '../http/http'; import { SocialProvider, Post } from './social'; +import { HttpClient } from '@angular/common/http'; +import { Platform } from 'ionic-angular'; +import { chooseEndpoint } from '../../utils'; +import { Observable } from 'rxjs/Observable'; +import { from } from 'rxjs/observable/from'; - -const API_ENDPOINT = 'https://api.twitter.com/1.1/'; - -const API: any = { - base: [API_ENDPOINT], - statuses: [API_ENDPOINT, 'statuses'], -} -API.timeline = API.statuses.concat(['user_timeline.json']); - +const ENDPOINT = chooseEndpoint('/twitter', 'https://api.twitter.com/1.1'); /* Generated class for the Twitter provider. @@ -26,22 +23,20 @@ API.timeline = API.statuses.concat(['user_timeline.json']); @Injectable() export class TwitterProvider implements SocialProvider { - constructor(private http: Http) { } - - public async getPosts(handle:string, args?): Promise { - const response = (await this.http.get( - API.timeline, { screen_name: handle }, - { 'Authorization': 'Bearer ' + ENV.TWITTER_CONFIG.BEARER_TOKEN } - ).toPromise() as any[]); - - return response.map(t => { - return { - id: t.id, - link: 'https://twitter.com/statuses/' + t.id_str, - content: t.text, - origin: 'twitter', - }; - }); + private endpoint: string; + constructor(private http: HttpClient, private platform: Platform) { + } + + public getPosts(handle: string, args?): Observable { + return this.http.get(`${ENDPOINT}/statuses/user_timeline.json`, { + params: { screen_name: handle }, + headers: { 'Authorization': 'Bearer ' + ENV.TWITTER_CONFIG.BEARER_TOKEN } + }).map((tweetsResponse: any) => tweetsResponse.map(t => ({ + id: t.id, + link: 'https://twitter.com/statuses/' + t.id_str, + content: t.text, + origin: 'twitter', + }))); } } diff --git a/src/utils.ts b/src/utils.ts index e69de29..25bc712 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -0,0 +1,10 @@ + +/** + * Choose appropriate API end point or the proxy to avoid CORS in Ionic dev server + * @param devServerEndpoint endpoint in Ionic dev server + * @param appEndpoint endpoint to use in real application + */ +export function chooseEndpoint(devServerEndpoint:string, appEndpoint:string) { + // courtesy: https://forum.ionicframework.com/t/check-if-run-on-emulator-dev-production-or-livereload/71845/9 + return window.hasOwnProperty('IonicDevServer') ? devServerEndpoint : appEndpoint; +} From 8d98fbdc40b6f058bda6e4152ba168f2d92e007a Mon Sep 17 00:00:00 2001 From: Amitosh Swain Mahapatra Date: May 30 2018 07:00:27 +0000 Subject: [PATCH 3/4] Fix TS lint errors --- diff --git a/src/app/app.component.ts b/src/app/app.component.ts index d3a02b7..a115f57 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -12,8 +12,8 @@ import { SplashScreen } from '@ionic-native/splash-screen'; @Component({ templateUrl: 'app.html', }) -export class MyApp { - pages: Array<{ title: string, component: any }>; +export class App { + pages: { title: string, component: any }[]; rootPage: any; @ViewChild('content') nav: NavController; diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 3c5e34c..84f0ade 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -9,7 +9,7 @@ import { SocialSharing } from '@ionic-native/social-sharing'; import { SpinnerDialog } from '@ionic-native/spinner-dialog'; import { Toast } from '@ionic-native/toast'; -import { MyApp } from './app.component'; +import { App } from './app.component'; import { FirstPage } from '../pages/first/first'; import { MagazinePage } from '../pages/magazine/magazine'; import { AskPage } from '../pages/ask/ask'; @@ -21,7 +21,7 @@ import { Browser } from '../providers/browser/browser'; @NgModule({ declarations: [ - MyApp, + App, FirstPage, MagazinePage, AskPage, @@ -32,11 +32,11 @@ import { Browser } from '../providers/browser/browser'; imports: [ BrowserModule, HttpClientModule, - IonicModule.forRoot(MyApp) + IonicModule.forRoot(App) ], bootstrap: [IonicApp], entryComponents: [ - MyApp, + App, FirstPage, MagazinePage, AskPage, diff --git a/src/pages/first/first.ts b/src/pages/first/first.ts index e6574ae..ed1f25e 100644 --- a/src/pages/first/first.ts +++ b/src/pages/first/first.ts @@ -1,7 +1,5 @@ import { Component } from '@angular/core'; import { NavController } from 'ionic-angular'; -import { SocialSharing } from '@ionic-native/social-sharing'; - import { Browser } from '../../providers/browser/browser'; import { MagazinePage } from '../magazine/magazine'; import { AskPage } from '../ask/ask'; @@ -21,7 +19,7 @@ import { SocialPage } from '../social/social'; export class FirstPage { private posts: Array; - constructor(private nav: NavController, private browser: Browser, private socialSharing: SocialSharing) { + constructor(private nav: NavController, private browser: Browser) { this.posts = []; } diff --git a/src/providers/ask-fedora/ask-fedora.ts b/src/providers/ask-fedora/ask-fedora.ts index 9775aff..09afe00 100644 --- a/src/providers/ask-fedora/ask-fedora.ts +++ b/src/providers/ask-fedora/ask-fedora.ts @@ -1,9 +1,6 @@ import 'rxjs/add/operator/map'; import { Injectable } from '@angular/core'; - import { HttpClient } from '@angular/common/http'; -import { Platform } from 'ionic-angular'; - import { chooseEndpoint } from '../../utils'; const ENDPOINT = chooseEndpoint('/ask-fedora', 'https://ask.fedoraproject.org/en/api/v1'); @@ -29,8 +26,7 @@ export interface Question { */ @Injectable() export class AskFedoraService { - private endpoint:string; - constructor(private http: HttpClient, private platform: Platform) { + constructor(private http: HttpClient) { } getQuestions() { diff --git a/src/providers/fedo-cal/fedo-cal.ts b/src/providers/fedo-cal/fedo-cal.ts index bb24f93..ce0c7bb 100644 --- a/src/providers/fedo-cal/fedo-cal.ts +++ b/src/providers/fedo-cal/fedo-cal.ts @@ -5,7 +5,6 @@ import * as _ from 'lodash'; import * as moment from 'moment-timezone'; import { HttpClient } from '@angular/common/http'; -import { Platform } from 'ionic-angular'; import { Observable } from 'rxjs/Observable'; import { chooseEndpoint } from '../../utils'; @@ -63,8 +62,7 @@ export interface Meeting { */ @Injectable() export class FedoCalService { - private endpoint: string; - constructor(private http: HttpClient, private platform: Platform) { + constructor(private http: HttpClient) { } getCalendars(): Observable { diff --git a/src/providers/fedora-magazine/fedora-magazine.ts b/src/providers/fedora-magazine/fedora-magazine.ts index 000b79b..df22c3b 100644 --- a/src/providers/fedora-magazine/fedora-magazine.ts +++ b/src/providers/fedora-magazine/fedora-magazine.ts @@ -1,8 +1,5 @@ -import { Injectable } from '@angular/core'; import 'rxjs/add/operator/map'; - -import * as _ from 'lodash'; - +import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs/Observable'; import { chooseEndpoint } from '../../utils'; diff --git a/src/providers/social/twitter.ts b/src/providers/social/twitter.ts index 59bf6d2..490c566 100644 --- a/src/providers/social/twitter.ts +++ b/src/providers/social/twitter.ts @@ -1,16 +1,11 @@ -import { Injectable } from '@angular/core'; import 'rxjs/add/operator/map'; import 'rxjs/add/operator/do'; -import * as _ from 'lodash'; - -import ENV from '@environment'; - +import { Injectable } from '@angular/core'; import { SocialProvider, Post } from './social'; import { HttpClient } from '@angular/common/http'; -import { Platform } from 'ionic-angular'; import { chooseEndpoint } from '../../utils'; import { Observable } from 'rxjs/Observable'; -import { from } from 'rxjs/observable/from'; +import ENV from '@environment'; const ENDPOINT = chooseEndpoint('/twitter', 'https://api.twitter.com/1.1'); @@ -22,9 +17,7 @@ const ENDPOINT = chooseEndpoint('/twitter', 'https://api.twitter.com/1.1'); */ @Injectable() export class TwitterProvider implements SocialProvider { - - private endpoint: string; - constructor(private http: HttpClient, private platform: Platform) { + constructor(private http: HttpClient) { } public getPosts(handle: string, args?): Observable { From 2d3fded5c4886c72b3245e25ba71266b4b684e77 Mon Sep 17 00:00:00 2001 From: Amitosh Swain Mahapatra Date: May 30 2018 07:02:31 +0000 Subject: [PATCH 4/4] Add documentation to providers and pages --- diff --git a/src/app/app.component.ts b/src/app/app.component.ts index a115f57..67a0859 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -9,18 +9,24 @@ import { WomenPage } from '../pages/women/women'; import { FirstPage } from '../pages/first/first'; import { SplashScreen } from '@ionic-native/splash-screen'; +/** + * Entrypoint for the Fedora App + */ @Component({ templateUrl: 'app.html', }) export class App { + /** + * Contains the pages that constitute this app + */ pages: { title: string, component: any }[]; + rootPage: any; @ViewChild('content') nav: NavController; constructor(platform: Platform, splashScreen: SplashScreen) { - // used for an example of ngFor and navigation this.pages = [ { title: 'Home', component: FirstPage }, { title: 'Magazine', component: MagazinePage }, @@ -40,9 +46,14 @@ export class App { }); } - openPage(page) { - // Reset the content nav to have just this page - // we wouldn't want the back button to show in this scenario + /** + * Navigate to a new page + * + * @param page page to navigate to + */ + openPage(page): void { + // Reset the content nav to have just this page we wouldn't want the back + // button to show in this scenario this.nav.push(page.component); } } diff --git a/src/app/config.env.ts.example b/src/app/config.env.ts.example index 19cfb63..8b44a9b 100644 --- a/src/app/config.env.ts.example +++ b/src/app/config.env.ts.example @@ -8,7 +8,8 @@ const FB_CONFIG = { }; const TWITTER_CONFIG = { - BEARER_TOKEN: 'AAAAAEXAMPLEAAAAAAAAMmWwEXAMPLE9tGRvIFnIR8XYXmIFTFaEGagjX0%3Dup2JDIi9hjbCJKGaEGDqkLMtYSGumkyMa6SbwXx0FMB1vOlvN0' + BEARER_TOKEN: 'AAAAAEXAMPLEAAAAAAAAMmWwEXAMPLE9tGRvIFnIR8XYXmEXAMpLEGgjX0%3' + + 'Dup2JDExAmplEJKGaEXAmpLetYSGumkyExAMplEx0FMB1vOlvN0' }; const ENV = { diff --git a/src/app/main.ts b/src/app/main.ts index 060f649..059feec 100644 --- a/src/app/main.ts +++ b/src/app/main.ts @@ -4,6 +4,8 @@ import { AppModule } from './app.module'; import { enableProdMode } from '@angular/core'; if (ENV.PROD) { + // Enable Angular prod mode in PROD builds only. Angular Prod mode drops a + // considerable amount of assertions enableProdMode(); } diff --git a/src/pages/ask/ask.html b/src/pages/ask/ask.html index ba6ba7a..be8ca8a 100644 --- a/src/pages/ask/ask.html +++ b/src/pages/ask/ask.html @@ -39,17 +39,17 @@ - {{ question.vote }} + {{ question.score }}
VOTE
- {{ question.answers }} + {{ question.answerCount }}
Ans
- {{ question.view }} + {{ question.viewCount }}
VIEW
diff --git a/src/pages/ask/ask.ts b/src/pages/ask/ask.ts index e61edc1..ce42a88 100644 --- a/src/pages/ask/ask.ts +++ b/src/pages/ask/ask.ts @@ -1,21 +1,24 @@ import { Component } from '@angular/core'; import { SocialSharing } from '@ionic-native/social-sharing'; - import { Browser } from '../../providers/browser/browser'; -import { AskFedoraService } from '../../providers/ask-fedora/ask-fedora'; - -/* - Generated class for the AskPage page. +import { AskFedoraService, Question } from '../../providers/ask-fedora/ask-fedora'; - See http://ionicframework.com/docs/v2/components/#navigation for more info on - Ionic pages and navigation. -*/ +/** + * Shows latest questions from Ask Fedora + * + * Fetches latest 30 questions from Ask Fedora and displays the questions, the + * answers and the number of votes each question receives. + */ @Component({ templateUrl: 'ask.html', providers: [Browser, AskFedoraService], }) export class AskPage { - private questions: any; + + /** + * Stores list of displayed questions + */ + private questions: Question[]; constructor(private browser: Browser, private askFedora: AskFedoraService, private socialSharing: SocialSharing) { @@ -26,7 +29,10 @@ export class AskPage { this.updateQuestions(); } - updateQuestions() { + /** + * Fetch a list of latest questions using Ask Fedora API. + */ + updateQuestions(): void { this.askFedora .getQuestions() .subscribe(questions => { @@ -34,16 +40,31 @@ export class AskPage { }); } - openQuestion(event) { - this.browser.open(event.link); + /** + * Open a question in a browser. + * + * Opens question in an in-app browser in mobile app and in a new tab on desktop. + * + * @param question question to open + */ + openQuestion(question: Question): void { + this.browser.open(question.link); } - shareQuestion(event) { + /** + * Share the question using a third-party app installed in the user's device + * + * Allows to share the question using apps like WhatsApp, Facebook, or any app that + * exposes a share interface to the underlying OS. + * + * @param question question to share + */ + shareQuestion(question: Question): void { this.socialSharing.share( - event.title, - event.title, + question.content, + question.title, null, - event.link + question.link ); } } diff --git a/src/pages/calendar/calendar.ts b/src/pages/calendar/calendar.ts index 1ec2f2b..45f372e 100644 --- a/src/pages/calendar/calendar.ts +++ b/src/pages/calendar/calendar.ts @@ -1,24 +1,39 @@ import { Component } from '@angular/core'; import { Calendar } from '@ionic-native/calendar'; -import { FedoCalService, Calendar as CalendarType } from '../../providers/fedo-cal/fedo-cal'; +import { FedoCalService, Calendar as CalendarType, Meeting } from '../../providers/fedo-cal/fedo-cal'; -/* - Generated class for the CalendarPage page. - - See http://ionicframework.com/docs/v2/components/#navigation for more info on - Ionic pages and navigation. -*/ +/** + * We default to the QA calendar + */ const DEFAULT_CALENDAR = 'QA'; +/** + * The FedoCal interface + * + * Shows the list of calendars availabe on FedoCal and the meetings of each + * calendar. Also allows to add meetings from the calendar to the system calendar. + */ @Component({ templateUrl: 'calendar.html', providers: [FedoCalService, Calendar] }) export class CalendarPage { + + /** + * List of calendars in FedoCal + */ private calendars: CalendarType[]; - private meetings: Array; + + /** + * List of meetings in the selected calendar + */ + private meetings: Meeting[]; + + /** + * ID of the selected calendar + */ private selectedCalendar: string; constructor(private fedoCal: FedoCalService, private calendar: Calendar) { @@ -33,7 +48,10 @@ export class CalendarPage { this.updateMeetings(); } - updateCalendars() { + /** + * Update the list of calendars from FedoCal + */ + updateCalendars(): void { this.fedoCal .getCalendars() .subscribe(calendars => { @@ -41,7 +59,10 @@ export class CalendarPage { }); } - updateMeetings() { + /** + * Update the list of meetings for the selected calendar + */ + updateMeetings(): void { this.fedoCal .getMeetings(this.selectedCalendar) .subscribe(meetings => { @@ -49,13 +70,18 @@ export class CalendarPage { }); } - addToCalendar(event) { + /** + * Add a FedoCal meeting to the system calendar + * + * @param meeting meeting to add to the calendar + */ + addToCalendar(meeting:Meeting): void { this.calendar.createEventInteractively( - event.name, - event.location, - event.real_description, - event.datetime_start, - event.datetime_end + meeting.name, + meeting.location, + meeting.description, + meeting.time, + meeting.timeEnd ); } } diff --git a/src/pages/first/first.ts b/src/pages/first/first.ts index ed1f25e..6a8b464 100644 --- a/src/pages/first/first.ts +++ b/src/pages/first/first.ts @@ -1,46 +1,47 @@ import { Component } from '@angular/core'; import { NavController } from 'ionic-angular'; -import { Browser } from '../../providers/browser/browser'; import { MagazinePage } from '../magazine/magazine'; import { AskPage } from '../ask/ask'; import { CalendarPage } from '../calendar/calendar'; import { SocialPage } from '../social/social'; -/* - Generated class for the MagazinePage page. - - See http://ionicframework.com/docs/v2/components/#navigation for more info on - Ionic pages and navigation. -*/ +/** + * Home page of the Fedora App + */ @Component({ templateUrl: 'first.html', }) export class FirstPage { - private posts: Array; - - constructor(private nav: NavController, private browser: Browser) { - this.posts = []; - } - openPost(event) { - this.browser.open(event.link); + constructor(private nav: NavController) { } - openMag() { + /** + * Navigate to Fedora Magazine section + */ + openMag(): void { this.nav.push(MagazinePage); } - openAsk() { + + /** + * Navigate to Ask Fedora section + */ + openAsk(): void { this.nav.push(AskPage); } - openSocial() { + + /** + * Navigate to Fedora Social section + */ + openSocial(): void { this.nav.push(SocialPage); } - openCal() { - this.nav.push(CalendarPage); - } - - login(event) { + /** + * Navigate to Fedora Calendar section + */ + openCal(): void { + this.nav.push(CalendarPage); } } diff --git a/src/pages/magazine/magazine.ts b/src/pages/magazine/magazine.ts index b0c724f..172d1ca 100644 --- a/src/pages/magazine/magazine.ts +++ b/src/pages/magazine/magazine.ts @@ -4,17 +4,18 @@ import { SocialSharing } from '@ionic-native/social-sharing'; import { Browser } from '../../providers/browser/browser'; import { FedoraMagazineService, Post } from '../../providers/fedora-magazine/fedora-magazine'; -/* - Generated class for the MagazinePage page. - - See http://ionicframework.com/docs/v2/components/#navigation for more info on - Ionic pages and navigation. -*/ +/** + * Shows latest posts from Fedora Magazine + */ @Component({ templateUrl: 'magazine.html', providers: [FedoraMagazineService], }) export class MagazinePage { + + /** + * List of posts from Fedora Magazine + */ private posts: Post[]; constructor(private browser: Browser, @@ -26,21 +27,39 @@ export class MagazinePage { this.updatePosts(); } - updatePosts() { + /** + * Fetch latest posts from Fedor Magazine API + */ + updatePosts(): void { this.fedoraMag.getPosts() .subscribe(posts => { this.posts = posts; }); } - openPost(event) { - this.browser.open(event.link); + /** + * Open a post in a browser + * + * Opens the post in an in-app browser in mobile app and in a new tab on desktop. + * + * @param post post to open + */ + openPost(post:Post): void { + this.browser.open(post.link); } - sharePost(event) { + /** + * Share the post using a third-party app installed in the user's device + * + * Allows to share the post using apps like WhatsApp, Facebook, or any app that + * exposes a share interface to the underlying OS. + * + * @param post post to share + */ + sharePost(post:Post): void { this.socialSharing.share( - event.title, event.title, - null, event.link + post.excerpt, post.title, + null, post.permalink ); } } diff --git a/src/pages/social/social.ts b/src/pages/social/social.ts index 185dd8d..3f79a04 100644 --- a/src/pages/social/social.ts +++ b/src/pages/social/social.ts @@ -12,19 +12,22 @@ const HANDLE = { TWITTER: 'fedora_qa', }; -/* - Generated class for the SocialPage page. +const DEFAULT_POST_TITLE = 'Update from Fedora Project'; - See http://ionicframework.com/docs/v2/components/#navigation for more info on - Ionic pages and navigation. -*/ +/** + * Shows updates from the social media channels of Fedora + */ @Component({ templateUrl: 'social.html', providers: [FacebookProvider, TwitterProvider], }) export class SocialPage { + /** + * List of posts from different social media channels + */ private posts: Post[]; + constructor(private browser: Browser, private fb: FacebookProvider, private twitter: TwitterProvider, private socialSharing: SocialSharing) { this.posts = []; @@ -34,23 +37,45 @@ export class SocialPage { this.updatePosts(); } - private updatePosts() { + /** + * Fetch posts from social media channels + * + * Currently, we fetch posts from Facebook and Twitter + */ + private updatePosts(): void { forkJoin(this.fb.getPosts(HANDLE.FB), this.twitter.getPosts(HANDLE.TWITTER)) .subscribe(values => { this.posts = [...values[0], ...values[1]] as Post[]; }); } - openPost(event) { - this.browser.open(event.link); + /** + * Open a post in a browser + * + * Opens the post in an in-app browser in mobile app and in a new tab on desktop. + * On some platforms, the post may directly open in the app of the social media + * service. + * + * @param post post to open + */ + openPost(post:Post): void { + this.browser.open(post.link); } - sharePost(event) { + /** + * Share the post using a third-party app installed in the user's device + * + * Allows to share the post using apps like WhatsApp, Facebook, or any app that + * exposes a share interface to the underlying OS. + * + * @param post post to share + */ + sharePost(post:Post): void { this.socialSharing.share( - event.title, - event.title, + post.content, + DEFAULT_POST_TITLE, null, - event.link + post.link ); } } diff --git a/src/pages/women/women.ts b/src/pages/women/women.ts index 7ef83df..0b037c9 100644 --- a/src/pages/women/women.ts +++ b/src/pages/women/women.ts @@ -1,11 +1,10 @@ import { Component } from '@angular/core'; -/* - Generated class for the MagazinePage page. - - See http://ionicframework.com/docs/v2/components/#navigation for more info on - Ionic pages and navigation. -*/ +/** + * Fedora Women section + * + * Under construction + */ @Component({ templateUrl: 'women.html', }) diff --git a/src/providers/ask-fedora/ask-fedora.ts b/src/providers/ask-fedora/ask-fedora.ts index 09afe00..1b62eef 100644 --- a/src/providers/ask-fedora/ask-fedora.ts +++ b/src/providers/ask-fedora/ask-fedora.ts @@ -2,45 +2,95 @@ import 'rxjs/add/operator/map'; import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { chooseEndpoint } from '../../utils'; +import { Observable } from 'rxjs/Observable'; +/** + * Ask Fedora API endpoint + * + * It does not support CORS so we have to proxy it through Ionic CLI during development + */ const ENDPOINT = chooseEndpoint('/ask-fedora', 'https://ask.fedoraproject.org/en/api/v1'); +/** + * A question on Ask Fedora + */ export interface Question { - id: string, + /** + * Question ID + */ + id: number, + + /** + * Question title + */ title: string, + + /** + * Permalink to the question + */ link: string, - answers: number, + + /** + * Number of answers to this question + */ + answerCount: number, + + /** + * Content of this question + */ content: string, - timestamp: Date, + + /** + * Time of posting of this question + */ + addedAt: Date, + + /** + * Tags associated with this question + */ tags: string[], - view: number, - vote: number, -} + /** + * Number of views registered by this question + */ + viewCount: number, + + /** + * Total score for this question + * + * It is the sum of all up-votes and down-votes. + */ + score: number, +} -/* - Generated class for the AskFedora provider. - See https://angular.io/docs/ts/latest/guide/dependency-injection.html - for more info on providers and Angular 2 DI. -*/ +/** + * Service for Ask Fedora API + * + * Provides a read-only access to questions and answers posted in Ask Fedora. + */ @Injectable() export class AskFedoraService { constructor(private http: HttpClient) { } - getQuestions() { + /** + * Fetch a list of questions from Ask Fedora API + * + * @returns Observable which emits an array of questions + */ + getQuestions(): Observable { return this.http.get(`${ENDPOINT}/questions/`) - .map((data: any) => data.questions.map(q => ({ + .map((data: any) => (data.questions as any[]).map(q => ({ id: q.id, title: q.title, link: q.url, - answers: q.answer_count, + answerCount: q.answer_count, content: q.summary, - timestamp: q.added_at, + addedAt: new Date(parseInt(q.added_at, 10)), tags: q.tags, - view: q.view_count, - vote: q.score, + viewCount: q.view_count, + score: q.score, }))); } } diff --git a/src/providers/browser/browser.ts b/src/providers/browser/browser.ts index 2b3715d..b038007 100644 --- a/src/providers/browser/browser.ts +++ b/src/providers/browser/browser.ts @@ -4,28 +4,36 @@ import { SpinnerDialog } from '@ionic-native/spinner-dialog'; import { Toast } from '@ionic-native/toast'; import { Platform } from 'ionic-angular'; -/* - Generated class for the Browser provider. - - See https://angular.io/docs/ts/latest/guide/dependency-injection.html - for more info on providers and Angular 2 DI. -*/ +/** + * Wrapper over in-app browser to show a busy indicator while loading web content + */ @Injectable() export class Browser { + private browser: InAppBrowserObject; + constructor(private platform: Platform, private inAppBrowser: InAppBrowser, private spinnerDialog: SpinnerDialog, private toast: Toast) { this.browser = null; } + /** + * Called when content loading is started + */ private startHandler = () => { this.spinnerDialog.show(); } + /** + * Called when content loading is complete + */ private stopHandler = () => { this.spinnerDialog.hide(); } + /** + * Called when there is an error while loading content + */ private errorHandler = (error) => { this.toast.showShortBottom('Something went wrong.'); this.browser.close(); @@ -33,8 +41,15 @@ export class Browser { this.browser = null; } - open(link) { - const browser: InAppBrowserObject = this.inAppBrowser.create(encodeURI(link), '_blank'); + /** + * Open a link in an in-app browser + * + * Attaches lifecycle callbacks only on platforms which support them. + * + * @param link link to open in a an in-app browser + */ + public open(link): void { + const browser = this.inAppBrowser.create(encodeURI(link), '_blank'); this.browser = browser; // Cordova does not fire the events in browser. diff --git a/src/providers/fedo-cal/fedo-cal.ts b/src/providers/fedo-cal/fedo-cal.ts index ce0c7bb..f753d4a 100644 --- a/src/providers/fedo-cal/fedo-cal.ts +++ b/src/providers/fedo-cal/fedo-cal.ts @@ -8,15 +8,31 @@ import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs/Observable'; import { chooseEndpoint } from '../../utils'; - +/** + * FedoCal API endpoint + * + * It does not support CORS so we have to proxy it through Ionic CLI during development + */ const ENDPOINT = chooseEndpoint('/fedocal', 'https://apps.fedoraproject.org/calendar/api'); +/** + * Date format for display + */ const DATE_FORMAT = 'dddd, MMMM Do YYYY'; + +/** + * Time format for display + */ const TIME_FORMAT = 'h:mm A z'; -/* - Convert title to capital case, but skip special names like FESCo, FAmSCo, i18n -*/ +/** + * Convert calendar name from API to a value suitable for display + * + * Convert title to capital case, but skip special names like FESCo, FAmSCo, i18n + * + * @param name Calendar name as in API + * @returns Friendly representation of the name + */ function calendarNameToDisplayName(name: string): string { switch (name) { case 'i18n': @@ -28,43 +44,114 @@ function calendarNameToDisplayName(name: string): string { } } +/** + * A calendar on FedoCal + * + * Calendars are assoicated with a group / SIG / or event. They contain a number + * of recurring meetings or events. + */ export interface Calendar { + + /** + * Name of calendar, as expressed in the API + * + * Serves as an ID for API calls. + */ realName: string, + + /** + * Human friendly calendar name obtained from `realName` + */ displayName: string, + + /** + * Calendar's description + */ description: string, + + /** + * Group with admin previlages for this calendar + */ adminGroup: string, + + /** + * Group with edit previlages for this calendar + */ editorGroup: string, + + /** + * Contact person for this calendar + */ contact: string, + + /** + * Whether the calendar is enabled + */ enabled: boolean, } +/** + * A meeting on a FedoCal calendar + */ export interface Meeting { + + /** + * Name of the meeting + */ name: string, + + /** + * Description of the meeting + */ description: string, + + /** + * Where is the meeting happening? + * + * Can be an IRC channel or even a physical location. + */ location: string, + + /** + * Meeting start time + */ time: Date, + + /** + * Meeting end time + */ timeEnd: Date, + + /** + * Start time formatted for display + */ displayTime: { dateString: string, timeString: string, }, + + /** + * End time formatted for display + */ displayTimeEnd: { dateString: string, timeString: string, } } -/* - Generated class for the FedoCal provider. - - See https://angular.io/docs/ts/latest/guide/dependency-injection.html - for more info on providers and Angular 2 DI. -*/ +/** + * Service for FedoCal + */ @Injectable() export class FedoCalService { constructor(private http: HttpClient) { } + /** + * Fetch the list of calendars from FedoCal API + * + * @returns Observable which emits an array of calendars + */ getCalendars(): Observable { return this.http.get(`${ENDPOINT}/calendars/`) .map((data: any) => @@ -79,7 +166,13 @@ export class FedoCalService { }))); } - getMeetings(calendar): Observable { + /** + * Fetch the list of meetings for a given FedoCal calendar name + * + * @param calendar FedoCal calendar name + * @returns Observable which emits an array of meetings + */ + getMeetings(calendar:string): Observable { return this.http.get(`${ENDPOINT}/meetings/`, { params: { calendar: calendar } }) .map((data: any) => data.meetings.map(m => { @@ -92,6 +185,7 @@ export class FedoCalService { time: mTime.toDate(), timeEnd: mTimeEnd.toDate(), displayTime: { + // Format momentjs object to the defined format for display dateString: mTime.format(DATE_FORMAT), timeString: mTime.format(TIME_FORMAT) }, @@ -105,8 +199,15 @@ export class FedoCalService { } } - -function dateToMoment(date, time, timezone) { +/** + * Convert a date consisting of date, time and timezone as different strings to + * a single momentjs date + * + * @param date Date string + * @param time Time string + * @param timezone Timezone identifier + */ +function dateToMoment(date:string, time:string, timezone:string) { const m = moment.tz(`${date} ${time}`, timezone).tz('UTC'); return m; } diff --git a/src/providers/fedora-magazine/fedora-magazine.ts b/src/providers/fedora-magazine/fedora-magazine.ts index df22c3b..8b52bd9 100644 --- a/src/providers/fedora-magazine/fedora-magazine.ts +++ b/src/providers/fedora-magazine/fedora-magazine.ts @@ -6,60 +6,75 @@ import { chooseEndpoint } from '../../utils'; const ENDPOINT = chooseEndpoint('/fedora-magazine', 'https://fedoramagazine.org/wp-json/wp/v2'); +/** + * Represents a post on Fedora Magazine + */ export interface Post { + /** + * Unique ID of the post, supplied by the CMS + */ id: number, + + /** + * A sluggified link to the post + */ link: string, + + /** + * Permalink to the post + */ + permalink:string, + + /** + * Post title + */ title: string, - image: any, + + /** + * URL to the featured image of the post + */ + image: string, + + /** + * A short excerpt of the post + */ excerpt: string, + + /** + * The content of the post + */ content: string, - date: Date -} -/* - Generated class for the FedoraMag provider. + /** + * Time of publication + */ + publishedAt: Date +} - See https://angular.io/docs/ts/latest/guide/dependency-injection.html - for more info on providers and Angular 2 DI. -*/ +/** + * Service for fetching posts from Fedora Magazine API + */ @Injectable() export class FedoraMagazineService { constructor(private http: HttpClient) { } + /** + * Fetch the list of latest posts on Fedora Magazine + * + * @returns Observable which emits an array of posts + */ getPosts(): Observable { return this.http.get(`${ENDPOINT}/posts`) .map((data: any[]) => data.map((post: any) => ({ id: post.id, link: post.link, + permalink: post.guid.rendered, title: post.title.rendered, image: post.featured_media, excerpt: post.excerpt.rendered, content: post.content.rendered, - date: new Date(post.date_gmt+'Z'), + publishedAt: new Date(post.date_gmt+'Z'), }))); } } - - /* getMedia_url(posts) { - if (!_.isEmpty(this.media_url[posts])) { - return Promise.resolve(this.media_url[posts]); - } - - return new Promise((resolve, reject) => { - this.request.get(this.API.media_url,{ posts : posts }) - .then(data => { - this.media_url[posts] = _.map(data, i => { - // var excerpt = p.excerpt.rendered; - var img_url = { - img_id: i.id, - img_link: i.guid.rendered, - }; - //image_url = func_url(post.image); - return post; - }); - - return resolve(this.posts); - }).catch(reject); - }); - }*/ diff --git a/src/providers/social/facebook.ts b/src/providers/social/facebook.ts index 48e4ea0..a3192b4 100644 --- a/src/providers/social/facebook.ts +++ b/src/providers/social/facebook.ts @@ -7,20 +7,30 @@ import { SocialProvider, Post, FACEBOOK } from './social'; import { Observable } from 'rxjs/Observable'; import { Observer } from 'rxjs/Observer'; -/* - Generated class for the Fb provider. - - See https://angular.io/docs/ts/latest/guide/dependency-injection.html - for more info on providers and Angular 2 DI. -*/ +/** + * Service for Facebook API + * + * Allows to fetch data from Facebook pages. + */ @Injectable() export class FacebookProvider implements SocialProvider { + + /** + * Facebook API instance + */ private fb: Facebook; constructor() { this.fb = new Facebook(ENV.FB_CONFIG); } - private api(uri) { + /** + * Perform a call to a Facebook API using the library + * + * Converts callback-style error handling to observables. + * + * @param uri Facebook API URI + */ + private api(uri:string) { return Observable.create((emitter: Observer) => { this.fb.api(uri, res => { if (!res || res.error) { @@ -33,6 +43,13 @@ export class FacebookProvider implements SocialProvider { }); } + /** + * Fetch the list of posts for a given Facebook page + * + * @param page ID of the Facebook page + * @param args Extra args for pagination etc. + * @returns Observable which emits an array of Facebook posts + */ public getPosts(page: string, args?): Observable { return this.api(`${page}/posts`) .map(res => { diff --git a/src/providers/social/social.ts b/src/providers/social/social.ts index 8832ffe..10b17b7 100644 --- a/src/providers/social/social.ts +++ b/src/providers/social/social.ts @@ -1,19 +1,51 @@ import { Observable } from "rxjs/Observable"; +/** + * ID for facebook provider + */ export const FACEBOOK = 'facebook'; + +/** + * ID for twitter provider + */ export const TWITTER = 'twitter'; +/** + * Represents a post in social media service + */ export interface Post { + /** + * Service specific unique ID + */ id:string, + + /** + * Origin of the post + */ origin:string, + + /** + * Link to the app of website of the social media service hosting the post + */ link:string, - content:string, -} -export interface GetPostExtraArgs { - offset?: string, + /** + * Text content of the post + */ + content:string, } +/** + * A generic social media provider + */ export interface SocialProvider { - getPosts(resID: string, args?:GetPostExtraArgs): Observable + + /** + * Fetch the list of posts for a given service specific resource ID + * + * @param resID service specific resource IS + * @param args Extra args for pagination etc. + * @returns Observable which emits an array of posts + */ + getPosts(resID: string, args?:{ offset?: string }): Observable } diff --git a/src/providers/social/twitter.ts b/src/providers/social/twitter.ts index 490c566..444b7d6 100644 --- a/src/providers/social/twitter.ts +++ b/src/providers/social/twitter.ts @@ -9,17 +9,21 @@ import ENV from '@environment'; const ENDPOINT = chooseEndpoint('/twitter', 'https://api.twitter.com/1.1'); -/* - Generated class for the Twitter provider. - - See https://angular.io/docs/ts/latest/guide/dependency-injection.html - for more info on providers and Angular 2 DI. -*/ +/** + * Service for Twitter API + */ @Injectable() export class TwitterProvider implements SocialProvider { constructor(private http: HttpClient) { } + /** + * Fetch the list of tweets for a given Twitter handle + * + * @param handle Twitter handle whose tweets to load + * @param args Extra args for pagination etc. + * @returns Observable which emits an array of tweets + */ public getPosts(handle: string, args?): Observable { return this.http.get(`${ENDPOINT}/statuses/user_timeline.json`, { params: { screen_name: handle }, diff --git a/src/utils.ts b/src/utils.ts index 25bc712..77c0b48 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,8 +1,9 @@ /** * Choose appropriate API end point or the proxy to avoid CORS in Ionic dev server + * * @param devServerEndpoint endpoint in Ionic dev server - * @param appEndpoint endpoint to use in real application + * @param appEndpoint endpoint to use in real application */ export function chooseEndpoint(devServerEndpoint:string, appEndpoint:string) { // courtesy: https://forum.ionicframework.com/t/check-if-run-on-emulator-dev-production-or-livereload/71845/9