├── .gitignore ├── README.md ├── license.md ├── strapify ├── .babelrc ├── .prettierrc ├── bundle │ ├── .gitkeep │ ├── main.js │ ├── main.js.map │ ├── strapify-v0.0.7.js │ └── strapify-v0.0.7.js.map ├── jest.config.js ├── package-lock.json ├── package.json ├── src │ ├── Strapify.js │ ├── StrapifyCollection.js │ ├── StrapifyControl.js │ ├── StrapifyEZFormsForm.js │ ├── StrapifyErrors.js │ ├── StrapifyField.js │ ├── StrapifyForm.js │ ├── StrapifyParse.js │ ├── StrapifyRelation.js │ ├── StrapifyRepeatable.js │ ├── StrapifySingleType.js │ ├── StrapifyTemplate.js │ ├── injector-backup.js │ ├── injector.js │ ├── strapify-parser.js │ ├── strapify-parser.js.pegjs │ └── util │ │ └── strapiRequest.js ├── tests │ ├── DOM.test.js │ ├── Strapify.test.js │ ├── html-templates.js │ ├── html-templates │ │ ├── collection.html │ │ ├── combined-filter-sort-page.html │ │ ├── components.html │ │ ├── empty.html │ │ ├── nested-collection-deep.html │ │ ├── nested-collection.html │ │ ├── nested-collections-and-relations.html │ │ ├── single-type-components.html │ │ ├── strapi-background-image.html │ │ ├── strapi-class-add.html │ │ ├── strapi-class-conditional.html │ │ ├── strapi-class-replace.html │ │ ├── strapi-css-rule.html │ │ ├── strapi-delete.html │ │ ├── strapi-field.html │ │ ├── strapi-filter.html │ │ ├── strapi-into.html │ │ ├── strapi-page-and-strapi-page-size.html │ │ ├── strapi-relation.html │ │ ├── strapi-single-type-background-image.html │ │ ├── strapi-single-type-class-add.html │ │ ├── strapi-single-type-class-conditional.html │ │ ├── strapi-single-type-class-replace.html │ │ ├── strapi-single-type-css-rule.html │ │ ├── strapi-single-type-field.html │ │ ├── strapi-single-type-into.html │ │ ├── strapi-single-type-relation.html │ │ ├── strapi-single-type-repeatable.html │ │ └── strapi-sort.html │ ├── html-tests-validated │ │ ├── .gitkeep │ │ ├── collection.html │ │ ├── combined-filter-sort-page.html │ │ ├── components.html │ │ ├── empty.html │ │ ├── nested-collection-deep.html │ │ ├── nested-collection.html │ │ ├── nested-collections-and-relations.html │ │ ├── single-type-components.html │ │ ├── strapi-background-image.html │ │ ├── strapi-class-add.html │ │ ├── strapi-class-conditional.html │ │ ├── strapi-class-replace.html │ │ ├── strapi-css-rule.html │ │ ├── strapi-delete.html │ │ ├── strapi-field.html │ │ ├── strapi-filter.html │ │ ├── strapi-into.html │ │ ├── strapi-page-and-strapi-page-size.html │ │ ├── strapi-relation.html │ │ ├── strapi-repeatable.html │ │ ├── strapi-single-type-background-image.html │ │ ├── strapi-single-type-class-add.html │ │ ├── strapi-single-type-class-conditional.html │ │ ├── strapi-single-type-class-replace.html │ │ ├── strapi-single-type-css-rule.html │ │ ├── strapi-single-type-field.html │ │ ├── strapi-single-type-into.html │ │ ├── strapi-single-type-relation.html │ │ ├── strapi-single-type-repeatable.html │ │ └── strapi-sort.html │ ├── manual-tests │ │ └── strapi-repeatable-pagination.html │ ├── test-logs │ │ └── .gitkeep │ └── util.js └── webpack.config.js ├── test-client ├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── public │ └── index.html ├── scripts │ └── setupTypeScript.js ├── src │ ├── components │ │ ├── App.svelte │ │ ├── Header.svelte │ │ ├── StatusIndicator.svelte │ │ └── URLInput.svelte │ ├── global.css │ ├── main.js │ └── scripts │ │ └── post-webflow-url.js └── webpack.config.js ├── test-server ├── index.js ├── output │ └── .gitkeep ├── package-lock.json └── package.json ├── test-strapi ├── .editorconfig ├── .env.example ├── .eslintignore ├── .eslintrc ├── .gitignore ├── README.md ├── config │ ├── admin.js │ ├── api.js │ ├── database.js │ ├── middlewares.js │ ├── plugins.js │ └── server.js ├── database │ └── migrations │ │ └── .gitkeep ├── favicon.png ├── media-src-files │ ├── Human Feeding The Little Squirrel.mp4 │ ├── clowfish.mp4 │ ├── dog tea.jpg │ └── mammal.jpg ├── package-lock.json ├── package.json ├── public │ ├── robots.txt │ └── uploads │ │ └── .gitkeep ├── src │ ├── admin │ │ ├── app.example.js │ │ └── webpack.config.example.js │ ├── api │ │ ├── .gitkeep │ │ ├── collection1 │ │ │ ├── content-types │ │ │ │ └── collection1 │ │ │ │ │ └── schema.json │ │ │ ├── controllers │ │ │ │ └── collection1.js │ │ │ ├── routes │ │ │ │ └── collection1.js │ │ │ └── services │ │ │ │ └── collection1.js │ │ ├── strapi-class-test │ │ │ ├── content-types │ │ │ │ └── strapi-class-test │ │ │ │ │ └── schema.json │ │ │ ├── controllers │ │ │ │ └── strapi-class-test.js │ │ │ ├── routes │ │ │ │ └── strapi-class-test.js │ │ │ └── services │ │ │ │ └── strapi-class-test.js │ │ ├── strapi-components-test │ │ │ ├── content-types │ │ │ │ └── strapi-components-test │ │ │ │ │ └── schema.json │ │ │ ├── controllers │ │ │ │ └── strapi-components-test.js │ │ │ ├── routes │ │ │ │ └── strapi-components-test.js │ │ │ └── services │ │ │ │ └── strapi-components-test.js │ │ ├── strapi-css-rule-test │ │ │ ├── content-types │ │ │ │ └── strapi-css-rule-test │ │ │ │ │ └── schema.json │ │ │ ├── controllers │ │ │ │ └── strapi-css-rule-test.js │ │ │ ├── routes │ │ │ │ └── strapi-css-rule-test.js │ │ │ └── services │ │ │ │ └── strapi-css-rule-test.js │ │ ├── strapi-field-test │ │ │ ├── content-types │ │ │ │ └── strapi-field-test │ │ │ │ │ └── schema.json │ │ │ ├── controllers │ │ │ │ └── strapi-field-test.js │ │ │ ├── routes │ │ │ │ └── strapi-field-test.js │ │ │ └── services │ │ │ │ └── strapi-field-test.js │ │ ├── strapi-filter-sort-page-test │ │ │ ├── content-types │ │ │ │ └── strapi-filter-sort-page-test │ │ │ │ │ └── schema.json │ │ │ ├── controllers │ │ │ │ └── strapi-filter-sort-page-test.js │ │ │ ├── routes │ │ │ │ └── strapi-filter-sort-page-test.js │ │ │ └── services │ │ │ │ └── strapi-filter-sort-page-test.js │ │ ├── strapi-into-test │ │ │ ├── content-types │ │ │ │ └── strapi-into-test │ │ │ │ │ └── schema.json │ │ │ ├── controllers │ │ │ │ └── strapi-into-test.js │ │ │ ├── routes │ │ │ │ └── strapi-into-test.js │ │ │ └── services │ │ │ │ └── strapi-into-test.js │ │ ├── strapi-relation-test-a │ │ │ ├── content-types │ │ │ │ └── strapi-relation-test-a │ │ │ │ │ └── schema.json │ │ │ ├── controllers │ │ │ │ └── strapi-relation-test-a.js │ │ │ ├── routes │ │ │ │ └── strapi-relation-test-a.js │ │ │ └── services │ │ │ │ └── strapi-relation-test-a.js │ │ ├── strapi-relation-test-b │ │ │ ├── content-types │ │ │ │ └── strapi-relation-test-b │ │ │ │ │ └── schema.json │ │ │ ├── controllers │ │ │ │ └── strapi-relation-test-b.js │ │ │ ├── routes │ │ │ │ └── strapi-relation-test-b.js │ │ │ └── services │ │ │ │ └── strapi-relation-test-b.js │ │ ├── strapi-repeatable-test │ │ │ ├── content-types │ │ │ │ └── strapi-repeatable-test │ │ │ │ │ └── schema.json │ │ │ ├── controllers │ │ │ │ └── strapi-repeatable-test.js │ │ │ ├── routes │ │ │ │ └── strapi-repeatable-test.js │ │ │ └── services │ │ │ │ └── strapi-repeatable-test.js │ │ ├── strapi-single-type-class-test │ │ │ ├── content-types │ │ │ │ └── strapi-single-type-class-test │ │ │ │ │ └── schema.json │ │ │ ├── controllers │ │ │ │ └── strapi-single-type-class-test.js │ │ │ ├── routes │ │ │ │ └── strapi-single-type-class-test.js │ │ │ └── services │ │ │ │ └── strapi-single-type-class-test.js │ │ ├── strapi-single-type-components-test │ │ │ ├── content-types │ │ │ │ └── strapi-single-type-components-test │ │ │ │ │ └── schema.json │ │ │ ├── controllers │ │ │ │ └── strapi-single-type-components-test.js │ │ │ ├── routes │ │ │ │ └── strapi-single-type-components-test.js │ │ │ └── services │ │ │ │ └── strapi-single-type-components-test.js │ │ ├── strapi-single-type-css-rule-test │ │ │ ├── content-types │ │ │ │ └── strapi-single-type-css-rule-test │ │ │ │ │ └── schema.json │ │ │ ├── controllers │ │ │ │ └── strapi-single-type-css-rule-test.js │ │ │ ├── routes │ │ │ │ └── strapi-single-type-css-rule-test.js │ │ │ └── services │ │ │ │ └── strapi-single-type-css-rule-test.js │ │ ├── strapi-single-type-field-test │ │ │ ├── content-types │ │ │ │ └── strapi-single-type-field-test │ │ │ │ │ └── schema.json │ │ │ ├── controllers │ │ │ │ └── strapi-single-type-field-test.js │ │ │ ├── routes │ │ │ │ └── strapi-single-type-field-test.js │ │ │ └── services │ │ │ │ └── strapi-single-type-field-test.js │ │ ├── strapi-single-type-into-test │ │ │ ├── content-types │ │ │ │ └── strapi-single-type-into-test │ │ │ │ │ └── schema.json │ │ │ ├── controllers │ │ │ │ └── strapi-single-type-into-test.js │ │ │ ├── routes │ │ │ │ └── strapi-single-type-into-test.js │ │ │ └── services │ │ │ │ └── strapi-single-type-into-test.js │ │ ├── strapi-single-type-relation-test │ │ │ ├── content-types │ │ │ │ └── strapi-single-type-relation-test │ │ │ │ │ └── schema.json │ │ │ ├── controllers │ │ │ │ └── strapi-single-type-relation-test.js │ │ │ ├── routes │ │ │ │ └── strapi-single-type-relation-test.js │ │ │ └── services │ │ │ │ └── strapi-single-type-relation-test.js │ │ └── strapi-single-type-repeatable-test │ │ │ ├── content-types │ │ │ └── strapi-single-type-repeatable-test │ │ │ │ └── schema.json │ │ │ ├── controllers │ │ │ └── strapi-single-type-repeatable-test.js │ │ │ ├── routes │ │ │ └── strapi-single-type-repeatable-test.js │ │ │ └── services │ │ │ └── strapi-single-type-repeatable-test.js │ ├── components │ │ ├── general │ │ │ ├── image-info.json │ │ │ ├── name.json │ │ │ └── repeatable-test-component.json │ │ └── nesting-tests │ │ │ ├── component-one-level-deep.json │ │ │ └── component-two-levels-deep.json │ ├── extensions │ │ └── .gitkeep │ └── index.js └── types │ └── generated │ ├── components.d.ts │ └── contentTypes.d.ts └── version-history.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | 106 | # vscode files 107 | .vscode 108 | 109 | # dev server output folder 110 | /test-server/output/* 111 | 112 | # strapify test files 113 | /strapify/tests/html-tests-unvalidated/* 114 | 115 | # strapify test log files 116 | /strapify/tests/test-logs/* 117 | 118 | # gitkeep files 119 | !.gitkeep 120 | 121 | 122 | 123 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Strapify 2 | 3 | Strapify is a javascript tool for the automatic client side injection of Strapi CMS data into a website. 4 | 5 | Documentation https://www.strapify.dev/docs 6 | 7 | ## How to Run For Development 8 | For a website with files stored locally, build Strapify and add /strapify/bundle/main.js to your HTML file. 9 | 10 | For a website which is hosted, such as a Webflow site, it is easiest to develop Strapify using the development server and client, which will allow you to scrape the site and add the Strapify script with a single button press. 11 | 12 | #### Strapify 13 | - cd into the /strapify folder and install npm deps 14 | ```shell 15 | npm install 16 | ``` 17 | - install peggy (must be installed globally) and build the peggy parser with 18 | ```shell 19 | npm install -g peggy 20 | npm run buildpeggy 21 | ``` 22 | - and start webpack with hot reloding in another terminal (used to bundle the source scripts) with 23 | ```shell 24 | npm run bundlehot 25 | ``` 26 | 27 | 28 | #### Development Server 29 | - cd into the /test-server folder and install npm deps 30 | ```shell 31 | npm install 32 | ``` 33 | - run the server with 34 | ```shell 35 | npm run starthot 36 | ``` 37 | 38 | 39 | #### Development Client 40 | - cd into the /test-client folder and install npm deps 41 | ```shell 42 | npm install 43 | ``` 44 | - run the client with 45 | ```shell 46 | npm run dev 47 | ``` 48 | the app will be available on localhost:8080. You can test it with these links: 49 | - webflow: https://strapify-demo.webflow.io/ 50 | - strapi: http://54.163.229.233:1337 51 | 52 | 53 | ## How to build 54 | For development builds cd into /strapify and run the following commands 55 | ```shell 56 | npm install -g peggy 57 | npm run buildpeggy 58 | ``` 59 | ```shell 60 | npm run bundle 61 | ``` 62 | the script can then be found in /strapify/bundle/main.js 63 | 64 | For production builds cd into /strapify and run the following commands 65 | ```shell 66 | npm install -g peggy 67 | npm run buildpeggy 68 | ``` 69 | ```shell 70 | npm run bundleprod 71 | ``` 72 | the script can then be found in /strapify/bundle/strapify.js 73 | 74 | ## How to make changes 75 | The source files for Strapify are found in /strapify/src. Make any changes and build. 76 | 77 | ## Testing 78 | Strapify uses Jest for testing. Some functions are unit tested as usual but since Strapify is all about modifying the DOM, we use a more elaborate system that requires manual human validation initially, and then automated difference testing afterwards. 79 | 80 | This system involves three directories 81 | 1. /strapify/tests/html-templates (place any tests you make in here) 82 | 2. /strapify/tests/html-tests-unvalidated (never add anything here) 83 | 3. /strapify/tests/html-tests-validated (move files from html-tests-unvalidated to this directory after manual validation) 84 | 85 | The DOM based testing process is as follows: 86 | 1. create any tests and add them to the html-templates directory 87 | 2. run the tests in DOM.test.js. This will result in a file for each template, with the same name as the template, being created in html-tests-unvalidated. These files contain a dump of the DOM after Strapify runs. 88 | 3. if the test has not been manually validated, the test will fail. In this case, open the test file in html-tests-unvalidated and validate that everything is correct. You must disable javascript when you open the file in a browser. If everything is ok, move it to html-tests-validated and run Jest again 89 | 4. if a validated file does exist, the dumped DOM will be compared to the content of the validated file to detect any differences. 90 | 91 | This system allows for unexpected changes in behaviour to be detected automatically. 92 | -------------------------------------------------------------------------------- /license.md: -------------------------------------------------------------------------------- 1 | # License 2 | 3 | The MIT License (MIT) 4 | ===================== 5 | 6 | Copyright © `2023` `Strapify Contributors (Austin Goodman, Ray Keating, Civiconnect)` 7 | 8 | Permission is hereby granted, free of charge, to any person 9 | obtaining a copy of this software and associated documentation 10 | files (the “Software”), to deal in the Software without 11 | restriction, including without limitation the rights to use, 12 | copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | copies of the Software, and to permit persons to whom the 14 | Software is furnished to do so, subject to the following 15 | conditions: 16 | 17 | The above copyright notice and this permission notice shall be 18 | included in all copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, 21 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 22 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 23 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 24 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 25 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 26 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 27 | OTHER DEALINGS IN THE SOFTWARE. 28 | -------------------------------------------------------------------------------- /strapify/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env"], 3 | "sourceMaps": true 4 | } -------------------------------------------------------------------------------- /strapify/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "tabWidth": 4, 4 | "printWidth": 120, 5 | "singleQuote": false 6 | } 7 | -------------------------------------------------------------------------------- /strapify/bundle/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/strapify-dev/Strapify/021dfcdf383a85e79f8ad8195473e23787765881/strapify/bundle/.gitkeep -------------------------------------------------------------------------------- /strapify/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: "jest-puppeteer", 3 | moduleFileExtensions: ['js'], 4 | moduleDirectories: ['node_modules'], 5 | //testEnvironment: 'jsdom' 6 | testTimeout: 20000 7 | }; -------------------------------------------------------------------------------- /strapify/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "strapify", 3 | "version": "0.0.6", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "jest", 8 | "bundle": "webpack --mode development", 9 | "bundlehot": "webpack --watch --mode development", 10 | "bundleprod": "webpack --mode production", 11 | "bundlegzip": "webpack --mode production --env gzip=true", 12 | "bundleanalyze": "webpack --mode production --env analyze=true", 13 | "buildpeggy": "peggy -o ./src/strapify-parser.js ./src/strapify-parser.js.pegjs", 14 | "build": "npm run buildpeggy && npm run bundleprod && npm run bundlegzip" 15 | }, 16 | "author": "Austin Goodman, Ray Keating", 17 | "license": "UNLICENSED", 18 | "dependencies": { 19 | "axios": "^1.1.3", 20 | "dotenv": "^16.0.3", 21 | "marked": "^4.2.2" 22 | }, 23 | "devDependencies": { 24 | "@babel/core": "^7.21.4", 25 | "@babel/preset-env": "^7.21.4", 26 | "babel-jest": "^29.3.1", 27 | "babel-loader": "^9.1.2", 28 | "compression-webpack-plugin": "^10.0.0", 29 | "diff-dom": "^4.2.8", 30 | "jest": "^29.3.1", 31 | "jest-cli": "^29.3.1", 32 | "jest-environment-jsdom": "^29.3.1", 33 | "jest-puppeteer": "^6.2.0", 34 | "webpack": "^5.80.0", 35 | "webpack-bundle-analyzer": "^4.7.0", 36 | "webpack-cli": "^4.10.0" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /strapify/src/StrapifyEZFormsForm.js: -------------------------------------------------------------------------------- 1 | import Strapify from "./Strapify"; 2 | import { strapiEZFormsSubmit } from "./util/strapiRequest"; 3 | 4 | class StrapifyEZFormsForm { 5 | #formContainerElm; 6 | #formElm; 7 | #formSubmitElm; 8 | #state = "initial"; 9 | #stateElms; 10 | 11 | #attributes = { 12 | "strapi-ezforms-form": undefined, 13 | "strapi-success-redirect": undefined, 14 | "strapi-error-redirect": undefined, 15 | "strapi-hide-on-success": undefined, 16 | "strapi-hide-on-error": undefined, 17 | } 18 | 19 | constructor(formContainerElement) { 20 | this.#formContainerElm = formContainerElement; 21 | this.#updateAttributes(); 22 | 23 | //check if the formContainerElm is a form element 24 | if (this.#formContainerElm.tagName === "FORM") { 25 | this.#formElm = this.#formContainerElm; 26 | } else { 27 | //find the child form which is closest to the formContainerElm 28 | this.#formElm = this.#formContainerElm.querySelector("form"); 29 | } 30 | 31 | this.#stateElms = Strapify.findStateElements(this.#formContainerElm); 32 | this.#reflectState(); 33 | 34 | this.#formSubmitElm = Strapify.findEZFormSubmitElms(this.#formContainerElm)[0]; 35 | } 36 | 37 | #updateAttributes() { 38 | Object.keys(this.#attributes).forEach((attribute) => { 39 | this.#attributes[attribute] = this.#formContainerElm.getAttribute(attribute); 40 | }) 41 | } 42 | 43 | #reflectState() { 44 | this.#stateElms.forEach(stateElm => { 45 | const stateKey = stateElm.getAttribute("strapi-state-element"); 46 | 47 | if (stateKey === this.#state) { 48 | stateElm.classList.remove("strapify-hide"); 49 | } else { 50 | stateElm.classList.add("strapify-hide"); 51 | } 52 | }); 53 | } 54 | 55 | async process() { 56 | const ezFormsElm = this.#formElm; 57 | const submitElm = this.#formSubmitElm; 58 | 59 | submitElm.addEventListener("click", (event) => { 60 | event.preventDefault(); 61 | 62 | this.#state = "loading"; 63 | this.#reflectState(); 64 | 65 | strapiEZFormsSubmit(ezFormsElm).then((data) => { 66 | this.#state = "success"; 67 | this.#reflectState(); 68 | 69 | //dispatch a custom event with the data 70 | this.#formContainerElm.dispatchEvent(new CustomEvent("strapiEZFormsSubmitted", { 71 | bubbles: false, 72 | detail: { 73 | data: data 74 | } 75 | })); 76 | 77 | if(this.#attributes["strapi-hide-on-success"] !== null && this.#attributes["strapi-hide-on-success"] !== undefined) { 78 | this.#formContainerElm.classList.add("strapify-hide"); 79 | } 80 | 81 | if (this.#attributes["strapi-success-redirect"]) { 82 | window.location.href = this.#attributes["strapi-success-redirect"]; 83 | } 84 | }).catch((error) => { 85 | this.#state = "error"; 86 | this.#reflectState(); 87 | 88 | //dispatch a custom event with the error 89 | this.#formContainerElm.dispatchEvent(new CustomEvent("strapiEZFormsError", { 90 | bubbles: false, 91 | detail: { 92 | error: error 93 | } 94 | })); 95 | 96 | if(this.#attributes["strapi-hide-on-error"] !== null && this.#attributes["strapi-hide-on-error"] !== undefined) { 97 | this.#formContainerElm.classList.add("strapify-hide"); 98 | } 99 | 100 | if (this.#attributes["strapi-error-redirect"]) { 101 | window.location.href = this.#attributes["strapi-error-redirect"]; 102 | } 103 | }); 104 | }); 105 | } 106 | } 107 | 108 | export default StrapifyEZFormsForm; -------------------------------------------------------------------------------- /strapify/src/StrapifyErrors.js: -------------------------------------------------------------------------------- 1 | // internal state that keeps track of how many errors have been logged 2 | let errorCount = 0; 3 | 4 | let thrownLogs = []; 5 | 6 | function logIfUnseen(message, logType) { 7 | // logType can be "warn", or "error" 8 | if (!thrownLogs.includes(error)) { 9 | thrownLogs.push(error); 10 | console.group( 11 | `%cSTRAPIFY ${logType === "warn" ? "WARNING" : "ERROR"}`, 12 | `background-color: ${ 13 | logType === "warn" ? "#9b9023" : "#aa3d3d" 14 | }; color: #ffffff; font-weight: bold; padding: 4px;` 15 | ); 16 | console[logType](message); 17 | console.groupEnd(); 18 | } 19 | } 20 | 21 | function toast(message) { 22 | const toast = document.createElement("div"); 23 | toast.classList.add("toast"); 24 | toast.textContent = message; 25 | 26 | // Create the close button 27 | const closeButton = document.createElement("button"); 28 | closeButton.textContent = "×"; 29 | closeButton.classList.add("close-button"); 30 | closeButton.style.backgroundColor = "transparent"; 31 | closeButton.style.border = "none"; 32 | closeButton.style.color = "#fff"; 33 | closeButton.style.fontSize = "20px"; 34 | closeButton.style.marginLeft = "12px"; 35 | closeButton.style.cursor = "pointer"; 36 | 37 | // Set the toast styles 38 | toast.style.display = "flex"; 39 | toast.style.alignItems = "center"; 40 | toast.style.position = "fixed"; 41 | toast.style.bottom = "16px"; 42 | toast.style.left = "16px"; 43 | toast.style.padding = "12px"; 44 | toast.style.backgroundColor = "#333"; 45 | toast.style.color = "#fff"; 46 | toast.style.borderRadius = "4px"; 47 | toast.style.boxShadow = "0 0 8px rgba(0, 0, 0, 0.3)"; 48 | toast.style.opacity = "0"; 49 | toast.style.transition = "opacity 0.3s ease-in-out"; 50 | toast.style.zIndex = "9999"; 51 | 52 | toast.appendChild(closeButton); 53 | document.body.appendChild(toast); 54 | 55 | // Close the toast when the close button is clicked 56 | closeButton.addEventListener("click", () => { 57 | toast.style.opacity = "0"; 58 | setTimeout(() => { 59 | document.body.removeChild(toast); 60 | }, 300); 61 | }); 62 | 63 | setTimeout(() => { 64 | toast.style.opacity = "1"; 65 | }, 100); 66 | } 67 | 68 | function warn(message) { 69 | logIfUnseen(message, "warn"); 70 | } 71 | 72 | function error(message) { 73 | errorCount++; 74 | if (errorCount > 0) { 75 | toast( 76 | `${errorCount} Strapify error${ 77 | errorCount > 1 ? "s" : "" 78 | } logged. See console for details.` 79 | ); 80 | } 81 | logIfUnseen(message, "error"); 82 | } 83 | 84 | function checkForTemplateElement(templateElms, containerElement) { 85 | if (templateElms.length === 0) { 86 | if (containerElement.getAttribute("strapi-collection")) { 87 | error( 88 | `No template element found for collection "${containerElement.getAttribute( 89 | "strapi-collection" 90 | )}"` 91 | ); 92 | } 93 | } 94 | } 95 | 96 | function checkIfText(strapiData, elm) { 97 | if (typeof strapiData === "object") { 98 | warn( 99 | `The field "${elm.getAttribute( 100 | "strapi-field" 101 | )}" is set on a text element (p, span, h) but does not contain text. If you are trying to set an image or video field, set it on an img or div element instead.` 102 | ); 103 | } 104 | } 105 | 106 | function checkIfSingleMedia(strapiData, elm) { 107 | if (typeof strapiData !== "object") { 108 | error( 109 | `The field "${elm.getAttribute("strapi-field")}" in the "${elm 110 | .closest("[strapi-collection]") 111 | .getAttribute( 112 | "strapi-collection" 113 | )}" collection is not a media field, but is set on an element.` 114 | ); 115 | } 116 | } 117 | 118 | function isMultipleMedia(strapiData, elm) { 119 | if (Array.isArray(strapiData.data)) { 120 | warn( 121 | `The field "${elm.getAttribute("strapi-field")}" in the "${elm 122 | .closest("[strapi-collection]") 123 | .getAttribute( 124 | "strapi-collection" 125 | )}" collection is a multiple media field. strapi-field only works on single media fields. To display multiple media fields, use strapi-repeatable with a strapi-template and strapi-field inside` 126 | ); 127 | return true; 128 | } else { 129 | return false; 130 | } 131 | } 132 | 133 | function checkIfUndefinedStrapiDataValue( 134 | strapiDataValue, 135 | fieldPath, 136 | fieldElement 137 | ) { 138 | if (strapiDataValue === undefined) { 139 | error( 140 | `Error fetching strapi data for field "${fieldPath}" in collection "${fieldElement 141 | .closest("[strapi-collection]") 142 | .getAttribute("strapi-collection")}. Check that the field exists.` 143 | ); 144 | } 145 | } 146 | 147 | function checkIfRichText(strapiData, elm) { 148 | // this is a bit of a hacky way to check if the strapiData is rich text, but it should help the webflow people stop using divs for text (text blocks) 149 | // if the strapiData contains a new line character or a #, it is likely rich text. If it doesn't, it is likely a string. Throw a warning if it is a string. 150 | if ( 151 | typeof strapiData === "string" && 152 | !/#+/.test(strapiData) && 153 | !strapiData.includes("\n") && 154 | !strapiData.includes("\r") 155 | ) { 156 | // if the strapiData is a youtube link, don't throw a warning 157 | if (strapiData.includes("http")) { 158 | return; 159 | } 160 | warn( 161 | `The text field "${elm.getAttribute("strapi-field")}" in the "${elm 162 | ?.closest("[strapi-collection]") 163 | ?.getAttribute( 164 | "strapi-collection" 165 | )}" collection is set on a div element rather than a p, span, or h. This may alter the styling of the text. If "${elm.getAttribute( 166 | "strapi-field" 167 | )}" is a rich text field, ignore this warning.` 168 | ); 169 | } 170 | } 171 | 172 | const ErrorHandler = { 173 | checkForTemplateElement, 174 | checkIfText, 175 | checkIfSingleMedia, 176 | isMultipleMedia, 177 | checkIfUndefinedStrapiDataValue, 178 | checkIfRichText, 179 | warn, 180 | error, 181 | toast, 182 | }; 183 | 184 | export default ErrorHandler; 185 | -------------------------------------------------------------------------------- /strapify/src/StrapifyForm.js: -------------------------------------------------------------------------------- 1 | import Strapify from "./Strapify"; 2 | import { strapiRegister, strapiAuthenticate } from "./util/strapiRequest"; 3 | 4 | class StrapifyForm { 5 | #formElement; 6 | #mutationObserver; 7 | 8 | #formInputElms; 9 | #formSubmitElm; 10 | 11 | #attributes = { 12 | "strapi-form": undefined, 13 | "strapi-auth": undefined, 14 | "strapi-success-redirect": undefined, 15 | "strapi-error-redirect": undefined, 16 | } 17 | 18 | constructor(formElement) { 19 | this.#formElement = formElement; 20 | this.#updateAttributes(); 21 | 22 | // this.#mutationObserver = new MutationObserver((mutations) => { 23 | // mutations.forEach((mutation) => { 24 | // if (mutation.type === "attributes") { 25 | // this.#updateAttributes(); 26 | // this.process(); 27 | // } 28 | // }); 29 | // }); 30 | 31 | // this.#mutationObserver.observe(this.#singleTypeElement, { 32 | // attributes: true, 33 | // attributeFilter: ["strapi-single-type"] 34 | // }); 35 | 36 | this.#formInputElms = Strapify.findFormInputElms(this.#formElement); 37 | this.#formSubmitElm = Strapify.findFormSubmitElms(this.#formElement)[0]; 38 | 39 | // set any strapi-auth forms to return false on submit 40 | if (this.#attributes["strapi-auth"]) { 41 | this.#formElement.addEventListener("submit", (e) => { 42 | e.preventDefault(); 43 | return false; 44 | }); 45 | } 46 | 47 | // this.#formElement.addEventListener("strapiAuthRegistered", (event) => { 48 | // console.log(event); 49 | // }); 50 | 51 | // this.#formElement.addEventListener("strapiAuthLoggedIn", (event) => { 52 | // console.log(event); 53 | // }); 54 | } 55 | 56 | destroy() { 57 | this.#mutationObserver.disconnect(); 58 | } 59 | 60 | #updateAttributes() { 61 | Object.keys(this.#attributes).forEach((attribute) => { 62 | this.#attributes[attribute] = this.#formElement.getAttribute(attribute); 63 | }) 64 | } 65 | 66 | #getFormData() { 67 | const formData = {}; 68 | 69 | this.#formInputElms.forEach((inputElm) => { 70 | let name = inputElm.getAttribute("strapi-form-input"); 71 | if (!name) { 72 | name = inputElm.getAttribute("strapi-auth-input"); 73 | } 74 | 75 | // split input value by | separator, with optional whitespace (e.g. "username | email") 76 | const names = name.split(/\s*\|\s*/); 77 | names.forEach((name) => { 78 | name = name.trim(); 79 | formData[name] = inputElm.value; 80 | }) 81 | 82 | }) 83 | 84 | return formData; 85 | } 86 | 87 | async #onAuthSubmit(e) { 88 | const formData = this.#getFormData() 89 | 90 | if (this.#attributes["strapi-auth"] === "register") { 91 | try { 92 | const responseData = await strapiRegister(formData); 93 | 94 | localStorage.setItem("jwt", responseData.jwt); 95 | localStorage.setItem("user", JSON.stringify(responseData.user)); 96 | 97 | //dispatch custom event with registered user data 98 | this.#formElement.dispatchEvent(new CustomEvent("strapiAuthRegistered", { 99 | bubbles: false, 100 | detail: { 101 | user: responseData.user 102 | } 103 | })); 104 | 105 | if(this.#attributes["strapi-success-redirect"]) { 106 | window.location.href = this.#attributes["strapi-success-redirect"]; 107 | } 108 | } catch (error) { 109 | //dispatch custom event with error 110 | this.#formElement.dispatchEvent(new CustomEvent("strapiAuthRegisterError", { 111 | bubbles: false, 112 | detail: { 113 | error: error, 114 | errorMessage: error.response.data.error.message 115 | } 116 | })); 117 | 118 | if(this.#attributes["strapi-error-redirect"]) { 119 | window.location.href = this.#attributes["strapi-error-redirect"]; 120 | } 121 | 122 | console.error(error); 123 | console.error(error.response.data.error.message); 124 | } 125 | 126 | 127 | } else if (this.#attributes["strapi-auth"] === "authenticate") { 128 | try { 129 | const responseData = await strapiAuthenticate(formData.identifier, formData.password); 130 | localStorage.setItem("jwt", responseData.jwt); 131 | localStorage.setItem("user", JSON.stringify(responseData.user)); 132 | 133 | //dispatch custom event with authenticated user data 134 | this.#formElement.dispatchEvent(new CustomEvent("strapiAuthLoggedIn", { 135 | bubbles: false, 136 | detail: { 137 | user: responseData.user 138 | } 139 | })); 140 | 141 | if(this.#attributes["strapi-success-redirect"]) { 142 | window.location.href = this.#attributes["strapi-success-redirect"]; 143 | } 144 | } catch (error) { 145 | //dispatch custom event with error 146 | this.#formElement.dispatchEvent(new CustomEvent("strapiAuthLogInError", { 147 | bubbles: false, 148 | detail: { 149 | error: error, 150 | errorMessage: error.response.data.error.message 151 | } 152 | })); 153 | 154 | if(this.#attributes["strapi-error-redirect"]) { 155 | window.location.href = this.#attributes["strapi-error-redirect"]; 156 | } 157 | 158 | console.error(error); 159 | console.error(error.response.data.error.message); 160 | } 161 | 162 | } 163 | } 164 | 165 | async #processForm() { 166 | 167 | } 168 | 169 | async #processAuth() { 170 | //remove any existing event listeners 171 | this.#formSubmitElm.removeEventListener("click", this.#onAuthSubmit.bind(this)); 172 | this.#formSubmitElm.addEventListener("click", this.#onAuthSubmit.bind(this)); 173 | } 174 | 175 | async process() { 176 | if (this.#attributes["strapi-form"]) { 177 | await this.#processForm(); 178 | } 179 | 180 | if (this.#attributes["strapi-auth"]) { 181 | await this.#processAuth(); 182 | } 183 | } 184 | } 185 | 186 | export default StrapifyForm; -------------------------------------------------------------------------------- /strapify/src/StrapifyRelation.js: -------------------------------------------------------------------------------- 1 | import Strapify from "./Strapify.js"; 2 | import StrapifyCollection from "./StrapifyCollection.js"; 3 | import StrapifyField from "./StrapifyField"; 4 | import strapiRequest from "./util/strapiRequest"; 5 | 6 | class StrapifyTemplate { 7 | #relationElement; 8 | #strapifyCollection; 9 | #mutationObserver; 10 | 11 | #strapiDataId; 12 | #strapiDataAttributes; 13 | 14 | #attributes = { 15 | "strapi-relation": undefined, 16 | "strapi-single-type-relation": undefined, 17 | } 18 | 19 | constructor(relationElement, strapiDataId, strapiDataAttributes) { 20 | //set the collection element and update the attributes 21 | this.#relationElement = relationElement; 22 | this.#strapiDataId = strapiDataId; 23 | this.#strapiDataAttributes = strapiDataAttributes; 24 | this.#updateAttributes(); 25 | 26 | //create mutation observer to watch for attribute changes 27 | this.#mutationObserver = new MutationObserver((mutations) => { 28 | mutations.forEach((mutation) => { 29 | if (mutation.type === "attributes") { 30 | this.#updateAttributes(); 31 | this.process(); 32 | } 33 | }); 34 | }); 35 | 36 | //observe the collection element for attribute changes 37 | this.#mutationObserver.observe(this.#relationElement, { 38 | attributes: true, 39 | attributeFilter: ["strapi-relation"] 40 | }); 41 | } 42 | 43 | destroy() { 44 | this.#mutationObserver.disconnect(); 45 | this.#strapifyCollection.destroy(); 46 | this.#relationElement.remove(); 47 | } 48 | 49 | #updateAttributes() { 50 | Object.keys(this.#attributes).forEach((attribute) => { 51 | this.#attributes[attribute] = this.#relationElement.getAttribute(attribute); 52 | }) 53 | } 54 | 55 | async process() { 56 | const relationElement = this.#relationElement; 57 | 58 | if (this.#strapifyCollection) { 59 | this.#strapifyCollection.destroy() 60 | } 61 | 62 | const strapiDataId = this.#strapiDataId; 63 | const strapiDataAttributes = this.#strapiDataAttributes; 64 | 65 | //use the relation ids to generate a filter string 66 | let relationArgs 67 | let relationFieldName 68 | let relationCollectionName 69 | if (this.#attributes["strapi-relation"]) { 70 | relationArgs = this.#attributes["strapi-relation"].split(",").map(arg => arg.trim()); 71 | relationFieldName = relationArgs[0]; 72 | relationCollectionName = relationArgs[1]; 73 | } 74 | else if (this.#attributes["strapi-single-type-relation"]) { 75 | relationArgs = this.#attributes["strapi-single-type-relation"].split(",").map(arg => arg.trim()); 76 | relationFieldName = relationArgs[0].split(".")[1]; 77 | relationCollectionName = relationArgs[1]; 78 | } 79 | 80 | //if the relation field is empty, delete all templates and return 81 | if (strapiDataAttributes[relationFieldName].data == null) { 82 | const templates = Strapify.findTemplateElms(relationElement); 83 | templates.forEach(template => template.remove()); 84 | return; 85 | } 86 | 87 | //get the relation data 88 | const relationData = Strapify.getStrapiComponentValue(relationFieldName, strapiDataAttributes).data; 89 | 90 | //get the relation ids 91 | let relationIDs = []; 92 | if (Array.isArray(relationData)) { 93 | relationIDs = relationData.map(relation => relation.id); 94 | } else { 95 | relationIDs = [relationData.id]; 96 | } 97 | 98 | //query string arg offset to allow for the first 10 relations to be used by user 99 | const qsOffset = 10; 100 | 101 | //use the relation ids to generate a filter string 102 | let filterString = relationIDs.reduce((acc, cur, i) => { 103 | let filter = `[id][$in][${qsOffset + i}]=${cur}`; 104 | i < relationIDs.length - 1 && (filter += " | "); 105 | return acc + filter; 106 | }, ""); 107 | 108 | //when the filter string is empty, change it to filter for a non-existent id 109 | if (!filterString) { 110 | filterString = "[id][$eq]=-1"; 111 | } 112 | 113 | // let filterString 114 | // if (Array.isArray(relationData)) { 115 | // filterString = relationData.map(relation => `[id][$eq]=${relation.id}`).join(" | "); 116 | // } else { 117 | // filterString = `[id][$eq]=${relationData.id}`; 118 | // } 119 | 120 | 121 | 122 | //add the filter string to the relation element 123 | relationElement.setAttribute("strapi-filter-internal-relation", filterString); 124 | 125 | //create a strapify collection with the relationelement 126 | this.#strapifyCollection = new StrapifyCollection(relationElement); 127 | await this.#strapifyCollection.process() 128 | 129 | } 130 | } 131 | 132 | export default StrapifyTemplate; -------------------------------------------------------------------------------- /strapify/src/StrapifyRepeatable.js: -------------------------------------------------------------------------------- 1 | import Strapify from "./Strapify.js"; 2 | import StrapifyCollection from "./StrapifyCollection.js"; 3 | import StrapifyField from "./StrapifyField"; 4 | import strapiRequest from "./util/strapiRequest"; 5 | 6 | class StrapifyRepeatable { 7 | #repeatableElement; 8 | #strapifyCollection; 9 | #strapiDataId; 10 | #strapiDataAttributes 11 | #mutationObserver; 12 | 13 | #attributes = { 14 | "strapi-repeatable": undefined, 15 | "strapi-single-type-repeatable": undefined, 16 | } 17 | 18 | constructor(repeatableElement, strapiDataId, strapiDataAttributes) { 19 | //set the collection element and update the attributes 20 | this.#repeatableElement = repeatableElement; 21 | this.#strapiDataId = strapiDataId; 22 | this.#strapiDataAttributes = strapiDataAttributes; 23 | this.#updateAttributes(); 24 | 25 | //create mutation observer to watch for attribute changes 26 | // this.#mutationObserver = new MutationObserver((mutations) => { 27 | // mutations.forEach((mutation) => { 28 | // if (mutation.type === "attributes") { 29 | // //this.#strapifyCollection.destroy(); 30 | // //this.#updateAttributes(); 31 | // //this.process(); 32 | // } 33 | // }); 34 | // }); 35 | 36 | // //observe the collection element for attribute changes 37 | // this.#mutationObserver.observe(this.#repeatableElement, { 38 | // attributes: true, 39 | // attributeFilter: ["strapi-repeatable", "strapi-single-type-repeatable", "strapi-page", "strapi-page-size"] 40 | // }); 41 | } 42 | 43 | destroy() { 44 | this.#mutationObserver.disconnect(); 45 | this.#strapifyCollection.destroy(); 46 | this.#repeatableElement.remove(); 47 | } 48 | 49 | #updateAttributes() { 50 | Object.keys(this.#attributes).forEach((attribute) => { 51 | this.#attributes[attribute] = this.#repeatableElement.getAttribute(attribute); 52 | }) 53 | } 54 | 55 | getOverrideData() { 56 | const fieldName = this.#attributes["strapi-repeatable"] ? this.#attributes["strapi-repeatable"] : this.#attributes["strapi-single-type-repeatable"].split(".")[1]; 57 | 58 | //when the data field is null (explicitly not undefined), we have an empty media field 59 | //StrapifyCollection will delete the element if the data is null 60 | if (this.#strapiDataAttributes[fieldName].data === null) { 61 | Strapify.findTemplateElms(this.#repeatableElement).forEach((collectionElm) => { 62 | collectionElm.remove(); 63 | }) 64 | return null; 65 | } 66 | 67 | //if data is not null or undefined, we have a media field 68 | let overrideData 69 | if (this.#strapiDataAttributes[fieldName].data) { 70 | overrideData = { 71 | data: this.#strapiDataAttributes[fieldName].data.map((fieldData) => { 72 | return { attributes: { [fieldName]: { data: fieldData } } } 73 | }), 74 | meta: {} 75 | } 76 | } 77 | //otherwise we must have a component field 78 | else { 79 | overrideData = { 80 | data: this.#strapiDataAttributes[fieldName].map((fieldData) => { 81 | return { attributes: { [fieldName]: fieldData } } 82 | }), 83 | meta: {} 84 | } 85 | } 86 | 87 | //need to manually paginate since strapi doesn't support pagination on repeatable fields 88 | const pageSize = parseInt(this.#repeatableElement.getAttribute("strapi-page-size")) || 25; 89 | const page = parseInt(this.#repeatableElement.getAttribute("strapi-page")) || 1; 90 | const pageCount = Math.ceil(overrideData.data.length / pageSize); 91 | const total = overrideData.data.length; 92 | 93 | //split override data into pages 94 | const pagedOverrideData = []; 95 | for (let i = 0; i < overrideData.data.length; i += pageSize) { 96 | pagedOverrideData.push(overrideData.data.slice(i, i + pageSize)); 97 | } 98 | 99 | //replace the data with the page we want 100 | overrideData.data = pagedOverrideData[page - 1]; 101 | 102 | //update the meta 103 | overrideData.meta.pagination = { 104 | page, 105 | pageSize, 106 | pageCount, 107 | total 108 | } 109 | 110 | return overrideData; 111 | } 112 | 113 | async process() { 114 | const repeatableElement = this.#repeatableElement; 115 | 116 | //why on earth do I need to bind this to the function? 117 | const strapifyCollection = new StrapifyCollection(repeatableElement, this.getOverrideData.bind(this)); 118 | this.#strapifyCollection = strapifyCollection; 119 | 120 | await strapifyCollection.process() 121 | } 122 | } 123 | 124 | export default StrapifyRepeatable; -------------------------------------------------------------------------------- /strapify/src/StrapifyTemplate.js: -------------------------------------------------------------------------------- 1 | import Strapify from "./Strapify.js"; 2 | import StrapifyRelation from "./StrapifyRelation.js"; 3 | import StrapifyField from "./StrapifyField"; 4 | import StrapifyRepeatable from "./StrapifyRepeatable.js"; 5 | 6 | class StrapifyTemplate { 7 | //the template element this class manages 8 | #templateElement; 9 | 10 | //the strapi data id and attributes 11 | #strapiDataId 12 | #strapiDataAttributes 13 | 14 | //the strapify field, relation, and repeatable objects which belong to this template 15 | #strapifyFields = []; 16 | #strapifyRelations = []; 17 | #strapifyRepeatables = []; 18 | 19 | //the allowed attributes for the template element 20 | #attributes = { 21 | "strapi-template": undefined, 22 | "strapi-template-conditional": undefined 23 | } 24 | 25 | constructor(templateElement, strapiDataId, strapiDataAttributes, strapifyCollection) { 26 | this.#templateElement = templateElement; 27 | this.#strapiDataId = strapiDataId; 28 | this.#strapiDataAttributes = strapiDataAttributes; 29 | this.#updateAttributes(); 30 | this.#addIds(); 31 | } 32 | 33 | //destroy all descendant strapify objects and delete the template element 34 | destroy() { 35 | this.#strapifyFields.forEach(field => field.destroy()); 36 | this.#strapifyRelations.forEach(relation => relation.destroy()); 37 | this.#strapifyRepeatables.forEach(repeatable => repeatable.destroy()); 38 | this.#templateElement.remove(); 39 | } 40 | 41 | #updateAttributes() { 42 | Object.keys(this.#attributes).forEach((attribute) => { 43 | this.#attributes[attribute] = this.#templateElement.getAttribute(attribute); 44 | }) 45 | } 46 | 47 | #addIds() { 48 | if (this.#strapiDataId) { 49 | this.#templateElement.setAttribute("strapi-template-id", this.#strapiDataId); 50 | } 51 | }; 52 | 53 | async process() { 54 | //find strapify field elements, instatiate strapify field objects, and process them 55 | const strapifyFieldElements = Strapify.findFieldElms(this.#templateElement); 56 | strapifyFieldElements.forEach(fieldElement => { 57 | const strapifyField = new StrapifyField(fieldElement) 58 | this.#strapifyFields.push(strapifyField); 59 | 60 | strapifyField.process(this.#strapiDataAttributes) 61 | }); 62 | 63 | const processPromises = []; 64 | 65 | //find strapify repeatable elements, instatiate strapify repeatable objects, and process them 66 | const strapifyRepeatableElements = Strapify.findRepeatableElms(this.#templateElement); 67 | for (const repeatableElement of strapifyRepeatableElements) { 68 | const strapifyRepeatable = new StrapifyRepeatable(repeatableElement, this.#strapiDataId, this.#strapiDataAttributes) 69 | this.#strapifyRepeatables.push(strapifyRepeatable); 70 | 71 | processPromises.push(strapifyRepeatable.process()) 72 | } 73 | 74 | //find strapify relation elements, instatiate strapify relation objects, and process them 75 | const strapifyRelationElements = Strapify.findRelationElms(this.#templateElement); 76 | strapifyRelationElements.forEach(relationElement => { 77 | const strapifyRelation = new StrapifyRelation(relationElement, this.#strapiDataId, this.#strapiDataAttributes) 78 | this.#strapifyRelations.push(strapifyRelation); 79 | 80 | processPromises.push(strapifyRelation.process()) 81 | }) 82 | 83 | //wait for all strapify objects to process 84 | await Promise.allSettled(processPromises); 85 | 86 | //remove strapify-hide class from template element 87 | this.#templateElement.classList.remove("strapify-hide"); 88 | } 89 | } 90 | 91 | export default StrapifyTemplate; -------------------------------------------------------------------------------- /strapify/src/injector.js: -------------------------------------------------------------------------------- 1 | import StrapifyCollection from "./StrapifyCollection" 2 | import StrapifySingleType from "./StrapifySingleType"; 3 | import StrapifyForm from "./StrapifyForm"; 4 | import StrapifyEZFormsForm from "./StrapifyEZFormsForm"; 5 | import Strapify from "./Strapify"; 6 | import { strapiRequest } from "./util/strapiRequest"; 7 | 8 | const version = process.env.VERSION; 9 | const debugMode = Strapify.debugMode; 10 | 11 | const hiddenTemplateElms = []; 12 | const hiddenSingleTypeElms = []; 13 | 14 | //wait for content to load and scripts to execute 15 | document.addEventListener("DOMContentLoaded", () => { 16 | if (debugMode) { 17 | //log the version 18 | console.log(`running strapify version ${version}`); 19 | } 20 | 21 | //create a class called strapify-hide and insert it into the head 22 | const strapifyHideStyle = document.createElement("style"); 23 | strapifyHideStyle.innerHTML = ".strapify-hide { display: none !important; }"; 24 | document.head.appendChild(strapifyHideStyle); 25 | 26 | //do a preparse for any template elms and hide them 27 | const templateElms = document.querySelectorAll("[strapi-template]"); 28 | templateElms.forEach((templateElm) => { 29 | templateElm.classList.add("strapify-hide"); 30 | hiddenTemplateElms.push(templateElm); 31 | }); 32 | 33 | //do a preparse for any single type elms and hide them 34 | const singleTypeElms = document.querySelectorAll("[strapi-single-type]"); 35 | singleTypeElms.forEach((singleTypeElm) => { 36 | singleTypeElm.classList.add("strapify-hide"); 37 | hiddenSingleTypeElms.push(singleTypeElm); 38 | }); 39 | 40 | //try to get the user from local storage 41 | const user = localStorage.getItem("user"); 42 | 43 | //if the user exists, make a request to the user endpoint to test the jwt 44 | if (user) { 45 | strapiRequest("/api/users/me").then((response) => { 46 | //when we succeed, dispatch a custom event with the user data 47 | document.dispatchEvent(new CustomEvent("strapiUserAuthenticated", { 48 | bubbles: false, 49 | detail: { 50 | user: response 51 | } 52 | })); 53 | }).catch((error) => { 54 | //when we fail, dispatch a custom event to indicate that the user authentication failed 55 | document.dispatchEvent(new CustomEvent("strapiUserAuthenticationError", { 56 | bubbles: false, 57 | detail: { 58 | error: error 59 | } 60 | })); 61 | 62 | //then remove the user and jwt from local storage 63 | localStorage.removeItem("user"); 64 | localStorage.removeItem("jwt"); 65 | 66 | //then refresh the page 67 | window.location.reload(); 68 | }).finally(() => { 69 | //in any case, strapify!!! 70 | strapify(); 71 | }); 72 | } else { 73 | //if there was no user in local storage, strapify!!! 74 | strapify(); 75 | } 76 | }); 77 | 78 | //when the strapify has initialized, write a message to the console 79 | document.addEventListener("strapifyInitialized", () => { 80 | if (debugMode) console.log("strapify finished"); 81 | }); 82 | 83 | //this is essentially the entry point for Strapify. It is called when the DOM is ready and user authenticated has been handled 84 | async function strapify() { 85 | //find all elements with the strapi-delete attribute and remove them 86 | const deleteElms = document.body.querySelectorAll("[strapi-delete]"); 87 | deleteElms.forEach(deleteElm => deleteElm.remove()); 88 | 89 | //find all top level elements (not descendents/processed by other strapify elements) 90 | const singleTypeElms = document.querySelectorAll(Strapify.validStrapifySingleTypeAttributes.map((attr) => `[${attr}]`).join(", ")); 91 | const collectionElms = document.body.querySelectorAll("[strapi-collection]"); 92 | const formElms = document.body.querySelectorAll("[strapi-form], [strapi-auth]"); 93 | const logoutElms = document.body.querySelectorAll("[strapi-logout]"); 94 | const ezFormsElms = Strapify.findEZFormElms(); 95 | 96 | //the elements will be processed asynchronously, so we store the promises in an array on which we can await 97 | const processPromises = [] 98 | 99 | //instantiate the strapify objects and process them 100 | for (let i = 0; i < formElms.length; i++) { 101 | const formElm = formElms[i] 102 | const strapifyForm = new StrapifyForm(formElm) 103 | processPromises.push(strapifyForm.process()) 104 | } 105 | for (let i = 0; i < singleTypeElms.length; i++) { 106 | const singleTypeElm = singleTypeElms[i] 107 | const strapifySingleType = new StrapifySingleType(singleTypeElm); 108 | processPromises.push(strapifySingleType.process()); 109 | } 110 | for (let i = 0; i < collectionElms.length; i++) { 111 | const collectionElm = collectionElms[i] 112 | const strapifyCollection = new StrapifyCollection(collectionElm); 113 | processPromises.push(strapifyCollection.process()); 114 | } 115 | for (let i = 0; i < ezFormsElms.length; i++) { 116 | const ezFormsElm = ezFormsElms[i] 117 | const strapifyEZFormsForm = new StrapifyEZFormsForm(ezFormsElm); 118 | processPromises.push(strapifyEZFormsForm.process()); 119 | } 120 | 121 | //logout elements are a simple case, so we just handle them here 122 | for (let i = 0; i < logoutElms.length; i++) { 123 | const logoutElm = logoutElms[i] 124 | const logoutRedirect = logoutElm.getAttribute("strapi-logout-redirect"); 125 | logoutElm.addEventListener("click", () => { 126 | localStorage.removeItem("user"); 127 | localStorage.removeItem("jwt"); 128 | if (logoutRedirect) { 129 | window.location = logoutRedirect; 130 | } else { 131 | window.location.reload(); 132 | } 133 | }); 134 | } 135 | 136 | //wait for all the strapify objects to finish processing 137 | await Promise.allSettled(processPromises) 138 | 139 | //webflow cancer treatment 140 | // if (window.Webflow && window.Webflow.require) { 141 | // console.log("reinitializing ix2 (in strapify-injector)") 142 | // window.Webflow.destroy(); 143 | // window.Webflow.ready(); 144 | // window.Webflow.require("ix2").init(); 145 | // document.dispatchEvent(new Event("readystatechange")); 146 | // } 147 | 148 | //dispatch the strapifyInitialized event 149 | document.dispatchEvent(new CustomEvent("strapifyInitialized", { 150 | bubbles: false, 151 | detail: { 152 | userAuthenticated: !!localStorage.getItem("user"), 153 | user: JSON.parse(localStorage.getItem("user")) 154 | } 155 | })); 156 | } -------------------------------------------------------------------------------- /strapify/src/strapify-parser.js.pegjs: -------------------------------------------------------------------------------- 1 | { 2 | const operatorDict = { 3 | "&&": "and", "||": "or", 4 | "==": "eq", "!=": "ne", 5 | "<=": "le", ">=": "ge", 6 | "<": "lt", ">": "gt" 7 | } 8 | } 9 | 10 | entry = 11 | operator / comparison 12 | 13 | group = 14 | "(" _ exp: (operator / comparison) _ ")" 15 | { 16 | return exp 17 | } 18 | 19 | /* operators */ 20 | operator = 21 | left: (group / comparison) _ op: ("||" / "&&") _ right: (group / comparison) 22 | { 23 | return { 24 | type: operatorDict[op], 25 | left: left, 26 | right: right 27 | } 28 | } 29 | 30 | comparison = 31 | left: (literal / variable) _ 32 | op: ("==" / "<=" / ">=" / "!=" / "<" / ">") _ 33 | right: (literal / variable) _ 34 | { 35 | return { 36 | type: operatorDict[op], 37 | left: left, 38 | right: right 39 | } 40 | } 41 | 42 | 43 | /* variables */ 44 | variable = 45 | variables: (simpleVariable "."?)+ 46 | { 47 | let val = "" 48 | for(let i = 0; i < variables.length; i++) { 49 | val += variables[i][0].value 50 | if(variables[i][1]) { 51 | val += "." 52 | } 53 | } 54 | 55 | return { 56 | type: "variable", 57 | value: val 58 | } 59 | } 60 | 61 | simpleVariable = 62 | variable: [A-Za-z0-9\-\_]+ 63 | { 64 | return { 65 | type: "variable", 66 | value: variable.join("") 67 | } 68 | } 69 | 70 | /* literals */ 71 | literal = 72 | val: (string / number / boolean / null) 73 | 74 | string = 75 | "'" string: [^']* "'" 76 | { 77 | return { 78 | type: "string", 79 | value: string.join("") 80 | } 81 | } 82 | 83 | null = 84 | "null" 85 | { 86 | return { 87 | type: "null", 88 | value: null 89 | } 90 | } 91 | 92 | boolean = 93 | bool: ("true" / "false") 94 | { 95 | return { 96 | type: "boolean", 97 | value: bool 98 | } 99 | } 100 | 101 | number = 102 | num: float / integer 103 | 104 | float = 105 | first: integer '.' second: integer 106 | { 107 | return { 108 | type: "float", 109 | value: first.value + "." + second.value 110 | } 111 | } 112 | 113 | integer = 114 | digits: [0-9]+ 115 | { 116 | return { 117 | type: "integer", 118 | value: digits.join("") 119 | } 120 | } 121 | 122 | _ "whitespace" 123 | = [ \t\n\r]* -------------------------------------------------------------------------------- /strapify/src/util/strapiRequest.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import Strapify from "../Strapify"; 3 | import ErrorHandler from "../StrapifyErrors"; 4 | 5 | const strapiRequest = async (slug, queryString) => { 6 | const url = `${Strapify.apiURL}${slug}${queryString ? queryString : ""}` 7 | const jwt = localStorage.getItem("jwt"); 8 | 9 | try { 10 | const headers = {} 11 | 12 | if (jwt) { 13 | headers["Authorization"] = `Bearer ${jwt}`; 14 | } 15 | 16 | const response = await axios.get( 17 | url, { 18 | headers: headers, 19 | }); 20 | return response.data; 21 | } catch (err) { 22 | if (!err.response) { 23 | ErrorHandler.toast(`An unexpected error occurred trying to fetch data from ${url}. (No response)`); 24 | console.error(err); 25 | throw err 26 | } 27 | switch (err?.response.status) { 28 | case 401: 29 | ErrorHandler.warn(`Unable to access the collection or single type: "${slug.replace("/api/", "")}" due to missing or bad authentication. (401)`) 30 | break; 31 | case 403: 32 | ErrorHandler.warn(`You are not authorized to access the collection or single type: "${slug.replace("/api/", "")}". (403)`) 33 | break; 34 | case 404: 35 | ErrorHandler.error(`Invalid collection or single type: "${slug.replace("/api/", "")}" (404)`) 36 | break; 37 | default: 38 | ErrorHandler.toast(`An unexpected error occurred trying to fetch data from ${url}. (${err.response.status})`); 39 | console.error(err); 40 | break; 41 | } 42 | throw err 43 | } 44 | }; 45 | 46 | const strapiRegister = async (formData) => { 47 | try { 48 | const response = await axios.post(`${Strapify.apiURL}/api/auth/local/register`, formData); 49 | return response.data; 50 | } catch (err) { 51 | throw err 52 | } 53 | }; 54 | 55 | const strapiAuthenticate = async (identifier, password) => { 56 | try { 57 | const response = await axios.post(`${Strapify.apiURL}/api/auth/local`, { 58 | identifier: identifier, 59 | password: password, 60 | }); 61 | return response.data; 62 | } catch (err) { 63 | throw err 64 | } 65 | } 66 | 67 | const strapiEZFormsSubmit = async (formElement) => { 68 | const formData = new FormData(formElement); 69 | const formDataJson = Object.fromEntries(formData.entries()); 70 | const jwt = localStorage.getItem("jwt"); 71 | 72 | try { 73 | const headers = {} 74 | 75 | if (jwt) { 76 | headers["Authorization"] = `Bearer ${jwt}`; 77 | } 78 | 79 | const response = await axios.post( 80 | `${Strapify.apiURL}/api/ezforms/submit`, 81 | { headers: headers, formData: formDataJson } 82 | ); 83 | return response.data; 84 | } catch (err) { 85 | throw err 86 | } 87 | } 88 | 89 | export default strapiRequest; 90 | export { strapiRequest, strapiRegister, strapiAuthenticate, strapiEZFormsSubmit } 91 | -------------------------------------------------------------------------------- /strapify/tests/DOM.test.js: -------------------------------------------------------------------------------- 1 | import { fail } from 'assert' 2 | import { DiffDOM } from "diff-dom" 3 | import htmlTemplates from './html-templates' 4 | import { writeFile, readFile, fileExists } from './util' 5 | const path = require('path') 6 | 7 | describe("DOM tests", () => { 8 | Object.keys(htmlTemplates).forEach((htmlTemplateName) => { 9 | //test each of the test definitions 10 | test(htmlTemplateName.replace(/-/g, " "), async () => { 11 | //we read the template into filePath, write the unvalidated file to unvalidatedFilePath, and compare it to the validated file at validatedFilePath 12 | const filePath = path.join(__dirname, `./html-templates/${htmlTemplateName}.html`) 13 | const unvalidatedFilePath = path.join(__dirname, `./html-tests-unvalidated/${htmlTemplateName}.html`) 14 | const validatedFilePath = path.join(__dirname, `./html-tests-validated/${htmlTemplateName}.html`) 15 | 16 | //restart puppeteer 17 | await jestPuppeteer.resetBrowser() 18 | 19 | //use this to block until strapify is finished 20 | const strapifyInitializedPromise = new Promise(async (resolve, reject) => { 21 | await page.exposeFunction("onStrapifyInitialized", () => { 22 | resolve() 23 | }) 24 | }) 25 | 26 | //add a listener for strapifyInitialized 27 | await page.evaluateOnNewDocument(() => { 28 | document.addEventListener("strapifyInitialized", () => { 29 | window.onStrapifyInitialized() 30 | }) 31 | }) 32 | 33 | //navigate to the template file 34 | await page.goto(filePath) 35 | 36 | //wait for strapify to finish 37 | await strapifyInitializedPromise 38 | 39 | //wait a bit for good measure 40 | //await page.waitForTimeout(500) 41 | 42 | //remove the strapify script element 43 | await page.evaluate(() => { 44 | document.querySelector("script").remove() 45 | }) 46 | 47 | //write the unvalidated file 48 | const pageContents = await page.content() 49 | writeFile(unvalidatedFilePath, pageContents) 50 | 51 | //check for the validated file 52 | if (!fileExists(validatedFilePath)) { 53 | fail(`No validated file to compare to. Create the validated file with path ${validatedFilePath}`) 54 | } 55 | 56 | //read the validated file 57 | const validatedFileContents = readFile(validatedFilePath) 58 | 59 | //remove all tabs, newlines and double or more spaces from the page contents and validated file contents 60 | const cleanedPageContents = pageContents.replace(/\t|\n|\s{2,}/g, '') 61 | const cleanedValidatedFileContents = validatedFileContents.replace(/\t|\n|\s{2,}/g, '') 62 | 63 | //diff the two DOMs 64 | const diff = new DiffDOM().diff(cleanedPageContents, cleanedValidatedFileContents) 65 | 66 | //if the diff is not empty, write the diff to a file 67 | if (diff.length > 0) { 68 | writeFile(path.join(__dirname, `./test-logs/${htmlTemplateName}.log`), JSON.stringify(diff, null, 2)) 69 | } 70 | 71 | await expect(diff).toEqual([]) 72 | }) 73 | }) 74 | }) -------------------------------------------------------------------------------- /strapify/tests/Strapify.test.js: -------------------------------------------------------------------------------- 1 | import Strapify from '../strapify-src/strapify'; 2 | import htmlTemplates from './html-templates' 3 | 4 | describe("Strapify.js", () => { 5 | 6 | describe("query string functions", () => { 7 | 8 | describe("getQueryStringVariables", () => { 9 | test("works with a single variable", () => { 10 | delete window.location 11 | window.location = new URL("https://www.example.com?var=Hello%20World") 12 | 13 | expect(Strapify.getQueryStringVariables()).toStrictEqual({ var: "Hello%20World" }) 14 | }) 15 | test("works with two variables", () => { 16 | delete window.location 17 | window.location = new URL("https://www.example.com?var1=Hello&var2=World") 18 | 19 | expect(Strapify.getQueryStringVariables()).toStrictEqual({ var1: "Hello", var2: "World" }) 20 | }) 21 | test("works with no variables", () => { 22 | delete window.location 23 | window.location = new URL("https://www.example.com") 24 | 25 | expect(Strapify.getQueryStringVariables()).toStrictEqual({}) 26 | }) 27 | }) 28 | 29 | describe("substituteQueryStringVariables", () => { 30 | test("works with a single variable", () => { 31 | let input = "qs.var" 32 | delete window.location 33 | window.location = new URL("https://www.example.com?var=Hello%20World") 34 | 35 | expect(Strapify.substituteQueryStringVariables(input)).toBe("Hello%20World") 36 | }) 37 | test("works with two variables", () => { 38 | let input = "qs.var1.qs.var2" 39 | delete window.location 40 | window.location = new URL("https://www.example.com?var1=Hello&var2=World") 41 | 42 | expect(Strapify.substituteQueryStringVariables(input)).toBe("Hello.World") 43 | }) 44 | test("works with no variables", () => { 45 | let input = "" 46 | delete window.location 47 | window.location = new URL("https://www.example.com?var1=Hello&var2=World") 48 | 49 | expect(Strapify.substituteQueryStringVariables(input)).toBe("") 50 | }) 51 | }) 52 | 53 | describe("removeQueryStringVariableReferences", () => { 54 | test("works with a single occurance", () => { 55 | const input = "qs.var" 56 | expect(Strapify.removeQueryStringVariableReferences(input)).toBe("") 57 | }) 58 | 59 | test("works with a double occurance", () => { 60 | const input = "qs.var.qs.var2" 61 | expect(Strapify.removeQueryStringVariableReferences(input)).toBe("") 62 | }) 63 | 64 | test("works with leading word", () => { 65 | const input = "leading.qs.var" 66 | expect(Strapify.removeQueryStringVariableReferences(input)).toBe("leading") 67 | }) 68 | 69 | test("works with trailing word", () => { 70 | const input = "qs.var.trailing" 71 | expect(Strapify.removeQueryStringVariableReferences(input)).toBe("trailing") 72 | }) 73 | 74 | test("works with leading and trailing word", () => { 75 | const input = "leading.qs.var.trailing" 76 | expect(Strapify.removeQueryStringVariableReferences(input)).toBe("leading.trailing") 77 | }) 78 | 79 | test("works with complex combination", () => { 80 | const input = "qs.var0.word0.qs.var1.word1.qs.var2.qs.var3.word2.qs.var4" 81 | expect(Strapify.removeQueryStringVariableReferences(input)).toBe("word0.word1.word2") 82 | }) 83 | 84 | }) 85 | 86 | }) 87 | }) -------------------------------------------------------------------------------- /strapify/tests/html-templates.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | 4 | const htmlTemplatePath = path.join(__dirname, 'html-templates'); 5 | const htmlTemplates = {}; 6 | 7 | //read in all the files in ./html-templates 8 | const files = fs.readdirSync(htmlTemplatePath); 9 | 10 | //for each file, read it in and add it to the htmlTemplates object 11 | files.forEach((file) => { 12 | const filePath = path.join(htmlTemplatePath, file); 13 | const fileContents = fs.readFileSync(filePath, 'utf8'); 14 | htmlTemplates[file.replace(".html", "")] = fileContents; 15 | }); 16 | 17 | function composeTemplates(templateNames, development = true) { 18 | //choose the base template 19 | const base = development ? htmlTemplates["base-development"] : htmlTemplates["base"]; 20 | 21 | //content is inserted into the base template before the occurance of this string 22 | const insertBeforeString = ""; 23 | 24 | //insert the content into the base template 25 | const insertionString = templateNames.reduce((acc, templateName) => { 26 | acc += htmlTemplates[templateName]; 27 | acc += "\n" 28 | return acc; 29 | }, ""); 30 | 31 | //insert the insertionString into the base template and return the result 32 | return base.replace(insertBeforeString, insertionString); 33 | } 34 | 35 | export default htmlTemplates 36 | -------------------------------------------------------------------------------- /strapify/tests/html-templates/collection.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |

