@corraya/cms-editor
v0.10.1
Published
Blokowy edytor wizualny treści dla aplikacji Vue 3 — kanwa, konspekt, panel właściwości i renderer HTML.
Maintainers
Readme
@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-editorZależ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.
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 buildWydanie
npm version patch # albo minor / major
npm publish # scope jest publiczny, więc bez --access
git push --follow-tagsdist/ 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 zostaje jasna i to jest decyzja, nie przeoczenie: pokazuje podgląd publikowanej strony, a ta jest jasna. Ciemna kanwa pokazywałaby coś, czego czytelnik nigdy nie zobaczy.
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.
