From 80e88f1e8c173f09c2df0f2869775d620f237bc5 Mon Sep 17 00:00:00 2001
From: Amitosh Swain Mahapatra <amitosh.swain@gmail.com>
Date: Mar 17 2019 05:38:15 +0000
Subject: Improve offline caching for WordPress Blog Provider using IndexedDB


---

diff --git a/ionic.config.json b/ionic.config.json
index 67d9cf5..a3d88e6 100644
--- a/ionic.config.json
+++ b/ionic.config.json
@@ -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",
diff --git a/package-lock.json b/package-lock.json
index 68e2ce4..135c2d8 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -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",
diff --git a/package.json b/package.json
index e5177f9..15ed947 100644
--- a/package.json
+++ b/package.json
@@ -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",
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 1a1d5ea..6d783ae 100755
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -26,6 +26,7 @@ import { NoResultsComponent } from '../components/no-results/no-results';
 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 @@ import { Browser } from '../providers/browser/browser';
     StatusBar,
     Toast,
     { provide: ErrorHandler, useClass: IonicErrorHandler },
+    DatabaseProvider,
   ]
 })
 export class AppModule { }
diff --git a/src/assets/img/logo.svg b/src/assets/img/logo.svg
new file mode 100644
index 0000000..2c9e7a5
--- /dev/null
+++ b/src/assets/img/logo.svg
@@ -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>
diff --git a/src/pages/home/home.html b/src/pages/home/home.html
index 8cf028a..f82c0c4 100644
--- a/src/pages/home/home.html
+++ b/src/pages/home/home.html
@@ -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>
diff --git a/src/pages/home/home.ts b/src/pages/home/home.ts
index 506d898..09e36a9 100644
--- a/src/pages/home/home.ts
+++ b/src/pages/home/home.ts
@@ -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 @@ export class HomePage {
    * 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 @@ export class HomePage {
   }
 
   /**
-   * 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 @@ export class HomePage {
    * 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');
 }
diff --git a/src/pages/magazine/magazine.html b/src/pages/magazine/magazine.html
index e384403..a0c6a43 100644
--- a/src/pages/magazine/magazine.html
+++ b/src/pages/magazine/magazine.html
@@ -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>
diff --git a/src/pages/magazine/magazine.ts b/src/pages/magazine/magazine.ts
index 2023c32..f0c016a 100644
--- a/src/pages/magazine/magazine.ts
+++ b/src/pages/magazine/magazine.ts
@@ -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 @@ export class MagazinePage {
   //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 @@ export class MagazinePage {
   }
 
   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 @@ export class MagazinePage {
    */
   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 @@ export class MagazinePage {
    *
    * @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 @@ export class MagazinePage {
   }
 
   /**
- * 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 @@ export class MagazinePage {
       unsubscribedToast.present();
     }
   }
+
+  beautify(date:Date) {
+    return beautifyDate(date, 'blog');
+  }
+
 }
diff --git a/src/pages/more/more.ts b/src/pages/more/more.ts
index eeed3b6..f10039b 100755
--- a/src/pages/more/more.ts
+++ b/src/pages/more/more.ts
@@ -3,8 +3,7 @@ import { NavController, NavParams } from 'ionic-angular';
 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 @@ export class BookmarksPage {
 @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 @@ export class PodcastPlayer {
     date.setSeconds(time);
     return date.toISOString().substr(14, 5);
   }
-}
\ No newline at end of file
+}
diff --git a/src/pages/women/women.html b/src/pages/women/women.html
deleted file mode 100644
index 6417a41..0000000
--- a/src/pages/women/women.html
+++ /dev/null
@@ -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>
diff --git a/src/pages/women/women.scss b/src/pages/women/women.scss
deleted file mode 100644
index f590bdb..0000000
--- a/src/pages/women/women.scss
+++ /dev/null
@@ -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*/
diff --git a/src/pages/women/women.ts b/src/pages/women/women.ts
deleted file mode 100644
index 0b037c9..0000000
--- a/src/pages/women/women.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-import { Component } from '@angular/core';
-
-/**
- * Fedora Women section
- *
- * Under construction
- */
-@Component({
-  templateUrl: 'women.html',
-})
-export class WomenPage {
-}
diff --git a/src/providers/carousel/carousel.ts b/src/providers/carousel/carousel.ts
new file mode 100644
index 0000000..43f4483
--- /dev/null
+++ b/src/providers/carousel/carousel.ts
@@ -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()
+    );
+  }
+}
diff --git a/src/providers/community-blog/community-blog.ts b/src/providers/community-blog/community-blog.ts
deleted file mode 100644
index 746139f..0000000
--- a/src/providers/community-blog/community-blog.ts
+++ /dev/null
@@ -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
diff --git a/src/providers/database/database.ts b/src/providers/database/database.ts
new file mode 100644
index 0000000..837a57a
--- /dev/null
+++ b/src/providers/database/database.ts
@@ -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',
+    });
+  }
+}
diff --git a/src/providers/fedora-magazine/fedora-magazine.ts b/src/providers/fedora-magazine/fedora-magazine.ts
deleted file mode 100644
index 3ee6c58..0000000
--- a/src/providers/fedora-magazine/fedora-magazine.ts
+++ /dev/null
@@ -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
-      })));
-  }
-}
diff --git a/src/providers/fedora-podcast/podcast.ts b/src/providers/fedora-podcast/podcast.ts
index 9dde1bf..11b3174 100755
--- a/src/providers/fedora-podcast/podcast.ts
+++ b/src/providers/fedora-podcast/podcast.ts
@@ -21,47 +21,47 @@ const PODCAST_ID = 4543
  * 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 @@ export interface Podcast {
  */
 @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
+        })))
+      );
+  }
+}
diff --git a/src/providers/package-search/package-search.ts b/src/providers/package-search/package-search.ts
index b1921a8..f339695 100644
--- a/src/providers/package-search/package-search.ts
+++ b/src/providers/package-search/package-search.ts
@@ -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 @@ export class PackageSearchProvider {
     });
 
     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)