collection test

11 | 12 |
13 |
14 |

p

15 |
16 |
17 | 18 | 19 | -------------------------------------------------------------------------------- /strapify/tests/html-templates/combined-filter-sort-page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 17 | 18 | 19 | 20 |

combined strapi-filter, strapi-sort and strapi-page test

21 | 22 |

no sort, filter, page:

23 |
24 |
25 |

name

26 |

first last

27 |

number

28 |

boolean

29 |

enum

30 |

date

31 |
32 |
33 |
34 | 35 |

sort by full_name, filter by number:

36 |
38 |
39 |

name

40 |

first last

41 |

number

42 |

boolean

43 |

enum

44 |

date

45 |
46 |
47 |
48 | 49 |

sort by date enum, filter by number or enum:

50 |
52 |
53 |

name

54 |

first last

55 |

number

56 |

boolean

57 |

enum

58 |

date

59 |
60 |
61 |
62 | 63 | 64 | -------------------------------------------------------------------------------- /strapify/tests/html-templates/components.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |

components test

11 | 12 |
13 |
14 |

p

15 | 16 |

p

17 |

p

18 | 19 |
20 |
21 |
22 | 23 | 24 | -------------------------------------------------------------------------------- /strapify/tests/html-templates/empty.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /strapify/tests/html-templates/nested-collection-deep.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |

