🎉 Hello World
14
.editorconfig
Normal file
@ -0,0 +1,14 @@
|
||||
# editorconfig.org
|
||||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = false
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
insert_final_newline = false
|
7
.gitignore
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
node_modules
|
||||
temp
|
||||
.tmp
|
||||
dist
|
||||
.sass-cache
|
||||
.DS_Store
|
||||
build
|
21
LICENSE.txt
Normal file
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 Bharani / Email This
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
122
README.md
Normal file
@ -0,0 +1,122 @@
|
||||
<div align="center">
|
||||
<h1>
|
||||
Extension Boilerplate
|
||||
</h1>
|
||||
|
||||
<p>
|
||||
<strong>A foundation for creating browser extensions for Chrome, Opera & Firefox.</strong>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
Now that Firefox supports WebExtensions, it has become a lot easier to build browser extensions/addons for multiple browsers without duplicating the codebase. This project serves as a sensible starting point to help you get started.
|
||||
|
||||
<div align="center">
|
||||
<a href="https://www.emailthis.me/open-source/extension-boilerplate">
|
||||
<img src="./resources/chrome-promo/large.png" alt="Extension Boilerplate">
|
||||
</a>
|
||||
</div>
|
||||
|
||||
I have extracted this from the browser extensions that I built for my side-project, [Email This](https://www.emailthis.me).
|
||||
|
||||
> Side note: Do check out [**Email This**](https://www.emailthis.me). It is a simpler alternative to bookmarking tools like Pocket, Readability & Instapaper. Email This will remove ads & distractions from an article and send you a nice email with just the text/images. No need to install any additional applications or login to another app just to access your bookmarks.
|
||||
The Chrome Extensions is available [on the Chrome Web Store](https://chrome.google.com/webstore/detail/email-this/lgblkllcjgihfnlefhnnpppndbbjallh).
|
||||
|
||||
|
||||
## Features
|
||||
|
||||
<dl>
|
||||
<dt>Write once and deploy to Chrome, Opera & Firefox</dt>
|
||||
<dd>
|
||||
Based on WebExtensions. It also includes a tiny polyfill to bring uniformity to the APIs exposed by different browsers.
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
<dl>
|
||||
<dt>Live-reload</dt>
|
||||
<dd>
|
||||
Your changes to CSS, HTML & JS files will be relayed instantly without having to manually reload the extension. This ends up saving a lot of time and improving the developer experience.
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
<dl>
|
||||
<dt>Sensible starting point</dt>
|
||||
<dd>
|
||||
This comes with a gulp based workflow that converts modern <strong>ES6</strong> JavaScript and <strong>SCSS</strong> to JS/CSS.
|
||||
Scripts are transpiled using Babel and bundled using Browserify.
|
||||
Sourcemaps are available for both JS and SCSS.
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
<dl>
|
||||
<dt>Sketch (.sketch) assets for icons and promo images</dt>
|
||||
<dd>
|
||||
A .sketch file is included in the resources directory. This has all the icons and promo images that will be needed while uploading the extensions to the app stores.
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
<dl>
|
||||
<dt>Easily configurable and extendable</dt>
|
||||
<dd>
|
||||
The gulpfile is easily understandable and configurable. If you want to add additional tasks or remove un-used ones, you can easily tweak that file to suit your needs.
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
<dl>
|
||||
<dt>Platform specific & Environment specific variables.</dt>
|
||||
<dd>
|
||||
You might need to specify different data variables based on your environment. For example, you might want to use a localhost API endpoint during development and a production API endpoint once the extension is submitted to the appstore. You can specify such data in the json files inside `config` directory.
|
||||
|
||||
You can also set custom data variables based on the platform (different variable for Chrome, FF, Opera).
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
|
||||
|
||||
## Installation
|
||||
1. Clone the repository `git clone https://github.com/EmailThis/extension-boilerplate.git`
|
||||
2. Run `npm install`
|
||||
3. Run `npm run build`
|
||||
|
||||
Alternately, if you want to try out the sample extension, here are the download links. After you download it, unzip the file and load it in your browser using the steps mentioned below.
|
||||
- [**Download Chrome Extension**](https://github.com/EmailThis/extension-boilerplate/releases/download/v1.0/chrome.zip)
|
||||
- [**Download Opera Extension**](https://github.com/EmailThis/extension-boilerplate/releases/download/v1.0/opera.zip)
|
||||
- [**Download Firefox Extension**](https://github.com/EmailThis/extension-boilerplate/releases/download/v1.0/firefox.zip)
|
||||
|
||||
|
||||
##### Load the extension in Chrome & Opera
|
||||
1. Open Chrome/Opera browser and navigate to chrome://extensions
|
||||
2. Select "Developer Mode" and then click "Load unpacked extension..."
|
||||
3. From the file browser, choose to `extension-boilerplate/build/chrome` or (`extension-boilerplate/build/opera`)
|
||||
|
||||
|
||||
##### Load the extension in Firefox
|
||||
1. Open Firefox browser and navigate to about:debugging
|
||||
2. Click "Load Temporary Add-on" and from the file browser, choose `extension-boilerplate/build/firefox`
|
||||
|
||||
|
||||
## Developing
|
||||
The following tasks can be used when you want to start developing the extension and want to enable live reload -
|
||||
|
||||
- `npm run chrome-watch`
|
||||
- `npm run opera-watch`
|
||||
- `npm run firefox-watch`
|
||||
|
||||
|
||||
## Packaging
|
||||
Run `npm run dist` to create a zipped, production-ready extension for each browser. You can then upload that to the appstore.
|
||||
|
||||
|
||||
## TODO
|
||||
- [ ] Add support for Safari
|
||||
- [x] Add Firefox & Opera Promo images
|
||||
- [x] Add sample screenshot templates
|
||||
- [ ] Write a guide for using config variables & JS preprocessor
|
||||
|
||||
|
||||
-----------
|
||||
This project is licensed under the MIT license.
|
||||
|
||||
If you have any questions or comments, please create a new issue. I'd be happy to hear your thoughts.
|
||||
|
||||
|
||||
Bharani, [Email This](https://www.emailthis.me)
|
3
config/chrome.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"extension": "chrome"
|
||||
}
|
3
config/development.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"env": "development"
|
||||
}
|
3
config/firefox.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"extension": "firefox"
|
||||
}
|
3
config/opera.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"extension": "opera"
|
||||
}
|
3
config/production.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"env": "production"
|
||||
}
|
162
gulpfile.babel.js
Normal file
@ -0,0 +1,162 @@
|
||||
import fs from "fs";
|
||||
import gulp from 'gulp';
|
||||
import {merge} from 'event-stream'
|
||||
import browserify from 'browserify';
|
||||
import source from 'vinyl-source-stream';
|
||||
import buffer from 'vinyl-buffer';
|
||||
import preprocessify from 'preprocessify';
|
||||
import gulpif from "gulp-if";
|
||||
|
||||
const $ = require('gulp-load-plugins')();
|
||||
|
||||
var production = process.env.NODE_ENV === "production";
|
||||
var target = process.env.TARGET || "chrome";
|
||||
var environment = process.env.NODE_ENV || "development";
|
||||
|
||||
var generic = JSON.parse(fs.readFileSync(`./config/${environment}.json`));
|
||||
var specific = JSON.parse(fs.readFileSync(`./config/${target}.json`));
|
||||
var context = Object.assign({}, generic, specific);
|
||||
|
||||
var manifest = {
|
||||
dev: {
|
||||
"background": {
|
||||
"scripts": [
|
||||
"scripts/livereload.js",
|
||||
"scripts/background.js"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
||||
firefox: {
|
||||
"applications": {
|
||||
"gecko": {
|
||||
"id": "my-app-id@mozilla.org"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tasks
|
||||
gulp.task('clean', () => {
|
||||
return pipe(`./build/${target}`, $.clean())
|
||||
})
|
||||
|
||||
gulp.task('build', (cb) => {
|
||||
$.runSequence('clean', 'styles', 'ext', cb)
|
||||
});
|
||||
|
||||
gulp.task('watch', ['build'], () => {
|
||||
$.livereload.listen();
|
||||
|
||||
gulp.watch(['./src/**/*']).on("change", () => {
|
||||
$.runSequence('build', $.livereload.reload);
|
||||
});
|
||||
});
|
||||
|
||||
gulp.task('default', ['build']);
|
||||
|
||||
gulp.task('ext', ['manifest', 'js'], () => {
|
||||
return mergeAll(target)
|
||||
});
|
||||
|
||||
|
||||
// -----------------
|
||||
// COMMON
|
||||
// -----------------
|
||||
gulp.task('js', () => {
|
||||
return buildJS(target)
|
||||
})
|
||||
|
||||
gulp.task('styles', () => {
|
||||
return gulp.src('src/styles/**/*.scss')
|
||||
.pipe($.plumber())
|
||||
.pipe($.sass.sync({
|
||||
outputStyle: 'expanded',
|
||||
precision: 10,
|
||||
includePaths: ['.']
|
||||
}).on('error', $.sass.logError))
|
||||
.pipe(gulp.dest(`build/${target}/styles`));
|
||||
});
|
||||
|
||||
gulp.task("manifest", () => {
|
||||
return gulp.src('./manifest.json')
|
||||
.pipe(gulpif(!production, $.mergeJson({
|
||||
fileName: "manifest.json",
|
||||
jsonSpace: " ".repeat(4),
|
||||
endObj: manifest.dev
|
||||
})))
|
||||
.pipe(gulpif(target === "firefox", $.mergeJson({
|
||||
fileName: "manifest.json",
|
||||
jsonSpace: " ".repeat(4),
|
||||
endObj: manifest.firefox
|
||||
})))
|
||||
.pipe(gulp.dest(`./build/${target}`))
|
||||
});
|
||||
|
||||
|
||||
|
||||
// -----------------
|
||||
// DIST
|
||||
// -----------------
|
||||
gulp.task('dist', (cb) => {
|
||||
$.runSequence('build', 'zip', cb)
|
||||
});
|
||||
|
||||
gulp.task('zip', () => {
|
||||
return pipe(`./build/${target}/**/*`, $.zip(`${target}.zip`), './dist')
|
||||
})
|
||||
|
||||
|
||||
// Helpers
|
||||
function pipe(src, ...transforms) {
|
||||
return transforms.reduce((stream, transform) => {
|
||||
const isDest = typeof transform === 'string'
|
||||
return stream.pipe(isDest ? gulp.dest(transform) : transform)
|
||||
}, gulp.src(src))
|
||||
}
|
||||
|
||||
function mergeAll(dest) {
|
||||
return merge(
|
||||
pipe('./src/icons/**/*', `./build/${dest}/icons`),
|
||||
pipe(['./src/_locales/**/*'], `./build/${dest}/_locales`),
|
||||
pipe([`./src/images/${target}/**/*`], `./build/${dest}/images`),
|
||||
pipe(['./src/images/shared/**/*'], `./build/${dest}/images`),
|
||||
pipe(['./src/**/*.html'], `./build/${dest}`)
|
||||
)
|
||||
}
|
||||
|
||||
function buildJS(target) {
|
||||
const files = [
|
||||
'background.js',
|
||||
'contentscript.js',
|
||||
'options.js',
|
||||
'popup.js',
|
||||
'livereload.js'
|
||||
]
|
||||
|
||||
let tasks = files.map( file => {
|
||||
return browserify({
|
||||
entries: 'src/scripts/' + file,
|
||||
debug: true
|
||||
})
|
||||
.transform('babelify', { presets: ['es2015'] })
|
||||
.transform(preprocessify, {
|
||||
includeExtensions: ['.js'],
|
||||
context: context
|
||||
})
|
||||
.bundle()
|
||||
.pipe(source(file))
|
||||
.pipe(buffer())
|
||||
.pipe(gulpif(!production, $.sourcemaps.init({ loadMaps: true }) ))
|
||||
.pipe(gulpif(!production, $.sourcemaps.write('./') ))
|
||||
.pipe(gulpif(production, $.uglify({
|
||||
"mangle": false,
|
||||
"output": {
|
||||
"ascii_only": true
|
||||
}
|
||||
})))
|
||||
.pipe(gulp.dest(`build/${target}/scripts`));
|
||||
});
|
||||
|
||||
return merge.apply(null, tasks);
|
||||
}
|
51
manifest.json
Normal file
@ -0,0 +1,51 @@
|
||||
{
|
||||
"name": "Shikiryu Bookmarklet",
|
||||
"version": "0.0.1",
|
||||
"manifest_version": 2,
|
||||
"description": "Email a link for later read",
|
||||
"icons": {
|
||||
"16": "icons/icon-16.png",
|
||||
"128": "icons/icon-128.png"
|
||||
},
|
||||
"default_locale": "en",
|
||||
"background": {
|
||||
"scripts": [
|
||||
"scripts/background.js"
|
||||
]
|
||||
},
|
||||
"permissions": [
|
||||
"tabs",
|
||||
"storage",
|
||||
"http://*/*",
|
||||
"https://*/*"
|
||||
],
|
||||
"options_ui": {
|
||||
"page": "options.html"
|
||||
},
|
||||
"content_scripts": [
|
||||
{
|
||||
"matches": [
|
||||
"http://*/*",
|
||||
"https://*/*"
|
||||
],
|
||||
"js": [
|
||||
"scripts/contentscript.js"
|
||||
],
|
||||
"run_at": "document_end",
|
||||
"all_frames": false
|
||||
}
|
||||
],
|
||||
"browser_action": {
|
||||
"default_icon": {
|
||||
"19": "icons/icon-19.png",
|
||||
"38": "icons/icon-38.png"
|
||||
},
|
||||
"default_title": "Shikiryu Bookmarklet",
|
||||
"default_popup": "popup.html"
|
||||
},
|
||||
"applications": {
|
||||
"gecko": {
|
||||
"id": "bookrmarklet@shikiryu.com"
|
||||
}
|
||||
}
|
||||
}
|
53
package.json
Normal file
@ -0,0 +1,53 @@
|
||||
{
|
||||
"name": "extension-boilerplate",
|
||||
"version": "0.0.2",
|
||||
"description": "A template for creating cross-browser extensions",
|
||||
"scripts": {
|
||||
"chrome-build": "cross-env TARGET=chrome gulp",
|
||||
"opera-build": "cross-env TARGET=opera gulp",
|
||||
"firefox-build": "cross-env TARGET=firefox gulp",
|
||||
"build": "cross-env NODE_ENV=production npm run chrome-build && cross-env NODE_ENV=production npm run opera-build && cross-env NODE_ENV=production npm run firefox-build",
|
||||
"chrome-watch": "cross-env TARGET=chrome gulp watch",
|
||||
"opera-watch": "cross-env TARGET=opera gulp watch",
|
||||
"firefox-watch": "cross-env TARGET=firefox gulp watch",
|
||||
"chrome-dist": "cross-env NODE_ENV=production cross-env TARGET=chrome gulp dist",
|
||||
"opera-dist": "cross-env NODE_ENV=production cross-env TARGET=opera gulp dist",
|
||||
"firefox-dist": "cross-env NODE_ENV=production cross-env TARGET=firefox gulp dist",
|
||||
"dist": "npm run chrome-dist && npm run opera-dist && npm run firefox-dist"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/EmailThis/extension-boilerplate"
|
||||
},
|
||||
"author": "Bharani <bharani91@gmail.com> (https://github.com/bharani91)",
|
||||
"bugs": {
|
||||
"url": "https://github.com/EmailThis/extension-boilerplate/issues"
|
||||
},
|
||||
"homepage": "https://www.emailthis.me/extension-boilerplate",
|
||||
"devDependencies": {
|
||||
"babel-core": "^6.1.2",
|
||||
"babel-preset-es2015": "^6.1.2",
|
||||
"babelify": "^7.3.0",
|
||||
"browserify": "^14.1.0",
|
||||
"cross-env": "^3.2.4",
|
||||
"event-stream": "^3.3.4",
|
||||
"gulp": "^3.9.0",
|
||||
"gulp-babel": "^6.1.0",
|
||||
"gulp-clean": "^0.3.1",
|
||||
"gulp-eslint": "^2.0.0",
|
||||
"gulp-if": "^2.0.2",
|
||||
"gulp-livereload": "^3.8.1",
|
||||
"gulp-load-plugins": "^0.5.3",
|
||||
"gulp-merge-json": "^1.0.0",
|
||||
"gulp-plumber": "^1.1.0",
|
||||
"gulp-rename": "^1.2.2",
|
||||
"gulp-run-sequence": "*",
|
||||
"gulp-sass": "^2.2.0",
|
||||
"gulp-sourcemaps": "^1.6.0",
|
||||
"gulp-uglify": "^1.5.4",
|
||||
"gulp-zip": "^2.0.3",
|
||||
"preprocessify": "^1.0.1",
|
||||
"vinyl-buffer": "^1.0.0",
|
||||
"vinyl-source-stream": "^1.1.0"
|
||||
}
|
||||
}
|
BIN
resources/chrome-promo/large.png
Normal file
After Width: | Height: | Size: 666 KiB |
BIN
resources/chrome-promo/marquee.png
Normal file
After Width: | Height: | Size: 998 KiB |
BIN
resources/chrome-promo/small.png
Normal file
After Width: | Height: | Size: 158 KiB |
BIN
resources/extension-assets.sketch
Normal file
14
src/_locales/en/messages.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"appName": {
|
||||
"message": "Ext Starter",
|
||||
"description": "The name of the extension."
|
||||
},
|
||||
"appDescription": {
|
||||
"message": "Boilerplate for building cross browser extensions",
|
||||
"description": "The description of the extension."
|
||||
},
|
||||
"btnTooltip": {
|
||||
"message": "Ext Starter",
|
||||
"description": "Tooltip for the button."
|
||||
}
|
||||
}
|
BIN
src/icons/apple-touch-icon.png
Normal file
After Width: | Height: | Size: 3.3 KiB |
BIN
src/icons/favicon-16x16.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
src/icons/favicon-32x32.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
src/icons/favicon-96x96.png
Normal file
After Width: | Height: | Size: 8.3 KiB |
BIN
src/icons/icon-128.png
Normal file
After Width: | Height: | Size: 8.7 KiB |
BIN
src/icons/icon-16.png
Normal file
After Width: | Height: | Size: 355 B |
BIN
src/icons/icon-19.png
Normal file
After Width: | Height: | Size: 420 B |
BIN
src/icons/icon-38.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
src/icons/icon-64.png
Normal file
After Width: | Height: | Size: 3.9 KiB |
0
src/images/.gitkeep
Normal file
0
src/images/chrome/.gitkeep
Normal file
0
src/images/firefox/.gitkeep
Normal file
0
src/images/opera/.gitkeep
Normal file
0
src/images/shared/.gitkeep
Normal file
53
src/options.html
Normal file
@ -0,0 +1,53 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<link href="styles/options.css" rel="stylesheet">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>Options & Settings</title>
|
||||
</head>
|
||||
<body class="wrap">
|
||||
<div class="grid">
|
||||
<div class="unit whole center-on-mobiles">
|
||||
<div class="heading">
|
||||
<h1>Shikiryu Bookmarklet</h1>
|
||||
<p class="lead">Save this page by email for later read</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="content">
|
||||
<div id="msg"></div>
|
||||
</section>
|
||||
|
||||
<section class="content">
|
||||
<div class="grid">
|
||||
<div class="unit whole center-on-mobiles">
|
||||
<div class="option">
|
||||
<h5>URL</h5>
|
||||
<input type="text" name="url" value="https://bookmarklet.shikiryu.com" />
|
||||
</div>
|
||||
|
||||
<div class="option">
|
||||
<h5>Token</h5>
|
||||
<input type="text" name="token" value="" />
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
<footer class="main-footer">
|
||||
<div class="grid">
|
||||
<div class="unit whole center-on-mobiles">
|
||||
<p class="text-center text-muted">
|
||||
© Shikiryu (thanks to https://github.com/EmailThis/extension-boilerplate)
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</footer>
|
||||
<script src="scripts/options.js"></script>
|
||||
</body>
|
||||
</html>
|
24
src/popup.html
Normal file
@ -0,0 +1,24 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<link href="styles/popup.css" rel="stylesheet">
|
||||
</head>
|
||||
<body>
|
||||
<div id="app" class="popup-content">
|
||||
<h1 class="app-name">Extension Boilerplate</h1>
|
||||
|
||||
<div id="display-container"></div>
|
||||
|
||||
<footer>
|
||||
<p>
|
||||
<small>
|
||||
<a href="#" class="js-options">Options</a>
|
||||
</small>
|
||||
</p>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<script src="scripts/popup.js"></script>
|
||||
</body>
|
||||
</html>
|
31
src/scripts/background.js
Normal file
@ -0,0 +1,31 @@
|
||||
import ext from "./utils/ext";
|
||||
import storage from "./utils/storage";
|
||||
|
||||
ext.runtime.onMessage.addListener(
|
||||
function(request, sender, sendResponse) {
|
||||
if(request.action === "perform-save") {
|
||||
var data = JSON.parse(request.data);
|
||||
var url;
|
||||
var token;
|
||||
storage.get('url', function(resp) {
|
||||
url = resp.url;
|
||||
storage.get('token', function(resp) {
|
||||
token = resp.token;
|
||||
console.log("Extension Type: ", "/* @echo extension */");
|
||||
console.log("PERFORM AJAX", request.data);
|
||||
var destination = url+"?v=1&u="+encodeURIComponent(data.url)+"&t="+encodeURIComponent(data.title)+"&m="+encodeURIComponent(token);
|
||||
|
||||
console.log("Destination: ", destination);
|
||||
fetch(destination)
|
||||
.then(function(response) {
|
||||
console.log(response);
|
||||
});
|
||||
|
||||
sendResponse({ action: "saved" });
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
);
|
34
src/scripts/contentscript.js
Normal file
@ -0,0 +1,34 @@
|
||||
import ext from "./utils/ext";
|
||||
|
||||
var extractTags = () => {
|
||||
var url = document.location.href;
|
||||
if(!url || !url.match(/^http/)) return;
|
||||
|
||||
var data = {
|
||||
title: "",
|
||||
description: "",
|
||||
url: document.location.href
|
||||
}
|
||||
|
||||
var ogTitle = document.querySelector("meta[property='og:title']");
|
||||
if(ogTitle) {
|
||||
data.title = ogTitle.getAttribute("content")
|
||||
} else {
|
||||
data.title = document.title
|
||||
}
|
||||
|
||||
var descriptionTag = document.querySelector("meta[property='og:description']") || document.querySelector("meta[name='description']")
|
||||
if(descriptionTag) {
|
||||
data.description = descriptionTag.getAttribute("content")
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
function onRequest(request, sender, sendResponse) {
|
||||
if (request.action === 'process-page') {
|
||||
sendResponse(extractTags())
|
||||
}
|
||||
}
|
||||
|
||||
ext.runtime.onMessage.addListener(onRequest);
|
20
src/scripts/livereload.js
Normal file
@ -0,0 +1,20 @@
|
||||
'use strict';
|
||||
|
||||
import ext from "./utils/ext";
|
||||
|
||||
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:', error);
|
||||
};
|
||||
|
||||
connection.onmessage = function (e) {
|
||||
if (e.data) {
|
||||
var data = JSON.parse(e.data);
|
||||
if (data && data.command === 'reload') {
|
||||
ext.runtime.reload();
|
||||
}
|
||||
}
|
||||
};
|
28
src/scripts/options.js
Normal file
@ -0,0 +1,28 @@
|
||||
import ext from "./utils/ext";
|
||||
import storage from "./utils/storage";
|
||||
|
||||
var urlInput = document.querySelector("[name=url]");
|
||||
var tokenInput = document.querySelector("[name=token]");
|
||||
var message = document.getElementById("msg");
|
||||
|
||||
storage.get('url', function(resp) {
|
||||
urlInput.value = resp.url || "https://bookmarklet.shikiryu.com";
|
||||
});
|
||||
|
||||
storage.get('token', function(resp) {
|
||||
tokenInput.value = resp.token;
|
||||
});
|
||||
|
||||
urlInput.addEventListener("blur", function(e) {
|
||||
var value = this.value;
|
||||
storage.set({ url: value }, function() {
|
||||
message.innerHTML = "URL changed!";
|
||||
});
|
||||
});
|
||||
|
||||
tokenInput.addEventListener("blur", function(e) {
|
||||
var value = this.value;
|
||||
storage.set({ token: value }, function() {
|
||||
message.innerHTML = "Token saved!";
|
||||
});
|
||||
});
|
63
src/scripts/popup.js
Normal file
@ -0,0 +1,63 @@
|
||||
import ext from "./utils/ext";
|
||||
import storage from "./utils/storage";
|
||||
|
||||
var popup = document.getElementById("app");
|
||||
storage.get('color', function(resp) {
|
||||
var color = resp.color;
|
||||
if(color) {
|
||||
popup.style.backgroundColor = color
|
||||
}
|
||||
});
|
||||
|
||||
var template = (data) => {
|
||||
var json = JSON.stringify(data);
|
||||
return (`
|
||||
<div class="site-description">
|
||||
<h3 class="title">${data.title}</h3>
|
||||
<p class="description">${data.description}</p>
|
||||
<a href="${data.url}" target="_blank" class="url">${data.url}</a>
|
||||
</div>
|
||||
<div class="action-container">
|
||||
<button data-bookmark='${json}' id="save-btn" class="btn btn-primary">Save</button>
|
||||
</div>
|
||||
`);
|
||||
}
|
||||
var renderMessage = (message) => {
|
||||
var displayContainer = document.getElementById("display-container");
|
||||
displayContainer.innerHTML = `<p class='message'>${message}</p>`;
|
||||
}
|
||||
|
||||
var renderBookmark = (data) => {
|
||||
var displayContainer = document.getElementById("display-container")
|
||||
if(data) {
|
||||
var tmpl = template(data);
|
||||
displayContainer.innerHTML = tmpl;
|
||||
} else {
|
||||
renderMessage("Sorry, could not extract this page's title and URL")
|
||||
}
|
||||
}
|
||||
|
||||
ext.tabs.query({active: true, currentWindow: true}, function(tabs) {
|
||||
var activeTab = tabs[0];
|
||||
chrome.tabs.sendMessage(activeTab.id, { action: 'process-page' }, renderBookmark);
|
||||
});
|
||||
|
||||
popup.addEventListener("click", function(e) {
|
||||
if(e.target && e.target.matches("#save-btn")) {
|
||||
e.preventDefault();
|
||||
var data = e.target.getAttribute("data-bookmark");
|
||||
ext.runtime.sendMessage({ action: "perform-save", data: data }, function(response) {
|
||||
if(response && response.action === "saved") {
|
||||
renderMessage("Your bookmark was saved successfully!");
|
||||
} else {
|
||||
renderMessage("Sorry, there was an error while saving your bookmark.");
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
var optionsLink = document.querySelector(".js-options");
|
||||
optionsLink.addEventListener("click", function(e) {
|
||||
e.preventDefault();
|
||||
ext.tabs.create({'url': ext.extension.getURL('options.html')});
|
||||
})
|
68
src/scripts/utils/ext.js
Normal file
@ -0,0 +1,68 @@
|
||||
const apis = [
|
||||
'alarms',
|
||||
'bookmarks',
|
||||
'browserAction',
|
||||
'commands',
|
||||
'contextMenus',
|
||||
'cookies',
|
||||
'downloads',
|
||||
'events',
|
||||
'extension',
|
||||
'extensionTypes',
|
||||
'history',
|
||||
'i18n',
|
||||
'idle',
|
||||
'notifications',
|
||||
'pageAction',
|
||||
'runtime',
|
||||
'storage',
|
||||
'tabs',
|
||||
'webNavigation',
|
||||
'webRequest',
|
||||
'windows',
|
||||
]
|
||||
|
||||
function Extension () {
|
||||
const _this = this
|
||||
|
||||
apis.forEach(function (api) {
|
||||
|
||||
_this[api] = null
|
||||
|
||||
try {
|
||||
if (chrome[api]) {
|
||||
_this[api] = chrome[api]
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
try {
|
||||
if (window[api]) {
|
||||
_this[api] = window[api]
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
try {
|
||||
if (browser[api]) {
|
||||
_this[api] = browser[api]
|
||||
}
|
||||
} catch (e) {}
|
||||
try {
|
||||
_this.api = browser.extension[api]
|
||||
} catch (e) {}
|
||||
})
|
||||
|
||||
try {
|
||||
if (browser && browser.runtime) {
|
||||
this.runtime = browser.runtime
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
try {
|
||||
if (browser && browser.browserAction) {
|
||||
this.browserAction = browser.browserAction
|
||||
}
|
||||
} catch (e) {}
|
||||
|
||||
}
|
||||
|
||||
module.exports = new Extension();
|
3
src/scripts/utils/storage.js
Normal file
@ -0,0 +1,3 @@
|
||||
import ext from "./ext";
|
||||
|
||||
module.exports = (ext.storage.sync ? ext.storage.sync : ext.storage.local);
|
132
src/styles/modules/_grid.scss
Normal file
@ -0,0 +1,132 @@
|
||||
/*
|
||||
* Gridism
|
||||
* A simple, responsive, and handy CSS grid by @cobyism
|
||||
* https://github.com/cobyism/gridism
|
||||
*/
|
||||
|
||||
/* Preserve some sanity */
|
||||
.grid,
|
||||
.unit {
|
||||
-webkit-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* Set up some rules to govern the grid */
|
||||
.grid {
|
||||
display: block;
|
||||
clear: both;
|
||||
}
|
||||
.grid .unit {
|
||||
float: left;
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
/* This ensures the outer gutters are equal to the (doubled) inner gutters. */
|
||||
.grid .unit:first-child { padding-left: 20px; }
|
||||
.grid .unit:last-child { padding-right: 20px; }
|
||||
|
||||
/* Nested grids already have padding though, so let's nuke it */
|
||||
.unit .unit:first-child { padding-left: 0; }
|
||||
.unit .unit:last-child { padding-right: 0; }
|
||||
.unit .grid:first-child > .unit { padding-top: 0; }
|
||||
.unit .grid:last-child > .unit { padding-bottom: 0; }
|
||||
|
||||
/* Let people nuke the gutters/padding completely in a couple of ways */
|
||||
.no-gutters .unit,
|
||||
.unit.no-gutters {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
/* Wrapping at a maximum width is optional */
|
||||
.wrap .grid,
|
||||
.grid.wrap {
|
||||
max-width: 978px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* Width classes also have shorthand versions numbered as fractions
|
||||
* For example: for a grid unit 1/3 (one third) of the parent width,
|
||||
* simply apply class="w-1-3" to the element. */
|
||||
.grid .whole, .grid .w-1-1 { width: 100%; }
|
||||
.grid .half, .grid .w-1-2 { width: 50%; }
|
||||
.grid .one-third, .grid .w-1-3 { width: 33.3332%; }
|
||||
.grid .two-thirds, .grid .w-2-3 { width: 66.6665%; }
|
||||
.grid .one-quarter,
|
||||
.grid .one-fourth, .grid .w-1-4 { width: 25%; }
|
||||
.grid .three-quarters,
|
||||
.grid .three-fourths, .grid .w-3-4 { width: 75%; }
|
||||
.grid .one-fifth, .grid .w-1-5 { width: 20%; }
|
||||
.grid .two-fifths, .grid .w-2-5 { width: 40%; }
|
||||
.grid .three-fifths, .grid .w-3-5 { width: 60%; }
|
||||
.grid .four-fifths, .grid .w-4-5 { width: 80%; }
|
||||
.grid .golden-small, .grid .w-g-s { width: 38.2716%; } /* Golden section: smaller piece */
|
||||
.grid .golden-large, .grid .w-g-l { width: 61.7283%; } /* Golden section: larger piece */
|
||||
|
||||
/* Clearfix after every .grid */
|
||||
.grid {
|
||||
*zoom: 1;
|
||||
}
|
||||
.grid:before, .grid:after {
|
||||
display: table;
|
||||
content: "";
|
||||
line-height: 0;
|
||||
}
|
||||
.grid:after {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
/* Utility classes */
|
||||
.align-center { text-align: center; }
|
||||
.align-left { text-align: left; }
|
||||
.align-right { text-align: right; }
|
||||
.pull-left { float: left; }
|
||||
.pull-right { float: right; }
|
||||
|
||||
/* A property for a better rendering of images in units: in
|
||||
this way bigger pictures are just resized if the unit
|
||||
becomes smaller */
|
||||
.unit img {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
/* Hide elements using this class by default */
|
||||
.only-on-mobiles {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
/* Responsive Stuff */
|
||||
@media screen and (max-width: 568px) {
|
||||
/* Stack anything that isn't full-width on smaller screens
|
||||
and doesn't provide the no-stacking-on-mobiles class */
|
||||
.grid:not(.no-stacking-on-mobiles) > .unit {
|
||||
width: 100% !important;
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
}
|
||||
.unit .grid .unit {
|
||||
padding-left: 0px;
|
||||
padding-right: 0px;
|
||||
}
|
||||
|
||||
/* Sometimes, you just want to be different on small screens */
|
||||
.center-on-mobiles {
|
||||
text-align: center !important;
|
||||
}
|
||||
.hide-on-mobiles {
|
||||
display: none !important;
|
||||
}
|
||||
.only-on-mobiles {
|
||||
display: block !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Expand the wrap a bit further on larger screens */
|
||||
@media screen and (min-width: 1180px) {
|
||||
.wider .grid,
|
||||
.grid.wider {
|
||||
max-width: 1180px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
}
|
95
src/styles/modules/_layout.scss
Normal file
@ -0,0 +1,95 @@
|
||||
html, body {
|
||||
box-sizing: border-box;
|
||||
&:before, &:after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: $font-family-base;
|
||||
line-height: $line-height-base;
|
||||
font-size: $font-size-base;
|
||||
color: $text-color;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
font-family: $headings-font-family;
|
||||
font-weight: $headings-font-weight;
|
||||
line-height: $headings-line-height;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
h1 { font-size: 1.75em; }
|
||||
h2 { font-size: 1.5em; }
|
||||
h3 { font-size: 1.25em; }
|
||||
h4 { font-size: 1.10em; }
|
||||
h5 { font-size: 1em; }
|
||||
h6 { font-size: .85em; }
|
||||
|
||||
nav {
|
||||
margin: 1em 0;
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
li {
|
||||
display: inline-block;
|
||||
margin-right: 1em;
|
||||
margin-bottom: .25em;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: $brand-primary;
|
||||
&:hover, &:focus {
|
||||
color: darken($brand-primary, 6.25%);
|
||||
}
|
||||
}
|
||||
|
||||
ul, ol {
|
||||
margin-top: 0;
|
||||
padding-top: 0;
|
||||
padding-left: 2.5em;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 1em 0;
|
||||
hyphens: auto;
|
||||
|
||||
&.lead {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
+ ul, + ol {
|
||||
margin-top: -.75em;
|
||||
}
|
||||
}
|
||||
|
||||
dd {
|
||||
margin-bottom: 1em;
|
||||
margin-left: 0;
|
||||
padding-left: 2.5em;
|
||||
}
|
||||
|
||||
dt {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin: 0;
|
||||
padding-left: 2.5em;
|
||||
}
|
85
src/styles/modules/_reset.scss
Normal file
@ -0,0 +1,85 @@
|
||||
/*
|
||||
App reset by Ben Frain @benfrain / benfrain.com
|
||||
Slightly modified by Bharani @bharani91 / github.com/bharani91
|
||||
*/
|
||||
|
||||
/*Hat tip to @thierrykoblentz for this approach: https://css-tricks.com/inheriting-box-sizing-probably-slightly-better-best-practice/ */
|
||||
html {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
* {
|
||||
user-select: none;
|
||||
-webkit-tap-highlight-color: rgba(255,255,255,0);
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
*:before,
|
||||
*:after {
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
input[type],
|
||||
[contenteditable] {
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
body,
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
p {
|
||||
margin: 0;
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
/*IMPORTANT: This removes the focus outline for most browsers. Be aware this is a backwards accessibilty step! Mozilla (i.e. Firefox) also adds a dotted outline around a tags and buttons when they receive tab focus which I haven't found an unhacky way of removing.*/
|
||||
a:focus,
|
||||
button:focus {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
button {
|
||||
appearance: none;
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
input,
|
||||
fieldset {
|
||||
appearance: none;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
min-width: 0;
|
||||
font-size: 1rem;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
/*This switches the default outline off when an input receives focus (really important for users tabbing through with a keyboard) so ensure you put something decent in for your input focus instead!!*/
|
||||
input:focus {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/*Removes the default focusring that Mozilla places on select items. From: http://stackoverflow.com/a/18853002/1147859
|
||||
Ensure you set `#000` to the colour you want your text to appear */
|
||||
select:-moz-focusring {
|
||||
color: transparent;
|
||||
text-shadow: 0 0 0 #000;
|
||||
}
|
7
src/styles/modules/_utilities.scss
Normal file
@ -0,0 +1,7 @@
|
||||
.text-muted {
|
||||
color: $text-secondary;
|
||||
}
|
||||
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
17
src/styles/modules/_variables.scss
Normal file
@ -0,0 +1,17 @@
|
||||
// Colors
|
||||
$black: #111111;
|
||||
$white: #ffffff;
|
||||
$gray: #eeeeee;
|
||||
$brand-primary: #4990E2;
|
||||
$text-color: $black;
|
||||
$text-secondary: #7A7A7A;
|
||||
|
||||
|
||||
// Typography
|
||||
$font-family-base: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
$headings-font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
$font-size-base: 16px;
|
||||
$line-height-base: 1.5;
|
||||
$headings-font-weight: 500;
|
||||
$headings-line-height: 1.1;
|
||||
$headings-color: $text-color;
|
38
src/styles/options.scss
Normal file
@ -0,0 +1,38 @@
|
||||
@import "modules/reset";
|
||||
@import "modules/variables";
|
||||
@import "modules/grid";
|
||||
@import "modules/layout";
|
||||
@import "modules/utilities";
|
||||
|
||||
.heading {
|
||||
margin: 1em auto;
|
||||
padding-bottom: 1em;
|
||||
border-bottom: 1px solid #eee;
|
||||
|
||||
h1 + .lead {
|
||||
margin-top: 0.2em;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
min-height: 300px;
|
||||
}
|
||||
|
||||
.option {
|
||||
margin: 1em auto;
|
||||
h5 {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.radio-group {
|
||||
label {
|
||||
display: block;
|
||||
}
|
||||
input[type="radio"] {
|
||||
display: inline-block;
|
||||
margin: 3px 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
88
src/styles/popup.scss
Normal file
@ -0,0 +1,88 @@
|
||||
@import "modules/reset";
|
||||
@import "modules/variables";
|
||||
@import "modules/layout";
|
||||
|
||||
body, html {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#app {
|
||||
padding: 20px 20px;
|
||||
background: $white;
|
||||
}
|
||||
|
||||
footer {
|
||||
color: $text-secondary;
|
||||
text-align: right;
|
||||
font-size: 0.8em;
|
||||
a {
|
||||
outline: none;
|
||||
color: lighten($text-secondary, 10%);
|
||||
&:hover {
|
||||
color: $text-secondary;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.popup-content {
|
||||
min-width: 220px;
|
||||
min-height: 100px;
|
||||
max-width: 350px;
|
||||
}
|
||||
|
||||
.app-name {
|
||||
font-size: 14px;
|
||||
text-transform: uppercase;
|
||||
color: $text-secondary;
|
||||
text-align: center;
|
||||
margin: 0 auto 1em auto;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.site-description {
|
||||
padding: 0.5em;
|
||||
border: 1px solid $gray;
|
||||
margin: 0;
|
||||
|
||||
h3 {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border-bottom: 1px solid $gray;
|
||||
padding-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.description {
|
||||
color: $text-secondary;
|
||||
max-height: 100px;
|
||||
font-size: 0.8em;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.url {
|
||||
outline: none;
|
||||
text-decoration: none;
|
||||
line-height: 1.2;
|
||||
font-size: 0.8em;
|
||||
word-break: break-word;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.action-container {
|
||||
margin: 1em auto;
|
||||
}
|
||||
|
||||
.btn {
|
||||
background: none;
|
||||
border: none;
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
padding: 0.5em 1.5em;
|
||||
background: $brand-primary;
|
||||
color: $white;
|
||||
font-weight: $headings-font-weight;
|
||||
border-radius: 2px;
|
||||
outline: none;
|
||||
font-size: 1em;
|
||||
}
|