├── .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 |
38 | Address
39 |
40 |
41 |
42 | Username
43 |
44 |
45 |
46 | Password
47 |
48 |
49 |
50 | Register
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 |
10 | User configuration, settings, options and views available here.
11 |
12 |
13 |
14 |
15 | Proceed
16 |
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) ? Create Endpoint : 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) ? Initiate new crawl : null}
102 |
103 | {(this.props.activeStep===6) ? {window.open(`${this.props.serverUrl}/crawls/${this.state.endpoint}`)}}>Go to endpoint : 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) ? Login : null}
84 |
85 | {/* already signed in */}
86 | {(this.props.authed==true) ? You are already signed in.
: null}
87 |
88 | {(this.props.authed==true) ? Continue : null}
89 |
90 | {(this.props.authed==true) ? Logout : 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 | this.saveSteps(this.props.property)}>Save
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)=>this.props.removeProperty(e, 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 | Done
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 |
38 | Endpoint Name
39 |
40 |
41 |
42 | Interval (in seconds)
43 |
44 |
45 |
46 | Create Endpoint
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 |
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 | };
--------------------------------------------------------------------------------