Tema Arayüzü
Bu sayfa size ne verir: eklentinizin ziyaretçiye dönük tarafı için her şey — temaya bir bileşen yerleştirin, ona sunucu tarafında işlenmiş veri verin ve backend'inizi çağırın. Sona geldiğinizde Shoutbox widget'ı ana sayfada en son shout'ları gösterir.
Site tarafı, src/theme/views/ altındaki görünüm dosyalarından oluşur (Frontend Geliştirme sayfasındaki görünümler bölümüne bakın); bunlar için main.js'e kod yazılmaz. Yalnızca aşağıdaki "İleri düzey" bölümündeki kayıtlar, main.js'teki onLoad()'ın else (tema) dalına girer.
Ana sayfaya bir widget yerleştirin
Tema, adlandırılmış kancalar (hook) sunar — eklentilerin bir bileşen enjekte edebileceği noktalar (kanca adlarının tam listesi Arayüz API Referansı'nda; bu eğitim page:home:top'u kullanır). Shoutbox'ı ana sayfada göstermek için, görünüm dosyasının export const view satırında page:home:top kancasını belirtin:
<!-- src/theme/views/ShoutboxWidget.svelte -->
<script module>
export const view = { hook: 'page:home:top' };
</script>
<div class="shoutbox"></div>Kaydın tamamı budur. Site tarafında pano.ui.hook.register({ name, component }) çalışmaz: view olmadan component ile kaydedilen bir öğe hata verilerek atılır.
Kontrol
Geliştirme sunucuları çalışırken, sitenin ana sayfasını açın. Sayfanın en tepesinde <div class="shoutbox"> kapsayıcısını görmelisiniz (tarayıcı geliştirici araçlarıyla inceleyin). Henüz load() eklemediyseniz — sonraki adım — boş olacaktır; bu beklenen bir durum. Onu hiç görmüyorsanız, tarayıcı konsolunda hataları kontrol edin ve dosyanın src/theme/views/ altında olduğunu ve kanca adının doğru yazıldığını doğrulayın.
Widget'a load() ile sunucu tarafında işlenmiş veri verin
İyi bir widget'ın veriye ihtiyacı vardır ve o verinin ilk sunucu yanıtında orada olması gerekir — sonradan tarayıcıda getirilmesi değil — böylece ziyaretçiler ve arama motorları shout'ları anında görür. Pano bunu SSR (sunucu tarafı işleme — sunucu, verisini sonradan getiren boş bir sayfa göndermek yerine, göndermeden önce bitmiş HTML'i oluşturur) ile yapar.
Bir kanca bileşeni, modül betiğinden — <script module> bloğu — bir load(event) fonksiyonu dışa aktarabilir. O blok, dosya ilk yüklendiğinde, bileşenin herhangi bir örneği var olmadan önce bir kez çalışır ki load()'ın orada yaşamasının ve normal örnek-başına bileşen kodunuzun altındaki düz <script>'te yaşamasının nedeni budur. Tema, sayfa hazırlanırken load()'ı çalıştırır (SSR sırasında sunucuda ve sayfalar arasında gezinirken tekrar istemcide) ve döndürdüğünüz her şeyi bileşene props olarak verir. Shoutbox'ta, load() herkese açık uç noktamızı çağırır:
<!-- src/theme/views/ShoutboxWidget.svelte -->
<script module>
import { api } from '@panomc/sdk/plugin-api';
export const view = { hook: 'page:home:top' };
export async function load(event) {
const res = await api.get({ path: '/shouts', request: event });
return { shouts: res.items ?? [] };
}
</script>
<script>
let { shouts = [] } = $props();
</script>
<div class="shoutbox">
{#each shouts as shout}
<p class="shout">{shout.message}</p>
{/each}
</div>event, gelen sayfa isteğidir — ziyaretçinin çerezlerini ve oturumunu taşır. Çoğunlukla onu API'nin kimin sorduğunu bilmesi için (yani request: event olarak) api'ye iletirsiniz.
Döndürdüğünüz nesne bileşenin props'u olur — burada shouts işlenmeye hazır olarak gelir. (Host bu props akışına hookProps der; o adla API referansında ve hata mesajlarında tanışacaksınız.)
load() sunucuda ve istemcide çalışır
Aynı load(), SSR sırasında ve yeniden istemci tarafı gezinmede çalışır, dolayısıyla onu iki kez çalışmaya güvenli tutun: yalnızca veri getirmeli ve döndürmelidir. Global değişkenleri değiştirmeyin, hiçbir şey yazmayın ve size verilen nesneleri değiştirmeyin — çünkü aynı fonksiyon sunucuda bir kez ve tarayıcıda tekrar çalışır. ("Yan etkisi olmadan iki kez çalışmaya güvenli" için tek kelimelik ad idempotent'tir.) Sunucu tarafı çağrının ziyaretçinin oturumunu taşıması için api'ye her zaman request: event geçin (sonraki bölüm).
Kontrol
Ana sayfayı yenileyin. <div class="shoutbox"> artık her shout için bir <p class="shout"> içermeli (backend'inizde herhangi biri varsa). Verinin gerçekten ilk yanıtta olduğunu doğrulamak için, sert bir yenileme yapın (Ctrl/Cmd+Shift+R) ve "Kaynağı görüntüle"yi kullanın — shout'ları HTML'de zaten mevcut görmelisiniz, boş değil.
Gösterecek bir şeyi olmadığında widget'ı gizleyin
load() { hookOptions: { invisible: true } } döndürürse, host o kanca için hiçbir şey işlemez. Announcement eklentisi, gösterilecek bir şey olmadığında kaybolmak için bunu kullanır.
API'nizi çağırma
Kendi eklentinizin uç noktalarına yapılan çağrılar, eklenti kimliğinizi zaten bilen bir istemci olan api üzerinden gider. Yollar, Kotlin uç noktanızın bildirdiği göreli yollardır (/shouts); api.panel ise bir PanelApi'nin uç noktalarına ulaşır. Çekirdek uç noktalarına yapılan çağrılar, /api/v1'e göreli yollarla ApiUtil'i kullanır. Her metot tek bir seçenek nesnesi alır:
import { api } from '@panomc/sdk/plugin-api';
import ApiUtil from '@panomc/sdk/utils/api'; // core endpoints only
// In a load() — pass request so the server-side call has the session:
const res = await api.get({ path: '/shouts', request: event });
// In a browser event handler — body is your JSON payload:
await api.panel.post({ path: '/shouts', body: { message } });
await api.panel.delete({ path: `/shouts/${id}` });
await api.panel.put({ path: '/config', body: config });Kural: bir load() içinde, her zaman request: event geçin ki getirme (fetch), SSR sırasında ziyaretçinin oturumuyla çalışsın. Tarayıcıda çalışan bir tıklama işleyicisinde onu atlayabilirsiniz.
request: event'i unutursanız
Çağrı tarayıcıda yine çalışır, ama SSR sırasında çıkış yapmış olarak çalışır. Belirti kafa karıştırıcıdır: veri eksiktir veya yalnızca sert bir yenilemede izin hataları alırsınız, oysa etrafta tıklandığında her şey iyi görünür. Bunu görürseniz, önce load() çağrılarınızı kontrol edin.
ApiUtil hataları nasıl bildirir
api ve ApiUtil, API hatalarında asla fırlatmaz: başarısız bir çağrı { error: { code, message?, details?, fields? } } nesnesine çözümlenir. Yanıtı kullanmadan önce her zaman res.error'ı kontrol edin (ve res.error.code'a göre dallanın). Listeler { items, page } olarak döner; bkz. Sayfalar.
İleri düzey — ilk okumada atlayın
Aşağıdaki her şey, ilk eklentinizde karşılaşmayacağınız durumlar içindir. İhtiyaç duyduğunuzda geri gelin.
pano.ui.app.onLoad ile paylaşılan veriyi bir kez getirme
pano.ui.app.onLoad(callback), temanın her sayfa isteğinde, sayfa işlenmeden önce çalıştırdığı bir fonksiyon kaydeder. Geri çağırması (data, event) alır; burada data, paylaşılan bir sayfa verisi torbası ve event, istektir (api'ye geçtiğinizle aynı tür). Bir getirmenin aynı anda birkaç kaydı beslemesi gerektiğinde kullanın.
Bileşen-başına bir load()'a alternatiftir: kanca görünümüne skipLoad: true verin ve verisini bunun yerine tek bir pano.ui.app.onLoad(async (data, event) => { ... })'tan getirin. Announcement eklentisi bunu yapar; FAQ ve Pages eklentileri ise app.onLoad'ı, getirdikleri veriden sayfa kaydetmek için kullanır.
Dinamik sayfalar ve temizlik
Bazen kaydettiğiniz sayfalar derleme zamanında bilinmez — backend'inizden gelirler (özel URL'ler, yönlendirmeler ve benzerleri). Onları listeyi getirdikten sonra pano.ui.app.onLoad'ın içinden kaydedin. Pages eklentisi tam olarak bunu yapar.
Önce zihinsel model, çünkü bu bölüm ona bağlıdır: tema, her ziyaretçi tarafından paylaşılan, tek, uzun ömürlü bir sunucu programıdır. Kaydettiğiniz her şey, o program yeniden başlayana kadar onun tarafından hatırlanır — ziyaretçi-başına ve sayfa-yüklemesi-başına değildir. Yani veriniz değişirse, siz kaldırmadıkça eski kayıtlar takılı kalır.
Buradaki keskin kenar budur. pano.ui.app.onLoad her istekte çalışır, ama kaydettiğiniz rotalar ve bağlantılar istekler arasında o süreçte kalır. Bir sayfa panelde silinirse, daha önce kaydettiğiniz girdi oyalanır — tarayıcı artık onu bilmese bile, süreç yeniden başlayana kadar SSR o hayalet rotayı sunmaya devam eder.
Çözüm, kaydettiğinizi izlemek ve gitmiş girdileri pano.ui.page.unregister(path) kullanarak kaldırmaktır:
const registeredPaths = new Set();
pano.ui.app.onLoad(async (data, event) => {
const res = await api.get({ path: '/pages', request: event });
const pages = res.items ?? [];
const incoming = new Set(pages.map((p) => p.url));
// Remove routes we registered before that are no longer present.
for (const path of registeredPaths) {
if (!incoming.has(path)) {
pano.ui.page.unregister(path);
registeredPaths.delete(path);
}
}
for (const page of pages) {
// the view file is src/theme/views/CustomPage.svelte; on the site a page names a view, not a component
pano.ui.page.register({ path: page.url, view: 'shoutbox:CustomPage' });
registeredPaths.add(page.url);
}
});Bir sayfayı pano.ui.page.register ile yeniden kaydetmek güvenlidir: aynı yol yalnızca önceki girdinin üzerine yazar, dolayısıyla burada yinelenen koruması gerekmez — gezinme bağlantılarının aksine, ki onlar yinelenirdi, editNavLinks'in some(...) kontrolüne ihtiyaç duymasının nedeni budur.
SSR'de hayalet rotalar
Veriden kaydedilen dinamik sayfalar Node sürecinde hayatta kalır (SSR — sunucu tarafı işleme — tek, uzun ömürlü bir programda çalışır). Kaldırılan öğelerin kaydını asla silmezseniz, silinen sayfalar Pano yeniden başlayana kadar SSR sırasında sunulmaya devam eder. pano-plugin-link-redirects eklentisi, eklediğiniz eskimiş gezinme bağlantılarını kaldırmak da dâhil, bu temizlik deseninin tam referansıdır.
Sırada ne var
Ziyaretçi tarafı bitti: SSR verili bir ana sayfa widget'ı, API çağrıları ve (isteğe bağlı olarak) dinamik sayfalar.
- Yönetici ekranları ekleyin → Panel Arayüzü — ayarlar bölümleri, gezinme bağlantılı tam panel sayfaları ve toast'lar.
- Arayüz API Referansı — her kanca adı, görünüm yuvası ve yaşam döngüsü etkinliği tek bir yerde.
- Metni çevirme — bileşenlerinizin etiketler için kullandığı
$_yardımcısı. - Backend Geliştirme —
load()veapiçağrılarınızın vurduğu Kotlin uç noktaları.