nested collection test

11 | 12 |
13 |
14 |

p

15 |
16 | 17 |
18 |
19 |

p

20 |
21 | 22 |
23 |
24 |

p

25 |
26 | 27 |
28 |
29 |

p

30 |
31 |
32 |
33 |
34 |
35 | 36 | 37 | -------------------------------------------------------------------------------- /strapify/tests/html-templates/nested-collection.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |

nested collection test

11 | 12 |
13 |
14 |

p

15 |
16 | 17 |
18 |
19 |

p

20 |
21 |
22 |
23 | 24 | 25 | -------------------------------------------------------------------------------- /strapify/tests/html-templates/nested-collections-and-relations.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 24 | 25 | 26 | 27 |

nested collections and relations test

28 | 29 |
30 |
31 |

p

32 |
33 | 34 |
35 |
36 |
37 |

p

38 |
39 |
40 | 41 |
42 |

collection A: element number

43 |
44 |
45 |

collection B: element number

46 |
47 |
48 |
49 |
50 |
51 | 52 | 53 | -------------------------------------------------------------------------------- /strapify/tests/html-templates/single-type-components.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |

single type components test

11 | 12 |
13 |

14 | p 15 |

16 | 17 |

18 | p 19 |

20 | 21 |

23 | p 24 |

25 |
26 | 27 | 28 | -------------------------------------------------------------------------------- /strapify/tests/html-templates/strapi-background-image.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |

collection test

11 | 12 |
13 |
14 |

15 |
16 |
17 | 18 | 19 | -------------------------------------------------------------------------------- /strapify/tests/html-templates/strapi-class-add.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 30 | 31 | 32 | 33 |

strapi-class-add test

34 | 35 |
36 |
37 |

background only:

38 |
39 |

impressive div

40 |
41 | 42 |

background and font:

43 |
44 |

impressive div

45 |
46 | 47 |
48 |
49 |
50 | 51 | 52 | -------------------------------------------------------------------------------- /strapify/tests/html-templates/strapi-class-conditional.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 30 | 31 | 32 | 33 |

strapi-class-conditional test

34 | 35 |
36 |
37 |
38 |

blue if should_be_blue, transparent otherwise

39 |
40 |
41 |
42 | 43 | 44 | -------------------------------------------------------------------------------- /strapify/tests/html-templates/strapi-class-replace.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 38 | 39 | 40 | 41 |

strapi-class-replace test

42 | 43 |
44 |
45 |

background only:

46 |
47 |

impressive div

48 |
49 | 50 |

background and font:

51 |
52 |

impressive div

53 |
54 | 55 |
56 |
57 |
58 | 59 | 60 | -------------------------------------------------------------------------------- /strapify/tests/html-templates/strapi-css-rule.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |

strapi-css-rule test

11 | 12 |
13 |
14 |
15 |
16 |
17 |
18 | 19 | 20 | -------------------------------------------------------------------------------- /strapify/tests/html-templates/strapi-delete.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |

collection test

11 | 12 |
13 |
14 |

p

15 |
16 |
17 | 18 |
19 |
20 |

p

21 |
22 |
23 | 24 | 25 | -------------------------------------------------------------------------------- /strapify/tests/html-templates/strapi-field.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |

strapi field test

11 | 12 |
13 |
14 |

text

15 |

email

16 |
17 |

rich text

18 |
19 | 20 |

integer

21 |

big integer

22 |

decimal

23 |

float

24 | 25 |

enum

26 | 27 |

date

28 |

datetime

29 |

time

30 | 31 |

boolean

32 | 33 | media 34 | 35 |

video from div:

36 |
37 | 38 |

video from video:

39 | 40 | 41 |

video from iframe nested in div:

42 |
43 | 44 |
45 | 46 |

video from iframe:

47 | 48 | 49 |
50 |
51 |
52 | 53 | 54 | -------------------------------------------------------------------------------- /strapify/tests/html-templates/strapi-filter.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 17 | 18 | 19 | 20 |

strapi-filter test

21 | 22 |

no filter:

23 |
24 |
25 |

name

26 |

first last

27 |

number

28 |

boolean

29 |

enum

30 |

date

31 |
32 |
33 |
34 | 35 |

filter by full_name:

36 |
38 |
39 |

name

40 |

first last

41 |

number

42 |

boolean

43 |

enum

44 |

date

45 |
46 |
47 |
48 | 49 |

filter by full_name and age:

50 |
52 |
53 |

name

54 |

first last

55 |

number

56 |

boolean

57 |

enum

58 |

date

59 |
60 |
61 |
62 | 63 |

filter by full_name or boolean:

64 |
66 |
67 |

name

68 |

first last

69 |

number

70 |

boolean

71 |

enum

72 |

date

73 |
74 |
75 |
76 | 77 | 78 | -------------------------------------------------------------------------------- /strapify/tests/html-templates/strapi-into.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |

strapi-into test

11 | 12 |
13 |
14 |

url into src

15 | 16 | 17 |

url and alt text (multiple arguments)

18 | 19 | 20 |

url into style with template

21 | 22 | 23 |

url into style with and name (multiple arguments with template)

24 | 25 | 26 |

27 |
28 |
29 | 30 | 31 | -------------------------------------------------------------------------------- /strapify/tests/html-templates/strapi-page-and-strapi-page-size.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 17 | 18 | 19 | 20 |

strapi-page and strapi-page-size test

21 | 22 |

no pagination:

23 |
24 |
25 |

name

26 |

first last

27 |

number

28 |

boolean

29 |

enum

30 |

date

31 |
32 |
33 |
34 | 35 |

page size 3:

36 |
37 |
38 |

name

39 |

first last

40 |

number

41 |

boolean

42 |

enum

43 |

date

44 |
45 |
46 |
47 | 48 |

page size 3 on page 2:

49 |
50 |
51 |

name

52 |

first last

53 |

number

54 |

boolean

55 |

enum

56 |

date

57 |
58 |
59 |
60 | 61 | 62 | -------------------------------------------------------------------------------- /strapify/tests/html-templates/strapi-relation.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 24 | 25 | 26 | 27 |

strapi relation test

28 | 29 |

one to many with collection A as parent:

30 |
31 |
32 |

collection A: element number

33 |
34 |
35 |

collection B: element number

36 |
37 |
38 |
39 |
40 |
41 | 42 |

one to many with collection B as parent:

43 |
44 |
45 |

collection B: element number

46 |
47 |
48 |

collection A: element number

49 |
50 |
51 |
52 |
53 |
54 | 55 | 56 |

many to many with collection A as parent:

57 |
58 |
59 |

collection A: element number

60 |
61 |
62 |

collection B: element number

63 |
64 |
65 |
66 |
67 |
68 | 69 |

many to many with collection B as parent:

70 |
71 |
72 |

collection B: element number

73 |
74 |
75 |

collection A: element number

76 |
77 |
78 |
79 |
80 |
81 | 82 |

one to one with collection A as parent:

83 |
84 |
85 |

collection A: element number

86 |
87 |
88 |

collection B: element number

89 |
90 |
91 |
92 |
93 |
94 | 95 |

one to one with collection B as parent:

96 |
97 |
98 |

collection B: element number

99 |
100 |
101 |

collection A: element number

102 |
103 |
104 |
105 |
106 |
107 | 108 |

one to many A to B only:

109 |
110 |
111 |

collection A: element number

112 |
113 |
114 |

collection B: element number

115 |
116 |
117 |
118 |
119 |
120 | 121 | 122 | -------------------------------------------------------------------------------- /strapify/tests/html-templates/strapi-single-type-background-image.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |

single type background image test

11 | 12 |
13 |

