diff --git a/.bowerrc b/.bowerrc
deleted file mode 100644
index ba0accc..0000000
--- a/.bowerrc
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "directory": "app/bower_components"
-}
diff --git a/.gitattributes b/.gitattributes
deleted file mode 100644
index 2125666..0000000
--- a/.gitattributes
+++ /dev/null
@@ -1 +0,0 @@
-* text=auto
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 3c2a653..f82b8b8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,10 +1,14 @@
node_modules
-temp
-.tmp
+extension
dist
-.sass-cache
-app/bower_components
-test/bower_components
-package
+.chrome-extension-key.pem
+jest_0/
+*.sublime-workspace
*.swp
.vscode
+yarn-error.log
+coverage/
+publish.sh
+stats.json
+.sentryclirc
+.DS_Store
diff --git a/.hintrc b/.hintrc
new file mode 100644
index 0000000..a11393f
--- /dev/null
+++ b/.hintrc
@@ -0,0 +1,5 @@
+{
+ "hints": {
+ "typescript-config/strict": "off"
+ }
+}
diff --git a/.jshintrc b/.jshintrc
deleted file mode 100644
index 06a890e..0000000
--- a/.jshintrc
+++ /dev/null
@@ -1,24 +0,0 @@
-{
- "node": true,
- "browser": true,
- "esnext": true,
- "bitwise": true,
- "camelcase": true,
- "curly": true,
- "eqeqeq": true,
- "immed": true,
- "indent": 4,
- "latedef": true,
- "newcap": true,
- "noarg": true,
- "quotmark": "single",
- "regexp": true,
- "undef": true,
- "unused": true,
- "strict": true,
- "trailing": true,
- "smarttabs": true,
- "globals" : {
- "chrome": true
- }
-}
diff --git a/.stylelintrc.js b/.stylelintrc.js
new file mode 100644
index 0000000..f3f4090
--- /dev/null
+++ b/.stylelintrc.js
@@ -0,0 +1,26 @@
+module.exports = {
+ extends: 'stylelint-config-standard',
+ rules: {
+ indentation: null,
+ 'font-family-no-missing-generic-family-keyword': null,
+ 'shorthand-property-no-redundant-values': null,
+ 'font-family-name-quotes': 'always-unless-keyword',
+ 'function-url-quotes': 'always',
+ 'selector-attribute-quotes': 'always',
+ 'string-quotes': 'single',
+ 'at-rule-no-vendor-prefix': true,
+ 'media-feature-name-no-vendor-prefix': true,
+ 'property-no-vendor-prefix': true,
+ 'selector-no-vendor-prefix': true,
+ 'value-no-vendor-prefix': true,
+ 'at-rule-no-unknown': true,
+ 'value-list-comma-newline-after': null,
+ 'declaration-colon-newline-after': null,
+ 'declaration-empty-line-before': null,
+ 'length-zero-no-unit': null,
+ 'function-url-scheme-whitelist': ['data'],
+ 'selector-descendant-combinator-no-non-space': null,
+ 'selector-combinator-space-before': null,
+ 'no-descending-specificity': null
+ }
+};
diff --git a/.travis.yml b/.travis.yml
deleted file mode 100644
index e35ed5e..0000000
--- a/.travis.yml
+++ /dev/null
@@ -1,10 +0,0 @@
-language: node_js
-node_js:
- - "6.1"
-
-before_script:
- - npm install -g grunt
- - npm install -g bower
- - bower install
-
-script: grunt build
diff --git a/.yo-rc.json b/.yo-rc.json
deleted file mode 100644
index 7a21352..0000000
--- a/.yo-rc.json
+++ /dev/null
@@ -1,3 +0,0 @@
-{
- "generator-mocha": {}
-}
\ No newline at end of file
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 9b0ea96..d36bcd2 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -4,46 +4,54 @@
Follow [this guide](http://minimul.com/developing-a-chrome-extension-with-yeoman.html) to build and debug the extension.
-In summary, install the required command line tools:
+Clone the repository:
- npm install -g grunt
- npm install -g bower
+ git clone https://github.com/churchs19/fullyfeedly.git
-Clone the repository:
+Development:
- git clone https://github.com/Muffo/fullyfeedly.git
+```bash
+# Install the dependencies
+npm install
-Install the dependencies:
+# Watch and build source code to 'extension/'
+npm run watch
- bower install
+# Watch and build source code to 'extension/' with system build notifications
+npm run watch:notif
-Build the extension:
+# Build source code for production
+npm run build:prod
- grunt build
+# Build source code for production + package source and build for store submissions
+npm run build:package
+# Run all linting checks
+npm run lint
-You can now [load the unpacked extension in Chrome](https://developer.chrome.com/extensions/getstarted#unpacked) from the local folder.
+# Clean webpack build cache (run if running into problems between builds)
+npm run cache:clean
+```
+
+You can now [load the unpacked extension in Chrome](https://developer.chrome.com/extensions/getstarted#unpacked) from the local folder.
## Branching strategy
The Github repository uses a **linear** commit history.
-
+
To simplify the contribution from external developers, the project moved away from the [GitFlow](http://nvie.com/posts/a-successful-git-branching-model/) branching strategy.
All the contributions should be submitted as pull requests to the master branch and must not contain any merge commit.
-
## Version numbers
Version numbers follow the [Semantic Versioning](http://semver.org) and are managed by the owner of the project.
Please, do not increase the version numbers in your pull requests.
-
## Submit a bug
When you open an issue, please include the following information:
-* Operating System
-* Whether you are using Feedly Premium or the beta version
-* A few screenshots of the issue
-
+- Operating System
+- Whether you are using Feedly Premium or the beta version
+- A few screenshots of the issue
diff --git a/Gruntfile.js b/Gruntfile.js
deleted file mode 100644
index f3e8f98..0000000
--- a/Gruntfile.js
+++ /dev/null
@@ -1,329 +0,0 @@
-// Generated on 2014-08-27 using generator-chrome-extension 0.2.9
-'use strict';
-
-// # Globbing
-// for performance reasons we're only matching one level down:
-// 'test/spec/{,*/}*.js'
-// use this if you want to recursively match all subfolders:
-// 'test/spec/**/*.js'
-
-module.exports = function (grunt) {
-
- // Load grunt tasks automatically
- require('load-grunt-tasks')(grunt);
-
- // Time how long tasks take. Can help when optimizing build times
- require('time-grunt')(grunt);
-
- // Configurable paths
- var config = {
- app: 'app',
- dist: 'dist'
- };
-
- grunt.initConfig({
-
- // Project settings
- config: config,
-
- // Watches files for changes and runs tasks based on the changed files
- watch: {
- bower: {
- files: ['bower.json'],
- tasks: ['bowerInstall']
- },
- js: {
- files: ['<%= config.app %>/scripts/{,*/}*.js'],
- tasks: ['jshint'],
- options: {
- livereload: true
- }
- },
- gruntfile: {
- files: ['Gruntfile.js']
- },
- styles: {
- files: ['<%= config.app %>/styles/{,*/}*.css'],
- tasks: [],
- options: {
- livereload: true
- }
- },
- livereload: {
- options: {
- livereload: '<%= connect.options.livereload %>'
- },
- files: [
- '<%= config.app %>/*.html',
- '<%= config.app %>/images/{,*/}*.{png,jpg,jpeg,gif,webp,svg}',
- '<%= config.app %>/manifest.json',
- '<%= config.app %>/_locales/{,*/}*.json'
- ]
- }
- },
-
- // Grunt server and debug server setting
- connect: {
- options: {
- port: 9000,
- livereload: 35729,
- // change this to '0.0.0.0' to access the server from outside
- hostname: 'localhost'
- },
- chrome: {
- options: {
- open: false,
- base: [
- '<%= config.app %>'
- ]
- }
- },
- test: {
- options: {
- open: false,
- base: [
- 'test',
- '<%= config.app %>'
- ]
- }
- }
- },
-
- // Empties folders to start fresh
- clean: {
- chrome: {
- },
- dist: {
- files: [{
- dot: true,
- src: [
- '<%= config.dist %>/*',
- '!<%= config.dist %>/.git*'
- ]
- }]
- }
- },
-
- // Make sure code styles are up to par and there are no obvious mistakes
- jshint: {
- options: {
- jshintrc: '.jshintrc',
- reporter: require('jshint-stylish')
- },
- all: [
- 'Gruntfile.js',
- '<%= config.app %>/scripts/{,*/}*.js',
- '!<%= config.app %>/scripts/vendor/*',
- 'test/spec/{,*/}*.js'
- ]
- },
- mocha: {
- all: {
- options: {
- run: true,
- urls: ['http://localhost:<%= connect.options.port %>/index.html']
- }
- }
- },
-
- // Automatically inject Bower components into the HTML file
- bowerInstall: {
- app: {
- src: [
- '<%= config.app %>/*.html'
- ]
- }
- },
-
- // Reads HTML for usemin blocks to enable smart builds that automatically
- // concat, minify and revision files. Creates configurations in memory so
- // additional tasks can operate on them
- useminPrepare: {
- options: {
- dest: '<%= config.dist %>'
- },
- html: [
- '<%= config.app %>/popup.html',
- '<%= config.app %>/options.html'
- ]
- },
-
- // Performs rewrites based on rev and the useminPrepare configuration
- usemin: {
- options: {
- assetsDirs: ['<%= config.dist %>', '<%= config.dist %>/images']
- },
- html: ['<%= config.dist %>/{,*/}*.html'],
- css: ['<%= config.dist %>/styles/{,*/}*.css']
- },
-
- // The following *-min tasks produce minifies files in the dist folder
- imagemin: {
- dist: {
- files: [{
- expand: true,
- cwd: '<%= config.app %>/images',
- src: '{,*/}*.{gif,jpeg,jpg,png}',
- dest: '<%= config.dist %>/images'
- }]
- }
- },
-
- svgmin: {
- dist: {
- files: [{
- expand: true,
- cwd: '<%= config.app %>/images',
- src: '{,*/}*.svg',
- dest: '<%= config.dist %>/images'
- }]
- }
- },
-
- htmlmin: {
- dist: {
- options: {
- // removeCommentsFromCDATA: true,
- // collapseWhitespace: true,
- // collapseBooleanAttributes: true,
- // removeAttributeQuotes: true,
- // removeRedundantAttributes: true,
- // useShortDoctype: true,
- // removeEmptyAttributes: true,
- // removeOptionalTags: true
- },
- files: [{
- expand: true,
- cwd: '<%= config.app %>',
- src: '*.html',
- dest: '<%= config.dist %>'
- }]
- }
- },
-
- // By default, your `index.html`'s will take care of
- // minification. These next options are pre-configured if you do not wish
- // to use the Usemin blocks.
- // cssmin: {
- // dist: {
- // files: {
- // '<%= config.dist %>/styles/main.css': [
- // '<%= config.app %>/styles/{,*/}*.css'
- // ]
- // }
- // }
- // },
- // uglify: {
- // dist: {
- // files: {
- // '<%= config.dist %>/scripts/scripts.js': [
- // '<%= config.dist %>/scripts/scripts.js'
- // ]
- // }
- // }
- // },
- // concat: {
- // dist: {}
- // },
-
- // Copies remaining files to places other tasks can use
- copy: {
- dist: {
- files: [{
- expand: true,
- dot: true,
- cwd: '<%= config.app %>',
- dest: '<%= config.dist %>',
- src: [
- '*.{ico,png,txt}',
- 'images/{,*/}*.{webp,gif}',
- '{,*/}*.html',
- 'styles/{,*/}*.css',
- 'styles/fonts/{,*/}*.*',
- '_locales/{,*/}*.json',
- ]
- }]
- }
- },
-
- // Run some tasks in parallel to speed up build process
- concurrent: {
- chrome: [
- ],
- dist: [
- 'imagemin',
- 'svgmin'
- ],
- test: [
- ]
- },
-
- // Auto buildnumber, exclude debug files. smart builds that event pages
- chromeManifest: {
- dist: {
- options: {
- buildnumber: false,
- background: {
- target: 'scripts/background.js',
- exclude: [
- 'scripts/chromereload.js'
- ]
- }
- },
- src: '<%= config.app %>',
- dest: '<%= config.dist %>'
- }
- },
-
- // Compres dist files to package
- compress: {
- dist: {
- options: {
- archive: function() {
- var manifest = grunt.file.readJSON('app/manifest.json');
- return 'package/fullyfeedly-' + manifest.version + '.zip';
- }
- },
- files: [{
- expand: true,
- cwd: 'dist/',
- src: ['**'],
- dest: ''
- }]
- }
- }
- });
-
- grunt.registerTask('debug', function () {
- grunt.task.run([
- 'jshint',
- 'concurrent:chrome',
- 'connect:chrome',
- 'watch'
- ]);
- });
-
- grunt.registerTask('test', [
- 'connect:test',
- 'mocha'
- ]);
-
- grunt.registerTask('build', [
- 'clean:dist',
- 'chromeManifest:dist',
- 'useminPrepare',
- 'concurrent:dist',
- 'cssmin',
- 'concat',
- 'uglify',
- 'copy',
- 'usemin',
- 'compress'
- ]);
-
- grunt.registerTask('default', [
- 'jshint',
- 'test',
- 'build'
- ]);
-};
diff --git a/README.md b/README.md
index 72bed37..aaefc76 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,11 @@
# FullyFeedly
-### Chrome extensions to read the full articles in Feedly.com
+## Chrome and Firefox extensions to read the full articles in Feedly.com
[](https://chrome.google.com/webstore/detail/fullyfeedly/ikdncbjpcpkheefmnbicggciklkeebmp?hl=en)
+[](https://addons.mozilla.org/en-US/firefox/addon/fullyfeedlyforfirefox/)
+
With FullyFeedly you can load the full content of the articles inside Feedly, also when the feed RSS contains only a short preview.
## Features
@@ -20,13 +22,11 @@ When reading an incomplete post, simply press the icon of FullFeedly in the URL
You can also enable the keyboard shortcut from the options page and display the full article with **f f**
-
## Developers
Pull requests are very welcome!! :+1:
-If you want to help read the [contributors guidelines](CONTRIBUTING.md) of this repository.
-
+If you want to help or build the extension yourself, please see the [contributors guidelines](CONTRIBUTING.md) of this repository.
Thanks a lot to the following developers for their help and contributions:
@@ -35,10 +35,9 @@ Thanks a lot to the following developers for their help and contributions:
* [@pamarcos](https://github.com/pamarcos)
* [@GuillermoEscobero](https://github.com/GuillermoEscobero)
-
### External API
The text of the article is extracted using the web API provided by:
-* [Mercury](https://mercury.postlight.com/): **recommended**, free for non-commercial use, API key required
+* [Mercury](https://github.com/postlight/mercury-parser): **recommended**, free and open-source
* [Boilerpipe](http://boilerpipe-web.appspot.com/): free to use, limited quota
diff --git a/app/manifest.json b/app/manifest.json
deleted file mode 100644
index 176bce5..0000000
--- a/app/manifest.json
+++ /dev/null
@@ -1,55 +0,0 @@
-{
- "name": "__MSG_appName__",
- "version": "0.10.0",
- "manifest_version": 2,
- "description": "__MSG_appDescription__",
- "icons": {
- "16": "images/icon-16.png",
- "128": "images/icon-128.png"
- },
- "default_locale": "en",
- "background": {
- "scripts": [
- "scripts/chromereload.js",
- "scripts/background.js"
- ],
- "persistent": false
- },
- "page_action": {
- "default_icon": {
- "19": "images/icon-19.png",
- "38": "images/icon-38.png"
- },
- "default_title": "fullyfeedly"
- },
- "content_scripts": [
- {
- "matches": [
- "*://*.feedly.com/*"
- ],
- "js": [
- "bower_components/spin.js/spin.js",
- "bower_components/iosoverlay/js/iosOverlay.js",
- "bower_components/mousetrap/mousetrap.js",
- "scripts/content.js"
- ],
- "css": [
- "bower_components/iosoverlay/css/iosOverlay.css",
- "styles/content.css"
- ]
- }
- ],
- "web_accessible_resources": [
- "images/cross.png",
- "images/check.png",
- "options.html"
- ],
- "permissions": [
- "declarativeContent",
- "activeTab",
- "storage",
- "https://boilerpipe-web.appspot.com/*",
- "https://mercury.postlight.com/*"
- ],
- "options_page": "options.html"
-}
diff --git a/app/options.html b/app/options.html
deleted file mode 100644
index 7aa321a..0000000
--- a/app/options.html
+++ /dev/null
@@ -1,110 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
- FullyFeedly - Options
-
-
-
-
-
-
-
-

-
FullyFeedly
-
Show the full content of the articles in Feedly.com
-
-
-
-
-
-
-
-
-
-
-
-
-
- Try me
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Setup your Mercury API key in two easy steps:
-
1. Create a free Mercury API account
-
Visit mercury.postlight.com/web-parser/
-
and create your free account.
-
You can also login using Google.
-
-
2. Get your API key
-
Your personal API key is available immediately after creating or logging in to your account on the main page.
-

-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/popup.html b/app/popup.html
deleted file mode 100644
index ac84ad2..0000000
--- a/app/popup.html
+++ /dev/null
@@ -1,28 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
- 'Allo, 'Allo!
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/scripts/background.js b/app/scripts/background.js
deleted file mode 100644
index 60c272a..0000000
--- a/app/scripts/background.js
+++ /dev/null
@@ -1,45 +0,0 @@
-'use strict';
-
-chrome.runtime.onInstalled.addListener(function (details) {
- console.log('FullyFeedly installed: previousVersion', details.previousVersion);
-});
-
-
-function printResponse(response) {
- console.log('Content response:\n' + response);
-}
-
-/* Regex-pattern to check URLs against.
- It matches URLs like: http[s]://[...]feedly.com[...] */
-var urlRegex = /^https?:\/\/(?:[^\.]+\.)?feedly\.com/;
-
-// When the browser-action button is clicked...
-chrome.pageAction.onClicked.addListener(function(tab) {
- // ...check the URL of the active tab against our pattern and...
- if (urlRegex.test(tab.url)) {
- // ...if it matches, send a message specifying a callback too
- chrome.tabs.sendMessage(tab.id, { text: 'extract_article' }, printResponse);
- }
-});
-
-// When the extension is installed or upgraded ...
-chrome.runtime.onInstalled.addListener(function() {
- // Replace all rules ...
- chrome.declarativeContent.onPageChanged.removeRules(undefined, function() {
- // With a new rule ...
- chrome.declarativeContent.onPageChanged.addRules([
- {
- // That fires when a page's URL contains a 'feedly.com' ...
- conditions: [
- new chrome.declarativeContent.PageStateMatcher({
- pageUrl: { urlContains: 'feedly.com' },
- })
- ],
- // And shows the extension's page action.
- actions: [ new chrome.declarativeContent.ShowPageAction() ]
- }
- ]);
- });
-});
-
-console.log('FullyFeedly: extension started');
diff --git a/app/scripts/chromereload.js b/app/scripts/chromereload.js
deleted file mode 100644
index cb07e47..0000000
--- a/app/scripts/chromereload.js
+++ /dev/null
@@ -1,22 +0,0 @@
-'use strict';
-
-// Reload client for Chrome Apps & Extensions.
-// The reload client has a compatibility with livereload.
-// WARNING: only supports reload command.
-
-var LIVERELOAD_HOST = 'localhost:';
-var LIVERELOAD_PORT = 35729;
-var connection = new WebSocket('ws://' + LIVERELOAD_HOST + LIVERELOAD_PORT + '/livereload');
-
-connection.onerror = function (error) {
- console.log('reload connection got error' + JSON.stringify(error));
-};
-
-connection.onmessage = function (e) {
- if (e.data) {
- var data = JSON.parse(e.data);
- if (data && data.command === 'reload') {
- chrome.runtime.reload();
- }
- }
-};
diff --git a/app/scripts/content.js b/app/scripts/content.js
deleted file mode 100644
index 82fbeb6..0000000
--- a/app/scripts/content.js
+++ /dev/null
@@ -1,408 +0,0 @@
-/*global Spinner:false, iosOverlay:false, Mousetrap:false */
-/*jslint latedef:false*/
-
-'use strict';
-
-/* ===================== Options ===================== */
-var options = {
- extractionAPI: 'Boilerpipe',
- mercuryAPIKey: '',
- enableShortcut: false
-};
-
-// Restores the options using the preferences stored in chrome.storage.
-(function restoreOptions() {
- chrome.storage.sync.get(
- options,
- function(items) {
- options = items;
- console.log('[FullyFeedly] Loaded options: ', options);
- }
- );
-})();
-
-
-/* ===================== Notifications ===================== */
-function loadingOverlay() {
-
- // Spinner options
- var spinOpts = {
- lines: 13, // The number of lines to draw
- length: 11, // The length of each line
- width: 5, // The line thickness
- radius: 17, // The radius of the inner circle
- corners: 1, // Corner roundness (0..1)
- rotate: 0, // The rotation offset
- color: '#FFF', // #rgb or #rrggbb
- speed: 1, // Rounds per second
- trail: 60, // Afterglow percentage
- shadow: false, // Whether to render a shadow
- hwaccel: false, // Whether to use hardware acceleration
- className: 'spinner', // The CSS class to assign to the spinner
- zIndex: 2e9, // The z-index (defaults to 2000000000)
- top: 'auto', // Top position relative to parent in px
- left: 'auto' // Left position relative to parent in px
- };
-
- // Create the spinner and the overlay
- var target = document.createElement('div');
- document.body.appendChild(target);
- var spinner = new Spinner(spinOpts).spin(target);
- var overlay = iosOverlay({
- text: chrome.i18n.getMessage('loading'),
- spinner: spinner
- });
-
- return overlay;
-}
-
-function genericOverlay(message, icon, duration, overlay) {
- var settings = {
- text: message,
- icon: icon
- };
-
- if (overlay === undefined || overlay === null) {
- overlay = iosOverlay(settings);
- }
- else {
- overlay.update(settings);
- }
- window.setTimeout(function() {
- overlay.hide();
- }, duration);
-
-}
-
-function successOverlay(message, overlay) {
- genericOverlay(chrome.i18n.getMessage(message),
- chrome.extension.getURL('images/check.png'),
- 1e3, overlay);
-}
-
-function failOverlay(message, overlay) {
- genericOverlay(chrome.i18n.getMessage(message),
- chrome.extension.getURL('images/cross.png'),
- 2e3, overlay);
-}
-
-/* ===================== Buttons management ===================== */
-function addButton(btnText, btnClass, btnAction, deleteBtnClass) {
-
- // Search the button to open the website and the container element
- var openWebsiteBtn = document.querySelector('.u100Entry .fx-button.secondary.full-width');
- var entryElement = document.querySelector('.u100Entry');
-
- if (openWebsiteBtn === null || entryElement === null) {
- return;
- }
-
- if(openWebsiteBtn.className.indexOf('websiteCallForAction') === -1) {
- openWebsiteBtn.className += ' websiteCallForAction';
- }
-
- // Create a new button used to load the article
- var newButton = openWebsiteBtn.cloneNode();
- newButton.className = btnClass;
- newButton.innerText = btnText;
-
- // Remove the link and assign a different action
- newButton.removeAttribute('href');
- newButton.onclick = btnAction;
-
- // Add the new button to the page
- entryElement.insertBefore(newButton, openWebsiteBtn);
-
- // Remove the old button
- var oldButton = document.querySelector('.' + deleteBtnClass);
- if (oldButton === null) {
- return;
- }
- oldButton.parentNode.removeChild(oldButton);
-}
-
-function addShowFullArticleBtn() {
- addButton(chrome.i18n.getMessage('showFullArticle'), 'showFullArticleBtn fx-button secondary full-width',
- fetchPageContent, 'showArticlePreviewBtn');
-
- // Add keyboard shortcut
- if (options.enableShortcut) {
- Mousetrap.bind('f f', fetchPageContent);
- }
-}
-
-function addShowArticlePreviewBtn(showPreviewFunction) {
- addButton(chrome.i18n.getMessage('showArticlePreview'), 'showArticlePreviewBtn fx-button secondary full-width',
- showPreviewFunction, 'showFullArticleBtn');
-
- // Add keyboard shortcut
- if (options.enableShortcut) {
- Mousetrap.bind('f f', showPreviewFunction);
- }
-}
-
-
-/* ===================== Boilerpipe ===================== */
-/**
- * Process the content of the article and add it to the page
- *
- * @param data Object JSON decoded response. Null if the request failed.
- */
-function onBoilerpipeArticleExtracted(data, overlay) {
-
- // Check if the API failed to extract the text
- if (data.status === null || data.status !== 'success') {
- console.log('[FullyFeedly] API failed to extract the content');
- failOverlay(chrome.i18n.getMessage('articleNotLoaded'), overlay);
- return;
- }
-
- // Get the content of the article
- var articleContent = data.response.content;
-
- // Search the element of the page that will containt the text
- var contentElement = document.querySelector('.entryBody .content');
- if (contentElement === null) {
- console.log('[FullyFeedly] There is something wrong: no content element found');
- failOverlay('contentNotFound', overlay);
- return;
- }
-
- // If there is an image we want to keep it
- var articleImage = contentElement.querySelector('img');
- if (articleImage !== null) {
- articleImage = articleImage.cloneNode();
- }
-
- // Replace the preview of the article with the full text
- var articlePreviewHTML = contentElement.innerHTML;
- contentElement.innerHTML = articleContent;
-
- // Clear image styles to fix formatting of images with class/style/width information in article markup
- Array.prototype.slice.call(contentElement.querySelectorAll('img')).forEach(function(el) {
- el.removeAttribute('class');
- el.removeAttribute('width');
- el.setAttribute('style', 'max-width:100%;');
- });
-
- // Toggle Success Overlay
- addUndoButton(articlePreviewHTML);
- successOverlay('done', overlay);
-}
-
-function boilerpipeRequest(xhr, overlay) {
- return function() {
- if (xhr.readyState === 4) {
- if (xhr.status === 200) {
- // Operation succeded
- var data = JSON.parse(xhr.responseText);
- onBoilerpipeArticleExtracted(data, overlay);
- } else if (xhr.status === 503) {
- console.log('[FullyFeedly] Boilerpipe API exceeded quota');
- failOverlay('APIOverQuota', overlay);
- } else {
- console.log('[FullyFeedly] Failed to load the content of the page');
- failOverlay(chrome.i18n.getMessage('articleNotFound'), overlay);
- }
- }
- };
-}
-
-/* ===================== Mercury ===================== */
-/**
- * Process the content of the article and add it to the page
- */
-function onMercuryArticleExtracted(data, overlay) {
-
- // Check if the API failed to extract the text
- if (data.content === null) {
- console.log('[FullyFeedly] API failed to extract the content');
- failOverlay('articleNotFound', overlay);
- return;
- }
-
- // Get the content of the article
- var articleContent = data.content;
-
- // Search the element of the page that will containt the text
- var contentElement = document.querySelector('.entryBody .content');
- if (contentElement === null) {
- console.log('[FullyFeedly] There is something wrong: no content element found');
- failOverlay('contentNotFound', overlay);
- return;
- }
-
- // If there is an image we want to keep it
- var articleImage = contentElement.querySelector('img');
- if (articleImage !== null) {
- articleImage = articleImage.cloneNode();
- }
-
- // Replace the preview of the article with the full text
- var articlePreviewHTML = contentElement.innerHTML;
- contentElement.innerHTML = articleContent;
-
- // Clear image styles to fix formatting of images with class/style/width information in article markup
- Array.prototype.slice.call(contentElement.querySelectorAll('img')).forEach(function(el) {
- el.removeAttribute('class');
- el.removeAttribute('width');
- el.setAttribute('style', 'max-width:100%;');
- });
-
- // Toggle success overlay
- successOverlay('done', overlay);
-
- // Put the image back at the beginning of the article
- if (articleImage !== null && contentElement.querySelector('img') === null) {
- contentElement.insertBefore(articleImage, contentElement.firstChild);
- }
-
- addUndoButton(articlePreviewHTML);
-}
-
-/* ===================== Mercury ===================== */
-function mercuryRequest(xhr, overlay) {
- return function() {
- if (xhr.readyState === 4) {
- if (xhr.status === 200) {
- // Operation succeded
- var data = JSON.parse(xhr.responseText);
- onMercuryArticleExtracted(data, overlay);
- } else if (xhr.status === 400) {
- console.log('[FullyFeedly] Mercury API Bad request: ' +
- 'The server could not understand your request. ' +
- 'Verify that request parameters (and content, if any) are valid.');
- failOverlay('APIBadRequest', overlay);
- } else if (xhr.status === 403) {
- console.log('[FullyFeedly] Mercury API Authorization Required: ' +
- 'Authentication failed or was not provided.');
- failOverlay('APIAuthorizationRequired', overlay);
- } else {
- console.log('[FullyFeedly] Mercury API Unknown error');
- failOverlay('APIUnknownError', overlay);
- }
- }
- };
-}
-
-/**
- * Performs an XMLHttpRequest to boilerpipe to get the content of the artile.
- */
-function fetchPageContent() {
-
- // Search the link of the article that is currently opened
- var linkElement = document.querySelector('.websiteCallForAction');
- if (linkElement === null) {
- console.log('[FullyFeedly] Link to article not found');
- failOverlay('articleNotFound');
- return;
- }
-
- // Get the link and convert it for usage as parameter in the GET request
- var pageUrl = linkElement.getAttribute('href');
- var encodedPageUrl = encodeURIComponent(pageUrl);
-
- // Show loading overlay
- var overlay = loadingOverlay();
-
- // Create the asynch HTTP request
- var xhr = new XMLHttpRequest();
- var url = '';
-
- // Select the API to use to extract the article
- if (options.extractionAPI === 'Boilerpipe') {
- // Prepare the request to Boilerpipe
- url = 'https://boilerpipe-web.appspot.com/extract?url=' +
- encodedPageUrl +
- '&extractor=ArticleExtractor&output=json&extractImages=';
-
- xhr.onreadystatechange = boilerpipeRequest(xhr, overlay);
- }
- else if (options.extractionAPI === 'Mercury') {
- if (options.mercuryAPIKey === '') {
- failOverlay('APIMissingKey', overlay);
- return;
- }
-
- url = 'https://mercury.postlight.com/parser?url='+ encodedPageUrl;
-
- xhr.onreadystatechange = mercuryRequest(xhr, overlay);
- }
- else {
- failOverlay('InvalidAPI', overlay);
- return;
- }
- xhr.open('GET', url, true);
- if (options.extractionAPI === 'Mercury') {
- xhr.setRequestHeader('x-api-key', options.mercuryAPIKey);
- }
- xhr.send();
-}
-
-/* ===================== Show Article Preview ===================== */
-
-// Add a button to undo the operation and show the original preview of the article
-function addUndoButton(articlePreviewHTML) {
-
- function getShowPreviewFunction(articlePreviewHTML) {
- return function() {
- // Search the element with the content
- var contentElement = document.querySelector('.entryBody .content');
- if (contentElement === null) {
- console.log('[FullyFeedly] There is something wrong: no content element found');
- failOverlay('error');
- return;
- }
-
- // Replace the preview of the article with the full text
- contentElement.innerHTML = articlePreviewHTML;
- addShowFullArticleBtn();
- successOverlay('done');
- };
- }
- addShowArticlePreviewBtn(getShowPreviewFunction(articlePreviewHTML));
-}
-
-/* ================ DOM Observer =============== */
-
-// Define a generic DOM Observer
-var observeDOM = (function() {
-
- var MutationObserver = window.MutationObserver || window.WebKitMutationObserver;
- return function(obj, callback) {
- // Define a new observer
- var obs = new MutationObserver(function(mutations) {
- if (mutations[0].addedNodes.length > 0 || mutations[0].removedNodes.length > 0) {
- callback(mutations);
- }
- });
- // Have the observer observe foo for changes in children
- obs.observe(obj, { childList:true, subtree:true });
- };
-})();
-
-// This is used to add the button to the article when its
-// preview is opened in Feedly
-observeDOM(document.getElementById('box'), function() {
- // Check if the button is already there otherwise add it
- if (document.querySelector('.showFullArticleBtn') !== null ||
- document.querySelector('.showArticlePreviewBtn') !== null) {
- return;
- }
- addShowFullArticleBtn();
-});
-
-// Listen for requests coming from clicks on the page action button
-chrome.runtime.onMessage.addListener(function(msg, sender, sendResponse) {
- // Process the requests according to the action specified
- if (msg.text && (msg.text === 'extract_article')) {
- // Check if the operation is allowed
- if (document.querySelector('.showFullArticleBtn') !== null )
- {
- fetchPageContent();
- }
- sendResponse('done');
- }
-});
-
-
diff --git a/app/scripts/options.js b/app/scripts/options.js
deleted file mode 100644
index 4236f01..0000000
--- a/app/scripts/options.js
+++ /dev/null
@@ -1,94 +0,0 @@
-/*global $:false, Mousetrap:false */
-
-'use strict';
-
-// Update the form showing the optional fields
-function updateForm() {
- var extractionAPI = $('#extractionAPI').val();
-
- if (extractionAPI === 'Mercury') {
- $('#mercuryKeyForm').show();
- }
- else {
- $('#mercuryKeyForm').hide();
- }
-}
-
-// Show a visual confirmation to the user
-function onKeyboardShortcut() {
- $('#tryShortcut').css('background-color', '#5cb85c');
- setTimeout(function() {
- $('#tryShortcut').css('background-color', 'white');
- }, 500);
-}
-
-// Saves options to chrome.storage
-function saveOptions() {
- var extractionAPI = $('#extractionAPI').val();
- var mercuryAPIKey = $('#mercuryAPIKey').val();
- var enableShortcut = $('#enableShortcut').prop('checked');
- var status = $('#status');
-
- // Check optional paramenters
- if (extractionAPI === 'Mercury') {
- if (mercuryAPIKey === '') {
- // Show the error message
- status.text('Missing Mercury API key');
- $('#mercuryKeyForm').addClass('has-error');
- setTimeout(function() {
- status.text('');
- $('#mercuryKeyForm').removeClass('has-error');
- }, 2000);
- return;
- }
- }
-
- chrome.storage.sync.set({
- extractionAPI: extractionAPI,
- mercuryAPIKey: mercuryAPIKey,
- enableShortcut: enableShortcut
- }, function() {
- // Update status to let user know options were saved.
- status.text('Options saved');
- setTimeout(function() {
- status.text('');
- }, 2000);
- });
-}
-
-// Restores the options using the preferences stored in chrome.storage.
-function restoreOptions() {
- // Use default value extractionAPI = 'Boilerpipe' and mercuryAPIKey = ''
- chrome.storage.sync.get({
- extractionAPI: 'Boilerpipe',
- mercuryAPIKey: '',
- enableShortcut: false
- }, function(items) {
- // In case the user has not switched to Mercury yet...
- if (items.extractionAPI === "Readability")
- items.extractionAPI = "Boilerpipe";
-
- $('#extractionAPI').val(items.extractionAPI);
- $('#mercuryAPIKey').val(items.mercuryAPIKey);
- $('#enableShortcut').prop('checked', items.enableShortcut);
- updateForm();
- });
-}
-
-function translateOptions() {
- var objects = document.getElementsByTagName('*'), i;
- for(i = 0; i < objects.length; i++) {
- if (objects[i].dataset && objects[i].dataset.message) {
- var html = chrome.i18n.getMessage(objects[i].dataset.message);
- if (html) {
- objects[i].innerHTML = chrome.i18n.getMessage(objects[i].dataset.message);
- }
- }
- }
-}
-document.addEventListener('DOMContentLoaded', restoreOptions);
-document.addEventListener('DOMContentLoaded', translateOptions);
-$('#save').click(saveOptions);
-$('#extractionAPI').change(updateForm);
-
-Mousetrap.bind('f f', onKeyboardShortcut);
diff --git a/app/scripts/popup.js b/app/scripts/popup.js
deleted file mode 100644
index 815abcb..0000000
--- a/app/scripts/popup.js
+++ /dev/null
@@ -1,3 +0,0 @@
-'use strict';
-
-console.log('\'Allo \'Allo! Popup');
diff --git a/app/styles/content.css b/app/styles/content.css
deleted file mode 100644
index 35c1c86..0000000
--- a/app/styles/content.css
+++ /dev/null
@@ -1,50 +0,0 @@
-.migrationWarning {
- border-style: solid;
- border-width: 1px;
- border-radius: 3px;
- padding: 10px;
- border-color: #a94442;
- color: #333333;
- font-family: sans-serif;
-}
-
-.migrationWarning b {
- color: #a94442 !important;
-}
-
-.showFullArticleBtn, .showArticlePreviewBtn {
- padding: 14px 20px 14px 20px;
- font-size: 14px;
- line-height: 17px;
- font-weight: normal;
- text-decoration: none;
- border-radius: 3px;
- color: #777;
- -webkit-appearance: none;
- -moz-appearance: none;
- -o-appearance: none;
- appearance: none;
- cursor: pointer;
- z-index: 1000;
- -webkit-font-smoothing: antialiased;
- cursor: pointer;
- border: 1px solid #DCDCDC;
- background: #FFF;
- box-shadow: none;
- display: block;
- margin-top: 40px;
- clear: both;
- text-align: center;
- transition: background-color 0.1s linear;
-}
-
-.showFullArticleBtn:hover, .showArticlePreviewBtn:hover {
- opacity: 1;
- background-color: #F0F0F0;
- border: 1px solid #D0D0D0;
- text-decoration: none;
-}
-
-.websiteCallForAction {
- margin-top: 10px !important;
-}
diff --git a/bower.json b/bower.json
deleted file mode 100644
index 1617741..0000000
--- a/bower.json
+++ /dev/null
@@ -1,11 +0,0 @@
-{
- "name": "fullyfeedly",
- "version": "0.0.0",
- "dependencies": {
- "bootstrap": "~3.2.0",
- "mousetrap": "~1.5.2",
- "spin.js": "spinjs#^2.3.2",
- "iosoverlay": "iOS-Overlay#0.0.1"
- },
- "devDependencies": {}
-}
diff --git a/build/config.js b/build/config.js
new file mode 100644
index 0000000..048f9db
--- /dev/null
+++ b/build/config.js
@@ -0,0 +1,79 @@
+const path = require('path');
+
+const initLoaderRules = require('./loaders');
+const initPlugins = require('./plugins');
+const initMinimizers = require('./minimizers');
+
+const extensions = ['.ts', '.tsx'];
+
+const entry = {
+ background: './src/background/index.ts',
+ content_script: './src/content/index.ts',
+ options: './src/options/index.ts'
+};
+
+const htmlTemplate = path.resolve(__dirname, '../src/options.html');
+
+const output = {
+ path: path.resolve(__dirname, '../extension'),
+ filename: '[name].js'
+};
+
+exports.extensions = extensions;
+exports.entry = entry;
+exports.htmlTemplate = htmlTemplate;
+exports.output = output;
+
+module.exports = ({ context = __dirname, mode = 'development', ...opts }) => {
+ const conf = {
+ context,
+ entry,
+ output,
+ mode,
+ devtool:
+ mode === 'development'
+ ? 'inline-cheap-module-source-map'
+ : 'source-map',
+ plugins: initPlugins({
+ ...opts,
+ mode,
+ template: htmlTemplate
+ }),
+ module: {
+ rules: initLoaderRules({ ...opts, mode, context })
+ },
+ resolve: {
+ extensions,
+ symlinks: false,
+ mainFields: ['browser', 'main', 'module'],
+ alias: {
+ src: path.resolve(context, './src')
+ }
+ },
+ stats: {
+ assetsSort: 'size',
+ children: false,
+ cached: false,
+ cachedAssets: false,
+ entrypoints: false,
+ excludeAssets: /\.(png|svg)/,
+ maxModules: 5
+ },
+ performance: {
+ hints: false
+ }
+ };
+
+ if (mode === 'production') {
+ conf.optimization = {
+ minimizer: initMinimizers()
+ };
+ }
+
+ // CI doesn't need source-maps
+ if (opts.isCI) {
+ delete conf.devtool;
+ }
+
+ return conf;
+};
diff --git a/build/env.js b/build/env.js
new file mode 100644
index 0000000..6b812fc
--- /dev/null
+++ b/build/env.js
@@ -0,0 +1,8 @@
+module.exports = ({ mode }) => {
+ const env = {
+ VERSION: process.env.npm_package_version,
+ NODE_ENV: mode
+ };
+
+ return env;
+};
diff --git a/build/index.js b/build/index.js
new file mode 100644
index 0000000..f390caf
--- /dev/null
+++ b/build/index.js
@@ -0,0 +1 @@
+module.exports = require('./config');
diff --git a/build/loaders.js b/build/loaders.js
new file mode 100644
index 0000000..2ba19be
--- /dev/null
+++ b/build/loaders.js
@@ -0,0 +1,74 @@
+// NOTE: Loader `include` paths are relative to this module
+const path = require('path');
+const CssExtractPlugin = require('mini-css-extract-plugin');
+
+const tsLoader = {
+ loader: 'ts-loader',
+ options: {
+ happyPackMode: true
+ }
+};
+
+const injectStylesLoader = {
+ loader: 'style-loader'
+};
+
+const extractStylesLoader = {
+ loader: CssExtractPlugin.loader
+};
+
+const cssModulesLoader = {
+ loader: 'css-loader',
+ options: {
+ modules: true,
+ importLoaders: 1
+ }
+};
+
+const cssVanillaLoader = {
+ loader: 'css-loader'
+};
+
+const postcssLoader = {
+ loader: 'postcss-loader'
+};
+
+const urlLoader = {
+ loader: 'url-loader',
+ options: {
+ limit: 8192
+ }
+};
+
+const svgLoader = {
+ test: /\.svg$/,
+ include: /node_modules/, // Only resolve SVG imports from node_modules (imported CSS) - for now
+ loader: 'svg-inline-loader'
+};
+
+module.exports = ({ mode, context, isCI = false, injectStyles = false }) => {
+ // style-loader's general method of inserting