diff --git a/.eslintignore b/.eslintignore
index 974862bb06..40b14045ed 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -3,3 +3,4 @@ logs/
firefox/
lib/vendor.bundle.js
activity-streams-env/
+!.*
diff --git a/.storybook/config.js b/.storybook/config.js
new file mode 100644
index 0000000000..48e5074b37
--- /dev/null
+++ b/.storybook/config.js
@@ -0,0 +1,9 @@
+const {configure} = require("@kadira/storybook");
+
+function loadStories() {
+ require("../content-test/components/Spotlight.story");
+ require("../content-test/components/ContextMenu.story");
+ // require as many stories as you need.
+}
+
+configure(loadStories, module);
diff --git a/.storybook/head.html b/.storybook/head.html
new file mode 100644
index 0000000000..aa95183a76
--- /dev/null
+++ b/.storybook/head.html
@@ -0,0 +1 @@
+
diff --git a/.storybook/webpack.config.js b/.storybook/webpack.config.js
new file mode 100644
index 0000000000..70dcd78216
--- /dev/null
+++ b/.storybook/webpack.config.js
@@ -0,0 +1,19 @@
+"use strict";
+const {plugins, resolve} = require("../webpack.common");
+
+module.exports = storybookBaseConfig =>
+ Object.assign(storybookBaseConfig, {
+ resolve,
+ module: {
+ loaders: [
+ {test: /\.json$/, loader: "json"},
+ {
+ test: /\.jsx?$/,
+ exclude: /node_modules/,
+ loader: "babel"
+ }
+ ]
+ },
+ devtool: "eval-sourcemap",
+ plugins: storybookBaseConfig.plugins.concat(plugins)
+ });
diff --git a/content-src/components/ActivityFeed/ActivityFeed.scss b/content-src/components/ActivityFeed/ActivityFeed.scss
index aab22afe55..e4e71094dc 100644
--- a/content-src/components/ActivityFeed/ActivityFeed.scss
+++ b/content-src/components/ActivityFeed/ActivityFeed.scss
@@ -83,7 +83,7 @@
}
&.bookmark .star {
- background: url('img/glyph-bookmark-16-blue.svg') no-repeat center center;
+ background: url('#{$image-path}glyph-bookmark-16-blue.svg') no-repeat center center;
background-size: 18px;
border: 0;
height: 18px;
diff --git a/content-src/components/LinkMenuButton/LinkMenuButton.scss b/content-src/components/LinkMenuButton/LinkMenuButton.scss
index 662f236d5c..87300ad97f 100644
--- a/content-src/components/LinkMenuButton/LinkMenuButton.scss
+++ b/content-src/components/LinkMenuButton/LinkMenuButton.scss
@@ -6,7 +6,7 @@
width: $link-menu-button-size;
height: $link-menu-button-size;
background-color: $white;
- background-image: url('img/glyph-more-16.svg');
+ background-image: url('#{$image-path}glyph-more-16.svg');
background-position: 65%;
background-repeat: no-repeat;
background-clip: padding-box;
diff --git a/content-src/components/LoadMore/LoadMore.scss b/content-src/components/LoadMore/LoadMore.scss
index 3c46220b94..c55414ecc8 100644
--- a/content-src/components/LoadMore/LoadMore.scss
+++ b/content-src/components/LoadMore/LoadMore.scss
@@ -16,7 +16,7 @@
display: inline-block;
width: 15px;
height: 15px;
- background-image: url('img/glyph-showmore-16.svg');
+ background-image: url('#{$image-path}glyph-showmore-16.svg');
background-size: 15px;
}
diff --git a/content-src/components/Loader/Loader.scss b/content-src/components/Loader/Loader.scss
index 3ffe698c9d..8b60983013 100644
--- a/content-src/components/Loader/Loader.scss
+++ b/content-src/components/Loader/Loader.scss
@@ -14,7 +14,7 @@
width: $loader-size;
height: $loader-size;
display: inline-block;
- background-image: url('img/loading@2x.png');
+ background-image: url('#{$image-path}loading@2x.png');
background-size: $loader-size $loader-size;
}
}
diff --git a/content-src/components/MediaPreview/MediaPreview.scss b/content-src/components/MediaPreview/MediaPreview.scss
index 7a1a2e9d58..9017e381b0 100644
--- a/content-src/components/MediaPreview/MediaPreview.scss
+++ b/content-src/components/MediaPreview/MediaPreview.scss
@@ -14,13 +14,13 @@
content: '';
width: 65px;
height: 40px;
- background: url('img/playButton@2x.png') no-repeat center center;
+ background: url('#{$image-path}playButton@2x.png') no-repeat center center;
background-size: 65px 40px;
display: block;
}
&:hover::after {
- background-image: url('img/playButton-hover@2x.png');
+ background-image: url('#{$image-path}playButton-hover@2x.png');
}
&.isPlaying::after {
diff --git a/content-src/components/Search/Search.scss b/content-src/components/Search/Search.scss
index a658c8b08b..2a2085360f 100644
--- a/content-src/components/Search/Search.scss
+++ b/content-src/components/Search/Search.scss
@@ -64,7 +64,7 @@
&:hover > #historyIcon,
&.active > #historyIcon {
- background-image: url('img/glyph-search-history.svg#search-history-active');
+ background-image: url('#{$image-path}glyph-search-history.svg#search-history-active');
}
}
}
@@ -76,7 +76,7 @@
display: inline-block;
margin-right: 10px;
margin-bottom: -3px;
- background-image: url('img/glyph-search-history.svg#search-history');
+ background-image: url('#{$image-path}glyph-search-history.svg#search-history');
}
.search-partners {
@@ -150,13 +150,13 @@
transition: box-shadow 150ms;
box-shadow: 0 0 0 0.5px $search-shadow;
background-color: $search-blue;
- background-image: url('img/glyph-forward-16-white.svg');
+ background-image: url('#{$image-path}glyph-forward-16-white.svg');
color: $white;
}
}
.search-label {
- background: url('img/glyph-search-16.svg') no-repeat center center;
+ background: url('#{$image-path}glyph-search-16.svg') no-repeat center center;
background-size: 20px;
position: absolute;
top: 0;
@@ -177,7 +177,7 @@
padding: 0;
transition: box-shadow 150ms;
box-shadow: none;
- background: $search-button-grey url('img/glyph-forward-16.svg') no-repeat center center;
+ background: $search-button-grey url('#${image-path}glyph-forward-16.svg') no-repeat center center;
background-size: 16px 16px;
&:hover {
@@ -186,7 +186,7 @@
transition: box-shadow 150ms;
box-shadow: 0 0 0 0.5px $search-shadow;
background-color: $search-blue;
- background-image: url('img/glyph-forward-16-white.svg');
+ background-image: url('#{$image-path}glyph-forward-16-white.svg');
color: $white;
}
}
diff --git a/content-src/main.js b/content-src/main.js
index 3a79fb4388..8ea83b828b 100644
--- a/content-src/main.js
+++ b/content-src/main.js
@@ -18,7 +18,7 @@ const Root = React.createClass({
});
function renderRootWhenAddonIsReady() {
- if (window.navigator.activity_streams_addon) {
+ if (window.navigator.activity_streams_addon || __CONFIG__.USE_SHIM) {
ReactDOM.render(, document.getElementById("root"));
} else {
// If the content bridge to the addon isn't set up yet, try again soon.
diff --git a/content-src/styles/icons.scss b/content-src/styles/icons.scss
index 2fbfc23f08..2ec09190b5 100644
--- a/content-src/styles/icons.scss
+++ b/content-src/styles/icons.scss
@@ -12,86 +12,86 @@
}
&.icon-activity-stream {
- background-image: url('img/glyph-activityStream-16.svg');
+ background-image: url('#{$image-path}glyph-activityStream-16.svg');
}
&.icon-arrow {
- background-image: url('img/glyph-showmore-16.svg');
+ background-image: url('#{$image-path}glyph-showmore-16.svg');
}
&.icon-bookmark {
- background-image: url('img/glyph-bookmark-16.svg');
+ background-image: url('#{$image-path}glyph-bookmark-16.svg');
}
&.icon-bookmark-white {
- background-image: url('img/glyph-bookmark-16-white.svg');
+ background-image: url('#{$image-path}glyph-bookmark-16-white.svg');
}
&.icon-bookmark-remove {
- background-image: url('img/glyph-bookmark-remove-16.svg');
+ background-image: url('#{$image-path}glyph-bookmark-remove-16.svg');
}
&.icon-delete {
- background-image: url('img/glyph-delete-16.svg');
+ background-image: url('#{$image-path}glyph-delete-16.svg');
}
&.icon-dismiss {
- background-image: url('img/glyph-dismiss-16.svg');
+ background-image: url('#{$image-path}glyph-dismiss-16.svg');
}
&.icon-firefox-white {
- background-image: url('img/glyph-firefox-white-16.svg');
+ background-image: url('#{$image-path}glyph-firefox-white-16.svg');
}
&.icon-new-window {
- background-image: url('img/glyph-newWindow-16.svg');
+ background-image: url('#{$image-path}glyph-newWindow-16.svg');
}
&.icon-new-window-private {
- background-image: url('img/glyph-newWindow-private-16.svg');
+ background-image: url('#{$image-path}glyph-newWindow-private-16.svg');
}
&.icon-forward {
- background-image: url('img/glyph-forward-16.svg');
+ background-image: url('#{$image-path}glyph-forward-16.svg');
}
&.icon-search {
- background-image: url('img/glyph-search-16.svg');
+ background-image: url('#{$image-path}glyph-search-16.svg');
}
&.icon-settings {
- background-image: url('img/glyph-settings-16.svg');
+ background-image: url('#{$image-path}glyph-settings-16.svg');
}
&.icon-showMore {
- background-image: url('img/glyph-showmore-16.svg');
+ background-image: url('#{$image-path}glyph-showmore-16.svg');
}
&.icon-timeline {
- background-image: url('img/list-icon.svg');
+ background-image: url('#{$image-path}list-icon.svg');
}
&.icon-check {
- background-image: url('img/glyph-check-16.svg');
+ background-image: url('#{$image-path}glyph-check-16.svg');
}
&.icon-pocket {
- background-image: url('img/glyph-pocket-16.svg');
+ background-image: url('#{$image-path}glyph-pocket-16.svg');
}
&.icon-historyItem {
- background-image: url('img/glyph-historyItem-16.svg');
+ background-image: url('#{$image-path}glyph-historyItem-16.svg');
}
&.icon-sync {
- background-image: url('img/glyph-sync-16.svg');
+ background-image: url('#{$image-path}glyph-sync-16.svg');
}
&.icon-tab {
- background-image: url('img/glyph-tab-16.svg');
+ background-image: url('#{$image-path}glyph-tab-16.svg');
}
&.icon-topic {
- background-image: url('img/glyph-topic-16.svg');
+ background-image: url('#{$image-path}glyph-topic-16.svg');
}
}
diff --git a/content-src/styles/variables.scss b/content-src/styles/variables.scss
index 3ca67659cb..61eb23307f 100644
--- a/content-src/styles/variables.scss
+++ b/content-src/styles/variables.scss
@@ -107,6 +107,8 @@ $context-menu-item-padding: 3px 12px;
$icon-size: 16px;
+$image-path: 'img/';
+
$tooltip-height: 50px;
$tooltip-background-color: #FFF3CE;
$tooltip-border-radius: 5px;
diff --git a/content-test/components/ContextMenu.story.js b/content-test/components/ContextMenu.story.js
new file mode 100644
index 0000000000..ce111b9ae0
--- /dev/null
+++ b/content-test/components/ContextMenu.story.js
@@ -0,0 +1,46 @@
+const React = require("react");
+const {storiesOf} = require("@kadira/storybook");
+const ContextMenu = require("components/ContextMenu/ContextMenu");
+
+const DEFAULT_OPTIONS = [
+ {label: "Apples", onClick: () => {}},
+ {label: "Oranges", onClick: () => {}},
+ {label: "Grapes", onClick: () => {}}
+];
+
+// This is so we can show the context menu. Note that this is only
+// necessary because ContextMenu has certain implicit dependencies on
+// its container. Fully encapsulated components (hopefully any new
+// new ones!) should be renderable without needing a container!
+const ContextMenuContainer = React.createClass({
+ getInitialState() {
+ return {visible: true};
+ },
+ render() {
+ const style = {
+ position: "relative",
+ display: "flex",
+ alignItems: "center",
+ justifyContent: "center",
+ width: 150,
+ height: 60,
+ border: "1px dashed #DDD"
+ };
+ const buttonStyle = {
+ fontSize: 11,
+ display: "inline-block"
+ };
+ return (
+ this.setState({visible})} />
+
+
);
+ }
+});
+
+storiesOf("ContextMenu", module)
+ .add("default props", () => (
+
+ ));
diff --git a/content-test/components/Spotlight.story.js b/content-test/components/Spotlight.story.js
new file mode 100644
index 0000000000..1ddaa8a94c
--- /dev/null
+++ b/content-test/components/Spotlight.story.js
@@ -0,0 +1,47 @@
+const React = require("react");
+const {SpotlightItem} = require("components/Spotlight/Spotlight");
+const {storiesOf, action} = require("@kadira/storybook");
+const {createMockProvider} = require("../test-utils");
+const {createSite} = require("../faker");
+const Provider = createMockProvider({dispatch: action("dispatched a redux action")});
+
+// XXX should get rid of container here. See comment in ContextMenu.story
+// for details.
+const Container = props => (
+
+
+ {props.children}
+
+
+);
+
+// Note that if a site image is not in the cache, it can take a while
+// (eg 10 seconds) to load, because the image load starts very late, for
+// unclear reasons. Presumably something to do with faker, tippy-top-sites,
+// lorempixel (used by faker for the images), or Spotlight (aka Highlight)
+// itself.
+storiesOf("Highlight", module)
+ .add("All valid properties", () => {
+ const site = createSite({images: 1});
+ site.bestImage = site.images[0];
+ return ();
+ })
+ .add("Missing an image", () => {
+ const site = createSite({images: 0});
+ return ();
+ })
+ .add("Missing a favicon_url", () => {
+ const site = createSite({images: 1});
+ site.bestImage = site.images[0];
+ site.favicon_url = null;
+ site.favicon = null;
+ site.favicon_color = null;
+ site.favicon_colors = null;
+ return ();
+ })
+ .add("Missing a description", () => {
+ const site = createSite({images: 1});
+ site.bestImage = site.images[0];
+ site.description = null;
+ return ();
+ });
diff --git a/package.json b/package.json
index 8bd8097fe6..fb2048c1be 100644
--- a/package.json
+++ b/package.json
@@ -26,6 +26,7 @@
"url-parse": "1.1.1"
},
"devDependencies": {
+ "@kadira/storybook": "2.2.1",
"babel": "6.5.2",
"babel-core": "6.13.2",
"babel-loader": "6.2.4",
@@ -170,7 +171,7 @@
"firefox": "npm run bundle:webpackAddon && jpm run -b nightly --prefs ./dev-prefs.json",
"test": "npm-run-all test:*",
"pretest": "npm run bundle && npm run copyTestImages && npm run copyTopSitesJson",
- "test:lint": "eslint --ext=.js,.jsx,.json . && sass-lint -v -q",
+ "test:lint": "eslint --ext=.js,.jsx,.json . .storybook && sass-lint -v -q",
"test:checkbinary": "echo \"JPM_FIREFOX_BINARY: ${JPM_FIREFOX_BINARY}\"",
"test:jpm": "jpm test -b ${JPM_FIREFOX_BINARY:-\"nightly\"} --prefs ./test-prefs.json -v",
"test:karma": "NODE_ENV=test karma start",
@@ -179,6 +180,7 @@
"package": "npm run bundle && jpm xpi && mv activity-streams.xpi dist/activity-streams-$npm_package_version.xpi",
"travis": "npm run test",
"prepush": "npm run test:lint && npm run yamscripts",
+ "storybook": "start-storybook -p 9001 -s ./data/content",
"help": "yamscripts help",
"yamscripts": "yamscripts compile",
"__": "# NOTE: THESE SCRIPTS ARE COMPILED!!! EDIT yamscripts.yml instead!!!"
diff --git a/webpack.common.js b/webpack.common.js
new file mode 100644
index 0000000000..fd34cc4c47
--- /dev/null
+++ b/webpack.common.js
@@ -0,0 +1,46 @@
+"use strict";
+const WebpackNotifierPlugin = require("webpack-notifier");
+const webpack = require("webpack");
+const path = require("path");
+const absolute = relPath => path.join(__dirname, relPath);
+const EnvLoaderPlugin = require("webpack-env-loader-plugin");
+
+let env = process.env.NODE_ENV || "development";
+
+let plugins = [
+ new WebpackNotifierPlugin(),
+ new EnvLoaderPlugin({
+ env,
+ filePattern: "config.{env}.yml",
+ loadLocalOverride: env === "development" ? "config.yml" : null,
+ reactEnv: true
+ })
+];
+
+if (env === "production") {
+ plugins = plugins.concat([
+ new webpack.optimize.OccurenceOrderPlugin(),
+ new webpack.optimize.UglifyJsPlugin({
+ test: /vendor/,
+ compress: {warnings: false}
+ }),
+ new webpack.optimize.DedupePlugin()
+ ]);
+}
+
+module.exports = {
+ resolve: {
+ extensions: ["", ".js", ".jsx"],
+ alias: {
+ "common": absolute("./common"),
+ "components": absolute("./content-src/components"),
+ "reducers": absolute("./content-src/reducers"),
+ "actions": absolute("./content-src/actions"),
+ "selectors": absolute("./content-src/selectors"),
+ "lib": absolute("./content-src/lib"),
+ "strings": absolute("./strings"),
+ "test": absolute("./content-test")
+ }
+ },
+ plugins
+};
diff --git a/webpack.config.js b/webpack.config.js
index b2ab893dac..0335ebed67 100644
--- a/webpack.config.js
+++ b/webpack.config.js
@@ -1,9 +1,8 @@
"use strict";
-const WebpackNotifierPlugin = require("webpack-notifier");
const webpack = require("webpack");
+const webpack_common = require("./webpack.common");
const path = require("path");
const absolute = relPath => path.join(__dirname, relPath);
-const EnvLoaderPlugin = require("webpack-env-loader-plugin");
const srcPath = absolute("./content-src/main.js");
const outputDir = absolute("./data/content");
@@ -11,29 +10,9 @@ const outputFilename = "bundle.js";
let env = process.env.NODE_ENV || "development";
-let plugins = [
- new WebpackNotifierPlugin(),
- new EnvLoaderPlugin({
- env,
- filePattern: "config.{env}.yml",
- loadLocalOverride: env === "development" ? "config.yml" : null,
- reactEnv: true
- })
-];
-
if (env !== "test") {
- plugins.push(new webpack.optimize.CommonsChunkPlugin("vendor", "vendor.bundle.js"));
-}
-
-if (env === "production") {
- plugins = plugins.concat([
- new webpack.optimize.OccurenceOrderPlugin(),
- new webpack.optimize.UglifyJsPlugin({
- test: /vendor/,
- compress: {warnings: false}
- }),
- new webpack.optimize.DedupePlugin()
- ]);
+ webpack_common.plugins.push(
+ new webpack.optimize.CommonsChunkPlugin("vendor", "vendor.bundle.js"));
}
module.exports = {
@@ -50,19 +29,6 @@ module.exports = {
filename: outputFilename
},
target: "web",
- resolve: {
- extensions: ["", ".js", ".jsx"],
- alias: {
- "common": absolute("./common"),
- "components": absolute("./content-src/components"),
- "reducers": absolute("./content-src/reducers"),
- "actions": absolute("./content-src/actions"),
- "selectors": absolute("./content-src/selectors"),
- "lib": absolute("./content-src/lib"),
- "strings": absolute("./strings"),
- "test": absolute("./content-test")
- }
- },
module: {
loaders: [
{test: /\.json$/, loader: "json"},
@@ -74,5 +40,6 @@ module.exports = {
]
},
devtool: env === "production" ? null : "eval", // This is for Firefox
- plugins
+ plugins: webpack_common.plugins,
+ resolve: webpack_common.resolve
};
diff --git a/yamscripts.yml b/yamscripts.yml
index 4c4b3748f2..3933214b6a 100644
--- a/yamscripts.yml
+++ b/yamscripts.yml
@@ -44,7 +44,7 @@ scripts:
test:
pre: =>bundle && =>copyTestImages && =>copyTopSitesJson
# test:lint: Run eslint
- lint: eslint --ext=.js,.jsx,.json . && sass-lint -v -q
+ lint: eslint --ext=.js,.jsx,.json . .storybook && sass-lint -v -q
checkbinary: echo "JPM_FIREFOX_BINARY: ${JPM_FIREFOX_BINARY}"
# test:jpm: Run jpm tests
jpm: jpm test -b ${JPM_FIREFOX_BINARY:-"nightly"} --prefs ./test-prefs.json -v
@@ -64,3 +64,6 @@ scripts:
# This is just to make sure we don't make commits with failing tests
# or uncompiled yamscripts.yml. Run automatically with husky.
prepush: =>test:lint && =>yamscripts
+
+# storybook: build a local react-storybook containing local stories for dev
+ storybook: start-storybook -p 9001 -s ./data/content