├── .gitignore ├── README.md ├── index.html ├── jquery.js ├── manifest.json ├── notes.txt ├── package-lock.json ├── package.json ├── semantic.css ├── src ├── assets │ ├── android-drawer.png │ ├── android-inbox.png │ ├── arrow-down-b.png │ ├── arrow-right-b.png │ ├── close.png │ ├── minus.png │ └── plus.png ├── background.js ├── components │ ├── Accordian.js │ ├── App.js │ ├── AuthDialog.jsx │ ├── AuthModal.jsx │ ├── Dashboard.js │ ├── Lowerbar.js │ ├── Modal.js │ ├── SegmentFive.js │ ├── SegmentFour.js │ ├── SegmentOne.js │ ├── SendDialog.jsx │ ├── SendModal.jsx │ ├── Step.js │ └── Toolbar.js ├── configure-webpack.js ├── contentscript.js └── main.js ├── style.css └── webpack.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # Database files 61 | db/ 62 | 63 | # Build files 64 | bundles/ 65 | 66 | .vscode/ 67 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LiveAPI 2 | Successful apps are built on data. As developers, we don’t always have access to the data that would help make our app successful. While the internet is a nearly-bottomless source of public data in the form of websites, that data is not always structured or available programmatically through an API. Time spent building an extraction algorithm and server is time not spent building your app. 3 | 4 | We’re developing LiveAPI, a developer tool to turn any website’s public data into an API in a few minutes. LiveAPI has two parts: a Chrome Extension to select data to extract and a user-hostable server that extracts data and serves up the user-created API endpoints. 5 | 6 | The following three-part guide that walks through how to get started and use LiveAPI. 7 | 8 | * [Part 1: Installation](https://medium.com/@brett.beekley/using-liveapi-part-1-installation-ba1aa13bc73b) 9 | * [Part 2: Authentication](https://medium.com/@pennwu/liveapi-a-visual-data-extraction-tool-part-2-17a1d32b2d52) 10 | * [Part 3: Using the Chrome Extension](https://medium.com/@melissjs/liveapi-a-visual-data-extraction-tool-part-3-e9d60c9ab28d) 11 | 12 | Installation Guide 13 | A primary goal of ours when building LiveAPI is to make setup painless. Installing the Chrome Extension only takes two clicks on the Chrome Web Store. You can now start using the LiveAPI extension on any site by clicking the icon in the top-right Chrome Menu and following the Using the Chrome Extension part of this guide. Setting up a server generally isn’t as simple. Keeping to our goal, we’ve automated the server installation process through a script that installs all of the necessary prerequisites, builds the code and starts the LiveAPI services. This script can be executed in one shell command: 14 | 15 | sudo curl -shttps://raw.githubusercontent.com/live-API/LAPI-Server/master/bin/pull.sh | bash -s 16 | 17 | This command is tested for MacOS and Amazon Linux, but should work on other flavors of RHEL and on Ubuntu. If you have any setup or compatibility issues, let us know on Github. We’re still early in determining our compatibility and would love your feedback. 18 | 19 | Port Setup 20 | LiveAPI server runs on port 4000 (http) and 4443 (https) by default and is accessible by adding the port to the domain (e.g. liveapi.yoursite.com:4443). To serve and access LiveAPI over the standard http and https ports, you can update the HTTP_PORT and HTTPS_PORT variables in server.js. 21 | 22 | If using AWS to host LiveAPI, EC2 requires the application to have root access to run on ports under 1000. To avoid running a web server like LiveAPI as root, you can use iptables to forward port 443 to LiveAPI’s 4443. See this guide for more information. What We’re Asking to Sudo 23 | 24 | The setup command grabs a shell script from github and runs it as the root. Since we don’t recommend executing unfamiliar code as root, we’ll outline what the scripts do. The pull.sh script is relatively simple and does the following: 25 | 26 | Installs git 27 | Pulls the latest LiveAPI server code from github 28 | Starts the installation script (start.sh) 29 | 30 | The installation script, start.sh, contains everything else need to install and start the server: 31 | Installs NodeJS/npm 32 | Installs MongoDB 33 | Installs Node module dependencies 34 | Builds and bundles the front-end JS 35 | Generates self-signed SSL certificates 36 | Starts the server 37 | 38 | Starting the server using npm start does the following: 39 | Starts the Mongo daemon 40 | Starts LiveAPI 41 | Manual Installation 42 | 43 | Alternatively, LiveAPI can be installed and started manually. Make sure to do each of the steps listed in the bullets above. If you are bringing your own SSL certs, such as from a CA, you can drop the certificate (cert.pem) and private key (key.pem) in the SSL folder in the root of LiveAPI. 44 | 45 | The minimum NodeJS version required for LiveAPI is 7, but there may be other requirements we have not found yet. We have tested on NodeJS >8.1.4 and MongoDB >3.4.4, but let us know on Github if you have any problems with these or other versions. 46 | 47 | Using LiveAPI Part 2: Creating Users and Authentication 48 | If you have not installed the LiveAPI server on your computer, or a cloud-hosting solution like AWS, read Part 1 to setup the server. 49 | 50 | By now, here is what you have completed: 51 | [x] Installation on AWS / Locally 52 | [x] Setup SSL Certificate 53 | [x] Start the LiveAPI server 54 | 55 | We built authorization into LiveAPI to allow you to control the endpoints created. First, create the admin account. 56 | 57 | Creating an Admin Account 58 | Go to https://localhost:4443/config. If your SSL Certificate is working correctly, you will see the above sign-in bar. Create the admin account by entering your login credentials. This will create a cookie in the browser, and bring you to the new page. 59 | 60 | If you get an error, your SSL certificate is not working. Visit Part 1: Installation on instructions to create an SSL certificate! 61 | 62 | Invite additional users 63 | When you want multiple people accessing the created endpoints, use invitation links to create their account. This gives the admin control over who has access to the endpoints created, and the corresponding data that is scraped.Entering the invitation link will prompt a username and password. 64 | 65 | Using LiveAPI Part 3: Chrome Extension 66 | If you have not installed the LiveAPI server on your computer, or a cloud-hosting solution like AWS, read Part 1 to setup the server. 67 | 68 | If you have not created one or more user accounts on your server, read Part 2 on authentication. 69 | 70 | Once steps one and two are finished, install the LiveAPI Chrome Extension from the Chrome Extension webstore.To activate the extension, navigate to the target URL you wish to scrape and click the browser action icon (right side on browser top bar). The UI bar will move you through the necessary steps for designing your endpoint. 71 | 72 | Step 1: 73 | Click on a DOM element of your choice that contains one of the properties that you want in your data objects; you will see your selection highlighted in yellow. Select another of the same kind on the page and if the elements are structurally similar, LiveAPI will highlight the remaining items of that kind on the page. Deselect anything you do not wish to include. Enter a name for your new property (such as “title”) then click “Save”. Repeat as desired for additional elements. 74 | 75 | Note: 76 | Use names with no spaces that are camelCased or snake_cased. Please do not use spaces or special characters besides dashes. These will be your keys in your key value pairs of each object. 77 | 78 | Step 2 and 3 are not functional at the moment and are in development. 79 | 80 | Step 4: 81 | Authenticate yourself with the credentials you created on the LiveAPI server. The address field is where you indicate the URL and port of your server. In most cases the port will be 4443. 82 | 83 | Step 5: 84 | Create a name for your endpoint. Observe the same rules for naming as above because this name will be part of your endpoint URI. Enter an interval measured in seconds and click “Create Endpoint”. You will then have the choice to navigate to your endpoint to verify accuracy of your data or you can elect to begin another endpoint design. 85 | 86 | And that’s it! 87 | Because data should be open and free. 88 | LiveAPI is and always will be open source. We welcome contributions from other developers. 89 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 |
9 | 10 | 12 | 13 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | 4 | "name": "LAPI Chrome Extension V2", 5 | "description": "This extension is the UI for LiveAPI", 6 | "version": "1.0", 7 | 8 | "browser_action": { 9 | // "default_icon": "icon.png", 10 | // "default_popup": "lapi.html", 11 | "default_title": "LiveAPI Endpoint Creation Tool" 12 | }, 13 | "web_accessible_resources": ["src/assets/*", "*.png", "*.eot", "*.woff", "*.woff2", "*.ttf", "*.svg", "bundles/*"], 14 | "background": { 15 | "scripts": ["bundles/background.bundle.js"], 16 | "persistent": false 17 | }, 18 | "content_scripts": [ 19 | { 20 | "matches": ["http://*/*","https://*/*", ""], 21 | "css": ["semantic.css", "style.css"], 22 | // "js": ["configure-webpack.js", "jquery.js", "bundles/contentscripts.bundle.js", "./node_modules/semantic-ui-css/semantic.css", "src/main.js"] 23 | "js": ["bundles/semantic.bundle.js", "jquery.js"] 24 | } 25 | ], 26 | "permissions": [ 27 | "tabs", 28 | "activeTab", 29 | "file:///*", 30 | "http://*/*", 31 | "https://*/*", 32 | "" 33 | ], 34 | "content_security_policy": "default-src 'self'; script-src 'self' 'unsafe-eval'; style-src * 'unsafe-inline' 'self' blob:; img-src 'self'; font-src 'self' *" 35 | // "background_page": "background.html" 36 | } -------------------------------------------------------------------------------- /notes.txt: -------------------------------------------------------------------------------- 1 | .constWidth { 2 | /* background: black; */ 3 | padding-left: 10px; 4 | padding-right: 10px; 5 | width: 100%; 6 | height: 150px; 7 | z-index: 2000; 8 | 9 | /* Permalink - use to edit and share this gradient: http://colorzilla.com/gradient-editor/#266d26+0,cbebff+100&1+0,0+100 */ 10 | background: -moz-linear-gradient(top, rgb(0, 168, 47) 0%, rgba(203,235,255,0) 100%); /* FF3.6-15 */ 11 | background: -webkit-linear-gradient(top, rgb(0, 168, 47) 0%,rgba(203,235,255,0) 100%); /* Chrome10-25,Safari5.1-6 */ 12 | background: linear-gradient(to bottom, rgb(0, 168, 47) 0%,rgba(203,235,255,0) 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */ 13 | /* filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#266d26', endColorstr='#00cbebff',GradientType=0 ); IE6-9 */ 14 | 15 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lapi-chrome-ext", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "background.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/Live-API/lapi-chrome-ext.git" 12 | }, 13 | "author": "", 14 | "license": "ISC", 15 | "bugs": { 16 | "url": "https://github.com/Live-API/lapi-chrome-ext/issues" 17 | }, 18 | "homepage": "https://github.com/Live-API/lapi-chrome-ext#readme", 19 | "dependencies": { 20 | "faker": "^4.1.0", 21 | "lodash": "^4.17.4", 22 | "path": "^0.12.7", 23 | "react": "^15.6.1", 24 | "react-dom": "^15.6.1", 25 | "semantic-ui-css": "^2.2.11", 26 | "semantic-ui-react": "^0.71.3", 27 | "webpack": "^3.4.1" 28 | }, 29 | "devDependencies": { 30 | "babel-cli": "^6.24.1", 31 | "babel-core": "^6.25.0", 32 | "babel-loader": "^7.1.1", 33 | "babel-polyfill": "^6.23.0", 34 | "babel-preset-env": "^1.6.0", 35 | "babel-preset-react": "^6.24.1", 36 | "css-loader": "^0.28.4", 37 | "file-loader": "^0.11.2", 38 | "react-hot-loader": "^1.3.1", 39 | "style-loader": "^0.18.2", 40 | "url-loader": "^0.5.9", 41 | "webpack": "^3.4.1", 42 | "webpack-dev-middleware": "^1.11.0" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/assets/android-drawer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Live-API/LAPI-Chrome-Ext/7b3e615a70a2c6a2277d3295035cc50a6f4fa102/src/assets/android-drawer.png -------------------------------------------------------------------------------- /src/assets/android-inbox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Live-API/LAPI-Chrome-Ext/7b3e615a70a2c6a2277d3295035cc50a6f4fa102/src/assets/android-inbox.png -------------------------------------------------------------------------------- /src/assets/arrow-down-b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Live-API/LAPI-Chrome-Ext/7b3e615a70a2c6a2277d3295035cc50a6f4fa102/src/assets/arrow-down-b.png -------------------------------------------------------------------------------- /src/assets/arrow-right-b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Live-API/LAPI-Chrome-Ext/7b3e615a70a2c6a2277d3295035cc50a6f4fa102/src/assets/arrow-right-b.png -------------------------------------------------------------------------------- /src/assets/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Live-API/LAPI-Chrome-Ext/7b3e615a70a2c6a2277d3295035cc50a6f4fa102/src/assets/close.png -------------------------------------------------------------------------------- /src/assets/minus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Live-API/LAPI-Chrome-Ext/7b3e615a70a2c6a2277d3295035cc50a6f4fa102/src/assets/minus.png -------------------------------------------------------------------------------- /src/assets/plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Live-API/LAPI-Chrome-Ext/7b3e615a70a2c6a2277d3295035cc50a6f4fa102/src/assets/plus.png -------------------------------------------------------------------------------- /src/background.js: -------------------------------------------------------------------------------- 1 | chrome.browserAction.onClicked.addListener(function(tab) { 2 | // console.log("without bang", $('#lapiChromeExtensionContainer')); 3 | // console.log("with bang", !!$('#lapiChromeExtensionContainer')); 4 | // if (!!$('#lapiChromeExtensionContainer')!=true) { 5 | chrome.tabs.executeScript(null, {file: "bundles/contentscripts.bundle.js"}); 6 | chrome.tabs.executeScript(null, {file: "src/main.js"}); 7 | // } 8 | // chrome.tabs.executeScript(null, {file: "bundles/contentscripts.bundle.js"}); 9 | }); 10 | 11 | // var elementExists = document.getElementById("find-me"); -------------------------------------------------------------------------------- /src/components/Accordian.js: -------------------------------------------------------------------------------- 1 | import _ from 'lodash' 2 | import faker from 'faker' 3 | import React from 'react' 4 | import { Accordion } from 'semantic-ui-react' 5 | 6 | 7 | const panels = _.times(3, () => ({ 8 | title: faker.lorem.sentence(), 9 | content: faker.lorem.paragraphs(), 10 | })) 11 | 12 | const AccordionExampleStyled = () => ( 13 | 14 | ) 15 | 16 | export default AccordionExampleStyled 17 | -------------------------------------------------------------------------------- /src/components/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react';; 2 | import $ from "../../jquery"; 3 | import Toolbar from "./Toolbar"; 4 | import Lowerbar from "./Lowerbar"; 5 | import SegmentOne from "./SegmentOne"; 6 | import SegmentFour from "./SegmentFour"; 7 | import SegmentFive from "./SegmentFive"; 8 | 9 | $.fn.getDOMPath = function () { 10 | let path = this.parents().addBack(); 11 | let DOMPath = path.get().map(item => { 12 | let self = $(item); 13 | let name = item.nodeName.toLowerCase(); 14 | let index = self.siblings(name).length ? ':nth-child(' + (self.index() + 1) + ')' : ""; 15 | if (name === 'html' || name === 'body') return name; 16 | return name + index; 17 | }); 18 | return DOMPath; 19 | } 20 | 21 | // Iterates through the DOM Path of an element, and gets (1) the least # of selectors to get the exact item, (2) path for common elements 22 | 23 | // If commonPath is empty, then there are no common elements 24 | // Returns an Array [uniquePath, commonPath] 25 | 26 | 27 | $.fn.getSelectors = function(getDOMPath) { 28 | let DOMPath = $(this).getDOMPath().reverse(); 29 | let i = 0; 30 | let commonPath; 31 | while (i < DOMPath.length) { 32 | let currElement = DOMPath.slice(0, i + 1); 33 | let cssSelectors = currElement.reverse().join(' > ') 34 | let result = $(cssSelectors); 35 | if (result.length === 1) { 36 | return [cssSelectors, commonPath]; 37 | } 38 | commonPath = cssSelectors.slice(); 39 | i++; 40 | } 41 | } 42 | 43 | function selectElement(App, siblingSelector, DOMPath, styles, text, position) { 44 | if (App.state.firstElement) { 45 | // execute highlightElement 46 | // highlightElement(App, position, DOMPath, styles, text); 47 | // can't directly add to state 48 | App.addRecommendation(siblingSelector); 49 | App.toggleFirstElement(); 50 | } else { 51 | if (!App.state.recommendations.includes(siblingSelector)) { 52 | highlightElement(App, position, DOMPath, styles, text); 53 | }; 54 | let siblingsDOM = $(siblingSelector).each((index, element) => { 55 | console.log('element', element); 56 | let elePosition = cumulativeOffset(element); 57 | let eleDOMPath = $(element).getSelectors($(element).getDOMPath)[0]; 58 | let eleStyles = setStyles.call(element); 59 | let eleText = cleanWhiteSpace($(element).text()); 60 | highlightElement(App, elePosition, eleDOMPath, eleStyles, eleText); 61 | }); 62 | console.log('propertyArray', App.state.propertyArray); 63 | 64 | // If siblingSelector matches recommendation in cue 65 | // Add Highlight to Each Recommendation [create new class] 66 | // Exclude the existing element 67 | // Add the DOM Path of Recommendation to Array (already in function) 68 | // Add the DOM Path of Recommendation to Data (already in function) 69 | // Else 70 | // Add Highlight to the Element 71 | // Add Recommendations via Function (w/ class) 72 | } 73 | } 74 | 75 | function setStyles () { 76 | return $(this).css([ 77 | "width", "height", "font-size", "font-weight", "font-family", "font-variant", "font-stretch", "line-height", "text-transform", "text-align", "padding-top", "padding-bottom", "padding-left", "padding-right", "letter-spacing"] 78 | ); 79 | } 80 | 81 | function highlightElement(App, position, DOMPath, styles, text) { 82 | App.addDOMToPropertyArray(DOMPath); 83 | $('.liveAPI-body').append( 84 | $('
') 85 | .offset({ top: position.top, left: position.left }) 86 | .css({ "font-size": styles["font-size"], "font-family": styles["font-family"], "font-variant": styles["font-variant"], "font-stretch": styles["font-stretch"], "line-height": styles["line-height"], "text-transform": styles["text-transform"], "text-align": styles["text-align"], "letter-spacing": styles["letter-spacing"] }) 87 | .data('DOMPath', DOMPath) 88 | // Add the data type here 89 | .addClass('liveAPI-newElement liveAPI-highlight liveAPI-yellow liveAPI-ignore') 90 | .append( 91 | $('
') 92 | .addClass('liveAPI-highlight-wrapper liveAPI-ignore') 93 | .css({ 94 | "max-width": styles["width"], "height": styles["height"],"padding-right": styles["padding-right"] 95 | }) 96 | .text(text) 97 | ) 98 | .append( 99 | $('') 100 | .addClass('liveAPI-highlight-button') 101 | .text('x') 102 | ) 103 | ) 104 | } 105 | 106 | // Removes leading, trailing, and excess whitespace between words from text 107 | function cleanWhiteSpace(text) { 108 | let revisedText = text.replace(/^\s+|\s+$/g, "").replace(/\s\s+/g, " "); 109 | return revisedText; 110 | } 111 | 112 | // Finds position of selected HTML element 113 | function cumulativeOffset(element) { 114 | let top = 0 115 | let left = 0; 116 | do { 117 | top += element.offsetTop || 0; 118 | left += element.offsetLeft || 0; 119 | element = element.offsetParent; 120 | } while (element); 121 | 122 | return { 123 | top: top + 230, 124 | left: left 125 | }; 126 | }; 127 | 128 | class App extends Component { 129 | constructor() { 130 | super(); 131 | this.state = { 132 | serverUrl: '', 133 | crawlUrl: '', 134 | activeStep: 1, 135 | authenticated: false, 136 | authAttemptNum: 0, 137 | stepsCompleted: [], 138 | lowerBar: true, 139 | scrapePropBtnArr: [], 140 | lowerSegment: false, 141 | property: undefined, 142 | propertyArray: [], 143 | recommendations: [], 144 | text: {}, 145 | firstElement: true 146 | } 147 | // ----------------toggle authentication 148 | this.signIn = () => { 149 | console.log(this); 150 | this.setState({ authenticated: true }); 151 | console.log("authenticated:", this.state.authenticated) 152 | } 153 | 154 | // initialize new crawl for already logged in user 155 | this.initializeNewCrawl = () => { 156 | this.setState({ 157 | activeStep: 1, 158 | authAttemptNum: 0, 159 | stepsCompleted: [], 160 | property: undefined, 161 | propertyArray: [] 162 | }) 163 | } 164 | 165 | this.jumpBack = (activeStepInt) => { 166 | this.setState({ activeStep: activeStepInt }); 167 | } 168 | 169 | // remove property 170 | this.removeProperty = (e, el) => { 171 | let propArr = this.state.scrapePropBtnArr; 172 | console.log("propArr", propArr); 173 | propArr.forEach((element, i) => { 174 | console.log("i", i); 175 | if (element === el.content) { 176 | propArr.splice(i, 1); 177 | console.log("HEY", propArr) 178 | this.setState({ scrapePropBtnArr: propArr }); 179 | this.deleteProperty(el.content); // from text obj 180 | console.log(this.text) 181 | } 182 | }); 183 | } 184 | 185 | // log user out 186 | this.logout = () => { 187 | this.setState({ 188 | activeStep: 1, 189 | authenticated: false, 190 | authAttemptNum: 0, 191 | stepsCompleted: [], 192 | property: undefined, 193 | propertyArray: [] 194 | }) 195 | } 196 | 197 | // increment auth attempts 198 | this.authAttemptedFunc = () => { 199 | let attempt = this.state.authAttemptNum; 200 | ++attempt 201 | this.setState({ authAttemptNum: attempt }); 202 | console.log("attempt", this.state.authAttemptNum); 203 | console.log("attempt from state", this.state.authAttemptNum); 204 | } 205 | 206 | // move to next step 207 | this.stepForward = () => { 208 | console.log("step", this.state.activeStep); 209 | let step = this.state.activeStep; 210 | let completedArr = this.state.stepsCompleted.slice(); 211 | if (this.state.activeStep <= 5) { 212 | completedArr.push(step); 213 | this.setState({ stepsCompleted: completedArr }); 214 | (step === 1) ? this.setState({ activeStep: 4 }) : this.setState({ activeStep: ++step }); 215 | console.log(this.state.stepsCompleted); 216 | } 217 | } 218 | 219 | this.closeEx = () => { 220 | $('#lapiChromeExtensionContainer').remove(); 221 | $('#targetBodyContainer').css({ 222 | '-ms-transform': 'translateY(0px)', 223 | '-webkit-transform': 'translateY(0px)', 224 | 'transform': 'translateY(0px)' 225 | }); 226 | } 227 | 228 | // toggle lowerbar transform 229 | this.lowerBarTransformCssToggle = () => { 230 | let pushDown = () => { 231 | $('#targetBodyContainer').css({ 232 | '-ms-transform': 'translateY(230px)', 233 | '-webkit-transform': 'translateY(230px)', 234 | 'transform': 'translateY(230px)' 235 | }) 236 | } 237 | 238 | let pullUp = () => { 239 | console.log("pulling body up") 240 | $('#targetBodyContainer').css({ 241 | '-ms-transform': 'translateY(35px)', 242 | '-webkit-transform': 'translateY(35px)', 243 | 'transform': 'translateY(35px)' 244 | }) 245 | } 246 | 247 | (!this.state.lowerBar) ? pushDown() : pullUp(); 248 | } 249 | 250 | 251 | // close lower and change icon 252 | this.toggleLower = () => { 253 | this.setState({ lowerBar: !this.state.lowerBar }); 254 | this.lowerBarTransformCssToggle(); 255 | } 256 | 257 | this.savePostURL = (url) => { 258 | this.setState({ serverUrl: url }) 259 | } 260 | 261 | /* 262 | Following functions are used in Step 1 to assign property name to the selected HTML elements. 263 | 264 | getPropertyName - gets value of textbox 265 | resetPropertyName - resets value of textbox after saving 266 | saveProperty - saves property name to state 267 | */ 268 | 269 | // Gets value of the property textbox 270 | this.getPropertyName = (e) => { 271 | this.setState({ property: e.target.value }); 272 | } 273 | 274 | // Clears the property textbox. Executed in saveProperty function 275 | this.resetPropertyName = () => { 276 | const propertyTextbox = document.getElementById('live-API-property-textbox'); 277 | propertyTextbox.value = ''; 278 | this.setState({ property: undefined }); 279 | } 280 | 281 | this.resetPropertyArray = () => { 282 | this.setState({ propertyArray: [] }); 283 | } 284 | 285 | this.resetHighlightedElements = () => { 286 | // Not sure why body is firing twice 287 | $('.liveAPI-body').find('.liveAPI-newElement').remove(); 288 | } 289 | this.saveProperty = (property) => { 290 | if (!property) return; 291 | let textObj = JSON.parse(JSON.stringify(this.state.text)); 292 | textObj[property] = this.state.propertyArray.slice(); 293 | this.setState({ text: textObj }); 294 | 295 | // MELISSSA 296 | let newArr = this.state.scrapePropBtnArr.slice(); 297 | newArr.push(property); 298 | this.setState({ 299 | // property: property, 300 | scrapePropBtnArr: newArr 301 | }); 302 | this.resetHighlightedElements(); 303 | this.resetPropertyName(); 304 | this.resetPropertyArray(); 305 | } 306 | 307 | // Delete property from text object 308 | this.deleteProperty = (property) => { 309 | if (!property) return; 310 | let textObj = JSON.parse(JSON.stringify(this.state.text)); 311 | delete textObj[property]; 312 | this.setState({ text: textObj }); 313 | } 314 | 315 | this.setCrawlUrl = (url) => { 316 | this.setState({ crawlUrl: url }); 317 | } 318 | 319 | this.getPropertyArray = () => { 320 | return this.state.propertyArray.slice(); 321 | } 322 | 323 | this.setPropertyArray = (propertyArray) => { 324 | this.setState({ "propertyArray": propertyArray }); 325 | } 326 | 327 | this.addDOMToPropertyArray = (DOMPath) => { 328 | const propArr = this.getPropertyArray(); 329 | propArr.push(DOMPath); 330 | this.setState({ "propertyArray": propArr }); 331 | } 332 | this.addRecommendation = (rcmd) => { 333 | const rcmdArr = this.state.recommendations.slice(); 334 | rcmdArr.push(rcmd); 335 | this.setState({ "recommendations": rcmdArr }); 336 | } 337 | this.toggleFirstElement = () => { 338 | this.state.firstElement ? this.setState({firstElement: false}) : this.setState({firstElement: true}); 339 | } 340 | // end constructor ///////////////////////////////////// 341 | } 342 | 343 | componentDidMount() { 344 | const App = this; 345 | 346 | // Prevents default click event 347 | $(document).on('click', '*', function () { 348 | return false; 349 | }); 350 | // Stop propagation for highlight components 351 | 352 | $(document).on('click', '.liveAPI-highlight', (e) => e.stopImmediatePropagation()); 353 | $(document).on('click', '.liveAPI-highlight-wrapper', (e) => e.stopImmediatePropagation()); 354 | // DOMPath is removed from state when item is deselected 355 | $(document).on('click', '.liveAPI-highlight-button', function (e) { 356 | // Execute this function for all elements that have this data type 357 | // Find them first, then execute the following function 358 | const propArr = App.getPropertyArray(); 359 | let DOMPath = $(this).parent().data('DOMPath'); 360 | let index = propArr.indexOf(DOMPath); 361 | propArr.splice(index, 1); 362 | App.setPropertyArray(propArr); 363 | $(this).parent().remove(); 364 | e.stopImmediatePropagation(); 365 | }); 366 | 367 | /* 368 | Return false when the element is part of the Toolbar 369 | Return false for creation of multiple highlighting 370 | Return false if the element is not a 371 | div, span, li, p element 372 | Add logic for div elements 373 | */ 374 | 375 | $(document).on('click', '*', function () { 376 | const position = cumulativeOffset(this); 377 | const DOMPath = $(this).getSelectors($(this).getDOMPath)[0]; 378 | const siblingSelector = $(this).getSelectors($(this).getDOMPath)[1]; 379 | // Remove event listener from elements with child div elements 380 | // Remove event listener from Toolbar elements 381 | // Add DOM Path to this.state.propertyArray 382 | if ($(this).find('div').length > 0) return false; 383 | if ($(this).closest('#lapiChromeExtensionContainer').length > 0) return false; 384 | if ($(this).hasClass("liveAPI-ignore")) return false; 385 | 386 | let styles = setStyles.call(this); 387 | let text = cleanWhiteSpace($(this).text()); 388 | highlightElement(App, position, DOMPath, styles, text); 389 | selectElement(App, siblingSelector, DOMPath, styles, text, position); 390 | }); 391 | } 392 | 393 | render() { 394 | return ( 395 |
396 |
397 | 398 | {/* Authenticate}/> 399 | Create API Endpoint} address='localhost:4000'/> */} 400 | 401 | 402 | {this.state.lowerBar ? : null} 403 | 404 | 405 | {/* setValFunc={this.handleChangeValue} value={this.state.segmentPropValue} saveFunc={this.saveScrapePropNames} */} 406 | 407 | {(this.state.lowerBar && (this.state.activeStep === 1)) ? : null} 408 | 409 | {/* {(this.state.lowerBar && (this.state.activeStep===4)) ? : null} */} 410 | 411 | 412 | {(this.state.lowerBar && (this.state.activeStep === 4)) ? : null} 413 | 414 | {(this.state.lowerBar && ((this.state.activeStep === 5) || (this.state.activeStep === 6))) ? : null} 415 | 416 |
417 |
418 | ); 419 | } 420 | } 421 | 422 | export default App; 423 | -------------------------------------------------------------------------------- /src/components/AuthDialog.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { render } from 'react-dom'; 3 | import { Card, Input, Divider, Button, Form, Icon } from 'semantic-ui-react' 4 | 5 | class CreateUserDialog extends Component { 6 | constructor(props) { 7 | super(props); 8 | 9 | this.state = {}; 10 | 11 | this.handleChange = this.handleChange.bind(this); 12 | this.handleSubmit = this.handleSubmit.bind(this); 13 | } 14 | 15 | handleChange(event) { 16 | const state = {}; 17 | state[event.target.name] = event.target.value; 18 | this.setState(state); 19 | } 20 | 21 | handleSubmit(event) { 22 | this.props.submission(this.state); 23 | event.preventDefault(); 24 | } 25 | 26 | render() { 27 | return ( 28 | 29 | 30 | 31 | Log into LiveAPI 32 | 33 | 34 | 35 | 36 |
37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 |
53 |
54 | ) 55 | } 56 | } 57 | 58 | export default CreateUserDialog; -------------------------------------------------------------------------------- /src/components/AuthModal.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Button, Header, Icon, Image, Modal } from 'semantic-ui-react'; 3 | import CreateUserDialog from './AuthDialog.jsx'; 4 | 5 | class AuthModal extends Component { 6 | postCredentials(cred) { 7 | const url = `http://${cred.address}/auth`; 8 | console.log(url); 9 | 10 | const xhr = new XMLHttpRequest(); 11 | 12 | xhr.onreadystatechange = function() { 13 | if (xhr.readyState == XMLHttpRequest.DONE) { 14 | console.log(xhr.responseText); 15 | } 16 | } 17 | 18 | xhr.open("POST", url); 19 | xhr.setRequestHeader("Content-Type", "application/json"); 20 | xhr.send(JSON.stringify({username: cred.username, password: cred.password})); 21 | } 22 | 23 | render() { 24 | return( 25 | 26 | 27 | 28 | 29 | 30 | ); 31 | } 32 | } 33 | 34 | export default AuthModal 35 | -------------------------------------------------------------------------------- /src/components/Dashboard.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import Accordian from "./Accordian"; 3 | 4 | class Dashboard extends Component { 5 | render() { 6 | return ( 7 | 8 | ) 9 | } 10 | } 11 | export default Dashboard; -------------------------------------------------------------------------------- /src/components/Lowerbar.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import Step from "./Step"; 3 | import { Grid, Container } from 'semantic-ui-react' 4 | 5 | class Lowerbar extends Component { 6 | render() { 7 | return ( 8 | 9 | 10 | 11 | ) 12 | } 13 | } 14 | export default Lowerbar; -------------------------------------------------------------------------------- /src/components/Modal.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Button, Header, Icon, Image, Modal } from 'semantic-ui-react' 3 | 4 | const ModalScrollingExample = () => ( 5 | } closeIcon='close'> 6 | LiveAPI User Menu 7 | 8 | 9 |
Contextual Menu Option
10 |

User configuration, settings, options and views available here.

11 |
12 |
13 | 14 | 17 | 18 |
19 | ) 20 | 21 | export default ModalScrollingExample 22 | -------------------------------------------------------------------------------- /src/components/SegmentFive.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Button, Input, Segment, Popup, Icon, Link } from 'semantic-ui-react' 3 | 4 | // document.addEventListener('DOMContentLoaded', function () { 5 | // var links = document.getElementsByTagName("a"); 6 | // for (var i = 0; i < links.length; i++) { 7 | // (function () { 8 | // var ln = links[i]; 9 | // var location = ln.href; 10 | // ln.onclick = function () { 11 | // chrome.tabs.create({active: true, url: location}); 12 | // }; 13 | // })(); 14 | // } 15 | // }); 16 | 17 | 18 | class SegmentFive extends Component { 19 | constructor(props) { 20 | super(props); 21 | this.state = {}; 22 | 23 | this.handleChange = (event) => { 24 | const state = {}; 25 | state[event.target.name] = event.target.value; 26 | this.setState(state); 27 | } 28 | 29 | this.postEndpoint = (config, text, crawlUrl, postUrl) => { 30 | return new Promise((res, rej) => { 31 | let data = { 32 | url: crawlUrl, 33 | interval: config.interval, 34 | endpoint: config.endpoint, 35 | "text": text 36 | } 37 | let postURL = postUrl + '/crawls'; 38 | console.log('data', data); 39 | console.log('postUrl', postUrl); 40 | const xhr = new XMLHttpRequest(); 41 | xhr.onreadystatechange = function() { 42 | if (xhr.readyState == XMLHttpRequest.DONE) { 43 | // res(xhr.responseText); 44 | console.log("status", xhr.status) 45 | res(xhr.status); 46 | } 47 | } 48 | 49 | xhr.open("POST", postURL); 50 | xhr.setRequestHeader("Content-Type", "application/json"); 51 | xhr.send(JSON.stringify(data)); 52 | }) 53 | }; 54 | 55 | // is ASYNC issue keeping active step from incrementing correctly? <-NOTE 56 | this.handleSubmit = async() => { 57 | if (this.state.endpoint && this.state.interval) { 58 | this.setState({disabled: false}) 59 | try { 60 | // console.log(await this.postEndpoint(this.state, this.props.text, this.props.url)); 61 | let status = (await this.postEndpoint(this.state, this.props.text, this.props.crawlUrl, this.props.serverUrl)); 62 | if (status === 200){ 63 | this.setState({serverErr: false}); 64 | this.props.doneFunc(); 65 | } else { 66 | this.setState({serverErr: true}); 67 | } 68 | } catch(err) { 69 | console.log(err); 70 | } 71 | } else { 72 | this.setState({disabled: true}); 73 | } 74 | } 75 | 76 | } 77 | 78 | render() { 79 | return ( 80 | 81 | 82 | 83 | {(this.props.activeStep===5) ? : null} 84 | 85 | {(this.props.activeStep===5) ? : null} 86 | 87 | {(this.props.activeStep===5) ? } 89 | content='This endpoint name will be part of the route for your endpoint; please do not use spaces or special characters besides dashes (www.yourserver.com/crawls/). The interval is the amount of time you want to wait between scrapes measured in seconds.' 90 | wide 91 | /> : null} 92 | 93 | {(this.props.activeStep===5) ? : null} 94 | 95 | {(this.props.activeStep===6) ?
Success! Your endpoint was created at {this.props.serverUrl}/crawls/{this.state.endpoint} and will be updated every {this.state.interval} second(s).
: null} 96 | 97 | {(this.state.disabled) ?
Please enter both a name and an interval.
: null} 98 | 99 | {(this.state.serverErr) ?
The server encountered an error; no endpoint has been created. Please try again.
: null} 100 | 101 | {(this.props.activeStep===6) ? : null} 102 | 103 | {(this.props.activeStep===6) ? : null} 104 | 105 |
106 | ) 107 | } 108 | } 109 | 110 | export default SegmentFive -------------------------------------------------------------------------------- /src/components/SegmentFour.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Button, Input, Segment, Popup, Icon } from 'semantic-ui-react' 3 | 4 | class SegmentFour extends Component { 5 | constructor(props) { 6 | super(props); 7 | this.state = {}; 8 | 9 | 10 | this.handleChange = (event) => { 11 | const state = {}; 12 | state[event.target.name] = event.target.value; 13 | this.setState(state); 14 | } 15 | 16 | this.postCredentials = (cred) => { 17 | return new Promise( (resolve, reject) => { 18 | 19 | this.props.savePostURL(`https://${cred.address}`); 20 | const url = `https://${cred.address}/auth`; 21 | console.log('url', url); 22 | console.log('this.state', this.state); 23 | 24 | const xhr = new XMLHttpRequest(); 25 | 26 | // xhr.onreadystatechange = function() { 27 | // if (xhr.readyState == XMLHttpRequest.DONE) { 28 | // console.log(xhr.responseText); 29 | // } 30 | // } 31 | 32 | xhr.onreadystatechange = () => { 33 | if (xhr.readyState == XMLHttpRequest.DONE) { 34 | 35 | if (xhr.status === 200) { 36 | this.props.signIn(); 37 | console.log(xhr.responseText); 38 | } else { 39 | console.log(this.props.authed); 40 | console.log("The status was not 200, but: ", xhr.status); 41 | console.log(xhr.responseText); 42 | } 43 | resolve(); 44 | } 45 | } 46 | 47 | 48 | xhr.open("POST", url); 49 | xhr.setRequestHeader("Content-Type", "application/json"); 50 | xhr.send(JSON.stringify({username: cred.username, password: cred.password})); 51 | }); 52 | } 53 | 54 | this.handleSubmit = async (event) => { 55 | event.preventDefault(); 56 | await this.postCredentials(this.state); 57 | // this.props.authAttemptedFunc(); 58 | (this.props.authed) ? this.props.doneFunc() : this.props.authAttemptedFunc(); 59 | } 60 | 61 | } 62 | 63 | render() { 64 | return ( 65 | 66 | 67 | 68 | 69 | {(this.props.authed==false) ? : null} 70 | 71 | {(this.props.authed==false) ? : null} 72 | 73 | {(this.props.authed==false) ? : null} 74 | 75 | 76 | {(this.props.authed==false) ? } 78 | content='This is the URL for your LAS server. If you did not set this up yourself, ask your system administrator for this address.' 79 | /> : null} 80 | 81 | {((this.props.authed==false) && (this.props.authAttemptedNum>0)) ?
Authentication failed, please try again.
: null} 82 | 83 | {(this.props.authed==false) ? : null} 84 | 85 | {/* already signed in */} 86 | {(this.props.authed==true) ?
You are already signed in.
: null} 87 | 88 | {(this.props.authed==true) ? : null} 89 | 90 | {(this.props.authed==true) ? : null} 91 | 92 | 93 | 94 |
95 | ) 96 | } 97 | } 98 | 99 | export default SegmentFour 100 | 101 | -------------------------------------------------------------------------------- /src/components/SegmentOne.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Button, Input, Segment, Popup, Icon, Grid } from 'semantic-ui-react' 3 | 4 | class SegmentOne extends Component { 5 | 6 | constructor(props) { 7 | super(props); 8 | this.state = { 9 | errText: '', 10 | saveErrBool: false, 11 | errBool: false 12 | } 13 | 14 | this.nextSteps = () => { 15 | this.props.setCrawlUrl(window.location.href); 16 | (this.props.scrapePropBtnArr.length === 0) ? (this.setState({errBool: true}), this.setState({errText: "Please save at least one DOM element to scrape."})) : (this.setState({errBool: false}), this.props.doneFunc()); 17 | } 18 | 19 | this.saveSteps = (passedProperty) => { 20 | if (!passedProperty || this.props.selPropArr.length === 0) { 21 | this.setState({saveErrBool: true}); 22 | this.setState({errText: "To save a property name you need to enter a name in the input field and select at least one DOM element as a value."}) 23 | } else { 24 | this.setState({saveErrBool: false}); 25 | this.props.saveProperty(this.props.property); 26 | } 27 | 28 | } 29 | 30 | 31 | } 32 | 33 | 34 | render() { 35 | return ( 36 | 37 | 38 | 39 | 40 | 41 | 42 | this.props.getPropertyName(e)}/> 43 | 44 | 45 | 46 | 47 | } 49 | content='Once you click on the DOM element you want as a property in your object, name it and save it. Do this as many times as you need to for subsequent properties.' 50 | /> 51 | 52 | 53 | 54 | 55 |
{this.props.scrapePropBtnArr.map((el)=>
56 | 57 | {((this.props.scrapePropBtnArr.length===0 && this.state.errBool) || this.state.saveErrBool) ?
{this.state.errText}
: null} 58 | 59 | 60 | 61 |
62 | 63 | 64 | 65 | 66 |
67 | 68 | 69 |
70 | ) 71 | } 72 | } 73 | 74 | export default SegmentOne 75 | 76 | -------------------------------------------------------------------------------- /src/components/SendDialog.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { render } from 'react-dom'; 3 | import { Card, Input, Divider, Button, Form, Icon } from 'semantic-ui-react' 4 | 5 | class SendDefinitionDialog extends Component { 6 | constructor(props) { 7 | super(props); 8 | 9 | this.state = {}; 10 | 11 | this.handleChange = this.handleChange.bind(this); 12 | this.handleSubmit = this.handleSubmit.bind(this); 13 | } 14 | 15 | handleChange(event) { 16 | const state = {}; 17 | state[event.target.name] = event.target.value; 18 | this.setState(state); 19 | } 20 | 21 | handleSubmit(event) { 22 | this.props.submission(this.state); 23 | event.preventDefault(); 24 | } 25 | 26 | render() { 27 | return ( 28 | 29 | 30 | 31 | Create API Endpoint 32 | 33 | 34 | 35 | 36 |
37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 |
49 |
50 | ) 51 | } 52 | } 53 | 54 | export default SendDefinitionDialog; -------------------------------------------------------------------------------- /src/components/SendModal.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Button, Header, Icon, Image, Modal } from 'semantic-ui-react'; 3 | import SendDefinitionDialog from './SendDialog.jsx'; 4 | 5 | class SendDefinitionModal extends Component { 6 | postCredentials(endpoint) { 7 | const url = `http://${this.props.address}/crawls`; 8 | console.log(url); 9 | 10 | const xhr = new XMLHttpRequest(); 11 | 12 | xhr.onreadystatechange = function() { 13 | if (xhr.readyState == XMLHttpRequest.DONE) { 14 | console.log(xhr.responseText); 15 | } 16 | } 17 | 18 | xhr.open("POST", url); 19 | xhr.setRequestHeader("Content-Type", "application/json"); 20 | console.log(JSON.stringify(endpoint)); 21 | } 22 | 23 | render() { 24 | return( 25 | 26 | 27 | 28 | 29 | 30 | ); 31 | } 32 | } 33 | 34 | export default SendDefinitionModal; 35 | -------------------------------------------------------------------------------- /src/components/Step.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Grid, Step } from 'semantic-ui-react' 3 | 4 | // onClick: ((this.props.stepsCompleted.includes(2)) ? (()=>{this.props.jumpBack(2)}) : null), 5 | 6 | class StepExampleOrdered extends Component { 7 | render() { 8 | const steps = [ 9 | { onClick: ((this.props.stepsCompleted.includes(1)) ? (()=>{this.props.jumpBack(1)}) : null), active: ((this.props.activeStep===1)?true:false), link: (this.props.stepsCompleted.includes(1)?true:false), completed: ((this.props.stepsCompleted.includes(1))?true:false), title: 'Select DOM Elements', description: 'Click on DOM elements that you want as properties in your data object.' }, 10 | {disabled: true, active: ((this.props.activeStep===2)?true:false), completed: ((this.props.stepsCompleted.includes(2))?true:false), title: 'Select Details', description: 'Click on nested elements that you want as properties in your data object.' }, 11 | { disabled: true, active: ((this.props.activeStep===3)?true:false), completed: ((this.props.stepsCompleted.includes(3))?true:false), title: 'Identify Pagination Links', description: 'If you desire pagination, click on the "next" link.' }, 12 | {onClick: ((this.props.stepsCompleted.includes(4)) ? (()=>{this.props.jumpBack(4)}) : null), active: ((this.props.activeStep===4)?true:false), link: (this.props.stepsCompleted.includes(4)?true:false), completed: ((this.props.stepsCompleted.includes(4))?true:false), title: 'Authorization', description: 'Authorize yourself and serve your endpoint' }, 13 | {onClick: ((this.props.stepsCompleted.includes(5)) ? (()=>{this.props.jumpBack(5)}) : null), active: ((this.props.activeStep===5)?true:false), link: (this.props.stepsCompleted.includes(5)?true:false), completed: ((this.props.stepsCompleted.includes(5))?true:false), title: 'Create Endpoint', description: 'Authorize yourself and serve your endpoint' } 14 | ] 15 | return ( 16 | 17 | ) 18 | } 19 | } 20 | 21 | export default StepExampleOrdered 22 | 23 | 24 | 25 | {/* 26 | 27 | 28 | Select DOM Elements 29 | Click on elements that you want as properties in your data object. 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 |
*/} -------------------------------------------------------------------------------- /src/components/Toolbar.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import Modal from "./Modal"; 3 | import { Icon, Header, Button } from 'semantic-ui-react'; 4 | import $ from '../../jquery.js'; 5 | //let imgUrlArrow = chrome.extension.getURL("src/assets/arrow-right-b.png") 6 | //let imgUrlX = require("../assets/close.png") 7 | 8 | 9 | 10 | class Toolbar extends Component { 11 | // componentDidMount(){ 12 | // console.log("body with jquery", $('#lapiChromeExtensionContainer')); 13 | // } 14 | 15 | render() { 16 | return ( 17 |
18 | 19 | {/*
*/} 20 | 21 |
22 | 23 |
24 | 25 | {/* {(this.state.active) ? : null} */} 26 |
27 | 28 |
29 | 30 |
31 | 32 | 33 | 34 | 35 |
36 | ) 37 | } 38 | } 39 | export default Toolbar; -------------------------------------------------------------------------------- /src/configure-webpack.js: -------------------------------------------------------------------------------- 1 | __webpack_public_path__ = window.chrome.extension.getURL('')+'bundles/'; 2 | 3 | -------------------------------------------------------------------------------- /src/contentscript.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from "./components/App"; 4 | import "./configure-webpack.js"; 5 | import 'semantic-ui-css/semantic.css'; 6 | import $ from '../jquery.js'; 7 | 8 | //turn on main.js 9 | // chrome.browserAction.onClicked.addListener(function(tab) { 10 | // chrome.tabs.executeScript(null, {file: "main.js"}); 11 | // }); 12 | 13 | let existsAlready = !($('#lapiChromeExtensionContainer').length === 0); 14 | if (existsAlready!=true) { 15 | // chrome.tabs.executeScript(null, {file: "contentscript.js"}); 16 | 17 | // var url = chrome.extension.getURL('container.html'); 18 | //let height = "35px"; 19 | let height = "165px"; 20 | 21 | // var iframe = ""; 22 | 23 | var containerDiv = "
"; 24 | 25 | // var containerDivSpacer = "
"; 26 | 27 | 28 | // grab old body, place into targetBodyContainer 29 | // console.log("above clone", ($('#targetBodyContainer').length===0)) 30 | var allBody; 31 | if ($('#targetBodyContainer').length === 0) { 32 | allBody = $('body').clone(); 33 | } else if ($('#targetBodyContainer').length === 1){ 34 | allBody = $('#targetBodyContainer').clone(); 35 | } 36 | 37 | // remove contents of body 38 | $('body').addClass('liveAPI-body').empty(); 39 | 40 | // append chrome ext div 41 | $('body').append(containerDiv); 42 | 43 | 44 | if ($('#targetBodyContainer').length === 0) { 45 | //add targetBodyContainer to body, fill with variable containing old body 46 | var targetBodyContainer = "
"; 47 | $('body').append(targetBodyContainer); 48 | $('#targetBodyContainer').append(allBody); 49 | console.log("running from inside") 50 | } 51 | 52 | 53 | 54 | // $('body').css({ 55 | // '-ms-transform': 'translateY(165px)', 56 | // '-webkit-transform': 'translateY(165px)', 57 | // 'transform': 'translateY(165px)' 58 | // }); 59 | 60 | 61 | 62 | 63 | ReactDOM.render(, document.getElementById('lapiChromeExtensionContainer')); 64 | // console.log("2nd without bang", $('#lapiChromeExtensionContainer')); 65 | // console.log("2nd with bang", !!$('#lapiChromeExtensionContainer')); 66 | console.log("2nd without bang", $('#targetBodyContainer')); 67 | console.log("2nd with one bang", !$('#targetBodyContainer')); 68 | console.log("2nd with bang", !!$('#targetBodyContainer')); 69 | console.log("arr length", $('#targetBodyContainer').length); 70 | } 71 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | // // let isOn = ($('#lapiChromeExtensionContainer').length > 0); 2 | // // if (isOn) { 3 | 4 | // // Logic for getting the DOM Path for a selected HTML Element 5 | // // Finds the parents of the selected element 6 | // // Adds classes, id, and index 7 | 8 | // $.fn.fullSelector = function () { 9 | // // returns an array of DOM path 10 | // var path = this.parents().addBack(); 11 | // // add parents 12 | // // adds the child, reverses order of the parents (?) 13 | // var quickCss = path.get().map(function (item) { 14 | // // add class, id, index 15 | // var self = $(item), 16 | // id = item.id ? '#' + item.id : '', 17 | // // gets all the classes for an item, and chains them together 18 | // clss = item.classList.length ? item.classList.toString().split(' ').map(function (c) { 19 | // return '.' + c; 20 | // }).join('') : '', 21 | // name = item.nodeName.toLowerCase(), 22 | // index = self.siblings(name).length ? ':nth-child(' + (self.index() + 1) + ')' : ''; 23 | // // Check if the name is html or body, which are returned immediately 24 | // if (name === 'html' || name === 'body') { 25 | // return name; 26 | // } 27 | // // Other elements are returned with their index, id, and classes 28 | // return name + index + id + clss; 29 | // // Shows parent-child relationship 30 | // }).join('>'); 31 | // return quickCss; 32 | // }; 33 | 34 | // // $(document).on('mouseenter', '*', function(e) { 35 | // // // $(this).stop().animate({'border': '3px solid yellow'}); 36 | // // $(this).addClass('liveAPI-border-yellow'); 37 | // // e.stopImmediatePropagation(); 38 | // // }) 39 | 40 | // // $(document).on('mouseleave', '*', function(e) { 41 | // // $(this).removeClass('liveAPI-border-yellow'); 42 | // // // $(this).stop().animate('border', 'none'); 43 | // // e.stopImmediatePropagation(); 44 | // // }) 45 | 46 | // // click on part of the page to see the CSS selector 47 | // $(document).on('click','*', function(){ 48 | // console.log('DOM Path', $(this).fullSelector() ); 49 | // return false; 50 | // }); 51 | 52 | // /* Avoid selection of parent elements 53 | // - select div elements that do not have child divs 54 | // - extract a, li elements 55 | 56 | // Select all elements w/ the same path 57 | // - upon x, remove extra elements selected 58 | // */ 59 | 60 | // // Highlighting functionality for a selected HTML element 61 | // // Create a div "overlay" that contains original text and exit button 62 | 63 | // $(document).on('click', '.liveAPI-highlight', function(e) { 64 | // e.stopImmediatePropagation(); 65 | // }); 66 | 67 | // $(document).on('click', '.liveAPI-highlight-wrapper', function(e) { 68 | // e.stopImmediatePropagation(); 69 | // }); 70 | 71 | // $(document).on('click', '.liveAPI-highlight-button', function(e) { 72 | // $(this).parent().remove(); 73 | // // prevents other listeners of the same event from being called 74 | // e.stopImmediatePropagation(); 75 | // }) 76 | 77 | // // #lapiChromeExtensionContainer 78 | 79 | // $(document).on('click', '*', function() { 80 | // let children = $(this).children().map((i, ele) => { 81 | // return ele.nodeName.toLowerCase(); 82 | // }).get(); 83 | // let path = $(this).parents().addBack().get().map((ele, i) => { 84 | // return ele.id; 85 | // }) 86 | // if ($(this)[0].nodeName.toLowerCase() === 'div' && children.includes('div')) return false; 87 | // if (path.includes('lapiChromeExtensionContainer')) return false; 88 | // let styles = $(this).css([ 89 | // "width", "height", "font-size", "font-weight", "font-family", "font-variant", "font-stretch", "line-height", "text-transform", "text-align", "padding-top", "padding-bottom", "padding-left", "padding-right", "letter-spacing"] 90 | // ); 91 | 92 | // const position = cumulativeOffset(this); 93 | // $('#lapiChromeExtensionContainer').append( 94 | // $('
') 95 | // .offset({top: position.top, left: position.left}) 96 | 97 | // // Assign div element the CSS properties of the HTML Element 98 | // .css({"font-size": styles["font-size"], "font-family": styles["font-family"], "font-variant": styles["font-variant"], "font-stretch": styles["font-stretch"], "line-height": styles["line-height"], "text-transform": styles["text-transform"], "text-align": styles["text-align"], "letter-spacing": styles["letter-spacing"]}) 99 | 100 | // // Add highlight and ignore classes 101 | // .addClass('liveAPI-highlight liveAPI-yellow liveAPI-ignore') 102 | // .append( 103 | // $('
') 104 | // .addClass('liveAPI-highlight-wrapper liveAPI-ignore') 105 | // .css({ 106 | // "max-width": styles["width"], "height": styles["height"],"padding-right": styles["padding-right"] 107 | // }) 108 | // // .text(cleanWhiteSpace($(this).getText())) 109 | // .text(cleanWhiteSpace($(this).text())) 110 | // ) 111 | // .append( 112 | // $('') 113 | // .addClass('liveAPI-highlight-button') 114 | // .text('x') 115 | // ) 116 | // ); 117 | // // console.log(cleanWhiteSpace($(this).text())); 118 | // }); 119 | 120 | // // Remove white space when creating div element 121 | // function cleanWhiteSpace(text) { 122 | // // Remove whitespace before or after text 123 | // let revisedText = text.replace(/^\s+|\s+$/, ""); 124 | // // Remove extra spaces between words 125 | // revisedText = revisedText.replace(/\s\s+/g, " "); 126 | // return revisedText; 127 | // } 128 | 129 | // // $.fn.getText = function() { 130 | // // let text = this.text(); 131 | // // let childLength = this.children().text().length; 132 | // // // console.log('childLength', childLength); 133 | // // // console.log('slice', text.slice(0, text.length - childLength)); 134 | // // // console.log('finished'); 135 | // // return text.slice(0, text.length - childLength); 136 | // // } 137 | 138 | // function cumulativeOffset(element) { 139 | // let top = 0 140 | // let left = 0; 141 | // do { 142 | // top += element.offsetTop || 0; 143 | // left += element.offsetLeft || 0; 144 | // element = element.offsetParent; 145 | // } while (element); 146 | 147 | // return { 148 | // top: top + 165, 149 | // left: left 150 | // }; 151 | // }; 152 | 153 | // /* 154 | // https://stackoverflow.com/questions/10619445/the-preferred-way-of-creating-a-new-element-with-jquery 155 | 156 | // https://stackoverflow.com/questions/11634770/get-position-offset-of-element-relative-to-a-parent-container 157 | // */ 158 | // // } 159 | 160 | 161 | 162 | // // Create Event Listeners for Different Element Types 163 | // // Add Helper Functions 164 | // // Bind (this), and execute -------------------------------------------------------------------------------- /style.css: -------------------------------------------------------------------------------- 1 | #lapiChromeExtensionContainer { 2 | /* -webkit-transform: translateZ(0); 3 | -webkit-transform: translateY(0); */ 4 | position: fixed; 5 | height: 165px; 6 | /* top: -165px; */ 7 | top: 0px; 8 | left: 0px; 9 | width: 100%; 10 | z-index: 2147483647; 11 | /* background: #dde084; 12 | color: black; */ 13 | } 14 | 15 | #targetBodyContainer{ 16 | /* position: absolute; */ 17 | /* top: 165px; */ 18 | -ms-transform: translateY(230px); 19 | -webkit-transform: translateY(230px); 20 | transform: translateY(230px) 21 | } 22 | 23 | 24 | /* #lapiChromeExtensionContainerSpacer { 25 | position: absolute; 26 | height: 165px; 27 | top: 0px; 28 | left: 0px; 29 | width: 100%; 30 | background: #dde084; 31 | 32 | } */ 33 | 34 | .redText { 35 | background: blue; 36 | opacity: .5; 37 | color: red; 38 | height: 100px; 39 | } 40 | 41 | .constWidth { 42 | padding-left: 10px; 43 | padding-right: 10px; 44 | width: 100%; 45 | height: 160px; 46 | z-index: 2000; 47 | /* Permalink - use to edit and share this gradient: http://colorzilla.com/gradient-editor/#266d26+0,cbebff+100&1+0,0+100 */ 48 | background: -moz-linear-gradient(top, rgb(0, 168, 47) 0%, rgba(203,235,255,0) 100%); /* FF3.6-15 */ 49 | background: -webkit-linear-gradient(top, rgb(0, 168, 47) 0%,rgba(203,235,255,0) 100%); /* Chrome10-25,Safari5.1-6 */ 50 | background: linear-gradient(to bottom, rgb(0, 168, 47) 0%,rgba(203,235,255,0) 100%); /* W3C, IE10+, FF16+, Chrome26+, Opera12+, Safari7+ */ 51 | /* filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#266d26', endColorstr='#00cbebff',GradientType=0 ); IE6-9 */ 52 | } 53 | 54 | #lapiChromeExtensionContainer > div > div > div.ui.container.constWidth { 55 | width: 100%; 56 | } 57 | 58 | div.step { 59 | width: 20%; 60 | } 61 | 62 | /* step link contract */ 63 | #lapiChromeExtensionContainer > div > div > div.ui.container.constWidth > div > a { 64 | width: 20%; 65 | } 66 | 67 | div>.step>.content { 68 | margin-top: 20px; 69 | } 70 | 71 | .tb { 72 | padding-top: 6px; 73 | padding-left: 10px; 74 | padding-right: 10px; 75 | width: 100%; 76 | height: 35px; 77 | background-color: #00a82f; 78 | color: white; 79 | font-size: 1.5em; 80 | } 81 | 82 | .ui.tertiary.segment { 83 | height: 30px; 84 | } 85 | 86 | #leftMenu { 87 | float: left; 88 | /* width: 35px; 89 | height: 35px; */ 90 | } 91 | 92 | #rightMenu { 93 | float: right; 94 | width: 70px; 95 | /* height: 35px; */ 96 | } 97 | 98 | .tb>#leftMenu>img { 99 | -webkit-filter: invert(1); 100 | filter: invert(1); 101 | } 102 | 103 | .tb>#rightMenu>img { 104 | -webkit-filter: invert(1); 105 | filter: invert(1); 106 | } 107 | 108 | .titleDiv { 109 | padding-top: 6px; 110 | float: left; 111 | width: 200px; 112 | padding-left: 12px; 113 | } 114 | 115 | #lapiChromeExtensionContainer > div > div > div.tb > h2 > i { 116 | font-size: 1em; 117 | } 118 | 119 | .rightHeader { 120 | float: right; 121 | } 122 | 123 | .leftHeader { 124 | float: left; 125 | } 126 | 127 | #lapiChromeExtensionContainer > div > div > div.tb > div.leftHeader > h3 { 128 | display: inline; 129 | background-color: inherit; 130 | } 131 | 132 | 133 | div.ui.header { 134 | background-color: inherit; 135 | } 136 | 137 | 138 | 139 | /* Parent div */ 140 | .liveAPI-highlight { 141 | position: absolute; 142 | box-sizing: border-box; 143 | border-radius: 2px; 144 | display: inline-block; 145 | white-space: pre-wrap; 146 | padding: 2px 6px; 147 | z-index: 2147483646; 148 | } 149 | 150 | .liveAPI-yellow { 151 | background-color: yellow; 152 | } 153 | 154 | /* Child div */ 155 | .liveAPI-highlight-wrapper { 156 | display: inline-block; 157 | position: relative; 158 | } 159 | 160 | .liveAPI-border-yellow { 161 | outline: 3px solid yellow; 162 | 163 | } 164 | 165 | /* #lapiChromeExtensionContainer > div > div > div.ui.raised.segment */ 166 | div.ui.raised.segment { 167 | margin-top: -36px; 168 | margin-right: 10px; 169 | margin-left: 10px; 170 | z-index: 2500; 171 | } 172 | 173 | /* #lapiChromeExtensionContainer > div > div > div.ui.raised.segment > button { 174 | margin-left: 10px; 175 | } */ 176 | 177 | 178 | .marginRightTen { 179 | margin-right: 10px; 180 | } 181 | 182 | .alertText { 183 | display: inline; 184 | color: red; 185 | margin-left: 20px; 186 | } 187 | 188 | 189 | .successText { 190 | color: #00a82f; 191 | font-weight: bold; 192 | float: left; 193 | margin-top: .6em; 194 | margin-left: 20px; 195 | } 196 | 197 | body > div.ui.page.modals.dimmer.transition.visible.active { 198 | /* transform: translateY(-165px); */ 199 | z-index: 6000; 200 | 201 | } 202 | 203 | /* div.ui.top.left.basic.inverted.popup.transition.visible */ 204 | div.ui.popup.transition.visible { 205 | z-index: 5100; 206 | } 207 | 208 | /* .floatLeft { 209 | float: left; 210 | } */ 211 | 212 | 213 | .alertTextGrid { 214 | display: inline; 215 | color: red; 216 | margin-left: 10px; 217 | position: relative; 218 | top: 8px; 219 | } 220 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | 4 | module.exports = { 5 | entry: { 6 | // Components for '/config' route 7 | semantic: path.join(__dirname, 'src/configure-webpack.js'), 8 | background: path.join(__dirname, 'src/background.js'), 9 | contentscripts: path.join(__dirname, 'src/contentscript.js'), 10 | }, 11 | output: { 12 | path: path.join(__dirname, 'bundles'), 13 | filename: '[name].bundle.js' 14 | }, 15 | module: { 16 | loaders: [ 17 | { 18 | test: /\.(eot|png|svg|[ot]tf|woff2?)(\?v=\d+\.\d+\.\d+)?$/, 19 | loader: 'url-loader', 20 | query: {limit: 10000} 21 | }, 22 | { 23 | test: /\.css$/, 24 | loaders: ['style-loader', 'css-loader'] }, 25 | { 26 | test: /.jsx?$/, 27 | loader: 'babel-loader', 28 | include: path.join(__dirname, 'src'), 29 | exclude: /node_modules/, 30 | query: { 31 | presets: [ 32 | 'react', 33 | ['env', { 34 | "modules": false, 35 | "targets": { 36 | "browsers": ["last 2 Chrome versions"] 37 | } 38 | }] 39 | ], 40 | 'plugins': [], 41 | } 42 | } 43 | ] 44 | }, 45 | }; --------------------------------------------------------------------------------