Edytor Typst pokazuje kod i skompilowane strony obok siebie. Uruchamia w przeglądarce prawdziwy kompilator Typst jako WebAssembly, więc strony wyglądają dokładnie tak jak w PDF - nagłówki, listy, tabele, wzory, rysunki, przypisy, odsyłacze oraz twoje własne reguły #set i #show. Kliknięcie dowolnego tekstu na stronie przenosi do jego miejsca w kodzie, a tekst można edytować bezpośrednio na stronie.
Wszystko działa w przeglądarce. Dokumenty nie są nigdzie wysyłane, nie ma rejestracji ani reklam.
Znaczniki, kod, wzory, łańcuchy znaków i komentarze we własnych kolorach, numery wierszy, listy kontynuowane po naciśnięciu Enter oraz błędy kompilatora z wierszem, kolumną i wskazówką.
Typst 0.14 skompilowany do WebAssembly działa w wątku roboczym obok edytora i kompiluje chwilę po każdej zmianie - pisanie pozostaje płynne.
Kliknij tekst na stronie, aby go tam zmienić; zmiana trafia do kodu. Kliknięcie wzoru lub obrazu przenosi do jego miejsca w kodzie.
Papier, marginesy, kolumny, czcionka, rozmiar, język, wyjustowany tekst, numerowane nagłówki i numery stron - ustawienia zmieniają reguły #set w twoim kodzie.
Dodawaj do dokumentu obrazy, pliki CSV, JSON, YAML, bibliografie, czcionki i inne pliki .typ i używaj ich przez image, csv, json lub #import.
PDF z kompilatora z prawdziwym tekstem do zaznaczania, każda strona jako PNG, JPG, WebP lub SVG albo źródło .typ z plikami jako ZIP.
Każda strona https (i localhost) może osadzić edytor za pomocą <iframe> i ?embed=1 - bez rejestracji, bez klucza. Po osadzeniu edytor ukrywa ten tekst i pokazuje przycisk Zapisz. Dokument można przekazywać do edytora i z niego na dwa sposoby.
src= otwiera plik (twój serwer musi zezwalać na CORS), webhook= sprawia, że „Zapisz” (Ctrl+S) wysyła dokument pod ten adres. Dodaj &token=…, aby wysyłać nagłówek Authorization: Bearer …, i &auto, aby zapisywać po każdej zmianie.
<iframe src="https://typst.giit.ch/?embed=1&src=https://your-site.com/docs/flyer.typ&webhook=https://your-site.com/save.php"
allow="clipboard-write" style="width:100%;height:800px;border:0"></iframe>
Webhook otrzymuje żądanie POST z JSON { editor, event: "save", id, name, content, assets, updated }. Mały skrypt PHP, który go zapisuje:
<?php
header('Access-Control-Allow-Origin: https://typst.giit.ch');
header('Access-Control-Allow-Headers: Content-Type, Authorization');
header('Access-Control-Allow-Methods: POST, OPTIONS');
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') exit;
if (($_SERVER['HTTP_AUTHORIZATION'] ?? '') !== 'Bearer SECRET') { http_response_code(403); exit; }
$d = json_decode(file_get_contents('php://input'), true);
$name = preg_replace('/[^\w\- ]/u', '_', $d['name']);
file_put_contents(__DIR__ . "/docs/$name.typ", $d['content']);
echo '{"ok":true}';
Edytor zgłasza typsteditor:ready, twoja strona odpowiada typsteditor:load, edytor zgłasza typsteditor:loaded, typsteditor:change przy rzeczywistych zmianach i typsteditor:save, gdy użytkownik zapisuje. Twoja strona może wysłać typsteditor:get (bieżący stan) lub typsteditor:save (naciśnięcie Zapisz). Zapis odbywa się wtedy przez twoje własne logowanie i sesję.
<iframe id="ed" src="https://typst.giit.ch/?embed=1" allow="clipboard-write" style="width:100%;height:800px;border:0"></iframe>
<script>
const ed = document.getElementById('ed');
const ORIGIN = 'https://typst.giit.ch';
addEventListener('message', async e => {
if (e.source !== ed.contentWindow || e.origin !== ORIGIN) return;
const m = e.data;
if (m.type === 'typsteditor:ready') { // edytor jest gotowy: przekaż mu dokument
ed.contentWindow.postMessage({
type: 'typsteditor:load',
name: 'Flyer',
content: await (await fetch('/docs/flyer.typ')).text(),
assets: { 'logo.png': 'data:image/png;base64,…' } // opcjonalne obrazy i pliki danych
}, ORIGIN);
}
if (m.type === 'typsteditor:change') document.title = '● not saved'; // rzeczywista zmiana
if (m.type === 'typsteditor:save') { // użytkownik kliknął „Zapisz”
await fetch('/api/save', { method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: m.name, content: m.content, assets: m.assets }) });
document.title = 'Saved';
}
});
// Przycisk zapisu na twojej stronie: odpowiedź przychodzi jako typsteditor:save
function saveNow() { ed.contentWindow.postMessage({ type: 'typsteditor:get' }, ORIGIN); }
</script>
Asystent AI na twojej stronie: działa także w ramce. Z allow="clipboard-write" w iframe „Kopiuj prompt” korzysta bezpośrednio ze schowka (w przeciwnym razie edytor używa starego polecenia kopiowania albo pokazuje prompt do ręcznego skopiowania). Link wysłany przez AI nie dotrze do edytora osadzonego w innej stronie, więc edytor prosi AI i użytkownika o wklejenie całej odpowiedzi AI do edytora - albo o otwarcie edytora we własnym oknie.
window.open("https://typst.giit.ch/?embed=window", "typsteditor-17") otwiera pełny edytor we własnym oknie, który wymienia te same wiadomości z window.opener. Linki od AI docierają do niego tam. typsteditor:load może też zawierać context: { host, title, rules } - prompt dla AI mówi wtedy, gdzie należy dokument, i stosuje twoje zasady - oraz hook: { url, token, assets: false }, webhook tylko dla tego dokumentu, który nigdy nie jest zapisywany. Zamknięcie okna z niezapisanymi zmianami wysyła typsteditor:closing. typsteditor:pdf prosi o PDF dokumentu strony i otrzymuje go z powrotem jako data URI, utworzony przez kompilator Typst z prawdziwym tekstem do zaznaczania.
Przekaż edytorowi własny prompt, adres w twojej witrynie i klucz dla tej sesji edytora. Okno AI pokazuje wtedy „Połączono z” twoją witryną, to, co przekazujesz AI, oraz wybór: zmienić bieżący dokument lub utworzyć nowy; Kopiuj prompt łączy to wszystko. AI wysyła swój wynik z kluczem na twój serwer, a twoja strona otwiera go w edytorze - nowy dokument otwiera się osobno i zostaje w przeglądarce użytkownika. Jeden klucz na sesję edytora: pozostaje ważny, dopóki edytor jest otwarty, a edytor prosi o niego przez typsteditor:key-request tylko wtedy, gdy go nie ma. Edytor nigdy sam nie wysyła klucza i nigdy go nie zapisuje; adres musi być adresem https na twoim własnym hoście lub jego subdomenie.
const key = async () => (await fetch('/api/ai-key.php?doc=17')).json(); // jeden klucz na sesję edytora
const k = await key();
ed.contentWindow.postMessage({ type: 'typsteditor:load', name: 'Flyer', content, ai: {
label: 'My Site', prompt: 'Keep the [[placeholders]]. Write in German.',
endpoint: 'https://your-site.com/api/ai-result.php', key: k.key } }, ORIGIN);
// zapamiętaj id z typsteditor:loaded i ignoruj change/save/closing z innym id
// tylko gdy edytor nie ma klucza:
if (m.type === 'typsteditor:key-request') { const k = await key(); ed.contentWindow.postMessage({ type: 'typsteditor:ai', key: k.key }, ORIGIN); }
// dopóki edytor jest otwarty, informuj serwer co minutę; zamknięcie edytora lub strony unieważnia klucz
setInterval(() => fetch('/api/ai-key.php?alive=' + k.key.slice(0, 8)), 60000);
// twój serwer ma wynik (polling, SSE …): target to "current" lub "new"
ed.contentWindow.postMessage({ type: 'typsteditor:load', name: 'Flyer', content: draft, from: 'ai', target }, ORIGIN);
<?php // api/ai-key.php - klucz tej sesji edytora zalogowanego użytkownika (wywołuje go twoja strona)
session_start(); // sprawdź, czy użytkownik może edytować ten dokument
$key = bin2hex(random_bytes(24));
$_SESSION['ai'][hash('sha256', $key)] = ['doc' => (int)$_GET['doc'], 'seen' => time()]; // lub w twojej bazie danych; ?alive= ponownie ustawia seen
echo json_encode(['key' => $key]);
<?php // api/ai-result.php - tu AI wysyła wynik (bez nagłówków CORS: AI wywołują go z serwera)
$key = preg_replace('/^Bearer\s+/', '', $_SERVER['HTTP_AUTHORIZATION'] ?? '');
$row = find_key(hash('sha256', $key)); // twoja baza danych: nieunieważniony, widziany w ciągu ostatnich 3 minut
if (!$row) { http_response_code(401); exit('{"ok":false,"error":"key not valid"}'); }
$d = json_decode(file_get_contents('php://input', false, null, 0, 2000000), true);
save_draft($row['doc'], $d['content'], $d['target'] === 'new' ? 'new' : 'current'); // twoja strona pokazuje go przez typsteditor:load
echo '{"ok":true}';
Twórz klucze z co najmniej 128 losowymi bitami, przechowuj tylko ich hash, wiąż każdy z zalogowanym użytkownikiem, dokumentem i tą sesją edytora, twórz jeden na sesję, utrzymuj go ważnym tylko wtedy, gdy twoja strona zgłasza sesję jako otwartą, i unieważniaj go, gdy edytor lub strona zostaną zamknięte.
Cały protokół jest opisany w llms.txt - przekaż go AI, a zbuduje połączenie dla twojego systemu. Edytor LaTeX działa tak samo z prefiksem latexeditor: - latex.giit.ch.
Nie. Edytor i kompilator Typst działają w całości w przeglądarce. Dokumenty, obrazy i pliki nigdy nie są wysyłane na serwer, chyba że sam skonfigurujesz webhook do własnego serwera.
Tak. Edytor uruchamia Typst 0.14 skompilowany do WebAssembly (typst.ts), więc strony i PDF są takie same jak z wiersza poleceń Typst. Jest ładowany raz (około 11 MB), a potem przechowywany przez przeglądarkę.
Kliknij „Eksportuj”, wybierz PDF i kliknij „Pobierz”. PDF pochodzi wprost z kompilatora Typst, z prawdziwym tekstem, który można zaznaczać i przeszukiwać.
Nie bezpośrednio: edytor nie ładuje niczego z innych serwerów, więc #import "@preview/…" zgłasza problem z nazwą pakietu. Pliki .typ pakietu można dodać do dokumentu w karcie Obrazy i importować je po nazwie pliku.
Czcionki dostarczane z Typst: Libertinus Serif (domyślna), New Computer Modern z jej czcionką matematyczną oraz DejaVu Sans Mono do kodu. Dodaj własne pliki .otf lub .ttf do dokumentu i używaj ich przez #set text(font: "Name").
Przeciągnij lub wklej obraz do edytora albo dodaj go w karcie Obrazy po prawej. Jest przechowywany razem z dokumentem w przeglądarce i używany przez #image("name.png"). Pliki CSV, JSON, YAML i .bib działają tak samo.
Tak, każda AI - ChatGPT, Claude, Gemini, Copilot lub AI w twoim narzędziu programistycznym. Kliknij „Asystent AI”, potem „Kopiuj prompt”, wklej go do swojej AI i napisz, co ma napisać. AI odpowiada linkiem; kliknij go, a dokument pojawi się w edytorze. „Cofnij” cofa każdą zmianę.
Tak. Osadź go w iframe i wymieniaj dokumenty przez postMessage albo pozwól mu wysyłać każdą zapisaną wersję na twój serwer przez webhook. Przykłady znajdziesz w sekcji Edytor na własnej stronie.
Nie. Edytor jest darmowy, bez reklam i bez rejestracji.