Repository: givanz/VvvebJs Branch: master Commit: 1b774684044f Files: 291 Total size: 33.1 MB Directory structure: gitextract_mj6sw9pv/ ├── .dockerignore ├── .gitattributes ├── .github/ │ ├── FUNDING.yml │ ├── lock.yml │ └── no-response.yml ├── .gitignore ├── .gitmodules ├── Credits.md ├── Dockerfile ├── LICENSE ├── README.md ├── css/ │ ├── editor.css │ └── vvvebjs-editor-helpers.css ├── demo/ │ ├── album/ │ │ └── index.html │ ├── blog/ │ │ ├── blog.css │ │ └── index.html │ ├── carousel/ │ │ ├── carousel.css │ │ └── index.html │ ├── narrow-jumbotron/ │ │ ├── index.html │ │ └── narrow-jumbotron.css │ ├── offcanvas/ │ │ ├── index.html │ │ ├── offcanvas.css │ │ └── offcanvas.js │ ├── pricing/ │ │ ├── index.html │ │ └── pricing.css │ └── product/ │ ├── index.html │ └── product.css ├── docker-compose.yml ├── editor.html ├── editor.php ├── gulpfile.js ├── libs/ │ ├── aos/ │ │ ├── aos.css │ │ └── aos.js │ ├── autocomplete/ │ │ └── autocomplete.js │ ├── builder/ │ │ ├── blocks-bootstrap4.js │ │ ├── builder.js │ │ ├── components-bootstrap4.js │ │ ├── components-bootstrap5.js │ │ ├── components-common.js │ │ ├── components-elements.js │ │ ├── components-embeds.js │ │ ├── components-html.js │ │ ├── components-server.js │ │ ├── components-widgets.js │ │ ├── inputs.js │ │ ├── oembed.js │ │ ├── plugin-ai-assistant.js │ │ ├── plugin-aos.js │ │ ├── plugin-bootstrap-colorpicker.js │ │ ├── plugin-ckeditor.js │ │ ├── plugin-codemirror.js │ │ ├── plugin-coloris.js │ │ ├── plugin-google-fonts.js │ │ ├── plugin-jszip.js │ │ ├── plugin-media.js │ │ ├── plugin-tinymce.js │ │ ├── section.js │ │ ├── sections-bootstrap4.js │ │ └── undo.js │ ├── codemirror/ │ │ ├── lib/ │ │ │ ├── clike.js │ │ │ ├── codemirror.css │ │ │ ├── codemirror.js │ │ │ ├── css.js │ │ │ ├── formatting.js │ │ │ ├── htmlmixed.js │ │ │ ├── search.js │ │ │ ├── searchcursor.js │ │ │ └── xml.js │ │ └── theme/ │ │ ├── duotone-dark.css │ │ └── material.css │ ├── coloris/ │ │ ├── coloris.css │ │ └── coloris.js │ ├── jszip/ │ │ ├── filesaver.js │ │ └── jszip.js │ └── media/ │ ├── media.css │ ├── media.js │ └── openverse.js ├── media/ │ └── sample.webm ├── new-page-blank-template.html ├── package.json ├── resources/ │ ├── google-fonts.json │ ├── line-awesome.html │ └── svg/ │ ├── icons/ │ │ ├── 150-outlined-icons/ │ │ │ ├── index.html │ │ │ └── readme.txt │ │ ├── 77_essential_icons/ │ │ │ ├── index.html │ │ │ └── readme.txt │ │ ├── ant-design-icons/ │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ └── index.html │ │ ├── boxicons/ │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ └── index.html │ │ ├── clarity-icons/ │ │ │ ├── LICENSE │ │ │ ├── NOTICE.txt │ │ │ ├── README.md │ │ │ └── index.html │ │ ├── coreui-icons/ │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ └── index.html │ │ ├── css.gg/ │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ └── index.html │ │ ├── dripicons/ │ │ │ ├── LICENSE │ │ │ ├── index.html │ │ │ └── readme.txt │ │ ├── elegant-font/ │ │ │ ├── gpl_license.txt │ │ │ ├── index.html │ │ │ └── mit_license.txt │ │ ├── eva-icons/ │ │ │ ├── LICENSE.txt │ │ │ ├── README.md │ │ │ └── index.html │ │ ├── feather/ │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ └── index.html │ │ ├── font-awesome/ │ │ │ ├── LICENSE.txt │ │ │ └── index.html │ │ ├── heroicons/ │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ └── index.html │ │ ├── iconoir/ │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ └── index.html │ │ ├── iconsax/ │ │ │ ├── index.html │ │ │ └── license.txt │ │ ├── ikonate/ │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ └── index.html │ │ ├── ionicons/ │ │ │ ├── LICENSE │ │ │ ├── index.html │ │ │ └── readme.md │ │ ├── jam-icons/ │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ └── index.html │ │ ├── linea/ │ │ │ ├── index.html │ │ │ ├── read_me.txt │ │ │ └── style.css │ │ ├── lineawesome/ │ │ │ ├── LICENSE.txt │ │ │ └── index.html │ │ ├── material-design/ │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ └── index.html │ │ ├── octicons/ │ │ │ ├── LICENSE │ │ │ ├── README.md │ │ │ └── index.html │ │ ├── olicons/ │ │ │ ├── README.md │ │ │ └── index.html │ │ ├── open-iconic/ │ │ │ ├── ICON-LICENSE │ │ │ ├── README.md │ │ │ └── index.html │ │ ├── pe-icon-7-stroke/ │ │ │ ├── index.html │ │ │ └── read-me.txt │ │ ├── remix-icon/ │ │ │ ├── README.md │ │ │ └── index.html │ │ ├── system-uicons/ │ │ │ ├── LICENSE │ │ │ └── index.html │ │ ├── tabler-icons/ │ │ │ ├── LICENSE │ │ │ ├── LICENSE.txt │ │ │ ├── README.md │ │ │ └── index.html │ │ ├── themify/ │ │ │ ├── index.html │ │ │ └── readme.txt │ │ └── unicons/ │ │ ├── LICENSE │ │ ├── README.md │ │ └── index.html │ └── separators/ │ └── digital-red-panther/ │ └── index.html ├── save.js ├── save.php ├── scan.php ├── scss/ │ ├── _autocomplete.scss │ ├── _bootstrap-css-vars.scss │ ├── _builder.scss │ ├── _csstree.scss │ ├── bootstrap/ │ │ ├── _accordion.scss │ │ ├── _alert.scss │ │ ├── _badge.scss │ │ ├── _breadcrumb.scss │ │ ├── _button-group.scss │ │ ├── _buttons.scss │ │ ├── _card.scss │ │ ├── _carousel.scss │ │ ├── _close.scss │ │ ├── _containers.scss │ │ ├── _dropdown.scss │ │ ├── _forms.scss │ │ ├── _functions.scss │ │ ├── _grid.scss │ │ ├── _helpers.scss │ │ ├── _images.scss │ │ ├── _list-group.scss │ │ ├── _maps.scss │ │ ├── _mixins.scss │ │ ├── _modal.scss │ │ ├── _nav.scss │ │ ├── _navbar.scss │ │ ├── _offcanvas.scss │ │ ├── _pagination.scss │ │ ├── _placeholders.scss │ │ ├── _popover.scss │ │ ├── _progress.scss │ │ ├── _reboot.scss │ │ ├── _root.scss │ │ ├── _spinners.scss │ │ ├── _tables.scss │ │ ├── _toasts.scss │ │ ├── _tooltip.scss │ │ ├── _transitions.scss │ │ ├── _type.scss │ │ ├── _utilities.scss │ │ ├── _variables-dark.scss │ │ ├── _variables.scss │ │ ├── bootstrap-grid.scss │ │ ├── bootstrap-reboot.scss │ │ ├── bootstrap-utilities.scss │ │ ├── bootstrap.scss │ │ ├── forms/ │ │ │ ├── _floating-labels.scss │ │ │ ├── _form-check.scss │ │ │ ├── _form-control.scss │ │ │ ├── _form-range.scss │ │ │ ├── _form-select.scss │ │ │ ├── _form-text.scss │ │ │ ├── _input-group.scss │ │ │ ├── _labels.scss │ │ │ └── _validation.scss │ │ ├── helpers/ │ │ │ ├── _clearfix.scss │ │ │ ├── _color-bg.scss │ │ │ ├── _colored-links.scss │ │ │ ├── _focus-ring.scss │ │ │ ├── _icon-link.scss │ │ │ ├── _position.scss │ │ │ ├── _ratio.scss │ │ │ ├── _stacks.scss │ │ │ ├── _stretched-link.scss │ │ │ ├── _text-truncation.scss │ │ │ ├── _visually-hidden.scss │ │ │ └── _vr.scss │ │ ├── mixins/ │ │ │ ├── _alert.scss │ │ │ ├── _backdrop.scss │ │ │ ├── _banner.scss │ │ │ ├── _border-radius.scss │ │ │ ├── _box-shadow.scss │ │ │ ├── _breakpoints.scss │ │ │ ├── _buttons.scss │ │ │ ├── _caret.scss │ │ │ ├── _clearfix.scss │ │ │ ├── _color-mode.scss │ │ │ ├── _color-scheme.scss │ │ │ ├── _container.scss │ │ │ ├── _deprecate.scss │ │ │ ├── _forms.scss │ │ │ ├── _gradients.scss │ │ │ ├── _grid.scss │ │ │ ├── _image.scss │ │ │ ├── _list-group.scss │ │ │ ├── _lists.scss │ │ │ ├── _pagination.scss │ │ │ ├── _reset-text.scss │ │ │ ├── _resize.scss │ │ │ ├── _table-variants.scss │ │ │ ├── _text-truncate.scss │ │ │ ├── _transition.scss │ │ │ ├── _utilities.scss │ │ │ └── _visually-hidden.scss │ │ ├── tests/ │ │ │ ├── jasmine.js │ │ │ ├── mixins/ │ │ │ │ ├── _auto-import-of-variables-dark.test.scss │ │ │ │ ├── _color-modes.test.scss │ │ │ │ ├── _media-query-color-mode-full.test.scss │ │ │ │ └── _utilities.test.scss │ │ │ ├── sass-true/ │ │ │ │ ├── register.js │ │ │ │ └── runner.js │ │ │ └── utilities/ │ │ │ └── _api.test.scss │ │ ├── utilities/ │ │ │ └── _api.scss │ │ └── vendor/ │ │ └── _rfs.scss │ ├── components/ │ │ └── gallery.scss │ ├── editor.scss │ ├── ionicons.css │ ├── line-awesome/ │ │ ├── _bordered_pulled.scss │ │ ├── _core.scss │ │ ├── _fixed-width.scss │ │ ├── _icons.scss │ │ ├── _larger.scss │ │ ├── _list.scss │ │ ├── _mixins.scss │ │ ├── _path.scss │ │ ├── _rotated-flipped.scss │ │ ├── _screen-reader.scss │ │ ├── _stacked.scss │ │ ├── _variables.scss │ │ ├── dist/ │ │ │ └── line-awesome/ │ │ │ └── css/ │ │ │ └── line-awesome.css │ │ └── line-awesome.scss │ └── vvvebjs-editor-helpers.scss └── upload.php ================================================ FILE CONTENTS ================================================ ================================================ FILE: .dockerignore ================================================ * ================================================ FILE: .gitattributes ================================================ * linguist-vendored *.js linguist-vendored=false ================================================ FILE: .github/FUNDING.yml ================================================ # These are supported funding model platforms custom: https://paypal.me/zgivan open_collective: vvvebjs ================================================ FILE: .github/lock.yml ================================================ # Configuration for Lock Threads - https://github.com/dessant/lock-threads # Number of days of inactivity before a closed issue or pull request is locked daysUntilLock: 365 # Skip issues and pull requests created before a given timestamp. Timestamp must # follow ISO 8601 (`YYYY-MM-DD`). Set to `false` to disable skipCreatedBefore: false # Issues and pull requests with these labels will be ignored. Set to `[]` to disable exemptLabels: - no-locking - help-wanted # Label to add before locking, such as `outdated`. Set to `false` to disable lockLabel: outdated # Comment to post before locking. Set to `false` to disable lockComment: > This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs. # Assign `resolved` as the reason for locking. Set to `false` to disable setLockReason: true # Limit to only `issues` or `pulls` # only: issues # Optionally, specify configuration settings just for `issues` or `pulls` # issues: # exemptLabels: # - help-wanted # lockLabel: outdated # pulls: # daysUntilLock: 30 # Repository to extend settings from # _extends: repo ================================================ FILE: .github/no-response.yml ================================================ # Configuration for probot-no-response - https://github.com/probot/no-response # Number of days of inactivity before an Issue is closed for lack of response daysUntilClose: 10 # Label requiring a response responseRequiredLabel: more-information-needed # Comment to post when closing an Issue for lack of response. Set to `false` to disable closeComment: > This issue has been automatically closed because there has been no response to our request for more information from the original author. With only the information that is currently in the issue, we don't have enough information to take action. Please reach out if you have or find the answers we need so that we can investigate further. ================================================ FILE: .gitignore ================================================ node_modules *.css.map *.js.map ================================================ FILE: .gitmodules ================================================ [submodule "demo/landing"] path = demo/landing url = https://github.com/givanz/landing ================================================ FILE: Credits.md ================================================ # Credits ## SVG icons * Eva Icons - MIT License - https://github.com/akveo/eva-icons * IonIcons - MIT license - https://ionic.io/ionicons * Freebiesbug - CC BY - https://freebiesbug.com/psd-freebies/150-free-outlined-icons-psd-ai-svg-webfont/ * Line Awesome - MIT license - https://icons8.com/line-awesome * 77 Essential Icons - CC BY 4.0 - https://dribbble.com/shots/1934932-77-Essential-Icons-Free-Download * Dripicons - CC BY 4.0 - https://github.com/amitjakhu/dripicons * Themify Icons - MIT License - https://themify.me/themify-icons * Feather - MIT License - https://github.com/feathericons/feather * Boxicons - MIT License - https://boxicons.com/ * Linea - CC BY 4.0 - https://www.linea.is/ * Core UI - CC BY 4.0 - https://github.com/coreui/coreui-icons * Open Iconic - MIT License - https://useiconic.com/open/ * Font Awesome - CC BY 4.0 https://github.com/FortAwesome/Font-Awesome * Elegant Icon Font - MIT License - https://www.elegantthemes.com/blog/resources/elegant-icon-font * Material Design - Apache 2.0 - https://github.com/Templarian/MaterialDesign * RemixIcon - Apache 2.0 License -https://github.com/Iconscout/unicons * Unicons - Apache 2.0 License -https://github.com/Remix-Design/remixicon * clarity-icons - MIT License - https://clarity.design/foundation/icons/ * Jam icons - MIT License - https://v2.jam-icons.com/ * Ant Design SVG icons - MIT License - https://github.com/ant-design/ant-design-icons * Olicons - CC BY - https://github.com/owlling/olicons * Css.gg - MIT License -https://github.com/astrit/css.gg * Tabler icons - MIT License - https://github.com/tabler/tabler-icons * Ikonate - MIT License - https://github.com/mikolajdobrucki/ikonate * iconoir - MIT License - https://github.com/lucaburgio/iconoir * octicons - MIT License - https://github.com/primer/octicons * heroicons - MIT License - https://github.com/tailwindlabs/heroicons * Health Icons - MIT License - https://github.com/resolvetosavelives/healthicons * iconsax - Custom Free License - https://github.com/lusaxweb/iconsax * System-uicons - Unilicense - https://github.com/CoreyGinnivan/system-uicons ================================================ FILE: Dockerfile ================================================ FROM php:8.3-apache ARG UNAME=www-data ARG UGROUP=www-data ARG UID=1000 ARG GID=1001 RUN usermod --uid $UID $UNAME RUN groupmod --gid $GID $UGROUP ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS ================================================ FILE: README.md ================================================ # VvvebJs

Vvveb

Drag and drop page builder javascript library.
Built with Vanilla Js with no dependencies or build tools and Bootstrap 5

Website | Documentation | Forum | Twitter

## [Live Demo](https://www.vvveb.com/vvvebjs/editor.html) For a full featured Open Source CMS using VvvebJs page builder check [Vvveb CMS](https://github.com/givanz/Vvveb) Using [Vvveb landing page template](https://github.com/givanz/landing) for demo page and Bootstrap 5 sections and blocks. ### Features * Components and blocks/snippets drag and drop and in page insert. * Undo/Redo operations. * One or two panels interface. * File manager and component hierarchy navigation. * Add new page modal with template and folder options. * Live code editor with codemirror plugin syntax highlighting. * Image upload with example php script included. * Page download or export html or save page on server with example php script included. * Components/Sections/Blocks list search. * Bootstrap 5 components. * Media gallery with integrated CC0 image search and server upload support. * Image, video and iframe elements resize handles. * Elements breadcrumb for easier parent elements selection. * Full Google fonts list support for font selection. * Youtube, Google maps, Charts.js etc widgets. * Optional CKEditor plugin to replace builtin text editor. * Zip download plugin to download the page and all assets as zip file. * SVG Icon component bundled with hundreds of free icons. * Animate on scroll support for page elements. * Theme global typography and color pallette editor. By default the editor comes with Bootstrap 5 and Widgets components and can be extended with any kind of components and inputs. ## Install * Clone the repository ```bash #git 2.13+ git clone --recurse-submodules https://github.com/givanz/VvvebJs # older git versions git clone --recursive https://github.com/givanz/VvvebJs ``` * Pull changes ```bash git pull --recurse-submodules ``` ## Usage Clone the repository or download a release then open `editor.html` Because of browser iframe security you need to use a webserver such as apache/xampp and open `http://localhost/editor.html` To disable browser security and open `editor.html` without installing a webserver run chrome with ```bash chrome --disable-web-security --user-data-dir=/tmp/temporary_profile editor.html ``` To use the image upload or page save feature you need to have php installed. ## Docker ### Local development From VvvebJs folder run ```bash docker-compose up ``` ### Image Or run image ```bash docker run -p 8080:80 vvveb/vvvebjs ``` Open http://localhost:8080/editor.php or http://localhost:8080/editor.html ## Save page Save page function needs either php or node ### PHP If you use docker, xampp or a shared hosting account php should work without any change. Saving is done using [save.php](save.php) ### Node For node go to VvvebJs folder and run ```bash npm install express node save.js ``` Open http://localhost:8080/editor.html Saving is done using [save.js](save.js) ## [Landing template](https://github.com/givanz/landing) To make changes to template files or sections run the following commands from `demo/landing` folder ### Install gulp ```bash npm i ``` ### Generate html files Template html partials are located in `demo/landing/src` folder. ```bash npm run gulp ``` ### Watch for changes for development ```bash npm run gulp watch ``` ### Generate sections list for page builder Sections html files are located in `demo/landing/src/sections` folder grouped in folders with the section group name. ```bash npm run gulp sections ``` ### Generate blocks list Blocks html files are located in `demo/landing/src/blocks` folder grouped in folders with the blocks group name. ```bash npm run gulp blocks ``` ### Generate screenshots for sections ```bash npm run gulp screenshots ``` ## Usage ### Initialize example ```js ================================================ FILE: demo/blog/blog.css ================================================ /* stylelint-disable selector-list-comma-newline-after */ .blog-header { line-height: 1; border-bottom: 1px solid #e5e5e5; } .blog-header-logo { font-family: "Playfair Display", Georgia, "Times New Roman", serif/*rtl:Amiri, Georgia, "Times New Roman", serif*/; font-size: 2.25rem; } .blog-header-logo:hover { text-decoration: none; } h1, h2, h3, h4, h5, h6 { font-family: "Playfair Display", Georgia, "Times New Roman", serif/*rtl:Amiri, Georgia, "Times New Roman", serif*/; } .display-4 { font-size: 2.5rem; } @media (min-width: 768px) { .display-4 { font-size: 3rem; } } .nav-scroller { position: relative; z-index: 2; height: 2.75rem; overflow-y: hidden; } .nav-scroller .nav { display: flex; flex-wrap: nowrap; padding-bottom: 1rem; margin-top: -1px; overflow-x: auto; text-align: center; white-space: nowrap; -webkit-overflow-scrolling: touch; } .nav-scroller .nav-link { padding-top: .75rem; padding-bottom: .75rem; font-size: .875rem; } .card-img-right { height: 100%; border-radius: 0 3px 3px 0; } .flex-auto { flex: 0 0 auto; } .h-250 { height: 250px; } @media (min-width: 768px) { .h-md-250 { height: 250px; } } /* Pagination */ .blog-pagination { margin-bottom: 4rem; } .blog-pagination > .btn { border-radius: 2rem; } /* * Blog posts */ .blog-post { margin-bottom: 4rem; } .blog-post-title { margin-bottom: .25rem; font-size: 2.5rem; } .blog-post-meta { margin-bottom: 1.25rem; color: #727272; } /* * Footer */ .blog-footer { padding: 2.5rem 0; color: #727272; text-align: center; background-color: #f9f9f9; border-top: .05rem solid #e5e5e5; } .blog-footer p:last-child { margin-bottom: 0; } ================================================ FILE: demo/blog/index.html ================================================ Blog Template for Bootstrap

Title of a longer featured blog post

Multiple lines of text that form the lede, informing new readers quickly and efficiently about what’s most interesting in this post’s contents.

Continue reading...

World

Featured post

Nov 12

This is a wider card with supporting text below as a natural lead-in to additional content.

Continue reading
Card image cap
Design

Post title

Nov 11

This is a wider card with supporting text below as a natural lead-in to additional content.

Continue reading
Card image cap

From the Firehose

Sample blog post

This blog post shows a few different types of content that’s supported and styled with Bootstrap. Basic typography, images, and code are all supported.


Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Sed posuere consectetur est at lobortis. Cras mattis consectetur purus sit amet fermentum.

Curabitur blandit tempus porttitor. Nullam quis risus eget urna mollis ornare vel eu leo. Nullam id dolor id nibh ultricies vehicula ut id elit.

Etiam porta sem malesuada magna mollis euismod. Cras mattis consectetur purus sit amet fermentum. Aenean lacinia bibendum nulla sed consectetur.

Heading

Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Morbi leo risus, porta ac consectetur ac, vestibulum at eros.

Sub-heading

Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.

Example code block

Aenean lacinia bibendum nulla sed consectetur. Etiam porta sem malesuada magna mollis euismod. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa.

Sub-heading

Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aenean lacinia bibendum nulla sed consectetur. Etiam porta sem malesuada magna mollis euismod. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus.

  • Praesent commodo cursus magna, vel scelerisque nisl consectetur et.
  • Donec id elit non mi porta gravida at eget metus.
  • Nulla vitae elit libero, a pharetra augue.

Donec ullamcorper nulla non metus auctor fringilla. Nulla vitae elit libero, a pharetra augue.

  1. Vestibulum id ligula porta felis euismod semper.
  2. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
  3. Maecenas sed diam eget risus varius blandit sit amet non magna.

Cras mattis consectetur purus sit amet fermentum. Sed posuere consectetur est at lobortis.

Another blog post

Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Sed posuere consectetur est at lobortis. Cras mattis consectetur purus sit amet fermentum.

Curabitur blandit tempus porttitor. Nullam quis risus eget urna mollis ornare vel eu leo. Nullam id dolor id nibh ultricies vehicula ut id elit.

Etiam porta sem malesuada magna mollis euismod. Cras mattis consectetur purus sit amet fermentum. Aenean lacinia bibendum nulla sed consectetur.

Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Morbi leo risus, porta ac consectetur ac, vestibulum at eros.

New feature

Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aenean lacinia bibendum nulla sed consectetur. Etiam porta sem malesuada magna mollis euismod. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus.

  • Praesent commodo cursus magna, vel scelerisque nisl consectetur et.
  • Donec id elit non mi porta gravida at eget metus.
  • Nulla vitae elit libero, a pharetra augue.

Etiam porta sem malesuada magna mollis euismod. Cras mattis consectetur purus sit amet fermentum. Aenean lacinia bibendum nulla sed consectetur.

Donec ullamcorper nulla non metus auctor fringilla. Nulla vitae elit libero, a pharetra augue.

About

Etiam porta sem malesuada magna mollis euismod. Cras mattis consectetur purus sit amet fermentum. Aenean lacinia bibendum nulla sed consectetur.

Elsewhere

  1. GitHub
  2. Twitter
  3. Facebook
================================================ FILE: demo/carousel/carousel.css ================================================ /* GLOBAL STYLES -------------------------------------------------- */ /* Padding below the footer and lighter body text */ body { padding-top: 3rem; padding-bottom: 3rem; color: #5a5a5a; } /* CUSTOMIZE THE CAROUSEL -------------------------------------------------- */ /* Carousel base class */ .carousel { margin-bottom: 4rem; } /* Since positioning the image, we need to help out the caption */ .carousel-caption { bottom: 3rem; z-index: 10; } /* Declare heights because of positioning of img element */ .carousel-item { height: 32rem; } .carousel-item > img { position: absolute; top: 0; left: 0; min-width: 100%; height: 32rem; } /* MARKETING CONTENT -------------------------------------------------- */ /* Center align the text within the three columns below the carousel */ .marketing .col-lg-4 { margin-bottom: 1.5rem; text-align: center; } .marketing h2 { font-weight: 400; } /* rtl:begin:ignore */ .marketing .col-lg-4 p { margin-right: .75rem; margin-left: .75rem; } /* rtl:end:ignore */ /* Featurettes ------------------------- */ .featurette-divider { margin: 5rem 0; /* Space out the Bootstrap
more */ } /* Thin out the marketing headings */ .featurette-heading { font-weight: 300; line-height: 1; /* rtl:remove */ letter-spacing: -.05rem; } /* RESPONSIVE CSS -------------------------------------------------- */ @media (min-width: 40em) { /* Bump up size of carousel content */ .carousel-caption p { margin-bottom: 1.25rem; font-size: 1.25rem; line-height: 1.4; } .featurette-heading { font-size: 50px; } } @media (min-width: 62em) { .featurette-heading { margin-top: 7rem; } } ================================================ FILE: demo/carousel/index.html ================================================ Carousel Template for Bootstrap
Generic placeholder image

Heading

Donec sed odio dui. Etiam porta sem malesuada magna mollis euismod. Nullam id dolor id nibh ultricies vehicula ut id elit. Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Praesent commodo cursus magna.

View details »

Generic placeholder image

Heading

Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio sem nec elit. Cras mattis consectetur purus sit amet fermentum. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh.

View details »

Generic placeholder image

Heading

Donec sed odio dui. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Vestibulum id ligula porta felis euismod semper. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus.

View details »


First featurette heading. It'll blow your mind.

Donec ullamcorper nulla non metus auctor fringilla. Vestibulum id ligula porta felis euismod semper. Praesent commodo cursus magna, vel scelerisque nisl consectetur. Fusce dapibus, tellus ac cursus commodo.

Generic placeholder image

Oh yeah, it's that good. See for yourself.

Donec ullamcorper nulla non metus auctor fringilla. Vestibulum id ligula porta felis euismod semper. Praesent commodo cursus magna, vel scelerisque nisl consectetur. Fusce dapibus, tellus ac cursus commodo.

Generic placeholder image

And lastly, this one. Checkmate.

Donec ullamcorper nulla non metus auctor fringilla. Vestibulum id ligula porta felis euismod semper. Praesent commodo cursus magna, vel scelerisque nisl consectetur. Fusce dapibus, tellus ac cursus commodo.

Generic placeholder image

================================================ FILE: demo/narrow-jumbotron/index.html ================================================ Narrow Jumbotron Template for Bootstrap

Project name

Jumbotron heading !!!

Cras justo odio, dapibus ac facilisis in, egestas eget quam. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus.

Sign up today

Subheading

Donec id elit non mi porta gravida at eget metus. Maecenas faucibus mollis interdum.

Subheading

Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Cras mattis consectetur purus sit amet fermentum.

Subheading

Maecenas sed diam eget risus varius blandit sit amet non magna.

Subheading

Donec id elit non mi porta gravida at eget metus. Maecenas faucibus mollis interdum.

Subheading

Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Cras mattis consectetur purus sit amet fermentum.

Subheading

Maecenas sed diam eget risus varius blandit sit amet non magna.

