profound-logoProfound CMS
⌘K
Admin
Theme
DocsTutorialBlogPhilosophy
DocsTutorialBlogPhilosophy

Hybrid

Collection of Pages with ComponentsTypy komponentówSetup server sent events (SSE) content refetchInstall Profound CMS as a proxySkrypty w kreatorze szablonówProject ScaffoldingBiblioteka multimediów

Headless

Szybki startSplit Screen JSON Component Builder with LLMComponent Zod Pull

REST API

Przegląd REST APIgetConnect your websitegetGET /routesgetGET /routegetGET /blocksgetGET /blocks/with-cel-cachegetGET /blocks/generatedgetGET /componentsgetGET /components/{name}getGET /dataset/{schema_name}getGET /content-changes (SSE)patchPATCH /dataset/{schema_name}postTłumaczenie postapatchKorekty tłumaczeńgetGET /usagepostPOST /csvpatchPATCH /csv
All Systems Operational
Powered Byprofound-logo
Theme

Skrypty w kreatorze szablonów

Praktyczny przewodnik po pisaniu wyrażeń CEL w systemie CMS.

Praktyczny przewodnik po pisaniu wyrażeń CEL w systemie CMS.


Jak działa CEL

CEL (Common Expression Language) to lekki język skryptowy wbudowany w nasz system CMS. Umożliwia tworzenie dynamicznych wyrażeń, które pobierają dane z dokumentów, odczytują parametry adresu URL i obliczają wartości w locie.

Co dzieje się podczas uruchamiania skryptu CEL:

Twój skrypt                    Silnik                         Wynik
    |                              |                              |
    v                              v                              v
documents.get("article", "intro") --> Pobiera z bazy danych --> { headline: "Welcome", body: "..." }
         .headline                --> Wyodrębnia pole       --> "Welcome"

Możesz myśleć o CEL jak o języku zapytań tylko do odczytu. Nie może modyfikować niczego w bazie danych — jedynie odczytuje dane i zwraca obliczony wynik. Dzięki temu można go bezpiecznie używać w dowolnym miejscu systemu CMS.


Podstawowe elementy

Każde wyrażenie CEL ma dostęp do trzech elementów:

ObiektCzym jestPrzykład
documentsPobiera dowolny dokument z systemu CMSdocuments.get("country", "us")
metaInformacje o bieżącym żądaniu (lokalizacja, parametry URL)meta.locale, meta.params.slug
schemaDefinicje pól bieżącego dokumentuschema.fields

Odwołania do bieżącego dokumentu za pomocą doc

Podczas pisania wyrażeń CEL w edytorze dokumentu możesz uzyskać dostęp do wartości pól bieżącego dokumentu za pomocą obiektu doc. Umożliwia to tworzenie pól obliczanych i odwołań między polami.

// Dostęp do pola ceny bieżącego dokumentu
doc.price

// Obliczenie sumy na podstawie pól bieżącego dokumentu
doc.price * doc.quantity

// Warunek zależny od statusu bieżącego dokumentu
doc.status == "published" ? doc.title : "Draft: " + doc.title

Obiekt doc zawiera wszystkie wartości pól edytowanego dokumentu. Jest przydatny w przypadku:

  • pól obliczanych (np. doc.price * doc.quantity),
  • warunków wyświetlania zależnych od stanu dokumentu,
  • wyrażeń w stylu walidacji.

Pobieranie dokumentów

Najpotężniejszą funkcją CEL jest możliwość pobierania dokumentów z dowolnego miejsca w systemie CMS.

Pobieranie pojedynczego dokumentu

Składnia: documents.get(schemaName, identifier)

Załóżmy, że masz dokument article zapisany z identyfikatorem "welcome-post".

// Zapisany w CMS jako: article / welcome-post
{
  "headline": "Welcome to Our Platform",
  "author": "Sarah Chen",
  "body": "We're excited to announce...",
  "tags": ["announcement", "news"]
}

Aby pobrać cały dokument:

documents.get("article", "welcome-post")

Zwraca: cały dokument.

Aby pobrać tylko nagłówek:

documents.get("article", "welcome-post").headline

Zwraca: "Welcome to Our Platform"

