@corraya/cms-editor
| Install | |
|---|---|
npm install @corraya/cms-editor |
|
| Latest Version: | 0.10.5 |
| Vue: | 3 |
| License: | PolyForm-Noncommercial-1.0.0 |
| Last Updated: | Oct 21, 2026 |
| Links: | Bitbucket · npm |
@corraya/cms-editor
Blokowy edytor wizualny treści dla aplikacji Vue 3: kanwa z podglądem, konspekt dokumentu, panel właściwości i renderer HTML.
Moduł nie zna hosta. Nie wykonuje żadnego zapytania sieciowego, nie zna żadnego adresu ani schematu bazy — wszystko, co zależne od aplikacji, wchodzi propsami.
Instalacja
npm install @corraya/cms-editor
Zależności równorzędne
vue oraz pakiety @tiptap/* są peerDependencies i host musi je dostarczyć.
Dla TipTapa to nie jest oszczędność rozmiaru, tylko poprawność: ProseMirror
trzyma stan w modułowych singletonach, a dwie jego kopie w jednym dokumencie dają
błędy, których praktycznie nie da się zdiagnozować.
Peery TipTapa są oznaczone jako opcjonalne, bo potrzebuje ich wyłącznie edytor.
Host sięgający tylko po /render nie ściąga ich wcale — bez tego npm doklejałby
mu kilkanaście pakietów po to, żeby wygenerować HTML.
Style
import '@corraya/cms-editor/style.css';
Interfejs edytora jest napisany klasami Tailwinda, a te generuje Tailwind hosta — więc host musi skanować zbudowany pakiet:
@import 'tailwindcss';
@source "../../node_modules/@corraya/cms-editor/dist";
Bez tej linii edytor wyrenderuje się bez stylów.
Kanwa pisze tekst czernią, jak strona bez własnych stylów, a nie kolorem panelu.
Host, którego front ma inny kolor treści, ustawia --cms-canvas-text — inaczej
podgląd i publikacja różnią się odcieniem. Czcionkę frontu podaje --cms-canvas-font
(np. 'Lato', sans-serif); host musi ją też załadować w panelu. Bez tej zmiennej
kanwa pisze czcionką panelu.
Kontener, w który host wstawia wyrenderowany dokument, nie powinien mieć własnych
reguł dla elementów (.content h2 { margin }, .content ul { margin } itp.) — bloki
niosą swoje odstępy i rozmiary, a takie reguły nakładają się na nie i strona wygląda
inaczej niż kanwa. Kontener potrzebuje tylko koloru tekstu, wyglądu linków
i overflow-wrap: anywhere, żeby długi adres nie rozpychał kolumny na telefonie.
Miniatury
Wgrane obrazy idą zawsze przez miniaturownik hosta, nigdy wprost z pliku:
/thumb/cms_<proporcja>_<szerokość>/<ścieżka>. Host musi znać te presety:
| Preset | Szerokości | Uwagi |
|---|---|---|
cms_orig_*, cms_1x1_*, cms_16x9_* |
400, 800, 1200, 1600 | proporcja pliku / kadr |
cms_9x16_* |
225, 450, 675, 900 | kadr |
cms_4x5_* |
320, 640, 960, 1280 | kadr |
cms_natural_1600 |
do 1600 | bez powiększania — obraz w „oryginalnym rozmiarze" |
Szerokości to deskryptory w w srcset, więc muszą się zgadzać co do piksela.
cms_natural_1600 nie może powiększać: mniejszy plik ma wrócić w swoim rozmiarze,
bo właśnie ten rozmiar pokazuje strona.
Użycie
<script setup>
import { CmsEditor } from '@corraya/cms-editor';
import '@corraya/cms-editor/style.css';
</script>
<template>
<CmsEditor
:initial-blocks="blocks"
:page-meta="meta"
:on-save="save"
:on-image-upload="upload"
@close="close"
/>
</template>
Warstwa adaptera
Props on* to cała powierzchnia styku z aplikacją:
| Prop | Odpowiedzialność hosta |
|---|---|
onSave(payload) |
zapis bloków i wygenerowanego HTML-a; może zwrócić { pageMeta } — pola po zapisie tak, jak przyjął je serwer (np. adres wyliczony z tytułu), a edytor wpisze je do panelu |
onImageUpload(file) |
przyjęcie pliku, zwrot { path } |
onImagePick() |
własna biblioteka mediów, zwrot { src, alt?, caption? } |
onLoadVersions() / onLoadVersion(id) |
historia wersji; lista jest pobierana po KAŻDYM udanym zapisie, także autozapisie, więc ma być tania |
previewUrl |
adres strony na froncie (string albo funkcja pól strony z ostatniego zapisu ręcznego) — ikona „Otwórz na stronie" |
documentTitle |
nazwa dokumentu w pasku zamiast „Edytor wizualny" |
legacyHtml |
treść ze starego edytora; póki dokument jest pusty, edytor proponuje przeniesienie jej na bloki (konwersja bez AI, jako kopia robocza do „Zapisz") |
slot topbar |
element hosta w pasku, np. przełącznik wersji językowych |
Autozapis a zapis ręczny
payload.type rozróżnia dwa rodzaje zapisu i edytor zakłada dla nich różne znaczenie:
autosave— kopia robocza. Host dopisuje ją do historii wersji, ale nie zmienia treści widocznej publicznie. Inaczej niedokończone zmiany lądowałyby na stronie w trakcie edycji.manual— „Zapisz": treść trafia na stronę.
Przy otwieraniu host sprawdza, czy najnowsza wersja to autozapis o treści innej niż
publiczna. Jeśli tak, podaje jej bloki w initialBlocks razem z
initialUnpublished: true, a edytor od startu pokazuje „Zmiany tylko w autozapisie".
Zamknięcie niczego nie wyrzuca. Edytor pyta o utratę tylko wtedy, gdy coś naprawdę
przepadnie, czyli przy zmianach pól strony (pageMeta nie jedzie z autozapisem).
Kopiowanie i wklejanie
Przez systemowy schowek, więc działa między dokumentami i między środowiskami — np. artykuł przygotowany na dev wklejony na produkcji.
- Element: „Kopiuj" na pasku bloku albo Ctrl/Cmd+C przy zaznaczonym bloku (poza edycją tekstu). Wklejenie: „Wklej ze schowka" w menu dodawania — wszędzie tam, gdzie da się dodać blok — albo Ctrl/Cmd+V (za zaznaczonym blokiem).
- Cały dokument: przyciski w pasku. Kopia niesie też domyślne odstępy i pola z bocznego panelu; przy wklejeniu edytor pyta, czy je przejąć. Wkleja się tylko do pól, które ma bieżący panel.
- Obrazy jadą w kopii jako data URL (pliki z jednego środowiska nie istnieją
w drugim) i przy wklejeniu są wgrywane ponownie przez
onImageUpload. W tym samym środowisku nic nie jest wgrywane drugi raz. - Wklejana treść przechodzi
normalizeDocumentjak import: nowe identyfikatory, odrzucenie nieznanych bloków i właściwości.
Rozszerzanie
plugins przyjmuje obiekty EditorPlugin wnoszące własne bloki (blocks) i typy
pól sidebara (fieldTypes). sidebarConfig opisuje zakładki i pola deklaratywnie
— tędy wchodzą pola swoiste dla typu treści, których biblioteka nie może znać:
artykuł ma inne niż strona swobodna.
Wbudowane typy pól: text, textarea, number, datetime, select (z options),
checkbox, group, slot oraz image — wgranie pliku przez onImageUpload
z podglądem; w polu zostaje ścieżka. Podgląd ma proporcje 16:9, a aspect
(np. '4 / 3') ustawia te, w których host faktycznie pokazuje obraz — autor widzi
wtedy kadr, zanim zobaczy go na stronie.
Renderowanie poza edytorem
Front publiczny nie potrzebuje edytora, tylko zamiany dokumentu na HTML — i ma na to osobne wejście, z którego nie prowadzi żadna ścieżka do Vue ani TipTapa:
import { renderDocument } from '@corraya/cms-editor/render';
const html = renderDocument(blocks, { pageMeta, configDefaults, documentDefaults });
| Wejście | Rozmiar (gzip) | Wciąga Vue? |
|---|---|---|
@corraya/cms-editor |
~35 kB + Vue i TipTap hosta | tak |
@corraya/cms-editor/render |
~8,7 kB | nie |
Wynik zawiera własny <style> z regułami wyliczonymi dla tych bloków, więc nie
trzeba dołączać arkusza z pakietu. Znaczniki niosą natomiast klasy Tailwinda
(text-2xl, leading-relaxed, rounded-md…), które musi wygenerować host —
u siebie albo @source na zapisanym HTML-u.
Nieznany typ bloku nie wywraca renderowania: zostaje po nim komentarz HTML. Dokument zapisany nowszą wersją edytora wyświetli się w tej części, którą host rozumie, zamiast zniknąć w całości.
Bloki własne
Blok wniesiony przez hosta ma dwie warstwy w osobnych plikach: definition.ts
(metadane i renderHtml, bez Vue) oraz ui.ts (te same dane plus komponenty
edycyjne). Renderer dostaje pierwszą przez customBlocks, edytor drugą przez
plugins. Zlanie ich w jeden plik cofa cały podział — import renderera zacznie
wtedy ściągać Vue.
Import treści przez model językowy
Biblioteka nie wywołuje żadnego modelu — dostarcza to, czego host do tego potrzebuje, i przyjmuje wynik z powrotem.
import { buildAiSchema, coreRenderBlocks, normalizeDocument } from '@corraya/cms-editor/render';
buildAiSchema() składa opis struktury dokumentu z definicji bloków: typy, właściwości,
reguły zagnieżdżania i wartości dziedziczone. Build zrzuca go też do
dist/ai-schema.json, dostępnego jako @corraya/cms-editor/ai-schema.json — bo host
składający prompt często nie jest napisany w JavaScripcie.
Opisy dla modelu żyją przy definicjach bloków (BlockRenderDef.ai), nie w osobnym
promptcie. Prompt pisany ręcznie rozjeżdża się przy pierwszym nowym bloku i nikt tego
nie zauważa, bo model dalej zwraca coś sensownego, tylko gorszego.
Okno importu jest w bibliotece i włącza się samo, gdy host poda prop
onImportContent. Bez niego znika z interfejsu — biblioteka nigdy nie woła modelu sama.
Dwa punkty wejścia: przycisk w pasku górnym zastępuje cały dokument, pozycja w menu
dodawania bloku wstawia serię bloków w wybranym miejscu.
normalizeDocument() naprawia odpowiedź modelu: wycina nieznane typy bloków i
właściwości, pilnuje reguł zagnieżdżania, nadaje identyfikatory. Zwraca to, co udało
się uratować, plus listę ostrzeżeń — import wywracający się na jednym bloku
z trzydziestu jest bezużyteczny. Host musi użyć tej samej funkcji, inaczej użytkownik
zobaczy podgląd inny niż to, co zostanie zapisane.
Sanityzacja HTML-a w treści bloków zostaje po stronie hosta — biblioteka nie zakłada, jakiej używasz.
Zgodność wsteczna
Przyjęta zasada: drobne zmiany formatu dokumentu pozostają zgodne, a zmiana głównej wersji zgodności nie gwarantuje — edytor ma wtedy odmówić otwarcia dokumentu z komunikatem, zamiast próbować go migrować.
Nie jest to jeszcze zaimplementowane: dokument nie niesie dziś numeru wersji formatu. Do dodania przed pierwszym użyciem przez drugiego hosta.
Rozwój
npm install
npm run typecheck
npm run build
Wydanie
npm version patch # albo minor / major
npm publish # scope jest publiczny, więc bez --access
git push --follow-tags
dist/ jest wersjonowane w repozytorium, więc commit po każdej zmianie w src/
musi zawierać świeży build.
Tryb ciemny
Interfejs edytora przełącza się razem z hostem, warunkiem
prefers-color-scheme: dark z wyjątkiem :root.light. Działa to przez
przedefiniowanie palety Tailwinda w obrębie edytora — .bg-zinc-900 kompiluje
się do var(--color-zinc-900), więc podmiana zmiennej przestawia wszystkie
klasy naraz, bez wariantów dark: w komponentach.
Kanwa też ciemnieje, razem z paskami przewijania (color-scheme: dark). Tekst
treści ma wtedy --cms-canvas-text-dark (domyślnie #fafafa) — host ustawia go
na kolor tekstu swojego frontu w trybie ciemnym. Kolory, które blok wnosi do wyniku
(obramowania i nagłówek tabeli, znak cytatu), są liczone od koloru tekstu, więc
działają w obu trybach także na stronie hosta.
Host, który przełącza motyw inaczej (klasą, atrybutem), nadpisuje te same
zmienne --color-* na .cms-editor-overlay i .cms-editor-modal.
Licencja
PolyForm Noncommercial 1.0.0 — wolno używać do dowolnych celów niekomercyjnych. Zastosowanie komercyjne wymaga odrębnych ustaleń z CORRAYA.
Related Packages
revenexx hard fork of blökkli — interactive page building experience for Nuxt. I...
Version History
| Version | Released | Vue | License |
|---|---|---|---|
| 0.10.5 | ^3.5.0 | PolyForm-Noncommercial-1.0.0 | |
| 0.10.4 | ^3.5.0 | PolyForm-Noncommercial-1.0.0 | |
| 0.10.3 | ^3.5.0 | PolyForm-Noncommercial-1.0.0 | |
| 0.10.2 | ^3.5.0 | PolyForm-Noncommercial-1.0.0 | |
| 0.10.1 | ^3.5.0 | PolyForm-Noncommercial-1.0.0 | |
| 0.10.0 | ^3.5.0 | PolyForm-Noncommercial-1.0.0 | |
| 0.6.1 | ^3.5.0 | PolyForm-Noncommercial-1.0.0 |