├── .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 | [](https://packagist.org/packages/handmadeweb/buildamic)
2 | [](https://packagist.org/packages/handmadeweb/buildamic)
3 | [](LICENSE.md)
4 | [](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 |
2 |
3 |
11 |
12 |
13 |
26 | {{ __("Fieldtypes") }}
27 |
28 | ×
29 |
30 |
31 |
32 |
92 |
93 |
94 |
95 |
96 |
97 |
141 |
142 |
143 |
--------------------------------------------------------------------------------
/resources/js/components/columns/ColumnSettings.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
29 |
--------------------------------------------------------------------------------
/resources/js/components/columns/GridColumn.vue:
--------------------------------------------------------------------------------
1 |
2 |
13 |
20 |
21 |
27 |
28 |
29 |
30 |
31 | Add Module
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
72 |
73 |
81 |
--------------------------------------------------------------------------------
/resources/js/components/fields/FieldDisplay.vue:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
{{
20 | field.config.buildamic_settings.admin_label ||
21 | field.config.statamic_settings.field.display ||
22 | field.config.statamic_settings.handle
23 | }}
24 |
25 |
32 |
33 |
34 |
35 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/resources/js/components/fields/FieldSettings.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
46 |
--------------------------------------------------------------------------------
/resources/js/components/fields/SingleField.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
20 |
21 |
22 |
23 |
24 |
25 |
96 |
97 |
102 |
--------------------------------------------------------------------------------
/resources/js/components/fields/overrides/ColorFieldtype.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
80 |
--------------------------------------------------------------------------------
/resources/js/components/fieldtypes/Buildamic.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
18 |
19 |
25 | Add Section
26 |
27 |
28 |
29 |
30 |
31 |
32 |
82 |
83 |
123 |
--------------------------------------------------------------------------------
/resources/js/components/inputs/BuildamicInputText.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
12 |
13 |
14 |
15 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/resources/js/components/rows/ColumnSelector.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Choose column layout:
7 |
35 |
36 |
37 |
38 |
161 |
162 |
178 |
--------------------------------------------------------------------------------
/resources/js/components/rows/GridRow.vue:
--------------------------------------------------------------------------------
1 |
2 |
16 |
19 |
20 |
26 | Select columns
27 |
28 |
29 |
30 |
31 |
32 |
33 |
49 |
50 |
51 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
132 |
--------------------------------------------------------------------------------
/resources/js/components/rows/RowSettings.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | Column Gap
13 |
25 |
26 |
32 |
33 |
34 |
35 |
36 |
64 |
--------------------------------------------------------------------------------
/resources/js/components/sections/GridGlobalSection.vue:
--------------------------------------------------------------------------------
1 |
2 |
15 |
22 |
23 |
24 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
100 |
--------------------------------------------------------------------------------
/resources/js/components/sections/GridSection.vue:
--------------------------------------------------------------------------------
1 |
2 |
18 |
19 |
26 |
27 |
34 |
35 |
40 | Add Row
41 |
42 |
43 |
59 |
60 |
61 |
62 |
63 |
64 |
92 |
--------------------------------------------------------------------------------
/resources/js/components/sections/SectionSettings.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Boxed Layout?
8 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
65 |
--------------------------------------------------------------------------------
/resources/js/components/sets/SetField.vue:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
22 |
23 |
24 |
25 |
26 |
27 |
121 |
122 |
127 |
128 |
142 |
--------------------------------------------------------------------------------
/resources/js/components/sets/SetSettings.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
11 |
12 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
73 |
--------------------------------------------------------------------------------
/resources/js/components/shared/AdminLabel.vue:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
15 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/resources/js/components/shared/DesignTab.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
16 |
20 |
21 |
22 |
23 |
43 |
--------------------------------------------------------------------------------
/resources/js/components/shared/ErrorDisplay.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | {{
4 | Object.values(error).join(", ")
5 | }}
6 |
7 |
8 |
9 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/resources/js/components/shared/FieldBase.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
128 |
129 |
130 |
--------------------------------------------------------------------------------
/resources/js/components/shared/ModuleControls.vue:
--------------------------------------------------------------------------------
1 |
2 |
36 |
37 |
38 |
145 |
--------------------------------------------------------------------------------
/resources/js/components/shared/OptionsTab.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {{ classOption.display }}
7 |
14 |
15 |
16 | {{ idOption.display }}
17 |
24 |
25 |
26 |
27 | Data Attributes
28 |
32 |
33 |
34 |
Module Link
35 |
36 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
128 |
129 |
130 |
--------------------------------------------------------------------------------
/resources/js/components/shared/SettingStack.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
12 |
13 |
14 |
Settings for {{ admin_label }}
15 |
16 | Cancel
17 | Save
18 |
19 |
20 |
25 |
26 |
27 |
28 |
29 |
30 |
106 |
107 |
108 |
--------------------------------------------------------------------------------
/resources/js/components/shared/settings/AlignmentControls.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
14 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
81 |
82 |
--------------------------------------------------------------------------------
/resources/js/components/shared/settings/BreakpointSwitcher.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 | {{ option }}
12 |
13 |
14 |
15 |
16 |
17 |
52 |
62 |
--------------------------------------------------------------------------------
/resources/js/components/shared/settings/DataAttributes.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 |
{{
6 | instructions
7 | }}
8 |
13 |
{{ label }}:
14 |
15 |
16 |
28 |
29 |
30 |
42 |
43 |
51 |
52 |
53 |
Add fields
56 |
57 |
58 |
59 |
126 |
127 |
132 |
--------------------------------------------------------------------------------
/resources/js/components/shared/settings/SettingsGroup.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ heading }}
4 |
5 |
6 |
7 |
8 |
15 |
16 |
24 |
--------------------------------------------------------------------------------
/resources/js/components/shared/settings/SettingsText.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
15 |
16 | {{ inline["font-size"].config.display }}
17 |
26 |
27 |
28 | {{ inline.color.config.display }}:
29 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
88 |
89 |
90 |
--------------------------------------------------------------------------------
/resources/js/components/shared/settings/ToggleControls.vue:
--------------------------------------------------------------------------------
1 |
2 |
37 |
38 |
39 |
86 |
--------------------------------------------------------------------------------
/resources/js/components/tabs/VueTab.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
33 |
--------------------------------------------------------------------------------
/resources/js/components/tabs/VueTabs.vue:
--------------------------------------------------------------------------------
1 |
2 |
35 |
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 |
19 | {{ $field->type() }}-{{ $field->handle() }}.blade.php
20 | {{ $field->type() }}.blade.php
21 |
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 |
10 | @foreach($terms as $term)
11 | {{ $term->get('title') }}
12 | @endforeach
13 |
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 |
12 | @isset($webm) @endisset
13 | @isset($mp4) @endisset
14 | Your browser does not support the video tag.
15 |
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 |