Aby pobrać autora:

documents.get("article", "welcome-post").author

Zwraca: "Sarah Chen"


Używanie parametrów URL

Gdy strona ma dynamiczne trasy, takie jak /articles/[slug], możesz użyć meta.params, aby pobrać parametr URL i właściwy dokument.

Jeśli ktoś odwiedzi /articles/welcome-post:

documents.get("article", meta.params.slug).headline

Zwraca: "Welcome to Our Platform"

W ten sposób tworzysz dynamiczne strony — ten sam skrypt CEL działa dla dowolnego artykułu, wykorzystując wartość slug znajdującą się w adresie URL.


Pobieranie wielu dokumentów

Składnia: documents.find(schemaName) lub documents.find(schemaName, filter)

// Pobierz wszystkie kraje
documents.find("country")

Zwraca: listę dokumentów.

// Pobierz kraje z filtrem
documents.find("country", { "where": { "code": "us" } })

Tłumaczenia

CEL obsługuje pobieranie przetłumaczonej treści dokumentów na dwa sposoby: automatyczne tłumaczenie na podstawie lokalizacji oraz jawne wyszukiwanie tłumaczenia.

Automatyczne tłumaczenie za pomocą meta.locale

Gdy ustawiono meta.locale (np. na podstawie parametrów trasy lub preferencji użytkownika), documents.get() automatycznie scala przetłumaczoną treść:

// Jeśli meta.locale ma wartość "fr", zwraca francuskie tłumaczenie scalone z dokumentem bazowym
documents.get("greeting", "welcome").headline

Jak to działa:

  1. Pobierana jest treść dokumentu bazowego.
  2. Jeśli meta.locale nie ma wartości "en" ani "en-US", wyszukiwane jest tłumaczenie w tabeli translations.
  3. Przetłumaczone pola są scalane z treścią bazową: { ...baseContent, ...translatedContent }.

Oznacza to, że przetłumaczone pola zastępują pola bazowe, a nieprzetłumaczone korzystają z dokumentu bazowego.

Jawne tłumaczenie za pomocą documents.translated()

Jeśli trzeba pobrać konkretne tłumaczenie niezależnie od bieżącej lokalizacji:

Składnia: documents.translated(schemaName, identifier, locale)

// Zawsze pobierz tłumaczenie hiszpańskie
documents.translated("greeting", "welcome", "es").headline

// Pobierz tłumaczenie na podstawie parametru URL
documents.translated("product", meta.params.id, meta.params.lang).description

// Porównaj tłumaczenia
documents.translated("article", "intro", "en").title + " / " + documents.translated("article", "intro", "fr").title

Przykłady z praktyki

Przykład 1: Tytuł bloku hero z innego dokumentu

Masz blok hero-block, który powinien wyświetlać nagłówek pobrany z dokumentu article.

Skrypt CEL w polu tytułu bloku hero:

documents.get("article", "homepage-hero").headline

Wynik: blok hero wyświetla "Build Faster, Ship Smarter".

Przykład 2: Nazwa kraju na podstawie kodu

Tworzysz stronę /countries/[code] i chcesz wyświetlić pełną nazwę kraju.

documents.get("country", meta.params.code).name

Gdy ktoś odwiedzi /countries/us:

  • meta.params.code = "us"
  • wynik: "United States"

Przykład 3: Treść zależna od lokalizacji

Wyświetlaj różne nagłówki zależnie od lokalizacji użytkownika.

meta.locale == "ar-SA" ? "Welcome, everyone" : "Welcome"

Jeśli lokalizacja to "ar-SA", zwracane jest "Welcome, everyone"; w przeciwnym razie zwracane jest "Welcome".

Przykład 4: Łańcuchowe wyszukiwanie dokumentów

documents.get("country", documents.get("article", "us-news").countryCode).name

Wewnętrzne wywołanie pobiera artykuł, .countryCode wyodrębnia kod kraju, kolejne wywołanie pobiera kraj, a .name wyodrębnia jego nazwę.

Przykład 5: Wartości zapasowe

Jeśli dokument może nie istnieć, możesz podać wartość zapasową:

documents.get("article", meta.params.slug) != null
  ? documents.get("article", meta.params.slug).headline
  : "Article Not Found"

