Skip to content

Content builder ​

Content builder umożliwia redaktorowi układanie treści strony z bloków. Struktura jest trójpoziomowa i w całości mieści się wewnątrz formularza vee-validate — wartości nie są przechowywane w osobnym store.

Te same typy treści w stałym formularzu

Gdy struktura treści ma być narzucona przez dewelopera, a nie układana przez redaktora, zamiast buildera używa się Custom fields. Oba mechanizmy korzystają z tych samych typów treści i tych samych modeli po stronie backendu.

Hierarchia ​

section_groups[]          grupa sekcji
└── sections[]            sekcja
    └── items[]           blok (element sekcji)
PoziomKomponent polaDefinicja formularza
korzeńform/VContentBuilder.vue—
grupaContentBuilderSectionGroupField.vueforms/section-group.form.js
sekcjaContentBuilderSectionsField.vue, ContentBuilderSectionField.vueforms/section.form.js
blokContentBuilderSectionItemField.vueforms/section-item.form.js

Każdy poziom stanowi useFieldArray opakowany w vuedraggable z uchwytem .drag-handle.

Zastosowanie w formularzu ​

vue
<!-- src/modules/blog/components/BlogArticleForm.vue -->
<base-form-section class="q-mb-lg">
  <v-content-builder />
</base-form-section>
js
// src/modules/blog/forms/article.form.js
initialValues() {
  return {
    // ...
    section_groups: [],
  };
},

validationSchema() {
  return object({
    // ...
    section_groups: array().of(getContentBuilderValidationSchema()),
  });
}

Propsy komponentu VContentBuilder ​

PropTypDomyślnieOpis
nameString"section_groups"Ścieżka pola w formularzu
modeStringnullWartość "shared-section" ukrywa typ bloku odpowiadający sekcji współdzielonej

Wartość name musi odpowiadać wartościom początkowym i schematowi

Zmieniając prop name, zmień jednocześnie klucz w initialValues() oraz w validationSchema().

Transformacja danych z API ​

Zapytanie o szczegóły musi przepuścić dane przez transformContentBuilderData:

js
// src/modules/blog/api/queries/article.queries.js
queryFn: async () => {
  const { data } = await getArticle(toValue(id));

  return {
    ...data.model,
    section_groups: transformContentBuilderData(data.model.section_groups),
  };
},

Bez tej transformacji builder nie wczyta treści

Funkcja transformContentBuilderData normalizuje strukturę otrzymaną z backendu, w tym kolekcje mediów, do kształtu oczekiwanego przez pola formularza. Uwzględnij ją w każdym modelu korzystającym z content buildera — jej brak objawia się pustym builderem podczas edycji, bez żadnego błędu.

Rejestr typów bloków ​

Typy bloków zdefiniowane są w dwóch plikach:

js
// src/modules/contentBuilder/constants/section-types.js
export const HEADER = "header";
export const TEXT = "text";
export const TEXT_IMAGE = "text-image";
export const YOUTUBE = "youtube";
export const IMAGE = "image";
export const FILE = "file";
export const SHARED_SECTION = "shared-section";
export const CUSTOM_ITEM = "custom-item";
export const OTHER = "other";
js
// src/modules/contentBuilder/configs/section-type-config.js
export const sectionTypeConfig = {
  [HEADER]: {
    component: () =>
      import("modules/contentBuilder/components/sectionItem/ContentBuilderSectionItemHeader.vue"),

    getFields: () => ({
      title: "",
      tag: "h1",
    }),

    validation: {
      title: string().required().label("labels.title"),
      tag: string().required().label("labels.type"),
    },

    itemOptions: {
      layout: null,
      hasGaps: false,
      hasImgClasses: false,
    },

    customComponents: [],

    allowCopy: true,
    allowDelete: true,
  },
  // ...
};

Pola konfiguracji typu ​

PoleTypOpis
componentfunkcjaFabryka dynamicznego importu komponentu bloku
getFieldsfunkcjaZwraca wartości początkowe pól bloku
validationobiektKształt yup przekazywany do object() — walidacja bloku
itemOptions.layoutstring | nullWskazuje zestaw opcji układu do wyświetlenia
itemOptions.hasGapsbooleanWłącza ustawienia odstępów
itemOptions.hasImgClassesbooleanWłącza ustawienia klas obrazu
customComponentstablicaDodatkowe komponenty opcji
allowCopybooleanZezwala na kopiowanie bloku
allowDeletebooleanZezwala na usunięcie bloku

Dodawanie nowego typu bloku ​

Krok 1: dodaj stałą typu ​

js
// src/modules/contentBuilder/constants/section-types.js
export const QUOTE = "quote";

Krok 2: utwórz komponent bloku ​

vue
<!-- src/modules/contentBuilder/components/sectionItem/ContentBuilderSectionItemQuote.vue -->
<script setup>
const props = defineProps({
  name: { type: String, required: true },
});
</script>

<template>
  <div class="row q-col-gutter-md">
    <v-input class="col-12" :name="`${name}.quote`" :label="$t('labels.quote')" />
    <v-input class="col-12" :name="`${name}.author`" :label="$t('labels.author')" />
  </div>