+        }))
+      );
   }
 
 }
diff --git a/src/providers/social/facebook.ts b/src/providers/social/facebook.ts
index d2f6d9b..57b3b88 100644
--- a/src/providers/social/facebook.ts
+++ b/src/providers/social/facebook.ts
@@ -2,7 +2,6 @@ import { Injectable } from '@angular/core';
 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 @@ export class FacebookProvider implements SocialProvider {
               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;
diff --git a/src/providers/social/social.ts b/src/providers/social/social.ts
index e69e753..b93d201 100644
--- a/src/providers/social/social.ts
+++ b/src/providers/social/social.ts
@@ -37,7 +37,7 @@ export interface SocialMediaPost {
   /**
    * Date of the post
    */
-  date: string,
+  date: Date
 }
 
 /**
diff --git a/src/providers/social/twitter.ts b/src/providers/social/twitter.ts
index 7a3d9d0..0466a0a 100644
--- a/src/providers/social/twitter.ts
+++ b/src/providers/social/twitter.ts
@@ -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 @@ export class TwitterProvider implements SocialProvider {
         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"))
       })))
     );
   }
diff --git a/src/providers/word-press-blog/word-press-blog.ts b/src/providers/word-press-blog/word-press-blog.ts
new file mode 100644
index 0000000..d6251aa
--- /dev/null
+++ b/src/providers/word-press-blog/word-press-blog.ts
@@ -0,0 +1,218 @@
+import { HttpClient } from '@angular/common/http';
+import { Injectable } from '@angular/core';
+import { map, tap } from 'rxjs/operators';
+import { DatabaseProvider } from '../database/database';
+import { Observable } from 'rxjs/Observable';
+import { from } from 'rxjs/observable/from';
+import { chooseEndpoint } from '../../utils';
+
+// Blog definitions
+export const BLOG = Object.seal({
+  fedoraMagazine: {
+    id: 'fedoraMagazine',
+    title: 'Fedora Magazine',
+    originalEndpoint: 'https://fedoramagazine.org',
+    endpoint: chooseEndpoint('/fedora-magazine', 'https://fedoramagazine.org')
+  },
+  communityBlog: {
+    id: 'communityBlog',
+    title: 'Fedora Community Blog',
+    originalEndpoint: 'https://communityblog.fedoraproject.org',
+    endpoint: chooseEndpoint('/community-blog', 'https://communityblog.fedoraproject.org')
+  }
+});
+
+/**
+ * Represents a blog post
+ */
+export interface BlogPost {
+  /**
+   * 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: Date
+
+  /**
+   * Number of comments on the article
+   */
+  comments: number,
+
+  /**
+   * Featured Image of article
+   */
+  featuredImage: string
+}
+
+interface BlogPostFetchArgs {
+  page?: number,
+  orderBy?: 'date' | 'title',
+  order?: 'asc' | 'desc',
+  categories?: string[],
+  tags?: string[]
+}
+
+/**
+ * Represents a single wordpress blog
+ */
+export class Blog {
+
+  public postsPerPage = 10;
+
+  public readonly posts$;
+
+  constructor(public readonly id: string, public readonly endpoint: string,
+    private http: HttpClient, private db: DatabaseProvider) {
+  }
+
+  /**
+   * Fetch the list of latest posts on the blog using the WordPress API
+   *
+   * @returns Observable which emits an array of posts
+   */
+  public fetchPosts(funcParams: BlogPostFetchArgs = {}): Observable<BlogPost[]> {
+    const {
+      page = 1,
+      orderBy = 'date',
+      order = 'desc',
+      categories = [],
+      tags = []
+    } = funcParams;
+    // WordPress API parameters
+    const params: any = {
+      _embed: '',
+      page,
+      orderby: orderBy,
+      order,
+      per_page: this.postsPerPage,
+    }
+
+    if (categories.length > 0) {
+      params.categories = categories.join(',')
+    }
+
+    if (tags.length > 0) {
+      params.tags = tags.join(',')
+    }
+
+    return this.http.get(`${this.endpoint}/wp-json/wp/v2/posts`, { params })
+      .pipe(
+        map((data: any[]) => data.map((post: any) => {
+          const featuredMedia = post._embedded['wp:featuredmedia'] && post._embedded['wp:featuredmedia'][0];
+          return {
+            id: post.id as number,
+            link: post.link as string,
+            permalink: post.guid.rendered as string,
+            title: post.title.rendered as string,
+            excerpt: post.excerpt.rendered as string,
+            content: post.content.rendered as string,
+            publishedAt: new Date(post.date_gmt + 'Z'),
+            featuredImage: featuredMedia && (
+              featuredMedia.media_details.sizes.medium ?
+                featuredMedia.media_details.sizes.medium.source_url :
+                featuredMedia.media_details.source_url
+            ),
+            comments: post._embedded['replies'] !== undefined ? post._embedded['replies']['0'].length : 0,
+          };
+        })),
+    );
+  }
+
+  /**
+   * Updates the list of posts in the offline cache from the WordPress API
+   *
+   * @returns Observable which emits the updated list of posts
+   */
+  public updatePosts(): Observable<BlogPost[]> {
+    return this.fetchPosts()
+      .pipe(
+        tap(
+          async posts => {
+            // Store data in IndexedDB
+            try {
+              const table = this.db.table<BlogPost>(this.id);
+              await this.db.transaction('rw', table, async () => {
+                // We perform an upsert using bulkPut
+                await table.bulkPut(posts);
+                const count = await table.count();
+                if (count > this.postsPerPage) {
+                  // Delete old posts
+                  await table.orderBy('publishedAt').reverse().offset(this.postsPerPage).delete();
+                }
+              });
+            } catch (error) {
+              // We ignore this error, but log it for debugging
+              console.error('Unable to persist posts');
+              console.error(error);
+            }
+          })
+      );
+  }
+
+  /**
+   * Fetch the list of posts on the Blog from offline cache
+   *
+   * @returns Observable which emits an array of posts
+   */
+  public loadCachedPosts(): Observable<BlogPost[]> {
+    return from(
+      this.db.table(this.id)
+        .orderBy('publishedAt').reverse().toArray()
+    );
+  }
+}
+
+/**
+ * Provider for WordPress API
+ */
+@Injectable()
+export class WordPressBlogProvider {
+
+  private blogs: Map<string, Blog>
+
+  constructor(private http: HttpClient, private db: DatabaseProvider) {
+    this.blogs = new Map();
+  }
+
+  /**
+   * Get a cached instance to a Blog for a blog definition
+   *
+   * @param blog A blog definition
+   */
+  getBlog(blog: { id: string, endpoint: string }) {
+
+    if (!this.blogs.has(blog.id)) {
+      this.blogs.set(blog.id, new Blog(blog.id, blog.endpoint, this.http, this.db));
+    }
+
+    return this.blogs.get(blog.id);
+  }
+}