Możesz również sprawdzić, czy istnieje konkretne pole:

documents.get("article", "intro").author != null
  ? documents.get("article", "intro").author
  : "Unknown Author"

Przykład 6: Praca z listami

Sprawdź, czy artykuł ma określony tag:

"featured" in documents.get("article", "welcome-post").tags

Zwraca: true, jeśli artykuł ma tag "featured".

Pobierz pierwszy tag:

documents.get("article", "welcome-post").tags[0]

Policz tagi:

size(documents.get("article", "welcome-post").tags)

Trasy parametryczne i meta.params

Trasy parametryczne są kluczem do tworzenia dynamicznych, lokalizowanych stron. Po zdefiniowaniu wzorca, takiego jak /{lang}/landingPage, CMS wyodrębnia parametry z adresu URL i udostępnia je za pomocą meta.params.

Jak działają parametry tras

Trasy używają składni :paramName lub {paramName} do definiowania dynamicznych segmentów:

WzorzecPrzykładowy URLWyodrębnione parametry
/:lang/landingPage/ko/landingPage{ lang: "ko" }
/{country}/{lang}/products/us/en/products{ country: "us", lang: "en" }
/articles/:slug/articles/welcome-post{ slug: "welcome-post" }

Każdy parametr trasy można powiązać ze schematem dokumentu w celu walidacji:

{
  "pattern": "/{lang}/landingPage",
  "param_bindings": {
    "lang": "language"
  }
}

Powiązanie instruuje CMS, aby wyodrębnił segment lang, zwalidował go względem schematu language, a następnie udostępnił pełny dokument w rozwiązanych parametrach.

Przykład: strona docelowa zależna od języka

Skrypt CEL pobierający lokalizowaną treść:

documents.get("greeting", meta.params.lang).headline
URLmeta.params.langWynik
/ko/landingPage"ko""Welcome"
/en/landingPage"en""Welcome"
/ja/landingPage"ja""Welcome"

Zaawansowany wzorzec: trasy kraj + język

Dla tras takich jak /{country}/{lang}/products:

// Pobierz nazwę kraju
documents.get("country", meta.params.country).name

// Pobierz listę produktów na podstawie kraju
documents.find("product", { "where": { "country": meta.params.country } })

// Połącz powitanie zależne od kraju i języka użytkownika
documents.get("greeting", meta.params.lang).headline + " from " + documents.get("country", meta.params.country).name

CMS waliduje parametry hierarchicznie: najpierw country względem schematu country, następnie lang względem schematu language, a opcjonalnie sprawdza, czy lang znajduje się w tablicy country.languages[].


meta.segments — dostęp do surowej ścieżki URL

meta.segments udostępnia surową ścieżkę URL jako tablicę. Jest przydatne, gdy potrzebujesz dostępu pozycyjnego bez nazwanych parametrów.

Ścieżka URLmeta.segments
/articles/tech/ai-news["articles", "tech", "ai-news"]
/ko/landingPage["ko", "landingPage"]
/us/en/products/featured["us", "en", "products", "featured"]
/[]

Kiedy używać meta.segments, a kiedy meta.params

ZastosowanieNajlepsze podejście
Nazwane parametry ze wzorca trasymeta.params.lang
Dostęp pozycyjnymeta.segments[0]
Pobranie głębokości ścieżkisize(meta.segments)
Sprawdzenie, czy ścieżka zawiera segment"admin" in meta.segments

Przykłady z meta.segments

// Pobierz pierwszy segment, często kod języka
meta.segments[0]

// Sprawdź głębokość ścieżki
size(meta.segments) > 2 ? "deep" : "shallow"

// Sprawdź, czy jesteśmy w sekcji administracyjnej
"admin" in meta.segments ? "admin mode" : "public mode"

// Wartość zapasowa: użyj segmentu, jeśli parametr nie jest powiązany
has(meta.params.lang) ? meta.params.lang : meta.segments[0]

Skrócona dokumentacja obiektu meta

Obiekt meta zawiera cały kontekst bieżącego żądania:

WłaściwośćTypOpis
meta.localestringBieżący kod lokalizacji, np. "en-US", "ko-KR", "ar-SA"
meta.paramsRecord<string, string>Parametry trasy wyodrębnione ze wzorca URL
meta.segmentsstring[]Ścieżka URL podzielona na segmenty
meta.docIdstring | nullUUID bieżącego dokumentu, null dla nowych dokumentów
meta.titlestringTytuł bieżącego dokumentu

meta.locale

Kod lokalizacji jest zgodny z formatem BCP 47:

// Sprawdź lokalizacje języków RTL
meta.locale == "ar-SA" || meta.locale == "he-IL" ? "rtl" : "ltr"

// Pobierz tylko część językową
meta.locale.split("-")[0]  // Nieobsługiwane — użyj meta.params.lang

meta.params

Parametry tras są zawsze ciągami znaków. CMS waliduje je względem powiązanych schematów przed wykonaniem wyrażenia:

// Dostęp do nazwanego parametru
meta.params.lang
meta.params.country
meta.params.slug

// Sprawdź, czy parametr istnieje
has(meta.params.category)

// Użyj w pobieraniu dokumentu
documents.get("greeting", meta.params.lang)
documents.ref("airports").get(meta.params.code)

meta.docId i meta.title

meta.docId zawiera UUID bieżącego dokumentu i jest przydatne w skryptach odwołujących się do siebie:

meta.docId != null ? "editing" : "creating new"
meta.docId != null ? documents.get("article", meta.docId).status : "draft"

Tytułu dokumentu można użyć na przykład tak:

"Editing: " + meta.title
meta.title.contains("Draft") ? "work in progress" : "published"

documents.ref() — łańcuchowe wyszukiwanie

Gdy schemat jest znany, ale identyfikator jest dynamiczny, możesz użyć czytelniejszej składni:

// Tradycyjne podejście
documents.get("airports", meta.params.code).name

// Za pomocą ref()
documents.ref("airports").get(meta.params.code).name

Oba sposoby są równoważne, ale ref() wyraźniej pokazuje dynamiczną część odwołania.


Szybka ściąga

Pobieranie dokumentów

documents.get("schema", "identifier")       // Pobierz jeden dokument
documents.get("schema", "id").fieldName     // Pobierz konkretne pole
documents.find("schema")                    // Pobierz wszystkie dokumenty
documents.find("schema", { "where": {...}}) // Zapytanie z filtrem
documents.ref("schema").get(identifier)     // Łańcuchowe wyszukiwanie
documents.translated("schema", "id", "fr")  // Pobierz z określoną lokalizacją

Zmienne kontekstowe

meta.locale          // "en-US", "ar-SA" itd.
meta.params.xyz      // Parametr URL o nazwie "xyz"
meta.segments        // Ścieżka URL jako tablica
meta.segments[0]     // Pierwszy segment ścieżki
meta.docId           // ID bieżącego dokumentu lub null
meta.title           // Tytuł bieżącego dokumentu
doc.fieldName        // Wartość pola bieżącego dokumentu

Operatory

// Porównanie
==  !=  <  <=  >  >=

// Logika
&&  ||  !

// Operator trójargumentowy
condition ? valueIfTrue : valueIfFalse

// Przynależność
"value" in listOrMap

Często używane funkcje

size(list)                    // Liczba elementów
size(string)                  // Długość ciągu znaków
"text".startsWith("te")       // true
"text".endsWith("xt")         // true
"text".contains("ex")         // true
has(object.property)          // Sprawdź, czy właściwość istnieje
hasProperty(obj, "key")       // Sprawdź, czy obiekt ma klucz

Komunikaty o błędach

Jeśli coś pójdzie nie tak, zobaczysz jeden z poniższych komunikatów:

BłądZnaczenie
SYNTAX_ERRORLiterówka w skrypcie, brak cudzysłowu lub nieprawidłowy operator
TYPE_ERRORPołączono typy, których nie można ze sobą używać
RUNTIME_ERRORSkrypt został uruchomiony, ale napotkał problem, np. niezdefiniowaną zmienną
FETCH_LIMIT_EXCEEDEDPobieranych jest zbyt wiele dokumentów, maksymalnie 50
TIMEOUTSkrypt działał zbyt długo, maksymalnie 5 sekund
AST_DEPTH_EXCEEDEDWyrażenie jest zbyt głęboko zagnieżdżone, maksymalna głębokość to 50
SCRIPT_TOO_LONGSkrypt przekracza limit 5000 znaków

