#93 Improve offline caching using IndexedDB
Merged 3 months ago by amitosh. Opened a year ago by amitosh.
amitosh/Fedora-app mag-offline-improvement  into  master

file modified
+2 -2

@@ -14,7 +14,7 @@ 

      },

      {

        "path": "/fedora-magazine",

-       "proxyUrl": "https://fedoramagazine.org/wp-json/wp/v2/"

+       "proxyUrl": "https://fedoramagazine.org"

      },

      {

        "path": "/ask-fedora",

@@ -22,7 +22,7 @@ 

      },

      {

        "path": "/community-blog",

-       "proxyUrl": "https://communityblog.fedoraproject.org/wp-json/wp/v2/"

+       "proxyUrl": "https://communityblog.fedoraproject.org"

      },

      {

        "path": "/podcast",

file modified
+103 -8

@@ -14,6 +14,14 @@ 

          "source-map": "^0.5.6",

          "typescript": "~2.6.1",

          "webpack-sources": "^1.0.1"

+       },

+       "dependencies": {

+         "typescript": {

+           "version": "2.6.2",

+           "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.6.2.tgz",

+           "integrity": "sha1-PFtv1/beCRQmkCfwPAlGdY92c6Q=",

+           "dev": true

+         }

        }

      },

      "@angular/common": {

@@ -744,15 +752,15 @@ 

        "dev": true

      },

      "@types/lodash": {

-       "version": "4.14.123",

-       "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.123.tgz",

-       "integrity": "sha512-pQvPkc4Nltyx7G1Ww45OjVqUsJP4UsZm+GWJpigXgkikZqJgRm4c48g027o6tdgubWHwFRF15iFd+Y4Pmqv6+Q==",

+       "version": "4.14.121",

+       "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.121.tgz",

+       "integrity": "sha512-ORj7IBWj13iYufXt/VXrCNMbUuCTJfhzme5kx9U/UtcIPdJYuvPDUAlHlbNhz/8lKCLy9XGIZnGrqXOtQbPGoQ==",

        "dev": true

      },

      "@types/lodash-es": {

-       "version": "4.17.3",

-       "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.3.tgz",

-       "integrity": "sha512-iHI0i7ZAL1qepz1Y7f3EKg/zUMDwDfTzitx+AlHhJJvXwenP682ZyGbgPSc5Ej3eEAKVbNWKFuwOadCj5vBbYQ==",

+       "version": "4.17.2",

+       "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.2.tgz",

+       "integrity": "sha512-Bpl6bh7kF9IdepJEfSd6g/E6OPq99jcIQP5g0xqv47P1FjLOP1+PENCy2vWvrzY+KwnnHwGAbUcTxbEr+WAnUw==",

        "dev": true,

        "requires": {

          "@types/lodash": "*"

@@ -3291,6 +3299,11 @@ 

          "repeating": "^2.0.0"

        }

      },

+     "dexie": {

+       "version": "2.0.4",

+       "resolved": "https://registry.npmjs.org/dexie/-/dexie-2.0.4.tgz",

+       "integrity": "sha512-aQ/s1U2wHxwBKRrt2Z/mwFNHMQWhESerFsMYzE+5P5OsIe5o1kgpFMWkzKTtkvkyyEni6mWr/T4HUJuY9xIHLA=="

+     },

      "di": {

        "version": "0.0.1",

        "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz",

@@ -3679,8 +3692,7 @@ 

      "escape-string-regexp": {

        "version": "1.0.5",

        "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",

-       "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",

-       "dev": true

+       "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="

      },

      "escope": {

        "version": "3.6.0",

@@ -4013,6 +4025,30 @@ 

        "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=",

        "dev": true

      },

+     "filename-reserved-regex": {

+       "version": "1.0.0",

+       "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-1.0.0.tgz",

+       "integrity": "sha1-5hz4BfDeHJhFZ9A4bcXfUO5a9+Q="

+     },

+     "filenamify": {

+       "version": "1.2.1",

+       "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-1.2.1.tgz",

+       "integrity": "sha1-qfL/0RxQO+0wABUCknI3jx8TZaU=",

+       "requires": {

+         "filename-reserved-regex": "^1.0.0",

+         "strip-outer": "^1.0.0",

+         "trim-repeated": "^1.0.0"

+       }

+     },

+     "filenamify-url": {

+       "version": "1.0.0",

+       "resolved": "https://registry.npmjs.org/filenamify-url/-/filenamify-url-1.0.0.tgz",

+       "integrity": "sha1-syvYExnvWGO3MHi+1Q9GpPeXX1A=",

+       "requires": {

+         "filenamify": "^1.0.0",

+         "humanize-url": "^1.0.0"

+       }

+     },

      "fileset": {

        "version": "2.0.3",

        "resolved": "https://registry.npmjs.org/fileset/-/fileset-2.0.3.tgz",

@@ -5214,6 +5250,15 @@ 

          }

        }

      },

+     "humanize-url": {

+       "version": "1.0.1",

+       "resolved": "https://registry.npmjs.org/humanize-url/-/humanize-url-1.0.1.tgz",

+       "integrity": "sha1-9KuZ4NKIF0yk4eUEB8VfuuRk7/8=",

+       "requires": {

+         "normalize-url": "^1.0.0",

+         "strip-url-auth": "^1.0.0"

+       }

+     },

      "iconv-lite": {

        "version": "0.4.23",

        "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz",

@@ -5493,6 +5538,11 @@ 

          "path-is-inside": "^1.0.1"

        }

      },

+     "is-plain-obj": {

+       "version": "1.1.0",

+       "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz",

+       "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4="

+     },

      "is-plain-object": {

        "version": "2.0.4",

        "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",

@@ -7251,6 +7301,17 @@ 

        "integrity": "sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=",

        "dev": true

      },

+     "normalize-url": {

+       "version": "1.9.1",

+       "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-1.9.1.tgz",

+       "integrity": "sha1-LMDWazHqIwNkWENuNiDYWVTGbDw=",

+       "requires": {

+         "object-assign": "^4.0.1",

+         "prepend-http": "^1.0.0",

+         "query-string": "^4.1.0",

+         "sort-keys": "^1.0.0"

+       }

+     },

      "npm-run-path": {

        "version": "2.0.2",

        "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",

@@ -7752,6 +7813,11 @@ 

        "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==",

        "dev": true

      },

+     "prepend-http": {

+       "version": "1.0.4",

+       "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz",

+       "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw="

+     },

      "preserve": {

        "version": "0.2.0",

        "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz",

@@ -9034,6 +9100,14 @@ 

          }

        }

      },