</template>

Prop name zawiera pełną ścieżkę pola

Komponent bloku otrzymuje ścieżkę w rodzaju section_groups[0].sections[1].items[0]. Nazwy pól buduj więc zawsze przez interpolację — `${name}.quote`. Nazwa zapisana na sztywno nie podepnie się pod formularz i przestanie działać przy przestawianiu bloków.

Krok 3: dopisz wpis w rejestrze ​

js
// src/modules/contentBuilder/configs/section-type-config.js
[QUOTE]: {
  component: () =>
    import("modules/contentBuilder/components/sectionItem/ContentBuilderSectionItemQuote.vue"),

  getFields: () => ({
    quote: "",
    author: "",
  }),

  validation: {
    quote: string().required().label("labels.quote"),
    author: string().nullable().label("labels.author"),
  },

  itemOptions: {
    layout: null,
    hasGaps: false,
    hasImgClasses: false,
  },

  customComponents: [],

  allowCopy: true,
  allowDelete: true,
},

Krok 4: uzupełnij tłumaczenia ​

Dodaj etykiety w src/modules/contentBuilder/trans/{en,pl} — opisują to Tłumaczenia.

Krok 5: zgłoś typ po stronie backendu ​

Lista typów pochodzi z API

Dostępne typy pobierane są zapytaniem GET content-builder/section-types do store'a content-builder. Sam wpis w sectionTypeConfig nie wystarczy — dopóki backend nie zwróci nowego typu, nie pojawi się on w selektorze dodawania bloku.

Renderowanie bloku w czasie działania ​

js
// src/modules/contentBuilder/components/sectionItem/ContentBuilderSectionItemField.vue
const { componentIs, componentAttrs } = useContentBuilderSectionItemComponent(
  sectionItem,
  () => props.type,
);
vue
<component :is="componentIs" :name="name" v-bind="componentAttrs" />

Composable odczytuje sectionTypeConfig[type].component i opakowuje go w defineAsyncComponent. Dla typu custom-item dokłada dodatkowo prop options, pochodzący z pola option_settings bloku.

Dodawanie bloków przez użytkownika ​

Komponent ContentBuilderSectionSelect.vue to niewielki formularz vee-validate z polami section_type oraz — dla typów oznaczonych jako selectable — sectionable. Emituje zdarzenia add i copy z ładunkiem { sectionType, sectionable }.

Dane do wyboru pochodzą ze store'a content-builder:

AkcjaEndpoint
fetchSectionTypes()GET content-builder/section-types
fetchSectionables({ type })GET content-builder/sectionables?type=...

Getter sectionTypesBasedOnMode filtruje listę — w trybie "shared-section" usuwa typ shared-section, uniemożliwiając zagnieżdżenie sekcji współdzielonej w niej samej.

Wartości początkowe nowego bloku tworzy sectionItemForm:

js
// src/modules/contentBuilder/forms/section-item.form.js
sectionItemForm.initialValues(type, currentValues?)        // zwykły blok
sectionItemForm.selectableInitialValues(type, sectionable) // blok wskazujący na inny byt
sectionItemForm.copyValues(item, isSelectable)             // kopia bez identyfikatora

Opcje bloków, sekcji i grup ​

js
import { getOptions } from "modules/contentBuilder/helpers/forms/options";

getOptions("group");   // klasy, kontener, marginesy, ustawienia flex/grid
getOptions("section"); // jak wyżej
getOptions("item");    // layout, gap_x, gap_y, img_classes

Kluczami wspólnymi są classes, custom_component, in_container, custom_padding oraz custom_margin.

Za renderowanie opcji odpowiadają komponenty options/ContentBuilderSectionOptionsField.vue, options/ContentBuilderSectionItemOptionsField.vue oraz ContentBuilderOptionSettingsField.vue. Dostępne układy zdefiniowane są w pliku constants/section-item-layouts.js.

Zakres opcji widocznych dla redaktora wynika z pola itemOptions w rejestrze typu.

Sekcje współdzielone ​

Sekcja współdzielona to zestaw grup sekcji zapisany jednokrotnie i wstawiany do wielu stron.

ElementPlik
FormularzContentBuilderSharedSectionForm.vue
ListaContentBuilderSharedSectionsTable.vue
Stronypages/sharedSections/

Podczas edycji samej sekcji współdzielonej builder uruchamiany jest w trybie ograniczonym:

vue
<!-- ContentBuilderSharedSectionForm.vue -->
<v-content-builder mode="shared-section" />

W formularzu docelowej strony sekcja jest wstawiana jako blok typu shared-section. Karta pozwala przejść do jej edycji bez opuszczania formularza strony; zapis aktualizuje sekcję współdzieloną. Custom fields zależne od typu są rejestrowane osobno w src/modules/contentBuilder/constants/shared-section-custom-fields.js. Nie są kopiowane do custom fields strony. Szczegóły: Pola sekcji współdzielonych.

Made by Moonbite