================================================ FILE: demo/narrow-jumbotron/narrow-jumbotron.css ================================================ /* Space out content a bit */ body { padding-top: 1.5rem; padding-bottom: 1.5rem; background-color:#fff; } /* Everything but the jumbotron gets side spacing for mobile first views */ .header, .marketing, .footer { padding-right: 1rem; padding-left: 1rem; } /* Custom page header */ .header { padding-bottom: 1rem; border-bottom: .05rem solid #e5e5e5; } /* Make the masthead heading the same height as the navigation */ .header h3 { margin-top: 0; margin-bottom: 0; line-height: 3rem; } /* Custom page footer */ .footer { padding-top: 1.5rem; color: #777; border-top: .05rem solid #e5e5e5; } /* Customize container */ @media (min-width: 48em) { .container { max-width: 46rem; } } .container-narrow > hr { margin: 2rem 0; } /* Main marketing message and sign up button */ .jumbotron { text-align: center; border-bottom: .05rem solid #e5e5e5; } /* Supporting marketing content */ .marketing { margin: 3rem 0; } .marketing p + h4 { margin-top: 1.5rem; } /* Responsive: Portrait tablets and up */ @media screen and (min-width: 48em) { /* Remove the padding we set earlier */ .header, .marketing, .footer { padding-right: 0; padding-left: 0; } /* Space out the masthead */ .header { margin-bottom: 2rem; } /* Remove the bottom border on the jumbotron for visual effect */ .jumbotron { border-bottom: 0; } } ================================================ FILE: demo/offcanvas/index.html ================================================ Offcanvas template for Bootstrap

Bootstrap

Since 2011
Recent updates

@username Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus.

@username Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus.

@username Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus.

All updates
Suggestions
Full Name Follow
@username
Full Name Follow
@username
Full Name Follow
@username
All suggestions
================================================ FILE: demo/offcanvas/offcanvas.css ================================================ html, body { overflow-x: hidden; /* Prevent scroll on narrow devices */ } body { padding-top: 56px; } @media (max-width: 991.98px) { .offcanvas-collapse { position: fixed; top: 56px; /* Height of navbar */ bottom: 0; left: 100%; width: 100%; padding-right: 1rem; padding-left: 1rem; overflow-y: auto; visibility: hidden; background-color: #343a40; transition: transform .3s ease-in-out, visibility .3s ease-in-out; } .offcanvas-collapse.open { visibility: visible; transform: translateX(-100%); } } .nav-scroller { position: relative; z-index: 2; height: 2.75rem; overflow-y: hidden; } .nav-scroller .nav { display: flex; flex-wrap: nowrap; padding-bottom: 1rem; margin-top: -1px; overflow-x: auto; color: rgba(255, 255, 255, .75); text-align: center; white-space: nowrap; -webkit-overflow-scrolling: touch; } .nav-underline .nav-link { padding-top: .75rem; padding-bottom: .75rem; font-size: .875rem; color: #6c757d; } .nav-underline .nav-link:hover { color: #007bff; } .nav-underline .active { font-weight: 500; color: #343a40; } .text-white-50 { color: rgba(255, 255, 255, .5); } .bg-purple { background-color: #6f42c1; } ================================================ FILE: demo/offcanvas/offcanvas.js ================================================ (function () { 'use strict' document.querySelector('[data-bs-toggle="offcanvas"]').addEventListener('click', function () { document.querySelector('.offcanvas-collapse').classList.toggle('open') }) })() ================================================ FILE: demo/pricing/index.html ================================================ Pricing example for Bootstrap

Company name

Sign up

Pricing

Quickly build an effective pricing table for your potential customers with this Bootstrap example. It’s built with default Bootstrap components and utilities with little customization.

Free

$0 / mo

  • 10 users included
  • 2 GB of storage
  • Email support
  • Help center access

Pro

$15 / mo

  • 20 users included
  • 10 GB of storage
  • Priority email support
  • Help center access

Enterprise

$29 / mo

  • 30 users included
  • 15 GB of storage
  • Phone and email support
  • Help center access
================================================ FILE: demo/pricing/pricing.css ================================================ .container { max-width: 960px; } .pricing-header { max-width: 700px; } ================================================ FILE: demo/product/index.html ================================================ Product example for Bootstrap

Punny headline

And an even wittier subheading to boot. Jumpstart your marketing efforts with this example based on Apple’s marketing pages.

Coming soon

Another headline

And an even wittier subheading.

Another headline

And an even wittier subheading.

Another headline

And an even wittier subheading.

Another headline

And an even wittier subheading.

Another headline

And an even wittier subheading.

Another headline

And an even wittier subheading.

Another headline

And an even wittier subheading.

Another headline

And an even wittier subheading.

================================================ FILE: demo/product/product.css ================================================ .container { max-width: 960px; } /* * Custom translucent site header */ .site-header { background-color: rgba(0, 0, 0, .85); -webkit-backdrop-filter: saturate(180%) blur(20px); backdrop-filter: saturate(180%) blur(20px); } .site-header a { color: #8e8e8e; transition: color .15s ease-in-out; } .site-header a:hover { color: #fff; text-decoration: none; } /* * Dummy devices (replace them with your own or something else entirely!) */ .product-device { position: absolute; right: 10%; bottom: -30%; width: 300px; height: 540px; background-color: #333; border-radius: 21px; transform: rotate(30deg); } .product-device::before { position: absolute; top: 10%; right: 10px; bottom: 10%; left: 10px; content: ""; background-color: rgba(255, 255, 255, .1); border-radius: 5px; } .product-device-2 { top: -25%; right: auto; bottom: 0; left: 5%; background-color: #e5e5e5; } /* * Extra utilities */ .flex-equal > * { flex: 1; } @media (min-width: 768px) { .flex-md-equal > * { flex: 1; } } ================================================ FILE: docker-compose.yml ================================================ # sudo apt install docker-compose # sudo docker-compose up -d version: '3.8' services: php: build: context: . dockerfile: Dockerfile volumes: - ./:/var/www/html ports: - 8080:80 ================================================ FILE: editor.html ================================================ VvvebJs
Pages
 
 
 
 
 
 
S
Navigator
================================================ FILE: editor.php ================================================ through2.obj( function( file, enc, cb ) { if ( file.stat ) { file.stat.atime = file.stat.mtime = file.stat.ctime = new Date(); } cb( null, file ); }); gulp.task('fileinclude', function() { //gulp.src(['./html/**/*.html', '!**/_*/**']) return gulp.src(['./html/*.html', './html/**/*.html', '!**/_*/**']) .pipe(fileinclude({ prefix: '@@', basepath: '@file' })) .pipe(formatHtml()) //.pipe( touch() ) .pipe(gulp.dest('./')); }); gulp.task('sass', function() { //gulp.src(['./html/**/*.html', '!**/_*/**']) return gulp.src(['./scss/*.scss']) .pipe(sass()) .pipe(gulp.dest('./css')); }); gulp.task('watch', function () { gulp.watch(['./html/*.html', './html/**/*.html'], gulp.series('fileinclude')); gulp.watch(['./scss/*.scss'], gulp.series('sass')); }); // Default Task gulp.task('default', gulp.series('fileinclude', 'sass')); ================================================ FILE: libs/aos/aos.css ================================================ [data-aos][data-aos][data-aos-duration="50"],body[data-aos-duration="50"] [data-aos]{transition-duration:50ms}[data-aos][data-aos][data-aos-delay="50"],body[data-aos-delay="50"] [data-aos]{transition-delay:0s}[data-aos][data-aos][data-aos-delay="50"].aos-animate,body[data-aos-delay="50"] [data-aos].aos-animate{transition-delay:50ms}[data-aos][data-aos][data-aos-duration="100"],body[data-aos-duration="100"] [data-aos]{transition-duration:.1s}[data-aos][data-aos][data-aos-delay="100"],body[data-aos-delay="100"] [data-aos]{transition-delay:0s}[data-aos][data-aos][data-aos-delay="100"].aos-animate,body[data-aos-delay="100"] [data-aos].aos-animate{transition-delay:.1s}[data-aos][data-aos][data-aos-duration="150"],body[data-aos-duration="150"] [data-aos]{transition-duration:.15s}[data-aos][data-aos][data-aos-delay="150"],body[data-aos-delay="150"] [data-aos]{transition-delay:0s}[data-aos][data-aos][data-aos-delay="150"].aos-animate,body[data-aos-delay="150"] [data-aos].aos-animate{transition-delay:.15s}[data-aos][data-aos][data-aos-duration="200"],body[data-aos-duration="200"] [data-aos]{transition-duration:.2s}[data-aos][data-aos][data-aos-delay="200"],body[data-aos-delay="200"] [data-aos]{transition-delay:0s}[data-aos][data-aos][data-aos-delay="200"].aos-animate,body[data-aos-delay="200"] [data-aos].aos-animate{transition-delay:.2s}[data-aos][data-aos][data-aos-duration="250"],body[data-aos-duration="250"] [data-aos]{transition-duration:.25s}[data-aos][data-aos][data-aos-delay="250"],body[data-aos-delay="250"] [data-aos]{transition-delay:0s}[data-aos][data-aos][data-aos-delay="250"].aos-animate,body[data-aos-delay="250"] [data-aos].aos-animate{transition-delay:.25s}[data-aos][data-aos][data-aos-duration="300"],body[data-aos-duration="300"] [data-aos]{transition-duration:.3s}[data-aos][data-aos][data-aos-delay="300"],body[data-aos-delay="300"] [data-aos]{transition-delay:0s}[data-aos][data-aos][data-aos-delay="300"].aos-animate,body[data-aos-delay="300"] [data-aos].aos-animate{transition-delay:.3s}[data-aos][data-aos][data-aos-duration="350"],body[data-aos-duration="350"] [data-aos]{transition-duration:.35s}[data-aos][data-aos][data-aos-delay="350"],body[data-aos-delay="350"] [data-aos]{transition-delay:0s}[data-aos][data-aos][data-aos-delay="350"].aos-animate,body[data-aos-delay="350"] [data-aos].aos-animate{transition-delay:.35s}[data-aos][data-aos][data-aos-duration="400"],body[data-aos-duration="400"] [data-aos]{transition-duration:.4s}[data-aos][data-aos][data-aos-delay="400"],body[data-aos-delay="400"] [data-aos]{transition-delay:0s}[data-aos][data-aos][data-aos-delay="400"].aos-animate,body[data-aos-delay="400"] [data-aos].aos-animate{transition-delay:.4s}[data-aos][data-aos][data-aos-duration="450"],body[data-aos-duration="450"] [data-aos]{transition-duration:.45s}[data-aos][data-aos][data-aos-delay="450"],body[data-aos-delay="450"] [data-aos]{transition-delay:0s}[data-aos][data-aos][data-aos-delay="450"].aos-animate,body[data-aos-delay="450"] [data-aos].aos-animate{transition-delay:.45s}[data-aos][data-aos][data-aos-duration="500"],body[data-aos-duration="500"] [data-aos]{transition-duration:.5s}[data-aos][data-aos][data-aos-delay="500"],body[data-aos-delay="500"] [data-aos]{transition-delay:0s}[data-aos][data-aos][data-aos-delay="500"].aos-animate,body[data-aos-delay="500"] [data-aos].aos-animate{transition-delay:.5s}[data-aos][data-aos][data-aos-duration="550"],body[data-aos-duration="550"] [data-aos]{transition-duration:.55s}[data-aos][data-aos][data-aos-delay="550"],body[data-aos-delay="550"] [data-aos]{transition-delay:0s}[data-aos][data-aos][data-aos-delay="550"].aos-animate,body[data-aos-delay="550"] [data-aos].aos-animate{transition-delay:.55s}[data-aos][data-aos][data-aos-duration="600"],body[data-aos-duration="600"] [data-aos]{transition-duration:.6s}[data-aos][data-aos][data-aos-delay="600"],body[data-aos-delay="600"] [data-aos]{transition-delay:0s}[data-aos][data-aos][data-aos-delay="600"].aos-animate,body[data-aos-delay="600"] [data-aos].aos-animate{transition-delay:.6s}[data-aos][data-aos][data-aos-duration="650"],body[data-aos-duration="650"] [data-aos]{transition-duration:.65s}[data-aos][data-aos][data-aos-delay="650"],body[data-aos-delay="650"] [data-aos]{transition-delay:0s}[data-aos][data-aos][data-aos-delay="650"].aos-animate,body[data-aos-delay="650"] [data-aos].aos-animate{transition-delay:.65s}[data-aos][data-aos][data-aos-duration="700"],body[data-aos-duration="700"] [data-aos]{transition-duration:.7s}[data-aos][data-aos][data-aos-delay="700"],body[data-aos-delay="700"] [data-aos]{transition-delay:0s}[data-aos][data-aos][data-aos-delay="700"].aos-animate,body[data-aos-delay="700"] [data-aos].aos-animate{transition-delay:.7s}[data-aos][data-aos][data-aos-duration="750"],body[data-aos-duration="750"] [data-aos]{transition-duration:.75s}[data-aos][data-aos][data-aos-delay="750"],body[data-aos-delay="750"] [data-aos]{transition-delay:0s}[data-aos][data-aos][data-aos-delay="750"].aos-animate,body[data-aos-delay="750"] [data-aos].aos-animate{transition-delay:.75s}[data-aos][data-aos][data-aos-duration="800"],body[data-aos-duration="800"] [data-aos]{transition-duration:.8s}[data-aos][data-aos][data-aos-delay="800"],body[data-aos-delay="800"] [data-aos]{transition-delay:0s}[data-aos][data-aos][data-aos-delay="800"].aos-animate,body[data-aos-delay="800"] [data-aos].aos-animate{transition-delay:.8s}[data-aos][data-aos][data-aos-duration="850"],body[data-aos-duration="850"] [data-aos]{transition-duration:.85s}[data-aos][data-aos][data-aos-delay="850"],body[data-aos-delay="850"] [data-aos]{transition-delay:0s}[data-aos][data-aos][data-aos-delay="850"].aos-animate,body[data-aos-delay="850"] [data-aos].aos-animate{transition-delay:.85s}[data-aos][data-aos][data-aos-duration="900"],body[data-aos-duration="900"] [data-aos]{transition-duration:.9s}[data-aos][data-aos][data-aos-delay="900"],body[data-aos-delay="900"] [data-aos]{transition-delay:0s}[data-aos][data-aos][data-aos-delay="900"].aos-animate,body[data-aos-delay="900"] [data-aos].aos-animate{transition-delay:.9s}[data-aos][data-aos][data-aos-duration="950"],body[data-aos-duration="950"] [data-aos]{transition-duration:.95s}[data-aos][data-aos][data-aos-delay="950"],body[data-aos-delay="950"] [data-aos]{transition-delay:0s}[data-aos][data-aos][data-aos-delay="950"].aos-animate,body[data-aos-delay="950"] [data-aos].aos-animate{transition-delay:.95s}[data-aos][data-aos][data-aos-duration="1000"],body[data-aos-duration="1000"] [data-aos]{transition-duration:1s}[data-aos][data-aos][data-aos-delay="1000"],body[data-aos-delay="1000"] [data-aos]{transition-delay:0s}[data-aos][data-aos][data-aos-delay="1000"].aos-animate,body[data-aos-delay="1000"] [data-aos].aos-animate{transition-delay:1s}[data-aos][data-aos][data-aos-duration="1050"],body[data-aos-duration="1050"] [data-aos]{transition-duration:1.05s}[data-aos][data-aos][data-aos-delay="1050"],body[data-aos-delay="1050"] [data-aos]{transition-delay:0s}[data-aos][data-aos][data-aos-delay="1050"].aos-animate,body[data-aos-delay="1050"] [data-aos].aos-animate{transition-delay:1.05s}[data-aos][data-aos][data-aos-duration="1100"],body[data-aos-duration="1100"] [data-aos]{transition-duration:1.1s}[data-aos][data-aos][data-aos-delay="1100"],body[data-aos-delay="1100"] [data-aos]{transition-delay:0s}[data-aos][data-aos][data-aos-delay="1100"].aos-animate,body[data-aos-delay="1100"] [data-aos].aos-animate{transition-delay:1.1s}[data-aos][data-aos][data-aos-duration="1150"],body[data-aos-duration="1150"] [data-aos]{transition-duration:1.15s}[data-aos][data-aos][data-aos-delay="1150"],body[data-aos-delay="1150"] [data-aos]{transition-delay:0s}[data-aos][data-aos][data-aos-delay="1150"].aos-animate,body[data-aos-delay="1150"] [data-aos].aos-animate{transition-delay:1.15s}[data-aos][data-aos][data-aos-duration="1200"],body[data-aos-duration="1200"] [data-aos]{transition-duration:1.2s}[data-aos][data-aos][data-aos-delay="1200"],body[data-aos-delay="1200"] [data-aos]{transition-delay:0s}[data-aos][data-aos][data-aos-delay="1200"].aos-animate,body[data-aos-delay="1200"] [data-aos].aos-animate{transition-delay:1.2s}[data-aos][data-aos][data-aos-duration="1250"],body[data-aos-duration="1250"] [data-aos]{transition-duration:1.25s}[data-aos][data-aos][data-aos-delay="1250"],body[data-aos-delay="1250"] [data-aos]{transition-delay:0s}[data-aos][data-aos][data-aos-delay="1250"].aos-animate,body[data-aos-delay="1250"] [data-aos].aos-animate{transition-delay:1.25s}[data-aos][data-aos][data-aos-duration="1300"],body[data-aos-duration="1300"] [data-aos]{transition-duration:1.3s}[data-aos][data-aos][data-aos-delay="1300"],body[data-aos-delay="1300"] [data-aos]{transition-delay:0s}[data-aos][data-aos][data-aos-delay="1300"].aos-animate,body[data-aos-delay="1300"] [data-aos].aos-animate{transition-delay:1.3s}[data-aos][data-aos][data-aos-duration="1350"],body[data-aos-duration="1350"] [data-aos]{transition-duration:1.35s}[data-aos][data-aos][data-aos-delay="1350"],body[data-aos-delay="1350"] [data-aos]{transition-delay:0s}[data-aos][data-aos][data-aos-delay="1350"].aos-animate,body[data-aos-delay="1350"] [data-aos].aos-animate{transition-delay:1.35s}[data-aos][data-aos][data-aos-duration="1400"],body[data-aos-duration="1400"] [data-aos]{transition-duration:1.4s}[data-aos][data-aos][data-aos-delay="1400"],body[data-aos-delay="1400"] [data-aos]{transition-delay:0s}[data-aos][data-aos][data-aos-delay="1400"].aos-animate,body[data-aos-delay="1400"] [data-aos].aos-animate{transition-delay:1.4s}[data-aos][data-aos][data-aos-duration="1450"],body[data-aos-duration="1450"] [data-aos]{transition-duration:1.45s}[data-aos][data-aos][data-aos-delay="1450"],body[data-aos-delay="1450"] [data-aos]{transition-delay:0s}[data-aos][data-aos][data-aos-delay="1450"].aos-animate,body[data-aos-delay="1450"] [data-aos].aos-animate{transition-delay:1.45s}[data-aos][data-aos][data-aos-duration="1500"],body[data-aos-duration="1500"] [data-aos]{transition-duration:1.5s}[data-aos][data-aos][data-aos-delay="1500"],body[data-aos-delay="1500"] [data-aos]{transition-delay:0s}[data-aos][data-aos][data-aos-delay="1500"].aos-animate,body[data-aos-delay="1500"] [data-aos].aos-animate{transition-delay:1.5s}[data-aos][data-aos][data-aos-duration="1550"],body[data-aos-duration="1550"] [data-aos]{transition-duration:1.55s}[data-aos][data-aos][data-aos-delay="1550"],body[data-aos-delay="1550"] [data-aos]{transition-delay:0s}[data-aos][data-aos][data-aos-delay="1550"].aos-animate,body[data-aos-delay="1550"] [data-aos].aos-animate{transition-delay:1.55s}[data-aos][data-aos][data-aos-duration="1600"],body[data-aos-duration="1600"] [data-aos]{transition-duration:1.6s}[data-aos][data-aos][data-aos-delay="1600"],body[data-aos-delay="1600"] [data-aos]{transition-delay:0s}[data-aos][data-aos][data-aos-delay="1600"].aos-animate,body[data-aos-delay="1600"] [data-aos].aos-animate{transition-delay:1.6s}[data-aos][data-aos][data-aos-duration="1650"],body[data-aos-duration="1650"] [data-aos]{transition-duration:1.65s}[data-aos][data-aos][data-aos-delay="1650"],body[data-aos-delay="1650"] [data-aos]{transition-delay:0s}[data-aos][data-aos][data-aos-delay="1650"].aos-animate,body[data-aos-delay="1650"] [data-aos].aos-animate{transition-delay:1.65s}[data-aos][data-aos][data-aos-duration="1700"],body[data-aos-duration="1700"] [data-aos]{transition-duration:1.7s}[data-aos][data-aos][data-aos-delay="1700"],body[data-aos-delay="1700"] [data-aos]{transition-delay:0s}[data-aos][data-aos][data-aos-delay="1700"].aos-animate,body[data-aos-delay="1700"] [data-aos].aos-animate{transition-delay:1.7s}[data-aos][data-aos][data-aos-duration="1750"],body[data-aos-duration="1750"] [data-aos]{transition-duration:1.75s}[data-aos][data-aos][data-aos-delay="1750"],body[data-aos-delay="1750"] [data-aos]{transition-delay:0s}[data-aos][data-aos][data-aos-delay="1750"].aos-animate,body[data-aos-delay="1750"] [data-aos].aos-animate{transition-delay:1.75s}[data-aos][data-aos][data-aos-duration="1800"],body[data-aos-duration="1800"] [data-aos]{transition-duration:1.8s}[data-aos][data-aos][data-aos-delay="1800"],body[data-aos-delay="1800"] [data-aos]{transition-delay:0s}[data-aos][data-aos][data-aos-delay="1800"].aos-animate,body[data-aos-delay="1800"] [data-aos].aos-animate{transition-delay:1.8s}[data-aos][data-aos][data-aos-duration="1850"],body[data-aos-duration="1850"] [data-aos]{transition-duration:1.85s}[data-aos][data-aos][data-aos-delay="1850"],body[data-aos-delay="1850"] [data-aos]{transition-delay:0s}[data-aos][data-aos][data-aos-delay="1850"].aos-animate,body[data-aos-delay="1850"] [data-aos].aos-animate{transition-delay:1.85s}[data-aos][data-aos][data-aos-duration="1900"],body[data-aos-duration="1900"] [data-aos]{transition-duration:1.9s}[data-aos][data-aos][data-aos-delay="1900"],body[data-aos-delay="1900"] [data-aos]{transition-delay:0s}[data-aos][data-aos][data-aos-delay="1900"].aos-animate,body[data-aos-delay="1900"] [data-aos].aos-animate{transition-delay:1.9s}[data-aos][data-aos][data-aos-duration="1950"],body[data-aos-duration="1950"] [data-aos]{transition-duration:1.95s}[data-aos][data-aos][data-aos-delay="1950"],body[data-aos-delay="1950"] [data-aos]{transition-delay:0s}[data-aos][data-aos][data-aos-delay="1950"].aos-animate,body[data-aos-delay="1950"] [data-aos].aos-animate{transition-delay:1.95s}[data-aos][data-aos][data-aos-duration="2000"],body[data-aos-duration="2000"] [data-aos]{transition-duration:2s}[data-aos][data-aos][data-aos-delay="2000"],body[data-aos-delay="2000"] [data-aos]{transition-delay:0s}[data-aos][data-aos][data-aos-delay="2000"].aos-animate,body[data-aos-delay="2000"] [data-aos].aos-animate{transition-delay:2s}[data-aos][data-aos][data-aos-duration="2050"],body[data-aos-duration="2050"] [data-aos]{transition-duration:2.05s}[data-aos][data-aos][data-aos-delay="2050"],body[data-aos-delay="2050"] [data-aos]{transition-delay:0s}[data-aos][data-aos][data-aos-delay="2050"].aos-animate,body[data-aos-delay="2050"] [data-aos].aos-animate{transition-delay:2.05s}[data-aos][data-aos][data-aos-duration="2100"],body[data-aos-duration="2100"] [data-aos]{transition-duration:2.1s}[data-aos][data-aos][data-aos-delay="2100"],body[data-aos-delay="2100"] [data-aos]{transition-delay:0s}[data-aos][data-aos][data-aos-delay="2100"].aos-animate,body[data-aos-delay="2100"] [data-aos].aos-animate{transition-delay:2.1s}[data-aos][data-aos][data-aos-duration="2150"],body[data-aos-duration="2150"] [data-aos]{transition-duration:2.15s}[data-aos][data-aos][data-aos-delay="2150"],body[data-aos-delay="2150"] [data-aos]{transition-delay:0s}[data-aos][data-aos][data-aos-delay="2150"].aos-animate,body[data-aos-delay="2150"] [data-aos].aos-animate{transition-delay:2.15s}[data-aos][data-aos][data-aos-duration="2200"],body[data-aos-duration="2200"] [data-aos]{transition-duration:2.2s}[data-aos][data-aos][data-aos-delay="2200"],body[data-aos-delay="2200"] [data-aos]{transition-delay:0s}[data-aos][data-aos][data-aos-delay="2200"].aos-animate,body[data-aos-delay="2200"] [data-aos].aos-animate{transition-delay:2.2s}[data-aos][data-aos][data-aos-duration="2250"],body[data-aos-duration="2250"] [data-aos]{transition-duration:2.25s}[data-aos][data-aos][data-aos-delay="2250"],body[data-aos-delay="2250"] [data-aos]{transition-delay:0s}[data-aos][data-aos][data-aos-delay="2250"].aos-animate,body[data-aos-delay="2250"] [data-aos].aos-animate{transition-delay:2.25s}[data-aos][data-aos][data-aos-duration="2300"],body[data-aos-duration="2300"] [data-aos]{transition-duration:2.3s}[data-aos][data-aos][data-aos-delay="2300"],body[data-aos-delay="2300"] [data-aos]{transition-delay:0s}[data-aos][data-aos][data-aos-delay="2300"].aos-animate,body[data-aos-delay="2300"] [data-aos].aos-animate{transition-delay:2.3s}[data-aos][data-aos][data-aos-duration="2350"],body[data-aos-duration="2350"] [data-aos]{transition-duration:2.35s}[data-aos][data-aos][data-aos-delay="2350"],body[data-aos-delay="2350"] [data-aos]{transition-delay:0s}[data-aos][data-aos][data-aos-delay="2350"].aos-animate,body[data-aos-delay="2350"] [data-aos].aos-animate{transition-delay:2.35s}[data-aos][data-aos][data-aos-duration="2400"],body[data-aos-duration="2400"] [data-aos]{transition-duration:2.4s}[data-aos][data-aos][data-aos-delay="2400"],body[data-aos-delay="2400"] [data-aos]{transition-delay:0s}[data-aos][data-aos][data-aos-delay="2400"].aos-animate,body[data-aos-delay="2400"] [data-aos].aos-animate{transition-delay:2.4s}[data-aos][data-aos][data-aos-duration="2450"],body[data-aos-duration="2450"] [data-aos]{transition-duration:2.45s}[data-aos][data-aos][data-aos-delay="2450"],body[data-aos-delay="2450"] [data-aos]{transition-delay:0s}[data-aos][data-aos][data-aos-delay="2450"].aos-animate,body[data-aos-delay="2450"] [data-aos].aos-animate{transition-delay:2.45s}[data-aos][data-aos][data-aos-duration="2500"],body[data-aos-duration="2500"] [data-aos]{transition-duration:2.5s}[data-aos][data-aos][data-aos-delay="2500"],body[data-aos-delay="2500"] [data-aos]{transition-delay:0s}[data-aos][data-aos][data-aos-delay="2500"].aos-animate,body[data-aos-delay="2500"] [data-aos].aos-animate{transition-delay:2.5s}[data-aos][data-aos][data-aos-duration="2550"],body[data-aos-duration="2550"] [data-aos]{transition-duration:2.55s}[data-aos][data-aos][data-aos-delay="2550"],body[data-aos-delay="2550"] [data-aos]{transition-delay:0s}[data-aos][data-aos][data-aos-delay="2550"].aos-animate,body[data-aos-delay="2550"] [data-aos].aos-animate{transition-delay:2.55s}[data-aos][data-aos][data-aos-duration="2600"],body[data-aos-duration="2600"] [data-aos]{transition-duration:2.6s}[data-aos][data-aos][data-aos-delay="2600"],body[data-aos-delay="2600"] [data-aos]{transition-delay:0s}[data-aos][data-aos][data-aos-delay="2600"].aos-animate,body[data-aos-delay="2600"] [data-aos].aos-animate{transition-delay:2.6s}[data-aos][data-aos][data-aos-duration="2650"],body[data-aos-duration="2650"] [data-aos]{transition-duration:2.65s}[data-aos][data-aos][data-aos-delay="2650"],body[data-aos-delay="2650"] [data-aos]{transition-delay:0s}[data-aos][data-aos][data-aos-delay="2650"].aos-animate,body[data-aos-delay="2650"] [data-aos].aos-animate{transition-delay:2.65s}[data-aos][data-aos][data-aos-duration="2700"],body[data-aos-duration="2700"] [data-aos]{transition-duration:2.7s}[data-aos][data-aos][data-aos-delay="2700"],body[data-aos-delay="2700"] [data-aos]{transition-delay:0s}[data-aos][data-aos][data-aos-delay="2700"].aos-animate,body[data-aos-delay="2700"] [data-aos].aos-animate{transition-delay:2.7s}[data-aos][data-aos][data-aos-duration="2750"],body[data-aos-duration="2750"] [data-aos]{transition-duration:2.75s}[data-aos][data-aos][data-aos-delay="2750"],body[data-aos-delay="2750"] [data-aos]{transition-delay:0s}[data-aos][data-aos][data-aos-delay="2750"].aos-animate,body[data-aos-delay="2750"] [data-aos].aos-animate{transition-delay:2.75s}[data-aos][data-aos][data-aos-duration="2800"],body[data-aos-duration="2800"] [data-aos]{transition-duration:2.8s}[data-aos][data-aos][data-aos-delay="2800"],body[data-aos-delay="2800"] [data-aos]{transition-delay:0s}[data-aos][data-aos][data-aos-delay="2800"].aos-animate,body[data-aos-delay="2800"] [data-aos].aos-animate{transition-delay:2.8s}[data-aos][data-aos][data-aos-duration="2850"],body[data-aos-duration="2850"] [data-aos]{transition-duration:2.85s}[data-aos][data-aos][data-aos-delay="2850"],body[data-aos-delay="2850"] [data-aos]{transition-delay:0s}[data-aos][data-aos][data-aos-delay="2850"].aos-animate,body[data-aos-delay="2850"] [data-aos].aos-animate{transition-delay:2.85s}[data-aos][data-aos][data-aos-duration="2900"],body[data-aos-duration="2900"] [data-aos]{transition-duration:2.9s}[data-aos][data-aos][data-aos-delay="2900"],body[data-aos-delay="2900"] [data-aos]{transition-delay:0s}[data-aos][data-aos][data-aos-delay="2900"].aos-animate,body[data-aos-delay="2900"] [data-aos].aos-animate{transition-delay:2.9s}[data-aos][data-aos][data-aos-duration="2950"],body[data-aos-duration="2950"] [data-aos]{transition-duration:2.95s}[data-aos][data-aos][data-aos-delay="2950"],body[data-aos-delay="2950"] [data-aos]{transition-delay:0s}[data-aos][data-aos][data-aos-delay="2950"].aos-animate,body[data-aos-delay="2950"] [data-aos].aos-animate{transition-delay:2.95s}[data-aos][data-aos][data-aos-duration="3000"],body[data-aos-duration="3000"] [data-aos]{transition-duration:3s}[data-aos][data-aos][data-aos-delay="3000"],body[data-aos-delay="3000"] [data-aos]{transition-delay:0s}[data-aos][data-aos][data-aos-delay="3000"].aos-animate,body[data-aos-delay="3000"] [data-aos].aos-animate{transition-delay:3s}[data-aos]{pointer-events:none}[data-aos].aos-animate{pointer-events:auto}[data-aos][data-aos][data-aos-easing=linear],body[data-aos-easing=linear] [data-aos]{transition-timing-function:cubic-bezier(.25,.25,.75,.75)}[data-aos][data-aos][data-aos-easing=ease],body[data-aos-easing=ease] [data-aos]{transition-timing-function:ease}[data-aos][data-aos][data-aos-easing=ease-in],body[data-aos-easing=ease-in] [data-aos]{transition-timing-function:ease-in}[data-aos][data-aos][data-aos-easing=ease-out],body[data-aos-easing=ease-out] [data-aos]{transition-timing-function:ease-out}[data-aos][data-aos][data-aos-easing=ease-in-out],body[data-aos-easing=ease-in-out] [data-aos]{transition-timing-function:ease-in-out}[data-aos][data-aos][data-aos-easing=ease-in-back],body[data-aos-easing=ease-in-back] [data-aos]{transition-timing-function:cubic-bezier(.6,-.28,.735,.045)}[data-aos][data-aos][data-aos-easing=ease-out-back],body[data-aos-easing=ease-out-back] [data-aos]{transition-timing-function:cubic-bezier(.175,.885,.32,1.275)}[data-aos][data-aos][data-aos-easing=ease-in-out-back],body[data-aos-easing=ease-in-out-back] [data-aos]{transition-timing-function:cubic-bezier(.68,-.55,.265,1.55)}[data-aos][data-aos][data-aos-easing=ease-in-sine],body[data-aos-easing=ease-in-sine] [data-aos]{transition-timing-function:cubic-bezier(.47,0,.745,.715)}[data-aos][data-aos][data-aos-easing=ease-out-sine],body[data-aos-easing=ease-out-sine] [data-aos]{transition-timing-function:cubic-bezier(.39,.575,.565,1)}[data-aos][data-aos][data-aos-easing=ease-in-out-sine],body[data-aos-easing=ease-in-out-sine] [data-aos]{transition-timing-function:cubic-bezier(.445,.05,.55,.95)}[data-aos][data-aos][data-aos-easing=ease-in-quad],body[data-aos-easing=ease-in-quad] [data-aos]{transition-timing-function:cubic-bezier(.55,.085,.68,.53)}[data-aos][data-aos][data-aos-easing=ease-out-quad],body[data-aos-easing=ease-out-quad] [data-aos]{transition-timing-function:cubic-bezier(.25,.46,.45,.94)}[data-aos][data-aos][data-aos-easing=ease-in-out-quad],body[data-aos-easing=ease-in-out-quad] [data-aos]{transition-timing-function:cubic-bezier(.455,.03,.515,.955)}[data-aos][data-aos][data-aos-easing=ease-in-cubic],body[data-aos-easing=ease-in-cubic] [data-aos]{transition-timing-function:cubic-bezier(.55,.085,.68,.53)}[data-aos][data-aos][data-aos-easing=ease-out-cubic],body[data-aos-easing=ease-out-cubic] [data-aos]{transition-timing-function:cubic-bezier(.25,.46,.45,.94)}[data-aos][data-aos][data-aos-easing=ease-in-out-cubic],body[data-aos-easing=ease-in-out-cubic] [data-aos]{transition-timing-function:cubic-bezier(.455,.03,.515,.955)}[data-aos][data-aos][data-aos-easing=ease-in-quart],body[data-aos-easing=ease-in-quart] [data-aos]{transition-timing-function:cubic-bezier(.55,.085,.68,.53)}[data-aos][data-aos][data-aos-easing=ease-out-quart],body[data-aos-easing=ease-out-quart] [data-aos]{transition-timing-function:cubic-bezier(.25,.46,.45,.94)}[data-aos][data-aos][data-aos-easing=ease-in-out-quart],body[data-aos-easing=ease-in-out-quart] [data-aos]{transition-timing-function:cubic-bezier(.455,.03,.515,.955)}@media screen{html:not(.no-js) [data-aos^=fade][data-aos^=fade]{opacity:0;transition-property:opacity,-webkit-transform;transition-property:opacity,transform;transition-property:opacity,transform,-webkit-transform}html:not(.no-js) [data-aos^=fade][data-aos^=fade].aos-animate{opacity:1;-webkit-transform:none;transform:none}html:not(.no-js) [data-aos=fade-up]{-webkit-transform:translate3d(0,100px,0);transform:translate3d(0,100px,0)}html:not(.no-js) [data-aos=fade-down]{-webkit-transform:translate3d(0,-100px,0);transform:translate3d(0,-100px,0)}html:not(.no-js) [data-aos=fade-right]{-webkit-transform:translate3d(-100px,0,0);transform:translate3d(-100px,0,0)}html:not(.no-js) [data-aos=fade-left]{-webkit-transform:translate3d(100px,0,0);transform:translate3d(100px,0,0)}html:not(.no-js) [data-aos=fade-up-right]{-webkit-transform:translate3d(-100px,100px,0);transform:translate3d(-100px,100px,0)}html:not(.no-js) [data-aos=fade-up-left]{-webkit-transform:translate3d(100px,100px,0);transform:translate3d(100px,100px,0)}html:not(.no-js) [data-aos=fade-down-right]{-webkit-transform:translate3d(-100px,-100px,0);transform:translate3d(-100px,-100px,0)}html:not(.no-js) [data-aos=fade-down-left]{-webkit-transform:translate3d(100px,-100px,0);transform:translate3d(100px,-100px,0)}html:not(.no-js) [data-aos^=zoom][data-aos^=zoom]{opacity:0;transition-property:opacity,-webkit-transform;transition-property:opacity,transform;transition-property:opacity,transform,-webkit-transform}html:not(.no-js) [data-aos^=zoom][data-aos^=zoom].aos-animate{opacity:1;-webkit-transform:translateZ(0) scale(1);transform:translateZ(0) scale(1)}html:not(.no-js) [data-aos=zoom-in]{-webkit-transform:scale(.6);transform:scale(.6)}html:not(.no-js) [data-aos=zoom-in-up]{-webkit-transform:translate3d(0,100px,0) scale(.6);transform:translate3d(0,100px,0) scale(.6)}html:not(.no-js) [data-aos=zoom-in-down]{-webkit-transform:translate3d(0,-100px,0) scale(.6);transform:translate3d(0,-100px,0) scale(.6)}html:not(.no-js) [data-aos=zoom-in-right]{-webkit-transform:translate3d(-100px,0,0) scale(.6);transform:translate3d(-100px,0,0) scale(.6)}html:not(.no-js) [data-aos=zoom-in-left]{-webkit-transform:translate3d(100px,0,0) scale(.6);transform:translate3d(100px,0,0) scale(.6)}html:not(.no-js) [data-aos=zoom-out]{-webkit-transform:scale(1.2);transform:scale(1.2)}html:not(.no-js) [data-aos=zoom-out-up]{-webkit-transform:translate3d(0,100px,0) scale(1.2);transform:translate3d(0,100px,0) scale(1.2)}html:not(.no-js) [data-aos=zoom-out-down]{-webkit-transform:translate3d(0,-100px,0) scale(1.2);transform:translate3d(0,-100px,0) scale(1.2)}html:not(.no-js) [data-aos=zoom-out-right]{-webkit-transform:translate3d(-100px,0,0) scale(1.2);transform:translate3d(-100px,0,0) scale(1.2)}html:not(.no-js) [data-aos=zoom-out-left]{-webkit-transform:translate3d(100px,0,0) scale(1.2);transform:translate3d(100px,0,0) scale(1.2)}html:not(.no-js) [data-aos^=slide][data-aos^=slide]{transition-property:-webkit-transform;transition-property:transform;transition-property:transform,-webkit-transform;visibility:hidden}html:not(.no-js) [data-aos^=slide][data-aos^=slide].aos-animate{visibility:visible;-webkit-transform:translateZ(0);transform:translateZ(0)}html:not(.no-js) [data-aos=slide-up]{-webkit-transform:translate3d(0,100%,0);transform:translate3d(0,100%,0)}html:not(.no-js) [data-aos=slide-down]{-webkit-transform:translate3d(0,-100%,0);transform:translate3d(0,-100%,0)}html:not(.no-js) [data-aos=slide-right]{-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}html:not(.no-js) [data-aos=slide-left]{-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}html:not(.no-js) [data-aos^=flip][data-aos^=flip]{-webkit-backface-visibility:hidden;backface-visibility:hidden;transition-property:-webkit-transform;transition-property:transform;transition-property:transform,-webkit-transform}html:not(.no-js) [data-aos=flip-left]{-webkit-transform:perspective(2500px) rotateY(-100deg);transform:perspective(2500px) rotateY(-100deg)}html:not(.no-js) [data-aos=flip-left].aos-animate{-webkit-transform:perspective(2500px) rotateY(0);transform:perspective(2500px) rotateY(0)}html:not(.no-js) [data-aos=flip-right]{-webkit-transform:perspective(2500px) rotateY(100deg);transform:perspective(2500px) rotateY(100deg)}html:not(.no-js) [data-aos=flip-right].aos-animate{-webkit-transform:perspective(2500px) rotateY(0);transform:perspective(2500px) rotateY(0)}html:not(.no-js) [data-aos=flip-up]{-webkit-transform:perspective(2500px) rotateX(-100deg);transform:perspective(2500px) rotateX(-100deg)}html:not(.no-js) [data-aos=flip-up].aos-animate{-webkit-transform:perspective(2500px) rotateX(0);transform:perspective(2500px) rotateX(0)}html:not(.no-js) [data-aos=flip-down]{-webkit-transform:perspective(2500px) rotateX(100deg);transform:perspective(2500px) rotateX(100deg)}html:not(.no-js) [data-aos=flip-down].aos-animate{-webkit-transform:perspective(2500px) rotateX(0);transform:perspective(2500px) rotateX(0)}} ================================================ FILE: libs/aos/aos.js ================================================ !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.AOS=t()}(this,function(){"use strict";var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{},t="Expected a function",n=NaN,o="[object Symbol]",i=/^\s+|\s+$/g,a=/^[-+]0x[0-9a-f]+$/i,r=/^0b[01]+$/i,c=/^0o[0-7]+$/i,s=parseInt,u="object"==typeof e&&e&&e.Object===Object&&e,d="object"==typeof self&&self&&self.Object===Object&&self,l=u||d||Function("return this")(),f=Object.prototype.toString,m=Math.max,p=Math.min,b=function(){return l.Date.now()};function v(e,n,o){var i,a,r,c,s,u,d=0,l=!1,f=!1,v=!0;if("function"!=typeof e)throw new TypeError(t);function y(t){var n=i,o=a;return i=a=void 0,d=t,c=e.apply(o,n)}function h(e){var t=e-u;return void 0===u||t>=n||t<0||f&&e-d>=r}function k(){var e=b();if(h(e))return x(e);s=setTimeout(k,function(e){var t=n-(e-u);return f?p(t,r-(e-d)):t}(e))}function x(e){return s=void 0,v&&i?y(e):(i=a=void 0,c)}function O(){var e=b(),t=h(e);if(i=arguments,a=this,u=e,t){if(void 0===s)return function(e){return d=e,s=setTimeout(k,n),l?y(e):c}(u);if(f)return s=setTimeout(k,n),y(u)}return void 0===s&&(s=setTimeout(k,n)),c}return n=w(n)||0,g(o)&&(l=!!o.leading,r=(f="maxWait"in o)?m(w(o.maxWait)||0,n):r,v="trailing"in o?!!o.trailing:v),O.cancel=function(){void 0!==s&&clearTimeout(s),d=0,i=u=a=s=void 0},O.flush=function(){return void 0===s?c:x(b())},O}function g(e){var t=typeof e;return!!e&&("object"==t||"function"==t)}function w(e){if("number"==typeof e)return e;if(function(e){return"symbol"==typeof e||function(e){return!!e&&"object"==typeof e}(e)&&f.call(e)==o}(e))return n;if(g(e)){var t="function"==typeof e.valueOf?e.valueOf():e;e=g(t)?t+"":t}if("string"!=typeof e)return 0===e?e:+e;e=e.replace(i,"");var u=r.test(e);return u||c.test(e)?s(e.slice(2),u?2:8):a.test(e)?n:+e}var y=function(e,n,o){var i=!0,a=!0;if("function"!=typeof e)throw new TypeError(t);return g(o)&&(i="leading"in o?!!o.leading:i,a="trailing"in o?!!o.trailing:a),v(e,n,{leading:i,maxWait:n,trailing:a})},h="Expected a function",k=NaN,x="[object Symbol]",O=/^\s+|\s+$/g,j=/^[-+]0x[0-9a-f]+$/i,E=/^0b[01]+$/i,N=/^0o[0-7]+$/i,z=parseInt,C="object"==typeof e&&e&&e.Object===Object&&e,A="object"==typeof self&&self&&self.Object===Object&&self,q=C||A||Function("return this")(),L=Object.prototype.toString,T=Math.max,M=Math.min,S=function(){return q.Date.now()};function D(e){var t=typeof e;return!!e&&("object"==t||"function"==t)}function H(e){if("number"==typeof e)return e;if(function(e){return"symbol"==typeof e||function(e){return!!e&&"object"==typeof e}(e)&&L.call(e)==x}(e))return k;if(D(e)){var t="function"==typeof e.valueOf?e.valueOf():e;e=D(t)?t+"":t}if("string"!=typeof e)return 0===e?e:+e;e=e.replace(O,"");var n=E.test(e);return n||N.test(e)?z(e.slice(2),n?2:8):j.test(e)?k:+e}var $=function(e,t,n){var o,i,a,r,c,s,u=0,d=!1,l=!1,f=!0;if("function"!=typeof e)throw new TypeError(h);function m(t){var n=o,a=i;return o=i=void 0,u=t,r=e.apply(a,n)}function p(e){var n=e-s;return void 0===s||n>=t||n<0||l&&e-u>=a}function b(){var e=S();if(p(e))return v(e);c=setTimeout(b,function(e){var n=t-(e-s);return l?M(n,a-(e-u)):n}(e))}function v(e){return c=void 0,f&&o?m(e):(o=i=void 0,r)}function g(){var e=S(),n=p(e);if(o=arguments,i=this,s=e,n){if(void 0===c)return function(e){return u=e,c=setTimeout(b,t),d?m(e):r}(s);if(l)return c=setTimeout(b,t),m(s)}return void 0===c&&(c=setTimeout(b,t)),r}return t=H(t)||0,D(n)&&(d=!!n.leading,a=(l="maxWait"in n)?T(H(n.maxWait)||0,t):a,f="trailing"in n?!!n.trailing:f),g.cancel=function(){void 0!==c&&clearTimeout(c),u=0,o=s=i=c=void 0},g.flush=function(){return void 0===c?r:v(S())},g},W=function(){};function P(e){e&&e.forEach(function(e){var t=Array.prototype.slice.call(e.addedNodes),n=Array.prototype.slice.call(e.removedNodes);if(function e(t){var n=void 0,o=void 0;for(n=0;n=o.out&&!n.once?a():t>=o.in?e.animated||(function(e,t){t&&t.forEach(function(t){return e.classList.add(t)})}(i,n.animatedClassNames),V("aos:in",i),e.options.id&&V("aos:in:"+e.options.id,i),e.animated=!0):e.animated&&!n.once&&a()}(e,window.pageYOffset)})},Z=function(e){for(var t=0,n=0;e&&!isNaN(e.offsetLeft)&&!isNaN(e.offsetTop);)t+=e.offsetLeft-("BODY"!=e.tagName?e.scrollLeft:0),n+=e.offsetTop-("BODY"!=e.tagName?e.scrollTop:0),e=e.offsetParent;return{top:n,left:t}},ee=function(e,t,n){var o=e.getAttribute("data-aos-"+t);if(void 0!==o){if("true"===o)return!0;if("false"===o)return!1}return o||n},te=function(e,t){return e.forEach(function(e,n){var o=ee(e.node,"mirror",t.mirror),i=ee(e.node,"once",t.once),a=ee(e.node,"id"),r=t.useClassNames&&e.node.getAttribute("data-aos"),c=[t.animatedClassName].concat(r?r.split(" "):[]).filter(function(e){return"string"==typeof e});t.initClassName&&e.node.classList.add(t.initClassName),e.position={in:function(e,t,n){var o=window.innerHeight,i=ee(e,"anchor"),a=ee(e,"anchor-placement"),r=Number(ee(e,"offset",a?0:t)),c=a||n,s=e;i&&document.querySelectorAll(i)&&(s=document.querySelectorAll(i)[0]);var u=Z(s).top-o;switch(c){case"top-bottom":break;case"center-bottom":u+=s.offsetHeight/2;break;case"bottom-bottom":u+=s.offsetHeight;break;case"top-center":u+=o/2;break;case"center-center":u+=o/2+s.offsetHeight/2;break;case"bottom-center":u+=o/2+s.offsetHeight;break;case"top-top":u+=o;break;case"bottom-top":u+=o+s.offsetHeight;break;case"center-top":u+=o+s.offsetHeight/2}return u+r}(e.node,t.offset,t.anchorPlacement),out:o&&function(e,t){window.innerHeight;var n=ee(e,"anchor"),o=ee(e,"offset",t),i=e;return n&&document.querySelectorAll(n)&&(i=document.querySelectorAll(n)[0]),Z(i).top+i.offsetHeight-o}(e.node,t.offset)},e.options={once:i,mirror:o,animatedClassNames:c,id:a}}),e},ne=function(){var e=document.querySelectorAll("[data-aos]");return Array.prototype.map.call(e,function(e){return{node:e}})},oe=[],ie=!1,ae={offset:120,delay:0,easing:"ease",duration:400,disable:!1,once:!1,mirror:!1,anchorPlacement:"top-bottom",startEvent:"DOMContentLoaded",animatedClassName:"aos-animate",initClassName:"aos-init",useClassNames:!1,disableMutationObserver:!1,throttleDelay:99,debounceDelay:50},re=function(){return document.all&&!window.atob},ce=function(){arguments.length>0&&void 0!==arguments[0]&&arguments[0]&&(ie=!0),ie&&(oe=te(oe,ae),X(oe),window.addEventListener("scroll",y(function(){X(oe,ae.once)},ae.throttleDelay)))},se=function(){if(oe=ne(),de(ae.disable)||re())return ue();ce()},ue=function(){oe.forEach(function(e,t){e.node.removeAttribute("data-aos"),e.node.removeAttribute("data-aos-easing"),e.node.removeAttribute("data-aos-duration"),e.node.removeAttribute("data-aos-delay"),ae.initClassName&&e.node.classList.remove(ae.initClassName),ae.animatedClassName&&e.node.classList.remove(ae.animatedClassName)})},de=function(e){return!0===e||"mobile"===e&&U.mobile()||"phone"===e&&U.phone()||"tablet"===e&&U.tablet()||"function"==typeof e&&!0===e()};return{init:function(e){return ae=I(ae,e),oe=ne(),ae.disableMutationObserver||_.isSupported()||(console.info('\n aos: MutationObserver is not supported on this browser,\n code mutations observing has been disabled.\n You may have to call "refreshHard()" by yourself.\n '),ae.disableMutationObserver=!0),ae.disableMutationObserver||_.ready("[data-aos]",se),de(ae.disable)||re()?ue():(document.querySelector("body").setAttribute("data-aos-easing",ae.easing),document.querySelector("body").setAttribute("data-aos-duration",ae.duration),document.querySelector("body").setAttribute("data-aos-delay",ae.delay),-1===["DOMContentLoaded","load"].indexOf(ae.startEvent)?document.addEventListener(ae.startEvent,function(){ce(!0)}):window.addEventListener("load",function(){ce(!0)}),"DOMContentLoaded"===ae.startEvent&&["complete","interactive"].indexOf(document.readyState)>-1&&ce(!0),window.addEventListener("resize",$(ce,ae.debounceDelay,!0)),window.addEventListener("orientationchange",$(ce,ae.debounceDelay,!0)),oe)},refresh:ce,refreshHard:se}}); ================================================ FILE: libs/autocomplete/autocomplete.js ================================================ /** * Json key/value autocomplete for jQuery * Provides a transparent way to have key/value autocomplete * Copyright (C) 2008 Ziadin Givan * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see http://www.gnu.org/licenses/ * * Examples * * document.querySelectorAll("input#example").autocomplete("autocomplete.php");//using default parameters * * document.querySelectorAll("input#example").autocomplete("autocomplete.php",{minChars:3,timeout:3000,validSelection:false,parameters:{'myparam':'myvalue'},before : function(input,text) {},after : function(input,text) {}}); * * minChars = Minimum characters the input must have for the ajax request to be made * timeOut = Number of miliseconds passed after user entered text to make the ajax request * validSelection = If set to true then will invalidate (set to empty) the value field if the text is not selected (or modified) from the list of items. * parameters = Custom parameters to be passed * after, before = a function that will be caled before/after the ajax request */ function _AutocompleteInput(el, params) { let textInput = el; let name = textInput.getAttribute("name"); let text = textInput.dataset.text; let textName = name; let index = 0; //check if name is array[name] if ((index = textName.lastIndexOf(']')) > 0) { textName = textName.substring(0, index) + "_text" + textName.substring(index, this.length); } else { textName += "_text"; } textInput.setAttribute("name", textName); //create a new hidden input that will be used for holding the return value when posting the form, then swap names with the original input let hiddenInput = generateElements('')[0]; hiddenInput.value = textInput.value; textInput.after(hiddenInput); if (text) { textInput.value = text; } let valueInput = el.nextElementSibling; //create the ul that will hold the text and values valueInput.before(generateElements('')[0]); let list = valueInput.previousElementSibling; list.after(generateElements('')[0]); let btnClose = list.nextElementSibling; let oldText = ''; let typingTimeout; let size = 0; let selected = -1; let self = this; let settings = { ...{//provide default settings minChars : 1, timeout: 1000, after : null, before : null, onSelect : null, validSelection : true, allowFreeText:false, searchOnFocus:true, useKeyAsValue:false, url : el.dataset.url, listName : el.dataset.listName ?? "list", parameters : {'inputName' : valueInput.getAttribute('name'), 'inputId' : textInput.getAttribute('id')} }, ...params}; function selectOption(value, text) { valueInput.value = value; textInput.value = text; const e = new CustomEvent("autocomplete.change", {bubbles: true, detail: { value, text, name, listName:settings.listName } }); textInput.dispatchEvent(e); //textInput.trigger("autocomplete.change", [ value, text, name, settings.listName ]); if (typeof settings.onSelect == "function") { settings.onSelect(value, text, name, settings.listName); } clear(); } list.addEventListener("click", function (e) { let item = event.target.closest("li"); if (item) { value = item.getAttribute('value'); text = item.textContent; if (settings.useKeyAsValue) { selectOption(value, value); } else { selectOption(value, text); } } }); function getData(text){ window.clearInterval(typingTimeout); if (text != oldText && (settings.minChars != null && text.length >= settings.minChars)) { clear(); if (typeof settings.before == "function") { settings.before(textInput,text); } textInput.classList.add('autocomplete-loading'); settings.parameters.text = text.trim(); let results = (data) => { let items = ''; if (data) { size = 0; for ( key in data ) {//get key => value let txt = generateElements("" + data[key] + "")[0].textContent; let replace = txt.replace(text, "" + text + ""); let value = data[key].replace(txt, replace); items += '
  • ' + value + '
  • '; size++; } list.style.width = (Math.max(100, textInput.clientWidth)) + "px"; list.innerHTML = items; //on mouse hover over elements set selected class and on click set the selected value and close list list.style.display = "block"; if (typeof settings.after == "function") { settings.after(textInput,text); } textInput.classList.add('autocomplete-open'); } textInput.classList.remove('autocomplete-loading'); }; if (settings.url) { fetch(settings.url + "&"+ new URLSearchParams(settings.parameters)) .then((response) => { if (!response.ok) { throw new Error(response) } return response.json() }) .then(results) .catch(error => { console.log(error.statusText); //displayToast("bg-danger", "Error", "Error saving!"); }); } if (settings.data) { results(settings.data.filter((search) => search.indexOf(text) !== -1)); } oldText = text; } } function clear() { textInput.classList.remove('autocomplete-open'); textInput.classList.remove('autocomplete-loading'); list.style.display = "none"; size = 0; selected = -1; } btnClose.addEventListener("click", function (e) { clear(); e.preventDefault(); return false; }); if (settings.searchOnFocus) { textInput.addEventListener("focusin", function(e) { getData(textInput.value); }); } textInput.addEventListener("focusout", function(e) { //if no valid selection empty input setTimeout(() => { if (!hiddenInput.value && !settings.allowFreeText) { textInput.value = ""; } clear(); }, 500); }); textInput.addEventListener("keydown", function(e) { window.clearInterval(typingTimeout); if(e.which == 27) {//escape clear(); } else if (e.which == 46 || e.which == 8) {//delete and backspace clear(); //invalidate previous selection if (settings.validSelection) valueInput.value = ""; } else if(e.which == 13) {//enter if ( list.style.display == "none") {//if the list is not visible then make a new request, otherwise hide the list getData(textInput.value); } else { clear(); } if (settings.allowFreeText) { selectOption(textInput.value, textInput.value); clear(); } else { let selected = list.querySelector("li.selected"); if (selected) { if (settings.useKeyAsValue) { selectOption(selected.getAttribute('value'), selected.getAttribute('value')); } else { selectOption(selected.getAttribute('value'), selected.textContent); } clear(); } } e.preventDefault(); return false; } else if(e.which == 40 || e.which == 9 || e.which == 38) {//move up, down switch(e.which) { case 40: //down case 9: selected = (selected >= size - 1) ? 0 : selected + 1; break; case 38://up selected = (selected < 0) ? size -1 : selected - 1; break; default: break; } //set selected item and input values list.querySelectorAll("li").forEach((e, i) => { if (i == selected) { //textInput.value = e.textContent; //valueInput.value = e.getAttribute('value'); e.classList.add("selected"); } else { e.classList.remove("selected"); } }); } else { //invalidate previous selection if (settings.validSelection) valueInput.value = ''; typingTimeout = window.setTimeout(function() { getData(textInput.value) },settings.timeout); } }); return textInput; } function _AutocompleteList(el, options) { let autocomplete = _AutocompleteInput(el, options);//$(el).autocomplete(options); let values = {}; let settings = { ...{//provide default settings listName : el.dataset.listName ?? "list", }, ...options}; //look ahead if autocomple-list element is already in the page and reuse //useful to populate the autocomplete list from db let list; let sibling = autocomplete; while (sibling = sibling.nextElementSibling) { if (sibling.classList.contains("autocomplete-list")) { if (sibling.tagName != "input") { list = sibling; } break; } } if (!list) { list = generateElements('
    ')[0]; } let autocomplete_hidden = autocomplete.nextElementSibling; let name = autocomplete_hidden.getAttribute("name"); autocomplete_hidden.nextElementSibling.nextElementSibling.after(list); let autocomplete_list_hidden = generateElements('')[0]; //list.after(autocomplete_list_hidden.nextElementSibling);//add list after btn-close function addItem(value, text) { list.append(generateElements('
    \ ' + text + '\ \ \
    ')[0]); autocomplete.value = ""; }; function setList() { let values = {}; list.querySelectorAll('input[type="hidden"]').forEach(el => { values[el.value] = el.parentNode.querySelector("span").textContent; }); autocomplete_list_hidden.value = JSON.stringify(values); return values; }; function setValue(value) { if (value == "" || value == undefined) return false; let values = []; // value = decodeURIComponent(value); if (typeof value == "string") { values = JSON.parse(value); } else /*if (typeof value == "string")*/ { values = value; } for (key in values) { addItem(key, values[key]); } setList(); }; autocomplete.addEventListener("autocomplete.change", function (event) { autocomplete.addItem(event.detail.value, event.detail.text); values = autocomplete.setList(); const e = new CustomEvent('autocompletelist.change', {bubbles: true, detail: [ JSON.stringify(values) ] }); event.currentTarget.dispatchEvent(e); //autocomplete.trigger("autocompletelist.change", [ JSON.stringify(values) ]); //target, event, element, input } ); /* autocomplete.on("autocomplete.change", function(event, value, text) { autocomplete.addItem(value, text); values = autocomplete.setList(); const e = new CustomEvent('autocompletelist.change', {bubbles: true, detail: [ JSON.stringify(values) ] }); event.currentTarget.dispatchEvent(e); //autocomplete.trigger("autocompletelist.change", [ JSON.stringify(values) ]); }); */ list.addEventListener("click", function (event, value, text) { let item = event.target.closest(".remove-btn"); if (item) { item.parentNode.remove(); let values = setList(); const e = new CustomEvent('autocompletelist.change', {bubbles: true, detail: [ values ] }); autocomplete.dispatchEvent(e); //autocomplete.trigger("autocompletelist.change", [ JSON.stringify(values) ]); event.preventDefault(); return false; } }); autocomplete.setValue = setValue; autocomplete.addItem = addItem; autocomplete.setList = setList; el.autocompleteList = autocomplete; return autocomplete; } function _TagsInput(el, options) { let autocomplete = _AutocompleteInput(el, options);//$(el).autocomplete(options); let settings = { ...{//provide default settings listName : el.dataset.listName ?? "list", }, ...options}; let list = autocomplete.parentNode;//document.querySelectorAll('
    '); let autocomplete_hidden = autocomplete.nextElementSibling; let name = autocomplete_hidden.getAttribute("name"); autocomplete_hidden.nextElementSibling;//.after(list); let autocomplete_list_hidden = generateElements('')[0]; list.appendChild(autocomplete_list_hidden); function addItem(value, text) { let attributes = ';' let name = ''; //if name is array set value otherwise set value as array key if (settings.listName.lastIndexOf('[') > 0) { name = settings.listName + '[' + value + ']'; attributes = 'name="' + name + '" value="' + text + '"'; } else { name = settings.listName + '[' + settings.listId + '][' + value + ']'; attributes = 'name="' + name + '" value="' + text + '"'; } autocomplete.before(generateElements('
    ' + text + '\ \ \
    ')[0]); autocomplete.value = ""; }; function setList() { let values = {}; //console.log(('input[name ="list[]"]', list).serialize()); list.querySelectorAll('.tag input[type="hidden"]').forEach(el => { values[el.value] = el.parentNode.querySelector("span").textContent; }); //values = encodeURIComponent(JSON.stringify(values)); //values = JSON.stringify(values);//.replace('"', '\"'); autocomplete_list_hidden.value = JSON.stringify(values); return values; }; function setValue(value) { if (value == "" || value == undefined) return false; let values = []; // value = decodeURIComponent(value); if (typeof value == "string") { values = JSON.parse(value); } else /*if (typeof value == "string")*/ { values = value; } for (key in values) { addItem(key, values[key]); } setList(); }; let self = this; autocomplete.addEventListener("autocomplete.change", function(event) { addItem(event.detail.value, event.detail.text); let values = autocomplete.setList(); const e = new CustomEvent("tagsinput.change", {bubbles: true, detail: [ values ] }); autocomplete.dispatchEvent(e); //autocomplete.trigger("tagsinput.change", [ values ]); }); list.addEventListener("click", function (event) { let item = event.target.closest(".remove-btn"); if (item) { item.parentNode.remove(); let values = setList(); const e = new CustomEvent('tagsinput.change', {bubbles: true, detail: [ values ] }); autocomplete.dispatchEvent(e); event.preventDefault(); return false; } }); autocomplete.setValue = setValue; autocomplete.addItem = addItem; autocomplete.setList = setList; el.tagsInput = autocomplete; return autocomplete; } ================================================ FILE: libs/builder/blocks-bootstrap4.js ================================================ /* Copyright 2017 Ziadin Givan Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. https://github.com/givanz/Vvvebjs */ //Snippets from https://bootsnipp.com/license Vvveb.BlocksGroup['Bootstrap'] = ["bootstrap4/product-card", "bootstrap4/user-online", "bootstrap4/our-team", "bootstrap4/login-form", "bootstrap4/about-team", "bootstrap4/pricing-1", "bootstrap4/loading-circle", "bootstrap4/block-quote", "bootstrap4/subscribe-newsletter"]; Vvveb.Blocks.add("bootstrap4/product-card", { name: "Product Cards with Transition", image: "https://d2d3qesrx8xj6s.cloudfront.net/img/screenshots/0c3153bcb2ed97483a82b1f4ea966f8187379792.png", html: `
    Low KMS 2018 Alternate Text
    Used $28,000.00 13000 Kms
    Honda Accord LX
    View
    Fully-Loaded 2017 Alternate Text
    Used $28,000.00 13000 Kms
    Honda CIVIC HATCHBACK LS
    View
    Price Reduced 2018 Alternate Text
    Used $22,000.00 8000 Kms
    Honda Accord Hybrid LT
    View
    `, }); Vvveb.Blocks.add("bootstrap4/user-online", { name: "User online", image: "https://d2d3qesrx8xj6s.cloudfront.net/img/screenshots/75091e3b5e6efba238457f05e6f9edd847de1bf8.jpg", html: `
    `, }); Vvveb.Blocks.add("bootstrap4/our-team", { name: "Our team", image: "https://d2d3qesrx8xj6s.cloudfront.net/img/screenshots/b43c39513963d870d399a0aab2438af225f9f485.jpg", html:`
    OUR TEAM

    card image

    Sunlimetech

    This is basic card with image on top, title, description and button.

    Sunlimetech

    This is basic card with image on top, title, description and button.This is basic card with image on top, title, description and button.This is basic card with image on top, title, description and button.

    card image

    Sunlimetech

    This is basic card with image on top, title, description and button.

    Sunlimetech

    This is basic card with image on top, title, description and button.This is basic card with image on top, title, description and button.This is basic card with image on top, title, description and button.

    card image

    Sunlimetech

    This is basic card with image on top, title, description and button.

    Sunlimetech

    This is basic card with image on top, title, description and button.This is basic card with image on top, title, description and button.This is basic card with image on top, title, description and button.

    card image

    Sunlimetech

    This is basic card with image on top, title, description and button.

    Sunlimetech

    This is basic card with image on top, title, description and button.This is basic card with image on top, title, description and button.This is basic card with image on top, title, description and button.

    card image

    Sunlimetech

    This is basic card with image on top, title, description and button.

    Sunlimetech

    This is basic card with image on top, title, description and button.This is basic card with image on top, title, description and button.This is basic card with image on top, title, description and button.

    card image

    Sunlimetech

    This is basic card with image on top, title, description and button.

    Sunlimetech

    This is basic card with image on top, title, description and button.This is basic card with image on top, title, description and button.This is basic card with image on top, title, description and button.

    `, }); Vvveb.Blocks.add("bootstrap4/login-form", { name: "Login form", image: "https://d2d3qesrx8xj6s.cloudfront.net/img/screenshots/fd3f41be24ffb976d66edf08adc4b2453a871b19.jpeg", html:`
    `, }); Vvveb.Blocks.add("bootstrap4/about-team", { name: "About and Team Section", image: "https://assets.startbootstrap.com/img/screenshots/snippets/about-team.jpg", html:`
    ...
    Team Member
    Web Developer
    ...
    Team Member
    Web Developer
    ...
    Team Member
    Web Developer
    ...
    Team Member
    Web Developer
    `, }); Vvveb.Blocks.add("bootstrap4/pricing-1", { name: "Pricing table", image: "https://d2d3qesrx8xj6s.cloudfront.net/img/screenshots/e92f797807bb4cd880bc3f161d9f9869854b6991.jpeg", html:`

    Plan 1

    $100 / Month

    • Personal use
    • Unlimited projects
    • 27/7 support

    Plan 2

    $200 / Month

    • Personal use
    • Unlimited projects
    • 27/7 support

    Plan 3

    $300 / Month

    • Personal use
    • Unlimited projects
    • 27/7 support
    `, }); Vvveb.Blocks.add("bootstrap4/loading-circle", { name: "Loading circle", image: "https://d2d3qesrx8xj6s.cloudfront.net/img/screenshots/39f0571b9a377cb7ac9c0c11d2346b07dabe1c66.png", html:`
    `, }); Vvveb.Blocks.add("bootstrap4/block-quote", { name: "Block quote", image: "https://d2d3qesrx8xj6s.cloudfront.net/img/screenshots/d9f382e143b77d5a630dd79a2a3860611a8a953c.jpg", html:`

    Don't believe anything that you read on the internet, it may be fake.


    Abraham Lincoln

    896  ❤

    `, }); Vvveb.Blocks.add("bootstrap4/subscribe-newsletter", { name: "Subscribe newsletter", image: "https://d2d3qesrx8xj6s.cloudfront.net/img/screenshots/4f610196b7cb9596555c9c8c475d93ab4ef084f6.jpg", html:` `, }); ================================================ FILE: libs/builder/builder.js ================================================ /* Copyright 2017 Ziadin Givan Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. https://github.com/givanz/VvvebJs */ // Simple JavaScript Templating and buildParams // John Resig - https://johnresig.com/ - MIT Licensed (function(){ let cache = {}; let startTag = "{%"; let endTag = "%}"; let re1 = new RegExp(`((^|${endTag})[^\t]*)'`,"g"); let re2 = new RegExp(`\t=(.*?)${endTag}`,"g"); this.tmpl = function tmpl(str, data){ // Figure out if we're getting a template, or if we need to // load the template - and be sure to cache the result. let fn = /^[-a-zA-Z0-9]+$/.test(str) ? cache[str] = cache[str] || tmpl(document.getElementById(str).innerHTML) : // Generate a reusable function that will serve as a template // generator (and which will be cached). new Function("obj", "let p=[],print=function(){p.push.apply(p,arguments);};" + // Introduce the data as local variables using with(){} "with(obj){p.push('" + // Convert the template into pure JavaScript str .replace(/[\r\t\n]/g, " ") .split(startTag).join("\t") .replace(re1, "$1\r") .replace(re2, "',$1,'") .split("\t").join("');") .split(endTag).join("p.push('") .split("\r").join("\\'") + "');}return p.join('');"); // Provide some basic currying to the user return data ? fn( data ) : fn; }; })(); function buildParams( prefix, obj, add ) { let rbracket = /\[\]$/; if ( Array.isArray( obj ) ) { // Serialize array item. for(const key in obj) { let v = obj[key]; if ( rbracket.test( prefix ) ) { // Treat each array item as a scalar. add( prefix, v ); } else { // Item is non-scalar (array or object), encode its numeric index. buildParams( prefix + "[" + ( typeof v === "object" && v != null ? key : "" ) + "]", v, add ); } } } else if ( typeof obj === "object" ) { // Serialize object item. for (const name in obj ) { buildParams( prefix + "[" + name + "]", obj[ name ], add ); } } else { // Serialize scalar item. add( prefix, obj ); } } // Serialize an array of form elements or a set of // key/values into a query string function nestedFormData( a ) { let prefix, s = [], add = function( key, valueOrFunction ) { // If value is a function, invoke it and use its return value let value = typeof valueOrFunction === "function" ? valueOrFunction() : valueOrFunction; s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value == null ? "" : value ); }; if ( a == null ) { return ""; } // If an array was passed in, assume that it is an array of form elements. if ( Array.isArray( a ) || ( Object.is( a ) ) ) { // Serialize the form elements for(const key in object) { let v = object[key]; //jQuery.each( a, function() { add( key, v ); }; } else { // If traditional, encode the "old" way (the way 1.3.2 or older // did it), otherwise encode params recursively. for (const prefix in a ) { buildParams( prefix, a[ prefix ], add ); } } // Return the resulting serialization return s.join( "&" ); }; let delay = (function(){ let timer = 0; return function(callback, ms){ clearTimeout (timer); timer = setTimeout(callback, ms); }; })(); function isElement(obj){ return (typeof obj==="object") && (obj.nodeType===1) && (typeof obj.style === "object") && (typeof obj.ownerDocument ==="object")/* && obj.tagName != "BODY"*/; } function generateElements(html) { const template = document.createElement('template'); template.innerHTML = html.trim(); return template.content.children; } function offset(el) { box = el.getBoundingClientRect(); docElem = document.documentElement; return { top: box.top + window.pageYOffset - docElem.clientTop, left: box.left + window.pageXOffset - docElem.clientLeft }; } if (Vvveb === undefined) var Vvveb = {}; Vvveb.defaultComponent = "_base"; Vvveb.preservePropertySections = true; //icon = use component icon when dragging | html = use component html to create draggable element Vvveb.dragIcon = 'icon'; //if empty the html of the component is used to view dropping in real time but for large elements it can jump around for this you can set a html placeholder with this option Vvveb.dragElementStyle = "background:limegreen;width:100%;height:3px;border:1px solid limegreen;box-shadow:0px 0px 2px 1px rgba(0,0,0,0.14);overflow:hidden;"; Vvveb.dragHtml = '
    '; Vvveb.baseUrl = document.currentScript?document.currentScript.src.replace(/[^\/]*?\.js$/,''):''; Vvveb.imgBaseUrl = Vvveb.baseUrl; Vvveb.ComponentsGroup = {}; Vvveb.SectionsGroup = {}; Vvveb.BlocksGroup = {}; Vvveb.StylesGroup = {}; Vvveb.Components = { _components: {}, _nodesLookup: {}, _attributesLookup: {}, _classesLookup: {}, _classesRegexLookup: {}, componentPropertiesElement: "#right-panel .component-properties", componentPropertiesDefaultSection: "content", get: function(type) { return this._components[type]; }, updateProperty: function(type, key, value) { let properties = this._components[type]["properties"]; for (property in properties) { if (key == properties[property]["key"]) { return this._components[type]["properties"][property] = Object.assign(properties[property], value); } } }, getProperty: function(type, key) { let properties = this._components[type] ? this._components[type]["properties"] : []; for (property in properties) { if (key == properties[property]["key"]) { return properties[property]; } } }, add: function(type, data) { data.type = type; this._components[type] = data; if (data.nodes) { for (let i in data.nodes) { this._nodesLookup[ data.nodes[i] ] = data; } } if (data.attributes) { if (data.attributes.constructor === Array) { for (let i in data.attributes) { this._attributesLookup[ data.attributes[i] ] = data; } } else { for (let i in data.attributes) { if (typeof this._attributesLookup[i] === 'undefined') { this._attributesLookup[i] = {}; } if (typeof this._attributesLookup[i][ data.attributes[i] ] === 'undefined') { this._attributesLookup[i][ data.attributes[i] ] = {}; } this._attributesLookup[i][ data.attributes[i] ] = data; } } } if (data.classes) { for (let i in data.classes) { this._classesLookup[ data.classes[i] ] = data; } } if (data.classesRegex) { for (let i in data.classesRegex) { this._classesRegexLookup[ data.classesRegex[i] ] = data; } } }, extend: function(inheritType, type, data) { let newData = data; if (inheritData = this._components[inheritType]) { newData = {...inheritData, ...data}; newData.properties = (data.properties ? data.properties : []).concat(inheritData.properties ? inheritData.properties : []); } //sort by order newData.properties.sort(function (a,b) { if (typeof a.sort === "undefined") a.sort = 0; if (typeof b.sort === "undefined") b.sort = 0; if (a.sort < b.sort) return -1; if (a.sort > b.sort) return 1; return 0; }); this.add(type, newData); }, matchNode: function(node) { let component = {}; if (!node || !node.tagName) return false; if (node.attributes && node.attributes.length) { //search for attributes for (let i in node.attributes) { if (node.attributes[i]) { attr = node.attributes[i].name; value = node.attributes[i].value; if (attr in this._attributesLookup) { component = this._attributesLookup[ attr ]; //currently we check that is not a component by looking at name attribute //if we have a collection of objects it means that attribute value must be checked if (typeof component["name"] === "undefined") { if (value in component) { return component[value]; } } else { return component; } } } } for (let i in node.attributes) { attr = node.attributes[i].name; value = node.attributes[i].value; //check for node classes if (attr == "class") { classes = value.split(" "); for (j in classes) { if (classes[j] in this._classesLookup) return this._classesLookup[ classes[j] ]; } for (regex in this._classesRegexLookup) { regexObj = new RegExp(regex); if (regexObj.exec(value)) { return this._classesRegexLookup[ regex ]; } } } } } tagName = node.tagName.toLowerCase(); if (tagName in this._nodesLookup) return this._nodesLookup[ tagName ]; return false; //return false; }, renderProperties: function(component, properties, componentsPanelSections, container) { let fn = function(component, property) { if (property.input) { property.input.addEventListener('propertyChange', (event) => { element = selectedElement = Vvveb.Builder.selectedEl; let value = event.detail.value, input = event.detail.input, origEvent = event.detail.origEvent; if (property.element) element = property.element; if (property.child) element = element.querySelector(property.child); if (property.parent) element = element.parent(property.parent); if (property.onChange) { let ret = property.onChange(element, value, input, component, origEvent); //if on change returns an object then is returning the dom node otherwise is returning the new value if (typeof ret == "object") { element = ret; } else { value = ret; } }/* else */ if (property.htmlAttr) { let oldValue; if (property.htmlAttr == "class" && property.validValues) { oldValue = element.getAttribute(property.htmlAttr); if (property.validValues) { element.classList.remove(...property.validValues.filter(v => v)); } if (value) { element.classList.add(...value.split(" ")); } } else if (property.htmlAttr == "style") { //keep old style for undo oldStyle = window.FrameDocument.getElementById("vvvebjs-styles").textContent; element = Vvveb.StyleManager.setStyle(element, property.key, value); } else if (property.htmlAttr == "innerHTML") { oldValue = element.innerHTML; element = Vvveb.ContentManager.setHtml(element, value); } else if (property.htmlAttr == "outerHTML") { oldValue = element.outerHTML; element = Vvveb.ContentManager.setHtml(element, value, true); } else if (property.htmlAttr == "innerText") { oldValue = element.innerText; element = Vvveb.ContentManager.setText(element, value); } else { oldValue = element.getAttribute(property.htmlAttr); //if value is empty then remove attribute useful for attributes without values like disabled if (value) { element.setAttribute(property.htmlAttr, value); } else { element.removeAttribute(property.htmlAttr); } } if (property.htmlAttr == "style") { mutation = { type: 'style', target: element, attributeName: property.htmlAttr, oldValue: oldStyle, newValue: window.FrameDocument.getElementById("vvvebjs-styles").textContent}; Vvveb.Undo.addMutation(mutation); } else { Vvveb.Undo.addMutation({ type: 'attributes', target: element, attributeName: property.htmlAttr, oldValue: oldValue, newValue: value }); } } if (component && component.onChange) { element = component.onChange(element, property, value, input); } if (property.child || property.parent) { Vvveb.Builder.selectNode(selectedElement); } else { Vvveb.Builder.selectNode(element); } return element; }); } return property.input; }; let element; let defaultSection = this.componentPropertiesDefaultSection; let section; if (componentsPanelSections) { section = componentsPanelSections[defaultSection].querySelector('.section[data-section="default"]'); } let nodeElement = Vvveb.Builder.selectedEl; for (let i in properties) { let property = properties[i]; let element = nodeElement; if (property.beforeInit) property.beforeInit(element); if (property.element) element = property.element; if (property.child) element = element.querySelector(property.child) ?? element; if (property.parent) element = element.closest(property.parent) ?? element; if (property.data) { property.data["key"] = property.key; } else { property.data = {"key" : property.key}; } if (typeof property.group === 'undefined') property.group = null; property.input = property.inputtype.init(property.data, element); let value; if (property.init) { property.inputtype.setValue(property.init(element)); } else if (property.htmlAttr) { if (property.htmlAttr == "style") { //value = element.css(property.key);//jquery css returns computed style value = Vvveb.StyleManager.getStyle(element, property.key);//getStyle returns declared style } else if (property.htmlAttr == "innerHTML") { value = Vvveb.ContentManager.getHtml(element); } else if (property.htmlAttr == "outerHTML") { value = Vvveb.ContentManager.getHtml(element, true); } else if (property.htmlAttr == "innerText") { value = Vvveb.ContentManager.getText(element); } else { value = element.getAttribute(property.htmlAttr); } //if attribute is class check if one of valid values is included as class to set the select if (value && property.htmlAttr == "class" && property.validValues) { let valid = value.split(" ").filter(function(el) { return property.validValues.indexOf(el) != -1 }); if (valid && valid.length) { value = valid[0]; } else { value = ""; } } if (!value && property.defaultValue) { value = property.defaultValue; } property.inputtype.setValue(value); } else { if (!value && property.defaultValue) { value = property.defaultValue; } property.inputtype.setValue(value); } fn(component, property); let propertySection = defaultSection; if (property.section) { propertySection = property.section; } if (property.inputtype == SectionInput) { section = componentsPanelSections[propertySection].querySelector('.section[data-section="' + property.key + '"]'); if (Vvveb.preservePropertySections && section) { section.replaceChildren(); } else { componentsPanelSections[propertySection].append(property.input); section = componentsPanelSections[propertySection].querySelector('.section[data-section="' + property.key + '"]'); } } else { let row = generateElements(tmpl('vvveb-property', property))[0]; row.querySelector('.input').append(property.input); if (container) { container.append(row); } else { section.append(row); } } if (property.inputtype.afterInit) { property.inputtype.afterInit(property.input); } if (property.afterInit) { property.afterInit(element, property.input); } } }, render: function(type, panel = false) { let component = this._components[type]; if (!component) return; if (!panel) { //panel = document.querySelector(this.componentPropertiesElement); panel = this.componentPropertiesElement; } let defaultSection = this.componentPropertiesDefaultSection; let componentsPanelSections = {}; document.querySelectorAll(panel + " .tab-pane").forEach((el, i) => { let sectionName = el.dataset.section; componentsPanelSections[sectionName] = el; for (const item of el.querySelectorAll( 'label:not([data-header="default"]) + input,' + 'label:not([data-header="default"]),' + '.section:not([data-section="default"])' )) { item.remove(); } }); let section = componentsPanelSections[defaultSection].querySelector('.section[data-section="default"]'); if (!(Vvveb.preservePropertySections && section)) { let template = tmpl("vvveb-input-sectioninput", {key:"default", header:component.name}); componentsPanelSections[defaultSection].replaceChildren(); componentsPanelSections[defaultSection].append(generateElements(template)[0]); section = componentsPanelSections[defaultSection].querySelector(".section"); } componentsPanelSections[defaultSection].querySelector('[data-header="default"] span').innerHTML = component.name; section.replaceChildren(); if (component.beforeInit) component.beforeInit(Vvveb.Builder.selectedEl); this.renderProperties(component, component.properties, componentsPanelSections); if (component.init) component.init(Vvveb.Builder.selectedEl); } }; Vvveb.Blocks = { _blocks: {}, get: function(type) { return this._blocks[type]; }, add: function(type, data) { data.type = type; this._blocks[type] = data; }, }; Vvveb.Sections = { _sections: {}, get: function(type) { return this._sections[type]; }, add: function(type, data) { data.type = type; this._sections[type] = data; }, }; Vvveb.Styles = { _styles: {}, get: function(type) { return this._styles[type]; }, getByStyle: function(style) { for (const name in this._styles) { if (this._styles[name].style == style) { return name; } } }, add: function(type, data) { data.type = type; this._styles[type] = data; }, }; Vvveb.WysiwygEditor = { isActive: false, oldValue: '', doc:false, editorSetStyle: function (tag, style = {}, toggle = false) { let iframeWindow = Vvveb.Builder.iframe.contentWindow; let selection = iframeWindow.getSelection(); let element = this.element; let range; if (!tag) { tag = "span"; } if (selection.rangeCount > 0) { //check if the whole text is inside an existing node to use the node directly if ((selection.baseNode && selection.baseNode.nextSibling == null && selection.baseNode.previousSibling == null && selection.anchorOffset == 0 && selection.focusOffset == selection.baseNode.length) || (selection.anchorOffset == selection.focusOffset)) { element = selection.baseNode.parentNode; } else { element = document.createElement(tag); range = selection.getRangeAt(0); try { range.surroundContents(element); range.selectNodeContents(element.childNodes[0], 0); } catch (e) { let content = range.extractContents(); element.appendChild(content); range.insertNode(element); range.selectNodeContents(element); } } } if (element && style) { for (name in style) { if ( !style[name] || (toggle && element.style.getPropertyValue(name))) { element.style.removeProperty(name); } else { element.style.setProperty(name, style[name]); } } } //if edited text is an empty span remove the span if (element.tagName == "SPAN" && element.style.length == 0 && element.attributes.length <= 1) { let textNode = iframeWindow.document.createTextNode(element.innerText); element.replaceWith(textNode); element = textNode; range = iframeWindow.document.createRange(); range.selectNodeContents(element); selection.removeAllRanges(); selection.addRange(range); } //select link element to edit link etc if (tag == "a") { Vvveb.Builder.selectNode(element); Vvveb.Builder.loadNodeComponent(element); } return element; }, init: function(doc) { this.doc = doc; let self = this; document.getElementById("bold-btn").addEventListener("click", function (e) { //doc.execCommand('bold',false,null); //self.editorSetStyle("b", {"font-weight" : "bold"}, true); self.editorSetStyle(false, {"font-weight" : "bold"}, true); e.preventDefault(); return false; }); document.getElementById("italic-btn").addEventListener("click", function (e) { //doc.execCommand('italic',false,null); //self.editorSetStyle("i", {"font-style" : "italic"}, true); self.editorSetStyle(false, {"font-style" : "italic"}, true); e.preventDefault(); return false; }); document.getElementById("underline-btn").addEventListener("click", function (e) { //doc.execCommand('underline',false,null); //self.editorSetStyle("u", {"text-decoration" : "underline"}, true); self.editorSetStyle(false, {"text-decoration" : "underline"}, true); e.preventDefault(); return false; }); document.getElementById("strike-btn").addEventListener("click", function (e) { //doc.execCommand('strikeThrough',false,null); //self.editorSetStyle("strike", {"text-decoration" : "line-through"}, true); self.editorSetStyle(false, {"text-decoration" : "line-through"}, true); e.preventDefault(); return false; }); document.getElementById("link-btn").addEventListener("click", function (e) { //doc.execCommand('createLink',false,"#"); self.editorSetStyle("a"); e.preventDefault(); return false; }); document.getElementById("fore-color").addEventListener("change", function (e) { //doc.execCommand('foreColor',false,this.value); self.editorSetStyle(false, {"color" : this.value}); e.preventDefault(); return false; }); document.getElementById("back-color").addEventListener("change", function (e) { //doc.execCommand('hiliteColor',false,this.value); self.editorSetStyle(false, {"background-color" : this.value}); e.preventDefault(); return false; }); document.getElementById("font-size").addEventListener("change", function (e) { //doc.execCommand('fontSize',false,this.value); self.editorSetStyle(false, {"font-size" : this.value}); e.preventDefault(); return false; }); let sizes = ""; for (i = 1;i <= 128; i++) { sizes += ""; } document.getElementById("font-size").innerHTML = sizes; document.getElementById("font-family").addEventListener("change", function (e) { let option = this.options[this.selectedIndex]; let element = self.editorSetStyle(false, {"font-family" : this.value}); Vvveb.FontsManager.addFont(option.dataset.provider, this.value, element); //doc.execCommand('fontName',false,this.value); e.preventDefault(); return false; }); document.getElementById("justify-btn").addEventListener("click", function (e) { //let command = "justify" + this.dataset.value; //doc.execCommand(command,false,"#"); self.editorSetStyle(false, {"text-align" : e.srcElement.dataset.value}); e.preventDefault(); return false; }); doc.addEventListener('keydown', event => { if (event.key === 'Enter') { let target = event.target.closest("[contenteditable]"); if (target) { doc.execCommand('insertLineBreak'); event.preventDefault(); } } }) }, undo: function(element) { this.doc.execCommand('undo',false,null); }, redo: function(element) { this.doc.execCommand('redo',false,null); }, edit: function(element) { element.setAttribute("contenteditable", true); element.setAttribute("spellcheckker", false); document.getElementById("wysiwyg-editor").style.display = "block"; this.element = element; this.isActive = true; this.oldValue = element.innerHTML; document.getElementById("font-family").value = Vvveb.StyleManager.getStyle(element,'font-family'); document.getElementById("fore-color").value = Vvveb.StyleManager.getStyle(element,'color'); document.getElementById("back-color").value = Vvveb.StyleManager.getStyle(element,'background-color'); element.focus(); }, destroy: function(element) { element.removeAttribute("contenteditable"); element.removeAttribute("spellcheckker"); document.getElementById("wysiwyg-editor").style.display = "none"; this.isActive = false; node = this.element; Vvveb.Undo.addMutation({type:'characterData', target: node, oldValue: this.oldValue, newValue: node.innerHTML}); } } Vvveb.Builder = { component : {}, dragMoveMutation : false, isPreview : false, runJsOnSetHtml : false, designerMode : false, highlightEnabled : false, selectPadding: 0, leftPanelWidth: 275, ignoreClasses: ["clearfix", "masonry", "has-shadow"], init: function(url, callback) { let self = this; self.loadControlGroups(); self.loadBlockGroups(); self.loadSectionGroups(); self.loadStylesGroups(); self.selectedEl = null; self.highlightEl = null; self.initCallback = callback; self.documentFrame = document.querySelector("#iframe-wrapper > iframe"); self.canvas = document.getElementById("canvas"); self._loadIframe(url + (url.indexOf('?') > -1 ? '&r=':'?r=') + Math.random()); self._initDragdrop(); self._initBox(); self.dragElement = null; self.highlightEnabled = true; self.leftPanelWidth = document.getElementById("left-panel").clientWidth; }, /* controls */ loadControlGroups : function() { let componentsList = document.querySelectorAll(".components-list"); let item = {}, component = {}; let count = 0; componentsList.forEach(function (list, i) { let type = list.dataset.type; list.replaceChildren(); count ++; for (group in Vvveb.ComponentsGroup) { list.append(generateElements( `
    1. `)[0]); //list.append('
      1. '); let componentsSubList = list.querySelector('li[data-section="' + group + '"] ol'); components = Vvveb.ComponentsGroup[ group ]; for (i in components) { const componentType = components[i]; component = Vvveb.Components.get(componentType); if (component) { item = generateElements(`
      2. ${component.name}
      3. `)[0]; if (component.image) { item.style.backgroundImage = "url(" + Vvveb.imgBaseUrl + component.image + ")"; item.style.backgroundRepeat = "no-repeat"; } componentsSubList.append(item); } } } }); }, loadSectionGroups : function() { let sectionsList = document.querySelectorAll(".sections-list"); let item = {}; sectionsList.forEach(function (list, i) { let type = list.dataset.type; list.replaceChildren(); for (group in Vvveb.SectionsGroup) { list.append(generateElements( `
        1. `)[0]); let sectionsSubList = list.querySelector('li[data-section="' + group + '"] ol'); let sections = Vvveb.SectionsGroup[ group ]; for (i in sections) { const sectionType = sections[i]; const section = Vvveb.Sections.get(sectionType); if (section) { item = generateElements(`
        2. ${section.name}
        3. `)[0]; if (section.image) { let image = ((section.image.indexOf('/') == -1) ? Vvveb.imgBaseUrl:'') + section.image; /* Object.assign(item.style,{ //backgroundImage: "url(" + image + ")", //backgroundRepeat: "no-repeat" });*/ item.querySelector("img").setAttribute("src", image); } sectionsSubList.append(item) } } } }); }, loadBlockGroups : function() { let blocksList = document.querySelectorAll(".blocks-list"); let item = {}; blocksList.forEach(function (list, i) { let type = list.dataset.type; list.replaceChildren(); for (group in Vvveb.BlocksGroup) { list.append(generateElements( `
          1. `)[0]); let blocksSubList = list.querySelector('li[data-section="' + group + '"] ol'); blocks = Vvveb.BlocksGroup[ group ]; for (i in blocks) { const blockType = blocks[i]; const block = Vvveb.Blocks.get(blockType); if (block) { item = generateElements(`
          2. ${block.name}
          3. `)[0]; if (block.image) { let image = ((block.image.indexOf('/') == -1) ? Vvveb.imgBaseUrl:'') + block.image; /* Object.assign(item.style,{ //backgroundImage: "url(" + image + ")", //backgroundRepeat: "no-repeat" });*/ item.querySelector("img").setAttribute("src", image); } blocksSubList.append(item); } } } }); }, loadStylesGroups : function() { let stylesList = document.querySelectorAll(".styles-list"); let item = {}; stylesList.forEach(function (list, i) { let type = list.dataset.type; list.replaceChildren(); for (group in Vvveb.StylesGroup) { list.append(generateElements( `
            1. `)[0]); let stylesSubList = list.querySelector('li[data-section="' + group + '"] ol'); styles = Vvveb.StylesGroup[ group ]; for (i in styles) { const styleType = styles[i]; const style = Vvveb.Styles.get(styleType); if (style) { item = generateElements(`
            2. ${style.name}
            3. `)[0]; if (style.image) { let image = ((style.image.indexOf('/') == -1) ? Vvveb.imgBaseUrl:'') + style.image; item.querySelector("img").setAttribute("src", image); } stylesSubList.append(item); } } } }); }, loadUrl : function(url, callback) { let self = this; document.getElementById("select-box").style.display = "none"; self.initCallback = callback; if (Vvveb.Builder.iframe.src != url) Vvveb.Builder.iframe.src = url; }, /* iframe */ _loadIframe : function(url) { let self = this; self.iframe = this.documentFrame; self.iframe.src = url; return this.documentFrame.addEventListener("load", function() { window.FrameWindow = self.iframe.contentWindow; window.FrameDocument = self.iframe.contentWindow.document; let addSectionBox = document.getElementById("add-section-box"); let highlightBox = document.getElementById("highlight-box"); let SelectBox = document.getElementById("select-box"); highlightBox.style.display = "none"; window.FrameWindow.addEventListener("beforeunload", function(event) { if (Vvveb.Undo.undoIndex >= 0) { let dialogText = "You have unsaved changes"; event.returnValue = dialogText; return dialogText; } }); window.FrameWindow.addEventListener("unload", function(event) { document.querySelector(".loading-message").classList.add("active"); Vvveb.Undo.reset(); }); //prevent accidental clicks on links when editing text window.FrameDocument.addEventListener("click", function(event) { if (Vvveb.WysiwygEditor.isActive && event.target.closest("a")) { event.preventDefault(); return false; } }); selectBoxPosition = function(event) { let pos; let target = self.selectedEl;// ?? self.highlightEl; highlightBox.style.display = "none"; if (target) { pos = offset(target); SelectBox.style.top = (pos.top - (self.frameDoc.scrollTop ?? 0) - self.selectPadding) + "px"; SelectBox.style.left = (pos.left - (self.frameDoc.scrollLeft ?? 0) - self.selectPadding) + "px"; SelectBox.style.width = ((target.offsetWidth ?? target.clientWidth) + self.selectPadding * 2) + "px"; SelectBox.style.height = ((target.offsetHeight ?? target.clientHeight) + self.selectPadding * 2) + "px"; } } window.FrameWindow.addEventListener("scroll", selectBoxPosition); window.FrameWindow.addEventListener("resize", selectBoxPosition); Vvveb.WysiwygEditor.init(window.FrameDocument); Vvveb.StyleManager.init(window.FrameDocument); Vvveb.ColorPaletteManager.init(window.FrameDocument); if (self.initCallback) self.initCallback(); return self._frameLoaded(); }); }, _frameLoaded : function() { let self = Vvveb.Builder; self.frameDoc = window.FrameDocument; self.frameHtml = window.FrameDocument.querySelector("html"); self.frameBody = window.FrameDocument.querySelector("body"); self.frameHead = window.FrameDocument.querySelector("head"); //insert editor helpers like non editable areas self.frameHead.append(generateElements('')[0]); self.frameHtml.setAttribute("data-vvvebjs-editor",""); self._initHighlight(); window.dispatchEvent(new CustomEvent("vvveb.iframe.loaded", {detail: self.frameDoc})); document.querySelector(".loading-message").classList.remove("active"); Vvveb.NewSection.init(); Vvveb.StyleList.load(self.frameDoc); //enable save button only if changes are made let setSaveButtonState = function (e) { if (Vvveb.Undo.hasChanges()){ document.querySelectorAll("#top-panel .save-btn").forEach(e => e.removeAttribute("disabled")); } else { document.querySelectorAll("#top-panel .save-btn").forEach(e => e.setAttribute("disabled", "true")); } }; Vvveb.Builder.frameBody.addEventListener("vvveb.undo.add", setSaveButtonState); Vvveb.Builder.frameBody.addEventListener("vvveb.undo.restore", setSaveButtonState); }, _getElementType: function(el) { //search for component attribute let componentName = ''; let componentAttribute = ''; if (el.attributes) { for (let j = 0; j < el.attributes.length; j++){ let nodeName = el.attributes[j].nodeName; if (nodeName.indexOf('data-component') > -1) { componentName = nodeName.replace('data-component-', ''); return [componentName, "component"]; } if (nodeName.indexOf('data-v-component-') > -1) { componentName = nodeName.replace('data-v-component-', ''); return [componentName,"component"]; } if (nodeName.indexOf('data-v-') > -1) { componentAttribute = (componentAttribute ? componentAttribute + " - " : "") + nodeName.replace('data-v-', '') + " "; } } } if (componentAttribute != '') return [componentAttribute, "attribute"]; if (el.id) { componentName = "#" + el.id; } else { componentName = (el.className && (typeof el.className == "string")) ? "." + el.className.split(" ")[0] : ""; } return [componentName, el.tagName]; }, loadNodeComponent: function(node) { const data = Vvveb.Components.matchNode(node); let component; if (data) component = data.type; else component = Vvveb.defaultComponent; Vvveb.component = Vvveb.Components.get(component); Vvveb.Components.render(component); this.selectedComponent = component; //if component properties is loaded in left panel tab instead of right panel show tab let propertiesTab = document.querySelector(".component-properties-tab a"); if (propertiesTab.offsetParent) {//if properites tab is enabled/visible propertiesTab.style.display = ""; const bsTab = bootstrap.Tab.getOrCreateInstance(propertiesTab); bsTab.show(); } }, reloadComponent: function() { Vvveb.Components.render(this.selectedComponent); }, moveNodeUp: function(node) { if (!node) { node = Vvveb.Builder.selectedEl; } const oldParent = node.parentNode; const oldNextSibling = node.nextSibling; const next = node.previousElementSibling; if (next) { next.before(node); } else { node.parentNode.before(node); } Vvveb.Builder.selectNode(node); const newParent = node.parentNode; const newNextSibling = node.nextSibling; Vvveb.Undo.addMutation({type: 'move', target: node, oldParent: oldParent, newParent: newParent, oldNextSibling: oldNextSibling, newNextSibling: newNextSibling}); }, moveNodeDown: function(node) { if (!node) { node = Vvveb.Builder.selectedEl; } const oldParent = node.parentNode; const oldNextSibling = node.nextSibling; const next = node.nextElementSibling; if (next) { next.after(node); } else { node.parentNode.after(node); } Vvveb.Builder.selectNode(node); const newParent = node.parentNode; const newNextSibling = node.nextSibling; Vvveb.Undo.addMutation({type: 'move', target: node, oldParent: oldParent, newParent: newParent, oldNextSibling: oldNextSibling, newNextSibling: newNextSibling}); }, cloneNode: function(node) { if (!node) { node = Vvveb.Builder.selectedEl; } const clone = node.cloneNode(true); node.after(clone); node.click(); Vvveb.Undo.addMutation({type: 'childList', target: node.parentNode, addedNodes: [clone], nextSibling: node.nextSibling}); }, selectNode: function(node) { let SelectBox = document.getElementById("select-box"); if (!node) { SelectBox.style.display = "none"; return; } let self = this; let SelectActions = document.getElementById("select-actions"); let AddSectionBtn = document.getElementById("add-section-btn"); let elementType = this._getElementType(node); if (self.texteditEl && (self.selectedEl != node)) { Vvveb.WysiwygEditor.destroy(self.texteditEl); self.selectPadding = 0; SelectBox.classList.remove("text-edit"); SelectActions.style.display = ""; self.texteditEl = null; } if (elementType[1] == "BODY") { SelectActions.style.display = "none"; AddSectionBtn.style.display = "none"; } else { SelectActions.style.display = ""; AddSectionBtn.style.display = ""; } let target = node; self.selectedEl = target; try { let pos = offset(target); let top = (pos.top - (self.frameDoc.scrollTop ?? 0) - self.selectPadding); SelectBox.style.top = top + "px"; SelectBox.style.left = (pos.left - (self.frameDoc.scrollLeft ?? 0) - self.selectPadding) + "px"; SelectBox.style.width = ((target.offsetWidth ?? target.clientWidth) + self.selectPadding * 2) + "px"; SelectBox.style.height = ((target.offsetHeight ?? target.clientHeight) + self.selectPadding * 2) + "px"; SelectBox.style.display = "block"; //move actions toolbar to bottom if there is no space on top if (top < 30) { SelectActions.style.top = "unset"; SelectActions.style.bottom = "-25px"; } else { SelectActions.style.top = ""; SelectActions.style.bottom = ""; } Vvveb.Breadcrumb.loadBreadcrumb(target); } catch(err) { console.log(err); return false; } document.querySelector("#highlight-name .type").innerHTML = elementType[0]; document.querySelector("#highlight-name .name").innerHTML = elementType[1]; window.dispatchEvent(new CustomEvent("vvveb.Builder.selectNode", {detail: {target}})); }, /* iframe highlight */ _initHighlight: function() { let self = Vvveb.Builder; let highlightMove = function(event) { if (self.highlightEnabled == true && event.target/* && isElement(event.target)*/) { self.highlightEl = target = event.target; let pos = offset(target); let height = target.offsetHeight; let halfHeight = Math.max(height / 2, 5); let width = target.offsetWidth; let halfWidth = Math.max(width / 2, 5); let prepend = true; let x = event.x; let y = event.y; if (self.isResize) { if (!self.initialPosition) { self.initialPosition = {x,y}; } let deltaX = x - self.initialPosition.x; let deltaY = y - self.initialPosition.y; pos = offset(self.selectedEl); width = self.initialSize.width; height = self.initialSize.height; switch (self.resizeHandler) { // top case "top-left": height -= deltaY; width -= deltaX; break; case "top-center": height -= deltaY; break; case "top-right": height -= deltaY; width += deltaX; break; // center case "center-left": width -= deltaX; break; case "center-right": width += deltaX; break; // bottom case "bottom-left": width -= deltaX; height += deltaY; break; case "bottom-center": height += deltaY; break; case "bottom-right": width += deltaX; height += deltaY; break; } if (self.resizeMode == "css") { self.selectedEl.style.width = width + "px"; self.selectedEl.style.height = height + "px"; } else { self.selectedEl.setAttribute("width", width); self.selectedEl.setAttribute("height", height); } let SelectBox = document.getElementById("select-box"); SelectBox.style.top = pos.top - (self.frameDoc.scrollTop ?? 0) + "px"; SelectBox.style.left = pos.left - (self.frameDoc.scrollLeft ?? 0) + "px"; SelectBox.style.width = width + "px"; SelectBox.style.height = self.selectedEl.offsetHeight + "px"; SelectBox.style.display = "block"; } else if (self.isDragging) { let noChildren = { input: true, textarea: true, img: true, svg: true, iframe: true, embed: true, col: true, area: true, hr: true, br: true, wbr: true }; let parent = self.highlightEl; let isBlock = (window.getComputedStyle(parent).display == "block"); if (self.dragType == "section") { let closest = parent.closest("section, header, footer, body"); if (closest) { parent = closest; } noChildren.section = true; } let parentTagName = parent.tagName.toLowerCase(); let isVattribute = false; //check if node is a data-v-attribute dynamic node that will override the content if added inside if (parent.childElementCount == 0) { for (let attr of parent.attributes) { if (attr.name.startsWith("data-v-") && !attr.name.startsWith("data-v-component-")) { isVattribute = true; break; } } } try { if ((pos.top < (y - halfHeight)) || (pos.left < (x - halfWidth))) { if (noChildren[parentTagName] || isBlock || isVattribute) { parent.after(self.dragElement); } else { if (parent == self.dragElement.parenNode) { parent.appendChild(self.dragElement); } else { parent.append(self.dragElement); } } prepend = true; } else { if (noChildren[parentTagName] || isBlock || isVattribute) { parent.parentNode.insertBefore(self.dragElement, parent); } else { parent.prepend(self.dragElement); } prepend = false; }; if (self.designerMode) { let parentOffset = offset(self.dragElement.offsetParent); self.dragElement.style.position = "absolute"; self.dragElement.style.x = x - (parentOffset.left - self.frameDoc.scrollLeft); self.dragElement.style.y = y - (parentOffset.top - self.frameDoc.scrollTop); } } catch(err) { console.log(err); return false; } if (!self.designerMode && self.iconDrag) { self.iconDrag.style.top = (y + 60) + "px"; self.iconDrag.style.left = (x + self.leftPanelWidth + 10) + "px"; } }// else //uncomment else to disable parent highlighting when dragging { //if text editor is open check if the highlighted element is not inside the editor if (Vvveb.WysiwygEditor.isActive ) { if (self.texteditEl.contains(event.target)) { return true; } } document.getElementById("highlight-box").setAttribute("style", `top:${pos.top - (self.frameDoc.scrollTop ?? 0)}px; left:${pos.left - (self.frameDoc.scrollLeft ?? 0)}px; width:${width}px; height:${height}px; display:${event.target.hasAttribute('contenteditable') ? "none":"block"}; border:${self.isDragging ? "1px dashed #0d6efd":""}; `); if (height < 50) { document.getElementById("section-actions").classList.add("outside"); } else { document.getElementById("section-actions").classList.remove("outside"); } let elementType = self._getElementType(event.target); document.querySelector("#highlight-name .type").innerHTML = elementType[0]; document.querySelector("#highlight-name .name").innerHTML = elementType[1]; } } }; self.frameBody.addEventListener("mousemove", highlightMove); let highlightUp = function(event) { self.isResize = false; document.querySelectorAll("#section-actions, #highlight-name").forEach(el => el.style.display = ""); if (self.isDragging) { self.isDragging = false; Vvveb.Builder.highlightEnabled = true; if (self.iconDrag) self.iconDrag.remove(); document.getElementById("component-clone")?.remove(); if (self.dragMoveMutation === false) { if (self.component.dragHtml || Vvveb.dragHtml) { //if dragHtml is set for dragging then set real component html if (self.component) { let html = self.component.html.replace('RANDOM_ID', Math.floor(Math.random() * 1000)); newElement = generateElements(html)[0]; self.dragElement.replaceWith(newElement); self.dragElement = newElement; } } if (self.component.afterDrop) self.dragElement = self.component.afterDrop(self.dragElement); } else { self.selectedEl.classList.remove("is-dragged"); self.dragElement.replaceWith(self.selectedEl); self.dragElement = self.selectedEl; } const node = self.dragElement; self.selectNode(node); Vvveb.TreeList.loadComponents(); Vvveb.TreeList.selectComponent(node); self.loadNodeComponent(node); if (self.dragType == "section") { node.scrollIntoView({behavior: "smooth", block: "center", inline: "center"}); } if (self.dragMoveMutation === false) { Vvveb.Undo.addMutation({type: 'childList', target: node.parentNode, addedNodes: [node], nextSibling: node.nextSibling}); } else { self.dragMoveMutation.newParent = node.parentNode; self.dragMoveMutation.newNextSibling = node.nextSibling; Vvveb.Undo.addMutation(self.dragMoveMutation); self.dragMoveMutation = false; } } }; self.frameBody.addEventListener("mouseup", highlightUp); let highlightDbClick = function(event) { if (Vvveb.Builder.isPreview == false && Vvveb.Builder.highlightEnabled) { if (!Vvveb.WysiwygEditor.isActive) { self.selectPadding = 10; self.texteditEl = target = event.target; Vvveb.WysiwygEditor.edit(self.texteditEl); _updateSelectBox = function(event) { if (!self.texteditEl) return; let pos = offset(self.selectedEl); let SelectBox = document.getElementById("select-box"); SelectBox.style.top = (pos.top - (self.frameDoc.scrollTop ?? 0) - self.selectPadding) + "px"; SelectBox.style.left = (pos.left - (self.frameDoc.scrollLeft ?? 0) - self.selectPadding) + "px"; SelectBox.style.width = (self.texteditEl.offsetWidth + (self.selectPadding * 2)) + "px"; SelectBox.style.height = (self.texteditEl.offsetHeight + (self.selectPadding * 2)) + "px"; SelectBox.style.display = "block"; }; //update select box when the text size is changed self.texteditEl.addEventListener("blur", _updateSelectBox); self.texteditEl.addEventListener("keyup", _updateSelectBox); self.texteditEl.addEventListener("paste", _updateSelectBox); self.texteditEl.addEventListener("input", _updateSelectBox); _updateSelectBox(); document.getElementById("select-box").classList.add("text-edit") document.getElementById("select-actions").style.display = "none"; document.getElementById("highlight-box").style.display = "none"; } } }; self.frameBody.addEventListener("dblclick", highlightDbClick); let highlightClick = function(event) { if (Vvveb.Builder.isPreview == false && Vvveb.Builder.highlightEnabled){ if (event.target) { if (Vvveb.WysiwygEditor.isActive ) { if (self.texteditEl.contains(event.target)) { return true; } } self.selectNode(event.target); Vvveb.TreeList.selectComponent(event.target); self.loadNodeComponent(event.target); if (Vvveb.component.resizable) { document.getElementById("select-box").classList.add("resizable"); self.resizeMode = Vvveb.component.resizeMode; } else { document.getElementById("select-box").classList.remove("resizable"); } document.getElementById("add-section-box").style.display = "none"; event.preventDefault(); return false; } } }; self.frameBody.addEventListener("click", highlightClick); }, _initBox: function() { let self = this; document.getElementById("drag-btn").addEventListener("mousedown", function(event) { //self.dragElement = self.selectedEl.setAttribute("style",Vvveb.dragElementStyle); if (event.which == 1) {//left click self.isDragging = true; document.querySelectorAll("#section-actions, #highlight-name, #select-box").forEach(el => el.style.display = ""); if (self.designerMode) { self.dragElement = self.selectedEl; } else { self.selectedEl.style.position = ""; self.selectedEl.style.top = ""; self.selectedEl.style.left = ""; self.selectedEl.classList.add("is-dragged"); self.dragElement = generateElements(Vvveb.dragHtml)[0]; } const node = self.selectedEl; self.dragMoveMutation = {type: 'move', target: node, oldParent: node.parentNode, oldNextSibling: node.nextSibling}; //self.selectNode(false); event.preventDefault(); return false; } }); let resizeDown = function(event) { if (event.which == 1) {//left click document.querySelector("#section-actions, #highlight-name, #highlight-box").style.display = "none"; self.isResize = true; self.initialSize = {"width" : self.selectedEl.offsetWidth, "height" : self.selectedEl.offsetHeight}; self.initialPosition = false; self.resizeHandler = this.className; event.preventDefault(); return false; } }; document.querySelectorAll(".resize > div").forEach(e => e.addEventListener("mousedown", resizeDown)); document.getElementById("down-btn").addEventListener("click", function(event) { document.getElementById("select-box").style.display = "none"; Vvveb.Builder.moveNodeDown(); event.preventDefault(); return false; }); document.getElementById("up-btn").addEventListener("click", function(event) { document.getElementById("select-box").style.display = "none"; Vvveb.Builder.moveNodeUp(); event.preventDefault(); return false; }); document.getElementById("clone-btn").addEventListener("click", function(event) { Vvveb.Builder.cloneNode(); event.preventDefault(); return false; }); document.getElementById("parent-btn").addEventListener("click", function(event) { const node = self.selectedEl.parentNode; self.selectNode(node); self.loadNodeComponent(node); Vvveb.TreeList.selectComponent(node); event.preventDefault(); return false; }); document.getElementById("save-reusable-btn").addEventListener("click", function(event) { const node = self.selectedEl; let type = 'block'; if (node.tagName.toLowerCase() == 'section') { type = 'section'; } const name = prompt("Enter name for new reusable " + type, ''); if (name) { Vvveb.Builder.saveElement(node, type, name); } event.preventDefault(); return false; }); let codeEditorOldValue; document.getElementById("edit-code-btn").addEventListener("click", function(event) { let value = Vvveb.Builder.selectedEl.innerHTML; Vvveb.ModalCodeEditor.show(); Vvveb.ModalCodeEditor.setValue(value); codeEditorOldValue = value; event.preventDefault(); return false; }); let onSave = function(event) { Vvveb.Builder.selectedEl.innerHTML = event.detail; const node = Vvveb.Builder.selectedEl; Vvveb.Undo.addMutation({type:'characterData', target: node, oldValue: codeEditorOldValue, newValue: node.innerHTML}); Vvveb.Builder.selectNode(node); }; window.addEventListener("vvveb.ModalCodeEditor.save", onSave); document.getElementById("translate-code-btn")?.addEventListener("click", function(event) { let selectedEl = Vvveb.Builder.selectedEl; let value = selectedEl.innerHTML.trim(); // uncomment to use outerHTML, not recommended //let value = selectedEl.outerHTML; Vvveb.ModalCodeEditor.show(); Vvveb.ModalCodeEditor.setValue(value); let onSave = function(event) { selectedEl.innerHTML = event.detail; //selectedEl.outerHTML = value; }; window.removeEventListener("vvveb.ModalCodeEditor.save", onSave); window.addEventListener("vvveb.ModalCodeEditor.save", onSave); event.preventDefault(); return false; }); document.getElementById("delete-btn").addEventListener("click", function(event) { document.getElementById("select-box").style.display = "none"; const node = self.selectedEl; Vvveb.Undo.addMutation({type: 'childList', target: node.parentNode, removedNodes: [node], nextSibling: node.nextSibling}); self.selectedEl.remove(); Vvveb.TreeList.loadComponents(); Vvveb.SectionList.loadSections(); event.preventDefault(); return false; }); let addSectionBox = document.getElementById("add-section-box"); let addSectionElement; document.getElementById("add-section-btn").addEventListener("click", function(event) { addSectionElement = self.highlightEl; addSectionBox.style.display = "block"; addSectionBox.classList.remove("only-base"); let tagName = self.highlightEl.tagName.toLowerCase(); let bsTab; if (["section", "footer", "header"].includes(tagName)) { addSectionBox.classList.add("only-sections"); bsTab = bootstrap.Tab.getOrCreateInstance(document.getElementById("box-sections-tab")); } else { addSectionBox.classList.remove("only-sections"); bsTab = bootstrap.Tab.getOrCreateInstance(document.getElementById("box-components-tab")); } bsTab.show(); let pos = offset(addSectionElement); let top = ((pos.top + window.FrameWindow.pageYOffset + addSectionElement.clientTop) - self.frameHtml.scrollTop) + addSectionElement.offsetHeight; let left = ((pos.left + window.FrameWindow.pageXOffset + addSectionElement.clientLeft) - self.frameHtml.scrollLeft) + (addSectionElement.offsetWidth / 2) - (addSectionBox.offsetWidth / 2); let outerHeight = window.FrameWindow.innerHeight + self.frameHtml.scrollTop; if ((left + addSectionBox.offsetWidth) > self.frameHtml.offsetWidth) left = self.frameHtml.offsetWidth - addSectionBox.offsetWidth; if (((top + addSectionBox.offsetHeight) + self.frameHtml.scrollTop) > outerHeight) top = top - addSectionBox.offsetHeight; //check if box is out of viewport and move inside if (left < 0) left = 0; if (top < 0) top = 0; addSectionBox.style.top = top + "px"; addSectionBox.style.left = left + "px"; event.preventDefault(); return false; }); document.getElementById("close-section-btn").addEventListener("click", function(event) { addSectionBox.style.display = "none"; }); function addSectionComponent(component, after = true) { let html = component.html.replace('RANDOM_ID', Math.floor(Math.random() * 1000)) let node = generateElements(html)[0]; let tagName = node.tagName.toLowerCase(); let element = addSectionElement; //if section add after current section if (["section", "footer", "header", "nav"].includes(tagName)) { if (!element) { element = Vvveb.Builder.frameBody.querySelector(':scope > ' + tagName + ':last-of-type') || Vvveb.Builder.frameBody.querySelector(':scope > section:last-of-type') || Vvveb.Builder.frameBody.querySelector(':scope > header:last-of-type') || Vvveb.Builder.frameBody.querySelector(':scope > main') || Vvveb.Builder.frameBody.querySelector(':scope > footer:last-of-type'); } else { while (element = element.parentElement) { tagName = element.tagName.toLowerCase(); if (["section", "footer", "header", "nav"].includes(tagName)) { after = true; break; } } } if (!element) { element = addSectionElement; } } if (after) { element.after(node); } else { element.append(node); } if (component.afterDrop) { node = component.afterDrop(node); } self.selectNode(node); self.loadNodeComponent(node); Vvveb.TreeList.loadComponents(); Vvveb.TreeList.selectComponent(node); node.scrollIntoView({behavior: "smooth", block: "center", inline: "center"}); Vvveb.Undo.addMutation({type: 'childList', target: node.parentNode, addedNodes: [node], nextSibling: node.nextSibling}); } addSectionBox.addEventListener("click", function(event) { let element = event.target.closest("ol li"); if (element) { let itemType = element.dataset.dragType; let html; switch (itemType) { case 'component': html = Vvveb.Components.get(element.dataset.type); break; case 'block': html = Vvveb.Blocks.get(element.dataset.type); break; case 'section': html = Vvveb.Sections.get(element.dataset.type); break; } addSectionComponent(html, (document.querySelector("[name='add-section-insert-mode']:checked").value == "after")); addSectionBox.style.display = "none"; } }); }, /* drag and drop */ _initDragdrop : function() { let self = this; self.isDragging = false; document.addEventListener("mousedown", function(event) { let element = event.target.closest(".drag-elements-sidepane ul > li > ol > li[data-drag-type]"); let html; if (element && event.which == 1) {//left click document.getElementById("component-clone")?.remove(); document.querySelectorAll("#section-actions, #highlight-name, #select-box").forEach(e => e.style.display = "none"); self.dragType = element.dataset.dragType; if (self.dragType == "component") { self.component = Vvveb.Components.get(element.dataset.type); } else if (self.dragType == "section") { self.component = Vvveb.Sections.get(element.dataset.type); } else if (self.dragType == "block") { self.component = Vvveb.Blocks.get(element.dataset.type); } if (self.component.dragHtml) { html = self.component.dragHtml; } else if (Vvveb.dragHtml) { html = Vvveb.dragHtml; } else { html = self.component.html.replace('RANDOM_ID', Math.floor(Math.random() * 1000)); } self.dragElement = generateElements(html)[0]; //self.dragElement.css("border", "1px dashed #4285f4"); if (self.component.dragStart) self.dragElement = self.component.dragStart(self.dragElement); self.isDragging = true; if (Vvveb.dragIcon == 'html') { self.iconDrag = generateElements(html)[0]; self.iconDrag.setAttribute("id", "dragElement-clone"); self.iconDrag.style.position = "absolute"; } else if (self.designerMode == false) { self.iconDrag = document.createElement("img"); self.iconDrag.setAttribute("id", "dragElement-clone"); self.iconDrag.setAttribute("src", element.style.backgroundImage.replace(/^url\(['"](.+)['"]\)/, '$1')); self.iconDrag.style.zIndex = "100"; self.iconDrag.style.position = "absolute"; self.iconDrag.style.width = "64px"; self.iconDrag.style.height = "64px"; self.iconDrag.style.top = event.y + "px"; self.iconDrag.style.left = event.x + "px"; } document.body.append(self.iconDrag); event.preventDefault(); return false; } }); document.addEventListener('mouseup', function(event) { if (self.iconDrag && self.isDragging == true) { self.isDragging = false; document.getElementById("component-clone")?.remove(); document.querySelectorAll("#section-actions, #highlight-name, #select-box").forEach(el => el.style.display = ""); self.iconDrag.remove(); if(self.dragElement){ self.dragElement.remove(); } } }); document.addEventListener('mousemove', function(event) { if (self.iconDrag && self.isDragging == true) { let x = (event.clientX || event.clientX); let y = (event.clientY || event.clientY); self.iconDrag.style.left = (x - 60) + "px"; self.iconDrag.style.top = (y - 30) + "px"; const elementMouseIsOver = document.elementFromPoint(x - 60, y - 40); //if drag elements hovers over iframe switch to iframe mouseover handler return; if (elementMouseIsOver && elementMouseIsOver.tagName == 'IFRAME') { self.frameBody.dispatchEvent(new MouseEvent("mousemove", { bubbles: true, cancelable: true, })); //self.frameBody.trigger("mousemove", event); event.stopPropagation(); self.selectNode(false); } } }); document.addEventListener("mouseup", function(event) { let element = event.target.closest(".drag-elements-sidepane ul > ol > li > li"); if (element) { self.isDragging = false; document.getElementById("component-clone")?.remove() document.querySelectorAll("#section-actions, #highlight-name, #select-box").forEach(el => el.style.display = ""); } }); }, removeHelpers: function (html, keepHelperAttributes = false) { //tags like stylesheets or scripts html = html.replace(/<[^>]+?data-vvveb-helpers.+?>/gi, ""); html = html.replace(/<[^>]+?vvvebjs-new-section.+?>.+?<\/newsection>/gims, ""); //attributes if (!keepHelperAttributes) { html = html.replace(/\s*data-vvveb-\w+(=["'].*?["'])?\s*/gi, ""); } html = html.replaceAll("vvveb-hidden", "").replaceAll("data-vvvebjs-editor", ""); return html; }, getHtml: function(keepHelperAttributes = true, filter = true) { let doc = window.FrameDocument; let hasDoctpe = (doc.doctype !== null); let html = ""; doc.querySelectorAll("[contenteditable]").forEach(e => e.removeAttribute("contenteditable")); doc.querySelectorAll("[spellcheckker]").forEach(e => e.removeAttribute("spellcheckker")); doc.querySelectorAll('script[src^="chrome-extension://"]').forEach(e => e.remove()); doc.querySelectorAll('script[src^="moz-extension://"]').forEach(e => e.remove()); // scroll page to top to avoid saving the page in a different state // like saving with sticky classes set for navbar etc // this.iframe.contentWindow.scrollTo(0,0); if (filter) { window.dispatchEvent(new CustomEvent("vvveb.getHtml.before", {detail: doc})); } if (hasDoctpe) html = "\n"; Vvveb.FontsManager.cleanUnusedFonts(); html += doc.documentElement.outerHTML; html = this.removeHelpers(html, keepHelperAttributes); if (filter) { window.dispatchEvent(new CustomEvent("vvveb.getHtml.after", {detail: doc})); window.dispatchEvent(new CustomEvent("vvveb.getHtml.filter", {detail: html})); } return html; }, setHtml: function(html) { //documentElement.innerHTML resets each time and the page flickers //return window.FrameDocument.documentElement.innerHTML = html; function getTag(html, tag, outerHtml = false) { const start = html.indexOf("<" + tag); const end = html.indexOf("= 0 && end >= 0) { if (outerHtml) return html.slice(start, end + 3 + tag.length); else return html.slice(html.indexOf(">", start) + 1, end); } else { return html; } } if (this.runJsOnSetHtml) { this.frameBody.innerHTML = getTag(html, "body"); } else { window.FrameDocument.body.innerHTML = getTag(html, "body"); } //use outerHTML if you want to set body tag attributes //window.FrameDocument.body.outerHTML = getTag(html, "body", true); //set head html only if changed to avoid page flicker let headHtml = getTag(html, "head"); if (window.FrameDocument.head.innerHTML != headHtml) { window.FrameDocument.head.innerHTML = headHtml; } }, saveElement: function(element, type, name, callback) { if (type == 'section') { Vvveb.Sections.add('reusable/'+ name, { name, image: "img/logo-small.png", html: element.outerHTML}); if (Vvveb.SectionsGroup["Reusable"] === undefined) { Vvveb.SectionsGroup["Reusable"] = []; } Vvveb.SectionsGroup["Reusable"].push('reusable/'+ name); Vvveb.Builder.loadSectionGroups(); } else { Vvveb.Blocks.add('reusable/'+ name, { name, image: "img/logo-small.png", html: element.outerHTML}); if (Vvveb.BlocksGroup["Reusable"] === undefined) { Vvveb.BlocksGroup["Reusable"] = []; } Vvveb.BlocksGroup["Reusable"].push('reusable/'+ name); Vvveb.Builder.loadBlockGroups(); } let data = {type, name, html:element.outerHTML}; fetch(saveReusableUrl, {method: "POST", body: new URLSearchParams(data)}) .then((response) => { if (!response.ok) { throw new Error(response) } return response.text() }) .then((data) => { if (callback) callback(data); let bg = "success"; if (true || data.success || text == "success") { } else { bg = "danger"; } displayToast(bg, "Save", data.message ?? data); }) .catch(error => { console.log(error.statusText); displayToast("danger", "Error", "Error saving!"); }); /* return $.ajax({ type: "POST", url: saveReusableUrl,//set your server side save script url data: data, cache: false, }).done(function (data, text) { if (callback) callback(data); let bg = "success"; if (data.success || text == "success") { } else { bg = "danger"; } displayToast(bg, "Save", data.message ?? data); }).fail(function (data) { displayToast("danger", "Error", "Error saving!"); alert(data.responseText); }); */ }, saveAjax: function(data, saveUrl, callback, error ) { if (!data["file"]) { data["file"] = Vvveb.FileManager.getCurrentFileName(); } if (!data["startTemplateUrl"]) { data["html"] = this.getHtml(); } //data['elements'] = new URLSearchParams(data['elements']); return fetch(saveUrl, { method: "POST", headers: {'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'}, body: nestedFormData(data) }) .then((response) => { if (!response.ok) { return Promise.resolve(response.text()).then((responseInText) => { return Promise.reject([response, responseInText]); }); } return response.text(); }) .then((data) => { if (callback) callback(data); Vvveb.Undo.reset(); document.querySelectorAll("#top-panel .save-btn").forEach(e => e.setAttribute("disabled", "true")); window.dispatchEvent(new CustomEvent("vvveb.Builder.saveAjax", { detail: data, })); }) .catch((err) => { if (error) error(err); let message = err; try { let [response, responseInText] = err; let message = responseInText ?? response.statusText ?? "Error uploading!"; } catch (e) { } if (err.hasOwnProperty('text')) err.text().then( errorMessage => { let message = errorMessage.substr(0, 200); displayToast("danger", "Error", message); }); displayToast("danger", "Error", message.substr(0, 200)); }); }, setDesignerMode: function(designerMode = false) { this.designerMode = designerMode; } }; Vvveb.ModalCodeEditor = { modal: false, editor: false, init: function(modal = false, editor = false) { if (modal) { this.modal = modal; } else { this.modal = document.getElementById('codeEditorModal'); } if (editor) { this.editor = editor; } else { this.editor = this.modal.querySelector('textarea'); } let self = this; this.modal.querySelector('.save-btn').addEventListener("click", function(event) { window.dispatchEvent(new CustomEvent("vvveb.ModalCodeEditor.save", {detail: self.getValue()})); self.hide(); return false; }); }, show: function(value) { if (!this.modal) { this.init(); } const bsModal = bootstrap.Modal.getOrCreateInstance(this.modal); return bsModal.show(); }, hide: function(value) { const bsModal = bootstrap.Modal.getOrCreateInstance(this.modal); return bsModal.hide(); }, getValue: function() { return this.editor.value;; }, setValue: function(value) { if (!this.modal) { this.init(); } //enable save button document.querySelectorAll("#top-panel .save-btn").forEach(e => e.removeAttribute("disabled")); this.editor.value = value; }, } Vvveb.CodeEditor = { isActive: false, oldValue: '', doc:false, textarea:false, init: function(doc) { this.textarea = document.querySelector("#vvveb-code-editor textarea"); this.textarea.value = Vvveb.Builder.getHtml(); this.textarea.addEventListener("keyup", e => { delay(() => Vvveb.Builder.setHtml(this.value), 1000); }); //load code on document changes Vvveb.Builder.frameBody.addEventListener("vvveb.undo.add", () => Vvveb.CodeEditor.setValue()); Vvveb.Builder.frameBody.addEventListener("vvveb.undo.restore", () => Vvveb.CodeEditor.setValue()); //load code when a new url is loaded Vvveb.Builder.documentFrame.addEventListener("load", () => Vvveb.CodeEditor.setValue()); this.isActive = true; }, setValue: function(value) { if (this.isActive) { this.textarea.value = Vvveb.Builder.getHtml(); } }, destroy: function(element) { //this.isActive = false; }, toggle: function() { if (this.isActive != true) { this.isActive = true; return this.init(); } this.isActive = false; this.destroy(); } } Vvveb.CssEditor = { isActive: false, oldValue: '', doc:false, textarea:false, init: function(doc) { this.textarea = document.getElementById("css-editor") this.textarea.value = Vvveb.StyleManager.getCss(); let self = this; /* document.querySelectorAll('[href="#csscode-tab"]').forEach( t => t.addEventListener("click", e => { self.textarea.value = Vvveb.StyleManager.getCss(); })); */ this.textarea.addEventListener("keyup", e => { delay(() => Vvveb.StyleManager.setCss(self.textarea.value), 1000); }); }, getValue: function() { return this.textarea.value; }, setValue: function(value, updateStyles = true) { this.textarea.value = value; if (updateStyles) { Vvveb.StyleManager.setCss(value); } }, destroy: function() { }, toggle: function() { if (this.isActive != true) { this.isActive = true; return this.init(); } this.isActive = false; this.destroy(); } } function displayToast(type, title, message, position = 'bottom', id = null) { if (!id) { id = position + "-toast"; } let toast = document.getElementById(id); let header = toast.querySelector(".toast-header"); toast.classList.remove("bottom-0", "top-0"); toast.classList.add(position + "-0"); toast.querySelector(".toast-body .message").innerHTML = message; header.classList.remove("danger", "success"); header.classList.add("bg-" + type); header.querySelector("strong").textContent = title; let toastDisplay = toast.cloneNode(true); toast.parentNode.appendChild(toastDisplay); let delay = 3000; if (type == "danger") { delay = 10000; } let bsToast = new bootstrap.Toast(toastDisplay, {animation:true, delay}); toastDisplay.addEventListener('hidden.bs.toast', () => { toastDisplay.remove(); }); bsToast.show(); } Vvveb.Gui = { init: function() { document.querySelectorAll("[data-vvveb-action]").forEach(function (el,i) { const on = el.dataset.vvvebOn ?? "click"; el.addEventListener(on, Vvveb.Gui[el.dataset.vvvebAction]); }); this.shortcuts(); }, shortcuts: function() { let self = this; handleShortcuts = function(e) { if (e.ctrlKey) { switch (e.key) { case 's': e.preventDefault(); let btn = document.querySelector('.save-btn'); let url = btn.dataset.vvvebUrl; self.saveAjax(null, url, document.querySelector('.save-btn')); return; case 'z': e.preventDefault(); self.undo(); return; case 'Z': case 'y': e.preventDefault(); self.redo(); return; case 'L': e.preventDefault(); self.toggleTreeList(); return; case 'e': e.preventDefault(); self.toggleEditor(); return; case 'P': e.preventDefault(); self.newPage(); return; case 'S': e.preventDefault(); self.newSection(); return; } } } //handle shortcuts from main window and iframe also document.addEventListener('keydown', handleShortcuts); window.addEventListener('vvveb.iframe.loaded', () => { Vvveb.Builder.frameBody.addEventListener('keydown', handleShortcuts); }); }, undo : function () { if (Vvveb.WysiwygEditor.isActive) { Vvveb.WysiwygEditor.undo(); } else { Vvveb.Undo.undo(); } Vvveb.Builder.selectNode(); }, redo : function () { if (Vvveb.WysiwygEditor.isActive) { Vvveb.WysiwygEditor.redo(); } else { Vvveb.Undo.redo(); } Vvveb.Builder.selectNode(); }, //show modal with html content save : function () { document.getElementById('textarea-modal textarea').value = Vvveb.Builder.getHtml(); document.getElementById('textarea-modal').modal(); }, //post html content through ajax to save to filesystem/db saveAjax : function (event, saveUrl = null, saveBtn = null) { let btn = saveBtn ?? this; saveUrl = saveUrl ?? this.dataset.vvvebUrl; let file = Vvveb.FileManager.getPageData('file'); //if offcanvas check if user provided new template name if (btn.classList.contains("save-offcanvas")) { if (document.querySelector("#save-offcanvas [name=template]:checked").value == "new") { file = document.querySelector("#save-offcanvas [name=folder]").value + "/" + document.querySelector("#save-offcanvas [name=file]").value; } } btn.querySelector(".loading").classList.remove("d-none"); btn.querySelector(".button-text").classList.add("d-none"); return Vvveb.Builder.saveAjax({file}, saveUrl, (data) => { //use toast to show save status let bg = "success"; if (true || data.success || data == "success") { document.querySelectorAll("#top-panel .save-btn").forEach(e => e.setAttribute("disabled", "true")); } else { bg = "danger"; } displayToast(bg, "Save", data.message ?? data); const offcanvas = document.getElementById('save-offcanvas'); if (offcanvas) { let instance = bootstrap.Offcanvas.getInstance(offcanvas); if (instance) instance.hide(); } btn.querySelector(".loading").classList.add("d-none"); btn.querySelector(".button-text").classList.remove("d-none"); }, (error) => { btn.querySelector(".loading").classList.add("d-none"); btn.querySelector(".button-text").classList.remove("d-none"); let message = error?.statusText ?? "Error saving!"; displayToast("danger", "Error", message); }); }, download : function () { const filename = /[^\/]+$/.exec(Vvveb.Builder.iframe.src)[0]; const uriContent = "data:application/octet-stream," + encodeURIComponent(Vvveb.Builder.getHtml()); let link = document.createElement('a'); if ('download' in link) { link.dataset.download = filename; link.href = uriContent; link.target = "_blank"; document.body.appendChild(link); const result = link.click(); document.body.removeChild(link); link.remove(); } else { location.href = uriContent; } }, viewport : function () { let breakpoint = this.dataset.view; document.getElementById("canvas").classList.remove("responsive"); document.getElementById("iframe-wrapper").removeAttribute("style"); document.querySelectorAll(".responsive-btns .active").forEach(e => e.classList.remove("active")); if (Vvveb.StyleManager.currentBreakpoint == breakpoint) { Vvveb.StyleManager.currentBreakpoint = "none"; breakpoint = null; } if (breakpoint) { document.getElementById("canvas").classList.add("responsive"); document.getElementById("iframe-wrapper").style.width = Vvveb.StyleManager.breakpoints[breakpoint].replace(".98", ""); Vvveb.StyleManager.currentBreakpoint = breakpoint; this.classList.add("active"); } }, toggleEditor : function () { document.getElementById("vvveb-builder").classList.toggle("bottom-panel-expand"); document.getElementById("toggleEditorJsExecute").classList.toggle("d-none"); //hide breadcrumb when showing the editor document.querySelector(".breadcrumb-navigator .breadcrumb").classList.toggle("d-none"); document.querySelector(".breadcrumb-navigator .nav-tabs").classList.toggle("d-none"); Vvveb.CodeEditor.toggle(); Vvveb.CssEditor.toggle(); }, toggleHidden : function () { Vvveb.Builder.frameBody.classList.toggle("vvveb-hidden"); }, toggleEditorJsExecute : function () { Vvveb.Builder.runJsOnSetHtml = this.checked; }, preview : function () { (Vvveb.Builder.isPreview == true)?Vvveb.Builder.isPreview = false:Vvveb.Builder.isPreview = true; document.getElementById("iframe-layer").classList.toggle("d-none"); document.getElementById("vvveb-builder").classList.toggle("preview"); }, fullscreen : function () { launchFullScreen(document); // the whole page }, search : function () { let searchText = this.value; let panel = this.parentNode.parentNode.querySelector("div > ul"); panel.querySelectorAll("li ol li").forEach(function (el, i) { el.style.display = "none"; if (el.dataset.search.indexOf(searchText) > -1) el.style.display = ""; }); }, clearSearch : function (e) { let input = this.parentNode.querySelector("input"); input.value = ""; input.dispatchEvent(new KeyboardEvent("keyup", { bubbles: true, cancelable: true, })); }, toggleSections : function (e) { let icon = this.querySelector("i"); let closed = (this.dataset.closed == "true"); if (closed) { icon.setAttribute("class", "la la-minus"); this.dataset.closed = "false"; } else { icon.setAttribute("class", "la la-plus"); this.dataset.closed = "true"; } this.parentNode.parentNode.parentNode.querySelectorAll('input.header_check[type="checkbox"]').forEach(e => e.checked = closed); }, //Pages, file/components tree newPage : function () { let newPageModal = document.getElementById('new-page-modal'); let form = newPageModal.querySelector("form"); const bsModal = bootstrap.Modal.getOrCreateInstance(newPageModal); bsModal.show(); let submitForm = function(e) { let data = {}; this.querySelectorAll("input[type=text],input[type=checkbox]:checked,input[type=radio]:checked,input[name=image], select:not(:disabled)").forEach( (el, i) => { if (el.offsetParent || el.name == 'image') data[el.name] = el.value; }); if (data['file']) { data['title'] = data['file'].replace('/', '').replace('.html', ''); //let name = data['name'] = data['folder'].replace('/', '_') + "-" + data['title']; if (!data['name']) { data['name'] = data['title']; } data['url'] = data['file'] = data['folder'] + "/" + data['file']; //data['url'] = Vvveb.themeBaseUrl + data['url']; } e.preventDefault(); return Vvveb.Builder.saveAjax(data, this.action, function (savedData) { data.title = data.name; if (typeof savedData === 'object' && savedData !== null) { data.name = savedData.name ?? data.name; data.url = savedData.url ?? data.url; data.file = savedData.file ?? data.file; data.title = savedData.title ?? data.title; } let page = Vvveb.FileManager.addPage(data.name, data); Vvveb.FileManager.loadPage(data.name); Vvveb.FileManager.scrollToPage(page); bsModal.hide(); }); }; if (!form.dataset.init) { form.addEventListener("submit", submitForm); form.dataset.init = true; } }, newSection : function (e, onlyBase = false) { let addSectionBox = document.getElementById("add-section-box"); let addSectionElement = {}; //addSectionElement = self.highlightEl; addSectionBox.style.display = "block"; let bsTab; addSectionBox.classList.add("only-sections"); if (onlyBase) { addSectionBox.classList.add("only-base"); } else { addSectionBox.classList.remove("only-base"); } bsTab = bootstrap.Tab.getOrCreateInstance(document.getElementById("box-sections-tab")); bsTab.show(); event.preventDefault(); return false; }, setDesignerMode : function () { //aria-pressed attribute is updated after action is called and we check for false instead of true let designerMode = this.attributes["aria-pressed"].value == "true"; Vvveb.Builder.setDesignerMode(designerMode); }, //layout togglePanel: function (panel, cssVar) { panel = document.querySelector(panel); let body = document.querySelector("body"); let prevValue = getComputedStyle(body).getPropertyValue(cssVar); let visible = false; if (prevValue !== "0px") { panel.dataset.layoutToggle = prevValue; body.style.setProperty(cssVar, "0px"); panel.style.display = "none"; visible = false; } else { prevValue= panel.dataset.layoutToggle; body.style.setProperty(cssVar, ""); panel.style.display = ""; visible = true; } return visible; }, toggleFileManager: function () { Vvveb.Gui.togglePanel("#filemanager", "--builder-filemanager-height"); }, toggleLeftColumn: function () { Vvveb.Gui.togglePanel("#left-panel", "--builder-left-panel-width"); }, toggleRightColumn: function (rightColumnEnabled = null) { rightColumnEnabled = Vvveb.Gui.togglePanel("#right-panel", "--builder-right-panel-width"); document.getElementById("vvveb-builder").classList.toggle("no-right-panel"); document.querySelector(".component-properties-tab").classList.toggle("d-none"); Vvveb.Components.componentPropertiesElement = (rightColumnEnabled ? "#right-panel" :"#left-panel #properties") + " .component-properties"; let componentTab = document.querySelector("#components-tab"); if (document.getElementById("properties").offsetParent) { const bsTab = bootstrap.Tab.getOrCreateInstance(componentTab); componentTab.style.display = ""; bsTab.show(); } }, toggleTreeList: function () { let treeList = document.getElementById("tree-list"); treeList.classList.toggle("d-none"); if (!treeList.offsetParent) { document.getElementById("toggle-tree-list").classList.remove("active"); } }, treeListRight: function () { let treeList = document.getElementById("tree-list"); let btnIcon = document.querySelector("[data-vvveb-action='treeListRight'] i"); if (treeList.style.height) { treeList.style.height = ""; treeList.style.right = ""; treeList.style.top = ""; treeList.style.left = ""; treeList.style.width = ""; btnIcon.className = "icon-stop-outline"; } else { treeList.style.height = "100vh"; treeList.style.height = "calc(100vh - 35px)"; treeList.style.right = "0"; treeList.style.top = "35px"; treeList.style.left = "auto"; treeList.style.width = "300px"; btnIcon.className = "icon-remove-outline"; } }, darkMode: function () { let theme = document.documentElement.getAttribute("data-bs-theme"); let icon = document.querySelector(".btn-dark-mode i"); if (theme == "dark") { theme = "light"; icon.classList.remove("la-moon") icon.classList.add("la-sun"); } else if (theme == "light" || theme == "auto") { theme = "dark"; icon.classList.remove("la-sun") icon.classList.add("la-moon"); } else { theme = "auto"; } document.documentElement.setAttribute("data-bs-theme", theme); localStorage.setItem('theme', theme); //document.cookie = 'theme=' + theme; }, zoomChange: function () { let wrapper = document.getElementById("iframe-wrapper"); let scale = ""; let height = ""; if (this.value != "100") { scale = "scale(" + this.value + "%)"; height = ((100 / this.value) * 100) + "%"; } wrapper.style.transform = scale; wrapper.style.height = height; }, setState: function () { Vvveb.StyleManager.setState(this.value); Vvveb.Builder.reloadComponent(); } } Vvveb.StyleManager = { styles:{}, cssContainer:false, breakpoints: { 'sm':'575.98px', 'md':'767.98px', 'lg':'991.98px', 'xl':'1199.98px', 'xxl':'1399.98px' }, doc:false, inlineCSS:false, currentElement:null, currentSelector:null, currentBreakpoint:"none", state:"",//hover, active etc init: function(doc) { if (doc) { this.doc = doc; let style = false; let _style = false; //check if editor style is present for (let i = 0; i < doc.styleSheets.length; i++) { _style = doc.styleSheets[i]; if (_style.ownerNode.id && _style.ownerNode.id == "vvvebjs-styles") { style = _style.ownerNode; break; } } //if style element does not exist create it if (!style) { style = generateElements('')[0]; doc.head.append(style); return this.cssContainer = style; } //if it exists this.cssContainer = style; this.loadCss(); return this.cssContainer; } }, loadCss: function() { let style = this.cssContainer.sheet; //if style exist then load all css styles for editor for (let j = 0; j < style.cssRules.length; j++) { let media = "none"; if (style.cssRules[j].media) { for (breakpoint in this.breakpoints) { if (style.cssRules[j].media[0] === "@media (max-width: " + this.breakpoints[breakpoint] + ")") { media = breakpoint; break; } } } style.cssRules[j].media const selector = (media === "none") ? style.cssRules[j].selectorText : style.cssRules[j].cssRules[0].selectorText; const styles = (media === "none") ? style.cssRules[j].style : style.cssRules[j].cssRules[0].style; if (media) { this.styles[media] = this.styles[media] ?? {}; if (selector) { this.styles[media][selector] = {}; for (let k = 0; k < styles.length; k++) { const property = styles[k]; const value = styles[property]; this.styles[media][selector][property] = value; } } } } }, getSelectorForElement: function(element) { if (!element) return ''; let currentElement = element; let selector = []; while (currentElement.parentElement) { let elementSelector = ""; let classSelector = Array.from(currentElement.classList).map(function (className) { if (Vvveb.Builder.ignoreClasses.indexOf(className) == -1) { return "." + className; } }).join(""); //element (tag) selector let tag = currentElement.tagName.toLowerCase(); //exclude top most element body unless the parent element is body if (tag == "body" && selector.length > 1) { break; } //stop at a unique element (with id) if (currentElement.id) { elementSelector = "#" + currentElement.id; selector.push(elementSelector); break; } else if (classSelector) { //class selector elementSelector = classSelector; } else { //element selector elementSelector = tag } if (elementSelector) { selector.push(elementSelector); } currentElement = currentElement.parentElement; } return selector.reverse().join(" > "); }, setState: function(state) { this.state = state; }, addSelectorState: function(selector) { return selector + (this.state ? ":" + this.state : ""); }, setStyle: function(element, styleProp, value) { let selector; if (typeof(element) == "string") { selector = element; } else { let node = element; //if propert is set with inline style attribute then override it and don't save to css //inline text editor sets properties like font-size inline that can't be later overriten from css if (node.style && node.style[styleProp]) { node.style[styleProp] = value; return element; } selector = this.getSelectorForElement(node); } if (this.inlineCSS) { element.style[styleProp] = value; return element; } selector = this.addSelectorState(selector); const media = this.currentBreakpoint; //styles[media][selector][styleProp] = value if (!this.styles[media]) { this.styles[media] = {}; } if (!this.styles[media][selector]) { this.styles[media][selector] = {}; } if (!this.styles[media][selector][styleProp]) { this.styles[media][selector][styleProp] = {}; } this.styles[media][selector][styleProp] = value; this.generateCss(media); window.dispatchEvent(new CustomEvent("vvveb.StyleManager.setStyle", {detail: {element, styleProp, value}})); return element; //uncomment bellow code to set css in element's style attribute //return element.css(styleProp, value); }, setCss: function (css) { this.cssContainer.innerHTML = css; this.loadCss(); }, getCss: function (css) { return this.cssContainer.innerHTML; }, generateCss: function (media) { //let css = ""; //for (selector in this.styles[media]) { // css += `${selector} {`; // for (property in this.styles[media][selector]) { // value = this.styles[media][selector][property]; // css += `${property}: ${value};`; // } // css += '}'; //} //this.cssContainer.innerHTML = css; //return element; //refresh container element to avoid issues with changes from code editor this.cssContainer = this.doc.getElementById("vvvebjs-styles"); let css = ""; for (media in this.styles) { if (media !== "none") { let width = this.breakpoints[media]; css += `@media (max-width: ${width}){\n\n` } for (selector in this.styles[media]) { css += `${selector} {\n`; for (property in this.styles[media][selector]) { const value = this.styles[media][selector][property]; css += `\t${property}: ${value};\n`; } css += '}\n\n'; } if (media !== "none") { css += `}\n\n` } } return this.cssContainer.innerHTML = css; }, _getCssStyle: function(element, styleProp){ let value = "", el, selector, media; el = element; if (el != this.currentElement) { selector = this.getSelectorForElement(el); this.currentElement = el; this.currentSelector = selector } else { selector = this.currentSelector; } selector = this.addSelectorState(selector); media = this.currentBreakpoint; if (el.style && el.style.length > 0 && el.style[styleProp]) {//check inline value = el.style[styleProp]; } else if (this.styles[media] !== undefined && this.styles[media][selector] !== undefined && this.styles[media][selector][styleProp] !== undefined) {//check defined css value = this.styles[media][selector][styleProp]; if (styleProp == 'font-family') { } } else if (window.getComputedStyle) { value = document.defaultView.getDefaultComputedStyle ? document.defaultView.getDefaultComputedStyle(el,null).getPropertyValue(styleProp) : window.getComputedStyle(el,null).getPropertyValue(styleProp); } return value; }, getStyle: function(element,styleProp){ return this._getCssStyle(element, styleProp); } } Vvveb.ContentManager = { getAttr: function(element, attrName) { return element.getAttribute(attrName); }, setAttr: function(element, attrName, value) { return element.setAttribute(attrName, value); }, setHtml: function(element, html, outer = false) { if (outer) { const template = document.createElement('template'); template.innerHTML = html.trim(); let newelement = template.content.children[0]; element.parentNode.replaceChild(newelement,element); element.remove(); return newelement; } else { element.innerHTML = html; } return element; }, getHtml: function(element, outer = false) { if (outer) { return element.outerHTML; } else { return element.innerHTML; } }, setText: function(element, text) { return element.textContent = text; }, getText: function(element) { return element.textContent; }, }; function getNodeTree (node, parent, allowedComponents, idToNode = {}) { function getNodeTreeTraverse (node, parent, id = '') { if (node.hasChildNodes()) { for (let j = 0; j < node.childNodes.length; j++) { const child = node.childNodes[j]; //skip text and comments nodes if (child.nodeType == 3 || child.nodeType == 8) { continue; } let element; if (child && child["attributes"] != undefined && (matchChild = Vvveb.Components.matchNode(child))) { if (Array.isArray(allowedComponents) && allowedComponents.indexOf(matchChild.type) == -1) { element = getNodeTreeTraverse(child, parent); continue; } let title; for (const attr of ["id", "title", "aria-label"]) { title = child.getAttribute(attr); if (title) { break; } } element = { name: matchChild.name, image: matchChild.image, type: matchChild.type, title, node: child, id: id + '-' + j, children: [] }; element.children = []; parent.push(element); idToNode[id + '-' + j] = child; element = getNodeTreeTraverse(child, element.children, id + '-' + j); } else { element = getNodeTreeTraverse(child, parent, id + '-' + j); } } } return false; } getNodeTreeTraverse(node, parent, '1'); } function drawComponentsTree(tree) { let j = 1; let prefix = Math.floor(Math.random() * 100); function drawComponentsTreeTraverse(tree) { let list = document.createElement("ol"); j++; for (i in tree) { let node = tree[i]; let id = node.id; let li; if (!id) { id = prefix + '-' + j + '-' + i; } let title = (node.title ? friendlyName(node.title.substr(0, 21)) : ""); if (title) { title = ` - ${title}`; } if (tree[i].children.length > 0) { li = generateElements('
            4. \ \ \
            5. ')[0]; li.append(drawComponentsTreeTraverse(node.children)); } else { li = generateElements('
            6. \ \ \
            7. ')[0]; } li._treeNode = node.node; list.append(li); } return list; } return drawComponentsTreeTraverse(tree); } let selected = null; let dragover = null; Vvveb.SectionList = { selector: '.sections-container', allowedComponents: {}, init: function(allowedComponents = {}) { this.allowedComponents = allowedComponents; document.querySelector(this.selector).addEventListener("click", function (e) { let element = e.target.closest(":scope > div .controls"); if (element) { let node = element.parentNode._node; if (node) { node.scrollIntoView({behavior: "smooth", block: "center", inline: "center"}); //node.click(); Vvveb.Builder.selectNode(node); Vvveb.Builder.loadNodeComponent(node); } } }); document.querySelector(this.selector).addEventListener("dblclick", function (e) { let element = e.target.closest(":scope > div"); if (element) { const node = element._node; node.click(); } }); document.querySelector(this.selector).addEventListener("click", function (e) { let element = e.target.closest("li[data-component] label"); if (element) { let node = element.parentNode._node; if (node) { node.scrollIntoView({behavior: "smooth", block: "center", inline: "center"}); node.click(); } } }); document.querySelector(this.selector).addEventListener("mouseenter", function (e) { let element = e.target.closest("li[data-component] label"); if (element) { const node = document.querySelector(element.parentNode._node); node.css("outline","1px dashed blue"); } }); document.querySelector(this.selector).addEventListener("mouseleave", function (e){ let element = e.target.closest("li[data-component] label"); if (element) { const node = document.querySelector(element.parentNode._node); node.css("outline",""); if (node.getAttribute("style") == "") node.removeAttribute("style"); } }); document.querySelector(this.selector).addEventListener("dragstart", this.dragStart); document.querySelector(this.selector).addEventListener("dragover", this.dragOver); document.querySelector(this.selector).addEventListener("dragend", this.dragEnd); document.querySelector(this.selector).addEventListener("click", function (e) { let element = e.target.closest(".delete-btn"); if (element) { let section = element.closest(".section-item"); let node = section._node; node.remove(); section.remove(); Vvveb.TreeList.loadComponents(); e.stopPropagation(); e.preventDefault(); } }); let sectionIn; let img = document.querySelector(".block-preview img"); document.querySelector(".sections-list").addEventListener("mouseover", function (e) { let element = e.target.closest("li[data-type]"); if (element) { if (sectionIn != element) { let src = element.querySelector("img").getAttribute("src"); sectionIn = element; img.setAttribute("src", src); img.style.display = ""; } } else { sectionIn = element; img.setAttribute("src", ""); img.style.display = "none"; } }) /* document.querySelector(this.selector).addEventListener("click", ".up-btn", function (e) { let section = e.target.closest(".section-item"); let node = section._node; Vvveb.Builder.moveNodeUp(node); Vvveb.Builder.moveNodeUp(section); e.preventDefault(); }); document.querySelector(this.selector).addEventListener("click", ".down-btn", function (e) { let section = e.target.closest(".section-item"); let node = section._node; Vvveb.Builder.moveNodeDown(node); Vvveb.Builder.moveNodeDown(section); e.preventDefault(); }); */ let self = this; document.querySelector(".sections-list").addEventListener("click", function (e) { let element = e.target.closest(".add-section-btn"); if (element) { let item = element.closest("li"); let section = Vvveb.Sections.get(item.dataset.type); let node = generateElements(section.html)[0]; let sectionType = node.tagName.toLowerCase(); let afterSection = Vvveb.Builder.frameBody.querySelector(":scope > " + sectionType + ":last-of-type"); if (afterSection) { afterSection.after(node); } else { if (sectionType == "nav") { afterSection = Vvveb.Builder.frameBody.querySelector(":scope > nav:first,> header:last-of-type"); if (afterSection) { afterSection.before(node); } else { Vvveb.Builder.frameBody.append(node); } } else if (sectionType != "footer") { afterSection = Vvveb.Builder.frameBody.querySelector("body > footer:last-of-type"); if (afterSection) { afterSection.before(node); } else { Vvveb.Builder.frameBody.append(node); } } else { Vvveb.Builder.frameBody.append(node); } } //node.click(); Vvveb.Builder.selectNode(node); Vvveb.Builder.loadNodeComponent(node); /* Vvveb.Builder.frameHtml.animate({ scrollTop: node.offset().top }, 1000); delay(() => node.click(), 1000); */ Vvveb.Undo.addMutation({type: 'childList', target: node.parentNode, addedNodes: [node], nextSibling: node.nextSibling}); self.loadSections(); Vvveb.TreeList.loadComponents(); Vvveb.TreeList.selectComponent(node); node.scrollIntoView({behavior: "smooth", block: "center", inline: "center"}); e.preventDefault(); } }); document.querySelector(this.selector).addEventListener("click", function (e) { let element = e.target.closest(".properties-btn"); if (element) { let section = element.closest(".section-item"); let node = section._node; node.click(); e.preventDefault(); } }); }, getSections: function() { let sections = []; let sectionList = window.FrameDocument.body.querySelectorAll(':scope > section, :scope > header, :scope > footer, :scope > main, :scope > nav'); sectionList.forEach(function (node, i) { let id = node.id ? node.id : (node.title ? node.title : node.ariaLabel ?? node.className); if (!id) { id = 'section-' + Math.floor(Math.random() * 10000); } let section = { name: id.replace(/[^\w+]+/g,' '), id: node.id, type: node.tagName.toLowerCase(), node: node }; sections.push(section); }); return sections; }, loadComponents: function(sectionListItem, section, allowedComponents = {}) { let tree = []; getNodeTree(section, tree, allowedComponents); let html = drawComponentsTree(tree); document.querySelector("ol", sectionListItem).replaceWith(html); }, addSection: function(data) { let section = generateElements(tmpl("vvveb-section", data))[0]; section._node = data.node; document.querySelector(this.selector).append(section); //this.loadComponents(section, data.node, this.allowedComponents); }, loadSections: function() { let sections = this.getSections(); let container = document.querySelector(this.selector); container.replaceChildren(); for (i in sections) { this.addSection(sections[i]); } }, //drag and drop dragOver: function(e) { let element = e.target.closest("div"); if (element) { if (e.target != dragover && e.target.className == "section-item") { if (dragover) { dragover.classList.remove("drag-over"); } dragover = e.target; dragover.classList.add("drag-over"); } } }, dragEnd: function (e) { let element = e.target.closest("div"); if (element) { if (dragover) { let parent = selected.parentNode; let selectedNode = selected._node; let replaceNode = dragover._node; if ((dragover.offsetTop > selected.offsetTop)) { //replace section item list parent.insertBefore(selected, dragover.nextElementSibling); //replace section replaceNode.parentNode.insertBefore(selectedNode, replaceNode.nextElementSibling); } else { //replace section item list parent.insertBefore(selected, dragover); //replace section replaceNode.parentNode.insertBefore(selectedNode, replaceNode); } dragover.classList.remove("drag-over"); let node = selectedNode; Vvveb.Undo.addMutation({type: 'move', target: node, oldParent: node.parentNode, oldNextSibling: node.nextSibling}); } selected = null; dragover = null; } }, dragStart: function (e) { let element = e.target.closest("div"); if (element) { selected = e.target } }, } Vvveb.StyleList = { selector: '.sections-container', allowedComponents: {}, applyStyle: function(style) { let doc = window.FrameDocument; let themeStyle = doc.getElementById("vvvebjs-theme-style"); if (!style) { if (themeStyle) themeStyle.remove(); return; } //if style element does not exist create it if (!themeStyle) { themeStyle = doc.createElement("link"); themeStyle.id = "vvvebjs-theme-style"; themeStyle.rel = "stylesheet"; themeStyle.media = "screen"; doc.head.append(themeStyle); } themeStyle.href = style; }, load: function(doc) { let themeStyle = window.FrameDocument.getElementById("vvvebjs-theme-style"); if (themeStyle) { let style = Vvveb.Styles.getByStyle(themeStyle.getAttribute("href")); if (style) { document.querySelector('.styles-list [data-type="' + style + '"]')?.classList.add("active"); } } }, init: function() { let self = this; document.querySelector(".styles-list").addEventListener("click", function (e) { let element = e.target.closest("li"); if (element) { let style = Vvveb.Styles.get(element.dataset.type); if (element.classList.contains("active")) { element.classList.remove("active"); style = "";//if already active remove current style } else { document.querySelector(".styles-list .active")?.classList.remove("active"); element.classList.add("active"); } self.applyStyle(style.style); console.log(style); e.preventDefault(); } }); } } Vvveb.TreeList = { selector: '#tree-list', container: null, tree: [], idToNode : {}, init: function() { // header move this.container = document.querySelector(this.selector); let header = this.container.querySelector(".header"); let isDown = false; let offset = [0,0]; let self = this; header.addEventListener('mousedown', function(e) { if (e.which == 1) {//left click isDown = true; offset = [ self.container.offsetLeft - e.clientX, self.container.offsetTop - e.clientY ]; } }, true); document.addEventListener('mouseup', function() { isDown = false; }, true); document.addEventListener('mousemove', function(event) { if (isDown) { event.preventDefault(); let left = Math.max(event.clientX + offset[0], 0); let top = Math.max(event.clientY + offset[1], 0); if (left >= 0 && (left < (window.innerWidth - self.container.clientWidth))) self.container.style.left = left + "px"; if (top >= 0 && (top < (window.innerHeight - self.container.clientHeight))) self.container.style.top = top + "px"; } }); document.querySelector(this.selector).addEventListener("click", function (e) { let element = e.target.closest("li[data-component]"); if (element) { const node = element._treeNode; node.scrollIntoView({behavior: "smooth", block: "center", inline: "center"}); //node.click(); Vvveb.Builder.selectNode(node); Vvveb.Builder.loadNodeComponent(node); document.querySelector(self.selector + " .active")?.classList.remove("active"); element.querySelector("label").classList.add("active"); } }) document.querySelector(this.selector).addEventListener("mousemove", function (e) { let element = e.target.closest("li[data-component]"); if (element) { const node = element._treeNode; node.dispatchEvent(new MouseEvent("mousemove", { bubbles: true, cancelable: true, })); } }) }, selectComponent: function(node) { let id; for (const i in this.idToNode) { if (node == this.idToNode[i]) { id = i; break; } } if (id) { let element = document.getElementById("id" + id); this.container.querySelector(".active")?.classList.remove("active"); //collapse all let checkboxes = this.container.querySelectorAll("input[type=checkbox]:checked"); for (let i = 0, len = checkboxes.length; i < len; i++) { checkboxes[i].checked = false; let label = checkboxes[i].labels[0]; if (label) { label.classList.remove("active"); } } //expand parents if (element) { let parent = element; let current = element; while (parent = current.closest("li")) { current = parent.parentNode; let input = parent.querySelector("input"); if (input && input.hasAttribute("type") && input.type == "checkbox") { input.checked = true; } } element.checked = true; element.labels[0].classList.add("active"); element.scrollIntoView({behavior: "smooth", block: "center", inline: "center"}); } } return false; }, loadComponents: function() { let list = this.container.querySelector(".tree > ol"); //if navigator not visible don't load if (list.offsetParent === null) return; this.tree = []; this.idToNode = {}; getNodeTree(window.FrameDocument.body, this.tree, {}, this.idToNode); let ol = drawComponentsTree(this.tree); list.replaceWith(ol); //list.replaceWith(html); }, } Vvveb.FileManager = { tree:false, pages:{}, currentPage: false, allowedComponents: {}, init: function(allowedComponents = {}) { this.allowedComponents = allowedComponents; this.tree = document.querySelector("#filemanager .tree > ol"); this.tree.replaceChildren(); this.tree.addEventListener("click", function (e) { let element = event.target.closest("a"); if (element) { e.stopImmediatePropagation(); if (element.classList.contains('view')) return; e.preventDefault(); return false; } }); this.tree.addEventListener("click", function (e) { let element = event.target.closest(".delete"); if (element) { Vvveb.FileManager.deletePage(element.closest("li"), e); e.stopImmediatePropagation(); e.preventDefault(); return false; } }); this.tree.addEventListener("click", function (e) { let element = event.target.closest(".rename"); if (element) { Vvveb.FileManager.renamePage(element.closest("li"), e, false); e.stopImmediatePropagation(); e.preventDefault(); return false; } }); this.tree.addEventListener("click", function (e) { let element = event.target.closest(".duplicate"); if (element) { Vvveb.FileManager.renamePage(element.closest("li"), e, true); e.stopImmediatePropagation(); e.preventDefault(); return false; } }); this.tree.addEventListener("click", function (e) { let element = event.target.closest("li[data-page] label"); if (element) { let page = element.parentNode.dataset.page; if (page) Vvveb.FileManager.loadPage(page, allowedComponents); e.preventDefault(); return false; } }); this.tree.addEventListener("click", function (e) { let element = event.target.closest("li[data-component] label"); if (element) { const node = e.currentTarget.parentNode._node; node.scrollIntoView({behavior: "smooth", block: "center", inline: "center"}); node.click(); } }); this.tree.addEventListener("mouseenter", function (e) { let element = event.target.closest("li[data-component] label"); if (element) { const node = e.currentTarget.parentNode._node; node.scrollIntoView({behavior: "smooth", block: "center", inline: "center"}); node.dispatchEvent(new MouseEvent("mousemove", { bubbles: true, cancelable: true, })); //node.trigger("mousemove"); } }); }, clear: function() { this.pages = {}; this.currentPage = false; this.tree.replaceChildren(); }, deletePage: function(element, e) { let page = element.dataset; if (confirm(`Are you sure you want to delete "${page.file}"template?`)) { let detail = {page, element}; //allow event to change page or cancel by setting page to false window.dispatchEvent(new CustomEvent("vvveb.FileManager.deletePage", { detail })); if (detail.page) { fetch(deleteUrl, {method: "POST", body: new URLSearchParams({file:page.file})}) .then((response) => { if (!response.ok) { return Promise.reject(response); } return response.text() }) .then((data) => { let bg = "success"; if (data.success) { document.querySelectorAll("#top-panel .save-btn").forEach(e => e.setAttribute("disabled", "true")); } else { bg = "danger"; } displayToast(bg, "Delete", data.message ?? data); }) .catch(error => { console.log(error); let message = error.statusText ?? "Error deleting page!"; displayToast("danger", "Error", message); error.text().then( errorMessage => { let message = errorMessage.substr(0, 200); displayToast("danger", "Error", message); }) }); element.remove(); } } }, renamePage: function(element, e, duplicate = false) { let page = element.dataset; let newfile = prompt(`Enter new file name for "${page.file}"`, page.file); let _self = this; if (newfile) { let detail = {page, newfile, element}; //allow event to change page or newfile or cancel by setting page to false window.dispatchEvent(new CustomEvent("vvveb.FileManager.renamePage", { detail })); if (detail.page) { fetch(renameUrl, {method: "POST", body: new URLSearchParams({file:page.file, newfile:newfile, duplicate})}) .then((response) => { if (!response.ok) { return Promise.reject(response); } return response.text() }) .then((data) => { let bg = "success"; if (data.success) { document.querySelectorAll("#top-panel .save-btn").forEach(e => e.setAttribute("disabled", "true")); } else { bg = "danger"; } newfile = data.newfile ?? newfile; displayToast(bg, "Rename", data.message ?? data); let baseName = newfile.replace('.html', ''); let newName = friendlyName(newfile.replace(/.*[\/\\]+/, '')).replace('.html', ''); if (duplicate) { let data = _self.pages[page.page]; data["file"] = newfile; data["title"] = newName; Vvveb.FileManager.addPage(baseName, data); } else { _self.pages[page.page]["file"] = newfile; _self.pages[page.page]["title"] = newName; page.url = data.url ?? page.url.replace(page.file, newfile); page.file = newfile; element.querySelector(":scope > label span").innerHTML = newName; element.querySelector(":scope > label a.view").setAttribute("href", page.url); _self.pages[page.page]["url"] = page.url; _self.pages[page.page]["file"] = page.file; } }) .catch(error => { console.log(error); let message = error.statusText ?? "Error renaming page!"; displayToast("danger", "Error", message); error.text().then( errorMessage => { let message = errorMessage.substr(0, 200); displayToast("danger", "Error", message); }) }); } } }, addPage: function(name, data, afterPage = false, append = true) { //allow event to change name or cancel by setting name to false window.dispatchEvent(new CustomEvent("vvveb.FileManager.addPage", { detail: [name, data], })); if (!name) { return false; } this.pages[name] = data; data['name'] = name; let folder = this.tree; if (data.folder) { if ((data.folder && data.folder != "/") && !(folder = folder.querySelector('li[data-folder="' + data.folder + '"]'))) { data.folderTitle = friendlyName(data.folder);//data.folder[0].toUpperCase() + data.folder.slice(1); folder = generateElements(tmpl("vvveb-filemanager-folder", data))[0]; this.tree.append(folder); } folder = folder.querySelector("ol"); } let page = generateElements(tmpl("vvveb-filemanager-page", data))[0]; if (afterPage && (afterPage = folder.querySelector('[data-page="' + afterPage + '"]'))) { afterPage.after(page); } else { if (append) { folder.append(page); } else { folder.prepend(page); } } return page; }, addPages: function(pages) { for (page in pages) { this.addPage(pages[page]['name'], pages[page]); } }, addComponent: function(name, url, title, page) { document.querySelector("[data-page='" + page + "'] > ol", this.tree).append( tmpl("vvveb-filemanager-component", {name:name, url:url, title:title})); }, loadComponents: function(allowedComponents = {}) { let tree = []; getNodeTree(window.FrameDocument.body, tree, allowedComponents); let html = drawComponentsTree(tree); document.querySelector("[data-page='" + this.currentPage + "'] > ol", this.tree).replaceWith(html); }, getCurrentUrl: function() { if (this.currentPage) { return this.pages[this.currentPage]['url']; } }, getCurrentPage: function() { return this.currentPage; }, getPageData: function(key) { if (this.currentPage) { if (key) { return this.pages[this.currentPage][key]; } else { return this.pages[this.currentPage]; } } }, getCurrentFileName: function() { if (this.currentPage) { let folder = this.pages[this.currentPage]['folder']; folder = folder ? folder + '/': ''; return folder + this.pages[this.currentPage]['file']; } }, reloadCurrentPage: function() { if (this.currentPage) return this.loadPage(this.currentPage); }, loadPage: function(name, allowedComponents = false, disableCache = true, loadComponents = false) { let url = this.pages[name]['url'] ?? ""; if (!url) { return; } let page = this.tree.querySelector("[data-page='" + name + "']"); //remove active from current active page this.tree.querySelector("[data-page].active")?.classList.remove("active"); //set loaded page as active page.classList.add("active"); //open parent folder if closed page.closest("[data-folder]")?.querySelector("input[type=checkbox]").setAttribute("checked", true); this.currentPage = name; document.querySelector(".btn-preview-url").setAttribute("href", url); //allow event to change page or url or cancel by setting url to false let self = this; window.dispatchEvent(new CustomEvent("vvveb.FileManager.loadPage", { detail: self.pages[name], })); if (url) { Vvveb.Builder.loadUrl(url + (disableCache ? (url.indexOf('?') > -1 ? '&r=':'?r=') + Math.random():''), function () { if (loadComponents) { Vvveb.FileManager.loadComponents(allowedComponents); } Vvveb.SectionList.loadSections(allowedComponents); Vvveb.TreeList.loadComponents(); Vvveb.StyleManager.init(); }); } }, scrollToPage: function(page) { page.scrollIntoView({behavior: "smooth", block: "center", inline: "center"}); }, } Vvveb.Breadcrumb = { tree:false, init: function() { this.tree = document.querySelector(".breadcrumb-navigator > .breadcrumb"); this.tree.replaceChildren(); this.tree.addEventListener("click", function (e) { let element = event.target.closest(".breadcrumb-item"); if (element) { let node = element._node; if (node) { //node.click(); Vvveb.Builder.selectNode(node); Vvveb.Builder.loadNodeComponent(node); node.scrollIntoView({behavior: "smooth", block: "center", inline: "center"}); } e.preventDefault(); } }); let currentHoverNode; this.tree.addEventListener("mousemove", function (e) { if (event.target == currentHoverNode) return; currentHoverNode = event.target; let element = event.target.closest(".breadcrumb-item"); if (element) { let node = element._node; node.dispatchEvent(new MouseEvent("mousemove", { bubbles: true, cancelable: true, })); } }) }, addElement: function(data, element) { let li = generateElements(tmpl("vvveb-breadcrumb-navigaton-item", data))[0]; li._node = element; this.tree.prepend(li); }, loadBreadcrumb: function(element) { this.tree.replaceChildren(); let currentElement = element; while (currentElement.parentElement) { let elementType = Vvveb.Builder._getElementType(currentElement); let el = elementType[1].toLowerCase(); this.addElement({ "name": el + " " + elementType[0], "className": "el-" + el }, currentElement); currentElement = currentElement.parentElement; } } } Vvveb.FontsManager = { activeFonts:[], providers: {},//{"google":GoogleFontsManager}; addFontList: function(provider, groupName, fontList) { let fonts = {}; let fontNames = []; let fontSelect = generateElements("")[0]; for (const font in fontList) { fontNames.push({"text":font, "value":font, "data-provider": provider}); let option = new Option(font, font); option.dataset.provider = provider; //option.style.setProperty("font-family", font);//font preview if the fonts are loaded in editor fontSelect.append(option); } document.getElementById("font-family").append(fontSelect); let list = Vvveb.Components.getProperty("_base", "font-family"); if (list) { list.onChange = function (node,value, input, component) { let option = input.options[input.selectedIndex]; Vvveb.FontsManager.addFont(option.dataset.provider, value, node); return node; }; list.data.options.push({optgroup:groupName}); list.data.options = list.data.options.concat(fontNames); Vvveb.Components.updateProperty("_base", "font-family", {data:list.data}); //update default font list fontList = list.data.options; } }, addProvider: function(provider, Obj) { this.providers[provider] = Obj; }, //add also element so we can keep track of the used fonts to remove unused ones addFont: function(provider, fontFamily, element = false) { if (!provider) return; let providerObj = this.providers[provider]; if (providerObj) { providerObj.addFont(fontFamily); this.activeFonts.push({provider, fontFamily, element}); } }, removeFont: function(provider, fontFamily) { if (!provider) return; let providerObj = this.providers[provider]; if (provider!= "default" && providerObj) { providerObj.removeFont(fontFamily); } }, //check if the added fonts are still used for the elements they were set and remove unused ones cleanUnusedFonts: function (){ for (i in this.activeFonts) { let elementFont = this.activeFonts[i]; if (elementFont.element) { if (Vvveb.StyleManager.getStyle(elementFont.element,'font-family').replaceAll('"','') != elementFont.fontFamily) { this.removeFont(elementFont.provider, elementFont.fontFamily); } } } } }; Vvveb.ColorPalette = { colors: {}, getAll: function() { return this.colors; }, add: function(name, color) { this.colors[name] = color; }, remove: function(color) { delete this.colors[name]; }, } function friendlyName(name) { name = name.replaceAll("--bs-","").replace(/[-_]/g, " ").trim(); return name = name[0].toUpperCase() + name.slice(1); } Vvveb.ColorPaletteManager = { cssVars: {"font": {}, "color" : {}, "dimensions": {}}, getType: function (type) { return this.cssVars[type]; }, getAllCSSVariableNames: function (styleSheets = document.styleSheets, selector){ for(let i = 0; i < styleSheets.length; i++){ try{ let cssRules = styleSheets[i].cssRules; for( let j = 0; j < cssRules.length; j++){ try{ let style = cssRules[j].style; if (selector && cssRules[j].selectorText && cssRules[j].selectorText != selector) continue; for(let k = 0; k < style.length; k++){ let name = style[k]; let value = style.getPropertyValue(name).trim(); let type = ""; if(name.startsWith("--")){ //ignore bootstrap rgb variables if (name.endsWith("-rgb")) continue; //ignore variables depending on other variables if (value.startsWith("var(")) continue; let friendlyName = name.replace("--bs-","").replaceAll("-", " "); if (value.startsWith("#")) { type = "color"; } else if (value.indexOf('"') >= 0 || value.indexOf("'") >= 0) { type = "font"; } else if (value.endsWith('em') > 0 || value.endsWith('px') > 0) { type = "dimensions"; } else if (!isNaN(parseFloat(value))) { type = "dimensions"; } if (type) { if (!this.cssVars[type]) this.cssVars[type] = {}; this.cssVars[type][name] = {value, type, friendlyName}; } } } } catch (error) {} } } catch (error) {} } return this.cssVars; }, getCssWithVars: function (styleSheets = document.styleSheets, vars){ let cssVars = {}; let css = ""; let cssStyles = ""; for(let i = 0; i < styleSheets.length; i++){ try{ let cssRules = styleSheets[i].cssRules; for( let j = 0; j < cssRules.length; j++){ try{ let style = cssRules[j].style; //if (selector && cssRules[j].selectorText && cssRules[j].selectorText != selector) continue; cssStyles = ""; for(let k = 0; k < style.length; k++){ let name = style[k]; let value = style.getPropertyValue(name); if(name.startsWith('--bs-btn-')) { for (v in vars) { if (value == vars[v]) { cssVars[name] = v; cssStyles += name + ":var(" + v + ");\n"; } } } } if (cssStyles) { css += cssRules[j].selectorText + "{\n" css += cssStyles; css += "}\n"; } } catch (error) {} } } catch (error) {} } return cssVars; }, init: function(document) { Vvveb.Components.render("config/bootstrap", "#configuration .component-properties"); //apply current theme color palette //let colors = Vvveb.ColorPaletteManager.getType("color"); let colors = this.cssVars.color; for (const name in colors) { let color = colors[name].value; if (color[0] == "#" && color.length == 7) {//add only valid hex color values 7 char long //add color as name to keep values unique Vvveb.ColorPalette.add(color, color); } } }, }; Vvveb.NewSection = { container:null, template:`
              `, init: function() { this.insert(); this.container.onmouseout = function(event) { Vvveb.Builder.highlightEnabled = true; }; this.container.onmouseover = function(event) { Vvveb.Builder.highlightEnabled = false; }; this.container.onclick = function(event) { if (event.target.id == 'new-blank-section') { Vvveb.Gui.newSection(null,true); } else { if (event.target.id == 'new-section') { Vvveb.Gui.newSection(null,false); } } }; }, insert: function() { let position = 'before'; let lastSection = Vvveb.Builder.frameBody.querySelector(':scope > footer:last-of-type'); if (!lastSection) { position = 'after'; lastSection = Vvveb.Builder.frameBody.querySelector(':scope > main:last-of-type') || Vvveb.Builder.frameBody.querySelector(':scope > section:last-of-type'); } this.container = generateElements(this.template)[0]; if (position == 'after') { lastSection.after(this.container); } else { lastSection.before(this.container); } return this.container; } } Vvveb.Config = { components :[], blocks :[], plugins :[], load: function(url = 'default.json') { $.getJSON( url, function( data ) { }); } } // Toggle fullscreen function launchFullScreen(document) { if(document.documentElement.requestFullScreen) { if (document.FullScreenElement) document.exitFullScreen(); else document.documentElement.requestFullScreen(); //mozilla } else if(document.documentElement.mozRequestFullScreen) { if (document.mozFullScreenElement) document.mozCancelFullScreen(); else document.documentElement.mozRequestFullScreen(); //webkit } else if(document.documentElement.webkitRequestFullScreen) { if (document.webkitFullscreenElement) document.webkitExitFullscreen(); else document.documentElement.webkitRequestFullScreen(); //ie } else if(document.documentElement.msRequestFullscreen) { if (document.msFullScreenElement) document.msExitFullscreen(); else document.documentElement.msRequestFullscreen(); } } let fontList = [{ value: "", text: "Default" }, { value: "Arial, Helvetica, sans-serif", text: "Arial" }, { value: "'Lucida Sans Unicode', 'Lucida Grande', sans-serif", text: 'Lucida Grande' }, { value: "'Palatino Linotype', 'Book Antiqua', Palatino, serif", text: 'Palatino Linotype' }, { value: "'Times New Roman', Times, serif", text: 'Times New Roman' }, { value: "Georgia, serif", text: "Georgia, serif" }, { value: "Tahoma, Geneva, sans-serif", text: "Tahoma" }, { value: "'Comic Sans MS', cursive, sans-serif", text: 'Comic Sans' }, { value: "Verdana, Geneva, sans-serif", text: 'Verdana' }, { value: "Impact, Charcoal, sans-serif", text: 'Impact' }, { value: "'Arial Black', Gadget, sans-serif", text: 'Arial Black' }, { value: "'Trebuchet MS', Helvetica, sans-serif", text: 'Trebuchet' }, { value: "'Courier New', Courier, monospace", text: 'Courier New' }, { value: "'Brush Script MT', sans-serif", text: 'Brush Script' }]; ================================================ FILE: libs/builder/components-bootstrap4.js ================================================ /* Copyright 2017 Ziadin Givan Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. https://github.com/givanz/Vvvebjs */ bgcolorClasses = ["bg-primary", "bg-secondary", "bg-success", "bg-danger", "bg-warning", "bg-info", "bg-light-subtle", "bg-dark", "bg-white"] bgcolorSelectOptions = [{ value: "Default", text: "" }, { value: "bg-primary", text: "Primary" }, { value: "bg-secondary", text: "Secondary" }, { value: "bg-success", text: "Success" }, { value: "bg-danger", text: "Danger" }, { value: "bg-warning", text: "Warning" }, { value: "bg-info", text: "Info" }, { value: "bg-light-subtle", text: "Light" }, { value: "bg-dark", text: "Dark" }, { value: "bg-white", text: "White" }]; function changeNodeName(node, newNodeName) { var newNode; newNode = document.createElement(newNodeName); attributes = node.get(0).attributes; for (i = 0, len = attributes.length; i < len; i++) { newNode.setAttribute(attributes[i].nodeName, attributes[i].nodeValue); } $(newNode).append($(node).contents()); $(node).replaceWith(newNode); return newNode; } Vvveb.ComponentsGroup['Bootstrap 4'] = ["html/container", "html/gridrow", "html/button", "html/buttongroup", "html/buttontoolbar", "html/heading", "html/image", "html/jumbotron", "html/alert", "html/card", "html/listgroup", "html/hr", "html/taglabel", "html/badge", "html/progress", "html/navbar", "html/breadcrumbs", "html/pagination", "html/form", "html/textinput", "html/textareainput", "html/selectinput", "html/fileinput", "html/checkbox", "html/radiobutton", "html/table", "html/paragraph", "html/link", "html/video", "html/button"]; var base_sort = 100;//start sorting for base component from 100 to allow extended properties to be first var style_section = 'style'; Vvveb.Components.add("_base", { name: "Element", properties: [{ key: "element_header", inputtype: SectionInput, name:false, sort:base_sort++, data: {header:"General"}, }, { name: "Id", key: "id", htmlAttr: "id", sort: base_sort++, inline:true, col:6, inputtype: TextInput }, { name: "Class", key: "class", htmlAttr: "class", sort: base_sort++, inline:true, col:6, inputtype: TextInput } ] }); //display Vvveb.Components.extend("_base", "_base", { properties: [ { key: "display_header", inputtype: SectionInput, name:false, sort: base_sort++, section: style_section, data: {header:"Display"}, }, { name: "Display", key: "display", htmlAttr: "style", sort: base_sort++, section: style_section, col:6, inline:true, inputtype: SelectInput, validValues: ["block", "inline", "inline-block", "none"], data: { options: [{ value: "block", text: "Block" }, { value: "inline", text: "Inline" }, { value: "inline-block", text: "Inline Block" }, { value: "none", text: "none" }] } }, { name: "Position", key: "position", htmlAttr: "style", sort: base_sort++, section: style_section, col:6, inline:true, inputtype: SelectInput, validValues: ["static", "fixed", "relative", "absolute"], data: { options: [{ value: "static", text: "Static" }, { value: "fixed", text: "Fixed" }, { value: "relative", text: "Relative" }, { value: "absolute", text: "Absolute" }] } }, { name: "Top", key: "top", htmlAttr: "style", sort: base_sort++, section: style_section, col:6, inline:true, parent:"", inputtype: CssUnitInput }, { name: "Left", key: "left", htmlAttr: "style", sort: base_sort++, section: style_section, col:6, inline:true, parent:"", inputtype: CssUnitInput }, { name: "Bottom", key: "bottom", htmlAttr: "style", sort: base_sort++, section: style_section, col:6, inline:true, parent:"", inputtype: CssUnitInput }, { name: "Right", key: "right", htmlAttr: "style", sort: base_sort++, section: style_section, col:6, inline:true, parent:"", inputtype: CssUnitInput },{ name: "Float", key: "float", htmlAttr: "style", sort: base_sort++, section: style_section, col:12, inline:true, inputtype: RadioButtonInput, data: { extraclass:"btn-group-sm btn-group-fullwidth", options: [{ value: "none", icon:"la la-times", //text: "None", title: "None", checked:true, }, { value: "left", //text: "Left", title: "Left", icon:"la la-align-left", checked:false, }, { value: "right", //text: "Right", title: "Right", icon:"la la-align-right", checked:false, }], } }, { name: "Opacity", key: "opacity", htmlAttr: "style", sort: base_sort++, section: style_section, col:12, inline:true, parent:"", inputtype: RangeInput, data:{ max: 1, //max zoom level min:0, step:0.1 }, }, { name: "Background Color", key: "background-color", sort: base_sort++, section: style_section, col:6, inline:true, htmlAttr: "style", inputtype: ColorInput, }, { name: "Text Color", key: "color", sort: base_sort++, section: style_section, col:6, inline:true, htmlAttr: "style", inputtype: ColorInput, }] }); //Typography Vvveb.Components.extend("_base", "_base", { properties: [ { key: "typography_header", inputtype: SectionInput, name:false, sort: base_sort++, section: style_section, data: {header:"Typography"}, }, { name: "Font size", key: "font-size", htmlAttr: "style", sort: base_sort++, section: style_section, col:6, inline:true, inputtype: CssUnitInput }, { name: "Font weight", key: "font-weight", htmlAttr: "style", sort: base_sort++, section: style_section, col:6, inline:true, inputtype: SelectInput, data: { options: [{ value: "", text: "Default" }, { value: "100", text: "Thin" }, { value: "200", text: "Extra-Light" }, { value: "300", text: "Light" }, { value: "400", text: "Normal" }, { value: "500", text: "Medium" }, { value: "600", text: "Semi-Bold" }, { value: "700", text: "Bold" }, { value: "800", text: "Extra-Bold" }, { value: "900", text: "Ultra-Bold" }], } }, { name: "Font family", key: "font-family", htmlAttr: "style", sort: base_sort++, section: style_section, col:12, inline:true, inputtype: SelectInput, data: { options: [{ value: "", text: "Default" }, { value: "Arial, Helvetica, sans-serif", text: "Arial" }, { value: '\'Lucida Sans Unicode\', \'Lucida Grande\', sans-serif', text: 'Lucida Grande' }, { value: '\'Palatino Linotype\', \'Book Antiqua\', Palatino, serif', text: 'Palatino Linotype' }, { value: '\'Times New Roman\', Times, serif', text: 'Times New Roman' }, { value: "Georgia, serif", text: "Georgia, serif" }, { value: "Tahoma, Geneva, sans-serif", text: "Tahoma" }, { value: '\'Comic Sans MS\', cursive, sans-serif', text: 'Comic Sans' }, { value: 'Verdana, Geneva, sans-serif', text: 'Verdana' }, { value: 'Impact, Charcoal, sans-serif', text: 'Impact' }, { value: '\'Arial Black\', Gadget, sans-serif', text: 'Arial Black' }, { value: '\'Trebuchet MS\', Helvetica, sans-serif', text: 'Trebuchet' }, { value: '\'Courier New\', Courier, monospace', text: 'Courier New' }, { value: '\'Brush Script MT\', sans-serif', text: 'Brush Script' }] } }, { name: "Text align", key: "text-align", htmlAttr: "style", sort: base_sort++, section: style_section, col:12, inline:true, inputtype: RadioButtonInput, data: { extraclass:"btn-group-sm btn-group-fullwidth", options: [{ value: "", icon:"la la-times", //text: "None", title: "None", checked:true, }, { value: "left", //text: "Left", title: "Left", icon:"la la-align-left", checked:false, }, { value: "center", //text: "Center", title: "Center", icon:"la la-align-center", checked:false, }, { value: "right", //text: "Right", title: "Right", icon:"la la-align-right", checked:false, }, { value: "justify", //text: "justify", title: "Justify", icon:"la la-align-justify", checked:false, }], }, }, { name: "Line height", key: "line-height", htmlAttr: "style", sort: base_sort++, section: style_section, col:6, inline:true, inputtype: CssUnitInput }, { name: "Letter spacing", key: "letter-spacing", htmlAttr: "style", sort: base_sort++, section: style_section, col:6, inline:true, inputtype: CssUnitInput }, { name: "Text decoration", key: "text-decoration-line", htmlAttr: "style", sort: base_sort++, section: style_section, col:12, inline:true, inputtype: RadioButtonInput, data: { extraclass:"btn-group-sm btn-group-fullwidth", options: [{ value: "none", icon:"la la-times", //text: "None", title: "None", checked:true, }, { value: "underline", //text: "Left", title: "underline", icon:"la la-long-arrow-alt-down", checked:false, }, { value: "overline", //text: "Right", title: "overline", icon:"la la-long-arrow-alt-up", checked:false, }, { value: "line-through", //text: "Right", title: "Line Through", icon:"la la-strikethrough", checked:false, }, { value: "underline overline", //text: "justify", title: "Underline Overline", icon:"la la-arrows-alt-v", checked:false, }], }, }, { name: "Decoration Color", key: "text-decoration-color", sort: base_sort++, section: style_section, col:6, inline:true, htmlAttr: "style", inputtype: ColorInput, }, { name: "Decoration style", key: "text-decoration-style", htmlAttr: "style", sort: base_sort++, section: style_section, col:6, inline:true, inputtype: SelectInput, data: { options: [{ value: "", text: "Default" }, { value: "solid", text: "Solid" }, { value: "wavy", text: "Wavy" }, { value: "dotted", text: "Dotted" }, { value: "dashed", text: "Dashed" }, { value: "double", text: "Double" }], } }] }) //Size Vvveb.Components.extend("_base", "_base", { properties: [{ key: "size_header", inputtype: SectionInput, name:false, sort: base_sort++, section: style_section, data: {header:"Size", expanded:false}, }, { name: "Width", key: "width", htmlAttr: "style", sort: base_sort++, section: style_section, col:6, inline:true, inputtype: CssUnitInput }, { name: "Height", key: "height", htmlAttr: "style", sort: base_sort++, section: style_section, col:6, inline:true, inputtype: CssUnitInput }, { name: "Min Width", key: "min-width", htmlAttr: "style", sort: base_sort++, section: style_section, col:6, inline:true, inputtype: CssUnitInput }, { name: "Min Height", key: "min-height", htmlAttr: "style", sort: base_sort++, section: style_section, col:6, inline:true, inputtype: CssUnitInput }, { name: "Max Width", key: "max-width", htmlAttr: "style", sort: base_sort++, section: style_section, col:6, inline:true, inputtype: CssUnitInput }, { name: "Max Height", key: "max-height", htmlAttr: "style", sort: base_sort++, section: style_section, col:6, inline:true, inputtype: CssUnitInput }] }); //Margin Vvveb.Components.extend("_base", "_base", { properties: [{ key: "margins_header", inputtype: SectionInput, name:false, sort: base_sort++, section: style_section, data: {header:"Margin", expanded:false}, }, { name: "Top", key: "margin-top", htmlAttr: "style", sort: base_sort++, section: style_section, col:6, inline:true, inputtype: CssUnitInput }, { name: "Right", key: "margin-right", htmlAttr: "style", sort: base_sort++, section: style_section, col:6, inline:true, inputtype: CssUnitInput }, { name: "Bottom", key: "margin-bottom", htmlAttr: "style", sort: base_sort++, section: style_section, col:6, inline:true, inputtype: CssUnitInput }, { name: "Left", key: "margin-left", htmlAttr: "style", sort: base_sort++, section: style_section, col:6, inline:true, inputtype: CssUnitInput }] }); //Padding Vvveb.Components.extend("_base", "_base", { properties: [{ key: "paddings_header", inputtype: SectionInput, name:false, sort: base_sort++, section: style_section, data: {header:"Padding", expanded:false}, }, { name: "Top", key: "padding-top", htmlAttr: "style", sort: base_sort++, section: style_section, col:6, inline:true, inputtype: CssUnitInput }, { name: "Right", key: "padding-right", htmlAttr: "style", sort: base_sort++, section: style_section, col:6, inline:true, inputtype: CssUnitInput }, { name: "Bottom", key: "padding-bottom", htmlAttr: "style", sort: base_sort++, section: style_section, col:6, inline:true, inputtype: CssUnitInput }, { name: "Left", key: "padding-left", htmlAttr: "style", sort: base_sort++, section: style_section, col:6, inline:true, inputtype: CssUnitInput }] }); //Border Vvveb.Components.extend("_base", "_base", { properties: [{ key: "border_header", inputtype: SectionInput, name:false, sort: base_sort++, section: style_section, data: {header:"Border", expanded:false}, }, { name: "Style", key: "border-style", htmlAttr: "style", sort: base_sort++, section: style_section, col:12, inline:true, inputtype: SelectInput, data: { options: [{ value: "", text: "Default" }, { value: "solid", text: "Solid" }, { value: "dotted", text: "Dotted" }, { value: "dashed", text: "Dashed" }], } }, { name: "Width", key: "border-width", htmlAttr: "style", sort: base_sort++, section: style_section, col:6, inline:true, inputtype: CssUnitInput }, { name: "Color", key: "border-color", sort: base_sort++, section: style_section, col:6, inline:true, htmlAttr: "style", inputtype: ColorInput, }] }); //Border radius Vvveb.Components.extend("_base", "_base", { properties: [{ key: "border_radius_header", inputtype: SectionInput, name:false, sort: base_sort++, section: style_section, data: {header:"Border radius", expanded:false}, }, { name: "Top Left", key: "border-top-left-radius", htmlAttr: "style", sort: base_sort++, section: style_section, col:6, inline:true, inputtype: CssUnitInput }, { name: "Top Right", key: "border-top-right-radius", htmlAttr: "style", sort: base_sort++, section: style_section, col:6, inline:true, inputtype: CssUnitInput }, { name: "Bottom Left", key: "border-bottom-left-radius", htmlAttr: "style", sort: base_sort++, section: style_section, col:6, inline:true, inputtype: CssUnitInput }, { name: "Bottom Right", key: "border-bottom-right-radius", htmlAttr: "style", sort: base_sort++, section: style_section, col:6, inline:true, inputtype: CssUnitInput }] }); //Background image Vvveb.Components.extend("_base", "_base", { properties: [{ key: "background_image_header", inputtype: SectionInput, name:false, sort: base_sort++, section: style_section, data: {header:"Background Image", expanded:false}, },{ name: "Image", key: "Image", sort: base_sort++, section: style_section, //htmlAttr: "style", inputtype: ImageInput, init: function(node) { var image = $(node).css("background-image").replace(/^url\(['"]?(.+)['"]?\)/, '$1'); return image; }, onChange: function(node, value) { $(node).css('background-image', 'url(' + value + ')'); return node; } }, { name: "Repeat", key: "background-repeat", sort: base_sort++, section: style_section, htmlAttr: "style", inputtype: SelectInput, data: { options: [{ value: "", text: "Default" }, { value: "repeat-x", text: "repeat-x" }, { value: "repeat-y", text: "repeat-y" }, { value: "no-repeat", text: "no-repeat" }], } }, { name: "Size", key: "background-size", sort: base_sort++, section: style_section, htmlAttr: "style", inputtype: SelectInput, data: { options: [{ value: "", text: "Default" }, { value: "contain", text: "contain" }, { value: "cover", text: "cover" }], } }, { name: "Position x", key: "background-position-x", sort: base_sort++, section: style_section, htmlAttr: "style", col:6, inline:true, inputtype: SelectInput, data: { options: [{ value: "", text: "Default" }, { value: "center", text: "center" }, { value: "right", text: "right" }, { value: "left", text: "left" }], } }, { name: "Position y", key: "background-position-y", sort: base_sort++, section: style_section, htmlAttr: "style", col:6, inline:true, inputtype: SelectInput, data: { options: [{ value: "", text: "Default" }, { value: "center", text: "center" }, { value: "top", text: "top" }, { value: "bottom", text: "bottom" }], } }] }); Vvveb.Components.extend("_base", "html/container", { classes: ["container", "container-fluid"], image: "icons/container.svg", html: '
              Container
              ', name: "Container", properties: [ { name: "Type", key: "type", htmlAttr: "class", inputtype: SelectInput, validValues: ["container", "container-fluid"], data: { options: [{ value: "container", text: "Default" }, { value: "container-fluid", text: "Fluid" }] } }, { name: "Background", key: "background", htmlAttr: "class", validValues: bgcolorClasses, inputtype: SelectInput, data: { options: bgcolorSelectOptions } }, { name: "Background Color", key: "background-color", htmlAttr: "style", inputtype: ColorInput, }, { name: "Text Color", key: "color", htmlAttr: "style", inputtype: ColorInput, }], }); Vvveb.Components.extend("_base", "html/heading", { image: "icons/heading.svg", name: "Heading", nodes: ["h1", "h2","h3", "h4","h5","h6"], html: "

              Heading

              ", properties: [ { name: "Size", key: "size", inputtype: SelectInput, onChange: function(node, value) { return changeNodeName(node, "h" + value); }, init: function(node) { var regex; regex = /H(\d)/.exec(node.nodeName); if (regex && regex[1]) { return regex[1] } return 1 }, data:{ options: [{ value: "1", text: "1" }, { value: "2", text: "2" }, { value: "3", text: "3" }, { value: "4", text: "4" }, { value: "5", text: "5" }, { value: "6", text: "6" }] }, }] }); Vvveb.Components.extend("_base", "html/link", { nodes: ["a"], name: "Link", html: 'Link', image: "icons/link.svg", properties: [{ name: "Url", key: "href", htmlAttr: "href", inputtype: LinkInput }, { name: "Target", key: "target", htmlAttr: "target", inputtype: TextInput }] }); Vvveb.Components.extend("_base", "html/image", { nodes: ["img"], name: "Image", html: '', /* afterDrop: function (node) { node.attr("src", ''); return node; },*/ image: "icons/image.svg", properties: [{ name: "Image", key: "src", htmlAttr: "src", inputtype: ImageInput }, { name: "Width", key: "width", htmlAttr: "width", inputtype: TextInput }, { name: "Height", key: "height", htmlAttr: "height", inputtype: TextInput }, { name: "Alt", key: "alt", htmlAttr: "alt", inputtype: TextInput }] }); Vvveb.Components.add("html/hr", { image: "icons/hr.svg", nodes: ["hr"], name: "Horizontal Rule", html: "
              " }); Vvveb.Components.extend("_base", "html/label", { name: "Label", nodes: ["label"], html: '', properties: [{ name: "For id", htmlAttr: "for", key: "for", inputtype: TextInput }] }); Vvveb.Components.extend("_base", "html/button", { classes: ["btn", "btn-link"], name: "Button", image: "icons/button.svg", html: '', properties: [{ name: "Link To", key: "href", htmlAttr: "href", inputtype: LinkInput }, { name: "Type", key: "type", htmlAttr: "class", inputtype: SelectInput, validValues: ["btn-default", "btn-primary", "btn-info", "btn-success", "btn-warning", "btn-info", "btn-light", "btn-dark", "btn-outline-primary", "btn-outline-info", "btn-outline-success", "btn-outline-warning", "btn-outline-info", "btn-outline-light", "btn-outline-dark", "btn-link"], data: { options: [{ value: "btn-default", text: "Default" }, { value: "btn-primary", text: "Primary" }, { value: "btn btn-info", text: "Info" }, { value: "btn-success", text: "Success" }, { value: "btn-warning", text: "Warning" }, { value: "btn-info", text: "Info" }, { value: "btn-light", text: "Light" }, { value: "btn-dark", text: "Dark" }, { value: "btn-outline-primary", text: "Primary outline" }, { value: "btn btn-outline-info", text: "Info outline" }, { value: "btn-outline-success", text: "Success outline" }, { value: "btn-outline-warning", text: "Warning outline" }, { value: "btn-outline-info", text: "Info outline" }, { value: "btn-outline-light", text: "Light outline" }, { value: "btn-outline-dark", text: "Dark outline" }, { value: "btn-link", text: "Link" }] } }, { name: "Size", key: "size", htmlAttr: "class", inputtype: SelectInput, validValues: ["btn-lg", "btn-sm"], data: { options: [{ value: "", text: "Default" }, { value: "btn-lg", text: "Large" }, { value: "btn-sm", text: "Small" }] } }, { name: "Target", key: "target", htmlAttr: "target", inputtype: TextInput }, { name: "Disabled", key: "disabled", htmlAttr: "class", inputtype: ToggleInput, validValues: ["disabled"], data: { on: "disabled", off: null } }] }); Vvveb.Components.extend("_base", "html/buttongroup", { classes: ["btn-group"], name: "Button Group", image: "icons/button_group.svg", html: '
              ', properties: [{ name: "Size", key: "size", htmlAttr: "class", inputtype: SelectInput, validValues: ["btn-group-lg", "btn-group-sm"], data: { options: [{ value: "", text: "Default" }, { value: "btn-group-lg", text: "Large" }, { value: "btn-group-sm", text: "Small" }] } }, { name: "Alignment", key: "alignment", htmlAttr: "class", inputtype: SelectInput, validValues: ["btn-group", "btn-group-vertical"], data: { options: [{ value: "", text: "Default" }, { value: "btn-group", text: "Horizontal" }, { value: "btn-group-vertical", text: "Vertical" }] } }] }); Vvveb.Components.extend("_base", "html/buttontoolbar", { classes: ["btn-toolbar"], name: "Button Toolbar", image: "icons/button_toolbar.svg", html: '' }); Vvveb.Components.extend("_base","html/alert", { classes: ["alert"], name: "Alert", image: "icons/alert.svg", html: '', properties: [{ name: "Type", key: "type", htmlAttr: "class", validValues: ["alert-primary", "alert-secondary", "alert-success", "alert-danger", "alert-warning", "alert-info", "alert-light", "alert-dark"], inputtype: SelectInput, data: { options: [{ value: "alert-primary", text: "Default" }, { value: "alert-secondary", text: "Secondary" }, { value: "alert-success", text: "Success" }, { value: "alert-danger", text: "Danger" }, { value: "alert-warning", text: "Warning" }, { value: "alert-info", text: "Info" }, { value: "alert-light", text: "Light" }, { value: "alert-dark", text: "Dark" }] } }] }); Vvveb.Components.extend("_base", "html/badge", { classes: ["badge"], image: "icons/badge.svg", name: "Badge", html: 'Primary badge', properties: [{ name: "Color", key: "color", htmlAttr: "class", validValues:["badge-primary", "badge-secondary", "badge-success", "badge-danger", "badge-warning", "badge-info", "badge-light", "badge-dark"], inputtype: SelectInput, data: { options: [{ value: "", text: "Default" }, { value: "badge-primary", text: "Primary" }, { value: "badge-secondary", text: "Secondary" }, { value: "badge-success", text: "Success" }, { value: "badge-warning", text: "Warning" }, { value: "badge-danger", text: "Danger" }, { value: "badge-info", text: "Info" }, { value: "badge-light", text: "Light" }, { value: "badge-dark", text: "Dark" }] } }] }); Vvveb.Components.extend("_base", "html/card", { classes: ["card"], image: "icons/panel.svg", name: "Card", html: '
              \ Card image cap\
              \

              Card title

              \

              Some quick example text to build on the card title and make up the bulk of the card\'s content.

              \ Go somewhere\
              \
              ' }); Vvveb.Components.extend("_base", "html/listgroup", { name: "List Group", image: "icons/list_group.svg", classes: ["list-group"], html: '
                \n
              • \n 14\n Cras justo odio\n
              • \n
              • \n 2\n Dapibus ac facilisis in\n
              • \n
              • \n 1\n Morbi leo risus\n
              • \n
              ' }); Vvveb.Components.extend("_base", "html/listitem", { name: "List Item", classes: ["list-group-item"], html: '
            8. 14 Cras justo odio
            9. ' }); Vvveb.Components.extend("_base", "html/breadcrumbs", { classes: ["breadcrumb"], name: "Breadcrumbs", image: "icons/breadcrumbs.svg", html: '' }); Vvveb.Components.extend("_base", "html/breadcrumbitem", { classes: ["breadcrumb-item"], name: "Breadcrumb Item", html: '', properties: [{ name: "Active", key: "active", htmlAttr: "class", validValues: ["", "active"], inputtype: ToggleInput, data: { on: "active", off: "" } }] }); Vvveb.Components.extend("_base", "html/pagination", { classes: ["pagination"], name: "Pagination", image: "icons/pagination.svg", html: '', properties: [{ name: "Size", key: "size", htmlAttr: "class", inputtype: SelectInput, validValues: ["btn-lg", "btn-sm"], data: { options: [{ value: "", text: "Default" }, { value: "btn-lg", text: "Large" }, { value: "btn-sm", text: "Small" }] } },{ name: "Alignment", key: "alignment", htmlAttr: "class", inputtype: SelectInput, validValues: ["justify-content-center", "justify-content-end"], data: { options: [{ value: "", text: "Default" }, { value: "justify-content-center", text: "Center" }, { value: "justify-content-end", text: "Right" }] } }] }); Vvveb.Components.extend("_base", "html/pageitem", { classes: ["page-item"], html: '
            10. 1
            11. ', name: "Pagination Item", properties: [{ name: "Link To", key: "href", htmlAttr: "href", child:".page-link", inputtype: TextInput }, { name: "Disabled", key: "disabled", htmlAttr: "class", validValues: ["disabled"], inputtype: ToggleInput, data: { on: "disabled", off: "" } }] }); Vvveb.Components.extend("_base", "html/progress", { classes: ["progress"], name: "Progress Bar", image: "icons/progressbar.svg", html: '
              ', properties: [{ name: "Background", key: "background", htmlAttr: "class", validValues: bgcolorClasses, inputtype: SelectInput, data: { options: bgcolorSelectOptions } }, { name: "Progress", key: "background", child:".progress-bar", htmlAttr: "class", validValues: ["", "w-25", "w-50", "w-75", "w-100"], inputtype: SelectInput, data: { options: [{ value: "", text: "None" }, { value: "w-25", text: "25%" }, { value: "w-50", text: "50%" }, { value: "w-75", text: "75%" }, { value: "w-100", text: "100%" }] } }, { name: "Progress background", key: "background", child:".progress-bar", htmlAttr: "class", validValues: bgcolorClasses, inputtype: SelectInput, data: { options: bgcolorSelectOptions } }, { name: "Striped", key: "striped", child:".progress-bar", htmlAttr: "class", validValues: ["", "progress-bar-striped"], inputtype: ToggleInput, data: { on: "progress-bar-striped", off: "", } }, { name: "Animated", key: "animated", child:".progress-bar", htmlAttr: "class", validValues: ["", "progress-bar-animated"], inputtype: ToggleInput, data: { on: "progress-bar-animated", off: "", } }] }); Vvveb.Components.extend("_base", "html/jumbotron", { classes: ["jumbotron"], image: "icons/jumbotron.svg", name: "Jumbotron", html: '
              \

              Hello, world!

              \

              This is a simple hero unit, a simple jumbotron-style component for calling extra attention to featured content or information.

              \
              \

              It uses utility classes for typography and spacing to space content out within the larger container.

              \

              \ Learn more\

              \
              ' }); Vvveb.Components.extend("_base", "html/navbar", { classes: ["navbar"], image: "icons/navbar.svg", name: "Nav Bar", html: '', properties: [{ name: "Color theme", key: "color", htmlAttr: "class", validValues: ["navbar-light", "navbar-dark"], inputtype: SelectInput, data: { options: [{ value: "", text: "Default" }, { value: "navbar-light", text: "Light" }, { value: "navbar-dark", text: "Dark" }] } },{ name: "Background color", key: "background", htmlAttr: "class", validValues: bgcolorClasses, inputtype: SelectInput, data: { options: bgcolorSelectOptions } }, { name: "Placement", key: "placement", htmlAttr: "class", validValues: ["fixed-top", "fixed-bottom", "sticky-top"], inputtype: SelectInput, data: { options: [{ value: "", text: "Default" }, { value: "fixed-top", text: "Fixed Top" }, { value: "fixed-bottom", text: "Fixed Bottom" }, { value: "sticky-top", text: "Sticky top" }] } }] }); Vvveb.Components.extend("_base", "html/form", { nodes: ["form"], image: "icons/form.svg", name: "Form", html: '
              ', properties: [{ name: "Style", key: "style", htmlAttr: "class", validValues: ["", "form-search", "form-inline", "form-horizontal"], inputtype: SelectInput, data: { options: [{ value: "", text: "Default" }, { value: "form-search", text: "Search" }, { value: "form-inline", text: "Inline" }, { value: "form-horizontal", text: "Horizontal" }] } }, { name: "Action", key: "action", htmlAttr: "action", inputtype: TextInput }, { name: "Method", key: "method", htmlAttr: "method", inputtype: TextInput }] }); Vvveb.Components.extend("_base", "html/textinput", { name: "Input", nodes: ["input"], //attributes: {"type":"text"}, image: "icons/text_input.svg", html: '
              ', properties: [{ name: "Value", key: "value", htmlAttr: "value", inputtype: TextInput }, { name: "Type", key: "type", htmlAttr: "type", inputtype: SelectInput, data: { options: [{ value: "text", text: "text" }, { value: "button", text: "button" }, { value: "checkbox", text: "checkbox" }, { value: "color", text: "color" }, { value: "date", text: "date" }, { value: "datetime-local", text: "datetime-local" }, { value: "email", text: "email" }, { value: "file", text: "file" }, { value: "hidden", text: "hidden" }, { value: "image", text: "image" }, { value: "month", text: "month" }, { value: "number", text: "number" }, { value: "password", text: "password" }, { value: "radio", text: "radio" }, { value: "range", text: "range" }, { value: "reset", text: "reset" }, { value: "search", text: "search" }, { value: "submit", text: "submit" }, { value: "tel", text: "tel" }, { value: "text", text: "text" }, { value: "time", text: "time" }, { value: "url", text: "url" }, { value: "week", text: "week" }] } }, { name: "Placeholder", key: "placeholder", htmlAttr: "placeholder", inputtype: TextInput }, { name: "Disabled", key: "disabled", htmlAttr: "disabled", col:6, inputtype: CheckboxInput, },{ name: "Required", key: "required", htmlAttr: "required", col:6, inputtype: CheckboxInput, }] }); Vvveb.Components.extend("_base", "html/selectinput", { nodes: ["select"], name: "Select Input", image: "icons/select_input.svg", html: '
              ', beforeInit: function (node) { properties = []; var i = 0; $(node).find('option').each(function() { data = {"value": this.value, "text": this.text}; i++; properties.push({ name: "Option " + i, key: "option" + i, //index: i - 1, optionNode: this, inputtype: TextValueInput, data: data, onChange: function(node, value, input) { option = $(this.optionNode); //if remove button is clicked remove option and render row properties if (input.nodeName == 'BUTTON') { option.remove(); Vvveb.Components.render("html/selectinput"); return node; } if (input.name == "value") option.attr("value", value); else if (input.name == "text") option.text(value); return node; }, }); }); //remove all option properties this.properties = this.properties.filter(function(item) { return item.key.indexOf("option") === -1; }); //add remaining properties to generated column properties properties.push(this.properties[0]); this.properties = properties; return node; }, properties: [{ name: "Option", key: "option1", inputtype: TextValueInput }, { name: "Option", key: "option2", inputtype: TextValueInput }, { name: "", key: "addChild", inputtype: ButtonInput, data: {text:"Add option", icon:"la-plus"}, onChange: function(node) { $(node).append(''); //render component properties again to include the new column inputs Vvveb.Components.render("html/selectinput"); return node; } }] }); Vvveb.Components.extend("_base", "html/textareainput", { name: "Text Area", image: "icons/text_area.svg", html: '
              ' }); Vvveb.Components.extend("_base", "html/radiobutton", { name: "Radio Button", attributes: {"type":"radio"}, image: "icons/radio.svg", html: '', properties: [{ name: "Name", key: "name", htmlAttr: "name", inputtype: TextInput }] }); Vvveb.Components.extend("_base", "html/checkbox", { name: "Checkbox", attributes: {"type":"checkbox"}, image: "icons/checkbox.svg", html: '', properties: [{ name: "Name", key: "name", htmlAttr: "name", inputtype: TextInput }] }); Vvveb.Components.extend("_base", "html/fileinput", { name: "Input group", attributes: {"type":"file"}, image: "icons/text_input.svg", html: '
              \ \
              ' }); Vvveb.Components.extend("_base", "html/table", { nodes: ["table"], classes: ["table"], image: "icons/table.svg", name: "Table", html: '\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \
              #First NameLast NameUsername
              1MarkOtto@mdo
              2JacobThornton@fat
              3Larrythe Bird@twitter
              ', properties: [ { name: "Type", key: "type", htmlAttr: "class", validValues: ["table-primary", "table-secondary", "table-success", "table-danger", "table-warning", "table-info", "table-light", "table-dark", "table-white"], inputtype: SelectInput, data: { options: [{ value: "Default", text: "" }, { value: "table-primary", text: "Primary" }, { value: "table-secondary", text: "Secondary" }, { value: "table-success", text: "Success" }, { value: "table-danger", text: "Danger" }, { value: "table-warning", text: "Warning" }, { value: "table-info", text: "Info" }, { value: "table-light", text: "Light" }, { value: "table-dark", text: "Dark" }, { value: "table-white", text: "White" }] } }, { name: "Responsive", key: "responsive", htmlAttr: "class", validValues: ["table-responsive"], inputtype: ToggleInput, data: { on: "table-responsive", off: "" } }, { name: "Small", key: "small", htmlAttr: "class", validValues: ["table-sm"], inputtype: ToggleInput, data: { on: "table-sm", off: "" } }, { name: "Hover", key: "hover", htmlAttr: "class", validValues: ["table-hover"], inputtype: ToggleInput, data: { on: "table-hover", off: "" } }, { name: "Bordered", key: "bordered", htmlAttr: "class", validValues: ["table-bordered"], inputtype: ToggleInput, data: { on: "table-bordered", off: "" } }, { name: "Striped", key: "striped", htmlAttr: "class", validValues: ["table-striped"], inputtype: ToggleInput, data: { on: "table-striped", off: "" } }, { name: "Inverse", key: "inverse", htmlAttr: "class", validValues: ["table-inverse"], inputtype: ToggleInput, data: { on: "table-inverse", off: "" } }, { name: "Head options", key: "head", htmlAttr: "class", child:"thead", inputtype: SelectInput, validValues: ["", "thead-inverse", "thead-default"], data: { options: [{ value: "", text: "None" }, { value: "thead-default", text: "Default" }, { value: "thead-inverse", text: "Inverse" }] } }] }); Vvveb.Components.extend("_base", "html/tablerow", { nodes: ["tr"], name: "Table Row", html: "Cell 1Cell 2Cell 3", properties: [{ name: "Type", key: "type", htmlAttr: "class", inputtype: SelectInput, validValues: ["", "success", "danger", "warning", "active"], data: { options: [{ value: "", text: "Default" }, { value: "success", text: "Success" }, { value: "error", text: "Error" }, { value: "warning", text: "Warning" }, { value: "active", text: "Active" }] } }] }); Vvveb.Components.extend("_base", "html/tablecell", { nodes: ["td"], name: "Table Cell", html: "Cell" }); Vvveb.Components.extend("_base", "html/tableheadercell", { nodes: ["th"], name: "Table Header Cell", html: "Head" }); Vvveb.Components.extend("_base", "html/tablehead", { nodes: ["thead"], name: "Table Head", html: "Head 1Head 2Head 3", properties: [{ name: "Type", key: "type", htmlAttr: "class", inputtype: SelectInput, validValues: ["", "success", "danger", "warning", "info"], data: { options: [{ value: "", text: "Default" }, { value: "success", text: "Success" }, { value: "anger", text: "Error" }, { value: "warning", text: "Warning" }, { value: "info", text: "Info" }] } }] }); Vvveb.Components.extend("_base", "html/tablebody", { nodes: ["tbody"], name: "Table Body", html: "Cell 1Cell 2Cell 3" }); Vvveb.Components.add("html/gridcolumn", { name: "Grid Column", image: "icons/grid_row.svg", classesRegex: ["col-"], html: '

              col-sm-4

              ', properties: [{ name: "Column", key: "column", inputtype: GridInput, data: {hide_remove:true}, beforeInit: function(node) { _class = $(node).attr("class"); var reg = /col-([^-\$ ]*)?-?(\d+)/g; var match; while ((match = reg.exec(_class)) != null) { this.data["col" + ((match[1] != undefined)?"_" + match[1]:"")] = match[2]; } }, onChange: function(node, value, input) { var _class = node.attr("class"); //remove previous breakpoint column size _class = _class.replace(new RegExp(input.name + '-\\d+?'), ''); //add new column size if (value) _class += ' ' + input.name + '-' + value; node.attr("class", _class); return node; }, }] }); Vvveb.Components.add("html/gridrow", { name: "Grid Row", image: "icons/grid_row.svg", classes: ["row"], html: '

              col-sm-4

              col-sm-4

              col-sm-4

              ', children :[{ name: "html/gridcolumn", classesRegex: ["col-"], }], beforeInit: function (node) { properties = []; var i = 0; var j = 0; $(node).find('[class*="col-"]').each(function() { _class = $(this).attr("class"); var reg = /col-([^-\$ ]*)?-?(\d+)/g; var match; var data = {}; while ((match = reg.exec(_class)) != null) { data["col" + ((match[1] != undefined)?"_" + match[1]:"")] = match[2]; } i++; properties.push({ name: "Column " + i, key: "column" + i, //index: i - 1, columnNode: this, col:12, inline:true, inputtype: GridInput, data: data, onChange: function(node, value, input) { //column = $('[class*="col-"]:eq(' + this.index + ')', node); var column = $(this.columnNode); //if remove button is clicked remove column and render row properties if (input.nodeName == 'BUTTON') { column.remove(); Vvveb.Components.render("html/gridrow"); return node; } //if select input then change column class _class = column.attr("class"); //remove previous breakpoint column size _class = _class.replace(new RegExp(input.name + '-\\d+?'), ''); //add new column size if (value) _class += ' ' + input.name + '-' + value; column.attr("class", _class); //console.log(this, node, value, input, input.name); return node; }, }); }); //remove all column properties this.properties = this.properties.filter(function(item) { return item.key.indexOf("column") === -1; }); //add remaining properties to generated column properties properties.push(this.properties[0]); this.properties = properties; return node; }, properties: [{ name: "Column", key: "column1", inputtype: GridInput }, { name: "Column", key: "column1", inline:true, col:12, inputtype: GridInput }, { name: "", key: "addChild", inputtype: ButtonInput, data: {text:"Add column", icon:"la la-plus"}, onChange: function(node) { $(node).append('
              Col-3
              '); //render component properties again to include the new column inputs Vvveb.Components.render("html/gridrow"); return node; } }] }); Vvveb.Components.extend("_base", "html/paragraph", { nodes: ["p"], name: "Paragraph", image: "icons/paragraph.svg", html: '

              Lorem ipsum

              ', properties: [{ name: "Text align", key: "text-align", htmlAttr: "class", inputtype: SelectInput, validValues: ["", "text-left", "text-center", "text-right"], inputtype: RadioButtonInput, data: { extraclass:"btn-group-sm btn-group-fullwidth", options: [{ value: "", icon:"la la-times", //text: "None", title: "None", checked:true, }, { value: "text-left", //text: "Left", title: "text-left", icon:"la la-align-left", checked:false, }, { value: "text-center", //text: "Center", title: "Center", icon:"la la-align-center", checked:false, }, { value: "text-right", //text: "Right", title: "Right", icon:"la la-align-right", checked:false, }], }, }] }); Vvveb.Components.extend("_base", "html/video", { nodes: ["video"], name: "Video", html: '