Rozszerzalność i przyszłe możliwości

Silnik CEL został zaprojektowany z myślą o rozszerzalności. Planowane możliwości obejmują:

Planowane: integracja z serwerem MCP

// W przyszłości: wywoływanie zewnętrznych usług przez MCP
mcp.translate(meta.params.text, "en", meta.params.lang)
mcp.analyze(documents.get("article", meta.params.id).body)

Planowane: funkcje AI

// W przyszłości: generowanie treści z użyciem AI
ai.summarize(documents.get("article", meta.params.id).body, 100)
ai.translate(meta.params.text, meta.params.targetLang)
ai.classify(meta.params.input, ["positive", "negative", "neutral"])

Możliwości te zostaną dodane za pośrednictwem systemu zarejestrowanych funkcji, z zachowaniem zgodności wstecznej z istniejącymi skryptami.


Wskazówki

  1. Korzystaj z autouzupełniania — wpisz documents. lub meta., a edytor pokaże dostępne opcje.
  2. Zaczynaj od prostych wyrażeń — najpierw przetestuj documents.get("schema", "id"), a dopiero potem dodaj .fieldName.
  3. Sprawdzaj wartości null — jeśli dokument może nie istnieć, dodaj wartość zapasową za pomocą != null ? ... : ....
  4. Nie pobieraj nadmiarowych danych — każde documents.get() lub documents.find() jest wliczane do limitu 50 pobrań.
  5. Preferuj meta.params zamiast meta.segments — nazwane parametry są walidowane i bardziej niezawodne.
  6. Używaj has() dla opcjonalnych parametrów — przed odczytem sprawdź has(meta.params.category).
  7. Używaj documents.ref() dla dynamicznych identyfikatorów — zapewnia czytelniejszą składnię.
  8. Używaj doc.fieldName dla odwołań do bieżącego dokumentu — umożliwia dostęp do jego pól w wyrażeniach obliczanych.

Odwołania między dokumentami

Ta sekcja opisuje zaawansowane wzorce łączenia dokumentów i budowania relacyjnych struktur treści.

Podstawowy wzorzec odwołania

Najprostsza forma polega na tym, że jeden dokument odwołuje się do drugiego za pomocą identyfikatora.

// Artykuł przechowuje ID autora; pobierz nazwę autora
documents.get("author", documents.get("article", "intro").authorId).name

Łańcuchowe wyszukiwanie za pomocą documents.ref()

// Tradycyjne podejście
documents.get("country", documents.get("airport", meta.params.code).countryCode).name

// Za pomocą ref()
documents.ref("country").get(documents.get("airport", meta.params.code).countryCode).name

Wielopoziomowe łańcuchy odwołań

// Lotnisko → Kraj → Region → Kontynent
documents.get("continent",
  documents.get("region",
    documents.get("country",
      documents.get("airport", meta.params.code).countryCode
    ).regionCode
  ).continentCode
).name

Odwołanie z tłumaczeniem

// Pobierz zlokalizowaną nazwę kraju dla lotniska
documents.translated("country",
  documents.get("airport", meta.params.code).countryCode,
  meta.params.lang
).name

Najlepsze praktyki dotyczące odwołań

  1. Ograniczaj głębokość łańcucha — każdy poziom zwiększa opóźnienie i liczbę pobrań.
  2. Buforuj wyniki pośrednie — jeśli potrzebujesz tej samej zagnieżdżonej wartości dwa razy, pobierz dokument nadrzędny tylko raz.
  3. Używaj sprawdzania null — odwołania mogą przestać działać po usunięciu dokumentów.
  4. Preferuj kody zamiast UUID — kody są czytelniejsze i stabilne między środowiskami.
  5. Pilnuj limitów pobierania — złożone łańcuchy mogą szybko osiągnąć limit 50 pobrań.

Dodatek A: kompletny przykład trasy parametrycznej