+     "sort-keys": {

+       "version": "1.1.2",

+       "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz",

+       "integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=",

+       "requires": {

+         "is-plain-obj": "^1.0.0"

+       }

+     },

      "source-list-map": {

        "version": "2.0.1",

        "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz",

@@ -9337,6 +9411,19 @@ 

          "get-stdin": "^4.0.1"

        }

      },

+     "strip-outer": {

+       "version": "1.0.1",

+       "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz",

+       "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==",

+       "requires": {

+         "escape-string-regexp": "^1.0.2"

+       }

+     },

+     "strip-url-auth": {

+       "version": "1.0.1",

+       "resolved": "https://registry.npmjs.org/strip-url-auth/-/strip-url-auth-1.0.1.tgz",

+       "integrity": "sha1-IrD6OkE4WzO+PzMVUbu4N/oM164="

+     },

      "supports-color": {

        "version": "5.2.0",

        "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.2.0.tgz",

@@ -9522,6 +9609,14 @@ 

        "integrity": "sha1-WIeWa7WCpFA6QetST301ARgVphM=",

        "dev": true

      },

+     "trim-repeated": {

+       "version": "1.0.0",

+       "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz",

+       "integrity": "sha1-42RqLqTokTEr9+rObPsFOAvAHCE=",

+       "requires": {

+         "escape-string-regexp": "^1.0.2"

+       }

+     },

      "trim-right": {

        "version": "1.0.1",

        "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz",

file modified
+4 -1

@@ -59,7 +59,9 @@ 

      "cordova-plugin-whitelist": "^1.3.3",

      "cordova-plugin-x-socialsharing": "^5.4.3",

      "cordova-plugin-x-toast": "^2.7.2",

+     "dexie": "^2.0.4",

      "es6-promise-plugin": "^4.2.2",

+     "filenamify-url": "^1.0.0",

      "ionic-angular": "^3.9.4",

      "ionic-plugin-keyboard": "^2.2.1",

      "ionicons": "^3.0.0",

@@ -74,7 +76,8 @@ 

      "@angular/compiler-cli": "^5.2.11",

      "@ionic/app-scripts": "^3.2.0",

      "@types/jasmine": "ts2.6",

-     "@types/lodash-es": "^4.17.3",

+     "@types/lodash-es": "ts2.6",

+     "@types/lodash": "ts2.6",

      "@types/node": "^10.12.10",

      "angular2-template-loader": "^0.6.2",

      "html-loader": "^0.5.5",

file modified
+2

@@ -26,6 +26,7 @@ 

  import { NoInternetComponent } from '../components/no-internet/no-internet';

  

  import { Browser } from '../providers/browser/browser';

+ import { DatabaseProvider } from '../providers/database/database';

  

  @NgModule({

    declarations: [

@@ -87,6 +88,7 @@ 

      StatusBar,

      Toast,

      { provide: ErrorHandler, useClass: IonicErrorHandler },

+     DatabaseProvider,

    ]

  })

  export class AppModule { }

@@ -0,0 +1,85 @@ 

+ <?xml version="1.0" encoding="UTF-8" standalone="no"?>

+ <!-- Created with Inkscape (http://www.inkscape.org/) -->

+ 

+ <svg

+    xmlns:dc="http://purl.org/dc/elements/1.1/"

+    xmlns:cc="http://creativecommons.org/ns#"

+    xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"

+    xmlns:svg="http://www.w3.org/2000/svg"

+    xmlns="http://www.w3.org/2000/svg"

+    xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"

+    xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"

+    width="33.866665mm"

+    height="33.865509mm"

+    viewBox="0 0 119.99999 119.9959"

+    id="svg5311"

+    version="1.1"

+    inkscape:version="0.91 r13725"

+    sodipodi:docname="logo.svg">

+   <defs

+      id="defs5313" />

+   <sodipodi:namedview

+      id="base"

+      pagecolor="#ffffff"

+      bordercolor="#666666"

+      borderopacity="1.0"

+      inkscape:pageopacity="0.0"

+      inkscape:pageshadow="2"

+      inkscape:zoom="0.35"

+      inkscape:cx="720.61746"

+      inkscape:cy="-365.81321"

+      inkscape:document-units="px"

+      inkscape:current-layer="layer1"

+      showgrid="false"

+      fit-margin-top="0"

+      fit-margin-left="0"

+      fit-margin-right="0"

+      fit-margin-bottom="0"

+      inkscape:window-width="1366"

+      inkscape:window-height="704"

+      inkscape:window-x="0"

+      inkscape:window-y="27"

+      inkscape:window-maximized="1" />

+   <metadata

+      id="metadata5316">

+     <rdf:RDF>

+       <cc:Work

+          rdf:about="">

+         <dc:format>image/svg+xml</dc:format>

+         <dc:type

+            rdf:resource="http://purl.org/dc/dcmitype/StillImage" />

+         <dc:title></dc:title>

+       </cc:Work>

+     </rdf:RDF>

+   </metadata>

+   <g

+      inkscape:label="Layer 1"

+      inkscape:groupmode="layer"

+      id="layer1"

+      transform="translate(862.76032,-46.553102)">

+     <g

+        transform="matrix(1.0016156,0,0,1.0016156,-1551.8414,-53.608799)"

+        id="g5241">

+       <path

+          inkscape:connector-curvature="0"

+          style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:expanded;font-size:11.72808552px;line-height:89.99999762%;font-family:Comfortaa;-inkscape-font-specification:'Comfortaa Bold Expanded';text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#3c6eb4;fill-opacity:1;stroke:none"

+          id="path5239"

+          d="m 799.33773,208.89468 0,4.61564 c 0,0.30924 -0.18707,0.49631 -0.5612,0.56121 l -0.0401,0 c -0.31687,0 -0.51158,-0.19661 -0.58411,-0.58984 l 0,-7.3186 c 0.0458,-0.42376 0.22524,-0.63564 0.5383,-0.63565 l 0.0859,0 c 0.31687,10e-6 0.52875,0.22525 0.63565,0.67574 l 2.62851,5.87549 c 0.0344,-0.13362 0.61083,-1.47555 1.72944,-4.0258 l 1.0766,-2.31927 c 0.12979,-0.13743 0.27105,-0.20615 0.42377,-0.20616 l 0.0859,0 c 0.31304,10e-6 0.49248,0.21189 0.5383,0.63565 l 0,7.3186 c -0.0725,0.39323 -0.26725,0.58984 -0.58412,0.58984 l -0.0401,0 c -0.37415,-0.0649 -0.56122,-0.25197 -0.56121,-0.56121 l 0,-4.62136 c -1.4202,3.21453 -2.16275,4.87334 -2.22765,4.97641 -0.15653,0.13744 -0.30924,0.20616 -0.45813,0.20616 -0.33596,0 -0.61275,-0.33978 -0.83036,-1.01933 l -1.85542,-4.15752 m -7.79387,-3.39015 5.3601,0 c 0.36268,0.0687 0.54402,0.2558 0.54402,0.56121 l 0,0.0859 c 0,0.31306 -0.21189,0.49249 -0.63565,0.5383 l -1.99286,0 0,6.82038 c 0,0.30924 -0.19089,0.49631 -0.57266,0.56121 l -0.0286,0 c -0.31688,0 -0.51158,-0.19661 -0.58411,-0.58984 l 0,-6.79175 -2.03867,0 c -0.39323,-0.0763 -0.58985,-0.28251 -0.58985,-0.61847 0.084,-0.37795 0.26343,-0.56693 0.53831,-0.56694" />

+       <path

+          inkscape:connector-curvature="0"

+          style="fill:#294172;fill-opacity:1"

+          id="path35"

+          d="m 807.77586,159.90439 c 0,-33.08386 -26.81923,-59.90405 -59.90309,-59.90405 -33.06851,0 -59.87718,26.79812 -59.90117,59.86183 l -0.002,-0.002 0,46.35579 0.002,0.002 c 0.0182,7.50719 6.10631,13.58472 13.61831,13.58472 0.0211,0 0.0422,-0.002 0.0643,-0.002 l 0.004,0.002 46.23777,0 0,0 c 33.07426,-0.0134 59.88006,-26.82403 59.88006,-59.89829 z" />

+       <path

+          inkscape:connector-curvature="0"

+          style="fill:#3c6eb4;fill-opacity:1"

+          id="path39"

+          d="m 765.46056,114.20011 c -15.52773,0 -28.11456,12.58684 -28.11456,28.11456 0,0.006 0,0.0125 0,0.0202 l 0,14.88103 -14.83401,0 c -0.003,0 -0.003,0 -0.005,0 -15.52869,0 -28.11457,12.52542 -28.11457,28.05123 0,15.52773 12.58588,28.11553 28.11457,28.11553 15.52677,0 28.11456,-12.58876 28.11456,-28.11553 0,-0.005 0,-0.0125 0,-0.0182 l 0,-14.94435 14.83305,0 c 0.003,0 0.005,0 0.006,0 15.52677,0 28.11457,-12.4621 28.11457,-27.98983 0,-15.52772 -12.5878,-28.11456 -28.11457,-28.11456 z m -28.1136,71.09192 c -0.0134,8.18365 -6.65324,14.81577 -14.83881,14.81577 -8.19516,0 -14.90021,-6.64459 -14.90021,-14.83976 0,-8.19612 6.70505,-14.9645 14.90021,-14.9645 0.008,0 0.0144,0.002 0.0201,0.002 l 12.71445,0 c 0.004,0 0.007,-0.002 0.01,-0.002 1.15717,0 2.09653,0.93649 2.09653,2.09365 0,9.6e-4 -9.6e-4,0.003 -9.6e-4,0.005 l 0,12.89004 -9.6e-4,0 z m 28.11264,-28.07714 c -0.005,0 -0.01,0 -0.0144,0 l -12.72308,0 c -0.002,0 -0.006,0 -0.006,0 -1.15909,0 -2.09557,-0.9384 -2.09557,-2.09557 l 0,-0.002 0,-12.82767 c 0.0144,-8.18653 6.65228,-14.81578 14.83784,-14.81578 8.19613,0 14.90213,6.64364 14.90213,14.84072 0.002,8.19517 -6.70504,14.90022 -14.90117,14.90022 z" />

+       <path

+          inkscape:connector-curvature="0"

+          style="fill:#ffffff"

+          id="path41"

+          d="m 737.34696,157.21489 0,-14.88102 c 0,-0.008 0,-0.0144 0,-0.0202 0,-15.52773 12.58683,-28.11457 28.11456,-28.11457 2.35656,0 4.02994,0.26387 6.21186,0.83573 3.17885,0.83286 5.77624,3.43792 5.7772,6.47093 9.6e-4,3.66532 -2.65975,6.32988 -6.63596,6.32988 -1.89503,0 -2.57916,-0.3627 -5.35406,-0.3627 -8.18557,0 -14.82345,6.62925 -14.83784,14.81578 l 0,12.82767 0,0.002 c 0,1.15717 0.93648,2.09557 2.09557,2.09557 0,0 0.004,0 0.006,0 l 9.74382,0 c 3.63174,0 6.56304,2.90539 6.56591,6.54577 0,3.64325 -2.93513,6.54289 -6.56591,6.54289 l -11.84514,0 0,14.94434 c 0,0.006 0,0.0134 0,0.0202 0,15.52581 -12.58779,28.11361 -28.11457,28.11361 -2.35559,0 -4.02993,-0.26578 -6.2109,-0.83573 -3.17885,-0.8319 -5.7772,-3.43888 -5.77816,-6.47093 0,-3.66532 2.65976,-6.33084 6.63693,-6.33084 1.89311,0 2.57915,0.36366 5.35213,0.36366 8.18557,0 14.82537,-6.63021 14.83977,-14.81578 0,0 0,-12.89388 0,-12.89388 0,-1.15525 -0.93936,-2.09173 -2.09653,-2.09173 -0.002,0 -0.003,0 -0.006,0 l -9.74285,-0.002 c -3.63174,0 -6.56688,-2.89771 -6.56688,-6.53905 -0.002,-3.66436 2.96489,-6.5496 6.63597,-6.5496 l 11.77509,0 0,0 z" />

+     </g>

+   </g>

+ </svg>

file modified
+9 -8

@@ -14,8 +14,8 @@ 

  <ion-content>

    <ion-slides autoplay="3000" centeredSlides effect="fade" loop speed="700" *ngIf="carousel.length !== 0">

      <ion-slide *ngFor="let post of carousel" tappable (click)="openUpdate(post)">

-       <a href="{{post.link}}">

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

+       <a [href]="post.postLink">

+         <img [src]="post.url" />

        </a>

      </ion-slide>

    </ion-slides>

@@ -32,24 +32,25 @@ 

      </ion-segment>

      <div [ngSwitch]="latestActive">

        <ion-list *ngSwitchCase="'blog'">

-         <ion-list *ngIf="blogposts.length !== 0">

-           <ion-card *ngFor="let blogpost of blogposts" tappable (click)="openUpdate(blogpost)">

+         <ion-list *ngIf="blogPosts.length !== 0">

+           <ion-card *ngFor="let blogpost of blogPosts" tappable (click)="openUpdate(blogpost)">

              <ion-card-header [innerHTML]="blogpost.title" class="body-title"></ion-card-header>

-             <ion-card-content [innerHTML]="blogpost.publishedAt" class="body-subtitle">

+             <ion-card-content class="body-subtitle">

+               {{ beautifyDateBlog(blogpost.publishedAt) }}

              </ion-card-content>

            </ion-card>

          </ion-list>

        </ion-list>

        <ion-list *ngSwitchCase="'social'">

-         <ion-list *ngIf="socialposts.length !== 0">

-           <ion-card *ngFor="let update of socialposts" tappable (click)="openUpdate(update)">

+         <ion-list *ngIf="socialPosts.length !== 0">

+           <ion-card *ngFor="let update of socialPosts" tappable (click)="openUpdate(update)">

              <ion-card-header>

                <img src="./assets/img/fedora_colored.svg" height="34px">

                <div>

                  <span class="active-title">Fedora</span>

                  <span class="body-subtitle">

                    <ion-icon name="logo-{{ update.origin }}" color="fedoraltblue"></ion-icon>

-                   {{update.date}}

+                   {{ beautifyDateSocial(update.date) }}

                  </span>

                </div>

              </ion-card-header>

file modified
+94 -48

@@ -1,55 +1,71 @@ 

  import { Component } from '@angular/core';

  import { NavController, ToastController } from 'ionic-angular';

- import { NotificationsPage } from '../../pages/notifications/notifications'

+ import { NotificationsPage } from '../../pages/notifications/notifications';

  import { Browser } from '../../providers/browser/browser';

  import { FacebookProvider } from '../../providers/social/facebook';

  import { TwitterProvider } from '../../providers/social/twitter';

  import { SocialMediaPost } from '../../providers/social/social';

  import { combineLatest } from 'rxjs/observable/combineLatest';

- import { FedoraMagazineService, Post, Image } from '../../providers/fedora-magazine/fedora-magazine';

- import { CommunityBlogService } from '../../providers/community-blog/community-blog';

+ import { merge } from 'rxjs/observable/merge';

+ import {

+   WordPressBlogProvider,

+   BlogPost,

+   Blog,

+   BLOG

+ } from '../../providers/word-press-blog/word-press-blog';

+ import { CarouselProvider, Image } from '../../providers/carousel/carousel';

+ import { beautifyDate } from '../../utils';

  

  const HANDLE = Object.seal({

    FB: 'TheFedoraProject',

-   TWITTER: 'fedora',

+   TWITTER: 'fedora'

  });

  

  @Component({

    selector: 'page-home',

    templateUrl: 'home.html',

-   providers: [FacebookProvider, TwitterProvider, FedoraMagazineService, CommunityBlogService]

+   providers: [

+     FacebookProvider,

+     TwitterProvider,

+     CarouselProvider,

+     WordPressBlogProvider

+   ]

  })

- 

  export class HomePage {

    /**

     * Set the currently active segment on Home Page as Community Blog

     */

-   latestActive: String = "blog";

+   latestActive: String = 'blog';

  

    /**

     * List of Facebook and Twitter Posts

     */

-   socialposts: SocialMediaPost[];

+   socialPosts: SocialMediaPost[] = [];

  

    /**

     * List of images fetched from FedoraMag showing top 5 latest events

     */

-   carousel: Image[];

+   carousel: Image[] = [];

  

    /**

     * List of posts from Fedora Community Blog

     */

-   blogposts: Post[];

- 

-   constructor(public navCtrl: NavController, private browser: Browser,

-               private fb: FacebookProvider,

-               private twitter: TwitterProvider,

-               private fedoraMag: FedoraMagazineService,

-               private communityblog: CommunityBlogService,

-               private toast: ToastController) {

-     this.socialposts = [];

-     this.blogposts = [];

-     this.carousel = [];

+   blogPosts: BlogPost[] = [];

+ 

+   private communityBlog: Blog;

+ 

+   loadingCommunityBlog = false;

+ 

+   constructor(

+     public navCtrl: NavController,

+     private browser: Browser,

+     private fb: FacebookProvider,

+     private twitter: TwitterProvider,

+     private toast: ToastController,

+     private carouselProvider: CarouselProvider,

+     wordpress: WordPressBlogProvider

+   ) {

+     this.communityBlog = wordpress.getBlog(BLOG.communityBlog);

    }

  

    /**

@@ -58,44 +74,68 @@ 

     * Currently, we fetch posts from Facebook and Twitter

     */

    private updateSocialMedia(): void {

-     combineLatest(this.fb.getPosts(HANDLE.FB), this.twitter.getPosts(HANDLE.TWITTER))

-       .subscribe(values => {

-         this.socialposts = [...values[0], ...values[1]] as SocialMediaPost[];

-       }, () => this.toast.create({

-           message: 'Could not load feed',

-           position: 'bottom',

-           duration: 2000,

-         }).present()

-       );

+     combineLatest(

+       this.fb.getPosts(HANDLE.FB),

+       this.twitter.getPosts(HANDLE.TWITTER)

+     ).subscribe(

+       values => {

+         this.socialPosts = [...values[0], ...values[1]] as SocialMediaPost[];

+       },

+       () =>

+         this.toast

+           .create({

+             message: 'Could not load feed',

+             position: 'bottom',

+             duration: 2000

+           })

+           .present()

+     );

    }

  

    /**

     * Fetch Latest 5 Images from 'Latest from Fedora Section' of Magazine for carousel

     */

    private updateImages(): void {

-     this.fedoraMag.getImages()

-       .subscribe(images => {

+     this.carouselProvider.getImages().subscribe(

+       images => {

          this.carousel = images;

-       }, () => this.toast.create({

-         message: 'Could not update header',

-         position: 'bottom',

-         duration: 2000,

-       }).present()

+       },

+       () =>

+         this.toast

+           .create({

+             message: 'Could not update header',

+             position: 'bottom',

+             duration: 2000

+           })

+           .present()

      );

    }

  

    /**

-    * Fetch Blog Posts from the Fedora Community Blog

+    * Fetch latest posts from Fedor Magazine API

     */

-   private updateBlogPosts(): void {

-     this.communityblog.getBlogPosts('null')

-       .subscribe(blogposts => {

-         this.blogposts = blogposts;

-       }, () => this.toast.create({

-         message: 'Could not load blog posts',

-         position: 'bottom',

-         duration: 2000,

-       }).present()

+   updateBlogPosts(): void {

+     this.loadingCommunityBlog = true;

+ 

+     merge(

+       this.communityBlog.loadCachedPosts(),

+       this.communityBlog.updatePosts()

+     ).subscribe(

+       posts => {

+         this.blogPosts = posts;

+       },

+       () => {

+         this.toast

+           .create({

+             message: 'Could not load blog posts',

+             position: 'bottom',

+             duration: 2000

+           })

+           .present();

+       },

+       () => {

+         this.loadingCommunityBlog = false;

+       }

      );

    }

  

@@ -106,7 +146,7 @@ 

    }

  

    /**

-    * Opens a update in a browser

+    * Opens an update in a browser

     *

     * Opens the update in an in-app browser in mobile app and in a new tab on desktop.

     *

@@ -120,7 +160,13 @@ 

     * Open the notifications pane from the home page

     */

    openNotificationPage() {

-     this.navCtrl.push(NotificationsPage, { animate: true, direction: 'forward' });

+     this.navCtrl.push(NotificationsPage, {

+       animate: true,

+       direction: 'forward'

+     });

    }

  

+   beautifyDateSocial = x => beautifyDate(x, 'social');

+ 

+   beautifyDateBlog = x => beautifyDate(x, 'blog');

  }

@@ -12,8 +12,10 @@ 

  </ion-header>

  

  <ion-content class="mag">

-   <ion-label>Latest Articles from Magazine

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

+   <ion-label>

+     Latest Articles from Magazine

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

+       Sort

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

      </button>

    </ion-label>

@@ -23,7 +25,7 @@ 

          <ion-col col-7>

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

            <ion-card-content>

-             <span [innerHTML]="post.publishedAt + ' — ' + post.comments + ' comments'" class="body-subtitle"></span>

+             <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>

file modified
+41 -13

@@ -1,29 +1,33 @@ 

  import { Component } from '@angular/core';

  

  import { Browser } from '../../providers/browser/browser';

- import { FedoraMagazineService, Post } from '../../providers/fedora-magazine/fedora-magazine';

  import { NotificationsPage } from '../../pages/notifications/notifications';

  import { NavController, ActionSheetController, ToastController } from 'ionic-angular';

+ import { WordPressBlogProvider, Blog, BlogPost, BLOG } from '../../providers/word-press-blog/word-press-blog';

+ import { merge } from 'rxjs/observable/merge';

+ import { beautifyDate } from '../../utils';

  

  /**

   * Shows latest posts from Fedora Magazine

   */

  @Component({

    templateUrl: 'magazine.html',

-   providers: [FedoraMagazineService],

+   providers: [WordPressBlogProvider],

  })

  export class MagazinePage {

  

    /**

     * List of posts from Fedora Magazine

     */

-   private posts: Post[];

+   posts: BlogPost[];

  

    /**

     * Number of posts

     */

    postCount = 15;

  

+   currentPage = 1;

+ 

    //Initialize array for showing calendar icon

    bookmarkIcon: string[] = [];

  

@@ -34,8 +38,18 @@ 

    //currently Active Sort Parameter

    sortBy: string = 'latest';

  

+   private blog: Blog;

+ 

+   private loading: boolean = false;

+ 

+   private error: boolean = false;

+ 

    constructor(private browser: Browser,

-     private fedoraMag: FedoraMagazineService, public navCtrl: NavController, public actionSheetCtrl: ActionSheetController, public toastCtrl: ToastController) {

+     private navCtrl: NavController,

+     private actionSheetCtrl: ActionSheetController,

+     private toastCtrl: ToastController,

+     wordpress: WordPressBlogProvider) {

+     this.blog = wordpress.getBlog(BLOG.fedoraMagazine);

      for (let i = 0; i < 50; i++) {

        //update src of icon

        this.bookmarkIcon[i] = this.inactiveIcon;

@@ -43,16 +57,25 @@ 

    }

  

    ngOnInit() {

-     this.updatePosts(this.postCount);

+     this.updateCachedPosts();

    }

  

    /**

-    * Fetch latest posts from Fedor Magazine API

+    * Fetch latest posts from Fedora Magazine API

     */

-   updatePosts(postCount): void {

-     this.fedoraMag.getPosts(postCount)

+ 

+   updateCachedPosts(): void {

+     this.loading = true;

+     this.error = false;

+ 

+     merge(this.blog.loadCachedPosts(), this.blog.updatePosts())

        .subscribe(posts => {

          this.posts = posts;

+       }, error => {

+         this.error = true;

+         console.error(error);

+       }, () => {

+         this.loading = false;

        });

    }

  

@@ -61,7 +84,8 @@ 

     */

    loadMore(postCount): void {

      this.postCount += 5;

-     this.updatePosts(this.postCount);

+     this.blog.fetchPosts({ page: ++this.currentPage })

+       .subscribe(posts => this.posts.splice(this.posts.length, 0, ...posts));

    }

  

    /**

@@ -71,14 +95,13 @@ 

     *

     * @param post post to open

     */

-   openPost(post: Post): void {

+   openPost(post: BlogPost): void {

      this.browser.open(post.link);

    }

  

    /**

    * Create an action sheet to sort the articles

    */

- 

    presentActionSheet() {

      let actionSheet = this.actionSheetCtrl.create({

        title: 'SORT ARTICLES BY',

@@ -143,9 +166,9 @@ 

    }

  

    /**

-  * Function called when someone taps the star to subscribe to the calendar

+  * Function called when someone taps the star to bookmark the blog post

   */

-   addToBookmark(post: Post, i: number): void {

+   addToBookmark(post: BlogPost, i: number): void {

      /**

       * Declare toasts for showing events

       */

@@ -169,4 +192,9 @@ 

        unsubscribedToast.present();

      }

    }

+ 

+   beautify(date:Date) {

+     return beautifyDate(date, 'blog');

+   }

+ 

  }

file modified
+10 -7

@@ -3,8 +3,7 @@ 

  import { NotificationsPage } from '../../pages/notifications/notifications';

  import { PackageSearchPage } from '../package-search/package-search';

  import { Browser } from '../../providers/browser/browser';

- import { Post } from '../../providers/fedora-magazine/fedora-magazine';

- import { CommunityBlogService } from '../../providers/community-blog/community-blog';

+ import { WordPressBlogProvider, BlogPost, BLOG, Blog } from '../../providers/word-press-blog/word-press-blog';

  import { PodcastService, Podcast } from '../../providers/fedora-podcast/podcast';

  

  @Component({

@@ -144,24 +143,28 @@ 

  @Component({

    selector: 'women-diversity',

    templateUrl: 'women-diversity.html',

-   providers: [CommunityBlogService]

+   providers: [WordPressBlogProvider]

  })

  export class WomenDiversity {

  

    /**

     * List of posts from Fedora Community Blog

     */

-   private blogposts: Post[];

+   private blogposts: BlogPost[];

+   private blog: Blog;

  

-   constructor(private browser: Browser, private communityblog: CommunityBlogService) {

+   constructor(private browser: Browser, wordPressBlog: WordPressBlogProvider) {

      this.blogposts = [];

+     this.blog = wordPressBlog.getBlog(BLOG.communityBlog);

    }

  

    /**

     * Fetch Blog Posts from the Fedora Community Blog

     */

    private updateBlogPosts(): void {

-     this.communityblog.getBlogPosts('454')

+     this.blog.fetchPosts({

+       categories: ['454']

+     })

        .subscribe(blogposts => {

          this.blogposts = blogposts;

        });

@@ -349,4 +352,4 @@ 

      date.setSeconds(time);

      return date.toISOString().substr(14, 5);

    }

- } 

\ No newline at end of file

+ }

@@ -1,15 +0,0 @@ 

- <!--

-   Generated template for the AskPage page.

- 

-   See http://ionicframework.com/docs/v2/components/#navigation for more info on

-   Ionic pages and navigation.

- -->

- <ion-content class="women">

-     <ion-navbar>

-         <button ion-button menuToggle>

-           <ion-icon name="menu"></ion-icon>

-         </button>

-       </ion-navbar>

- 

-   <p class="dummy">Coming Soon </p>

- </ion-content>

@@ -1,22 +0,0 @@ 

- 

- .women {

-   background-color: #abc178 ;

- 

- }

- 

- /*Women page */

- 

- 

- html, body

- {

- height: 100%;

- padding: 0;

- margin: 0;

- }

- 

- .dummy{

-   font-size: 3em;

-   text-align: center;

- }

- 

- /*Front page ends*/

@@ -1,12 +0,0 @@ 

- import { Component } from '@angular/core';

- 

- /**

-  * Fedora Women section

-  *

-  * Under construction

-  */

- @Component({

-   templateUrl: 'women.html',

- })

- export class WomenPage {

- }

@@ -0,0 +1,188 @@ 

+ import { HttpClient } from '@angular/common/http';

+ import { Injectable } from '@angular/core';

+ import { Storage } from '@ionic/storage';

+ import { BLOG } from '../word-press-blog/word-press-blog';

+ import { Observable } from 'rxjs/Observable';

+ import { from } from 'rxjs/observable/from';

+ import { merge } from 'rxjs/observable/merge';

+ import { map, tap, catchError, mergeMap } from 'rxjs/operators';

+ import filenamifyUrl from 'filenamify-url';

+ 

+ /**

+  * Represents a image on App Carousel fetched from fedora magazine

+  */

+ export interface Image {

+ 

+   /**

+    * Unique ID of the post, supplied by the CMS

+    */

+   id: number,

+ 

+   /**

+    * Link to the associated post

+    */

+   postLink: string,

+ 

+   /**

+    * Title of the associated post

+    */

+   title: string,

+ 

+   /**

+    * URL of the Image

+    */

+   url: string;

+ }

+ 

+ /**

+  * Source (blog) for the images

+  */

+ const SOURCE = BLOG.fedoraMagazine;

+ 

+ /**

+  * Images to display in the carousel

+  */

+ const NUM_IMAGES = '5';

+ 

+ /**

+  * Category to load images from

+  */

+ const CATEGORY = '609'; // 'New in Fedora'

+ 

+ /**

+  * Ionic storage key prefix

+  */

+ const KEY = 'carousel__images';

+ 

+ /**

+  * Converts a URL into a key for use in Ionic storage

+  * @param url URL

+  */

+ function urlKey(url: string) {

+   return KEY + '__' + filenamifyUrl(url);

+ }

+ 

+ /**

+  * Manages loading and caching of images displayed in the home page carousel

+  */

+ @Injectable()

+ export class CarouselProvider {

+ 

+   constructor(private http: HttpClient, private storage: Storage) {

+   }

+ 

+   /**

+    * Rehydrate an image from the cache

+    *

+    * If the image is not available, return the unchanged obect

+    *

+    * @param i image to load

+    */

+   private loadFile = async (i: Image): Promise<Image> => {

+     const fileKey = urlKey(i.url);

+     const data = await this.storage.get(fileKey);

+     if (data) {

+       const reader = new FileReader();

+       reader.readAsDataURL(data);

+       return new Promise<Image>((resolve, reject) => {

+         reader.onload = () => resolve({ ...i, url: reader.result });

+         reader.onerror = err => reject(err);

+       });

+     } else {

+       return i;

+     };

+   }

+ 

+   /**

+    * Load images from the cache

+    */

+   private loadCachedImages(): Observable<Image[]> {

+     return from(

+       this.storage.get(KEY)

+         .then((images: Image[]) => Promise.all(images.map(this.loadFile))))

+       .pipe(catchError(() => from([])));

+   }

+ 

+   /**

+    * Fetch the list of images on Fedora Magazine

+    *

+    * @returns Observable which emits an array of images

+    */

+   fetchImages(): Observable<Image[]> {

+     // We get the images from 'New in Fedora' section in Fedora Magazine

+     return this.http.get(`${SOURCE.endpoint}/wp-json/wp/v2/posts`, {

+       params: {

+         per_page: NUM_IMAGES,

+         categories: CATEGORY,

+         _embed: ''

+       }

+     }).pipe(

+       // Parse the API response

+       map<any[], Image[]>((data: any[]) => data.map((post: any) => ({

+         id: post.id,

+         postLink: post.link,

+         title: post.title,

+         url: post._embedded['wp:featuredmedia']['0'].media_details.sizes.medium_large.source_url

+       }))),

+       // Load images from the cache where possible

+       mergeMap(images => Promise.all(images.map(this.loadFile))),

+ 

+       // Save the new set of images

+       tap(async images => {

+         // Loads images into the offline cache

+         try {

+           await Promise.all(

+             images.map(async post => {

+               const fileName = urlKey(post.url);

+               const exists = await this.storage.get(fileName);

+               if (exists) {

+                 // Do nothing if image is already in cache

+                 return Promise.resolve();

+               } else {

+                 // Workaround for CORS, replace the endpoint with the proxy URL

+                 const url = post.url.replace(SOURCE.originalEndpoint, SOURCE.endpoint);

+ 

+                 return this.http.get(url, {

+                   responseType: 'blob'

+                 }).toPromise()

+                   .then(data => this.storage.set(fileName, data));

+               }

+             }));

+ 

+           // Garbage collect old images

+           const oldImages: Image[] = await this.storage.get(KEY);

+           if (oldImages) {

+             const newUrls = new Set(images.map(x => x.url));

+             const oldUrls = oldImages.map(x => x.url);

+ 

+             // Find expired posts and delete them

+             await Promise.all(oldUrls.map(url => newUrls.has(url) ?

+               Promise.resolve() :

+               this.storage.remove(urlKey(url))));

+           }

+           await this.storage.set(KEY, images);

+ 

+         } catch (error) {

+           // We actually ignore this error, however just log in for debugging

+           // This is not supposed to happen

+           console.error('Unable to cache carousel images');

+           console.error(error);

+         }

+       })

+     );

+   }

+ 

+   /**

+    * Fetch the images for displaying in the carousel

+    *

+    * This returns a list of cached values followed by values from the API

+    *

+    * @returns Observable which emits an array of images

+    */

+   getImages(): Observable<Image[]> {

+     return merge(

+       this.fetchImages(),

+       this.loadCachedImages()

+     );

+   }

+ }

@@ -1,42 +0,0 @@ 

- import 'rxjs/add/operator/map';

- import { Injectable } from '@angular/core';

- import { HttpClient } from '@angular/common/http';

- import { Observable } from 'rxjs/Observable';

- import { chooseEndpoint, beautifyDate } from '../../utils';

- import { Post } from '../fedora-magazine/fedora-magazine';

- 

- const ENDPOINT = chooseEndpoint('/community-blog', 'https://communityblog.fedoraproject.org/wp-json/wp/v2');

- 

- /**

-  * Service for fetching posts from Community Blog API

-  */

- @Injectable()

- export class CommunityBlogService {

-     constructor(private http: HttpClient) {

-     }

- 

-     /**

-      * Fetch the list of latest posts on Fedora Community Blog

-      *

-      * @param category from which posts are to be fetched

-      * @returns Observable which emits an array of blog posts

-      */

-     getBlogPosts(category): Observable<Post[]> {

- 

-         let request;

-         //if no category is specified, fetch posts without parameters.

-         category === 'null' ? request = this.http.get(`${ENDPOINT}/posts`) : request = this.http.get(`${ENDPOINT}/posts`, { params: {'categories': category}});

- 

-         return request.map((data: any[]) => data.map((blogpost: any) => ({

-                 id: blogpost.id,

-                 link: blogpost.link,

-                 permalink: blogpost.guid.rendered,

-                 title: blogpost.title.rendered,

-                 excerpt: blogpost.excerpt.rendered,

-                 content: blogpost.content.rendered,

-                 publishedAt: beautifyDate(blogpost.date_gmt, 'blog'),

-                 comments: null,

-                 featuredImage:''

-             })));

-     }

- } 

\ No newline at end of file

@@ -0,0 +1,35 @@ 

+ import Dexie from 'dexie';

+ 

+ const DB_NAME = 'fedora-app';

+ 

+ /**

+  * Provides database support for the application by wrapping IndexedDB using

+  * Dexie.js

+  */

+ export class DatabaseProvider extends Dexie {

+ 

+   constructor() {

+     super(DB_NAME);

+     this.ensureSchema();

+   }

+ 

+   /**

+    * Defines database schema and migrations.

+    *

+    * Migrations are automatically executed by Dexie when upgrading from previous

+    * versions.

+    */

+   private ensureSchema(): void {

+     // NEVER REMOVE OLDER VERSIONS.

+     // Database migrations are defined incrementally over one another.

+ 

+     // DB Version 1: 2018-06-25 18:30:54+05:30 amitosh

+ 

+     this.version(1).stores({

+       // define the schema using Dexie's schema syntax:

+       //

+       fedoraMagazine: '&id,publishedAt',

+       communityBlog: '&id,publishedAt',

+     });

+   }

+ }

@@ -1,168 +0,0 @@ 

- import { Storage } from '@ionic/storage';

- import { Injectable } from '@angular/core';

- import { HttpClient } from '@angular/common/http';

- import { Observable } from 'rxjs/Observable';

- import { map, tap } from 'rxjs/operators';

- import { fromPromise } from 'rxjs/observable/fromPromise';

- import { merge } from 'rxjs/observable/merge';

- import { chooseEndpoint, defaultValue, beautifyDate} from '../../utils';

- 

- /**

-  * Endpoint for this service.

-  *

-  * Since Fedora Magazine API does not support CORS, we need to use a proxy when

-  * running on browser platforms.

-  */

- const ENDPOINT = chooseEndpoint('/fedora-magazine', 'https://fedoramagazine.org/wp-json/wp/v2');

- 

- /**

-  * The key used for storing offline content

-  */

- const STORAGE_KEY = 'fedora-magazine__posts';

- 

- /**

-  * 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,

- 

-   /**

-    * A short excerpt of the post

-    */

-   excerpt: string,

- 

-   /**

-    * The content of the post

-    */

-   content: string,

- 

-   /**

-    * Time of publication

-    */

-   publishedAt: string,

- 

-   /**

-    * Number of comments on the article

-    */

- 

-   comments: number,

- 

-   /**

-    * Featured Image of article

-    */

- 

-   featuredImage: string

- }

- 

- /**

-  * Represents a image on App Carousel fetched from fedora magazine

-  */

- export interface Image {

-   /**

-     * Unique ID of the post, supplied by the CMS

-     */

-   id: number,

- 

-   /**

-    * A sluggified link to the post

-    */

-   link: string,

- 

-   /**

-    * The Image of the post

-    */

- 

-   image: string

- }

- 

- /**

-  * Service for fetching posts from Fedora Magazine API

-  */

- @Injectable()

- export class FedoraMagazineService {

- 

-   constructor(private http: HttpClient, private storage: Storage) {

-   }

- 

-   /**

-    * Fetch the list of posts on Fedora Magazine from offline cache

-    *

-    * @returns Observable which emits an array of posts

-    */

-   private loadCachedPosts() {

-     return fromPromise(this.storage.get(STORAGE_KEY)).pipe(defaultValue([]));

-   }

- 

-   /**

-    * Fetch the list of latest posts on Fedora Magazine from Fedora Magazine API

-    *

-    * @returns Observable which emits an array of posts

-    */

-   fetchPosts(postCount): Observable<Post[]> {

-     return this.http.get(`${ENDPOINT}/posts`, { params: { '_embed': '', 'per_page': postCount } })

-       .pipe(

-         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,

-           publishedAt: beautifyDate(post.date_gmt, 'blog'),

-           featuredImage: post._embedded['wp:featuredmedia']['0'].media_details.sizes.medium.source_url,

-           comments: post._embedded['replies'] !== undefined ? post._embedded['replies']['0'].length : 0,

-         })))

-       );

-   }

- 

-   /**

-    * Fetch the list of posts on Fedora Magazine

-    *

-    * Loads from offline cache first and then from HTTP. The response of the HTTP

-    * request is persisted into the disk cache for further requests.

-    *

-    * @returns Observable which emits an array of posts

-    */

-   getPosts(postCount) {

-     return merge(this.loadCachedPosts(), this.fetchPosts(postCount).pipe(

-       tap(x => this.storage.set(STORAGE_KEY, x)))

-     );

-   }

- 

-   /**

-    * Fetch the list of latest images on Fedora Magazine

-    *

-    * @returns Observable which emits an array of images

-    */

-   getImages(): Observable<Image[]> {

- 

-     let pageResults = '5'; // get top 5 posts only

-     let imageCategory = '609'; //get posts from New in Fedora category

- 

-     return this.http.get(`${ENDPOINT}/posts`, { params: { 'per_page': pageResults, 'categories': imageCategory, '_embed': '' } })

-       .map((data: any[]) => data.map((image: any) => ({

-         id: image.id,

-         link: image.link,

-         image: image._embedded['wp:featuredmedia']['0'].media_details.sizes.medium_large.source_url

-       })));

-   }

- }

@@ -21,47 +21,47 @@ 

   * Represents a podcast fetched from Simplecast

   */

  export interface Podcast {

-     /**

-      * Unique ID of the podcast, supplied by the Simplecast API

-      */

-     id: number,

+   /**

+    * Unique ID of the podcast, supplied by the Simplecast API

+    */

+   id: number,

  

-     /**

-      * The Time duration of the podcast

-      */

-     duration: number,

+   /**

+    * The Time duration of the podcast

+    */

+   duration: number,

  

-     /**

-      * The Podcast Number

-      */

+   /**

+    * The Podcast Number

+    */

  

-     number: number,

+   number: number,

  

-     /**

-      * Title of the podcast

-      */

+   /**

+    * Title of the podcast

+    */

  

-     title: string,

+   title: string,

  

-     /**

-      * Description of the podcast

-      */

-     description: string,

+   /**

+    * Description of the podcast

+    */

+   description: string,

  

-     /**

-      * Date of publishing the podcast

-      */

-     publishedAt: Date,

+   /**

+    * Date of publishing the podcast

+    */

+   publishedAt: Date,

  

-     /**

-      * URL of Podcast Source File

-      */

-     audioUrl: string,

+   /**

+    * URL of Podcast Source File

+    */

+   audioUrl: string,

  

-     /**

-      * Sharing URL of the Podcast Source File

-      */

-     shareUrl: string

+   /**

+    * Sharing URL of the Podcast Source File

+    */

+   shareUrl: string

  }

  

  /**

@@ -69,28 +69,28 @@ 

   */

  @Injectable()

  export class PodcastService {

-     constructor(private http: HttpClient) {

-     }

+   constructor(private http: HttpClient) {

+   }

  

-     /**

-      * Fetch the list of latest podcastss on Fedora Community 

-      *

-      * @returns Observable which emits an array of Podcasts

-      */

-     getPodcasts(): Observable<Podcast[]> {

-         return this.http.get(`${ENDPOINT}/podcasts/${PODCAST_ID}/episodes.json`, { headers: { 'X-API-KEY': ENV.SIMPLECAST_CONFIG.apiKey } })

-             .pipe(

-                 map((data: any[]) => data.map((podcast: any) => ({

-                     id: podcast.id,

-                     number: podcast.number,

-                     title: podcast.title,

-                     duration: podcast.duration,

-                     displayTime: Math.floor(podcast.duration/60),//convert to minutes

-                     description: podcast.description,

-                     publishedAt: podcast.published_at,

-                     audioUrl: podcast.audio_url,

-                     shareUrl: podcast.share_url

-                 })))

-             );

-     }

- } 

\ No newline at end of file

+   /**

+    * Fetch the list of latest podcastss on Fedora Community

+    *

+    * @returns Observable which emits an array of Podcasts

+    */

+   getPodcasts(): Observable<Podcast[]> {

+     return this.http.get(`${ENDPOINT}/podcasts/${PODCAST_ID}/episodes.json`, { headers: { 'X-API-KEY': ENV.SIMPLECAST_CONFIG.apiKey } })

+       .pipe(

+         map((data: any[]) => data.map((podcast: any) => ({

+           id: podcast.id,

+           number: podcast.number,

+           title: podcast.title,

+           duration: podcast.duration,

+           displayTime: Math.floor(podcast.duration / 60),//convert to minutes

+           description: podcast.description,

+           publishedAt: podcast.published_at,

+           audioUrl: podcast.audio_url,

+           shareUrl: podcast.share_url

+         })))

+       );

+   }

+ }

@@ -1,6 +1,7 @@ 

  import { HttpClient } from '@angular/common/http';

  import { Injectable } from '@angular/core';

  import { Observable } from 'rxjs/Observable';

+ import { map } from 'rxjs/operators';

  

  const API_ENDPOINT = 'https://apps.fedoraproject.org/packages/fcomm_connector/xapian/query/search_packages/';

  

@@ -50,13 +51,15 @@ 

      });

  

      return this.http.get(`${API_ENDPOINT}/${queryObjectStr}`)

-       .map((res: any) => ({

-         matches: res.total_rows,

-         count: res.visible_rows,

-         offset: res.start_row,

-         pageSize: res.rows_per_page,

-         packages: res.rows.map(mapPackage)

-       }));

+       .pipe(

+         map((res: any) => ({

+           matches: res.total_rows,

+           count: res.visible_rows,

+           offset: res.start_row,

+           pageSize: res.rows_per_page,

+           packages: res.rows.map(mapPackage)

+         }))

+       );

    }

  

  }

@@ -2,7 +2,6 @@ 

  import ENV from '@environment';

  import { Storage } from '@ionic/storage';

  import { compact } from 'lodash-es';

- import { beautifyDate } from '../../utils';

  import { SocialProvider, SocialMediaPost, FACEBOOK } from './social';

  import { Observable } from 'rxjs/Observable';

  import { fromPromise } from 'rxjs/observable/fromPromise';

@@ -68,7 +67,7 @@ 

                link: 'https://facebook.com/' + p.id,

                content: p.message,

                origin: FACEBOOK,

-               date: beautifyDate(p.created_time,'social')

+               date: new Date(p.created_time)

              };

  

              return post.content ? post : null;

@@ -37,7 +37,7 @@ 

    /**

     * Date of the post

     */

-   date: string,

+   date: Date

  }

  

  /**

@@ -1,6 +1,5 @@ 

  import ENV from '@environment';

  import { Injectable } from '@angular/core';

- import { beautifyDate } from '../../utils';

  import { SocialProvider, SocialMediaPost } from './social';

  import { HttpClient } from '@angular/common/http';

  import { Storage } from '@ionic/storage';

@@ -69,7 +68,9 @@ 

          link: 'https://twitter.com/statuses/' + t.id_str,

          content: t.text,

          origin: 'twitter',

-         date: beautifyDate(t.created_at,'social')

+         // Twitter has a horrible date format

+         // Credits: https://stackoverflow.com/a/2766516/2328165

+         date: new Date(t.created_at.replace(/^\w+ (\w+) (\d+) ([\d:]+) \+0000 (\d+)$/, "$1 $2 $4 $3 UTC"))

        })))

      );

    }