this should have a background (:

14 |
15 | 16 | 17 | -------------------------------------------------------------------------------- /strapify/tests/html-templates/strapi-single-type-class-add.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 30 | 31 | 32 | 33 |

strapi-single-type-class-add test

34 | 35 |
36 |
37 |

impressive div

38 |
39 | 40 |
42 |

impressive div

43 |
44 |
45 | 46 | 47 | -------------------------------------------------------------------------------- /strapify/tests/html-templates/strapi-single-type-class-conditional.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 30 | 31 | 32 | 33 |

strapi-single-type-class-conditional test

34 | 35 |
36 |
38 |

blue if should_be_blue, transparent otherwise

39 |
40 | 41 |
43 |

red if should_be_blue, transparent otherwise

44 |
45 | 46 |
48 |

blue if should_be_blue, red otherwise

49 |
50 |
51 | 52 | 53 | -------------------------------------------------------------------------------- /strapify/tests/html-templates/strapi-single-type-class-replace.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 30 | 31 | 32 | 33 |

strapi-single-type-class-replace test

34 | 35 |
36 |
37 |

impressive div

38 |
39 | 40 |
42 |

impressive div

43 |
44 |
45 | 46 | 47 | -------------------------------------------------------------------------------- /strapify/tests/html-templates/strapi-single-type-css-rule.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |

strapi-single-type-css-rule test

11 | 12 |
13 |
14 |
15 |
16 |
17 |
18 | 19 | 20 | -------------------------------------------------------------------------------- /strapify/tests/html-templates/strapi-single-type-field.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |

strapi single type field test

11 | 12 |
13 |

text

14 |

email

15 |
16 |

rich text

17 |
18 | 19 |

integer

20 |

big integer

21 |

decimal

22 |

float

23 | 24 |

enum

25 | 26 |

date

27 |

datetime

28 |

time

29 | 30 |

boolean

31 | 32 | media 33 | 34 |

video from div:

35 |
36 | 37 |

video from video:

38 | 39 | 40 |

video from iframe nested in div:

41 |
42 | 43 |
44 | 45 |

video from iframe:

46 | 47 | 48 |
49 |
50 | 51 | 52 | -------------------------------------------------------------------------------- /strapify/tests/html-templates/strapi-single-type-into.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |

strapi-single-type-into test

11 | 12 |
13 |

url into src

14 | 15 | 16 |

url and alt text (multiple arguments)

17 | 19 | 20 |

url into style with template

21 | 23 | 24 |

url into style with and name (multiple arguments with template)

25 | 27 |
28 | 29 | 30 | -------------------------------------------------------------------------------- /strapify/tests/html-templates/strapi-single-type-relation.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 24 | 25 | 26 | 27 |

strapi-single-type-relation test

28 | 29 |

one to one:

30 |
32 |
33 |

collection B: element number

34 |
35 |
36 |
37 | 38 |

one to many:

39 |
41 |
42 |

collection B: element number

43 |
44 |
45 |
46 | 47 | 48 | -------------------------------------------------------------------------------- /strapify/tests/html-templates/strapi-single-type-repeatable.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |

strapi-single-type-repeatable test

11 | 12 |
13 |

images:

14 |
15 | 16 |
17 |
18 | 19 |
20 |

videos:

21 |
22 |
24 |
25 | 26 |
27 |

videos:

28 |
29 |

30 |
31 |
32 | 33 | 34 | -------------------------------------------------------------------------------- /strapify/tests/html-templates/strapi-sort.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 17 | 18 | 19 | 20 |

strapi-sort test

21 | 22 |

no sort:

23 |
24 |
25 |

name

26 |

first last

27 |

number

28 |

boolean

29 |

enum

30 |

date

31 |
32 |
33 |
34 | 35 |

sort by name:

36 |
37 |
38 |

name

39 |

first last

40 |

number

41 |

boolean

42 |

enum

43 |

date

44 |
45 |
46 |
47 | 48 |

sort by name and number desc:

49 |
50 |
51 |

name

52 |

first last

53 |

number

54 |

boolean

55 |

enum

56 |

date

57 |
58 |
59 |
60 | 61 | 62 | -------------------------------------------------------------------------------- /strapify/tests/html-tests-validated/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/strapify-dev/Strapify/021dfcdf383a85e79f8ad8195473e23787765881/strapify/tests/html-tests-validated/.gitkeep -------------------------------------------------------------------------------- /strapify/tests/html-tests-validated/collection.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

collection test

8 | 9 |
10 | 11 |
12 |

Collection1 - entry 1

13 |
14 |

Collection1 - entry 2

15 |
16 |

Collection1 - entry 3

17 |
18 |

Collection1 - entry 4

19 |
20 | 21 | 22 | -------------------------------------------------------------------------------- /strapify/tests/html-tests-validated/components.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

components test

8 | 9 |
10 | 11 |
12 |

1A

13 | 14 |

1A

15 |

1B

16 | 17 |
18 |
19 |

2A

20 | 21 |

2A

22 |

2B

23 | 24 |
25 |
26 |

3A

27 | 28 |

3A

29 |

3B

30 | 31 |
32 |
33 | 34 | 35 | -------------------------------------------------------------------------------- /strapify/tests/html-tests-validated/empty.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /strapify/tests/html-tests-validated/nested-collection-deep.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

nested collection test

8 | 9 |
10 | 11 | 12 |
13 |

Collection1 - entry 1

14 |
15 |

Collection1 - entry 2

16 |
17 |

Collection1 - entry 3

18 |
19 |

Collection1 - entry 4

20 |
21 | 22 | 23 |
24 |

Collection1 - entry 1

25 |
26 |

Collection1 - entry 2

27 |
28 |

Collection1 - entry 3

29 |
30 |

Collection1 - entry 4

31 |
32 | 33 | 34 |
35 |

Collection1 - entry 1

36 |
37 |

Collection1 - entry 2

38 |
39 |

Collection1 - entry 3

40 |
41 |

Collection1 - entry 4

42 |
43 | 44 |
45 |

Collection1 - entry 1

46 |
47 |

Collection1 - entry 2

48 |
49 |

Collection1 - entry 3

50 |
51 |

Collection1 - entry 4

52 |
53 |
54 |
55 |
56 | 57 | 58 | -------------------------------------------------------------------------------- /strapify/tests/html-tests-validated/nested-collection.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

nested collection test

8 | 9 |
10 | 11 | 12 |
13 |

Collection1 - entry 1

14 |
15 |

Collection1 - entry 2

16 |
17 |

Collection1 - entry 3

18 |
19 |

Collection1 - entry 4

20 |
21 | 22 |
23 |

Collection1 - entry 1

24 |
25 |

Collection1 - entry 2

26 |
27 |

Collection1 - entry 3

28 |
29 |

Collection1 - entry 4

30 |
31 |
32 | 33 | 34 | -------------------------------------------------------------------------------- /strapify/tests/html-tests-validated/nested-collections-and-relations.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 21 | 22 | 23 | 24 |

nested collections and relations test

25 | 26 |
27 | 28 | 29 |
30 |

Collection1 - entry 1

31 |
32 |

Collection1 - entry 2

33 |
34 |

Collection1 - entry 3

35 |
36 |

Collection1 - entry 4

37 |
38 |
39 | 40 |
41 |

Collection1 - entry 1

42 |
43 |

Collection1 - entry 2

44 |
45 |

Collection1 - entry 3

46 |
47 |

Collection1 - entry 4

48 |
49 | 50 | 51 |
52 |

collection A: element 0

53 |
54 | 55 |
56 |

collection B: element 0

57 |
58 |
59 |

collection A: element 1

60 |
61 | 62 |
63 |

collection B: element 1

64 |
65 |

collection B: element 2

66 |
67 |
68 |

collection A: element 2

69 |
70 | 71 |
72 |

collection B: element 3

73 |
74 |

collection B: element 4

75 |
76 |
77 |
78 | 79 | 80 | -------------------------------------------------------------------------------- /strapify/tests/html-tests-validated/single-type-components.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

single type components test

8 | 9 |
10 |

A

11 | 12 |

A

13 | 14 |

B

15 |
16 | 17 | 18 | -------------------------------------------------------------------------------- /strapify/tests/html-tests-validated/strapi-background-image.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

collection test

8 | 9 |
10 | 11 |
12 |

test text 1

13 |
14 |

test text 2

15 |
16 | 17 | 18 | -------------------------------------------------------------------------------- /strapify/tests/html-tests-validated/strapi-class-add.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 27 | 28 | 29 | 30 |

strapi-class-add test

31 | 32 |
33 | 34 |
35 |

background only:

36 |
37 |

impressive div

38 |
39 | 40 |

background and font:

41 |
42 |

impressive div

43 |
44 | 45 |
46 |
47 |

background only:

48 |
49 |

impressive div

50 |
51 | 52 |

background and font:

53 |
54 |

impressive div

55 |
56 | 57 |
58 |
59 |

background only:

60 |
61 |

impressive div

62 |
63 | 64 |

background and font:

65 |
66 |

impressive div

67 |
68 | 69 |
70 |
71 |

background only:

72 |
73 |

impressive div

74 |
75 | 76 |

background and font:

77 |
78 |

impressive div

79 |
80 | 81 |
82 |
83 | 84 | 85 | -------------------------------------------------------------------------------- /strapify/tests/html-tests-validated/strapi-class-conditional.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 27 | 28 | 29 | 30 |

strapi-class-conditional test

31 | 32 |
33 | 34 |
35 |
36 |

blue if should_be_blue, transparent otherwise

37 |
38 |
39 |
40 |

blue if should_be_blue, transparent otherwise

41 |
42 |
43 |
44 |

blue if should_be_blue, transparent otherwise

45 |
46 |
47 |
48 |

blue if should_be_blue, transparent otherwise

49 |
50 |
51 | 52 | 53 | -------------------------------------------------------------------------------- /strapify/tests/html-tests-validated/strapi-class-replace.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 35 | 36 | 37 | 38 |

strapi-class-replace test

39 | 40 |
41 | 42 |
43 |

background only:

44 |
45 |

impressive div

46 |
47 | 48 |

background and font:

49 |
50 |

impressive div

51 |
52 | 53 |
54 |
55 |

background only:

56 |
57 |

impressive div

58 |
59 | 60 |

background and font:

61 |
62 |

impressive div

63 |
64 | 65 |
66 |
67 |

background only:

68 |
69 |

impressive div

70 |
71 | 72 |

background and font:

73 |
74 |

impressive div

75 |
76 | 77 |
78 |
79 |

background only:

80 |
81 |

impressive div

82 |
83 | 84 |

background and font:

85 |
86 |

impressive div

87 |
88 | 89 |
90 |
91 | 92 | 93 | -------------------------------------------------------------------------------- /strapify/tests/html-tests-validated/strapi-css-rule.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

strapi-css-rule test

8 | 9 |
10 | 11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | 19 | 20 | -------------------------------------------------------------------------------- /strapify/tests/html-tests-validated/strapi-delete.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

collection test

8 | 9 | 10 | 11 |
12 | 13 |
14 |

Collection1 - entry 1

15 |
16 |

Collection1 - entry 2

17 |
18 |

Collection1 - entry 3

19 |
20 |

Collection1 - entry 4

21 |
22 | 23 | 24 | -------------------------------------------------------------------------------- /strapify/tests/html-tests-validated/strapi-field.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

strapi field test

8 | 9 |
10 | 11 |
12 |

test text 1

13 |

testemail1@hotmail.com

14 |

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed scelerisque, sem sit amet accumsan dictum, leo neque finibus turpis, iaculis accumsan magna lectus et tellus. Praesent maximus elementum finibus. Donec aliquet tortor justo, eu ultrices velit mollis sit amet. In vitae blandit orci. Maecenas accumsan diam ligula, eu tristique dui eleifend id.

15 |
    16 |
  1. point 1
  2. 17 |
  3. point 2
  4. 18 |
19 |
    20 |
  • point 1
  • 21 |
  • point 2
  • 22 |
23 |
24 | 25 |

1000

26 |

100000000000

27 |

0.4443

28 |

0.6666

29 | 30 |

enum value 0

31 | 32 |

2023-01-10

33 |

2023-01-26T20:14:00.000Z

34 |

00:38:00.000

35 | 36 |

false

37 | 38 | 39 | 40 |

video from div:

41 | 42 | 43 |

video from video:

44 | 45 | 46 |

video from iframe nested in div:

47 |
48 | 49 |
50 | 51 |

video from iframe:

52 | 54 | 55 |
56 |
57 |

test text 2

58 |

testemail2@hotmail.com

59 |

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed scelerisque, sem sit amet accumsan dictum, leo neque finibus turpis, iaculis accumsan magna lectus et tellus. Praesent maximus elementum finibus. Donec aliquet tortor justo, eu ultrices velit mollis sit amet. In vitae blandit orci. Maecenas accumsan diam ligula, eu tristique dui eleifend id.

60 |
    61 |
  1. point 1
  2. 62 |
  3. point 2
  4. 63 |
64 |
    65 |
  • point 1
  • 66 |
  • point 2
  • 67 |
68 |
69 | 70 |

1000

71 |

100000000000

72 |

0.4443

73 |

0.6666

74 | 75 |

enum value 0

76 | 77 |

2023-01-10

78 |

2023-01-26T20:14:00.000Z

79 |

00:38:00.000

80 | 81 |

false

82 | 83 | 84 | 85 |

video from div:

86 | 87 | 88 |

video from video:

89 | 90 | 91 |

video from iframe nested in div:

92 |
93 | 94 |
95 | 96 |

video from iframe:

97 | 99 | 100 |
101 |
102 | 103 | 104 | -------------------------------------------------------------------------------- /strapify/tests/html-tests-validated/strapi-into.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

strapi-into test

8 | 9 |
10 | 11 |
12 |

url into src

13 | 14 | 15 |

url and alt text (multiple arguments)

16 | doggy 17 | 18 |

url into style with template

19 | 20 | 21 |

url into style with and name (multiple arguments with template)

22 | 23 | 24 |

doggy

25 |
26 | 27 | 28 | -------------------------------------------------------------------------------- /strapify/tests/html-tests-validated/strapi-repeatable.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |

repeatble test

11 | 12 |
13 |
14 |

text

15 |
16 |
17 | 18 |
19 |
20 | 21 |
22 |
23 |
24 |
25 |
26 | 27 |
28 |
29 |
30 |
31 |
32 | 33 |
34 |
35 |
36 | 37 | 38 | -------------------------------------------------------------------------------- /strapify/tests/html-tests-validated/strapi-single-type-background-image.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

single type background image test

8 | 9 |
10 |

this should have a background (:

11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /strapify/tests/html-tests-validated/strapi-single-type-class-add.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 27 | 28 | 29 | 30 |

strapi-single-type-class-add test

31 | 32 |
33 |
34 |

impressive div

35 |
36 | 37 |
38 |

impressive div

39 |
40 |
41 | 42 | 43 | -------------------------------------------------------------------------------- /strapify/tests/html-tests-validated/strapi-single-type-class-conditional.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 27 | 28 | 29 | 30 |

strapi-single-type-class-conditional test

31 | 32 |
33 |
34 |

blue if should_be_blue, transparent otherwise

35 |
36 | 37 |
38 |

red if should_be_blue, transparent otherwise

39 |
40 | 41 |
42 |

blue if should_be_blue, red otherwise

43 |
44 |
45 | 46 | 47 | -------------------------------------------------------------------------------- /strapify/tests/html-tests-validated/strapi-single-type-class-replace.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 27 | 28 | 29 | 30 |

strapi-single-type-class-replace test

31 | 32 |
33 |
34 |

impressive div

35 |
36 | 37 |
38 |

impressive div

39 |
40 |
41 | 42 | 43 | -------------------------------------------------------------------------------- /strapify/tests/html-tests-validated/strapi-single-type-css-rule.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

strapi-single-type-css-rule test

8 | 9 |
10 |
11 |
12 |
13 |
14 |
15 | 16 | 17 | -------------------------------------------------------------------------------- /strapify/tests/html-tests-validated/strapi-single-type-field.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

strapi single type field test

8 | 9 |
10 |

test text 1

11 |

testemail1@hotmail.com

12 |

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed scelerisque, sem sit amet accumsan dictum, leo neque finibus turpis, iaculis accumsan magna lectus et tellus. Praesent maximus elementum finibus. Donec aliquet tortor justo, eu ultrices velit mollis sit amet. In vitae blandit orci. Maecenas accumsan diam ligula, eu tristique dui eleifend id.

13 |
    14 |
  1. point 1
  2. 15 |
  3. point 2
  4. 16 |
17 | 21 |
22 | 23 |

1000

24 |

100000000000

25 |

0.4443

26 |

0.6666

27 | 28 |

enum value 0

29 | 30 |

2023-01-10

31 |

2023-01-14T05:25:00.000Z

32 |

22:57:00.000

33 | 34 |

false

35 | 36 | 37 | 38 |

video from div:

39 | 40 | 41 |

video from video:

42 | 43 | 44 |

video from iframe nested in div:

45 |
46 | 47 |
48 | 49 |

video from iframe:

50 | 52 | 53 |
54 |
55 | 56 | 57 | -------------------------------------------------------------------------------- /strapify/tests/html-tests-validated/strapi-single-type-into.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

strapi-single-type-into test

8 | 9 |
10 |

url into src

11 | 12 | 13 |

url and alt text (multiple arguments)

14 | 15 | 16 |

url into style with template

17 | 18 | 19 |

url into style with and name (multiple arguments with template)

20 | 21 |
22 | 23 | 24 | -------------------------------------------------------------------------------- /strapify/tests/html-tests-validated/strapi-single-type-relation.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 21 | 22 | 23 | 24 |

strapi-single-type-relation test

25 | 26 |

one to one:

27 |
28 | 29 |
30 |

collection B: element 0

31 |
32 |
33 | 34 |

one to many:

35 |
36 | 37 |
38 |

collection B: element 0

39 |
40 |

collection B: element 1

41 |
42 |

collection B: element 2

43 |
44 |
45 | 46 | 47 | -------------------------------------------------------------------------------- /strapify/tests/html-tests-validated/strapi-single-type-repeatable.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

strapi-single-type-repeatable test

8 | 9 |
10 |

images:

11 | 12 |
13 | 14 |
15 | 16 |
17 | 18 |
19 |

videos:

20 | 21 |
22 |
24 |
26 | 27 |
28 |

videos:

29 | 30 |
31 |

comp0

32 |
33 |

comp1

34 |
35 | 36 | 37 | -------------------------------------------------------------------------------- /strapify/tests/manual-tests/strapi-repeatable-pagination.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |

repeatble pagination test

11 | 12 |
13 |
14 |

text

15 |
16 |
17 | 18 |
19 | 20 | 21 | 22 |
23 | 24 |
25 |
26 |
27 |
28 | 29 | 30 | 31 |
32 | 33 |
34 |
35 |
36 |
37 | 38 | 39 | 40 |
41 | 42 |
43 |
44 |
45 | 46 | 47 | -------------------------------------------------------------------------------- /strapify/tests/test-logs/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/strapify-dev/Strapify/021dfcdf383a85e79f8ad8195473e23787765881/strapify/tests/test-logs/.gitkeep -------------------------------------------------------------------------------- /strapify/tests/util.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | 3 | function writeFile(filePath, contents) { 4 | //delete the file if it already exists 5 | if (fs.existsSync(filePath)) { 6 | fs.unlinkSync(filePath, (err) => { 7 | err && console.error(err) 8 | }) 9 | } 10 | 11 | //create the file 12 | fs.writeFile(filePath, contents, (err) => { 13 | err && console.error(err) 14 | }) 15 | } 16 | 17 | function readFile(filePath) { 18 | return fs.readFileSync(filePath, 'utf8') 19 | } 20 | 21 | function fileExists(filePath) { 22 | return fs.existsSync(filePath) 23 | } 24 | 25 | export { 26 | writeFile, readFile, fileExists 27 | } -------------------------------------------------------------------------------- /strapify/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const { EnvironmentPlugin } = require('webpack'); 3 | require('dotenv').config() 4 | //const WebpackBundleAnalyzer = require('webpack-bundle-analyzer') 5 | const CompressionPlugin = require("compression-webpack-plugin"); 6 | 7 | module.exports = (env, argv) => { 8 | const plugins = []; 9 | if (env.analyze) { 10 | // plugins.push(new WebpackBundleAnalyzer.BundleAnalyzerPlugin()); 11 | } 12 | if (env.gzip) { 13 | plugins.push(new CompressionPlugin({ 14 | algorithm: 'gzip', 15 | })); 16 | } 17 | 18 | 19 | 20 | const version = process.env.VERSION; 21 | if (!version) throw new Error("VERSION environment variable not set. Please set it in the .env file."); 22 | 23 | plugins.push(new EnvironmentPlugin({ 24 | VERSION: version, 25 | DEBUG: false 26 | })); 27 | 28 | return { 29 | entry: [path.resolve(__dirname, './src/injector.js')], 30 | output: { 31 | path: path.resolve(__dirname, './bundle'), 32 | filename: argv.mode === "development" ? `main.js` : `strapify-v${version}.js` 33 | }, 34 | plugins: plugins, 35 | devtool: 'source-map', 36 | module: { 37 | rules: [ 38 | { 39 | test: /\.m?js$/, 40 | exclude: /node_modules/, 41 | use: { 42 | loader: 'babel-loader', 43 | options: { 44 | presets: [ 45 | ['@babel/preset-env', { targets: "defaults" }], 46 | ] 47 | } 48 | } 49 | } 50 | ] 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /test-client/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | public/build/bundle.* 4 | -------------------------------------------------------------------------------- /test-client/README.md: -------------------------------------------------------------------------------- 1 | # test client 2 | -------------------------------------------------------------------------------- /test-client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "svelte-app", 3 | "version": "1.0.0", 4 | "devDependencies": { 5 | "cross-env": "^7.0.3", 6 | "css-loader": "^5.0.1", 7 | "file-loader": "^6.2.0", 8 | "mini-css-extract-plugin": "^1.3.4", 9 | "svelte": "^3.31.2", 10 | "svelte-loader": "^3.0.0", 11 | "webpack": "^5.16.0", 12 | "webpack-cli": "^4.4.0", 13 | "webpack-dev-server": "^4.11.1" 14 | }, 15 | "scripts": { 16 | "build": "cross-env NODE_ENV=production webpack", 17 | "dev": "webpack serve" 18 | }, 19 | "dependencies": { 20 | "debounce": "^1.2.1", 21 | "url-exist": "^3.0.1" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /test-client/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | Svelte app 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /test-client/src/components/App.svelte: -------------------------------------------------------------------------------- 1 | 94 | 95 |
96 |
97 | 98 |
99 |
100 | 103 | {#if downloadURL} 104 | Download 109 | {/if} 110 |
111 | 112 |
113 | {#if successMessage && successMessage !== "waiting for url" && successMessage !== "webflow url successfully downloaded"} 114 |

{successMessage}

115 | {/if} 116 | 117 | {#if successMessage === "webflow url successfully downloaded"} 118 |