Ten przykład tworzy wielojęzyczną stronę docelową dostępną pod adresem /{lang}/landingPage.

Krok 1: utworzenie schematu dokumentu powitania

W panelu administracyjnym CMS utwórz niestandardowy schemat o nazwie greeting.

Krok 2: utworzenie dokumentów powitania

Utwórz dokument dla każdego języka, na przykład greeting/ko, greeting/en i greeting/ja.

Krok 3: utworzenie strony

Utwórz stronę z konfiguracją:

  • Ścieżka/wzorzec: /{lang}/landingPage
  • Stan: Aktywna
  • Mapowanie segmentów dynamicznych: lang → komponent language

Krok 4: dodanie bloków ze skryptami CEL

Dodaj blok hero do trasy i użyj następujących skryptów:

// Nagłówek
documents.get("greeting", meta.params.lang).headline

// Podnagłówek
documents.get("greeting", meta.params.lang).subheadline

// Tekst CTA
documents.get("greeting", meta.params.lang).ctaText

// URL CTA
documents.get("greeting", meta.params.lang).ctaUrl

Krok 5: użycie w Next.js

Dodaj trasę typu catch-all. ParametricRoutePage rozwiązuje stronę, wyodrębnia meta.params z adresu URL, ocenia powiązania CEL po stronie serwera i renderuje każdy blok za pomocą rejestru.

Krok 6: testowanie tras

Odwiedź poniższe adresy, aby zobaczyć zlokalizowaną treść:

URLOczekiwany nagłówek
/ko/landingPage환영
/en/landingPageWelcome
/ja/landingPageいらっしゃいませ

Jak działa rozwiązywanie

Gdy użytkownik odwiedza /ko/landingPage:

  1. Dopasowanie trasy: CMS dopasowuje wzorzec /{lang}/landingPage.
  2. Wyodrębnienie parametru: meta.params.lang = "ko".
  3. Walidacja: CMS sprawdza, czy "ko" istnieje w schemacie language.
  4. Ocena CEL: wyrażenia takie jak documents.get("greeting", meta.params.lang) rozwiązują się do treści koreańskiej.
  5. Odpowiedź: zlokalizowane bloki są zwracane klientowi.

Dodatek B: dokumentacja techniczna

Interfejs CelMeta (TypeScript)

interface CelMeta {
  /** Bieżący kod lokalizacji */
  locale: string;
  /** Parametry trasy wyodrębnione z adresu URL */
  params: Record<string, string>;
  /** Segmenty ścieżki URL */
  segments: string[];
  /** ID bieżącego dokumentu */
  docId: string | null;
  /** Tytuł bieżącego dokumentu */
  title: string;
}

Algorytm wyodrębniania parametrów

Funkcja extractParams przetwarza ścieżki URL:

Wzorzec: /{country}/{lang}/products
Ścieżka: /us/en/products

Algorytm:
1. Normalizuje obie wartości (usuwa końcowe ukośniki)
2. Dzieli je na segmenty
3. Porównuje liczbę segmentów
4. Dla każdej pary segmentów wyodrębnia parametr, jeśli wzorzec używa : lub {}
5. Zwraca: { country: "us", lang: "en" }

Obsługiwane formaty powiązań parametrów

// Proste powiązanie (używa pola "code")
{ "lang": "language" }

// Szczegółowe powiązanie (niestandardowe pole slug)
{
  "lang": {
    "schemaName": "language",
    "slugField": "code"
  },
  "slug": {
    "schemaName": "article",
    "slugField": "slug"
  }
}

Priorytet wyszukiwania dokumentów

Podczas pobierania za pomocą documents.get(schema, identifier) system sprawdza kolejno:

  1. Dopasowanie UUID — jeśli identyfikator jest prawidłowym UUID, wyszukiwanie odbywa się po id.
  2. Pole code — sprawdzane jest pole content.code.
  3. Pole slug — sprawdzane jest pole content.slug.
  4. Dopasowanie tytułu — sprawdzane jest pole title.

Dzięki temu można elastycznie odwoływać się do dokumentów za pomocą dowolnego unikatowego identyfikatora.

Continue Reading
Previous‹Install Profound CMS as a proxyNextProject Scaffolding›