diff --git a/.firebaserc b/.firebaserc new file mode 100644 index 0000000..176fcfa --- /dev/null +++ b/.firebaserc @@ -0,0 +1,5 @@ +{ + "projects": { + "default": "flow-writer-com" + } +} diff --git a/.gitignore b/.gitignore index 4d29575..1bc07ef 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,7 @@ npm-debug.log* yarn-debug.log* yarn-error.log* + +/build +.firebase + diff --git a/firebase.json b/firebase.json new file mode 100644 index 0000000..340ed5b --- /dev/null +++ b/firebase.json @@ -0,0 +1,16 @@ +{ + "hosting": { + "public": "build", + "ignore": [ + "firebase.json", + "**/.*", + "**/node_modules/**" + ], + "rewrites": [ + { + "source": "**", + "destination": "/index.html" + } + ] + } +} diff --git a/package.json b/package.json index 95abf45..6167627 100644 --- a/package.json +++ b/package.json @@ -1,31 +1,36 @@ { - "name": "murmur", - "version": "0.1.0", - "private": true, - "dependencies": { - "react": "^16.8.6", - "react-dom": "^16.8.6", - "react-scripts": "3.0.1" - }, - "scripts": { - "start": "react-scripts start", - "build": "react-scripts build", - "test": "react-scripts test", - "eject": "react-scripts eject" - }, - "eslintConfig": { - "extends": "react-app" - }, - "browserslist": { - "production": [ - ">0.2%", - "not dead", - "not op_mini all" - ], - "development": [ - "last 1 chrome version", - "last 1 firefox version", - "last 1 safari version" - ] - } + "name": "flow", + "version": "0.1.0", + "private": true, + "dependencies": { + "clipboard-polyfill": "^2.8.1", + "react": "^16.8.6", + "react-dom": "^16.8.6", + "react-redux": "^7.0.3", + "react-scripts": "3.0.1", + "rebass": "^3.1.1", + "redux": "^4.0.1", + "styled-components": "^4.2.0" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test", + "eject": "react-scripts eject" + }, + "eslintConfig": { + "extends": "react-app" + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + } } diff --git a/public/circle.svg b/public/circle.svg new file mode 100644 index 0000000..e453ead --- /dev/null +++ b/public/circle.svg @@ -0,0 +1,3 @@ +Layer 1 \ No newline at end of file diff --git a/public/copy.svg b/public/copy.svg new file mode 100644 index 0000000..9b5c3cf --- /dev/null +++ b/public/copy.svg @@ -0,0 +1 @@ +04Layer 1 \ No newline at end of file diff --git a/public/favicon.ico b/public/favicon.ico deleted file mode 100644 index a11777c..0000000 Binary files a/public/favicon.ico and /dev/null differ diff --git a/public/flow.svg b/public/flow.svg new file mode 100644 index 0000000..32bed63 --- /dev/null +++ b/public/flow.svg @@ -0,0 +1 @@ +Icon Design 3Created by Ian Rahmadi Kurniawanfrom the Noun Project \ No newline at end of file diff --git a/public/index.html b/public/index.html index dd1ccfd..04a8459 100644 --- a/public/index.html +++ b/public/index.html @@ -1,16 +1,31 @@ - - - - - - + + + + + + + + - - - React App - - - -
- - + + + + + + + + +
+ diff --git a/public/manifest.json b/public/manifest.json index 1f2f141..7dc6e3a 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -1,15 +1,15 @@ { - "short_name": "React App", - "name": "Create React App Sample", - "icons": [ - { - "src": "favicon.ico", - "sizes": "64x64 32x32 24x24 16x16", - "type": "image/x-icon" - } - ], - "start_url": ".", - "display": "standalone", - "theme_color": "#000000", - "background_color": "#ffffff" + "short_name": "Flow", + "name": "Flow: Distraction-Free Writing App", + "icons": [ + { + "src": "circle.svg", + "sizes": "64x64 32x32 24x24 16x16", + "type": "image/svg+xml" + } + ], + "start_url": ".", + "display": "standalone", + "theme_color": "#000000", + "background_color": "#ffffff" } diff --git a/public/new.svg b/public/new.svg new file mode 100644 index 0000000..4e62c88 --- /dev/null +++ b/public/new.svg @@ -0,0 +1 @@ +Layer 1 \ No newline at end of file diff --git a/src/App.css b/src/App.css index b41d297..e69de29 100644 --- a/src/App.css +++ b/src/App.css @@ -1,33 +0,0 @@ -.App { - text-align: center; -} - -.App-logo { - animation: App-logo-spin infinite 20s linear; - height: 40vmin; - pointer-events: none; -} - -.App-header { - background-color: #282c34; - min-height: 100vh; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - font-size: calc(10px + 2vmin); - color: white; -} - -.App-link { - color: #61dafb; -} - -@keyframes App-logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} diff --git a/src/App.js b/src/App.js index ce9cbd2..8e4b84b 100644 --- a/src/App.js +++ b/src/App.js @@ -1,26 +1,22 @@ -import React from 'react'; -import logo from './logo.svg'; -import './App.css'; +import React from "react"; +import { createStore } from "redux"; +import { Provider } from "react-redux"; + +import Menu from "./components/Menu"; +import TextEditor from "./components/TextEditor"; +import reducer from "./store/reducer"; + +import "./App.css"; + +const store = createStore(reducer); function App() { - return ( -
-
- logo -

- Edit src/App.js and save to reload. -

- - Learn React - -
-
- ); + return ( + + + + + ); } export default App; diff --git a/src/Reboot.css b/src/Reboot.css new file mode 100644 index 0000000..ce796b5 --- /dev/null +++ b/src/Reboot.css @@ -0,0 +1,349 @@ +/*! + * Bootstrap Reboot v4.3.1 (https://getbootstrap.com/) + * Copyright 2011-2019 The Bootstrap Authors + * Copyright 2011-2019 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md) + */ +*, +*::before, +*::after { + box-sizing: border-box; +} + +html { + font-family: sans-serif; + line-height: 1.15; + -webkit-text-size-adjust: 100%; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} + +article, +aside, +figcaption, +figure, +footer, +header, +hgroup, +main, +nav, +section { + display: block; +} + +body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, + "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", + "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; + font-size: 1rem; + font-weight: 400; + line-height: 1.5; + color: #212529; + text-align: left; + background-color: #fff; +} + +[tabindex="-1"]:focus { + outline: 0 !important; +} + +hr { + box-sizing: content-box; + height: 0; + overflow: visible; +} + +h1, +h2, +h3, +h4, +h5, +h6 { + margin-top: 0; + margin-bottom: 0.5rem; +} + +p { + margin-top: 0; + margin-bottom: 1rem; +} + +abbr[title], +abbr[data-original-title] { + text-decoration: underline; + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; + cursor: help; + border-bottom: 0; + -webkit-text-decoration-skip-ink: none; + text-decoration-skip-ink: none; +} + +address { + margin-bottom: 1rem; + font-style: normal; + line-height: inherit; +} + +ol, +ul, +dl { + margin-top: 0; + margin-bottom: 1rem; +} + +ol ol, +ul ul, +ol ul, +ul ol { + margin-bottom: 0; +} + +dt { + font-weight: 700; +} + +dd { + margin-bottom: 0.5rem; + margin-left: 0; +} + +blockquote { + margin: 0 0 1rem; +} + +b, +strong { + font-weight: bolder; +} + +small { + font-size: 80%; +} + +sub, +sup { + position: relative; + font-size: 75%; + line-height: 0; + vertical-align: baseline; +} + +sub { + bottom: -0.25em; +} + +sup { + top: -0.5em; +} + +a { + color: #007bff; + text-decoration: none; + background-color: transparent; +} + +a:hover { + color: #0056b3; + text-decoration: underline; +} + +a:not([href]):not([tabindex]) { + color: inherit; + text-decoration: none; +} + +a:not([href]):not([tabindex]):hover, +a:not([href]):not([tabindex]):focus { + color: inherit; + text-decoration: none; +} + +a:not([href]):not([tabindex]):focus { + outline: 0; +} + +pre, +code, +kbd, +samp { + font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", + "Courier New", monospace; + font-size: 1em; +} + +pre { + margin-top: 0; + margin-bottom: 1rem; + overflow: auto; +} + +figure { + margin: 0 0 1rem; +} + +img { + vertical-align: middle; + border-style: none; +} + +svg { + overflow: hidden; + vertical-align: middle; +} + +table { + border-collapse: collapse; +} + +caption { + padding-top: 0.75rem; + padding-bottom: 0.75rem; + color: #6c757d; + text-align: left; + caption-side: bottom; +} + +th { + text-align: inherit; +} + +label { + display: inline-block; + margin-bottom: 0.5rem; +} + +button { + border-radius: 0; +} + +button:focus { + outline: 1px dotted; + outline: 5px auto -webkit-focus-ring-color; +} + +input, +button, +select, +optgroup, +textarea { + margin: 0; + font-family: inherit; + font-size: inherit; + line-height: inherit; +} + +button, +input { + overflow: visible; +} + +button, +select { + text-transform: none; +} + +select { + word-wrap: normal; +} + +button, +[type="button"], +[type="reset"], +[type="submit"] { + -webkit-appearance: button; +} + +button:not(:disabled), +[type="button"]:not(:disabled), +[type="reset"]:not(:disabled), +[type="submit"]:not(:disabled) { + cursor: pointer; +} + +button::-moz-focus-inner, +[type="button"]::-moz-focus-inner, +[type="reset"]::-moz-focus-inner, +[type="submit"]::-moz-focus-inner { + padding: 0; + border-style: none; +} + +input[type="radio"], +input[type="checkbox"] { + box-sizing: border-box; + padding: 0; +} + +input[type="date"], +input[type="time"], +input[type="datetime-local"], +input[type="month"] { + -webkit-appearance: listbox; +} + +textarea { + overflow: auto; + resize: vertical; +} + +fieldset { + min-width: 0; + padding: 0; + margin: 0; + border: 0; +} + +legend { + display: block; + width: 100%; + max-width: 100%; + padding: 0; + margin-bottom: 0.5rem; + font-size: 1.5rem; + line-height: inherit; + color: inherit; + white-space: normal; +} + +progress { + vertical-align: baseline; +} + +[type="number"]::-webkit-inner-spin-button, +[type="number"]::-webkit-outer-spin-button { + height: auto; +} + +[type="search"] { + outline-offset: -2px; + -webkit-appearance: none; +} + +[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} + +::-webkit-file-upload-button { + font: inherit; + -webkit-appearance: button; +} + +output { + display: inline-block; +} + +summary { + display: list-item; + cursor: pointer; +} + +template { + display: none; +} + +[hidden] { + display: none !important; +} +/*# sourceMappingURL=bootstrap-reboot.css.map */ diff --git a/src/components/AutosizeInput.js b/src/components/AutosizeInput.js new file mode 100644 index 0000000..ffd9541 --- /dev/null +++ b/src/components/AutosizeInput.js @@ -0,0 +1,263 @@ +/* +[License] +This source code is part of react-input-autosize +https://github.com/JedWatson/react-input-autosize +packege destributed under the MIT license. + +[Changes] +- All
elements were changed top elements in component render() method. +- Quick fix added to disable outline on element. Search file for "Quickfix". +*/ + +import React, { Component } from "react"; +import PropTypes from "prop-types"; + +const sizerStyle = { + position: "absolute", + top: 0, + left: 0, + visibility: "hidden", + height: 0, + overflow: "scroll", + whiteSpace: "pre" +}; + +const INPUT_PROPS_BLACKLIST = [ + "extraWidth", + "injectStyles", + "inputClassName", + "inputRef", + "inputStyle", + "minWidth", + "onAutosize", + "placeholderIsMinWidth" +]; + +const cleanInputProps = inputProps => { + INPUT_PROPS_BLACKLIST.forEach(field => delete inputProps[field]); + return inputProps; +}; + +const copyStyles = (styles, node) => { + node.style.fontSize = styles.fontSize; + node.style.fontFamily = styles.fontFamily; + node.style.fontWeight = styles.fontWeight; + node.style.fontStyle = styles.fontStyle; + node.style.letterSpacing = styles.letterSpacing; + node.style.textTransform = styles.textTransform; + node.style.outline = styles.outlineStyle; + node.style.outlineStyle = styles.outline; + node.style.outlineWidth = styles.outlineWidth; + node.style.border = styles.border; +}; + +const isIE = + typeof window !== "undefined" && window.navigator + ? /MSIE |Trident\/|Edge\//.test(window.navigator.userAgent) + : false; + +const generateId = () => { + // we only need an auto-generated ID for stylesheet injection, which is only + // used for IE. so if the browser is not IE, this should return undefined. + return isIE + ? "_" + + Math.random() + .toString(36) + .substr(2, 12) + : undefined; +}; + +class AutosizeInput extends Component { + constructor(props) { + super(props); + this.state = { + inputWidth: props.minWidth, + inputId: props.id || generateId() + }; + } + componentDidMount() { + this.mounted = true; + this.copyInputStyles(); + this.updateInputWidth(); + } + componentWillReceiveProps(nextProps) { + const { id } = nextProps; + if (id !== this.props.id) { + this.setState({ inputId: id || generateId() }); + } + } + componentDidUpdate(prevProps, prevState) { + if (prevState.inputWidth !== this.state.inputWidth) { + if (typeof this.props.onAutosize === "function") { + this.props.onAutosize(this.state.inputWidth); + } + } + this.updateInputWidth(); + } + componentWillUnmount() { + this.mounted = false; + } + inputRef = el => { + this.input = el; + if (typeof this.props.inputRef === "function") { + this.props.inputRef(el); + } + }; + placeHolderSizerRef = el => { + this.placeHolderSizer = el; + }; + sizerRef = el => { + this.sizer = el; + }; + copyInputStyles() { + if (!this.mounted || !window.getComputedStyle) { + return; + } + const inputStyles = this.input && window.getComputedStyle(this.input); + if (!inputStyles) { + return; + } + copyStyles(inputStyles, this.sizer); + if (this.placeHolderSizer) { + copyStyles(inputStyles, this.placeHolderSizer); + } + } + updateInputWidth() { + if ( + !this.mounted || + !this.sizer || + typeof this.sizer.scrollWidth === "undefined" + ) { + return; + } + let newInputWidth; + if ( + this.props.placeholder && + (!this.props.value || + (this.props.value && this.props.placeholderIsMinWidth)) + ) { + newInputWidth = + Math.max(this.sizer.scrollWidth, this.placeHolderSizer.scrollWidth) + 2; + } else { + newInputWidth = this.sizer.scrollWidth + 2; + } + // add extraWidth to the detected width. for number types, this defaults to 16 to allow for the stepper UI + const extraWidth = + this.props.type === "number" && this.props.extraWidth === undefined + ? 16 + : parseInt(this.props.extraWidth) || 0; + newInputWidth += extraWidth; + if (newInputWidth < this.props.minWidth) { + newInputWidth = this.props.minWidth; + } + if (newInputWidth !== this.state.inputWidth) { + this.setState({ + inputWidth: newInputWidth + }); + } + } + getInput() { + return this.input; + } + focus() { + this.input.focus(); + } + blur() { + this.input.blur(); + } + select() { + this.input.select(); + } + renderStyles() { + // this method injects styles to hide IE's clear indicator, which messes + // with input size detection. the stylesheet is only injected when the + // browser is IE, and can also be disabled by the `injectStyles` prop. + const { injectStyles } = this.props; + return isIE && injectStyles ? ( +