Örnekler
Baştan sona çalışan dört eklenti: hava durumu, medya kontrolleri, pomodoro ve GitHub PR'ları.
Her örnek adanotch init ile oluşturulmuş bir klasörde manifest.json ve src/index.ts olarak çalışır. Hepsi Adanotch'un test kitiyle denenmiştir.
Hava durumu
fetch, ayarlar, çevrimdışıyken son iyi yanıt ve grafik. Anahtar gerektirmeyen Open-Meteo kullanır.
const { activity, ui } = AdanotchKit;
interface Settings {
latitude: number;
longitude: number;
city: string;
}
interface Forecast {
current: { temperature_2m: number; weather_code: number };
daily: { time: string[]; temperature_2m_max: number[]; temperature_2m_min: number[] };
}
function symbol(code: number): string {
if (code === 0) return "sun.max.fill";
if (code <= 3) return "cloud.sun.fill";
if (code <= 48) return "cloud.fog.fill";
if (code <= 67) return "cloud.rain.fill";
if (code <= 77) return "cloud.snow.fill";
return "cloud.bolt.rain.fill";
}
activity<Settings>({
async update(ctx) {
const url = new URL("https://api.open-meteo.com/v1/forecast");
url.searchParams.set("latitude", String(ctx.settings.latitude));
url.searchParams.set("longitude", String(ctx.settings.longitude));
url.searchParams.set("current", "temperature_2m,weather_code");
url.searchParams.set("daily", "temperature_2m_max,temperature_2m_min");
url.searchParams.set("timezone", "auto");
let data: Forecast;
let stale = false;
try {
data = await ctx.network.fetchJSON<Forecast>(url.toString());
ctx.storage.set("last", data);
} catch (error) {
const last = ctx.storage.get<Forecast>("last");
if (!last || !(error instanceof NetworkError || error instanceof TimeoutError)) throw error;
data = last;
stale = true;
}
const temp = Math.round(data.current.temperature_2m);
const highs = data.daily.temperature_2m_max;
return {
compact: {
leading: ui.icon(symbol(data.current.weather_code)),
trailing: ui.text(`${temp}°`, { tone: stale ? "secondary" : "default" }),
},
expanded: ui.stack([
ui.stack(
[
ui.icon(symbol(data.current.weather_code), { tone: "accent" }),
ui.text(ctx.settings.city, { style: "title" }),
ui.spacer(),
ui.text(`${temp}°`, { style: "display" }),
],
{ axis: "h", spacing: 8, align: "center" },
),
ui.chart(highs, { style: "bar", tone: "warning" }),
stale && ui.text("Çevrimdışı; son bilinen değer", { style: "caption", tone: "secondary" }),
]),
};
},
});Şu an çalan
Olaylarla güncellenen bir eklenti: media.change, medya kontrolleri ve refresh: manual. Adanotch'un Müzik modülü açık olmalı.
const { activity, ui, media, events } = AdanotchKit;
activity({
setup(ctx) {
events.on("media.change", () => ctx.refresh());
},
async update() {
const track = await media.nowPlaying();
if (!track) {
return { compact: null, expanded: ui.text("Şu an bir şey çalmıyor", { tone: "secondary" }) };
}
const percent = track.duration > 0 ? (track.elapsed / track.duration) * 100 : 0;
return {
compact: {
leading: ui.icon(track.isPlaying ? "waveform" : "pause.fill", { tone: "accent" }),
trailing: ui.progress(percent, { tone: "accent" }),
},
expanded: ui.stack([
ui.text(track.title, { style: "title" }),
ui.text(`${track.artist} · ${track.album}`, { tone: "secondary" }),
ui.progress(percent),
ui.stack(
[
ui.button("", "previous", { icon: "backward.fill" }),
ui.button("", "toggle", { icon: track.isPlaying ? "pause.fill" : "play.fill", primary: true }),
ui.button("", "next", { icon: "forward.fill" }),
ui.spacer(),
ui.button("+30 sn", "seek", { value: Math.min(track.elapsed + 30, track.duration) }),
],
{ axis: "h", spacing: 8 },
),
]),
};
},
async onAction(action, _ctx, value) {
if (action === "previous") await media.previous();
if (action === "toggle") await media.toggle();
if (action === "next") await media.next();
if (action === "seek") await media.seek(Number(value));
},
});Pomodoro
Zamanlayıcılar, kompakt alanda kendi sayan ui.timer, ada bildirimleri, kalıcı durum ve Kestirmeler komutları (adanotch ext local.sen.pomodoro start).
const { activity, ui, events, island, notify } = AdanotchKit;
interface Settings {
focusMinutes: number;
breakMinutes: number;
}
type Phase = "idle" | "focus" | "break";
interface State {
phase: Phase;
endsAt: number | null;
done: number;
}
let timer: number | undefined;
activity<Settings>({
setup(ctx) {
events.on("command", ({ name }) => {
if (name === "start") start(ctx, "focus");
if (name === "stop") stop(ctx);
});
// Adanotch yeniden başladıysa süren turu kaldığı yerden sürdür.
const state = load(ctx);
if (state.endsAt && state.endsAt > Date.now()) schedule(ctx, state.endsAt);
},
update(ctx) {
const { phase, endsAt, done } = load(ctx);
const running = phase !== "idle" && endsAt !== null;
return {
compact: running
? { leading: ui.icon(phase === "focus" ? "brain.head.profile" : "cup.and.saucer"), trailing: ui.timer(endsAt) }
: { leading: ui.icon("timer", { tone: "secondary" }) },
expanded: ui.stack([
running
? ui.timer(endsAt, { textStyle: "display", tone: phase === "focus" ? "accent" : "success" })
: ui.text("Hazır", { style: "display", tone: "secondary" }),
ui.text(`Bugün ${done} tur`, { style: "caption", tone: "secondary" }),
ui.stack(
[
running
? ui.button("Durdur", "stop", { icon: "stop.fill" })
: ui.button(`${ctx.settings.focusMinutes} dk odaklan`, "start", { icon: "play.fill", primary: true }),
],
{ axis: "h" },
),
]),
};
},
onAction(action, ctx) {
if (action === "start") start(ctx, "focus");
if (action === "stop") stop(ctx);
},
});
function load(ctx: Adanotch.Context<Settings>): State {
return ctx.storage.get<State>("state") ?? { phase: "idle", endsAt: null, done: 0 };
}
function save(ctx: Adanotch.Context<Settings>, state: State) {
ctx.storage.set("state", state);
}
function start(ctx: Adanotch.Context<Settings>, phase: Exclude<Phase, "idle">) {
const minutes = phase === "focus" ? ctx.settings.focusMinutes : ctx.settings.breakMinutes;
const endsAt = Date.now() + minutes * 60_000;
save(ctx, { ...load(ctx), phase, endsAt });
schedule(ctx, endsAt);
ctx.refresh();
}
function stop(ctx: Adanotch.Context<Settings>) {
clearTimeout(timer);
save(ctx, { ...load(ctx), phase: "idle", endsAt: null });
island.hide("pomodoro");
ctx.refresh();
}
function schedule(ctx: Adanotch.Context<Settings>, endsAt: number) {
clearTimeout(timer);
timer = setTimeout(() => finish(ctx), endsAt - Date.now());
}
function finish(ctx: Adanotch.Context<Settings>) {
const state = load(ctx);
if (state.phase === "focus") {
save(ctx, { ...state, done: state.done + 1 });
notify.island({ text: "Mola zamanı", icon: "cup.and.saucer", tone: "success", duration: 5 });
start(ctx, "break");
} else {
notify.island({ text: "Mola bitti", icon: "brain.head.profile", tone: "accent" });
stop(ctx);
}
}GitHub PR'ları
Keychain'de saklanan bir token (secret ayarı), kimlik doğrulamalı fetch, tıklanabilir liste ve open().
const { activity, ui, secrets, open } = AdanotchKit;
interface Settings {
query: string;
}
interface SearchResult {
total_count: number;
items: {
number: number;
title: string;
html_url: string;
draft?: boolean;
repository_url: string;
user: { login: string; avatar_url: string };
}[];
}
activity<Settings>({
async update(ctx) {
const token = await secrets.get("token");
if (!token) {
return {
compact: { leading: ui.icon("arrow.triangle.pull", { tone: "secondary" }) },
expanded: ui.markdown("Ayarlar › Eklentiler'den bir **GitHub token** gir."),
};
}
const url = new URL("https://api.github.com/search/issues");
url.searchParams.set("q", `is:pr is:open ${ctx.settings.query}`);
url.searchParams.set("per_page", "20");
const res = await fetch(url, {
headers: {
authorization: `Bearer ${token}`,
accept: "application/vnd.github+json",
"x-github-api-version": "2022-11-28",
},
});
if (res.status === 401) {
return { expanded: ui.text("Token geçersiz", { tone: "danger" }) };
}
if (!res.ok) throw new Error(`GitHub ${res.status}`);
const { total_count, items } = await res.json<SearchResult>();
return {
compact: {
leading: ui.icon("arrow.triangle.pull"),
trailing: ui.badge(String(total_count), { tone: total_count > 0 ? "accent" : "secondary" }),
},
expanded: ui.scroll(
ui.list(
items.map((pr) => ({
title: pr.title,
subtitle: `${pr.repository_url.split("/").slice(-2).join("/")} #${pr.number} · ${pr.user.login}`,
icon: pr.draft ? "pencil.circle" : "arrow.triangle.pull",
tone: pr.draft ? ("secondary" as const) : ("default" as const),
action: "open",
value: pr.html_url,
})),
),
{ maxHeight: 280 },
),
};
},
async onAction(action, _ctx, value) {
if (action === "open" && typeof value === "string") await open(value);
},
});GitHub token için ince ayarlı bir token oluştur; yalnız okuma izni (Pull requests: Read) yeter.