npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@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.

Readme

@corraya/cms-editor

npm

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.

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 normalizeDocument jak 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 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.