mirror of
https://gitlab.com/bramw/baserow.git
synced 2025-04-07 22:35:36 +00:00
Resolve "Add Element styles tab"
This commit is contained in:
parent
4d2f1936b1
commit
a1e84d703d
41 changed files with 646 additions and 489 deletions
backend
src/baserow/contrib/builder
api
elements
migrations
tests/baserow/contrib/builder
web-frontend/modules
builder
components
elementTypes.jsmixins
pages
core
assets/scss
components/builder
all.scsselement.scsselement_preview.scss
variables.scsselements
page_editor.scsspage_root_element.scsspublic_page.scsslocales
|
@ -49,7 +49,7 @@ class PublicElementSerializer(serializers.ModelSerializer):
|
|||
|
||||
class Meta:
|
||||
model = Element
|
||||
fields = ("id", "type")
|
||||
fields = ("id", "type", "style_padding_top", "style_padding_bottom")
|
||||
extra_kwargs = {
|
||||
"id": {"read_only": True},
|
||||
"type": {"read_only": True},
|
||||
|
|
|
@ -22,7 +22,14 @@ class ElementSerializer(serializers.ModelSerializer):
|
|||
|
||||
class Meta:
|
||||
model = Element
|
||||
fields = ("id", "page_id", "type", "order")
|
||||
fields = (
|
||||
"id",
|
||||
"page_id",
|
||||
"type",
|
||||
"order",
|
||||
"style_padding_top",
|
||||
"style_padding_bottom",
|
||||
)
|
||||
extra_kwargs = {
|
||||
"id": {"read_only": True},
|
||||
"page_id": {"read_only": True},
|
||||
|
@ -50,13 +57,13 @@ class CreateElementSerializer(serializers.ModelSerializer):
|
|||
|
||||
class Meta:
|
||||
model = Element
|
||||
fields = ("before_id", "type")
|
||||
fields = ("before_id", "type", "style_padding_top", "style_padding_bottom")
|
||||
|
||||
|
||||
class UpdateElementSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Element
|
||||
fields = []
|
||||
fields = ("style_padding_top", "style_padding_bottom")
|
||||
|
||||
|
||||
class MoveElementSerializer(serializers.Serializer):
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
from abc import ABC
|
||||
from typing import Dict, Optional
|
||||
|
||||
from rest_framework import serializers
|
||||
|
@ -21,17 +20,70 @@ from baserow.contrib.builder.types import ElementDict
|
|||
from baserow.core.user_files.models import UserFile
|
||||
|
||||
|
||||
class BaseTextElementType(ElementType, ABC):
|
||||
class HeadingElementType(ElementType):
|
||||
"""
|
||||
Base class for text elements.
|
||||
A simple heading element that can be used to display a title.
|
||||
"""
|
||||
|
||||
type = "heading"
|
||||
model_class = HeadingElement
|
||||
serializer_field_names = ["value", "level"]
|
||||
allowed_fields = ["value", "level"]
|
||||
|
||||
class SerializedDict(ElementDict):
|
||||
value: Expression
|
||||
level: int
|
||||
|
||||
@property
|
||||
def serializer_field_overrides(self):
|
||||
from baserow.core.expression.serializers import ExpressionSerializer
|
||||
|
||||
overrides = {
|
||||
"value": ExpressionSerializer(
|
||||
help_text="The value of the element. Must be an expression.",
|
||||
required=False,
|
||||
allow_blank=True,
|
||||
default="",
|
||||
),
|
||||
"level": serializers.IntegerField(
|
||||
help_text="The level of the heading from 1 to 6.",
|
||||
min_value=1,
|
||||
max_value=6,
|
||||
default=1,
|
||||
),
|
||||
}
|
||||
|
||||
return overrides
|
||||
|
||||
def get_sample_params(self):
|
||||
return {
|
||||
"value": "Corporis perspiciatis",
|
||||
"level": 2,
|
||||
}
|
||||
|
||||
|
||||
class ParagraphElementType(ElementType):
|
||||
"""
|
||||
A simple paragraph element that can be used to display a paragraph of text.
|
||||
"""
|
||||
|
||||
type = "paragraph"
|
||||
model_class = ParagraphElement
|
||||
serializer_field_names = ["value"]
|
||||
allowed_fields = ["value"]
|
||||
|
||||
class SerializedDict(ElementDict):
|
||||
value: Expression
|
||||
|
||||
def get_sample_params(self):
|
||||
return {
|
||||
"value": "Suscipit maxime eos ea vel commodi dolore. "
|
||||
"Eum dicta sit rerum animi. Sint sapiente eum cupiditate nobis vel. "
|
||||
"Maxime qui nam consequatur. "
|
||||
"Asperiores corporis perspiciatis nam harum veritatis. "
|
||||
"Impedit qui maxime aut illo quod ea molestias."
|
||||
}
|
||||
|
||||
@property
|
||||
def serializer_field_overrides(self):
|
||||
from baserow.core.expression.serializers import ExpressionSerializer
|
||||
|
@ -46,65 +98,7 @@ class BaseTextElementType(ElementType, ABC):
|
|||
}
|
||||
|
||||
|
||||
class HeadingElementType(BaseTextElementType):
|
||||
"""
|
||||
A simple heading element that can be used to display a title.
|
||||
"""
|
||||
|
||||
type = "heading"
|
||||
model_class = HeadingElement
|
||||
|
||||
class SerializedDict(ElementDict):
|
||||
value: Expression
|
||||
level: int
|
||||
|
||||
@property
|
||||
def serializer_field_names(self):
|
||||
return super().serializer_field_names + ["level"]
|
||||
|
||||
@property
|
||||
def allowed_fields(self):
|
||||
return super().allowed_fields + ["level"]
|
||||
|
||||
@property
|
||||
def serializer_field_overrides(self):
|
||||
overrides = {
|
||||
"level": serializers.IntegerField(
|
||||
help_text="The level of the heading from 1 to 6.",
|
||||
min_value=1,
|
||||
max_value=6,
|
||||
default=1,
|
||||
)
|
||||
}
|
||||
overrides.update(super().serializer_field_overrides)
|
||||
return overrides
|
||||
|
||||
def get_sample_params(self):
|
||||
return {
|
||||
"value": "Corporis perspiciatis",
|
||||
"level": 2,
|
||||
}
|
||||
|
||||
|
||||
class ParagraphElementType(BaseTextElementType):
|
||||
"""
|
||||
A simple paragraph element that can be used to display a paragraph of text.
|
||||
"""
|
||||
|
||||
type = "paragraph"
|
||||
model_class = ParagraphElement
|
||||
|
||||
def get_sample_params(self):
|
||||
return {
|
||||
"value": "Suscipit maxime eos ea vel commodi dolore. "
|
||||
"Eum dicta sit rerum animi. Sint sapiente eum cupiditate nobis vel. "
|
||||
"Maxime qui nam consequatur. "
|
||||
"Asperiores corporis perspiciatis nam harum veritatis. "
|
||||
"Impedit qui maxime aut illo quod ea molestias."
|
||||
}
|
||||
|
||||
|
||||
class LinkElementType(BaseTextElementType):
|
||||
class LinkElementType(ElementType):
|
||||
"""
|
||||
A simple paragraph element that can be used to display a paragraph of text.
|
||||
"""
|
||||
|
@ -112,38 +106,34 @@ class LinkElementType(BaseTextElementType):
|
|||
type = "link"
|
||||
model_class = LinkElement
|
||||
PATH_PARAM_TYPE_TO_PYTHON_TYPE_MAP = {"text": str, "numeric": int}
|
||||
serializer_field_names = [
|
||||
"value",
|
||||
"navigation_type",
|
||||
"navigate_to_page_id",
|
||||
"navigate_to_url",
|
||||
"page_parameters",
|
||||
"variant",
|
||||
"target",
|
||||
"width",
|
||||
"alignment",
|
||||
]
|
||||
allowed_fields = [
|
||||
"value",
|
||||
"navigation_type",
|
||||
"navigate_to_page_id",
|
||||
"navigate_to_url",
|
||||
"page_parameters",
|
||||
"variant",
|
||||
"target",
|
||||
"width",
|
||||
"alignment",
|
||||
]
|
||||
|
||||
class SerializedDict(ElementDict):
|
||||
value: Expression
|
||||
destination: Expression
|
||||
open_new_tab: bool
|
||||
|
||||
@property
|
||||
def serializer_field_names(self):
|
||||
return super().serializer_field_names + [
|
||||
"navigation_type",
|
||||
"navigate_to_page_id",
|
||||
"navigate_to_url",
|
||||
"page_parameters",
|
||||
"variant",
|
||||
"target",
|
||||
"width",
|
||||
"alignment",
|
||||
]
|
||||
|
||||
@property
|
||||
def allowed_fields(self):
|
||||
return super().allowed_fields + [
|
||||
"navigation_type",
|
||||
"navigate_to_page_id",
|
||||
"navigate_to_url",
|
||||
"page_parameters",
|
||||
"variant",
|
||||
"target",
|
||||
"width",
|
||||
"alignment",
|
||||
]
|
||||
|
||||
def import_serialized(self, page, serialized_values, id_mapping):
|
||||
serialized_copy = serialized_values.copy()
|
||||
if serialized_copy["navigate_to_page_id"]:
|
||||
|
@ -160,6 +150,12 @@ class LinkElementType(BaseTextElementType):
|
|||
from baserow.core.expression.serializers import ExpressionSerializer
|
||||
|
||||
overrides = {
|
||||
"value": ExpressionSerializer(
|
||||
help_text="The value of the element. Must be an expression.",
|
||||
required=False,
|
||||
allow_blank=True,
|
||||
default="",
|
||||
),
|
||||
"navigation_type": serializers.ChoiceField(
|
||||
choices=LinkElement.NAVIGATION_TYPES.choices,
|
||||
help_text=LinkElement._meta.get_field("navigation_type").help_text,
|
||||
|
@ -203,11 +199,11 @@ class LinkElementType(BaseTextElementType):
|
|||
required=False,
|
||||
),
|
||||
}
|
||||
overrides.update(super().serializer_field_overrides)
|
||||
return overrides
|
||||
|
||||
def get_sample_params(self):
|
||||
return {
|
||||
"value": "test",
|
||||
"navigation_type": "custom",
|
||||
"navigate_to_page_id": None,
|
||||
"navigate_to_url": "http://example.com",
|
||||
|
|
|
@ -110,7 +110,7 @@ class ElementHandler:
|
|||
else:
|
||||
order = Element.get_last_order(page)
|
||||
|
||||
shared_allowed_fields = ["type"]
|
||||
shared_allowed_fields = ["type", "style_padding_top", "style_padding_bottom"]
|
||||
allowed_values = extract_allowed(
|
||||
kwargs, shared_allowed_fields + element_type.allowed_fields
|
||||
)
|
||||
|
@ -145,7 +145,7 @@ class ElementHandler:
|
|||
|
||||
element_type = element_type_registry.get_by_model(element)
|
||||
|
||||
shared_allowed_fields = []
|
||||
shared_allowed_fields = ["style_padding_top", "style_padding_bottom"]
|
||||
allowed_updates = extract_allowed(
|
||||
kwargs, shared_allowed_fields + element_type.allowed_fields
|
||||
)
|
||||
|
|
|
@ -51,6 +51,9 @@ class Element(
|
|||
on_delete=models.SET(get_default_element_content_type),
|
||||
)
|
||||
|
||||
style_padding_top = models.PositiveIntegerField(default=10)
|
||||
style_padding_bottom = models.PositiveIntegerField(default=10)
|
||||
|
||||
class Meta:
|
||||
ordering = ("order", "id")
|
||||
|
||||
|
@ -86,18 +89,7 @@ class Element(
|
|||
return cls.get_unique_orders_before_item(before, queryset)[0]
|
||||
|
||||
|
||||
class BaseTextElement(Element):
|
||||
"""
|
||||
Base class for text elements.
|
||||
"""
|
||||
|
||||
value = ExpressionField(default="")
|
||||
|
||||
class Meta:
|
||||
abstract = True
|
||||
|
||||
|
||||
class HeadingElement(BaseTextElement):
|
||||
class HeadingElement(Element):
|
||||
"""
|
||||
A Heading element to display a title.
|
||||
"""
|
||||
|
@ -109,18 +101,21 @@ class HeadingElement(BaseTextElement):
|
|||
H4 = 4
|
||||
H5 = 5
|
||||
|
||||
value = ExpressionField(default="")
|
||||
level = models.IntegerField(
|
||||
choices=HeadingLevel.choices, default=1, help_text="The level of the heading"
|
||||
)
|
||||
|
||||
|
||||
class ParagraphElement(BaseTextElement):
|
||||
class ParagraphElement(Element):
|
||||
"""
|
||||
A simple paragraph.
|
||||
"""
|
||||
|
||||
value = ExpressionField(default="")
|
||||
|
||||
class LinkElement(BaseTextElement):
|
||||
|
||||
class LinkElement(Element):
|
||||
"""
|
||||
A simple link.
|
||||
"""
|
||||
|
@ -141,6 +136,7 @@ class LinkElement(BaseTextElement):
|
|||
AUTO = "auto"
|
||||
FULL = "full"
|
||||
|
||||
value = ExpressionField(default="")
|
||||
navigation_type = models.CharField(
|
||||
choices=NAVIGATION_TYPES.choices,
|
||||
help_text="The navigation type.",
|
||||
|
|
|
@ -58,7 +58,12 @@ class ElementType(
|
|||
other_properties = {key: getattr(element, key) for key in self.allowed_fields}
|
||||
|
||||
serialized = self.SerializedDict(
|
||||
id=element.id, type=self.type, order=element.order, **other_properties
|
||||
id=element.id,
|
||||
type=self.type,
|
||||
order=element.order,
|
||||
style_padding_top=element.style_padding_top,
|
||||
style_padding_bottom=element.style_padding_bottom,
|
||||
**other_properties
|
||||
)
|
||||
|
||||
return serialized
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
# Generated by Django 3.2.18 on 2023-06-07 20:23
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
("builder", "0013_datasource"),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name="element",
|
||||
name="style_padding_bottom",
|
||||
field=models.PositiveIntegerField(default=10),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="element",
|
||||
name="style_padding_top",
|
||||
field=models.PositiveIntegerField(default=10),
|
||||
),
|
||||
]
|
|
@ -49,12 +49,16 @@ def test_builder_application_export(data_fixture):
|
|||
"id": element1.id,
|
||||
"type": "heading",
|
||||
"order": element1.order,
|
||||
"style_padding_top": 10,
|
||||
"style_padding_bottom": 10,
|
||||
"value": element1.value,
|
||||
"level": element1.level,
|
||||
},
|
||||
{
|
||||
"id": element2.id,
|
||||
"type": "paragraph",
|
||||
"style_padding_top": 10,
|
||||
"style_padding_bottom": 10,
|
||||
"order": element2.order,
|
||||
"value": element2.value,
|
||||
},
|
||||
|
@ -70,6 +74,8 @@ def test_builder_application_export(data_fixture):
|
|||
{
|
||||
"id": element3.id,
|
||||
"type": "heading",
|
||||
"style_padding_top": 10,
|
||||
"style_padding_bottom": 10,
|
||||
"order": element3.order,
|
||||
"value": element3.value,
|
||||
"level": element3.level,
|
||||
|
|
|
@ -1,38 +1,44 @@
|
|||
<template>
|
||||
<div class="element__menu">
|
||||
<div class="element-preview__menu">
|
||||
<div
|
||||
v-if="isDuplicating"
|
||||
class="loading element__menu-duplicate-loading"
|
||||
class="loading element-preview__menu-duplicate-loading"
|
||||
></div>
|
||||
<a v-else class="element__menu-item" @click="$emit('duplicate')">
|
||||
<a v-else class="element-preview__menu-item" @click="$emit('duplicate')">
|
||||
<i class="fas fa-copy"></i>
|
||||
<span class="element__menu-item-description">
|
||||
<span class="element-preview__menu-item-description">
|
||||
{{ $t('action.duplicate') }}
|
||||
</span>
|
||||
</a>
|
||||
<a
|
||||
class="element__menu-item"
|
||||
class="element-preview__menu-item"
|
||||
:class="{ disabled: moveUpDisabled }"
|
||||
@click="!moveUpDisabled && $emit('move', PLACEMENTS.BEFORE)"
|
||||
>
|
||||
<i class="fas fa-arrow-up"></i>
|
||||
<span v-if="!moveUpDisabled" class="element__menu-item-description">
|
||||
<span
|
||||
v-if="!moveUpDisabled"
|
||||
class="element-preview__menu-item-description"
|
||||
>
|
||||
{{ $t('elementMenu.moveUp') }}
|
||||
</span>
|
||||
</a>
|
||||
<a
|
||||
class="element__menu-item"
|
||||
class="element-preview__menu-item"
|
||||
:class="{ disabled: moveDownDisabled }"
|
||||
@click="!moveDownDisabled && $emit('move', PLACEMENTS.AFTER)"
|
||||
>
|
||||
<i class="fas fa-arrow-down"></i>
|
||||
<span v-if="!moveDownDisabled" class="element__menu-item-description">
|
||||
<span
|
||||
v-if="!moveDownDisabled"
|
||||
class="element-preview__menu-item-description"
|
||||
>
|
||||
{{ $t('elementMenu.moveDown') }}
|
||||
</span>
|
||||
</a>
|
||||
<a class="element__menu-item" @click="$emit('delete')">
|
||||
<a class="element-preview__menu-item" @click="$emit('delete')">
|
||||
<i class="fas fa-trash"></i>
|
||||
<span class="element__menu-item-description">
|
||||
<span class="element-preview__menu-item-description">
|
||||
{{ $t('action.delete') }}
|
||||
</span>
|
||||
</a>
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
<template>
|
||||
<div
|
||||
class="element"
|
||||
:class="{ 'element--active': active, 'element--in-error': inError }"
|
||||
class="element-preview"
|
||||
:class="{
|
||||
'element-preview--active': active,
|
||||
'element-preview--in-error': inError,
|
||||
}"
|
||||
@click="$emit('selected')"
|
||||
>
|
||||
<InsertElementButton
|
||||
v-if="active"
|
||||
class="element__insert--top"
|
||||
class="element-preview__insert--top"
|
||||
@click="$emit('insert', PLACEMENTS.BEFORE)"
|
||||
/>
|
||||
<ElementMenu
|
||||
|
@ -18,15 +21,14 @@
|
|||
@move="$emit('move', $event)"
|
||||
@duplicate="$emit('duplicate')"
|
||||
/>
|
||||
<component
|
||||
:is="elementType.editComponent"
|
||||
class="element__component"
|
||||
<PageRootElement
|
||||
:element="element"
|
||||
:builder="builder"
|
||||
/>
|
||||
:mode="'editing'"
|
||||
></PageRootElement>
|
||||
<InsertElementButton
|
||||
v-if="active"
|
||||
class="element__insert--bottom"
|
||||
class="element-preview__insert--bottom"
|
||||
@click="$emit('insert', PLACEMENTS.AFTER)"
|
||||
/>
|
||||
</div>
|
||||
|
@ -35,10 +37,12 @@
|
|||
<script>
|
||||
import ElementMenu from '@baserow/modules/builder/components/elements/ElementMenu'
|
||||
import InsertElementButton from '@baserow/modules/builder/components/elements/InsertElementButton'
|
||||
import PageRootElement from '@baserow/modules/builder/components/page/PageRootElement'
|
||||
import { PLACEMENTS } from '@baserow/modules/builder/enums'
|
||||
|
||||
export default {
|
||||
name: 'ElementPreview',
|
||||
components: { ElementMenu, InsertElementButton },
|
||||
components: { ElementMenu, InsertElementButton, PageRootElement },
|
||||
inject: ['builder'],
|
||||
props: {
|
||||
element: {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<a class="element__insert" @click="$emit('click')">
|
||||
<a class="element-preview__insert" @click="$emit('click')">
|
||||
<i class="fas fa-plus"></i>
|
||||
</a>
|
||||
</template>
|
||||
|
|
|
@ -9,11 +9,11 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import textElement from '@baserow/modules/builder/mixins/elements/textElement'
|
||||
import element from '@baserow/modules/builder/mixins/element'
|
||||
|
||||
export default {
|
||||
name: 'HeadingElement',
|
||||
mixins: [textElement],
|
||||
mixins: [element],
|
||||
props: {
|
||||
/**
|
||||
* @type {Object}
|
||||
|
|
|
@ -9,10 +9,12 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import element from '@baserow/modules/builder/mixins/element'
|
||||
import { IMAGE_SOURCE_TYPES } from '@baserow/modules/builder/enums'
|
||||
|
||||
export default {
|
||||
name: 'ImageElement',
|
||||
mixins: [element],
|
||||
props: {
|
||||
/**
|
||||
* @type {Object}
|
||||
|
@ -36,6 +38,7 @@ export default {
|
|||
classes() {
|
||||
return {
|
||||
[`element--alignment-${this.element.alignment}`]: true,
|
||||
'element--no-value': !this.imageSource && !this.element.alt_text,
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,21 +1,15 @@
|
|||
<template>
|
||||
<div class="link-element" :class="classes">
|
||||
<Button
|
||||
v-if="element.variant === 'button'"
|
||||
tag="a"
|
||||
v-bind="extraAttr"
|
||||
:target="element.target"
|
||||
:full-width="element.width === 'full'"
|
||||
@click="onClick($event)"
|
||||
>
|
||||
{{ element.value || $t('linkElement.noValue') }}
|
||||
</Button>
|
||||
<a
|
||||
v-else
|
||||
class="link-element__link"
|
||||
:class="{
|
||||
'link-element__link': element.variant !== 'button',
|
||||
'link-element__button': element.variant === 'button',
|
||||
'link-element__button--full-width':
|
||||
element.variant === 'button' && element.width === 'full',
|
||||
}"
|
||||
v-bind="extraAttr"
|
||||
:target="`_${element.target}`"
|
||||
@click="onClick($event)"
|
||||
@click="onClick"
|
||||
>
|
||||
{{ element.value || $t('linkElement.noValue') }}
|
||||
</a>
|
||||
|
@ -23,7 +17,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import textElement from '@baserow/modules/builder/mixins/elements/textElement'
|
||||
import element from '@baserow/modules/builder/mixins/element'
|
||||
import { LinkElementType } from '@baserow/modules/builder/elementTypes'
|
||||
|
||||
/**
|
||||
|
@ -40,18 +34,7 @@ import { LinkElementType } from '@baserow/modules/builder/elementTypes'
|
|||
|
||||
export default {
|
||||
name: 'LinkElement',
|
||||
mixins: [textElement],
|
||||
props: {
|
||||
/**
|
||||
* @type {LinkElement}
|
||||
*/
|
||||
element: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
builder: { type: Object, required: true },
|
||||
mode: { type: String, required: true },
|
||||
},
|
||||
mixins: [element],
|
||||
computed: {
|
||||
classes() {
|
||||
return {
|
||||
|
@ -100,6 +83,11 @@ export default {
|
|||
},
|
||||
methods: {
|
||||
onClick(event) {
|
||||
if (this.mode === 'editing') {
|
||||
event.preventDefault()
|
||||
return
|
||||
}
|
||||
|
||||
if (!this.url) {
|
||||
event.preventDefault()
|
||||
} else if (
|
||||
|
|
|
@ -1,65 +0,0 @@
|
|||
<template>
|
||||
<div class="link-element" :class="classes">
|
||||
<Button
|
||||
v-if="element.variant === 'button'"
|
||||
tag="a"
|
||||
v-bind="extraAttr"
|
||||
:target="element.target"
|
||||
:full-width="element.width === 'full'"
|
||||
@click.prevent
|
||||
>
|
||||
{{ element.value || $t('linkElement.noValue') }}
|
||||
</Button>
|
||||
<a
|
||||
v-else
|
||||
class="link-element__link"
|
||||
v-bind="extraAttr"
|
||||
:target="`_${element.target}`"
|
||||
@click.prevent
|
||||
>
|
||||
{{ element.value || $t('linkElement.noValue') }}
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import textElement from '@baserow/modules/builder/mixins/elements/textElement'
|
||||
import { LinkElementType } from '@baserow/modules/builder/elementTypes'
|
||||
|
||||
export default {
|
||||
name: 'LinkElementEdit',
|
||||
mixins: [textElement],
|
||||
props: {
|
||||
/**
|
||||
* @type {LinkElement}
|
||||
*/
|
||||
element: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
builder: { type: Object, required: true },
|
||||
},
|
||||
computed: {
|
||||
classes() {
|
||||
return {
|
||||
[`element--alignment-${this.element.alignment}`]: true,
|
||||
'element--no-value': !this.element.value,
|
||||
}
|
||||
},
|
||||
extraAttr() {
|
||||
const attr = {}
|
||||
if (this.url) {
|
||||
attr.href = this.url
|
||||
}
|
||||
return attr
|
||||
},
|
||||
url() {
|
||||
try {
|
||||
return LinkElementType.getUrlFromElement(this.element, this.builder)
|
||||
} catch (e) {
|
||||
return ''
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
|
@ -14,7 +14,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import textElement from '@baserow/modules/builder/mixins/elements/textElement'
|
||||
import element from '@baserow/modules/builder/mixins/element'
|
||||
import { generateHash } from '@baserow/modules/core/utils/hashing'
|
||||
|
||||
/**
|
||||
|
@ -25,7 +25,7 @@ import { generateHash } from '@baserow/modules/core/utils/hashing'
|
|||
|
||||
export default {
|
||||
name: 'ParagraphElement',
|
||||
mixins: [textElement],
|
||||
mixins: [element],
|
||||
props: {
|
||||
/**
|
||||
* @type {Object}
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
<template>
|
||||
<div>
|
||||
<component
|
||||
:is="getType(element).component"
|
||||
<PageRootElement
|
||||
v-for="element in elements"
|
||||
:key="element.id"
|
||||
:element="element"
|
||||
class="element__component"
|
||||
:builder="builder"
|
||||
:mode="mode"
|
||||
/>
|
||||
|
@ -13,7 +11,10 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import PageRootElement from '@baserow/modules/builder/components/page/PageRootElement'
|
||||
|
||||
export default {
|
||||
components: { PageRootElement },
|
||||
inject: ['builder', 'mode'],
|
||||
props: {
|
||||
page: {
|
||||
|
@ -33,10 +34,5 @@ export default {
|
|||
required: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getType(element) {
|
||||
return this.$registry.get('element', element.type)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
<template functional>
|
||||
<!--
|
||||
This element is supposed to be wrapping the root elements on a page. They allow
|
||||
setting a width, background, borders, and more, but this only makes sense if they're
|
||||
added to the root of the page. Child elements in for example a containing element must
|
||||
not be wrapped by this component.
|
||||
-->
|
||||
<div
|
||||
class="page-root-element"
|
||||
:style="{
|
||||
'padding-top': `${props.element.style_padding_top || 0}px`,
|
||||
'padding-bottom': `${props.element.style_padding_bottom || 0}px`,
|
||||
}"
|
||||
>
|
||||
<div class="page-root-element__inner">
|
||||
<component
|
||||
:is="$options.methods.getComponent(parent, props.element, props.mode)"
|
||||
:element="props.element"
|
||||
:builder="props.builder"
|
||||
:mode="props.mode"
|
||||
class="element"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'PageRootElement',
|
||||
props: {
|
||||
element: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
builder: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
mode: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getComponent(parent, element, mode) {
|
||||
const elementType = parent.$registry.get('element', element.type)
|
||||
const componentName = mode === 'editing' ? 'editComponent' : 'component'
|
||||
return elementType[componentName]
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
|
@ -11,50 +11,10 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { mapActions, mapGetters } from 'vuex'
|
||||
import { notifyIf } from '@baserow/modules/core/utils/error'
|
||||
import { clone } from '@baserow/modules/core/utils/object'
|
||||
import _ from 'lodash'
|
||||
import elementSidePanel from '@baserow/modules/builder/mixins/elementSidePanel'
|
||||
|
||||
export default {
|
||||
name: 'GeneralSidePanel',
|
||||
inject: ['builder'],
|
||||
computed: {
|
||||
...mapGetters({
|
||||
element: 'element/getSelected',
|
||||
}),
|
||||
|
||||
elementType() {
|
||||
if (this.element) {
|
||||
return this.$registry.get('element', this.element.type)
|
||||
}
|
||||
return null
|
||||
},
|
||||
|
||||
defaultValues() {
|
||||
return this.element
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
actionDebouncedUpdateSelectedElement: 'element/debouncedUpdateSelected',
|
||||
}),
|
||||
async onChange(newValues) {
|
||||
const oldValues = this.element
|
||||
if (!_.isEqual(newValues, oldValues)) {
|
||||
try {
|
||||
await this.actionDebouncedUpdateSelectedElement({
|
||||
// Here we clone the values to prevent
|
||||
// "modification oustide of the store" error
|
||||
values: clone(newValues),
|
||||
})
|
||||
} catch (error) {
|
||||
// Restore the previous saved values from the store
|
||||
this.$refs.elementForm.reset()
|
||||
notifyIf(error)
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
mixins: [elementSidePanel],
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
<template>
|
||||
<form @submit.prevent>
|
||||
<FormElement class="control">
|
||||
<label class="control__label">{{ label }}</label>
|
||||
<div class="control__elements">
|
||||
<input
|
||||
v-model="values[paddingName]"
|
||||
type="number"
|
||||
class="input"
|
||||
:class="{ 'input--error': $v.values[paddingName].$error }"
|
||||
@blur="$v.values[paddingName].$touch()"
|
||||
/>
|
||||
<div v-if="$v.values[paddingName].$error" class="error">
|
||||
{{ $t('styleBoxForm.paddingError') }}
|
||||
</div>
|
||||
</div>
|
||||
</FormElement>
|
||||
</form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { required, integer, between } from 'vuelidate/lib/validators'
|
||||
import form from '@baserow/modules/core/mixins/form'
|
||||
|
||||
export default {
|
||||
name: 'StyleBoxForm',
|
||||
mixins: [form],
|
||||
props: {
|
||||
label: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
paddingName: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
allowedValues: [this.paddingName],
|
||||
values: {
|
||||
[this.paddingName]: 0,
|
||||
},
|
||||
}
|
||||
},
|
||||
validations() {
|
||||
return {
|
||||
values: {
|
||||
[this.paddingName]: {
|
||||
required,
|
||||
integer,
|
||||
between: between(0, 200),
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
emitChange(newValues) {
|
||||
if (this.isFormValid()) {
|
||||
this.$emit('values-changed', newValues)
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
|
@ -1,12 +1,27 @@
|
|||
<template>
|
||||
<div>Style panel</div>
|
||||
<div :key="element.id">
|
||||
<StyleBoxForm
|
||||
:label="$t('styleSidePanel.paddingTop')"
|
||||
padding-name="style_padding_top"
|
||||
:default-values="defaultValues"
|
||||
@values-changed="onChange($event)"
|
||||
></StyleBoxForm>
|
||||
<StyleBoxForm
|
||||
:label="$t('styleSidePanel.paddingBottom')"
|
||||
padding-name="style_padding_bottom"
|
||||
:default-values="defaultValues"
|
||||
@values-changed="onChange($event)"
|
||||
></StyleBoxForm>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import elementSidePanel from '@baserow/modules/builder/mixins/elementSidePanel'
|
||||
import StyleBoxForm from '@baserow/modules/builder/components/page/sidePanels/StyleBoxForm.vue'
|
||||
|
||||
export default {
|
||||
name: 'StyleSidePanel',
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
components: { StyleBoxForm },
|
||||
mixins: [elementSidePanel],
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -2,7 +2,6 @@ import { Registerable } from '@baserow/modules/core/registry'
|
|||
import ParagraphElement from '@baserow/modules/builder/components/elements/components/ParagraphElement'
|
||||
import HeadingElement from '@baserow/modules/builder/components/elements/components/HeadingElement'
|
||||
import LinkElement from '@baserow/modules/builder/components/elements/components/LinkElement'
|
||||
import LinkElementEdit from '@baserow/modules/builder/components/elements/components/LinkElementEdit'
|
||||
import ParagraphElementForm from '@baserow/modules/builder/components/elements/components/forms/ParagraphElementForm'
|
||||
import HeadingElementForm from '@baserow/modules/builder/components/elements/components/forms/HeadingElementForm'
|
||||
import LinkElementForm from '@baserow/modules/builder/components/elements/components/forms/LinkElementForm'
|
||||
|
@ -131,10 +130,6 @@ export class LinkElementType extends ElementType {
|
|||
return LinkElement
|
||||
}
|
||||
|
||||
get editComponent() {
|
||||
return LinkElementEdit
|
||||
}
|
||||
|
||||
get formComponent() {
|
||||
return LinkElementForm
|
||||
}
|
||||
|
|
20
web-frontend/modules/builder/mixins/element.js
Normal file
20
web-frontend/modules/builder/mixins/element.js
Normal file
|
@ -0,0 +1,20 @@
|
|||
export default {
|
||||
props: {
|
||||
element: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
builder: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
mode: {
|
||||
// editing = being editing by the page editor
|
||||
// preview = previewing the application
|
||||
// public = publicly published application
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
}
|
46
web-frontend/modules/builder/mixins/elementSidePanel.js
Normal file
46
web-frontend/modules/builder/mixins/elementSidePanel.js
Normal file
|
@ -0,0 +1,46 @@
|
|||
import { mapActions, mapGetters } from 'vuex'
|
||||
import _ from 'lodash'
|
||||
|
||||
import { clone } from '@baserow/modules/core/utils/object'
|
||||
import { notifyIf } from '@baserow/modules/core/utils/error'
|
||||
|
||||
export default {
|
||||
inject: ['builder'],
|
||||
computed: {
|
||||
...mapGetters({
|
||||
element: 'element/getSelected',
|
||||
}),
|
||||
|
||||
elementType() {
|
||||
if (this.element) {
|
||||
return this.$registry.get('element', this.element.type)
|
||||
}
|
||||
return null
|
||||
},
|
||||
|
||||
defaultValues() {
|
||||
return this.element
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
actionDebouncedUpdateSelectedElement: 'element/debouncedUpdateSelected',
|
||||
}),
|
||||
async onChange(newValues) {
|
||||
const oldValues = this.element
|
||||
if (!_.isEqual(newValues, oldValues)) {
|
||||
try {
|
||||
await this.actionDebouncedUpdateSelectedElement({
|
||||
// Here we clone the values to prevent
|
||||
// "modification oustide of the store" error
|
||||
values: clone(newValues),
|
||||
})
|
||||
} catch (error) {
|
||||
// Restore the previous saved values from the store
|
||||
this.$refs.elementForm.reset()
|
||||
notifyIf(error)
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
export default {
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'Some temp default value',
|
||||
},
|
||||
},
|
||||
}
|
|
@ -72,5 +72,14 @@ export default {
|
|||
mode,
|
||||
}
|
||||
},
|
||||
head() {
|
||||
return {
|
||||
titleTemplate: '',
|
||||
title: this.page.name,
|
||||
bodyAttrs: {
|
||||
class: 'public-page',
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -5,9 +5,11 @@
|
|||
@import 'add_element_modal';
|
||||
@import 'page_preview';
|
||||
@import 'element';
|
||||
@import 'element_preview';
|
||||
@import 'side_panels';
|
||||
@import 'empty_side_panel_state';
|
||||
@import 'page';
|
||||
@import 'page_editor';
|
||||
@import 'page_root_element';
|
||||
@import 'page_settings_path_params_form_element';
|
||||
@import 'domain_card';
|
||||
@import 'dns_status';
|
||||
|
@ -15,5 +17,6 @@
|
|||
@import 'integration_settings';
|
||||
@import 'last_published_domain_date';
|
||||
@import 'publish_action_modal';
|
||||
@import 'public_page';
|
||||
@import 'data_source_context';
|
||||
@import 'data_source_form';
|
||||
|
|
|
@ -1,181 +1,19 @@
|
|||
.element__menu {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
border: solid 1px $color-neutral-400;
|
||||
border-radius: 3px;
|
||||
z-index: 2;
|
||||
|
||||
@include absolute(5px, 5px, auto, auto);
|
||||
}
|
||||
|
||||
.element__insert {
|
||||
@include center-text(26px, 10px);
|
||||
|
||||
display: block;
|
||||
border-radius: 100%;
|
||||
border: solid 1px $color-neutral-300;
|
||||
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.16);
|
||||
color: $color-primary-900;
|
||||
background-color: $white;
|
||||
|
||||
&:hover {
|
||||
background-color: $color-neutral-50;
|
||||
box-shadow: 0 3px 5px 0 rgba(0, 0, 0, 0.32);
|
||||
}
|
||||
|
||||
&--top,
|
||||
&--bottom {
|
||||
@include absolute(-13px, auto, auto, 50%);
|
||||
|
||||
margin-left: -12px;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
&--bottom {
|
||||
top: auto;
|
||||
bottom: -12px;
|
||||
}
|
||||
}
|
||||
|
||||
.element {
|
||||
position: relative;
|
||||
|
||||
.element__insert {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.element__menu {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.element__insert {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.element__menu {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.element--active) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&.element--active {
|
||||
cursor: inherit;
|
||||
|
||||
&::before {
|
||||
@include absolute(0, 0, 0, 0);
|
||||
|
||||
content: '';
|
||||
border: solid 1px $color-primary-500;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.element__insert {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.element__menu-item-description {
|
||||
@include absolute(-25px, -2px, auto, auto);
|
||||
|
||||
display: none;
|
||||
background-color: $color-neutral-900;
|
||||
font-size: 11px;
|
||||
color: $white;
|
||||
line-height: 20px;
|
||||
padding: 0 4px;
|
||||
border-radius: 3px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.element__menu-item {
|
||||
@include center-text(24px, 9px);
|
||||
|
||||
position: relative;
|
||||
background-color: $white;
|
||||
color: $color-primary-900;
|
||||
|
||||
&:hover {
|
||||
background-color: $color-neutral-100;
|
||||
|
||||
.element__menu-item-description {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
border-top-left-radius: 3px;
|
||||
border-bottom-left-radius: 3px;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-top-right-radius: 3px;
|
||||
border-bottom-right-radius: 3px;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
cursor: inherit;
|
||||
color: $color-neutral-300;
|
||||
|
||||
&:hover {
|
||||
background-color: $white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.element__component {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.element__menu-duplicate-loading {
|
||||
margin: 5px;
|
||||
// this is a placeholder, the class will be added to every element component.
|
||||
}
|
||||
|
||||
.element--no-value {
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.element--in-error::after {
|
||||
@extend .fas;
|
||||
|
||||
@include fa-icon;
|
||||
@include absolute(0, 0, auto, auto);
|
||||
|
||||
content: fa-content($fa-var-exclamation-circle);
|
||||
pointer-events: none;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
font-size: 20px;
|
||||
margin-right: 5px;
|
||||
margin-top: 5px;
|
||||
color: $color-error-300;
|
||||
}
|
||||
|
||||
.element--alignment-left {
|
||||
justify-content: start;
|
||||
|
||||
.button--full-width {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
.element--alignment-center {
|
||||
justify-content: center;
|
||||
|
||||
.button--full-width {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.element--alignment-right {
|
||||
justify-content: end;
|
||||
|
||||
.button--full-width {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,149 @@
|
|||
.element-preview__menu {
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
border: solid 1px $color-neutral-400;
|
||||
border-radius: 3px;
|
||||
z-index: 2;
|
||||
|
||||
@include absolute(5px, 5px, auto, auto);
|
||||
}
|
||||
|
||||
.element-preview__insert {
|
||||
@include center-text(26px, 10px);
|
||||
|
||||
display: block;
|
||||
border-radius: 100%;
|
||||
border: solid 1px $color-neutral-300;
|
||||
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.16);
|
||||
color: $color-primary-900;
|
||||
background-color: $white;
|
||||
|
||||
&:hover {
|
||||
background-color: $color-neutral-50;
|
||||
box-shadow: 0 3px 5px 0 rgba(0, 0, 0, 0.32);
|
||||
}
|
||||
|
||||
&--top,
|
||||
&--bottom {
|
||||
@include absolute(-13px, auto, auto, 50%);
|
||||
|
||||
margin-left: -12px;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
&--bottom {
|
||||
top: auto;
|
||||
bottom: -12px;
|
||||
}
|
||||
}
|
||||
|
||||
.element-preview {
|
||||
position: relative;
|
||||
|
||||
.element-preview__insert {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.element-preview__menu {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.element-preview__insert {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.element-preview__menu {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(.element-preview--active) {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
&.element-preview--active {
|
||||
cursor: inherit;
|
||||
|
||||
&::before {
|
||||
@include absolute(0, 0, 0, 0);
|
||||
|
||||
content: '';
|
||||
border: solid 1px $color-primary-500;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.element-preview__insert {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.element-preview__menu-item-description {
|
||||
@include absolute(-25px, -2px, auto, auto);
|
||||
|
||||
display: none;
|
||||
background-color: $color-neutral-900;
|
||||
font-size: 11px;
|
||||
color: $white;
|
||||
line-height: 20px;
|
||||
padding: 0 4px;
|
||||
border-radius: 3px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.element-preview__menu-item {
|
||||
@include center-text(24px, 9px);
|
||||
|
||||
position: relative;
|
||||
background-color: $white;
|
||||
color: $color-primary-900;
|
||||
|
||||
&:hover {
|
||||
background-color: $color-neutral-100;
|
||||
|
||||
.element-preview__menu-item-description {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
border-top-left-radius: 3px;
|
||||
border-bottom-left-radius: 3px;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
border-top-right-radius: 3px;
|
||||
border-bottom-right-radius: 3px;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
cursor: inherit;
|
||||
color: $color-neutral-300;
|
||||
|
||||
&:hover {
|
||||
background-color: $white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.element-preview__menu-duplicate-loading {
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.element-preview--in-error::after {
|
||||
@extend .fas;
|
||||
|
||||
@include fa-icon;
|
||||
@include absolute(0, 0, auto, auto);
|
||||
|
||||
content: fa-content($fa-var-exclamation-circle);
|
||||
pointer-events: none;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
line-height: 20px;
|
||||
font-size: 20px;
|
||||
margin-right: 5px;
|
||||
margin-top: 5px;
|
||||
color: $color-error-300;
|
||||
}
|
|
@ -1,2 +1,2 @@
|
|||
@import 'paragraphElementForm';
|
||||
@import 'linkElementForm';
|
||||
@import 'paragraph_element_form';
|
||||
@import 'link_element_form';
|
||||
|
|
|
@ -10,3 +10,11 @@
|
|||
color: $color-neutral-500;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.link-element-form__params-error {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
text-align: center;
|
||||
align-items: center;
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
.paragraph-element-form__value {
|
||||
resize: vertical;
|
||||
color: $color-primary-900;
|
||||
}
|
||||
|
||||
.link-element-form__params-error {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
text-align: center;
|
||||
align-items: center;
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
.paragraph-element-form__value {
|
||||
resize: vertical;
|
||||
color: $color-primary-900;
|
||||
}
|
|
@ -1,29 +1,29 @@
|
|||
.heading-element {
|
||||
margin: 0;
|
||||
color: $black;
|
||||
}
|
||||
|
||||
h1.heading-element {
|
||||
font-size: 24px;
|
||||
padding: 32px 82px;
|
||||
font-size: 30px;
|
||||
}
|
||||
|
||||
h2.heading-element {
|
||||
font-size: 20px;
|
||||
padding: 28px 82px;
|
||||
font-size: 26px;
|
||||
}
|
||||
|
||||
h3.heading-element {
|
||||
font-size: 18px;
|
||||
padding: 24px 82px;
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
h4.heading-element {
|
||||
font-size: 16px;
|
||||
padding: 20px 82px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
h5.heading-element {
|
||||
font-size: 15px;
|
||||
padding: 18px 82px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
h6.heading-element {
|
||||
font-size: 14px;
|
||||
padding: 16px 82px;
|
||||
font-style: italic;
|
||||
}
|
||||
|
|
|
@ -1,13 +1,45 @@
|
|||
.link-element {
|
||||
display: flex;
|
||||
padding: 5px 82px;
|
||||
}
|
||||
|
||||
.link-element__link {
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
padding: 0;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
border: 1px solid transparent;
|
||||
color: $black;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.link-element__button {
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
color: $white;
|
||||
background-color: $black;
|
||||
line-height: 28px;
|
||||
padding: 0 12px;
|
||||
border-radius: 3px;
|
||||
border: none;
|
||||
white-space: nowrap;
|
||||
text-align: left;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
background-color: lighten($black, 10%);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
background-color: lighten($black, 20%);
|
||||
}
|
||||
|
||||
&--full-width {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.element--alignment-center & {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.element--alignment-right & {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
.paragraph-element {
|
||||
font-size: 14px;
|
||||
padding: 2px 82px;
|
||||
margin: 0;
|
||||
color: $black;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
.page-root-element__inner {
|
||||
padding: 0 20px;
|
||||
margin: 0 auto;
|
||||
max-width: $builder-page-max-width;
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
.public-page {
|
||||
background-color: $white;
|
||||
}
|
|
@ -102,3 +102,5 @@ $file-field-modal-body-nav-width: 120px !default;
|
|||
$file-field-modal-foot-height: 108px !default;
|
||||
|
||||
$dashboard-breakpoint: 1100px;
|
||||
|
||||
$builder-page-max-width: 1280px;
|
||||
|
|
|
@ -524,5 +524,12 @@
|
|||
},
|
||||
"dropdown": {
|
||||
"empty": "No items available"
|
||||
},
|
||||
"styleSidePanel": {
|
||||
"paddingTop": "Padding top",
|
||||
"paddingBottom": "Padding bottom"
|
||||
},
|
||||
"styleBoxForm": {
|
||||
"paddingError": "The value must be an integer between 0 and 200."
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue