├── .babelrc ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── composer.json ├── images └── drag-dots.svg ├── jsconfig.json ├── mix-manifest.json ├── package-lock.json ├── package.json ├── public ├── css │ ├── buildamic.css │ └── buildamic.css.map ├── img │ ├── drag-dots.svg │ ├── text-center.svg │ ├── text-left.svg │ └── text-right.svg └── js │ ├── buildamic.js │ ├── buildamic.js.LICENSE.txt │ └── buildamic.js.map ├── resources ├── css │ └── buildamic.css ├── js │ ├── buildamic.js │ ├── components │ │ ├── ModuleSelector.vue │ │ ├── columns │ │ │ ├── ColumnSettings.vue │ │ │ └── GridColumn.vue │ │ ├── fields │ │ │ ├── FieldDisplay.vue │ │ │ ├── FieldSettings.vue │ │ │ ├── SingleField.vue │ │ │ └── overrides │ │ │ │ └── ColorFieldtype.vue │ │ ├── fieldtypes │ │ │ └── Buildamic.vue │ │ ├── inputs │ │ │ └── BuildamicInputText.vue │ │ ├── rows │ │ │ ├── ColumnSelector.vue │ │ │ ├── GridRow.vue │ │ │ └── RowSettings.vue │ │ ├── sections │ │ │ ├── GridGlobalSection.vue │ │ │ ├── GridSection.vue │ │ │ └── SectionSettings.vue │ │ ├── sets │ │ │ ├── SetField.vue │ │ │ └── SetSettings.vue │ │ ├── shared │ │ │ ├── AdminLabel.vue │ │ │ ├── DesignTab.vue │ │ │ ├── ErrorDisplay.vue │ │ │ ├── FieldBase.vue │ │ │ ├── ModuleControls.vue │ │ │ ├── OptionsTab.vue │ │ │ ├── SettingStack.vue │ │ │ └── settings │ │ │ │ ├── AlignmentControls.vue │ │ │ │ ├── BoxModelUI.vue │ │ │ │ ├── BreakpointSwitcher.vue │ │ │ │ ├── DataAttributes.vue │ │ │ │ ├── SettingsBackground.vue │ │ │ │ ├── SettingsGroup.vue │ │ │ │ ├── SettingsLayout.vue │ │ │ │ ├── SettingsText.vue │ │ │ │ └── ToggleControls.vue │ │ └── tabs │ │ │ ├── VueTab.vue │ │ │ └── VueTabs.vue │ ├── eventBus.js │ ├── factories │ │ └── modules │ │ │ ├── column.js │ │ │ ├── field.js │ │ │ ├── global-section.js │ │ │ ├── moduleDefaults.js │ │ │ ├── moduleFactory.js │ │ │ ├── row.js │ │ │ ├── section.js │ │ │ └── set.js │ ├── field-conditions │ │ ├── Constants.js │ │ ├── Converter.js │ │ ├── FieldConditions.js │ │ └── Validator.js │ ├── functions │ │ ├── helpers.js │ │ ├── idHelpers.js │ │ └── objectHelpers.js │ ├── mixins │ │ ├── ClipboardFunctions.js │ │ ├── OptionsFields.js │ │ └── SectionControls.js │ ├── services │ │ └── statamic_api.js │ └── store │ │ └── index.js ├── sass │ ├── _tailwindbase.scss │ ├── _tailwindcomponents.scss │ ├── _tailwindutilities.scss │ ├── buildamic.scss │ ├── buildy │ │ ├── _buildy.scss │ │ ├── _grid.scss │ │ └── components │ │ │ ├── _accordions.scss │ │ │ ├── _background-videos.scss │ │ │ ├── _blurbs.scss │ │ │ ├── _gallery.scss │ │ │ ├── _image-module.scss │ │ │ ├── _page-links.scss │ │ │ └── _sliders.scss │ ├── mixins │ │ ├── _breakpoints.scss │ │ ├── _mixins-master.scss │ │ └── _utilities.scss │ └── vendor │ │ └── _rfs.scss └── views │ ├── default-field.blade.php │ ├── fields │ ├── bard.blade.php │ ├── code.blade.php │ ├── collections.blade.php │ ├── html.blade.php │ ├── markdown.blade.php │ ├── terms.blade.php │ ├── text.blade.php │ └── textarea.blade.php │ └── layouts │ ├── column.blade.php │ ├── container.blade.php │ ├── field.blade.php │ ├── fieldset.blade.php │ ├── row.blade.php │ ├── section.blade.php │ └── set.blade.php ├── src ├── BuildamicFilters.php ├── BuildamicHelper.php ├── BuildamicRenderer.php ├── Fields │ ├── Field.php │ └── Fields.php ├── Fieldtypes │ ├── Buildamic.php │ ├── BuildamicBase.php │ ├── BuildamicColumn.php │ ├── BuildamicGlobal.php │ ├── BuildamicGlobalSection.php │ ├── BuildamicRow.php │ └── BuildamicSection.php ├── Filter.php ├── ServiceProvider.php ├── Tags │ ├── BuildamicScripts.php │ └── BuildamicStyles.php ├── Traits │ ├── AugmentsOnce.php │ ├── HasBuildamicSettings.php │ └── HasComputedAttributes.php └── helpers.php ├── tailwind.config.js └── webpack.mix.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env"] 3 | } 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to `buildamic` will be documented in this file 4 | 5 | ## 0.1.0 - 26/07/2021 (dd/mm/yyyy) 6 | 7 | - initial beta release 8 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Michael Rook 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Latest Version on Packagist](https://img.shields.io/packagist/v/handmadeweb/buildamic.svg?style=flat-square)](https://packagist.org/packages/handmadeweb/buildamic) 2 | [![Total Downloads](https://img.shields.io/packagist/dt/handmadeweb/buildamic.svg?style=flat-square)](https://packagist.org/packages/handmadeweb/buildamic) 3 | [![MIT Licensed](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](LICENSE.md) 4 | [![Run Tests](https://github.com/handmadeweb/buildamic/actions/workflows/tests.yml/badge.svg)](https://github.com/handmadeweb/buildamic/actions/workflows/tests.yml) 5 | 6 | Buildamic is a WIP "pagebuilder" for Statamic 3, It is currently in heavy development and likely to have breaking changes with frequency, as such is not considered ready to be used in production. 7 | 8 | ## THIS IS A BETA 9 | Please be aware that it is not recommended to use this in production just yet. 10 | 11 | ## Requirements 12 | * PHP 8.0 or higher 13 | * Statamic 3.2 or higher 14 | * Laravel 8.0 or higher 15 | 16 | ## Installation 17 | 18 | You can install the package via composer: 19 | 20 | ```bash 21 | composer require handmadeweb/buildamic 22 | ``` 23 | 24 | ## Usage 25 | 26 | ### Backend 27 | #### Adding to a blueprint 28 | 29 | Add the field to your blueprint, you may then choose what fields or sets will be available for Buildamic to use. 30 | 31 | #### Field/Fieldset/Set Display names 32 | Buildamic will display the "label" for the "field" from the first available. 33 | * Admin Label (Found in the options area of the "field") 34 | * Display (As configured on the blueprint) 35 | * Handle (As configured on the blueprint) 36 | 37 | ### Frontend 38 | #### Grid 39 | Buildamic comes with a grid starting point (which expects that you are using TailWind), If you aren't going to be writing your own grid, then you should include Buildamic's grid style in your header via the provided helpers for Antlers: `{{ buildamicStyles }}`, Blade: `@buildamicStyles` or PHP: `echo BuildamicHelper()->styles();` 40 | 41 | #### Outputting 42 | Outputting on the frontend is quite simple, you just use the handle that was given to the field when you configure it in your blueprint. 43 | And reference the below two examples on how to render the output in Antlers or Blade. 44 | 45 | Statamic automatically casts the handle to an instance of \Statamic\Fields\Value and will automatically render via the __toString methods. 46 | 47 | By default the handle will be "buildamic" 48 | 49 | #### Antlers output 50 | ```php 51 | // The easy way 52 | {{ buildamic }} 53 | ``` 54 | 55 | #### Blade output 56 | If you are using Blade then We advise using "Our perferred way" listed below, which is slightly faster and will show a more complete picture should you choose to run a code profiler (Example: [blackfire.io](https://www.blackfire.io/)) 57 | 58 | ```php 59 | // The easy way 60 | {!! $buildamic !!} 61 | 62 | // Our perferred way. 63 | {!! $buildamic->value()->render() !!} 64 | ``` 65 | 66 | #### View Engines & View Overrides 67 | Currently Buildamic only comes with view files written in Blade. 68 | Buildamic will still work if your front end uses Antlers, it just means that when Buildamic loops and renders fields, Blade will be used to do so. 69 | 70 | Should you need to override a given view (or create new ones) you can do so by creating the views at `resources/views/vendor/buildamic` 71 | 72 | #### Field view order 73 | When Buildamic tries to render a field, it will use the first available file, checked in the below order. 74 | 75 | * field type: markdown 76 | * field handle: hero-blurb 77 | * loaded file: fields/markdown-hero-blurb.blade.php 78 | 79 | Then 80 | 81 | * field type: markdown 82 | * loaded file: fields/markdown.blade.php 83 | 84 | Then 85 | 86 | * catch all 87 | * loaded file: default-field.blade.php 88 | 89 | In the event that a suitable view could not be located, rather than erroring out or logging an exception, something like the below will instead appear as a html comment. 90 | 91 | ```html 92 | 93 | 94 | 95 | ``` 96 | 97 | #### Fieldset view order 98 | When Buildamic tries to render a fieldset, it will first try to find a view that matches the handle of the fieldset. 99 | * handle: blurb 100 | * loaded file: fieldsets/blurb.blade.php 101 | 102 | If no suitable view was found, then Buildamic will loop through each field within the fieldset and will treat them as separate fields, in which case the fields view order will apply. 103 | 104 | #### Set view order 105 | When Buildamic tries to render a set, it will first try to find a view that matches the handle of the set. 106 | 107 | * handle: blurb 108 | * loaded file: sets/blurb.blade.php 109 | 110 | If no suitable view was found, then Buildamic will loop through each field within the set and will treat them as separate fields, in which case the fields view order will apply. 111 | 112 | ## Changelog 113 | 114 | Please see [CHANGELOG](https://github.com/handmadeweb/buildamic/blob/main/CHANGELOG.md) for more information what has changed recently. 115 | 116 | ## Contributing 117 | 118 | Please see [CONTRIBUTING](https://github.com/handmadeweb/buildamic/blob/main/CONTRIBUTING.md) for details. 119 | 120 | ## Credits 121 | 122 | - [Aaron Curle](https://github.com/aaroncurlehmw) 123 | - [John Pieters](https://github.com/sliver37) 124 | - [Michael Rook](https://github.com/michaelr0) 125 | - [Handmade Web & Design](https://github.com/handmadeweb) and [All Contributors](https://github.com/handmadeweb/buildamic/graphs/contributors) 126 | 127 | ## License 128 | 129 | The MIT License (MIT). Please see [License File](https://github.com/handmadeweb/buildamic/blob/main/LICENSE.md) for more information. 130 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "handmadeweb/buildamic", 3 | "license": "MIT", 4 | "description": "Buildamic is a Pagebuilder addon for Statamic 3", 5 | "keywords": [ 6 | "statamic", 7 | "statamic-addon", 8 | "page-builder" 9 | ], 10 | "require": { 11 | "php": "^8.0", 12 | "laravel/framework": "^8.80 || ^9.0", 13 | "statamic/cms": "3.2.* || 3.3.*||3.4.*", 14 | "handmadeweb/hookable-actions-filters": "^1.1", 15 | "edalzell/blade-directives": "^3.5" 16 | }, 17 | "require-dev": { 18 | "orchestra/testbench": "^6.0 || ^7.0", 19 | "phpunit/phpunit": "^8.5.16 || ^9.5.5" 20 | }, 21 | "scripts": { 22 | "test": "vendor/bin/phpunit", 23 | "test-coverage": "vendor/bin/phpunit --coverage-html coverage" 24 | }, 25 | "autoload": { 26 | "files": [ 27 | "src/helpers.php" 28 | ], 29 | "psr-4": { 30 | "HandmadeWeb\\Buildamic\\": "src" 31 | } 32 | }, 33 | "autoload-dev": { 34 | "psr-4": { 35 | "HandmadeWeb\\Buildamic\\Tests\\": "tests" 36 | } 37 | }, 38 | "extra": { 39 | "statamic": { 40 | "name": "Buildamic", 41 | "description": "Buildamic is a Pagebuilder addon for Statamic 3" 42 | }, 43 | "laravel": { 44 | "providers": [ 45 | "HandmadeWeb\\Buildamic\\ServiceProvider" 46 | ] 47 | } 48 | }, 49 | "config": { 50 | "allow-plugins": { 51 | "pixelfear/composer-dist-plugin": true 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /images/drag-dots.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["./resources/**/*"], 3 | "compilerOptions": { 4 | "module": "es2015", 5 | "moduleResolution": "node", 6 | "target": "es5", 7 | "sourceMap": true, 8 | "allowJs": true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /mix-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "/public/js/buildamic.js": "/public/js/buildamic.js", 3 | "/public/css/buildamic.css": "/public/css/buildamic.css" 4 | } 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "test:unit": "vue-cli-service test:unit", 5 | "dev": "mix", 6 | "development": "mix", 7 | "hot": "mix watch --hot", 8 | "prod": "mix --production", 9 | "production": "mix --production", 10 | "test": "jest", 11 | "watch": "mix watch", 12 | "watch-poll": "mix watch -- --watch-options-poll=1000" 13 | }, 14 | "dependencies": { 15 | "@simonwep/pickr": "^1.8.1", 16 | "axios": "^0.21.4", 17 | "js-laravel-validation": "^1.3.1", 18 | "uuid": "^8.3.2", 19 | "vue-eva-icons": "^1.1.1", 20 | "vue-gpickr": "^0.2.7", 21 | "vue-simple-accordion": "^0.1.0", 22 | "vuedraggable": "^2.24.3", 23 | "vuex": "^3.6.2" 24 | }, 25 | "devDependencies": { 26 | "@vue/test-utils": "^1.2.1", 27 | "babel-core": "^7.0.0-bridge.0", 28 | "cross-env": "^7.0", 29 | "laravel-mix": "^6.0", 30 | "postcss": "^8.1", 31 | "postcss-import": "^14.0.2", 32 | "postcss-preset-env": "^7.0.0", 33 | "resolve-url-loader": "^4.0.0", 34 | "sass": "^1.43.4", 35 | "sass-loader": "^12.1.0", 36 | "tailwindcss": "^3.3.0", 37 | "vue": "^2.6.12", 38 | "vue-jest": "^4.0.1", 39 | "vue-loader": "^15.9.7", 40 | "vue-template-compiler": "^2.6.12" 41 | } 42 | } -------------------------------------------------------------------------------- /public/img/drag-dots.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /public/img/text-center.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /public/img/text-left.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /public/img/text-right.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /resources/css/buildamic.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /resources/js/buildamic.js: -------------------------------------------------------------------------------- 1 | import Buildamic from './components/fieldtypes/Buildamic.vue'; 2 | import VueTabs from './components/tabs/VueTabs.vue' 3 | import VueTab from './components/tabs/VueTab.vue' 4 | import BreakpointSwitcher from './components/shared/settings/BreakpointSwitcher.vue' 5 | 6 | import { buildamicStore } from './store' 7 | 8 | Statamic.booting(() => { 9 | Statamic.$store.registerModule('buildamicStore', buildamicStore) 10 | Statamic.$components.register('vue-tabs', VueTabs) 11 | Statamic.$components.register('vue-tab', VueTab) 12 | Statamic.$components.register('breakpoint-switcher', BreakpointSwitcher) 13 | Statamic.$components.register('buildamic-fieldtype', Buildamic); 14 | 15 | // Statamic.use(vfmPlugin) 16 | }); 17 | -------------------------------------------------------------------------------- /resources/js/components/ModuleSelector.vue: -------------------------------------------------------------------------------- 1 | 96 | 97 | 141 | 142 | 143 | -------------------------------------------------------------------------------- /resources/js/components/columns/ColumnSettings.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 29 | -------------------------------------------------------------------------------- /resources/js/components/columns/GridColumn.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 38 | 39 | 72 | 73 | 81 | -------------------------------------------------------------------------------- /resources/js/components/fields/FieldDisplay.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /resources/js/components/fields/FieldSettings.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 46 | -------------------------------------------------------------------------------- /resources/js/components/fields/SingleField.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 96 | 97 | 102 | -------------------------------------------------------------------------------- /resources/js/components/fields/overrides/ColorFieldtype.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 80 | -------------------------------------------------------------------------------- /resources/js/components/fieldtypes/Buildamic.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 31 | 32 | 82 | 83 | 123 | -------------------------------------------------------------------------------- /resources/js/components/inputs/BuildamicInputText.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /resources/js/components/rows/ColumnSelector.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 161 | 162 | 178 | -------------------------------------------------------------------------------- /resources/js/components/rows/GridRow.vue: -------------------------------------------------------------------------------- 1 | 59 | 60 | 61 | 62 | 132 | -------------------------------------------------------------------------------- /resources/js/components/rows/RowSettings.vue: -------------------------------------------------------------------------------- 1 | 35 | 36 | 64 | -------------------------------------------------------------------------------- /resources/js/components/sections/GridGlobalSection.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 39 | 40 | 100 | -------------------------------------------------------------------------------- /resources/js/components/sections/GridSection.vue: -------------------------------------------------------------------------------- 1 | 61 | 62 | 63 | 64 | 92 | -------------------------------------------------------------------------------- /resources/js/components/sections/SectionSettings.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 65 | -------------------------------------------------------------------------------- /resources/js/components/sets/SetField.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 121 | 122 | 127 | 128 | 142 | -------------------------------------------------------------------------------- /resources/js/components/sets/SetSettings.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 73 | -------------------------------------------------------------------------------- /resources/js/components/shared/AdminLabel.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /resources/js/components/shared/DesignTab.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 43 | -------------------------------------------------------------------------------- /resources/js/components/shared/ErrorDisplay.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /resources/js/components/shared/FieldBase.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 128 | 129 | 130 | -------------------------------------------------------------------------------- /resources/js/components/shared/ModuleControls.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 145 | -------------------------------------------------------------------------------- /resources/js/components/shared/OptionsTab.vue: -------------------------------------------------------------------------------- 1 | 51 | 52 | 128 | 129 | 130 | -------------------------------------------------------------------------------- /resources/js/components/shared/SettingStack.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /resources/js/components/shared/settings/AlignmentControls.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 81 | 82 | -------------------------------------------------------------------------------- /resources/js/components/shared/settings/BreakpointSwitcher.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 52 | 62 | -------------------------------------------------------------------------------- /resources/js/components/shared/settings/DataAttributes.vue: -------------------------------------------------------------------------------- 1 | 58 | 59 | 126 | 127 | 132 | -------------------------------------------------------------------------------- /resources/js/components/shared/settings/SettingsGroup.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 15 | 16 | 24 | -------------------------------------------------------------------------------- /resources/js/components/shared/settings/SettingsText.vue: -------------------------------------------------------------------------------- 1 | 40 | 41 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /resources/js/components/shared/settings/ToggleControls.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 86 | -------------------------------------------------------------------------------- /resources/js/components/tabs/VueTab.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 33 | -------------------------------------------------------------------------------- /resources/js/components/tabs/VueTabs.vue: -------------------------------------------------------------------------------- 1 | 36 | 37 | 59 | 60 | 69 | -------------------------------------------------------------------------------- /resources/js/eventBus.js: -------------------------------------------------------------------------------- 1 | export const bus = new Vue(); 2 | -------------------------------------------------------------------------------- /resources/js/factories/modules/column.js: -------------------------------------------------------------------------------- 1 | import { InlineDefaults, AttributeDefaults } from './moduleDefaults.js' 2 | 3 | const Column = function ({ UUID, ADMIN_LABEL }) { 4 | this.uuid = `${UUID}` 5 | this.type = 'column' 6 | this.config = { 7 | enabled: true, 8 | buildamic_settings: { 9 | admin_label: ADMIN_LABEL || this.type, 10 | columnSizes: { 11 | "xs": 12, 12 | "sm": '', 13 | "md": '', 14 | "lg": 12, 15 | "xl": '' 16 | }, 17 | inline: { ...JSON.parse(JSON.stringify(InlineDefaults)) }, 18 | attributes: { ...JSON.parse(JSON.stringify(AttributeDefaults)) } 19 | } 20 | } 21 | this.value = [] 22 | } 23 | 24 | export { Column } 25 | -------------------------------------------------------------------------------- /resources/js/factories/modules/field.js: -------------------------------------------------------------------------------- 1 | import { InlineDefaults, AttributeDefaults } from './moduleDefaults.js' 2 | 3 | const Field = function ({ ADMIN_LABEL, CONFIG = {}, META = {}, HANDLE, VALUE, UUID, }) { 4 | this.uuid = `${UUID}` 5 | this.type = 'field' 6 | this.computed = { 7 | config: { 8 | ...CONFIG 9 | }, 10 | meta: { 11 | ...META 12 | } 13 | } 14 | this.config = { 15 | statamic_settings: { 16 | field: { 17 | type: CONFIG.type || '', 18 | }, 19 | handle: HANDLE 20 | }, 21 | buildamic_settings: { 22 | enabled: true, 23 | admin_label: ADMIN_LABEL || CONFIG.DISPLAY || HANDLE, 24 | inline: { ...JSON.parse(JSON.stringify(InlineDefaults)) }, 25 | attributes: { ...JSON.parse(JSON.stringify(AttributeDefaults)) } 26 | } 27 | } 28 | // this.meta = META 29 | this.value = VALUE 30 | } 31 | 32 | export { Field } 33 | -------------------------------------------------------------------------------- /resources/js/factories/modules/global-section.js: -------------------------------------------------------------------------------- 1 | import { InlineDefaults, AttributeDefaults } from './moduleDefaults.js' 2 | 3 | const GlobalSection = function ({ UUID, ADMIN_LABEL, VALUE }) { 4 | this.uuid = `${UUID}` 5 | this.type = 'global-section' 6 | this.useSettings = 'section' 7 | this.config = { 8 | enabled: true, 9 | buildamic_settings: { 10 | admin_label: ADMIN_LABEL || this.type, 11 | inline: { ...JSON.parse(JSON.stringify(InlineDefaults)) }, 12 | attributes: { ...JSON.parse(JSON.stringify(AttributeDefaults)) } 13 | } 14 | } 15 | this.value = VALUE 16 | } 17 | 18 | export { GlobalSection } 19 | -------------------------------------------------------------------------------- /resources/js/factories/modules/moduleDefaults.js: -------------------------------------------------------------------------------- 1 | const responsiveSizes = { 2 | 'xs': '', 3 | 'sm': '', 4 | 'md': '', 5 | 'lg': '', 6 | 'xl': '', 7 | } 8 | 9 | export const InlineDefaults = { 10 | background: { 11 | color: '', 12 | gradient: { 13 | value: "" 14 | }, 15 | image: {}, 16 | video: {} 17 | }, 18 | display: { 19 | ...JSON.parse(JSON.stringify(responsiveSizes)) 20 | }, 21 | width: { 22 | ...JSON.parse(JSON.stringify(responsiveSizes)) 23 | }, 24 | height: { 25 | ...JSON.parse(JSON.stringify(responsiveSizes)) 26 | }, 27 | 'text-align': { 28 | ...JSON.parse(JSON.stringify(responsiveSizes)) 29 | }, 30 | 'font-size': { 31 | ...JSON.parse(JSON.stringify(responsiveSizes)) 32 | }, 33 | items: { 34 | ...JSON.parse(JSON.stringify(responsiveSizes)) 35 | }, 36 | justifyContent: { 37 | ...JSON.parse(JSON.stringify(responsiveSizes)) 38 | }, 39 | placeItems: { 40 | ...JSON.parse(JSON.stringify(responsiveSizes)) 41 | }, 42 | gap: { 43 | ...JSON.parse(JSON.stringify(responsiveSizes)) 44 | }, 45 | margin: { 46 | mt: { 47 | ...JSON.parse(JSON.stringify(responsiveSizes)) 48 | }, 49 | mr: { 50 | ...JSON.parse(JSON.stringify(responsiveSizes)) 51 | }, 52 | mb: { 53 | ...JSON.parse(JSON.stringify(responsiveSizes)) 54 | }, 55 | ml: { 56 | ...JSON.parse(JSON.stringify(responsiveSizes)) 57 | } 58 | }, 59 | padding: { 60 | pt: { 61 | ...JSON.parse(JSON.stringify(responsiveSizes)) 62 | }, 63 | pr: { 64 | ...JSON.parse(JSON.stringify(responsiveSizes)) 65 | }, 66 | pb: { 67 | ...JSON.parse(JSON.stringify(responsiveSizes)) 68 | }, 69 | pl: { 70 | ...JSON.parse(JSON.stringify(responsiveSizes)) 71 | } 72 | }, 73 | // Add version number to this to check that modules use the latest version 74 | version: '1.0.0' 75 | } 76 | 77 | export const AttributeDefaults = { 78 | class: '', 79 | id: '', 80 | moduleLink: '', 81 | version: '1.0.0' 82 | } 83 | -------------------------------------------------------------------------------- /resources/js/factories/modules/moduleFactory.js: -------------------------------------------------------------------------------- 1 | import { generateID } from '../../functions/idHelpers' 2 | 3 | import { Section } from './section' 4 | import { GlobalSection } from './global-section' 5 | import { Row } from './row' 6 | import { Column } from './column' 7 | import { Field } from './field' 8 | import { Set } from './set' 9 | 10 | const module = { Section, GlobalSection, Row, Column, Field, Set }; 11 | 12 | const createModule = function (type, attributes) { 13 | const ModuleType = module[type]; 14 | return new ModuleType({ 15 | UUID: generateID(), 16 | ...attributes 17 | }); 18 | } 19 | 20 | export { createModule } 21 | -------------------------------------------------------------------------------- /resources/js/factories/modules/row.js: -------------------------------------------------------------------------------- 1 | import { InlineDefaults, AttributeDefaults } from './moduleDefaults.js' 2 | import { createModule } from "./moduleFactory"; 3 | 4 | const Row = function ({ UUID, ADMIN_LABEL }) { 5 | 6 | // We are including a column by default 7 | const column = createModule('Column'); 8 | 9 | this.uuid = `${UUID}` 10 | this.type = 'row' 11 | this.config = { 12 | enabled: true, 13 | buildamic_settings: { 14 | admin_label: ADMIN_LABEL || this.type, 15 | column_count_total: 12, 16 | inline: { ...JSON.parse(JSON.stringify(InlineDefaults)) }, 17 | attributes: { ...JSON.parse(JSON.stringify(AttributeDefaults)) } 18 | } 19 | } 20 | this.value = [column] 21 | } 22 | 23 | export { Row } 24 | -------------------------------------------------------------------------------- /resources/js/factories/modules/section.js: -------------------------------------------------------------------------------- 1 | import { InlineDefaults, AttributeDefaults } from './moduleDefaults.js' 2 | import { createModule } from "./moduleFactory"; 3 | 4 | const Section = function ({ UUID, ADMIN_LABEL }) { 5 | 6 | // Wea 7 | const row = createModule('Row'); 8 | 9 | this.uuid = `${UUID}` 10 | this.type = 'section' 11 | this.config = { 12 | enabled: true, 13 | buildamic_settings: { 14 | admin_label: ADMIN_LABEL || this.type, 15 | inline: { ...JSON.parse(JSON.stringify(InlineDefaults)) }, 16 | boxed_layout: true, 17 | attributes: { ...JSON.parse(JSON.stringify(AttributeDefaults)) } 18 | } 19 | } 20 | this.value = [row] 21 | } 22 | 23 | export { Section } 24 | -------------------------------------------------------------------------------- /resources/js/factories/modules/set.js: -------------------------------------------------------------------------------- 1 | // import { createModule } from './moduleFactory' 2 | import { InlineDefaults, AttributeDefaults } from './moduleDefaults.js' 3 | 4 | const Set = function ({ UUID, ADMIN_LABEL, VALUE, HANDLE, CONFIG = {}, META = {} }) { 5 | this.uuid = `${UUID}` 6 | this.type = 'set' 7 | this.computed = { 8 | config: { 9 | ...JSON.parse(JSON.stringify(CONFIG)) 10 | }, 11 | meta: { 12 | ...JSON.parse(JSON.stringify(META)) 13 | } 14 | } 15 | this.config = { 16 | statamic_settings: { 17 | field: { 18 | type: CONFIG.type || '', 19 | }, 20 | handle: HANDLE 21 | }, 22 | buildamic_settings: { 23 | enabled: true, 24 | admin_label: ADMIN_LABEL || HANDLE, 25 | inline: { ...JSON.parse(JSON.stringify(InlineDefaults)) }, 26 | attributes: { ...JSON.parse(JSON.stringify(AttributeDefaults)) } 27 | } 28 | 29 | } 30 | this.value = (VALUE).reduce((acc, cur) => { 31 | acc[cur.handle] = cur.value 32 | return acc 33 | }, {}) 34 | } 35 | 36 | export { Set } 37 | -------------------------------------------------------------------------------- /resources/js/field-conditions/Constants.js: -------------------------------------------------------------------------------- 1 | export const KEYS = [ 2 | 'if', 3 | 'if_any', 4 | 'show_when', 5 | 'show_when_any', 6 | 'unless', 7 | 'unless_any', 8 | 'hide_when', 9 | 'hide_when_any' 10 | ]; 11 | 12 | export const OPERATORS = [ 13 | 'equals', 14 | 'not', 15 | 'contains', 16 | 'contains_any', 17 | '===', 18 | '!==', 19 | '>', 20 | '>=', 21 | '<', 22 | '<=', 23 | 'custom', 24 | ]; 25 | 26 | export const ALIASES = { 27 | 'is': 'equals', 28 | '==': 'equals', 29 | 'isnt': 'not', 30 | '!=': 'not', 31 | 'includes': 'contains', 32 | 'includes_any': 'contains_any', 33 | }; 34 | -------------------------------------------------------------------------------- /resources/js/field-conditions/Converter.js: -------------------------------------------------------------------------------- 1 | import { OPERATORS, ALIASES } from './Constants.js'; 2 | 3 | export default class { 4 | 5 | fromBlueprint(conditions, prefix = null) { 6 | return Object.entries(conditions).map(([field, condition]) => this.splitRhs(field, condition, prefix)); 7 | } 8 | 9 | toBlueprint(conditions) { 10 | let converted = {}; 11 | 12 | conditions.forEach(condition => { 13 | converted[condition.field] = this.combineRhs(condition); 14 | }); 15 | 16 | return converted; 17 | } 18 | 19 | splitRhs(field, condition, prefix = null) { 20 | return { 21 | 'field': this.getScopedFieldHandle(field, prefix), 22 | 'operator': this.getOperatorFromRhs(condition), 23 | 'value': this.getValueFromRhs(condition) 24 | }; 25 | } 26 | 27 | getScopedFieldHandle(field, prefix) { 28 | if (field.startsWith('root.') || !prefix) { 29 | return field; 30 | } 31 | 32 | return prefix + field; 33 | } 34 | 35 | getOperatorFromRhs(condition) { 36 | let operator = '=='; 37 | 38 | this.getOperatorsAndAliases() 39 | .filter(value => new RegExp(`^${value} [^=]`).test(condition.toString())) 40 | .forEach(value => { 41 | operator = value 42 | }) 43 | 44 | return this.normalizeOperator(operator); 45 | } 46 | 47 | normalizeOperator(operator) { 48 | return ALIASES[operator] 49 | ? ALIASES[operator] 50 | : operator; 51 | } 52 | 53 | getValueFromRhs(condition) { 54 | let rhs = condition.toString(); 55 | 56 | this.getOperatorsAndAliases() 57 | .filter(value => new RegExp(`^${value} [^=]`).test(rhs)) 58 | .forEach(value => rhs = rhs.replace(new RegExp(`^${value}[ ]*`), '')) 59 | 60 | return rhs; 61 | } 62 | 63 | combineRhs(condition) { 64 | let operator = condition.operator ? condition.operator.trim() : ''; 65 | let value = condition.value.trim(); 66 | 67 | return `${operator} ${value}`.trim(); 68 | } 69 | 70 | getOperatorsAndAliases() { 71 | return OPERATORS.concat(Object.keys(ALIASES)); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /resources/js/field-conditions/FieldConditions.js: -------------------------------------------------------------------------------- 1 | export { default as FieldConditionsBuilder } from './Builder.vue'; 2 | export { default as FieldConditionsConverter } from './Converter.js'; 3 | export { default as FieldConditionsValidator } from './Validator.js'; 4 | export { default as ValidatesFieldConditions } from './ValidatorMixin.js'; 5 | export { KEYS as FIELD_CONDITIONS_KEYS } from './Constants.js'; 6 | export { OPERATORS as FIELD_CONDITIONS_OPERATORS } from './Constants.js'; 7 | export { ALIASES as FIELD_CONDITIONS_ALIASES } from './Constants.js'; 8 | -------------------------------------------------------------------------------- /resources/js/field-conditions/Validator.js: -------------------------------------------------------------------------------- 1 | import Converter from './Converter.js'; 2 | import { KEYS } from './Constants.js'; 3 | 4 | const NUMBER_SPECIFIC_COMPARISONS = [ 5 | '>', '>=', '<', '<=' 6 | ]; 7 | 8 | export default class { 9 | constructor(field, values, store, storeName) { 10 | this.field = field; 11 | this.values = values; 12 | this.store = store; 13 | this.storeName = storeName; 14 | this.passOnAny = false; 15 | this.showOnPass = true; 16 | this.converter = new Converter; 17 | } 18 | 19 | passesConditions() { 20 | let conditions = this.getConditions(); 21 | 22 | if (conditions === undefined) { 23 | return true; 24 | } else if (typeof conditions === 'string') { 25 | return this.passesCustomCondition(this.prepareCondition(conditions)); 26 | } 27 | 28 | conditions = this.converter.fromBlueprint(conditions, this.field.prefix); 29 | 30 | 31 | 32 | let passes = this.passOnAny 33 | ? this.passesAnyConditions(conditions) 34 | : this.passesAllConditions(conditions); 35 | 36 | return this.showOnPass ? passes : !passes; 37 | } 38 | 39 | getConditions() { 40 | let key = KEYS.filter(key => this.field[key])[0] 41 | 42 | if (!key) { 43 | return undefined; 44 | } 45 | 46 | if (key.includes('any')) { 47 | this.passOnAny = true; 48 | } 49 | 50 | if (key.includes('unless') || key.includes('hide_when')) { 51 | this.showOnPass = false; 52 | } 53 | 54 | return this.field[key]; 55 | } 56 | 57 | passesAllConditions(conditions) { 58 | return conditions.map(condition => { 59 | return this.prepareCondition(condition) 60 | }).every(condition => { 61 | return this.passesCondition(condition) 62 | }) 63 | } 64 | 65 | passesAnyConditions(conditions) { 66 | return conditions.map(condition => { 67 | return this.prepareCondition(condition) 68 | }).some(condition => { 69 | return this.passesCondition(condition) 70 | }) 71 | } 72 | 73 | prepareCondition(condition) { 74 | if (typeof condition === 'string' || condition.operator === 'custom') { 75 | return this.prepareCustomCondition(condition); 76 | } 77 | 78 | let operator = this.prepareOperator(condition.operator); 79 | let lhs = this.prepareLhs(condition.field, operator); 80 | let rhs = this.prepareRhs(condition.value, operator); 81 | 82 | return { lhs, operator, rhs }; 83 | } 84 | 85 | prepareOperator(operator) { 86 | switch (operator) { 87 | case null: 88 | case '': 89 | case 'is': 90 | case 'equals': 91 | return '=='; 92 | case 'isnt': 93 | case 'not': 94 | case '¯\\_(ツ)_/¯': 95 | return '!='; 96 | case 'includes': 97 | case 'contains': 98 | return 'includes'; 99 | case 'includes_any': 100 | case 'contains_any': 101 | return 'includes_any'; 102 | } 103 | 104 | return operator; 105 | } 106 | 107 | prepareLhs(field, operator) { 108 | let lhs = this.getFieldValue(field); 109 | 110 | // When performing a number comparison, cast to number. 111 | if (NUMBER_SPECIFIC_COMPARISONS.includes(operator)) { 112 | return Number(lhs); 113 | } 114 | 115 | // When performing lhs.includes(), if lhs is not an object or array, cast to string. 116 | if (operator === 'includes' && typeof lhs !== 'object') { 117 | return lhs ? lhs.toString() : ''; 118 | } 119 | 120 | // When lhs is an empty string, cast to null. 121 | if (typeof lhs === 'string' && !lhs) { 122 | lhs = null; 123 | } 124 | 125 | // Prepare for eval() and return. 126 | return typeof lhs === 'string' 127 | ? JSON.stringify(lhs.trim()) 128 | : lhs; 129 | } 130 | 131 | prepareRhs(rhs, operator) { 132 | // When comparing against null, true, false, cast to literals. 133 | switch (rhs) { 134 | case 'null': 135 | return null; 136 | case 'true': 137 | return true; 138 | case 'false': 139 | return false; 140 | } 141 | 142 | // When performing a number comparison, cast to number. 143 | if (NUMBER_SPECIFIC_COMPARISONS.includes(operator)) { 144 | return Number(rhs); 145 | } 146 | 147 | // When performing a comparison that cannot be eval()'d, return rhs as is. 148 | if (rhs === 'empty' || operator === 'includes' || operator === 'includes_any') { 149 | return rhs; 150 | } 151 | 152 | // Prepare for eval() and return. 153 | return typeof rhs === 'string' 154 | ? JSON.stringify(rhs.trim()) 155 | : rhs; 156 | } 157 | 158 | prepareCustomCondition(condition) { 159 | let functionName = this.prepareFunctionName(condition.value || condition); 160 | let params = this.prepareParams(condition.value || condition); 161 | 162 | let target = condition.field 163 | ? this.getFieldValue(condition.field) 164 | : null; 165 | 166 | return { functionName, params, target }; 167 | } 168 | 169 | prepareFunctionName(condition) { 170 | return condition 171 | .replace(new RegExp('^custom '), '') 172 | .split(':')[0]; 173 | } 174 | 175 | prepareParams(condition) { 176 | let params = condition.split(':')[1]; 177 | 178 | return params 179 | ? params.split(',').map(string => string.trim()) 180 | : []; 181 | } 182 | 183 | getFieldValue(field) { 184 | return field.startsWith('root.') 185 | ? data_get(this.rootValues, field.replace(new RegExp('^root.'), '')) 186 | : data_get(this.values, field); 187 | } 188 | 189 | passesCondition(condition) { 190 | if (condition.functionName) { 191 | return this.passesCustomCondition(condition); 192 | } 193 | 194 | if (condition.operator === 'includes') { 195 | return this.passesIncludesCondition(condition); 196 | } 197 | 198 | if (condition.operator === 'includes_any') { 199 | return this.passesIncludesAnyCondition(condition); 200 | } 201 | 202 | if (condition.rhs === 'empty') { 203 | condition.lhs = !condition.lhs; 204 | condition.rhs = true; 205 | } 206 | 207 | if (typeof condition.lhs === 'obejcjt') { 208 | return false; 209 | } 210 | 211 | return eval(`${condition.lhs} ${condition.operator} ${condition.rhs}`); 212 | } 213 | 214 | passesIncludesCondition(condition) { 215 | return condition.lhs.includes(condition.rhs); 216 | } 217 | 218 | passesIncludesAnyCondition(condition) { 219 | let values = condition.rhs.split(',').map(string => string.trim()); 220 | 221 | if (Array.isArray(condition.lhs)) { 222 | return [...condition.lhs, ...values].length; 223 | } 224 | 225 | return new RegExp(values.join('|')).test(condition.lhs); 226 | } 227 | 228 | passesCustomCondition(condition) { 229 | let customFunction = data_get(this.store.state.statamic.conditions, condition.functionName); 230 | 231 | if (typeof customFunction !== 'function') { 232 | console.error(`Statamic field condition [${condition.functionName}] was not properly defined.`); 233 | return false; 234 | } 235 | 236 | let passes = customFunction({ 237 | params: condition.params, 238 | target: condition.target, 239 | values: this.values, 240 | root: this.rootValues, 241 | store: this.store, 242 | storeName: this.storeName, 243 | }); 244 | 245 | return this.showOnPass ? passes : !passes; 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /resources/js/functions/helpers.js: -------------------------------------------------------------------------------- 1 | const debounce = (fn, delay) => { 2 | var timeoutID = null 3 | return function () { 4 | clearTimeout(timeoutID) 5 | var args = arguments 6 | var that = this 7 | timeoutID = setTimeout(function () { 8 | fn.apply(that, args) 9 | }, delay) 10 | } 11 | } 12 | 13 | const tryParseJSON = (jsonString) => { 14 | if (jsonString) { 15 | try { 16 | var o = JSON.parse(jsonString); 17 | 18 | // Handle non-exception-throwing cases: 19 | // Neither JSON.parse(false) or JSON.parse(1234) throw errors, hence the type-checking, 20 | // but... JSON.parse(null) returns null, and typeof null === "object", 21 | // so we must check for that, too. Thankfully, null is falsey, so this suffices: 22 | if (o && typeof o === "object") { 23 | return o; 24 | } 25 | } 26 | catch (e) { console.log("You didn't paste valid JSON, please re-try copying the section and paste again.") } 27 | 28 | return false; 29 | } 30 | }; 31 | 32 | const UCFirst = (text) => { 33 | return text.charAt(0).toUpperCase() + text.slice(1) 34 | } 35 | 36 | const spaceToDash = (str) => { 37 | str = str.replace(/\s+/g, '-').toLowerCase(); 38 | return str 39 | } 40 | 41 | const copyToClipboard = (text) => { 42 | var dummy = document.createElement("textarea"); 43 | document.body.appendChild(dummy); 44 | text = typeof text !== 'string' ? JSON.stringify(text) : text 45 | dummy.value = text; 46 | dummy.select(); 47 | document.execCommand("copy"); 48 | document.body.removeChild(dummy); 49 | } 50 | 51 | const stripTrailingSlash = (str) => { 52 | return str.endsWith('/') ? 53 | str.slice(0, -1) : 54 | str; 55 | }; 56 | 57 | export { debounce, UCFirst, spaceToDash, copyToClipboard, tryParseJSON, stripTrailingSlash, } 58 | -------------------------------------------------------------------------------- /resources/js/functions/idHelpers.js: -------------------------------------------------------------------------------- 1 | import { v4 as uuidv4 } from "uuid"; 2 | export function generateID() { 3 | return uuidv4() 4 | } 5 | 6 | export const recursifyID = (array) => { 7 | if (array && array.uuid) { 8 | array.uuid = generateID(); 9 | } 10 | if (!Array.isArray(array.value)) { 11 | return false 12 | } 13 | array.value.forEach((el) => { 14 | if (el.uuid || el.uuid === '') { 15 | el.uuid = generateID(); 16 | } 17 | if (!Array.isArray(el.value)) { 18 | return false 19 | } else { 20 | recursifyID(el) 21 | } 22 | }) 23 | } 24 | -------------------------------------------------------------------------------- /resources/js/functions/objectHelpers.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | /** 3 | * Dynamically sets a deeply nested value in an object. 4 | * Optionally "bores" a path to it if its undefined. 5 | * @function 6 | * @param {!object} obj - The object which contains the value you want to change/set. 7 | * @param {!array} path - An array representation of path to the value you want to change/set ['just','like','this'] or object dot notation "just.like.this" 8 | * @param {!mixed} value - The value you want to set it to. 9 | * @param {boolean} force - If true this will create the path/value if it doesn't already exist 10 | */ 11 | const setDeep = (obj, path, value) => { 12 | const force = true, 13 | overwrite = false; 14 | // If it's already an array, good game. Otherwise make it one from the . 15 | !Array.isArray(path) ? path = path.split('.').filter(path => path) : path 16 | return path.reduce((a, b, i) => { 17 | 18 | // Start index at 1 19 | i++ 20 | 21 | // This checks if the current string is actually a number, if so turn it into a proper number 22 | // This makes it array compatible :) 23 | b = isNaN(b) ? b : parseInt(b) 24 | 25 | // If overwrite is true, it will delete whatever was there before, use with caution 26 | if (overwrite && i !== path.length) { 27 | // Set it as a new object reference! 28 | Vue.set(a, b, {}) 29 | 30 | // Accumilate and move onto the next step 31 | return a[b] 32 | } 33 | 34 | // If force is enabled and it comes across an undefined path 35 | if (force && (typeof a[b] === "undefined" || a[b] === null || a[b] === "") && i !== path.length) { 36 | // Set it as a new object reference! 37 | Vue.set(a, b, {}) 38 | 39 | // Accumilate and move onto the next step 40 | return a[b] 41 | } 42 | 43 | // If we are on the last step of reduce the value is set 44 | if (i === path.length) { 45 | // console.log(a, b, value) 46 | Vue.set(a, b, value) 47 | return a[b]; 48 | } else { 49 | // Otherwise accumilate and move onto the next step 50 | return a[b] 51 | } 52 | 53 | }, obj); 54 | } 55 | 56 | const getDeep = (obj, path, defaultVal) => { 57 | path = Array.isArray(path) ? path : path.split('.').filter(path => path) 58 | const data = path.reduce((a, b) => a && a[b], obj) 59 | if (!data) { 60 | return 61 | } 62 | return data; 63 | } 64 | 65 | /** 66 | * Performs a deep merge of objects and returns new object. Does not modify 67 | * objects (immutable) and merges arrays via concatenation. 68 | * 69 | * @param {...object} objects - Objects to merge 70 | * @returns {object} New object with merged key/values 71 | */ 72 | const mergeDeep = (...objects) => { 73 | const isObject = obj => obj && typeof obj === 'object'; 74 | 75 | return objects.reduce((prev, obj) => { 76 | Object.keys(obj).forEach(key => { 77 | const pVal = prev[key]; 78 | const oVal = obj[key]; 79 | 80 | if (Array.isArray(pVal) && Array.isArray(oVal)) { 81 | prev[key] = pVal.concat(...oVal); 82 | } 83 | else if (isObject(pVal) && isObject(oVal)) { 84 | prev[key] = mergeDeep(pVal, oVal); 85 | } 86 | else { 87 | prev[key] = oVal; 88 | } 89 | }); 90 | 91 | return prev; 92 | }, {}); 93 | } 94 | 95 | const sameKeysDeep = (...objects) => { 96 | let union = new Set(); 97 | union = objects.reduce((keys, object) => keys.add(Object.keys(object)), union); 98 | if (union.size === 0) return true 99 | if (!objects.every((object) => union.size === Object.keys(object).length)) return false 100 | 101 | for (let key of union.keys()) { 102 | let res = objects.map((o) => (typeof o[key] === 'object' ? o[key] : {})) 103 | if (!objectsHaveSameKeys(...res)) return false 104 | } 105 | 106 | return true 107 | } 108 | 109 | export { setDeep, getDeep, mergeDeep, sameKeysDeep } 110 | -------------------------------------------------------------------------------- /resources/js/mixins/ClipboardFunctions.js: -------------------------------------------------------------------------------- 1 | 2 | import { mapActions, mapGetters } from "vuex"; 3 | 4 | export default { 5 | computed: { 6 | ...mapGetters(['pasteLocations']), 7 | isValidPasteLocation() { 8 | return this.pasteLocations.includes(this.component.type); 9 | } 10 | }, 11 | methods: { 12 | ...mapActions(["setPasteLocations"]), 13 | checkModuleCompatibility(type) { 14 | if (type === "field" || type === "set") { 15 | type = ["field", "set"] 16 | } else if (type === "section" || type === "global-section") { 17 | type = ["section", "global-section"] 18 | console.log("TYPE YESSS") 19 | } else { 20 | type = [type] 21 | } 22 | console.log({ type }) 23 | return type.includes(this.component.type); 24 | }, 25 | updateClipboard(newClip, context) { 26 | navigator.clipboard.writeText(newClip).then(() => { 27 | this.$toast.success(`${context.type} copied to clipboard, click any pulsing icon to paste it.`); 28 | this.setPasteLocations(context.type); 29 | }, function (err) { 30 | this.$toast.error(err) 31 | }); 32 | }, 33 | copyToClipboard() { 34 | let clipboardModule = JSON.stringify(this.component); 35 | navigator.permissions.query({ name: "clipboard-write" }).then(result => { 36 | if (result.state == "granted" || result.state == "prompt") { 37 | this.updateClipboard(clipboardModule, this.component); 38 | } 39 | }); 40 | }, 41 | pasteFromClipboard() { 42 | navigator.permissions.query({ name: "clipboard-read" }).then(result => { 43 | if (result.state == "granted" || result.state == "prompt") { 44 | navigator.clipboard.readText().then(clipboardText => { 45 | let newModule = JSON.parse(clipboardText); 46 | if (this.checkModuleCompatibility(newModule.type)) { 47 | this.cloneModule(newModule) 48 | this.setPasteLocations(''); 49 | this.emptyClipboard() 50 | } else { 51 | this.$toast.error(`Can only paste ${newModule.type} with other ${newModule.type}s'`); 52 | } 53 | }); 54 | } 55 | }); 56 | }, 57 | tryParseJSON(jsonString) { 58 | if (jsonString) { 59 | try { 60 | var o = JSON.parse(jsonString); 61 | 62 | // Handle non-exception-throwing cases: 63 | // Neither JSON.parse(false) or JSON.parse(1234) throw errors, hence the type-checking, 64 | // but... JSON.parse(null) returns null, and typeof null === "object", 65 | // so we must check for that, too. Thankfully, null is falsey, so this suffices: 66 | if (o && typeof o === "object") { 67 | return o; 68 | } 69 | } 70 | catch (e) { console.log("Clipboard does not contain valid JSON") } 71 | 72 | return false; 73 | } 74 | }, 75 | emptyClipboard() { 76 | navigator.permissions.query({ name: "clipboard-write" }).then(result => { 77 | if (result.state == "granted" || result.state == "prompt") { 78 | navigator.clipboard.writeText("").then(() => { 79 | this.$toast.success("Clipboard cleared"); 80 | }); 81 | } 82 | }); 83 | }, 84 | readFromClipboard() { 85 | navigator.permissions.query({ name: "clipboard-read" }).then(result => { 86 | if (result.state == "granted" || result.state == "prompt") { 87 | navigator.clipboard.readText().then(clipboardText => { 88 | if (!clipboardText) { 89 | this.setPasteLocations('') 90 | return 91 | } 92 | let newModule = this.tryParseJSON(clipboardText); 93 | 94 | if (newModule && newModule.type) { 95 | this.setPasteLocations(newModule.type); 96 | } else { 97 | this.setPasteLocations('') 98 | } 99 | }).catch(err => console.log(err)); 100 | } 101 | }).catch(err => console.log(err)); 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /resources/js/mixins/OptionsFields.js: -------------------------------------------------------------------------------- 1 | import resolveConfig from "tailwindcss/resolveConfig"; 2 | import tailwindConfig from "../../../tailwind.config.js"; 3 | import { InlineDefaults, AttributeDefaults } from '../factories/modules/moduleDefaults'; 4 | const fullConfig = resolveConfig(tailwindConfig); 5 | import { getDeep, setDeep, mergeDeep } from '../functions/objectHelpers' 6 | import { mapGetters, mapActions } from 'vuex' 7 | import { bus } from '../eventBus' 8 | 9 | const removeXSPrefixFromValue = (value) => { 10 | return value.indexOf(':') === -1 ? value : value.split(':')[1]; 11 | } 12 | 13 | export default { 14 | data() { 15 | return { 16 | reactiveTick: 0 17 | } 18 | }, 19 | computed: { 20 | ...mapGetters(["breakpoint", "dirtyFields"]), 21 | settings() { 22 | return this.field.config.buildamic_settings 23 | } 24 | }, 25 | created: function () { 26 | this.setDirtyFields([]); 27 | if (this.field) { 28 | // Backwards Compatibility with older versions of the addon 29 | if (!this.settings?.inline || InlineDefaults.version !== this.settings.inline?.version) { 30 | this.setInlineDefaults(); 31 | } 32 | if (!this.settings?.attributes || AttributeDefaults.version !== this.settings.attributes?.version) { 33 | this.setAttributeDefaults(); 34 | } 35 | } 36 | }, 37 | methods: { 38 | ...mapActions(['setDirtyFields']), 39 | setDeep, 40 | getDeep(e, obj = this.settings) { 41 | const val = getDeep(obj, e); 42 | return val ? val : false 43 | }, 44 | saveFields() { 45 | return true 46 | }, 47 | updateField({ obj = this.settings, path, type = null, key = '', val, vm = this }, responsive = false) { 48 | if (type === 'asset' && !val.length) return 49 | const fullPath = responsive ? `${path}.${this.breakpoint}` : path 50 | 51 | // We have an XS option for responsive sizes but tailwind has no prefix for the smallest size so 52 | // we need to remove xs from the value 53 | if (responsive && typeof val === 'string' && val !== 'none' && val && this.breakpoint === 'xs') { 54 | val = removeXSPrefixFromValue(val); 55 | } 56 | 57 | // When choosing the "none" option in select fields, remove the value 58 | if (val === 'none') { 59 | val = '' 60 | } 61 | 62 | // Get the fields value when first opening the stack and add that 63 | // to the dirty fields array allowing reverting changes if not saved. 64 | const existingIndex = this.dirtyFields.findIndex(el => el.obj.uuid, obj.uuid); 65 | const oldValue = { obj, fullPath, val: (getDeep(obj, fullPath) ?? '') }; 66 | if (existingIndex === -1) { 67 | this.setDirtyFields([...this.dirtyFields, oldValue]); 68 | } 69 | 70 | // Set the value in pagebuilder JSON and validate the field 71 | setDeep(obj, fullPath, val); 72 | this.$nextTick(() => { 73 | bus.$emit('validate-fields', path.split('.').pop()); 74 | }) 75 | 76 | this.reactiveTick++; 77 | }, 78 | updateMeta({ obj = this.settings, path, val }, responsive = false) { 79 | const fullPath = responsive ? `${path}.${this.breakpoint}` : path 80 | if (fullPath) { 81 | setDeep(obj, fullPath, val); 82 | } 83 | }, 84 | getTWClasses(type, prefix) { 85 | const options = Object.entries(fullConfig.theme[type]).reduce( 86 | (acc, [key, val]) => { 87 | if (key.charAt(0) !== '-') { 88 | acc[`${prefix}-${key}`] = `${prefix}-${key}`; 89 | } else { 90 | acc[`-${prefix}${key}`] = `-${prefix}${key}`; 91 | } 92 | return acc; 93 | }, 94 | {} 95 | ); 96 | return Object.assign({ 'none': 'N/A' }, options); 97 | }, 98 | setInlineDefaults() { 99 | this.setDeep(this.settings, 'inline', mergeDeep(JSON.parse(JSON.stringify(InlineDefaults)), (this.settings?.inline || {}))); 100 | }, 101 | setAttributeDefaults() { 102 | this.setDeep(this.settings, 'attributes', mergeDeep(JSON.parse(JSON.stringify(AttributeDefaults)), (this.settings?.attributes || {}))); 103 | } 104 | }, 105 | 106 | } 107 | -------------------------------------------------------------------------------- /resources/js/mixins/SectionControls.js: -------------------------------------------------------------------------------- 1 | import { createModule } from "../factories/modules/moduleFactory"; 2 | 3 | export default { 4 | data: function () { 5 | return { 6 | customSettings: { 7 | globals: { 8 | icon: "globe", 9 | title: "Add Global Module", 10 | action: () => this.addGlobal(), 11 | order: 30, 12 | }, 13 | }, 14 | } 15 | }, 16 | methods: { 17 | addRow() { 18 | const newModule = createModule("Row"); 19 | this.rows.push(newModule); 20 | }, 21 | addGlobal() { 22 | const newModule = createModule("GlobalSection"); 23 | this.sections.splice(this.sectionIndex + 1, 0, newModule); 24 | }, 25 | }, 26 | } 27 | -------------------------------------------------------------------------------- /resources/js/services/statamic_api.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | 3 | const tokenEl = document.querySelector('meta[name="csrf-token"]') 4 | const TOKEN = tokenEl ? tokenEl.getAttribute('content') : null 5 | 6 | const api = axios.create({ 7 | baseURL: '/api', 8 | headers: { 9 | 'Content-Type': 'application/json', 10 | 'X-Requested-With': 'XMLHttpRequest', 11 | 'X-CSRF-TOKEN': TOKEN || '' 12 | } 13 | }) 14 | 15 | const baseAPI = { 16 | get: async (endpoint) => { 17 | let res; 18 | 19 | try { 20 | let req = await api.get(endpoint) 21 | res = await req.data 22 | } catch (err) { 23 | if (err.request) { 24 | let errors = JSON.parse(err.request.response) 25 | res = errors 26 | } 27 | } 28 | return res 29 | } 30 | } 31 | 32 | export { baseAPI } 33 | -------------------------------------------------------------------------------- /resources/js/store/index.js: -------------------------------------------------------------------------------- 1 | export const buildamicStore = { 2 | state: () => ({ 3 | breakpoint: 'xs', 4 | pasteLocations: [], 5 | globals: [], 6 | fieldDefaults: {}, 7 | dirtyFields: [], 8 | errorBag: [], 9 | }), 10 | mutations: { 11 | SWITCH_BREAKPOINT(state, payload) { 12 | state.breakpoint = payload 13 | }, 14 | SET_FIELD_DEFAULTS(state, payload) { 15 | state.fieldDefaults = payload 16 | }, 17 | SET_GLOBALS(state, payload) { 18 | state.globals.push(...payload) 19 | }, 20 | SET_PASTE_LOCATIONS(state, payload) { 21 | state.pasteLocations = [...payload] 22 | }, 23 | SET_DIRTY_FIELDS(state, payload) { 24 | state.dirtyFields = payload 25 | }, 26 | SET_ERROR_BAG(state, payload) { 27 | state.errorBag = payload 28 | } 29 | }, 30 | actions: { 31 | switchBreakpoint({ commit }, payload) { 32 | if (payload) { 33 | commit('SWITCH_BREAKPOINT', payload) 34 | } 35 | }, 36 | setFieldDefaults({ commit }, payload) { 37 | commit('SET_FIELD_DEFAULTS', payload) 38 | }, 39 | setDirtyFields({ commit }, payload) { 40 | commit('SET_DIRTY_FIELDS', payload) 41 | }, 42 | setErrorBag({ commit }, payload) { 43 | commit('SET_ERROR_BAG', payload) 44 | }, 45 | setPasteLocations({ commit }, payload) { 46 | if (payload.includes('field') || payload.includes('set')) { 47 | payload = ['field', 'set'] 48 | } else if (payload.includes('section') || payload.includes('global-section')) { 49 | payload = ['section', 'global-section'] 50 | } else[ 51 | payload = [payload] 52 | ] 53 | 54 | commit('SET_PASTE_LOCATIONS', payload) 55 | }, 56 | setGlobals({ commit }, payload) { 57 | commit('SET_GLOBALS', payload) 58 | } 59 | }, 60 | getters: { 61 | breakpoint: state => state.breakpoint, 62 | fieldDefaults: state => state.fieldDefaults, 63 | globals: state => state.globals, 64 | pasteLocations: state => state.pasteLocations, 65 | dirtyFields: state => state.dirtyFields, 66 | errorBag: state => state.errorBag 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /resources/sass/_tailwindbase.scss: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | -------------------------------------------------------------------------------- /resources/sass/_tailwindcomponents.scss: -------------------------------------------------------------------------------- 1 | @tailwind components; 2 | -------------------------------------------------------------------------------- /resources/sass/_tailwindutilities.scss: -------------------------------------------------------------------------------- 1 | @tailwind utilities; 2 | -------------------------------------------------------------------------------- /resources/sass/buildamic.scss: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | /*-------------------------------------------------------------- 6 | # Functions, Variables, Mixins: (these should always come first) 7 | --------------------------------------------------------------*/ 8 | @import "mixins/mixins-master"; 9 | @import "vendor/rfs"; 10 | 11 | /*-------------------------------------------------------------- 12 | # Buildy: (All buildy defaults) 13 | --------------------------------------------------------------*/ 14 | @import "buildy/buildy"; 15 | 16 | :root { 17 | --default-spacer: 1rem; 18 | } 19 | 20 | [class*="buildamic"] { 21 | // Fix instruction label spacing 22 | .help-block { 23 | margin-top: 0.1rem !important; 24 | } 25 | 26 | .replicator-fieldtype { 27 | background: none !important; 28 | border: none !important; 29 | padding: 0; 30 | .field-inner, 31 | .replicator-fieldtype-container { 32 | padding-left: 0; 33 | margin-left: 0; 34 | } 35 | .replicator-set { 36 | margin-bottom: var(--default-spacer); 37 | } 38 | } 39 | 40 | .sortable-handle { 41 | width: 1rem; 42 | padding: 8px; 43 | cursor: -webkit-grab; 44 | cursor: grab; 45 | border: 1px solid rgba(0, 0, 0, 0.06); 46 | background-color: rgba(0, 0, 0, 0.03); 47 | background-image: url("/vendor/buildamic/img/drag-dots.svg"); 48 | background-position: 50% 50%; 49 | background-repeat: no-repeat; 50 | } 51 | 52 | @include media-breakpoint-up(md) { 53 | .form-group { 54 | padding: var(--default-spacer); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /resources/sass/buildy/_buildy.scss: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------ 2 | ---------------------- Individual Components --------- 3 | --------------------------------------------------------------*/ 4 | @import "grid"; 5 | @import "components/background-videos"; 6 | -------------------------------------------------------------------------------- /resources/sass/buildy/_grid.scss: -------------------------------------------------------------------------------- 1 | .container { 2 | width: 90%; 3 | margin-right: auto; 4 | margin-left: auto; 5 | padding-left: 2rem; 6 | padding-right: 2rem; 7 | @include media-breakpoint-up(lg) { 8 | padding-left: 0; 9 | padding-right: 0; 10 | } 11 | @include media-breakpoint-up(xl) { 12 | max-width: $containerWidth; 13 | } 14 | } 15 | 16 | @media (min-width: 2000px) { 17 | .container { 18 | max-width: $containerWidth; 19 | } 20 | } 21 | .buildamic-section { 22 | padding: 1rem 0; 23 | position: relative; 24 | @include media-breakpoint-up(md) { 25 | padding: 3rem 0; 26 | } 27 | } 28 | .buildamic-row { 29 | padding: 1rem 0; 30 | display: flex; 31 | flex-direction: column; 32 | gap: var(--col-gap, 3rem); 33 | @include media-breakpoint-up(md) { 34 | display: grid; 35 | grid-template-columns: repeat(var(--b-columns, 12), 1fr); 36 | grid-template-rows: repeat(var(--b-rows, 1), 1fr); 37 | padding: 2rem 0; 38 | gap: var(--gap-x-md, var(--col-gap)); 39 | } 40 | @include media-breakpoint-up(lg) { 41 | gap: var(--gap-x-lg, var(--gap-x-md, var(--col-gap))); 42 | } 43 | @include media-breakpoint-up(xl) { 44 | gap: var( 45 | --gap-x-xl, 46 | var(--gap-x-lg, var(--gap-x-md, var(--col-gap))) 47 | ); 48 | } 49 | } 50 | 51 | .buildamic-field, 52 | .buildamic-set { 53 | padding: 1rem 0; 54 | &:first-of-type { 55 | padding-top: 0; 56 | } 57 | &:last-of-type { 58 | padding-bottom: 0; 59 | } 60 | } 61 | 62 | .buildamic-field p:last-child { 63 | margin-bottom: 0; 64 | } 65 | 66 | .buildamic-column { 67 | @include media-breakpoint-up(md) { 68 | padding: 0; 69 | } 70 | } 71 | 72 | @each $name, $breakpoint in $grid-breakpoints { 73 | // Don't generate XS, instead have no prefix eg .mb-2 74 | @if $name == xs { 75 | @for $i from 0 through 12 { 76 | .col-#{$i} { 77 | grid-column: auto/span $i; 78 | } 79 | } 80 | } @else { 81 | @include media-breakpoint-up($name) { 82 | @for $i from 0 through 12 { 83 | .#{$name}\:col-#{$i} { 84 | grid-column: auto/span $i; 85 | } 86 | } 87 | } 88 | } 89 | } 90 | 91 | .grid { 92 | gap: var(--col-gap, 1rem); 93 | } 94 | -------------------------------------------------------------------------------- /resources/sass/buildy/components/_accordions.scss: -------------------------------------------------------------------------------- 1 | $spacing: 24px; 2 | $plus-size: 16px; 3 | $plus-thickness: 2px; 4 | $speed: 300ms; 5 | $easing: ease-in-out; 6 | $gray-dark: #546e7a; 7 | $gray: #cfd8dc; 8 | $gray-light: #eceff1; 9 | 10 | .bmcb-accordion-module { 11 | border-radius: 5px; 12 | background-color: white; 13 | } 14 | 15 | .accordion { 16 | overflow: hidden; 17 | transition: height $speed $easing; 18 | color: inherit; 19 | margin-bottom: 2rem; 20 | 21 | &-title, 22 | &-body { 23 | padding: $spacing; 24 | } 25 | 26 | &-title { 27 | display: flex; 28 | padding: $spacing/2 $spacing; 29 | align-items: center; 30 | list-style: none; // Hide the marker (proper method) 31 | border-radius: $global_radius; 32 | background: $color__quaternary; 33 | color: $color__white; 34 | outline: 0; 35 | font-family: var(--font-headings); 36 | cursor: pointer; 37 | transition: all $speed $easing; 38 | 39 | // Hide the marker in Webkit 40 | &::-webkit-details-marker { 41 | display: none; 42 | } 43 | 44 | // Our replacement marker 45 | &:before, 46 | &:after { 47 | content: ""; 48 | transition: transform 0.2s; 49 | } 50 | 51 | &:after { 52 | display: block; 53 | content: "\f067"; 54 | font-family: "Font Awesome 5 Free"; 55 | color: inherit; 56 | position: unset; 57 | @include fontSize(30); 58 | font-weight: 900; 59 | margin-left: auto; 60 | } 61 | 62 | [open] > & { 63 | border-radius: $global_radius $global_radius 0 0; 64 | } 65 | 66 | [open] &::after { 67 | display: block; 68 | transform: rotate(-45deg) !important; 69 | } 70 | } 71 | 72 | &-body { 73 | padding-left: $spacing * 1.5; 74 | color: $color__text-main; 75 | background: #eaeaea; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /resources/sass/buildy/components/_background-videos.scss: -------------------------------------------------------------------------------- 1 | .buildamic-bg-video { 2 | aspect-ratio: 16/9; 3 | position: absolute; 4 | top: 0; 5 | left: 0; 6 | width: 100%; 7 | height: 100%; 8 | z-index: -1; 9 | object-fit: cover; 10 | } 11 | -------------------------------------------------------------------------------- /resources/sass/buildy/components/_blurbs.scss: -------------------------------------------------------------------------------- 1 | /*------------------------------------------------------------ 2 | ---------------------- Blurb Styles -------------------- 3 | --------------------------------------------------------------*/ 4 | .bmcb-blurb { 5 | &__image-wrapper { 6 | display: flex; 7 | margin-bottom: 2rem; 8 | } 9 | 10 | &__title { 11 | margin-bottom: 1rem; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /resources/sass/buildy/components/_gallery.scss: -------------------------------------------------------------------------------- 1 | @import "~baguettebox.js/dist/baguetteBox.min.css"; 2 | 3 | .bmcb-gallery { 4 | &.bmcb-slider { 5 | .bmcb-gallery__items { 6 | position: relative; 7 | } 8 | } 9 | &__item { 10 | width: fit-content; 11 | } 12 | img { 13 | width: 100%; 14 | height: fit-content; 15 | // height: 200px; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /resources/sass/buildy/components/_image-module.scss: -------------------------------------------------------------------------------- 1 | .bmcb-image-module { 2 | img { 3 | width: 100%; 4 | object-fit: cover; 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /resources/sass/buildy/components/_page-links.scss: -------------------------------------------------------------------------------- 1 | // From the buildy feature internal_link 2 | .page-links-box { 3 | border: 3px solid $color__primary; 4 | border-radius: 2rem; 5 | padding: 3rem; 6 | h3 { 7 | font-family: var(--font-headings); 8 | color: $color__primary; 9 | } 10 | a { 11 | text-decoration: underline; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /resources/sass/buildy/components/_sliders.scss: -------------------------------------------------------------------------------- 1 | .bmcb-slider { 2 | margin-bottom: 6rem; 3 | position: relative; 4 | &__slide { 5 | position: relative; 6 | } 7 | &__slide-content { 8 | width: 100%; 9 | padding: 2rem; 10 | background: rgba(black, 0.6); 11 | color: var(--color-white); 12 | left: 0; 13 | bottom: 0; 14 | @include media-breakpoint-up(lg) { 15 | position: absolute; 16 | } 17 | } 18 | &__slide-title { 19 | font-size: 2em; 20 | margin-bottom: 0.2em; 21 | } 22 | &__slide-body { 23 | &:last-child { 24 | margin: 0; 25 | } 26 | } 27 | &__slide-image { 28 | display: block; 29 | } 30 | &__navigation-arrows { 31 | position: absolute; 32 | top: 0; 33 | left: 0; 34 | width: 100%; 35 | height: 100%; 36 | .bmcb-slider__arrow-prev, 37 | .bmcb-slider__arrow-next { 38 | position: absolute; 39 | top: 0; 40 | bottom: 0; 41 | margin: auto; 42 | display: flex; 43 | align-items: center; 44 | transition: transform 0.2s ease-out; 45 | i { 46 | padding: 3rem 1.6rem; 47 | background: var(--color-white); 48 | color: var(--color-black); 49 | display: block; 50 | cursor: pointer; 51 | @include media-breakpoint-up(xl) { 52 | padding: 0; 53 | width: 4rem; 54 | height: 4rem; 55 | display: flex; 56 | align-items: center; 57 | justify-content: center; 58 | border-radius: 50%; 59 | } 60 | } 61 | } 62 | .bmcb-slider__arrow-next { 63 | right: 0; 64 | } 65 | @include media-breakpoint-up(xl) { 66 | .bmcb-slider__arrow-prev { 67 | transform: translateX(-100%); 68 | } 69 | &:hover .bmcb-slider__arrow-prev { 70 | transform: translateX(30%); 71 | } 72 | .bmcb-slider__arrow-next { 73 | transform: translateX(100%); 74 | } 75 | &:hover .bmcb-slider__arrow-next { 76 | transform: translateX(-30%); 77 | } 78 | } 79 | } 80 | &__navigation-dots { 81 | display: flex; 82 | justify-content: center; 83 | position: absolute; 84 | cursor: pointer; 85 | padding: 2rem; 86 | z-index: 100; 87 | margin: 0; 88 | right: 0; 89 | top: 100%; 90 | width: 100%; 91 | @include media-breakpoint-up(lg) { 92 | margin-bottom: -2.7rem; 93 | } 94 | li { 95 | margin: 0 0.5rem; 96 | padding: 0; 97 | width: 1.6rem; 98 | height: 1.6rem; 99 | border-radius: 50%; 100 | display: block; 101 | background: #b6b6b6; 102 | &.is-active { 103 | background: var(--color-primary); 104 | } 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /resources/sass/mixins/_breakpoints.scss: -------------------------------------------------------------------------------- 1 | $containerWidth: 1280px; 2 | 3 | $grid-breakpoints: ( 4 | xs: 0, 5 | sm: 640px, 6 | md: 768px, 7 | lg: 1024px, 8 | xl: $containerWidth, 9 | xxl: 1536px, 10 | ) !default; 11 | 12 | // Breakpoint viewport sizes and media queries. 13 | // 14 | // Breakpoints are defined as a map of (name: minimum width), order from small to large: 15 | // 16 | // (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px) 17 | // 18 | // The map defined in the `$grid-breakpoints` global variable is used as the `$breakpoints` argument by default. 19 | 20 | // Name of the next breakpoint, or null for the last breakpoint. 21 | // 22 | // >> breakpoint-next(sm) 23 | // md 24 | // >> breakpoint-next(sm, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px)) 25 | // md 26 | // >> breakpoint-next(sm, $breakpoint-names: (xs sm md lg xl)) 27 | // md 28 | @function breakpoint-next( 29 | $name, 30 | $breakpoints: $grid-breakpoints, 31 | $breakpoint-names: map-keys($breakpoints) 32 | ) { 33 | $n: index($breakpoint-names, $name); 34 | @if not $n { 35 | @error "breakpoint `#{$name}` not found in `#{$breakpoints}`"; 36 | } 37 | @return if( 38 | $n < length($breakpoint-names), 39 | nth($breakpoint-names, $n + 1), 40 | null 41 | ); 42 | } 43 | 44 | // Minimum breakpoint width. Null for the smallest (first) breakpoint. 45 | // 46 | // >> breakpoint-min(sm, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px)) 47 | // 576px 48 | @function breakpoint-min($name, $breakpoints: $grid-breakpoints) { 49 | $min: map-get($breakpoints, $name); 50 | @if $name and not $min { 51 | $min: $name; 52 | } 53 | @return if($min != 0, $min, null); 54 | } 55 | 56 | // Maximum breakpoint width. 57 | // The maximum value is reduced by 0.02px to work around the limitations of 58 | // `min-` and `max-` prefixes and viewports with fractional widths. 59 | // See https://www.w3.org/TR/mediaqueries-4/#mq-min-max 60 | // Uses 0.02px rather than 0.01px to work around a current rounding bug in Safari. 61 | // See https://bugs.webkit.org/show_bug.cgi?id=178261 62 | // 63 | // >> breakpoint-max(md, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px)) 64 | // 767.98px 65 | @function breakpoint-max($name, $breakpoints: $grid-breakpoints) { 66 | $max: map-get($breakpoints, $name); 67 | @if $name and not $max { 68 | $max: $name; 69 | } 70 | @return if($max and $max > 0, $max - 0.02, null); 71 | } 72 | 73 | // Returns a blank string if smallest breakpoint, otherwise returns the name with a dash in front. 74 | // Useful for making responsive utilities. 75 | // 76 | // >> breakpoint-infix(xs, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px)) 77 | // "" (Returns a blank string) 78 | // >> breakpoint-infix(sm, (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px)) 79 | // "-sm" 80 | @function breakpoint-infix($name, $breakpoints: $grid-breakpoints) { 81 | @return if(breakpoint-min($name, $breakpoints) == null, "", "#{$name}\\:"); 82 | } 83 | 84 | // Media of at least the minimum breakpoint width. No query for the smallest breakpoint. 85 | // Makes the @content apply to the given breakpoint and wider. 86 | @mixin media-breakpoint-up($name, $breakpoints: $grid-breakpoints) { 87 | $min: breakpoint-min($name, $breakpoints); 88 | @if $min { 89 | @media (min-width: $min) { 90 | @content; 91 | } 92 | } @else { 93 | @content; 94 | } 95 | } 96 | 97 | // Media of at most the maximum breakpoint width. No query for the largest breakpoint. 98 | // Makes the @content apply to the given breakpoint and narrower. 99 | @mixin media-breakpoint-down($name, $breakpoints: $grid-breakpoints) { 100 | $max: breakpoint-max($name, $breakpoints); 101 | @if $max { 102 | @media (max-width: $max) { 103 | @content; 104 | } 105 | } @else { 106 | @content; 107 | } 108 | } 109 | 110 | // Media that spans multiple breakpoint widths. 111 | // Makes the @content apply between the min and max breakpoints 112 | @mixin media-breakpoint-between( 113 | $lower, 114 | $upper, 115 | $breakpoints: $grid-breakpoints 116 | ) { 117 | $min: breakpoint-min($lower, $breakpoints); 118 | $max: breakpoint-max($upper, $breakpoints); 119 | 120 | @if $min != null and $max != null { 121 | @media (min-width: $min) and (max-width: $max) { 122 | @content; 123 | } 124 | } @else if $max == null { 125 | @include media-breakpoint-up($lower, $breakpoints) { 126 | @content; 127 | } 128 | } @else if $min == null { 129 | @include media-breakpoint-down($upper, $breakpoints) { 130 | @content; 131 | } 132 | } 133 | } 134 | 135 | // Media between the breakpoint's minimum and maximum widths. 136 | // No minimum for the smallest breakpoint, and no maximum for the largest one. 137 | // Makes the @content apply only to the given breakpoint, not viewports any wider or narrower. 138 | @mixin media-breakpoint-only($name, $breakpoints: $grid-breakpoints) { 139 | $min: breakpoint-min($name, $breakpoints); 140 | $max: breakpoint-max(breakpoint-next($name, $breakpoints)); 141 | 142 | @if $min != null and $max != null { 143 | @media (min-width: $min) and (max-width: $max) { 144 | @content; 145 | } 146 | } @else if $max == null { 147 | @include media-breakpoint-up($name, $breakpoints) { 148 | @content; 149 | } 150 | } @else if $min == null { 151 | @include media-breakpoint-down($name, $breakpoints) { 152 | @content; 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /resources/sass/mixins/_mixins-master.scss: -------------------------------------------------------------------------------- 1 | @use "sass:math"; 2 | // Button sizes 3 | @mixin button-size($padding-y, $padding-x, $font-size) { 4 | padding: $padding-y $padding-x; 5 | font-size: $font-size; 6 | &:hover { 7 | padding: $padding-y $padding-x; 8 | } 9 | } 10 | 11 | @mixin fontSize($size, $min: 14) { 12 | $smallSize: $min; 13 | 14 | // If a size less than default min is passed, use that as the new min 15 | @if $size < $min { 16 | $smallSize: $size; 17 | } @else { 18 | // Oterhwise calculate a 40% reduction (typically a good size down to mobile). 19 | $smallSize: ($size - ($size * 0.4)); 20 | 21 | // If it's smaller than either 14px or the min we passed in explicitly, use min 22 | @if $smallSize < $min { 23 | $smallSize: $min; 24 | } 25 | } 26 | 27 | // Fallback 28 | font-size: ($size * 1px); 29 | 30 | $clampMin: math.div($smallSize, 10) * 1rem; 31 | $clampMed: math.div($size, 10) * 1vw; 32 | $clampMax: math.div($size, 10) * 1rem; 33 | 34 | font-size: clamp(#{$clampMin}, #{$clampMed}, #{$clampMax}); 35 | } 36 | 37 | // Clearfix 38 | @mixin clearfix() { 39 | content: ""; 40 | display: table; 41 | table-layout: fixed; 42 | } 43 | 44 | // Clear after (not all clearfix need this also) 45 | @mixin clearfix-after() { 46 | clear: both; 47 | } 48 | 49 | // Column width with margin 50 | @mixin column-width($numberColumns: 3) { 51 | width: map-get($columns, $numberColumns) - 52 | (($columns__margin * ($numberColumns - 1)) / $numberColumns); 53 | } 54 | 55 | // Equal-Width grid-column generator 56 | @mixin grid-cols($numberColumns: 6) { 57 | grid-template-columns: repeat($numberColumns, 1fr); 58 | } 59 | 60 | @import "breakpoints"; 61 | @import "utilities"; 62 | -------------------------------------------------------------------------------- /resources/sass/mixins/_utilities.scss: -------------------------------------------------------------------------------- 1 | // Utility generator 2 | // Used to generate utilities & print utilities 3 | @mixin generate-utility($utility, $infix, $is-rfs-media-query: false) { 4 | $values: map-get($utility, values); 5 | 6 | // If the values are a list or string, convert it into a map 7 | @if type-of($values) == "string" or type-of(nth($values, 1)) != "list" { 8 | $values: zip($values, $values); 9 | } 10 | 11 | @each $key, $value in $values { 12 | $properties: map-get($utility, property); 13 | 14 | // Multiple properties are possible, for example with vertical or horizontal margins or paddings 15 | @if type-of($properties) == "string" { 16 | $properties: append((), $properties); 17 | } 18 | 19 | // Use custom class if present 20 | $property-class: if( 21 | map-has-key($utility, class), 22 | map-get($utility, class), 23 | nth($properties, 1) 24 | ); 25 | $property-class: if($property-class == null, "", $property-class); 26 | 27 | $infix: if( 28 | $property-class == "" and str-slice($infix, 1, 1) == "-", 29 | str-slice($infix, 2), 30 | $infix 31 | ); 32 | 33 | // Don't prefix if value key is null (eg. with shadow class) 34 | $property-class-modifier: if( 35 | $key, 36 | if($property-class == "" and $infix == "", "", "-") + $key, 37 | "" 38 | ); 39 | 40 | @if map-get($utility, rfs) { 41 | // Inside the media query 42 | @if $is-rfs-media-query { 43 | $val: rfs-value($value); 44 | 45 | // Do not render anything if fluid and non fluid values are the same 46 | $value: if($val == rfs-fluid-value($value), null, $val); 47 | } @else { 48 | $value: rfs-fluid-value($value); 49 | } 50 | } 51 | 52 | @if $infix == "xs:/" { 53 | $infix: ""; 54 | } 55 | 56 | @if $value != null { 57 | .#{$infix + $property-class + $property-class-modifier} { 58 | @each $property in $properties { 59 | #{$property}: $value 60 | if(map-has-key($utility, important), !important, null); 61 | } 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /resources/views/default-field.blade.php: -------------------------------------------------------------------------------- 1 | @extends('buildamic::layouts.field') 2 | 3 | @section('field_content') 4 | @if(config('app.debug')) 5 | 6 | 7 | 8 | @endif 9 | 10 | 11 | 12 | 13 | 14 | @if(config('app.debug')) 15 |
16 |

This {{ $field->type() }}::{{ $field->handle() }} field could not be rendered, corresponding views not found. 17 | Create one of the following options in your `resources/views/vendor/buildamic/` folder:

18 | 22 |
23 | @endif 24 | @overwrite 25 | -------------------------------------------------------------------------------- /resources/views/fields/bard.blade.php: -------------------------------------------------------------------------------- 1 | @extends('buildamic::layouts.field') 2 | 3 | @section('field_content') 4 | @php 5 | $bardValue = $field->value()->value(); 6 | @endphp 7 | 8 | @if(is_array($bardValue)) 9 | @foreach($bardValue as $bardItem) 10 | @if(isset($bardItem['text'])) 11 | {!! $bardItem['text'] !!} 12 | @else 13 | {{-- @dd($bardItem) --}} 14 | @endif 15 | @endforeach 16 | @else 17 | {!! $bardValue !!} 18 | @endif 19 | @overwrite 20 | -------------------------------------------------------------------------------- /resources/views/fields/code.blade.php: -------------------------------------------------------------------------------- 1 | @extends('buildamic::layouts.field') 2 | 3 | @section('field_content') 4 | @php 5 | $fieldValue = $field->value(); 6 | @endphp 7 | 8 | {!! $fieldValue->shouldParseAntlers() ? \Statamic\Facades\Antlers::parse($fieldValue, collect(get_defined_vars())->except('__data', '__path')->toArray()) : $fieldValue !!} 9 | @overwrite 10 | -------------------------------------------------------------------------------- /resources/views/fields/collections.blade.php: -------------------------------------------------------------------------------- 1 | @extends('buildamic::layouts.field') 2 | 3 | @section('field_content') 4 | @php 5 | $collections = $field->value()->value(); 6 | @endphp 7 | 8 | @if($collections->isNotEmpty()) 9 |
10 | @if($entries = \Statamic\Facades\Entry::query()->where('collection', $collections->first()->id())->get()) 11 | @foreach($entries as $entry) 12 |
13 |

{{ $entry->augmentedValue('title') }}

14 |
15 | @endforeach 16 | @else 17 |

There are no results

18 | @endif 19 |
20 | @endif 21 | @overwrite 22 | -------------------------------------------------------------------------------- /resources/views/fields/html.blade.php: -------------------------------------------------------------------------------- 1 | @extends('buildamic::layouts.field') 2 | 3 | @section('field_content') 4 | @php 5 | $fieldValue = $field->value(); 6 | @endphp 7 | 8 | {!! $fieldValue->shouldParseAntlers() ? \Statamic\Facades\Antlers::parse($fieldValue, collect(get_defined_vars())->except('__data', '__path')->toArray()) : $fieldValue !!} 9 | @overwrite 10 | -------------------------------------------------------------------------------- /resources/views/fields/markdown.blade.php: -------------------------------------------------------------------------------- 1 | @extends('buildamic::layouts.field') 2 | 3 | @section('field_content') 4 | @php 5 | $fieldValue = $field->value(); 6 | @endphp 7 | 8 | {!! $fieldValue->shouldParseAntlers() ? \Statamic\Facades\Antlers::parse($fieldValue, collect(get_defined_vars())->except('__data', '__path')->toArray()) : $fieldValue !!} 9 | @overwrite 10 | -------------------------------------------------------------------------------- /resources/views/fields/terms.blade.php: -------------------------------------------------------------------------------- 1 | @extends('buildamic::layouts.field') 2 | 3 | @section('field_content') 4 | @php 5 | $terms = $field->value()->value(); 6 | @endphp 7 | 8 | @if($terms->isNotEmpty()) 9 | 14 | @endif 15 | @overwrite 16 | -------------------------------------------------------------------------------- /resources/views/fields/text.blade.php: -------------------------------------------------------------------------------- 1 | @extends('buildamic::layouts.field') 2 | 3 | @section('field_content') 4 | @php 5 | $fieldValue = $field->value(); 6 | @endphp 7 | 8 | @if($field->get('input_type') === 'text') 9 | {!! $fieldValue->shouldParseAntlers() ? \Statamic\Facades\Antlers::parse($fieldValue, collect(get_defined_vars())->except('__data', '__path')->toArray()) : $fieldValue !!} 10 | @elseif($field->get('input_type') === 'email') 11 | {{ $fieldValue }} 12 | @elseif($field->get('input_type') === 'tel') 13 | {{ $fieldValue }} 14 | @elseif($field->get('input_type') === 'url') 15 | {{ $fieldValue }} 16 | @else 17 | {!! $fieldValue->shouldParseAntlers() ? \Statamic\Facades\Antlers::parse($fieldValue, collect(get_defined_vars())->except('__data', '__path')->toArray()) : $fieldValue !!} 18 | @endif 19 | @overwrite 20 | -------------------------------------------------------------------------------- /resources/views/fields/textarea.blade.php: -------------------------------------------------------------------------------- 1 | @extends('buildamic::layouts.field') 2 | 3 | @section('field_content') 4 | @php 5 | $fieldValue = $field->value(); 6 | @endphp 7 | 8 | {!! $fieldValue->shouldParseAntlers() ? \Statamic\Facades\Antlers::parse($fieldValue, collect(get_defined_vars())->except('__data', '__path')->toArray()) : $fieldValue !!} 9 | @overwrite 10 | -------------------------------------------------------------------------------- /resources/views/layouts/column.blade.php: -------------------------------------------------------------------------------- 1 |
HtmlId($column->buildamicSetting('attributes.id')) !!} class="buildamic-column {{ $column->computedAttribute('class') }}"> 2 | @foreach($column->value() as $field) 3 | {!! $buildamic->renderField($field) !!} 4 | @endforeach 5 |
6 | -------------------------------------------------------------------------------- /resources/views/layouts/container.blade.php: -------------------------------------------------------------------------------- 1 | 2 |
HtmlId($buildamic->containerId()) !!} class="buildamic-container {{ $buildamic->containerClass() }}"> 3 | @foreach($sections as $section) 4 | @if($section->type() === 'buildamic-section') 5 | {!! $buildamic->renderSection($section) !!} 6 | @elseif($section->type() === 'buildamic-global-section') 7 | {!! $buildamic->renderGlobalSection($section) !!} 8 | @endif 9 | @endforeach 10 |
11 | -------------------------------------------------------------------------------- /resources/views/layouts/field.blade.php: -------------------------------------------------------------------------------- 1 | @php 2 | $background_image = $field->computedAttribute('background_image') ?? null; 3 | @endphp 4 | 5 |
buildamicSetting('moduleLink')) onclick="location.href='{{ $moduleLink }}';" @endif {!! BuildamicHelper()->HtmlId($field->buildamicSetting('attributes.id')) !!} {{ $field->computedAttribute('dataAtts') }} class="buildamic-field @isset($class) {{ $class }} @endisset @if($moduleLink) cursor-pointer @endif {{ $field->computedAttribute('class') }}"> 6 | @yield('field_content') 7 |
8 | -------------------------------------------------------------------------------- /resources/views/layouts/fieldset.blade.php: -------------------------------------------------------------------------------- 1 |
HtmlId($fieldset->buildamicSetting('attributes.id')) !!} {{ $fieldset->computedAttribute('dataAtts') }} class="buildamic-fieldset {{ $fieldset->computedAttribute('class') }}"> 2 | @yield('fieldset_content') 3 |
4 | -------------------------------------------------------------------------------- /resources/views/layouts/row.blade.php: -------------------------------------------------------------------------------- 1 |
HtmlId($row->buildamicSetting('attributes.id')) !!} {{ $row->computedAttribute('dataAtts') }} class="buildamic-row {{ $row->computedAttribute('class') }} {{ $row->computedAttribute('col_gap') }}" style="{{ $row->computedAttribute('column_count_total') }}"> 2 | @foreach($row->value() as $column) 3 | {!! $buildamic->renderColumn($column) !!} 4 | @endforeach 5 |
6 | -------------------------------------------------------------------------------- /resources/views/layouts/section.blade.php: -------------------------------------------------------------------------------- 1 | @php 2 | $boxed = $section->buildamicSetting('boxed_layout') ? true : false; 3 | $background_image = $section->computedAttribute('background_image') ?? null; 4 | $background_video = $section->computedAttribute('background_video') ?? null; 5 | $mp4 = $background_video['mp4'][0] ?? null; 6 | $webm = $background_video['webm'][0] ?? null; 7 | @endphp 8 | 9 |
HtmlId($section->buildamicSetting('attributes.id')) !!} {{ $section->computedAttribute('dataAtts') }} class="buildamic-section {{ $section->computedAttribute('class') }}"> 10 | @if($background_video) 11 | 16 | @endif 17 | 18 | @if ($boxed) 19 |
20 | @endif 21 | 22 | @foreach($section->value() as $row) 23 | {!! $buildamic->renderRow($row) !!} 24 | @endforeach 25 | 26 | @if ($boxed) 27 |
28 | @endif 29 |
30 | -------------------------------------------------------------------------------- /resources/views/layouts/set.blade.php: -------------------------------------------------------------------------------- 1 | @php 2 | $background_image = $set->computedAttribute('background_image') ?? null; 3 | @endphp 4 | 5 |
buildamicSetting('moduleLink')) onclick="location.href='{{ $moduleLink }}';" @endif {!! BuildamicHelper()->HtmlId($set->buildamicSetting('attributes.id')) !!} class="buildamic-set @isset($class) {{ $class }} @endisset @if($moduleLink) cursor-pointer @endif {{ $set->computedAttribute('class') }}"> 6 | @yield('set_content') 7 |
8 | -------------------------------------------------------------------------------- /src/BuildamicFilters.php: -------------------------------------------------------------------------------- 1 | 'method_name', 15 | * 16 | * Where filter_name will be used by Filter::add() and Filter::run() function calls. 17 | * And method_name is the public static function that should be called for that filter. 18 | * 19 | * $data will always be passed to these filters. 20 | * 21 | * @var array 22 | */ 23 | protected static $filters = [ 24 | 'buildamic_filter_everything' => 'filter_everything', 25 | // 'buildamic_filter_section' => 'filter_section', 26 | 'buildamic_filter_row' => 'filter_row', 27 | 'buildamic_filter_column' => 'filter_column', 28 | 'buildamic_filter_field' => 'filter_field', 29 | // 'buildamic_filter_field:markdown-blurb' => 'filter_field_markdown_handle_blurb', 30 | // 'buildamic_filter_set' => 'filter_set', 31 | // 'buildamic_filter_set:blurb' => 'filter_set_blurb', 32 | ]; 33 | 34 | public static function boot() 35 | { 36 | foreach (static::$filters as $filter_name => $method_name) { 37 | if (method_exists(static::class, $method_name)) { 38 | Filter::add($filter_name, [static::class, $method_name], 10, 1); 39 | } 40 | } 41 | } 42 | 43 | // public static function example_filter(Field $data): Field 44 | // { 45 | // // Can use computedAttributes. 46 | // 47 | // // set/replace computed attributes with the provided. 48 | // $data->field()->setcomputedAttributes(['foo' => 'bar']); 49 | // $data->field()->computedAttributes(); 50 | // // [ 51 | // // "foo" => "bar" 52 | // // ] 53 | // $data->field()->computedAttribute('foo'); 54 | // // Bar 55 | 56 | // // add/merge new data into the computed attribuites. 57 | // $data->field()->mergeComputedAttributes(['bar' => 'foo']); 58 | // $data->field()->computedAttributes(); 59 | // // [ 60 | // // "foo" => "bar", 61 | // // "bar" => "foo" 62 | // // ] 63 | // $data->field()->computedAttribute('bar'); 64 | // // Foo 65 | 66 | // // set/replace computed attributes with the provided. 67 | // $data->field()->setcomputedAttributes(['bar' => 'foo']); 68 | // $data->field()->computedAttributes(); 69 | // // [ 70 | // // "bar" => "foo" 71 | // // ] 72 | // $data->field()->computedAttribute('bar'); 73 | // // Foo 74 | // $data->field()->computedAttribute('foo'); 75 | // // null or $fallback (second property on computedAttribute method) 76 | 77 | // // Must be returned! 78 | // return $data; 79 | // } 80 | 81 | public static function filter_everything(Field|Fields $field) 82 | { 83 | $classList = collect(explode(' ', $field->buildamicSetting('attributes.class')))->filter()->toArray(); 84 | $dataAtts = static::get_data_attributes(collect($field->buildamicSetting('attributes.dataAtts'))->filter()->toArray()) ?? []; 85 | 86 | if (is_array($field->buildamicSetting('inline'))) { 87 | // Remove anything we don't want generated with the loop (e.g background is handled separately) 88 | $inlineClassList = collect($field->buildamicSetting('inline'))->filter(function ($value, $key) { 89 | return 'background' !== $key; 90 | })->filter()->toArray(); 91 | 92 | if (!empty($inlineClassList)) { 93 | foreach ($inlineClassList as $item) { 94 | $classList[] = static::get_tw_classes_from_inline_atts($item); 95 | } 96 | } 97 | 98 | if (!empty($field->buildamicSetting('inline.background'))) { 99 | // [] loop the background options 100 | } 101 | } 102 | 103 | $background_image = $field->buildamicSetting('inline')['background']['image']['value'][0] ?? null; 104 | $background_video = $field->buildamicSetting('inline')['background']['video'] ?? null; 105 | 106 | if (isset($background_image)) { 107 | $field->mergeComputedAttributes(['background_image' => glide_url($background_image)]); 108 | } 109 | if (isset($background_video)) { 110 | $field->mergeComputedAttributes(['background_video' => $background_video]); 111 | } 112 | $field->mergeComputedAttributes(['class' => implode(' ', $classList)]); 113 | $field->mergeComputedAttributes(['dataAtts' => implode(' ', $dataAtts)]); 114 | 115 | return $field; 116 | } 117 | 118 | // public static function filter_section(Field $section): Field 119 | // { 120 | // return $section; 121 | // } 122 | 123 | public static function filter_row(Field $row): Field 124 | { 125 | $colGaps = static::get_col_gap(collect($row->buildamicSetting('attributes.col_gap'))->filter()->toArray()) ?? []; 126 | $columnCountTotal = static::column_count_total($row->buildamicSetting('attributes.column_count_total') ?? 12); 127 | $row->mergeComputedAttributes(['col_gap' => implode(' ', $colGaps)]); 128 | $row->mergeComputedAttributes(['column_count_total' => $columnCountTotal]); 129 | 130 | return $row; 131 | } 132 | 133 | public static function filter_column(Field $column): Field 134 | { 135 | if (!empty($column->buildamicSetting('columnSizes'))) { 136 | $columnSizes = collect($column->buildamicSetting('columnSizes'))->map(function ($value, $key) { 137 | if (!empty($value)) { 138 | if ('xs' == $key) { 139 | return "col-{$value} "; 140 | } else { 141 | return "{$key}:col-{$value} "; 142 | } 143 | } 144 | })->filter()->implode(' '); 145 | 146 | if (!empty($columnSizes)) { 147 | $column->mergeComputedAttributes(['class' => $column->computedAttribute('class')." {$columnSizes}"]); 148 | } 149 | } 150 | 151 | return $column; 152 | } 153 | 154 | public static function filter_field(Field $field): Field 155 | { 156 | $field->mergeComputedAttributes(['class' => modify($field->type())->ensureLeft('buildamic-')->ensureRight('-field').' '.$field->computedAttribute('class')]); 157 | 158 | return $field; 159 | } 160 | 161 | // public static function filter_field_markdown_handle_blurb(Field $field): Field 162 | // { 163 | // return $field; 164 | // } 165 | 166 | // public static function filter_set_blurb(Value $data): Value 167 | // { 168 | // return $data; 169 | // } 170 | 171 | /** 172 | * Takes all the inline attributes, font-size, margins, everything, then concatinates them into a big 173 | * string of tailwind classes. 174 | */ 175 | protected static function get_tw_classes_from_inline_atts(array|string $classes): string 176 | { 177 | $generatedClasses = []; 178 | 179 | if (is_array($classes)) { 180 | foreach ($classes as $child) { 181 | if (!is_array($child) && !empty($child)) { 182 | $generatedClasses[] = $child; 183 | } elseif (is_array($child)) { 184 | $generatedClasses[] = static::get_tw_classes_from_inline_atts($child); 185 | } 186 | } 187 | } else { 188 | $generatedClasses[] = $classes; 189 | } 190 | 191 | return trim(implode(' ', $generatedClasses), ' '); 192 | } 193 | 194 | protected static function get_data_attributes(array $dataAtts = []): array 195 | { 196 | $generatedAtts = []; 197 | 198 | if (is_array($dataAtts) && !empty($dataAtts)) { 199 | foreach ($dataAtts as $att) { 200 | $generatedAtts[] = "data-{$att['key']}={$att['value']}"; 201 | } 202 | } 203 | 204 | return $generatedAtts; 205 | } 206 | 207 | protected static function get_col_gap(array $colgaps = []): array 208 | { 209 | $generatedClasses = []; 210 | 211 | if (!is_array($colgaps) || empty($colgaps)) { 212 | return $colgaps; 213 | } 214 | 215 | foreach ($colgaps as $breakpoint => $colgap) { 216 | // dd($breakpoint); 217 | if ('xs' == $breakpoint) { 218 | $generatedClasses[] = "gap-{$colgap}"; 219 | } else { 220 | $generatedClasses[] = "{$breakpoint}:gap-{$colgap}"; 221 | } 222 | } 223 | 224 | return $generatedClasses; 225 | } 226 | 227 | protected static function column_count_total(int $columnCountTotal = 12): string 228 | { 229 | if (empty($columnCountTotal)) { 230 | return ''; 231 | } 232 | if (0 != (int) $columnCountTotal % 12) { 233 | return "--b-columns: {$columnCountTotal};"; 234 | } 235 | 236 | return ''; 237 | } 238 | 239 | // protected static function get_inline_styles(array | string $attribute = ''): string 240 | // { 241 | // // [] get the background options and create an inline style attribute string 242 | // } 243 | } 244 | -------------------------------------------------------------------------------- /src/BuildamicHelper.php: -------------------------------------------------------------------------------- 1 | augmented(); 22 | } 23 | 24 | if ($entry instanceof AugmentedEntry) { 25 | $this->entry = $entry; 26 | } 27 | 28 | return $this; 29 | } 30 | 31 | public function HtmlId(?string $id) 32 | { 33 | if ($id) { 34 | return 'id="'.$id.'"'; 35 | } 36 | } 37 | 38 | public function renderField(?string $field = null) 39 | { 40 | if (!$this->entry instanceof AugmentedEntry) { 41 | return; 42 | } 43 | 44 | $fields = ['buildamic', 'content']; 45 | 46 | if (!empty($field) && !in_array($field, $fields)) { 47 | $fields = array_merge([$field], $fields); 48 | } 49 | 50 | foreach ($fields as $field) { 51 | if (optional($this->entry->get($field))->value() instanceof BuildamicRenderer) { 52 | return $this->entry->get($field)->value(); 53 | } 54 | } 55 | } 56 | 57 | public function scripts(): string 58 | { 59 | // $script = ''; 60 | // return ""; 61 | 62 | return ''; 63 | } 64 | 65 | public function styles(): string 66 | { 67 | $style = asset('vendor/buildamic/css/buildamic.css'); 68 | 69 | return ""; 70 | } 71 | 72 | // public function getSetting(Field $field, $key, $fallback = null) 73 | // { 74 | // return $field->buildamicSetting($key, $fallback); 75 | // } 76 | } 77 | -------------------------------------------------------------------------------- /src/Fields/Field.php: -------------------------------------------------------------------------------- 1 | handle, $this->config)) 19 | ->setBuildamicSettings($this->buildamicSettings()) 20 | ->setParent($this->parent) 21 | ->setParentField($this->parentField) 22 | ->setValue($this->value ?? null); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Fields/Fields.php: -------------------------------------------------------------------------------- 1 | setBuildamicSettings($this->buildamicSettings()) 20 | ->setParent($this->parent) 21 | ->setParentField($this->parentField) 22 | ->setItems($this->items) 23 | ->setFields($this->fields); 24 | } 25 | 26 | public function createFields(array $config): array 27 | { 28 | $buildamicSettings = $this->buildamicSettings(); 29 | 30 | $fields = parent::createFields($config); 31 | 32 | return collect($fields)->map(function ($field) use ($buildamicSettings) { 33 | return (new Field($field->handle(), [])) 34 | ->setConfig($field->config()) 35 | ->setBuildamicSettings($buildamicSettings) 36 | ->setParent($field->parent()) 37 | ->setParentField($field->parentField()) 38 | ->setValue($field->value() ?? null); 39 | })->all(); 40 | } 41 | 42 | protected function newField($handle, $config) 43 | { 44 | return (new Field($handle, $config)) 45 | ->setBuildamicSettings($this->buildamicSettings()) 46 | ->setParent($this->parent) 47 | ->setParentField($this->parentField); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Fieldtypes/Buildamic.php: -------------------------------------------------------------------------------- 1 | config('fields'), $this->field()->parent(), $this->field()); 18 | } 19 | 20 | public function set(string $setHandle) 21 | { 22 | return new Fields($this->config("sets.{$setHandle}.fields"), $this->field()->parent(), $this->field()); 23 | } 24 | 25 | protected function configFieldItems(): array 26 | { 27 | return [ 28 | 'container_id' => [ 29 | 'display' => __('Container ID'), 30 | 'instructions' => __('Enter CSS ID that you want to apply to the container.'), 31 | 'type' => 'text', 32 | 'width' => 50, 33 | ], 34 | 'container_class' => [ 35 | 'display' => __('Container Class'), 36 | 'instructions' => __('Enter any CSS classes that you want to apply to the container.'), 37 | 'type' => 'text', 38 | 'width' => 50, 39 | ], 40 | 'globals' => [ 41 | 'display' => __('Globals'), 42 | 'instructions' => __(''), 43 | 'type' => 'collections', 44 | ], 45 | 'fields' => [ 46 | 'display' => __('Fields'), 47 | 'type' => 'fields', 48 | ], 49 | 'sets' => [ 50 | 'display' => __('Sets'), 51 | 'type' => 'sets', 52 | ], 53 | ]; 54 | } 55 | 56 | /** 57 | * Load data into meta. 58 | * 59 | * @param mixed $data 60 | * 61 | * @return array 62 | */ 63 | public function preload() 64 | { 65 | $instance = $this; 66 | 67 | return [ 68 | 'fields' => $this->fields()->all()->map(function ($field) { 69 | return [ 70 | 'handle' => $field->handle(), 71 | 'meta' => $field->meta(), 72 | 'value' => $field->fieldtype()->preProcess($field->defaultValue()), 73 | 'config' => $field->toPublishArray(), 74 | ]; 75 | })->toArray(), 76 | 'sets' => collect($this->config('sets'))->map(function ($set, $handle) use ($instance) { 77 | $fields = [ 78 | 'handle' => $handle, 79 | 'display' => $set['display'], 80 | 'fields' => [], 81 | ]; 82 | 83 | foreach ($instance->set($handle)->all() as $field) { 84 | $fields['fields'][] = [ 85 | 'handle' => $field->handle(), 86 | 'meta' => $field->meta(), 87 | 'value' => $field->fieldtype()->preProcess($field->defaultValue()), 88 | 'config' => $field->toPublishArray(), 89 | ]; 90 | } 91 | 92 | return !empty($fields['fields']) ? $fields : null; 93 | })->filter()->toArray(), 94 | ]; 95 | } 96 | 97 | /** 98 | * $preProcess = true: Pre-process the data before it gets sent to the publish page. 99 | * $preProcess = false: Process the data before it gets saved. 100 | * 101 | * @param mixed $data 102 | * 103 | * @return array 104 | */ 105 | protected function processData($data, bool $preProcess = false) 106 | { 107 | $method = $preProcess ? 'preProcess' : 'process'; 108 | 109 | return collect($data)->map(function ($section) use ($method) { 110 | if ('preProcess' === $method) { 111 | if ('global-section' === $section['type']) { 112 | $field = new Field('global', [ 113 | 'type' => 'entries', 114 | 'collections' => $this->field()->get('globals'), 115 | ]); 116 | 117 | $field->setValue($section['value'] ?? $field->fieldtype()->defaultValue()); 118 | 119 | $section['computed'] = [ 120 | 'meta' => $field->meta(), 121 | 'config' => $field->toPublishArray(), 122 | ]; 123 | } 124 | } else { 125 | unset($section['computed']); 126 | unset($section['meta']); 127 | } 128 | 129 | $section['value'] = (new Field($section['uuid'], [])) 130 | ->setConfig(array_merge(['type' => "buildamic-{$section['type']}"], $section['config'])) 131 | ->setBuildamicSettings($section['config']['buildamic_settings'] ?? []) 132 | ->setParent($this->field()->parent()) 133 | ->setParentField($this->field()) 134 | ->setValue($section['value']) 135 | ->{$method}() 136 | ->value() ?? []; 137 | 138 | return $section; 139 | })->toArray(); 140 | } 141 | 142 | protected function performAugmentation($value, $shallow = false) 143 | { 144 | $parent = $this; 145 | 146 | $method = $shallow ? 'shallowAugment' : 'augment'; 147 | 148 | $value = collect($value)->map(function ($section) use ($parent, $method) { 149 | if (isset($field['config']['buildamic_settings']['enabled']) && !$field['config']['buildamic_settings']['enabled']) { 150 | return; 151 | } 152 | 153 | if (!in_array($section['type'], ['section', 'global-section'])) { 154 | return; 155 | } 156 | 157 | return (new Field($section['uuid'], [])) 158 | ->setConfig(array_merge(['type' => "buildamic-{$section['type']}"], $section['config'])) 159 | ->setBuildamicSettings($section['config']['buildamic_settings'] ?? []) 160 | ->setParent($parent->field()->parent()) 161 | ->setParentField($parent->field()) 162 | ->setValue($section['value']) 163 | ->{$method}(); 164 | })->filter()->all(); 165 | 166 | $this->field()->setValue($value); 167 | 168 | return new BuildamicRenderer($this, $shallow); 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/Fieldtypes/BuildamicBase.php: -------------------------------------------------------------------------------- 1 | defaultValue; 23 | } 24 | 25 | // Don't display anything on the Collections index page. 26 | public function preProcessIndex($data) 27 | { 28 | } 29 | 30 | /** 31 | * Pre-process the data before it gets sent to the publish page. 32 | * 33 | * @param mixed $data 34 | * 35 | * @return array|mixed 36 | */ 37 | public function preProcess($data) 38 | { 39 | if (empty($data)) { 40 | return $this->defaultValue(); 41 | } 42 | 43 | return $this->processData($data, true); 44 | } 45 | 46 | /** 47 | * Process the data before it gets saved. 48 | * 49 | * @param mixed $data 50 | * 51 | * @return array|mixed 52 | */ 53 | public function process($data) 54 | { 55 | if (!is_array($data)) { 56 | return $this->defaultValue(); 57 | } 58 | 59 | return $this->processData($data, false); 60 | } 61 | 62 | /** 63 | * $preProcess = true: Pre-process the data before it gets sent to the publish page. 64 | * $preProcess = false: Process the data before it gets saved. 65 | * 66 | * @param mixed $data 67 | * 68 | * @return array 69 | */ 70 | protected function processData($data, bool $preProcess = false) 71 | { 72 | return $data; 73 | } 74 | 75 | public function augment($value) 76 | { 77 | return $this->performAugmentation($value, false); 78 | } 79 | 80 | public function shallowAugment($value) 81 | { 82 | return $this->performAugmentation($value, true); 83 | } 84 | 85 | protected function performAugmentation($value, $shallow = false) 86 | { 87 | return $value; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/Fieldtypes/BuildamicColumn.php: -------------------------------------------------------------------------------- 1 | field()->parentField()->parentField()->parentField(); 24 | 25 | $method = $preProcess ? 'preProcess' : 'process'; 26 | 27 | return collect($data)->map(function ($field) use ($buildamicInstance, $method) { 28 | if ('field' === $field['type']) { 29 | $computedField = $buildamicInstance 30 | ->fieldType() 31 | ->fields() 32 | ->get($field['config']['statamic_settings']['handle']) 33 | ->setValue($field['value']) 34 | ->{$method}(); 35 | 36 | if ('preProcess' === $method) { 37 | $field['computed'] = [ 38 | 'meta' => $computedField->meta(), 39 | 'config' => $computedField->toPublishArray(), 40 | ]; 41 | } else { 42 | unset($field['computed']); 43 | unset($field['meta']); 44 | } 45 | 46 | $field['config']['statamic_settings'] = [ 47 | 'handle' => $field['config']['statamic_settings']['field']['handle'] ?? $field['config']['statamic_settings']['handle'], 48 | 'field' => [ 49 | 'type' => $field['config']['statamic_settings']['field']['type'], 50 | ], 51 | ]; 52 | 53 | $field['value'] = $buildamicInstance 54 | ->fieldType() 55 | ->fields() 56 | ->get($field['config']['statamic_settings']['handle']) 57 | ->setValue($field['value']) 58 | ->{$method}() 59 | ->value(); 60 | } elseif ('set' === $field['type']) { 61 | $fields = $buildamicInstance 62 | ->fieldType() 63 | ->set($field['config']['statamic_settings']['handle']) 64 | ->addValues($field['value']) 65 | ->{$method}(); 66 | 67 | if ('preProcess' === $method) { 68 | $field['computed'] = [ 69 | 'meta' => [], 70 | 'config' => [], 71 | ]; 72 | 73 | $fields->all()->each(function ($_field) use (&$field) { 74 | $field['computed']['meta'][$_field->handle()] = $_field->meta(); 75 | $field['computed']['config'][$_field->handle()] = $_field->toPublishArray(); 76 | }); 77 | } else { 78 | unset($field['computed']); 79 | unset($field['meta']); 80 | } 81 | 82 | $field['value'] = $fields->values()->toArray(); 83 | } elseif ('fieldset' === $field['type']) { 84 | // Fieldset (single field) 85 | if (isset($field['config']['statamic_settings']['field']) && is_string($field['config']['statamic_settings']['field'])) { 86 | $singleField = [ 87 | 'handle' => $field['config']['statamic_settings']['field'], 88 | 'field' => $field['config']['statamic_settings']['field'], 89 | 'config' => $field['config']['statamic_settings'] ?? [], 90 | ]; 91 | } 92 | 93 | $fields = (new Fields([])) 94 | ->setBuildamicSettings($field['config']['buildamic_settings']) 95 | ->setItems([$singleField ?? $field['config']['statamic_settings']]) 96 | ->addValues($field['value'] ?? []) 97 | ->{$method}(); 98 | 99 | if ('preProcess' === $method) { 100 | $field['computed'] = [ 101 | 'meta' => [], 102 | 'config' => [], 103 | ]; 104 | 105 | $fields->all()->each(function ($_field) use (&$field) { 106 | $field['computed']['meta'][$_field->handle()] = $_field->meta(); 107 | $field['computed']['config'][$_field->handle()] = $_field->toPublishArray(); 108 | }); 109 | } else { 110 | unset($field['computed']); 111 | unset($field['meta']); 112 | } 113 | 114 | $field['value'] = $fields->values()->toArray(); 115 | } 116 | 117 | return $field; 118 | })->toArray(); 119 | } 120 | 121 | protected function performAugmentation($value, $shallow = false) 122 | { 123 | $buildamicInstance = $this->field()->parentField()->parentField()->parentField(); 124 | $buildamicConfig = $buildamicInstance->config(); 125 | 126 | $method = $shallow ? 'shallowAugment' : 'augment'; 127 | 128 | $value = collect($value)->map(function ($field) use ($buildamicConfig) { 129 | if (isset($field['config']['buildamic_settings']['enabled']) && !$field['config']['buildamic_settings']['enabled']) { 130 | return; 131 | } 132 | 133 | if ('field' === $field['type']) { 134 | $config = collect($buildamicConfig['fields'] ?? [])->firstWhere('handle', $field['config']['statamic_settings']['handle']); 135 | 136 | // uuid: 98962c4d-2b1d-4579-b119-1757ee6cd608 137 | // type: field 138 | // config: 139 | // statamic_settings: 140 | // handle: markdown 141 | // field: 142 | // type: markdown 143 | // buildamic_settings: 144 | // enabled: true 145 | // admin_label: markdown 146 | // value: 'Markdown Value' 147 | return (new Field($field['config']['statamic_settings']['handle'], [])) 148 | ->setConfig(array_merge($config['field'], $field['config']['statamic_settings']['field'] ?? [])) 149 | ->setBuildamicSettings($field['config']['buildamic_settings'] ?? []) 150 | ->setParent($this->field()->parent()) 151 | ->setParentField($this->field()) 152 | ->setValue($field['value'] ?? null); 153 | } 154 | 155 | if ('fieldset' === $field['type']) { 156 | // - 157 | // uuid: 77290cd8-583d-4ad8-9137-cfa24097bf78 158 | // type: fieldset 159 | // config: 160 | // statamic_settings: 161 | // import: fieldset_example 162 | // prefix: prefix 163 | // buildamic_settings: 164 | // enabled: true 165 | // value: 166 | // prefixbio: '123456' 167 | // - 168 | // uuid: 77290cd8-583d-4ad8-9137-cfa24097bf781 169 | // type: fieldset 170 | // config: 171 | // statamic_settings: 172 | // handle: bio 173 | // field: fieldset_example.bio 174 | // config: 175 | // antlers: true 176 | // buildamic_settings: 177 | // enabled: true 178 | // value: 179 | // bio: '123456' 180 | 181 | if ($field['value'] instanceof Collection) { 182 | $field['value'] = $field['value']->all(); 183 | } 184 | 185 | return (new Fields([])) 186 | ->setBuildamicSettings($field['config']['buildamic_settings'] ?? []) 187 | ->setParent($this->field()->parent()) 188 | ->setParentField($this->field()) 189 | ->setItems([$field['config']['statamic_settings']]) 190 | ->addValues($field['value'] ?? []); 191 | } 192 | 193 | if ('set' === $field['type']) { 194 | $field['config']['statmic_settings']['field']['type'] = 'sets'; 195 | 196 | // uuid: 98962c4d-2b1d-4579-b119-1757ee6cd608 197 | // type: set 198 | // config: 199 | // statamic_settings: 200 | // handle: blurb 201 | // buildamic_settings: 202 | // enabled: true 203 | // admin_label: blurb 204 | // value: 205 | // title: Test 206 | // content: Testing 207 | return (new Field($field['config']['statamic_settings']['handle'], [])) 208 | ->setConfig($field['config']['statmic_settings']['field']) 209 | ->setBuildamicSettings($field['config']['buildamic_settings'] ?? []) 210 | ->setParent($this->field()->parent()) 211 | ->setParentField($this->field()) 212 | ->setValue($field['value'] ?? []); 213 | } 214 | })->filter()->all(); 215 | 216 | return $this->field()->setValue($value)->value(); 217 | } 218 | } 219 | -------------------------------------------------------------------------------- /src/Fieldtypes/BuildamicGlobal.php: -------------------------------------------------------------------------------- 1 | map(function ($column) use ($method) { 24 | $column['value'] = (new Field($column['uuid'], [])) 25 | ->setConfig(array_merge(['type' => 'buildamic-column'], $column['config'])) 26 | ->setBuildamicSettings($column['config']['buildamic_settings'] ?? []) 27 | ->setParent($this->field()->parent()) 28 | ->setParentField($this->field()) 29 | ->setValue($column['value']) 30 | ->{$method}() 31 | ->value() ?? []; 32 | 33 | return $column; 34 | })->toArray(); 35 | } 36 | 37 | protected function performAugmentation($value, $shallow = false) 38 | { 39 | $method = $shallow ? 'shallowAugment' : 'augment'; 40 | 41 | $value = collect($value)->map(function ($column) use ($method) { 42 | if (isset($field['config']['buildamic_settings']['enabled']) && !$field['config']['buildamic_settings']['enabled']) { 43 | return; 44 | } 45 | 46 | return (new Field($column['uuid'], [])) 47 | ->setConfig(array_merge(['type' => 'buildamic-column'], $column['config'])) 48 | ->setBuildamicSettings($column['config']['buildamic_settings'] ?? []) 49 | ->setParent($this->field()->parent()) 50 | ->setParentField($this->field()) 51 | ->setValue($column['value']) 52 | ->{$method}(); 53 | })->filter()->all(); 54 | 55 | return $this->field()->setValue($value)->value(); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Fieldtypes/BuildamicSection.php: -------------------------------------------------------------------------------- 1 | map(function ($row) use ($method) { 25 | $row['value'] = (new Field($row['uuid'], [])) 26 | ->setConfig(array_merge(['type' => 'buildamic-row'], $row['config'])) 27 | ->setBuildamicSettings($row['config']['buildamic_settings'] ?? []) 28 | ->setParent($this->field()->parent()) 29 | ->setParentField($this->field()) 30 | ->setValue($row['value']) 31 | ->{$method}() 32 | ->value() ?? []; 33 | 34 | return $row; 35 | })->toArray(); 36 | } 37 | 38 | protected function performAugmentation($value, $shallow = false) 39 | { 40 | $method = $shallow ? 'shallowAugment' : 'augment'; 41 | 42 | $value = collect($value)->map(function ($row) use ($method) { 43 | if (isset($field['config']['buildamic_settings']['enabled']) && !$field['config']['buildamic_settings']['enabled']) { 44 | return; 45 | } 46 | 47 | return (new Field($row['uuid'], [])) 48 | ->setConfig(array_merge(['type' => 'buildamic-row'], $row['config'])) 49 | ->setBuildamicSettings($row['config']['buildamic_settings'] ?? []) 50 | ->setParent($this->field()->parent()) 51 | ->setParentField($this->field()) 52 | ->setValue($row['value']) 53 | ->{$method}(); 54 | })->filter()->all(); 55 | 56 | return $this->field()->setValue($value)->value(); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Filter.php: -------------------------------------------------------------------------------- 1 | 'img', 20 | ]; 21 | 22 | protected $fieldtypes = [ 23 | \HandmadeWeb\Buildamic\Fieldtypes\Buildamic::class, 24 | 25 | \HandmadeWeb\Buildamic\Fieldtypes\BuildamicSection::class, 26 | \HandmadeWeb\Buildamic\Fieldtypes\BuildamicGlobalSection::class, 27 | 28 | \HandmadeWeb\Buildamic\Fieldtypes\BuildamicRow::class, 29 | \HandmadeWeb\Buildamic\Fieldtypes\BuildamicColumn::class, 30 | ]; 31 | 32 | protected $tags = [ 33 | \HandmadeWeb\Buildamic\Tags\BuildamicScripts::class, 34 | \HandmadeWeb\Buildamic\Tags\BuildamicStyles::class, 35 | ]; 36 | 37 | public function boot() 38 | { 39 | parent::boot(); 40 | 41 | $this->bootDirectives(); 42 | 43 | BuildamicFilters::boot(); 44 | } 45 | 46 | public function bootDirectives() 47 | { 48 | Blade::directive('buildamicScripts', function () { 49 | return 'scripts(); ?>'; 50 | }); 51 | 52 | Blade::directive('buildamicStyles', function () { 53 | return 'styles(); ?>'; 54 | }); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Tags/BuildamicScripts.php: -------------------------------------------------------------------------------- 1 | scripts(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Tags/BuildamicStyles.php: -------------------------------------------------------------------------------- 1 | styles(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Traits/AugmentsOnce.php: -------------------------------------------------------------------------------- 1 | isAugmented; 13 | } 14 | 15 | public function isShallowAugmented(): bool 16 | { 17 | return $this->isShallowAugmented; 18 | } 19 | 20 | protected function setAugmented(bool $augmented = true): static 21 | { 22 | $this->isAugmented = $augmented; 23 | 24 | return $this; 25 | } 26 | 27 | protected function setShallowAugmented(bool $shallowAugmented = true): static 28 | { 29 | $this->isShallowAugmented = $shallowAugmented; 30 | 31 | return $this; 32 | } 33 | 34 | public function augment(): static 35 | { 36 | if (!$this->isAugmented || $this->isShallowAugmented) { 37 | return parent::augment()->setAugmented(); 38 | } 39 | 40 | return $this; 41 | } 42 | 43 | public function shallowAugment(): static 44 | { 45 | if ($this->isAugmented || !$this->isShallowAugmented) { 46 | return parent::shallowAugment()->setShallowAugmented(); 47 | } 48 | 49 | return $this; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Traits/HasBuildamicSettings.php: -------------------------------------------------------------------------------- 1 | buildamicSettings = $buildamicSettings; 12 | 13 | return $this; 14 | } 15 | 16 | public function mergeBuildamicSettings(array $buildamicSettings): static 17 | { 18 | $this->buildamicSettings = array_merge($this->buildamicSettings, $buildamicSettings); 19 | 20 | return $this; 21 | } 22 | 23 | public function buildamicSettings(): array 24 | { 25 | return $this->buildamicSettings; 26 | } 27 | 28 | public function buildamicSetting(string|null $key = null, $fallback = null) 29 | { 30 | if (is_null($key)) { 31 | return $this->buildamicSettings(); 32 | } 33 | 34 | return array_get($this->buildamicSettings, $key, $fallback); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Traits/HasComputedAttributes.php: -------------------------------------------------------------------------------- 1 | computedAttributes = $computedAttributes; 12 | 13 | return $this; 14 | } 15 | 16 | public function mergeComputedAttributes(array $computedAttributes): static 17 | { 18 | $this->computedAttributes = array_merge($this->computedAttributes, $computedAttributes); 19 | 20 | return $this; 21 | } 22 | 23 | public function computedAttributes(): array 24 | { 25 | return $this->computedAttributes; 26 | } 27 | 28 | public function computedAttribute(string|null $key = null, $fallback = null) 29 | { 30 | if (is_null($key)) { 31 | return $this->computedAttributes(); 32 | } 33 | 34 | return array_get($this->computedAttributes, $key, $fallback); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/helpers.php: -------------------------------------------------------------------------------- 1 |