From a9007b825e4340bc1a10bc0184087072cb6e2e19 Mon Sep 17 00:00:00 2001 From: Manuel Bouza Date: Tue, 19 Feb 2019 15:32:53 +0100 Subject: [PATCH] WIP: Shadow DOM --- package.json | 3 + src/css/_form.scss | 152 +++++++++++++++++------------------ src/css/_reset.scss | 45 +++++++++++ src/css/_spinner.scss | 6 +- src/css/content.scss | 31 +++++++ src/css/main.scss | 48 ----------- src/css/options.scss | 0 src/css/styles.css | 22 +++++ src/js/components/Bubble.js | 10 +-- src/js/components/Modal.js | 33 ++------ src/js/components/Select.js | 1 - src/js/components/Spinner.js | 4 +- src/js/content.js | 44 +++++----- src/js/options.js | 8 +- src/js/popup.js | 6 -- src/js/remoteServices.js | 8 +- src/manifest.json | 5 +- src/options.html | 2 +- src/popup.html | 9 --- webpack.config.js | 20 +++-- yarn.lock | 14 ++++ 21 files changed, 251 insertions(+), 220 deletions(-) create mode 100644 src/css/_reset.scss create mode 100644 src/css/content.scss delete mode 100644 src/css/main.scss create mode 100644 src/css/options.scss create mode 100644 src/css/styles.css delete mode 100644 src/js/popup.js delete mode 100644 src/popup.html diff --git a/package.json b/package.json index 5cbe8da..dfac079 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "main": "bundle.js", "scripts": { "start": "node_modules/.bin/webpack --watch", + "build": "node_modules/.bin/webpack", "test": "node_modules/.bin/jest", "test:watch": "node_modules/.bin/jest --watch", "release": "copyfiles main.css main.min.js background.min.js manifest.json popup.html options.html node_modules/jquery/dist/jquery.min.js node_modules/select2/select2.js src/images/* release" @@ -20,6 +21,7 @@ "react": "^16.8.0", "react-dom": "^16.8.0", "react-select": "^2.3.0", + "react-shadow": "^16.3.2", "route-parser": "^0.0.5" }, "devDependencies": { @@ -42,6 +44,7 @@ "file-loader": "^3.0.1", "html-webpack-plugin": "^3.2.0", "jest": "^24.1.0", + "mini-css-extract-plugin": "^0.5.0", "node-sass": "^4.11.0", "prettier": "^1.16.4", "sass-loader": "^7.1.0", diff --git a/src/css/_form.scss b/src/css/_form.scss index 727184e..b8f0dd6 100644 --- a/src/css/_form.scss +++ b/src/css/_form.scss @@ -1,93 +1,91 @@ -#moco-bx-container { - input { - border-radius: 0; +input { + border-radius: 0; +} + +.form-group { + width: 100%; + margin: 1rem 0; + + label { + display: block; + font-weight: bold; + margin-bottom: 0.25rem; } - .form-group { + input, textarea { + padding: 0.25rem 0.5rem; + background-color: white; + border-color: #cccccc; width: 100%; - margin: 1rem 0; + } - label { - display: block; - font-weight: bold; - margin-bottom: 0.25rem; - } + text-muted { + color: #eee; + } + &.has-error { input, textarea { - padding: 0.25rem 0.5rem; - background-color: white; - border-color: #cccccc; - width: 100%; + border-color: #FB3A2F; } + } - text-muted { - color: #eee; - } + .form-error { + color: #FB3A2F; + } - &.has-error { - input, textarea { - border-color: #FB3A2F; - } - } + .input-group { + position: relative; + display: table; + border-collapse: separate; - .form-error { - color: #FB3A2F; - } - - .input-group { + input { position: relative; - display: table; - border-collapse: separate; - - input { - position: relative; - display: table-cell; - } - - .input-group-addon { - padding: 0.25rem 0.5rem; - font-weight: normal; - color: #555555; - text-align: center; - background-color: #eeeeee; - border: 1px solid #cccccc; - border-left: none; - display: table-cell; - width: 1%; - } - } - } - - input[name="hours"] { - width: 33%; - } - - textarea[name="description"] { - resize: none; - width: 100%; - } - - button { - display: inline-block; - padding: 6px 12px; - margin-bottom: 0; - font-weight: normal; - text-align: center; - white-space: nowrap; - color: white; - background-image: none; - background-color: #7dc332; - border-color: #7dc332; - cursor: pointer; - - &:hover:not(:disabled) { - background-color: #639a28; - border-color: #639a28; + display: table-cell; } - &:disabled { - opacity: 0.65; - cursor: default; + .input-group-addon { + padding: 0.25rem 0.5rem; + font-weight: normal; + color: #555555; + text-align: center; + background-color: #eeeeee; + border: 1px solid #cccccc; + border-left: none; + display: table-cell; + width: 1%; } } } + +input[name="hours"] { + width: 33%; +} + +textarea[name="description"] { + resize: none; + width: 100%; +} + +button { + display: inline-block; + padding: 6px 12px; + margin-bottom: 0; + font-weight: normal; + text-align: center; + white-space: nowrap; + color: white; + background-image: none; + background-color: #7dc332; + border-color: #7dc332; + cursor: pointer; + + &:hover:not(:disabled) { + background-color: #639a28; + border-color: #639a28; + } + + &:disabled { + opacity: 0.65; + cursor: default; + } +} diff --git a/src/css/_reset.scss b/src/css/_reset.scss new file mode 100644 index 0000000..356a32d --- /dev/null +++ b/src/css/_reset.scss @@ -0,0 +1,45 @@ +#moco-bx-bubble, #moco-bx-container { + html, body, div, span, applet, object, iframe, + h1, h2, h3, h4, h5, h6, p, blockquote, pre, + a, abbr, acronym, address, big, cite, code, + del, dfn, em, img, ins, kbd, q, s, samp, + small, strike, strong, sub, sup, tt, var, + b, u, i, center, + dl, dt, dd, ol, ul, li, + fieldset, form, label, legend, + table, caption, tbody, tfoot, thead, tr, th, td, + article, aside, canvas, details, embed, + figure, figcaption, footer, header, hgroup, + menu, nav, output, ruby, section, summary, + time, mark, audio, video { + margin: 0; + padding: 0; + border: 0; + font-size: 100%; + font: inherit; + vertical-align: baseline; + } + /* HTML5 display-role reset for older browsers */ + article, aside, details, figcaption, figure, + footer, header, hgroup, menu, nav, section { + display: block; + } + body { + line-height: 1; + } + ol, ul { + list-style: none; + } + blockquote, q { + quotes: none; + } + blockquote:before, blockquote:after, + q:before, q:after { + content: ''; + content: none; + } + table { + border-collapse: collapse; + border-spacing: 0; + } +} diff --git a/src/css/_spinner.scss b/src/css/_spinner.scss index 9bbf7d8..acbbc68 100644 --- a/src/css/_spinner.scss +++ b/src/css/_spinner.scss @@ -1,11 +1,11 @@ #moco-bx-bubble, #moco-bx-container { - @keyframes spinner { + @keyframes moco-bx-spinner { to { transform: rotate(360deg); } } - .spinner { + .moco-bx-spinner { display: inline-block; width: 2rem; height: 2rem; @@ -13,6 +13,6 @@ border: 2px solid #999; border-right-color: transparent; border-radius: 50%; - animation: spinner .75s linear infinite; + animation: moco-bx-spinner .75s linear infinite; } } diff --git a/src/css/content.scss b/src/css/content.scss new file mode 100644 index 0000000..57b4c49 --- /dev/null +++ b/src/css/content.scss @@ -0,0 +1,31 @@ +@import "mixins"; +@import "form"; +@import "spinner"; + +.moco-bx-bubble { + img { + width: 30px; + height: 30px; + } +} + +.moco-bx-modal { + position: fixed; /* Stay in place */ + z-index: 2000; /* Sit on top */ + padding-top: 100px; /* Location of the box */ + left: 0; + top: 0; + width: 100%; /* Full width */ + height: 100%; /* Full height */ + overflow: auto; /* Enable scroll if needed */ + background-color: rgb(0, 0, 0); /* Fallback color */ + background-color: rgba(0, 0, 0, 0.4); /* Black w/ opacity */ + + .moco-bx-modal-content { + background-color: white; + width: 600px; + padding: 40px; + margin: 0 auto; + } +} + diff --git a/src/css/main.scss b/src/css/main.scss deleted file mode 100644 index 8b4323e..0000000 --- a/src/css/main.scss +++ /dev/null @@ -1,48 +0,0 @@ -@import "mixins"; -@import "form"; -@import "spinner"; - -#moco-bx-bubble { - position: fixed; - bottom: 40px; - left: 50%; - margin-left: -30px; - z-index: 1000; - - height: 60px; - width: 60px; - background-color: white; - border-radius: 50%; - box-shadow: -1px -1px 15px 4px rgba(0, 0, 0, 0.05), - 2px 2px 15px 4px rgba(0, 0, 0, 0.05); - padding: 5px; - - display: flex; - flex-direction: column; - justify-content: space-around; - align-items: center; - - cursor: pointer; -} - -#moco-bx-container { - .moco-bx-modal { - position: fixed; /* Stay in place */ - z-index: 2000; /* Sit on top */ - padding-top: 100px; /* Location of the box */ - left: 0; - top: 0; - width: 100%; /* Full width */ - height: 100%; /* Full height */ - overflow: auto; /* Enable scroll if needed */ - background-color: rgb(0, 0, 0); /* Fallback color */ - background-color: rgba(0, 0, 0, 0.4); /* Black w/ opacity */ - } - - .moco-bx-modal-content { - background-color: white; - width: 600px; - padding: 40px; - margin: 0 auto; - } -} diff --git a/src/css/options.scss b/src/css/options.scss new file mode 100644 index 0000000..e69de29 diff --git a/src/css/styles.css b/src/css/styles.css new file mode 100644 index 0000000..22e7901 --- /dev/null +++ b/src/css/styles.css @@ -0,0 +1,22 @@ +#moco-bx-root { + position: fixed; + bottom: 40px; + left: 50%; + margin-left: -30px; + z-index: 1000; + + height: 60px; + width: 60px; + background-color: white; + border-radius: 50%; + box-shadow: -1px -1px 15px 4px rgba(0, 0, 0, 0.05), + 2px 2px 15px 4px rgba(0, 0, 0, 0.05); + padding: 5px; + + display: flex; + flex-direction: column; + justify-content: space-around; + align-items: center; + + cursor: pointer; +} diff --git a/src/js/components/Bubble.js b/src/js/components/Bubble.js index 3aefa72..0efe667 100644 --- a/src/js/components/Bubble.js +++ b/src/js/components/Bubble.js @@ -1,7 +1,7 @@ import React, { Component } from "react" import PropTypes from "prop-types" import ApiClient from "api/Client" -import Modal, { Content } from "components/Modal" +import Modal from "components/Modal" import InvalidConfigurationError from "components/InvalidConfigurationError" import Form from "components/Form" import Spinner from "components/Spinner" @@ -160,6 +160,7 @@ class Bubble extends Component { // EVENT HANDLERS ----------------------------------------------------------- handleKeyDown = event => { + event.stopPropagation() if (event.keyCode === 27) { this.close() } @@ -223,19 +224,18 @@ class Bubble extends Component { } return ( - <> +
{this.bookedHours > 0 && {this.bookedHours}h} {this.isOpen && ( - {this.renderContent()} + {this.renderContent()} )} - +
) } } diff --git a/src/js/components/Modal.js b/src/js/components/Modal.js index dbda927..d0d2a5e 100644 --- a/src/js/components/Modal.js +++ b/src/js/components/Modal.js @@ -1,5 +1,4 @@ import React, { Component } from "react" -import { createPortal } from "react-dom" import PropTypes from "prop-types" class Modal extends Component { @@ -7,35 +6,17 @@ class Modal extends Component { children: PropTypes.node.isRequired } - constructor(props) { - super(props) - this.el = document.createElement("div") - this.el.setAttribute("class", "moco-bx-modal") - } - - componentDidMount() { - const modalRoot = document.getElementById("moco-bx-container") - modalRoot.appendChild(this.el) - } - - componentWillUnmount() { - const modalRoot = document.getElementById("moco-bx-container") - modalRoot.removeChild(this.el) - } - // RENDER ------------------------------------------------------------------- render() { - return createPortal(this.props.children, this.el) + return ( +
+
+ {this.props.children} +
+
+ ) } } -export function Content({ children }) { - return
{children}
-} - -Content.propTypes = { - children: PropTypes.node -} - export default Modal diff --git a/src/js/components/Select.js b/src/js/components/Select.js index 4d054d6..0c290a2 100644 --- a/src/js/components/Select.js +++ b/src/js/components/Select.js @@ -12,7 +12,6 @@ import { flatMap, pathEq } from "lodash/fp" -import { trace } from "utils" const customTheme = theme => ({ ...theme, diff --git a/src/js/components/Spinner.js b/src/js/components/Spinner.js index be3e433..5fc215c 100644 --- a/src/js/components/Spinner.js +++ b/src/js/components/Spinner.js @@ -1,9 +1,7 @@ import React from 'react' const Spinner = () => ( -
- Loading... -
+
) export default Spinner diff --git a/src/js/content.js b/src/js/content.js index 9f78ac5..40a79c6 100644 --- a/src/js/content.js +++ b/src/js/content.js @@ -1,11 +1,11 @@ -import { createElement } from "react" +import React from "react" import ReactDOM from "react-dom" +import ShadowDOM from "react-shadow" import Bubble from "./components/Bubble" -import services from "remoteServices" -import { parseServices, createMatcher, createEnhancer } from "utils/urlMatcher" +import { createMatcher, createEnhancer } from "utils/urlMatcher" import remoteServices from "./remoteServices" import { pipe } from 'lodash/fp' -import "../css/main.scss" +import "../css/content.scss" const matcher = createMatcher(remoteServices) const serviceEnhancer = createEnhancer(window.document) @@ -32,35 +32,27 @@ const mountBubble = (settings) => { return } - if (!document.getElementById("moco-bx-container")) { - const domContainer = document.createElement("div") - domContainer.setAttribute("id", "moco-bx-container") - document.body.appendChild(domContainer) - } - - if (!document.getElementById("moco-bx-bubble")) { - const domBubble = document.createElement("div") - domBubble.setAttribute("id", "moco-bx-bubble") - document.body.appendChild(domBubble) + if (!document.getElementById("moco-bx-root")) { + const domRoot = document.createElement("div") + domRoot.setAttribute("id", "moco-bx-root") + document.body.appendChild(domRoot) } ReactDOM.render( - createElement(Bubble, { service, settings }), - document.getElementById("moco-bx-bubble") + +
+ +
+
, + document.getElementById("moco-bx-root") ) } const unmountBubble = () => { - const domBubble = document.getElementById("moco-bx-bubble") - const domContainer = document.getElementById("moco-bx-container") + const domRoot = document.getElementById("moco-bx-root") - if (domBubble) { - ReactDOM.unmountComponentAtNode(domBubble) - domBubble.remove() - } - - if (domContainer) { - ReactDOM.unmountComponentAtNode(domContainer) - domContainer.remove() + if (domRoot) { + ReactDOM.unmountComponentAtNode(domRoot) + domRoot.remove() } } diff --git a/src/js/options.js b/src/js/options.js index e563ccf..f1b0878 100644 --- a/src/js/options.js +++ b/src/js/options.js @@ -1,7 +1,7 @@ -import { createElement } from "react" +import React from "react" import ReactDOM from "react-dom" import Setup from "./components/Setup" -import "../css/main.scss" +import "../css/options.scss" -const domContainer = document.querySelector("#moco-bx-container") -ReactDOM.render(createElement(Setup), domContainer) +const domContainer = document.querySelector("#moco-bx-root") +ReactDOM.render(, domContainer) diff --git a/src/js/popup.js b/src/js/popup.js deleted file mode 100644 index 2fd0a17..0000000 --- a/src/js/popup.js +++ /dev/null @@ -1,6 +0,0 @@ -import { createElement } from 'react' -import ReactDOM from 'react-dom' -import Form from './components/Form' - -const domContainer = document.querySelector('#moco-bx-container') -ReactDOM.render(createElement(Form, {inline: false}), domContainer) diff --git a/src/js/remoteServices.js b/src/js/remoteServices.js index 0174236..881c766 100644 --- a/src/js/remoteServices.js +++ b/src/js/remoteServices.js @@ -25,6 +25,12 @@ export default { description: (document, service, { org, repo, id }) => `${org}/${repo}/${id} - ${document .querySelector(".gh-header-title") - .textContent.trim()}` + .textContent.trim()}`, + }, + + "trello": { + name: "trello", + urlPattern: "https://trello.com/c/:id/:title", + description: (document, service, { title }) => title.split('-').slice(1).join(' ') } } diff --git a/src/manifest.json b/src/manifest.json index 825aef7..ea58e05 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -28,7 +28,8 @@ "content_scripts": [ { "matches": [""], - "js": ["content.js"] + "js": ["content.js"], + "css": ["styles.css"] } ], "browser_action": { @@ -36,7 +37,7 @@ "default_title": "MOCO Time Tracking", "default_popup": "popup.html" }, - "web_accessible_resources": ["src/images/*"], + "web_accessible_resources": ["src/images/*", "content.css"], "commands": { "_execute_browser_action": { "suggested_key": { diff --git a/src/options.html b/src/options.html index 04d918b..1b0cfdc 100644 --- a/src/options.html +++ b/src/options.html @@ -4,6 +4,6 @@ -
+
diff --git a/src/popup.html b/src/popup.html deleted file mode 100644 index 04d918b..0000000 --- a/src/popup.html +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - -
- - diff --git a/webpack.config.js b/webpack.config.js index 21f0848..0bd9e3c 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,4 +1,5 @@ const path = require("path") +const MiniCssExtractPlugin = require("mini-css-extract-plugin") const CleanWebpackPlugin = require("clean-webpack-plugin") const HtmlWebpackPlugin = require("html-webpack-plugin") const CopyWebpackPlugin = require("copy-webpack-plugin") @@ -7,8 +8,7 @@ module.exports = { entry: { background: "./src/js/background.js", content: "./src/js/content.js", - options: "./src/js/options.js", - popup: "./src/js/popup.js" + options: "./src/js/options.js" }, output: { path: path.join(__dirname, "build"), @@ -19,7 +19,9 @@ module.exports = { { test: /\.scss$/, use: [ - "style-loader", + { + loader: MiniCssExtractPlugin.loader + }, "css-loader", { loader: "sass-loader", @@ -48,6 +50,10 @@ module.exports = { ] }, plugins: [ + new MiniCssExtractPlugin({ + filename: "[name].css", + chunkFilename: '[id].css' + }), new CleanWebpackPlugin(["build"]), new CopyWebpackPlugin([ { @@ -62,13 +68,11 @@ module.exports = { }) ) } + }, + { + from: "src/css/styles.css", } ]), - new HtmlWebpackPlugin({ - template: path.join(__dirname, "src", "popup.html"), - filename: "popup.html", - chunks: ["popup"] - }), new HtmlWebpackPlugin({ template: path.join(__dirname, "src", "options.html"), filename: "options.html", diff --git a/yarn.lock b/yarn.lock index 7d383b1..fb193e9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4563,6 +4563,15 @@ mimic-fn@^1.0.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ== +mini-css-extract-plugin@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.5.0.tgz#ac0059b02b9692515a637115b0cc9fed3a35c7b0" + integrity sha512-IuaLjruM0vMKhUUT51fQdQzBYTX49dLj8w68ALEAe2A4iYNpIC4eMac67mt3NzycvjOlf07/kYxJDc0RTl1Wqw== + dependencies: + loader-utils "^1.1.0" + schema-utils "^1.0.0" + webpack-sources "^1.1.0" + minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" @@ -5642,6 +5651,11 @@ react-select@^2.3.0: react-input-autosize "^2.2.1" react-transition-group "^2.2.1" +react-shadow@^16.3.2: + version "16.3.2" + resolved "https://registry.yarnpkg.com/react-shadow/-/react-shadow-16.3.2.tgz#a431a7101b0e222dbc669c4070ab395f42dbb675" + integrity sha512-NxFoBvEg2WD3V25jgiWgIltpDfs2XBCbDyi63jEM6Gch8g8r2cqJxSWy2GfG666EfOpRR5Zsrhu0Qn5yze1PrA== + react-transition-group@^2.2.1: version "2.5.3" resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.5.3.tgz#26de363cab19e5c88ae5dbae105c706cf953bb92"