├── .eslintrc.json ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── lerna.json ├── package-lock.json ├── package.json ├── packages ├── admin │ ├── @types │ │ ├── global.d.ts │ │ ├── uniform.d.ts │ │ ├── uniforms-bootstrap4.d.ts │ │ └── uniforms-bridge-json-schema.d.ts │ ├── LICENSE │ ├── README.md │ ├── package.json │ ├── rollup.config.js │ ├── src │ │ ├── html │ │ │ └── admin.html │ │ ├── images │ │ │ └── gsf-logo.png │ │ ├── scss │ │ │ ├── admin.scss │ │ │ └── custom-variables.scss │ │ └── ts │ │ │ ├── admin.ts │ │ │ ├── components │ │ │ ├── Dropdown.tsx │ │ │ ├── GsfClient.tsx │ │ │ ├── Modal.tsx │ │ │ ├── Table.tsx │ │ │ └── uniforms │ │ │ │ ├── GsfForm.tsx │ │ │ │ ├── bridge │ │ │ │ ├── GsfBridge.tsx │ │ │ │ └── GsfBridgeHelper.ts │ │ │ │ └── fields │ │ │ │ ├── GsfAutoField.tsx │ │ │ │ ├── GsfAutoFields.tsx │ │ │ │ ├── GsfBoolField.tsx │ │ │ │ ├── GsfNestField.tsx │ │ │ │ ├── GsfSelectField.tsx │ │ │ │ ├── custom │ │ │ │ ├── CodeEditorField.tsx │ │ │ │ ├── PluginDefinitionList.tsx │ │ │ │ ├── ScenarioDescriptionField.tsx │ │ │ │ └── ScenarioLinkField.tsx │ │ │ │ └── gsfConnectField.ts │ │ │ ├── index.d.ts │ │ │ ├── layout │ │ │ ├── Navigation.tsx │ │ │ └── Page.tsx │ │ │ └── pages │ │ │ ├── Admin.tsx │ │ │ ├── help │ │ │ └── Help.tsx │ │ │ ├── logs │ │ │ └── LogList.tsx │ │ │ ├── plugins │ │ │ ├── PluginDetail.tsx │ │ │ ├── PluginList.tsx │ │ │ └── model │ │ │ │ └── plugin-schema.json │ │ │ ├── projects │ │ │ ├── ProjectDetail.tsx │ │ │ ├── ProjectList.tsx │ │ │ ├── ProjectResults.tsx │ │ │ ├── model │ │ │ │ ├── PluginHelper.ts │ │ │ │ └── UIProject.ts │ │ │ └── schema │ │ │ │ ├── plugin-definition-schema.json │ │ │ │ └── project-base-schema.json │ │ │ ├── scenarios │ │ │ ├── ScenarioList.tsx │ │ │ └── model │ │ │ │ └── ScenarioHelper.ts │ │ │ ├── settings │ │ │ ├── DatabaseOperations.tsx │ │ │ └── SettingList.tsx │ │ │ ├── sites │ │ │ ├── SiteDetail.tsx │ │ │ ├── SiteDetailPlugins.tsx │ │ │ ├── SiteList.tsx │ │ │ ├── SitePluginDefinitions.tsx │ │ │ └── model │ │ │ │ ├── Resource.ts │ │ │ │ └── Site.ts │ │ │ └── start │ │ │ ├── Examples.tsx │ │ │ └── GettingStarted.tsx │ └── tsconfig.json ├── background │ ├── LICENSE │ ├── README.md │ ├── package.json │ ├── rollup.config.js │ ├── src │ │ └── ts │ │ │ ├── background-main.ts │ │ │ ├── helpers │ │ │ ├── ActiveTabHelper.ts │ │ │ ├── DeepMergeHelper.ts │ │ │ ├── ExportHelper.ts │ │ │ ├── JsonUrlHelper.ts │ │ │ └── MimeTypes.json │ │ │ ├── interfaces.d.ts │ │ │ ├── logger │ │ │ └── Logger.ts │ │ │ ├── plugins │ │ │ ├── ModuleNpmManager.ts │ │ │ ├── ModuleRuntimeManager.ts │ │ │ ├── ModuleStorageManager.ts │ │ │ └── builtin │ │ │ │ ├── DynamicNavigationPlugin.ts │ │ │ │ ├── ExtractHtmlContentPlugin.ts │ │ │ │ ├── ExtractTitlePlugin.ts │ │ │ │ ├── ExtractUrlsPlugin.ts │ │ │ │ ├── FetchPlugin.ts │ │ │ │ ├── InsertResourcesPlugin.ts │ │ │ │ ├── ScrollPlugin.ts │ │ │ │ ├── SelectResourcePlugin.ts │ │ │ │ └── UpsertResourcePlugin.ts │ │ │ └── storage │ │ │ ├── GsfProvider.ts │ │ │ ├── IdbLog.ts │ │ │ ├── IdbPlugin.ts │ │ │ ├── IdbProject.ts │ │ │ ├── IdbResource.ts │ │ │ ├── IdbScenario.ts │ │ │ ├── IdbSetting.ts │ │ │ ├── IdbSite.ts │ │ │ └── IdbStorage.ts │ ├── test │ │ ├── mocha.opts │ │ ├── resources │ │ │ └── scenarios │ │ │ │ ├── gsf-readme.md │ │ │ │ ├── scenario-a-package.json │ │ │ │ ├── scenario-a.tgz │ │ │ │ └── scenario-b-package.json │ │ ├── tsconfig.test.json │ │ ├── unit │ │ │ ├── plugins │ │ │ │ ├── test-base-plugin.ts │ │ │ │ ├── test-extract-html-content.ts │ │ │ │ ├── test-extract-urls-plugin.ts │ │ │ │ └── test-insert-resources-plugin.ts │ │ │ ├── test-deep-merge-helper.ts │ │ │ ├── test-export-helper.ts │ │ │ ├── test-json-url-helper.ts │ │ │ ├── test-module-npm-manager.ts │ │ │ ├── test-module-runtime-manager.ts │ │ │ ├── test-project-crud.ts │ │ │ ├── test-resource-crawl.ts │ │ │ ├── test-resource-crud.ts │ │ │ ├── test-scenario-crud.ts │ │ │ ├── test-site-crud.ts │ │ │ ├── test-site-dynamic-crawl.ts │ │ │ ├── test-site-static-crawl.ts │ │ │ ├── test-storage.ts │ │ │ └── test-user-plugin-crud.ts │ │ └── utils │ │ │ ├── ModuleHelper.ts │ │ │ ├── shims.ts │ │ │ └── ts-node-config.js │ ├── tsconfig.json │ └── typings │ │ ├── get-set-fetch │ │ └── index.d.ts │ │ └── global.d.ts ├── commons │ ├── README.md │ ├── lib │ │ ├── index.d.ts │ │ ├── index.ts │ │ ├── log.d.ts │ │ ├── npm.d.ts │ │ ├── plugin.ts │ │ ├── project.d.ts │ │ ├── resource.d.ts │ │ ├── scenario.d.ts │ │ ├── schema │ │ │ └── SchemaHelper.ts │ │ ├── setting.d.ts │ │ ├── site.d.ts │ │ ├── storage.d.ts │ │ └── table.d.ts │ ├── package.json │ ├── rollup.config.js │ ├── test │ │ ├── mocha.opts │ │ ├── test-schema-helper.ts │ │ └── ts-node-config.js │ └── tsconfig.json ├── extension │ ├── README.md │ ├── package.json │ ├── scripts │ │ ├── fix-firefox-addons-linter.ts │ │ ├── generate-file-list.ts │ │ ├── generate-firefox-policies.ts │ │ ├── generate-keys.ts │ │ ├── generate-tgz-npm-pkg.ts │ │ └── version-update.js │ ├── src │ │ └── resources │ │ │ ├── favicon.ico │ │ │ ├── icons │ │ │ ├── gsf_128x128.png │ │ │ ├── gsf_16x16.png │ │ │ ├── gsf_32x32.png │ │ │ └── gsf_48x48.png │ │ │ └── manifest.json │ ├── test │ │ ├── init │ │ │ ├── init-proxy.ts │ │ │ └── init-ts-node.js │ │ ├── integration │ │ │ └── admin │ │ │ │ ├── install │ │ │ │ └── test-thankyou-page.ts │ │ │ │ ├── logs │ │ │ │ └── test-logs-page.ts │ │ │ │ ├── plugin │ │ │ │ └── test-user-plugin-crud-pages.ts │ │ │ │ ├── project │ │ │ │ ├── test-project-crawl-extract-dynamic-content.ts │ │ │ │ ├── test-project-crawl-extract-html-headings.ts │ │ │ │ ├── test-project-crawl-extract-resources.ts │ │ │ │ ├── test-project-crawl-extract-static-content-custom-properties.ts │ │ │ │ ├── test-project-crawl-extract-static-content-infinite-scrolling.ts │ │ │ │ ├── test-project-crawl-extract-static-content-js-links.ts │ │ │ │ ├── test-project-crawl-extract-static-content-timer.ts │ │ │ │ ├── test-project-crawl-extract-static-content.ts │ │ │ │ ├── test-project-crawl-extract-static-incomplete-content.ts │ │ │ │ ├── test-project-crawl-resume.ts │ │ │ │ ├── test-project-crud-extract-resources.ts │ │ │ │ └── test-project-dynamic-schema.ts │ │ │ │ ├── scenario │ │ │ │ └── test-scenario-list.ts │ │ │ │ ├── settings │ │ │ │ └── test-settings-update-page.ts │ │ │ │ └── storage │ │ │ │ └── test-export-import.ts │ │ ├── mocha.opts │ │ ├── resources │ │ │ ├── firefox │ │ │ │ └── profile │ │ │ │ │ └── .gitkeep │ │ │ ├── security │ │ │ │ ├── ca │ │ │ │ │ └── .gitkeep │ │ │ │ └── web │ │ │ │ │ └── .gitkeep │ │ │ └── sites │ │ │ │ ├── raw.githubusercontent.com │ │ │ │ └── get-set-fetch │ │ │ │ │ └── extension │ │ │ │ │ └── master │ │ │ │ │ └── README.md │ │ │ │ ├── registry.npmjs.org │ │ │ │ └── extract-html-headings │ │ │ │ │ ├── - │ │ │ │ │ └── package │ │ │ │ │ │ └── dist │ │ │ │ │ │ └── ExtractHtmlHeadings.js │ │ │ │ │ └── 0.1.5 │ │ │ │ └── sitea.com │ │ │ │ ├── dynamic │ │ │ │ ├── articles │ │ │ │ │ ├── articleA.html │ │ │ │ │ ├── articleB.html │ │ │ │ │ ├── articleC.html │ │ │ │ │ ├── articleD.html │ │ │ │ │ └── dynamic-page-article-list-detail-different-urls.html │ │ │ │ └── products │ │ │ │ │ ├── dynamic-page-advanced-product-list-detail-different-dom-preloader.html │ │ │ │ │ ├── dynamic-page-advanced-product-list-detail-different-dom.html │ │ │ │ │ └── dynamic-page-product-list-detail-same-dom.html │ │ │ │ ├── favicon.ico │ │ │ │ ├── img │ │ │ │ ├── imgA-150.png │ │ │ │ └── imgB-850.png │ │ │ │ ├── index-js-links.html │ │ │ │ ├── index.html │ │ │ │ ├── pdf │ │ │ │ ├── pdfA-150.pdf │ │ │ │ └── pdfB-850.pdf │ │ │ │ ├── redirect │ │ │ │ ├── main.html │ │ │ │ ├── pageA.html │ │ │ │ └── pageB.html │ │ │ │ └── static │ │ │ │ ├── pageA.html │ │ │ │ ├── pageB-js-links.html │ │ │ │ ├── pageB.html │ │ │ │ ├── pageC.html │ │ │ │ ├── pageD.html │ │ │ │ ├── table.html │ │ │ │ └── timer.html │ │ ├── tmp │ │ │ └── .gitkeep │ │ └── tsconfig.test.json │ ├── tsconfig.json │ └── typings │ │ └── global.d.ts ├── scenarios │ ├── extract-resources │ │ ├── .gitignore │ │ ├── README.md │ │ ├── package.json │ │ ├── rollup.config.js │ │ ├── src │ │ │ └── ts │ │ │ │ ├── ExtractResources.ts │ │ │ │ └── plugins │ │ │ │ └── ImageFilterPlugin.ts │ │ └── tsconfig.json │ ├── scrape-dynamic-content │ │ ├── README.md │ │ ├── package.json │ │ ├── rollup.config.js │ │ ├── src │ │ │ └── ts │ │ │ │ └── ScrapeDynamicContent.ts │ │ └── tsconfig.json │ └── scrape-static-content │ │ ├── README.md │ │ ├── package.json │ │ ├── rollup.config.js │ │ ├── src │ │ └── ts │ │ │ └── ScrapeStaticContent.ts │ │ └── tsconfig.json └── test-utils │ ├── lib │ ├── helper │ │ ├── CertGenerator.ts │ │ ├── CrawlHelper.ts │ │ ├── FileHelper.ts │ │ ├── ProjectHelper.ts │ │ ├── ProxyServer.ts │ │ ├── ScenarioHelper.ts │ │ ├── TgzHelper.ts │ │ └── browser │ │ │ ├── BrowserHelper.ts │ │ │ ├── ChromeHelper.ts │ │ │ └── FirefoxHelper.ts │ ├── index.ts │ └── test │ │ └── crawl-project-base-suite.ts │ ├── package.json │ └── tsconfig.json ├── patches ├── get-set-fetch+0.3.8.patch ├── uniforms-bootstrap4+2.6.7.patch └── untar.js+0.3.0.patch └── scripts ├── fetch-browser.js └── symlink.js /.gitignore: -------------------------------------------------------------------------------- 1 | # scrape results 2 | _results/ 3 | 4 | # generated resources 5 | dist/ 6 | !/extract-html-headings/-/package/dist 7 | keys/ 8 | 9 | # Logs 10 | *.log 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | 15 | # Runtime data 16 | pids 17 | *.pid 18 | *.seed 19 | *.pid.lock 20 | 21 | # Directory for instrumented libs generated by jscoverage/JSCover 22 | lib-cov 23 | 24 | # Coverage directory used by tools like istanbul 25 | coverage 26 | 27 | # nyc test coverage 28 | .nyc_output 29 | 30 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 31 | .grunt 32 | 33 | # Bower dependency directory (https://bower.io/) 34 | bower_components 35 | 36 | # node-waf configuration 37 | .lock-wscript 38 | 39 | # Compiled binary addons (https://nodejs.org/api/addons.html) 40 | build/Release 41 | 42 | # Dependency directories 43 | node_modules/ 44 | node_modules 45 | jspm_packages/ 46 | 47 | 48 | # Optional npm cache directory 49 | .npm 50 | 51 | # Optional eslint cache 52 | .eslintcache 53 | 54 | # Optional REPL history 55 | .node_repl_history 56 | 57 | # Output of 'npm pack' 58 | *.tgz 59 | 60 | # Yarn Integrity file 61 | .yarn-integrity 62 | 63 | # dotenv environment variables file 64 | .env 65 | 66 | # next.js build output 67 | .next 68 | 69 | # vscode files 70 | .vscode 71 | 72 | # todo files 73 | todo* 74 | 75 | # comment files 76 | *.txt 77 | 78 | # generated files 79 | *.tar 80 | 81 | # cert and keys 82 | *.pem 83 | *.p12 84 | 85 | # firefox profile 86 | **/firefox/* 87 | 88 | # linked eslintrc files 89 | packages/**/.eslintrc.json -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "10" 4 | dist: xenial 5 | sudo: required 6 | services: 7 | - xvfb 8 | install: 9 | - npm install --global rollup 10 | - npm ci 11 | script: 12 | - npm run fetch:firefox 13 | - npm run build 14 | - npm run test 15 | after_script: 16 | # - npm run test:report 17 | - npm run clean -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 get-set-fetch 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | [![FOSSA Status](https://app.fossa.io/api/projects/git%2Bgithub.com%2Fget-set-fetch%2Fget-set-fetch.svg?type=shield)](https://app.fossa.io/projects/git%2Bgithub.com%2Fget-set-fetch%2Fget-set-fetch?ref=badge_shield) 5 | [![dependencies Status](https://david-dm.org/get-set-fetch/extension/status.svg)](https://david-dm.org/get-set-fetch/extension) 6 | [![Known Vulnerabilities](https://snyk.io/test/github/get-set-fetch/extension/badge.svg?targetFile=package.json)](https://snyk.io/test/github/get-set-fetch/extension?targetFile=package.json) 7 | [![Build Status](https://travis-ci.org/get-set-fetch/extension.svg?branch=master)](https://travis-ci.org/get-set-fetch/extension) 8 | [![Coverage Status](https://coveralls.io/repos/github/get-set-fetch/extension/badge.svg?branch=master)](https://coveralls.io/github/get-set-fetch/extension?branch=master) 9 | 10 | # Browser Extension 11 | get-set, Fetch! is a browser extension for scraping sites through out a series of parametrizable scraping scenarios. 12 | 13 | Currently supported browsers: 14 | [Chrome](https://chrome.google.com/webstore/detail/get-set-fetch-web-scraper/obanemoliijohdnhjjkdbekbhdjeolnk), 15 | [Firefox](https://addons.mozilla.org/en-US/firefox/addon/get-set-fetch-web-scraper/), 16 | [Edge](https://microsoftedge.microsoft.com/addons/detail/getset-fetch-web-scrap/bpoeflbhbglemehjccjfockpkhddppoh). 17 | 18 | The most common use cases are handled by builtin scenarios: 19 | - [Scrape Static Content](https://github.com/get-set-fetch/extension/tree/master/packages/scenarios/scrape-static-content) 20 | - Extracts text and binary content from static html pages based on CSS selectors. 21 | - [Scrape Dynamic Content](https://github.com/get-set-fetch/extension/tree/master/packages/scenarios/scrape-dynamic-content) 22 | - Extracts text and binary content from dynamic (javascript) pages based on CSS selectors. 23 | 24 | You can also install community based scenarios: 25 | 26 | - [Extract Html Headings](https://github.com/a1sabau/gsf-extension-extract-html-headings) - [v0.2.0](https://registry.npmjs.org/gsf-extension-extract-html-headings/0.2.0) 27 | - "Hello World" example of writing a scrape scenario. 28 | - [Extract Article Content](https://github.com/a1sabau/gsf-extension-readability/) - [v0.2.0](https://registry.npmjs.org/gsf-extension-readability//0.2.0) 29 | - Extract article content using Mozilla Readability library. 30 | 31 | 32 | If you wrote a scraping scenario and want to share it, please update the above list and make a pull request. 33 | 34 | The extension is structured as a monorepo with the following sub-packages: 35 | - commons: mostly typescript definitions 36 | - background: parses pages and stores relevant data in the builtin browser database (IndexedDB) 37 | - popup: toolbar appearance 38 | - admin: front-end for the background capabilities 39 | - scrape-static-content: builtin scenario 40 | - scrape-dynamic-content: builtin scenario 41 | - extension: builds the extension files and runs a comprehensive suite of integration tests 42 | 43 | You can find technical tidbits in each sub-package readme file. 44 | 45 | A detailed documentation with a series of examples is available at [getsetfetch.org](https://getsetfetch.org/extension/getting-started.html). 46 | 47 | 48 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.4.1", 3 | "command": { 4 | "version": { 5 | "message": "version update: %s" 6 | }, 7 | "publish": { 8 | "ignoreChanges": [ 9 | "*.md" 10 | ] 11 | }, 12 | "bootstrap": { 13 | "ignore": "component-*", 14 | "npmClientArgs": [] 15 | } 16 | }, 17 | "packages": [ 18 | "packages/*", 19 | "packages/scenarios/**/*" 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /packages/admin/@types/global.d.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line @typescript-eslint/interface-name-prefix 2 | interface Window { 3 | GsfClient; 4 | } 5 | 6 | declare module '*.png' { 7 | const value; 8 | export = value; 9 | } 10 | -------------------------------------------------------------------------------- /packages/admin/@types/uniform.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'uniforms' { 2 | interface IAutoFormProps { 3 | schema: any; 4 | onSubmit: any; 5 | model: any; 6 | validate: any; 7 | showInlineError: any; 8 | validator: any; 9 | grid: any; 10 | modelTransform: any; 11 | } 12 | 13 | interface IAutoFieldProps { 14 | id?: string; 15 | name?: string; 16 | value?: any; 17 | autoField?: any; 18 | element?: string; 19 | omitFields?: any[]; 20 | } 21 | 22 | export class BaseField extends React.Component { 23 | static displayName: string; 24 | static contextTypes: any; 25 | 26 | getFieldProps(name?, options?): any; 27 | getChildContextName(): any; 28 | } 29 | } -------------------------------------------------------------------------------- /packages/admin/@types/uniforms-bootstrap4.d.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | declare module 'uniforms-bootstrap4' { 3 | import * as React from 'react'; 4 | 5 | interface IAutoFormProps { 6 | schema: any; 7 | onSubmit: any; 8 | onChange?: any; 9 | model: any; 10 | validate: any; 11 | showInlineError: any; 12 | validator: any; 13 | grid?: any; 14 | modelTransform?: any; 15 | onChangeModel?: any; 16 | } 17 | 18 | interface IAutoFieldProps { 19 | id?: string; 20 | name?: string; 21 | value?: any; 22 | autoField?: any; 23 | element?: string; 24 | omitFields?: any[]; 25 | } 26 | 27 | export class AutoForm extends React.Component { 28 | getChildContext(): any; 29 | getNativeFormProps(): any; 30 | getSubmitField(): any; 31 | getChildContextSchema(): any; 32 | } 33 | 34 | 35 | export class AutoField extends React.Component { 36 | constructor(props:IAutoFieldProps, uniforms); 37 | getFieldProps(name, options): any; 38 | } 39 | 40 | export class RadioField extends React.Component { 41 | getFieldProps(name, options): any; 42 | } 43 | 44 | export class SelectField extends React.Component { 45 | getFieldProps(name, options): any; 46 | } 47 | 48 | export class DateField extends React.Component { 49 | getFieldProps(name, options): any; 50 | } 51 | 52 | export class ListField extends React.Component { 53 | getFieldProps(name, options): any; 54 | } 55 | 56 | export class NumField extends React.Component { 57 | getFieldProps(name, options): any; 58 | } 59 | 60 | export class NestField extends React.Component { 61 | getFieldProps(name, options): any; 62 | } 63 | 64 | export class TextField extends React.Component { 65 | getFieldProps(name, options): any; 66 | } 67 | 68 | export class LongTextField extends React.Component { 69 | getFieldProps(name, options): any; 70 | } 71 | 72 | export class BoolField extends React.Component { 73 | getFieldProps(name, options): any; 74 | } 75 | 76 | export class BaseField extends React.Component { 77 | static displayName: string; 78 | 79 | getFieldProps(name?, options?): any; 80 | getChildContextName(): any; 81 | } 82 | 83 | export function wrapField(props: any, elm: any): any; 84 | 85 | export class SubmitField extends React.Component { 86 | getFieldProps(name, options): any; 87 | } 88 | 89 | 90 | } 91 | -------------------------------------------------------------------------------- /packages/admin/@types/uniforms-bridge-json-schema.d.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | declare module 'uniforms-bridge-json-schema' { 4 | import { JSONSchema7 } from "json-schema"; 5 | 6 | export class JSONSchemaBridge { 7 | constructor(schema: JSONSchema7, validator: Function); 8 | 9 | getField(name: string): any; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/admin/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 get-set-fetch 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/admin/README.md: -------------------------------------------------------------------------------- 1 | # get-set-fetch-extension-admin 2 | 3 | React based frontend communicating with the backend (extension background script) via chrome.runtime.sendMessage. 4 | Bundled as a single page app with the help of react-router-dom. 5 | 6 | The following sections are available, either static or based on json-schemas: 7 | - getting-started 8 | - projects 9 | - sites 10 | - scenarios 11 | - plugins 12 | - logs 13 | - settings 14 | 15 | 16 | ## Roadmap 17 | - internationalization -------------------------------------------------------------------------------- /packages/admin/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "get-set-fetch-extension-admin", 3 | "version": "0.4.1", 4 | "description": "", 5 | "main": "dist/admin.js", 6 | "types": "dist/index.d.ts", 7 | "files": [ 8 | "dist/" 9 | ], 10 | "scripts": { 11 | "clean": "shx rm -rf dist", 12 | "lint": "eslint \"src/**/*.ts{,x}\"", 13 | "build": "npm run clean && npm run lint && npm run build:dist", 14 | "build:dist": "rollup --environment NODE_ENV:production --config rollup.config.js && npm run copy:html", 15 | "copy:html": "shx cp -R src/html/admin.html dist/admin.html", 16 | "build:dev": "webpack-dev-server --config ./webpack.config.dev.js --mode development" 17 | }, 18 | "keywords": [ 19 | "scraper", 20 | "extension" 21 | ], 22 | "author": { 23 | "name": "Andrei Sabau" 24 | }, 25 | "license": "MIT", 26 | "bugs": { 27 | "url": "https://github.com/get-set-fetch/extension/issues" 28 | }, 29 | "homepage": "https://github.com/get-set-fetch/extension#readme", 30 | "dependencies": { 31 | "@fortawesome/fontawesome-free": "^5.3.1", 32 | "bootstrap": "^4.1.1", 33 | "classnames": "^2.2.6", 34 | "get-set-fetch-extension-commons": "file:../commons", 35 | "immutable": "^4.0.0-rc.12", 36 | "is-my-json-valid": "^2.20.0", 37 | "query-string": "^6.1.0", 38 | "react": "^16.4.0", 39 | "react-beautiful-dnd": "^10.0.4", 40 | "react-dom": "^16.4.0", 41 | "react-router-dom": "^4.3.1", 42 | "uniforms": "^2.0.0-alpha.1", 43 | "uniforms-bootstrap4": "^2.0.0-alpha.1", 44 | "uniforms-bridge-json-schema": "^2.0.0-alpha.1", 45 | "util": "^0.12.0" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /packages/admin/rollup.config.js: -------------------------------------------------------------------------------- 1 | import commonjs from 'rollup-plugin-commonjs'; 2 | import typescript from 'rollup-plugin-typescript'; 3 | import json from 'rollup-plugin-json'; 4 | import resolve from 'rollup-plugin-node-resolve'; 5 | import globals from 'rollup-plugin-node-globals'; 6 | import scss from 'rollup-plugin-scss'; 7 | import url from 'rollup-plugin-url'; 8 | import { join } from 'path'; 9 | 10 | function gsfBuiltin() { 11 | return { 12 | name: 'gsf-builtin', 13 | resolveId(source) { 14 | if (source === 'util') { 15 | return require.resolve(join('..', '..', 'node_modules', 'util', 'util.js')); 16 | } 17 | return null; 18 | }, 19 | }; 20 | } 21 | 22 | export default { 23 | input: './src/ts/admin.ts', 24 | output: { 25 | dir: 'dist', 26 | format: 'esm', 27 | }, 28 | experimentalCodeSplitting: true, 29 | sourceMap: true, 30 | plugins: [ 31 | resolve({ 32 | browser: true, 33 | preferBuiltins: false, 34 | }), 35 | commonjs({ 36 | include: '../../node_modules/**', 37 | namedExports: { 38 | '../../node_modules/react': [ 39 | 'Children', 40 | 'Component', 41 | 'PureComponent', 42 | 'PropTypes', 43 | 'createElement', 44 | 'Fragment', 45 | 'cloneElement', 46 | 'StrictMode', 47 | 'createFactory', 48 | 'createRef', 49 | 'createContext', 50 | 'isValidElement', 51 | ], 52 | '../../node_modules/react-dom': [ 53 | 'render', 54 | 'hydrate', 55 | ], 56 | '../../node_modules/react-is': [ 57 | 'isValidElementType', 58 | ], 59 | '../../node_modules/uniforms': [ 60 | 'BaseField', 61 | ], 62 | '../../node_modules/uniforms-bootstrap4': [ 63 | 'AutoForm', 'AutoField', 'RadioField', 'SelectField', 'DateField', 'ListField', 'NumField', 'TextField', 'LongTextField', 'BoolField', 'wrapField', 'SubmitField', 64 | ], 65 | '../../node_modules/uniforms-bridge-json-schema': [ 66 | 'JSONSchemaBridge', 67 | ], 68 | }, 69 | }), 70 | gsfBuiltin(), 71 | typescript(), 72 | json(), 73 | scss({ 74 | output: 'dist/admin.css', 75 | runtime: 'node-sass', 76 | }), 77 | url({ 78 | limit: 0, 79 | include: [ '**/*.svg', '**/*.png', '**/*.jpg' ], 80 | destDir: 'dist/images', 81 | fileName: '[name][extname]', 82 | publicPath: '/admin/images/', 83 | }), 84 | globals(), 85 | ], 86 | }; 87 | -------------------------------------------------------------------------------- /packages/admin/src/html/admin.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | GSF Admin Area 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /packages/admin/src/images/gsf-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/get-set-fetch/extension/f06aa3217b15458a51c4c27072878ddd3d374436/packages/admin/src/images/gsf-logo.png -------------------------------------------------------------------------------- /packages/admin/src/scss/admin.scss: -------------------------------------------------------------------------------- 1 | @import "custom-variables"; 2 | @import "../../node_modules/bootstrap/scss/bootstrap"; 3 | 4 | /* 5 | @import "node_modules/@fortawesome/fontawesome-free/scss/fontawesome"; 6 | $fa-font-path: "../../node_modules/@fortawesome/fontawesome-free/webfonts"; 7 | @import "node_modules/@fortawesome/fontawesome-free/scss/regular.scss"; 8 | */ 9 | 10 | html, body{ 11 | height:100%; 12 | } 13 | 14 | body { 15 | padding-top: $spacer * 1.5; 16 | padding-bottom: $spacer * 1.5; 17 | } 18 | 19 | a.nav-link { 20 | text-decoration: underline; 21 | font-size: $font-size-base * 1.05; 22 | line-height: 25px; 23 | 24 | &:hover { 25 | text-decoration: underline; 26 | background-color: $gray-100; 27 | } 28 | 29 | &.active { 30 | font-weight: $font-weight-bold; 31 | font-size: $font-size-base * 1.25; 32 | text-decoration: underline; 33 | } 34 | } 35 | 36 | a.inner-nav-link { 37 | text-decoration: underline; 38 | font-weight: bold; 39 | } 40 | 41 | h5.inner { 42 | color: green; 43 | margin-top: 0.5rem; 44 | } 45 | 46 | h5.subtitle { 47 | color: #8b1010; 48 | margin-top: 0.5rem; 49 | } 50 | 51 | div.examples { 52 | & li { 53 | margin-bottom: 1.5rem; 54 | } 55 | & p { 56 | margin: 0; 57 | } 58 | & hr { 59 | margin-top: 0.5rem; 60 | } 61 | & p.notes { 62 | margin-bottom: 0.9rem; 63 | } 64 | } 65 | 66 | div.card-main { 67 | min-height: 100%; 68 | margin-right: 2rem; 69 | 70 | & .card-header { 71 | padding: 0.75rem 0.75rem 0.75rem $spacer * 2.5; 72 | & h3 { 73 | margin-bottom: 0; 74 | font-weight: $font-weight-bold; 75 | } 76 | & .btn { 77 | font-weight: $font-weight-bold; 78 | } 79 | } 80 | 81 | 82 | & .card-body { 83 | padding: 0; 84 | margin-bottom: $spacer * 2; 85 | } 86 | } 87 | 88 | .table-main { 89 | & thead th { 90 | border: none; 91 | font-weight: $font-weight-bold; 92 | 93 | &:first-child { 94 | padding: 0.75rem 0.75rem 0.75rem $spacer * 2.5; 95 | } 96 | } 97 | 98 | & tbody { 99 | & tr:hover { 100 | color: darken($secondary, 15%); 101 | } 102 | 103 | & tr:last-child { 104 | border-bottom: 1px solid $gray-300; 105 | } 106 | 107 | & th { 108 | padding: .75rem .75rem .75rem $spacer * 2; 109 | font-weight: $font-weight-normal; 110 | } 111 | 112 | & td { 113 | padding: .75rem;; 114 | } 115 | 116 | & a { 117 | text-decoration: none; 118 | // padding: .75rem; 119 | color: inherit; 120 | 121 | &:hover { 122 | color: darken($secondary, 15%); 123 | } 124 | } 125 | } 126 | } 127 | 128 | form.form-main { 129 | padding: $spacer $spacer $spacer $spacer * 2.5; 130 | 131 | & label { 132 | color:$primary; 133 | font-weight: $font-weight-bold; 134 | } 135 | } 136 | 137 | div.form-check label.form-check-label { 138 | font-weight: normal; 139 | } 140 | 141 | div.form-group label.btn-secondary { 142 | font-weight: normal; 143 | margin: 0; 144 | color: $white; 145 | } 146 | 147 | a.scenario-homepage { 148 | overflow: hidden; 149 | text-overflow: ellipsis; 150 | white-space: nowrap; 151 | display: inline-block; 152 | max-width: 300px; 153 | } 154 | 155 | select { 156 | background-image: none !important; 157 | } 158 | 159 | div.modal-bkg { 160 | position: absolute; 161 | width: 100%; 162 | height: 100%; 163 | background-color: black; 164 | opacity: 0.5; 165 | } 166 | 167 | p.version { 168 | color: $primary; 169 | font-weight: 500; 170 | font-size: 1.05rem; 171 | } -------------------------------------------------------------------------------- /packages/admin/src/scss/custom-variables.scss: -------------------------------------------------------------------------------- 1 | // Color system 2 | $white: #fff; //original: #fff , f6f7f7 3 | $gray-100: #eaebeb; //original: f8f9fa 4 | $gray-200: #e9ecef; 5 | $gray-300: #dee2e6; 6 | $gray-400: #ced4da; 7 | $gray-500: #adb5bd; 8 | $gray-600: #6c757d; 9 | $gray-700: #495057; 10 | $gray-800: #343a40; 11 | $gray-900: #212529; 12 | $black: #000; 13 | 14 | $blue: #293f54; // original: #007bff; 15 | $indigo: #6610f2; 16 | $purple: #6f42c1; 17 | $pink: #e83e8c; 18 | $red: #dc3545; 19 | $orange: #fd7e14; 20 | $yellow: #ffc107; 21 | $green: #28a745; 22 | $teal: #20c997; 23 | $cyan: #17a2b8; 24 | 25 | $primary: $blue; 26 | $secondary: #1976d2; // original: $gray-600; 27 | $success: $green; 28 | $info: $cyan; 29 | $warning: $yellow; 30 | $danger: $red; 31 | $light: $gray-100; 32 | $dark: $gray-800; 33 | 34 | 35 | // Settings for the `` element. 36 | $body-bg: #f6f7f7; // $white; 37 | $body-color: $gray-900; 38 | 39 | // Style anchor elements. 40 | $link-color: $primary; // original: theme-color("primary"); 41 | $link-decoration: none; 42 | $link-hover-color: darken($link-color, 15%); 43 | $link-hover-decoration: underline; 44 | // Darken percentage for links with `.text-*` class (e.g. `.text-success`) 45 | $emphasized-link-hover-darken-percentage: 15%; 46 | 47 | $card-bg: #fff; 48 | $card-cap-bg: #fff; 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /packages/admin/src/ts/admin.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import Admin from './pages/Admin'; 4 | import GsfClient from './components/GsfClient'; 5 | import '../scss/admin.scss'; 6 | 7 | // register GsfClient at window level, usefull for querying the extension IndexedDB via puppeteer 8 | window.GsfClient = GsfClient; 9 | 10 | ReactDOM.render( 11 | React.createElement(Admin, {}, null), 12 | document.getElementById('gsf'), 13 | ); 14 | -------------------------------------------------------------------------------- /packages/admin/src/ts/components/Dropdown.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | interface IProps { 4 | className: string; 5 | anchor: React.ReactElement; 6 | } 7 | 8 | const Dropdown:React.FC = props => { 9 | const [ open, setOpen ] = React.useState(false); 10 | const drop = React.useRef(null); 11 | 12 | function handleOutsideClick(e) { 13 | if (!e.target.closest(`.${drop.current.className}`) && open) { 14 | setOpen(false); 15 | } 16 | } 17 | 18 | React.useEffect(() => { 19 | document.addEventListener('click', handleOutsideClick); 20 | return () => { 21 | document.removeEventListener('click', handleOutsideClick); 22 | }; 23 | }); 24 | 25 | return ( 26 |
30 | { 31 | React.cloneElement(props.anchor, { onClick: () => setOpen(open => !open) }) 32 | } 33 | { 34 | // assumes a single direct child is present 35 | open && React.Children.map( 36 | props.children, (child:React.ReactElement) => React.cloneElement( 37 | child, 38 | { 39 | onClick: () => setOpen(false), 40 | }, 41 | ), 42 | ) 43 | } 44 |
45 | ); 46 | }; 47 | 48 | export default Dropdown; 49 | -------------------------------------------------------------------------------- /packages/admin/src/ts/components/GsfClient.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { HttpMethod, LogLevel } from 'get-set-fetch-extension-commons'; 3 | import Modal from './Modal'; 4 | 5 | export default class GsfClient { 6 | static fetchCount: number = 0; 7 | static spinnerTimeout: number; 8 | 9 | static addFetchOp() { 10 | GsfClient.fetchCount += 1; 11 | if (GsfClient.fetchCount === 1) { 12 | GsfClient.showSpinner(); 13 | } 14 | } 15 | 16 | /* 17 | show the spinner with a delay so that fast fetch responses don't cause the spinner to flicker 18 | */ 19 | static showSpinner() { 20 | GsfClient.spinnerTimeout = window.setTimeout( 21 | () => Modal.instance.show( 22 | null, 23 | [ 24 |
25 | Loading... 26 |
, 27 | ], 28 | null, 29 | ), 30 | 300, 31 | ); 32 | } 33 | 34 | static removeFetchOp() { 35 | GsfClient.fetchCount -= 1; 36 | if (GsfClient.fetchCount === 0) { 37 | window.clearTimeout(GsfClient.spinnerTimeout); 38 | Modal.instance.hide(); 39 | } 40 | } 41 | 42 | static fetch(method: HttpMethod, resource: string, body?: object): Promise { 43 | return new Promise((resolve, reject) => { 44 | GsfClient.addFetchOp(); 45 | (globalThis.browser || globalThis.chrome).runtime.sendMessage({ method, resource, body }, response => { 46 | if (response && response.error) { 47 | reject(response.error); 48 | } 49 | else { 50 | resolve(response); 51 | } 52 | GsfClient.removeFetchOp(); 53 | }); 54 | }); 55 | } 56 | 57 | static log(level: LogLevel, cls: string, ...msg: string[]) { 58 | return GsfClient.fetch(HttpMethod.POST, 'logs', { level, cls, msg: GsfClient.stringifyArgs(msg) }); 59 | } 60 | 61 | static stringifyArgs(args) { 62 | const compactArgs = args.map(arg => { 63 | // arg is object, usefull for serializing errors 64 | if (arg === Object(arg)) { 65 | return Object.getOwnPropertyNames(arg).reduce( 66 | (compactArg, propName) => Object.assign(compactArg, { [propName]: arg[propName] }), 67 | {}, 68 | ); 69 | } 70 | 71 | // arg is literal 72 | return arg; 73 | }); 74 | 75 | return compactArgs; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /packages/admin/src/ts/components/Table.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { NavLink } from 'react-router-dom'; 3 | import { IHeaderCol } from 'get-set-fetch-extension-commons'; 4 | 5 | interface IProps { 6 | header: IHeaderCol[]; 7 | data: any[]; 8 | emptyTableMsg?: string; 9 | rowLink?: (row) => string; 10 | } 11 | 12 | export default class Table extends React.Component { 13 | static defaultProps = { 14 | selectedRows: [], 15 | header: [], 16 | data: [], 17 | emptyTableMsg: 'No entries found', 18 | ahref: null, 19 | }; 20 | 21 | render() { 22 | if (!this.props.data || this.props.data.length === 0) { 23 | return ( 24 |

{this.props.emptyTableMsg}

25 | ); 26 | } 27 | 28 | return ( 29 | 30 | 31 | 32 | { 33 | this.props.header.map(col => ) 34 | } 35 | 36 | 37 | 38 | { 39 | this.props.data.map((row, rowIdx) => ( 40 | 41 | { 42 | this.props.header.map((col, idx) => this.renderCell(row, col, idx)) 43 | } 44 | 45 | )) 46 | } 47 | 48 |
{col.label}
49 | ); 50 | } 51 | 52 | renderCell(row, col, idx) { 53 | return idx === 0 ? this.renderThCell(row, col, idx) : this.renderTdCell(row, col, idx); 54 | } 55 | 56 | renderThCell(row, col, idx) { 57 | return {this.renderCellContent(row, col)}; 58 | } 59 | 60 | renderTdCell(row, col, idx) { 61 | return {this.renderCellContent(row, col)}; 62 | } 63 | 64 | renderCellContent(row, col) { 65 | if (typeof this.props.rowLink !== 'function' || col.renderLink === false) { 66 | return col.render(row); 67 | } 68 | 69 | return {col.render(row)}; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /packages/admin/src/ts/components/uniforms/GsfForm.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { AutoForm } from 'uniforms-bootstrap4'; 3 | import GsfAutoField from './fields/GsfAutoField'; 4 | 5 | export default class GsfForm extends AutoForm { 6 | getNativeFormProps() { 7 | const { 8 | ...props 9 | } = super.getNativeFormProps(); 10 | 11 | props.children = this.getChildContextSchema() 12 | .getSubfields() 13 | .map(key => ) 14 | .concat(this.props.children); 15 | 16 | props.className = 'form-main'; 17 | 18 | return props; 19 | } 20 | 21 | getChildContext() { 22 | const uniforms = super.getChildContext(); 23 | return uniforms; 24 | } 25 | 26 | getAutoField() { 27 | return GsfAutoField; 28 | } 29 | 30 | render() { 31 | return super.render(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/admin/src/ts/components/uniforms/bridge/GsfBridgeHelper.ts: -------------------------------------------------------------------------------- 1 | import createValidation from 'is-my-json-valid'; 2 | import GsfBridge from './GsfBridge'; 3 | 4 | 5 | export default class SchemaBridgeHelper { 6 | static createBridge(schema, data): GsfBridge { 7 | const validate = createValidation( 8 | schema, 9 | { 10 | greedy: true, 11 | verbose: true, 12 | formats: { 13 | regex: /^\/.+\/i{0,1}$/, 14 | }, 15 | }, 16 | ); 17 | 18 | const schemaValidator = model => { 19 | validate(model); 20 | 21 | if (validate.errors) { 22 | validate.errors.forEach(error => { 23 | if (error.message === 'is the wrong type') { 24 | // eslint-disable-next-line no-param-reassign 25 | error.message = 'is required'; 26 | } 27 | }); 28 | // eslint-disable-next-line no-throw-literal 29 | throw { details: validate.errors }; 30 | } 31 | }; 32 | 33 | return new GsfBridge(schema, data, schemaValidator); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/admin/src/ts/components/uniforms/fields/GsfAutoField.tsx: -------------------------------------------------------------------------------- 1 | import { AutoField, RadioField, DateField, NumField, TextField, LongTextField, BoolField } from 'uniforms-bootstrap4'; 2 | import { BaseField } from 'uniforms'; 3 | import { createElement } from 'react'; 4 | import GsfNestField from './GsfNestField'; 5 | import ScenarioDescriptionField from './custom/ScenarioDescriptionField'; 6 | import CodeEditorField from './custom/CodeEditorField'; 7 | import GsfSelectField from './GsfSelectField'; 8 | import ScenarioLinkField from './custom/ScenarioLinkField'; 9 | import GsfBoolField from './GsfBoolField'; 10 | 11 | export default class GsfAutoField extends AutoField { 12 | static displayName = 'AutoField'; 13 | static contextTypes = BaseField.contextTypes; 14 | 15 | render() { 16 | const props = this.getFieldProps(this.props.name, { ensureValue: false }); 17 | 18 | if (props.component === undefined) { 19 | if (props.allowedValues) { 20 | if (props.checkboxes && props.fieldType !== Array) { 21 | props.component = RadioField; 22 | } 23 | else { 24 | props.component = GsfSelectField; 25 | } 26 | } 27 | else { 28 | switch (props.fieldType) { 29 | case Date: 30 | props.component = DateField; 31 | break; 32 | case Number: 33 | props.component = NumField; 34 | break; 35 | case Object: 36 | props.component = GsfNestField; 37 | break; 38 | case String: 39 | switch (props.customField) { 40 | case 'LongTextField': 41 | props.component = LongTextField; 42 | break; 43 | case 'ScenarioDescription': 44 | props.component = ScenarioDescriptionField; 45 | break; 46 | case 'ScenarioLink': 47 | props.component = ScenarioLinkField; 48 | break; 49 | case 'CodeEditor': 50 | props.component = CodeEditorField; 51 | break; 52 | default: 53 | props.component = TextField; 54 | } 55 | break; 56 | case Boolean: 57 | props.component = GsfBoolField; 58 | break; 59 | default: 60 | } 61 | } 62 | } 63 | 64 | if (props.uniforms && props.uniforms.hidden) return null; 65 | return createElement(props.component, props); 66 | } 67 | 68 | getFieldProps(name, options) { 69 | const fieldProps = super.getFieldProps(name, options); 70 | fieldProps.id = fieldProps.name; 71 | return fieldProps; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /packages/admin/src/ts/components/uniforms/fields/GsfAutoFields.tsx: -------------------------------------------------------------------------------- 1 | import { createElement } from 'react'; 2 | import GsfAutoField from './GsfAutoField'; 3 | 4 | const GsfAutoFields = ( 5 | { autoField = GsfAutoField, element = 'div', fields = null, omitFields = [], ...props }, 6 | { uniforms: { schema } }, 7 | ) => createElement( 8 | element, 9 | props, 10 | (fields || schema.getSubfields()) 11 | .filter(field => omitFields.indexOf(field) === -1) 12 | .map(field => createElement(autoField, { key: field, name: field })), 13 | ); 14 | 15 | export default GsfAutoFields; 16 | -------------------------------------------------------------------------------- /packages/admin/src/ts/components/uniforms/fields/GsfBoolField.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import classnames from 'classnames'; 3 | import { wrapField } from 'uniforms-bootstrap4'; 4 | import gsfConnectField from './gsfConnectField'; 5 | 6 | const allowedValues = [ true, false ]; 7 | 8 | const renderBool = props => ( 9 | 28 | ); 29 | const Select = props => wrapField(props, renderBool(props)); 30 | export default gsfConnectField(Select); 31 | -------------------------------------------------------------------------------- /packages/admin/src/ts/components/uniforms/fields/GsfNestField.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import classnames from 'classnames'; 3 | import filterDOMProps from 'uniforms/filterDOMProps'; 4 | import injectName from 'uniforms/injectName'; 5 | import joinName from 'uniforms/joinName'; 6 | import GsfAutoField from './GsfAutoField'; 7 | import gsfConnectField from './gsfConnectField'; 8 | 9 | const GsfNest = ({ 10 | children, 11 | className, 12 | error, 13 | errorMessage, 14 | fields, 15 | itemProps, 16 | label, 17 | name, 18 | showInlineError, 19 | ...props 20 | }) => { 21 | if (props.childrenOnly) { 22 | return children 23 | ? injectName(name, children) 24 | : fields.map(key => ); 25 | } 26 | 27 | const title = props.field.title ? props.field.title : label; 28 | 29 | return ( 30 |
31 |
32 | {title &&

{title}

} 33 | {props.field.uniforms.help &&

{props.field.uniforms.help}

} 34 | 35 | {!!(error && showInlineError) && {errorMessage}} 36 | 37 | {children 38 | ? injectName(name, children) 39 | : fields.map(key => )} 40 |
41 | ); 42 | }; 43 | 44 | export default gsfConnectField(GsfNest, { ensureValue: false, includeInChain: false }); 45 | -------------------------------------------------------------------------------- /packages/admin/src/ts/components/uniforms/fields/GsfSelectField.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import classnames from 'classnames'; 3 | import { wrapField } from 'uniforms-bootstrap4'; 4 | import gsfConnectField from './gsfConnectField'; 5 | 6 | const xor = (item, array) => { 7 | const index = array.indexOf(item); 8 | if (index === -1) { 9 | return array.concat([ item ]); 10 | } 11 | 12 | return array.slice(0, index).concat(array.slice(index + 1)); 13 | }; 14 | 15 | const renderCheckboxes = props => props.allowedValues.map(item => ( 16 |
17 | 28 |
29 | )); 30 | const renderSelect = props => ( 31 | 50 | ); 51 | const Select = props => wrapField(props, props.checkboxes || props.fieldType === Array ? renderCheckboxes(props) : renderSelect(props)); 52 | export default gsfConnectField(Select); 53 | -------------------------------------------------------------------------------- /packages/admin/src/ts/components/uniforms/fields/custom/CodeEditorField.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import classnames from 'classnames'; 3 | import { wrapField } from 'uniforms-bootstrap4'; 4 | 5 | 6 | const CodeEditorField = props => wrapField( 7 | props, 8 |