mirror of
https://github.com/anomalyco/opencode.git
synced 2026-11-07 16:17:25 +03:00
fix(site): link a release's other highlights when an area filter hides them (#62500)
This commit is contained in:
@@ -100,14 +100,22 @@
|
||||
/* Settings › Changelog title › mono: the earlier mono titles, headings and labels. */
|
||||
[data-title="mono"]
|
||||
&
|
||||
:is([data-slot="subhead"], [data-slot="group-label"], [data-slot="area"], [data-slot="show-more"]) {
|
||||
:is(
|
||||
[data-slot="subhead"],
|
||||
[data-slot="group-label"],
|
||||
[data-slot="area"],
|
||||
[data-slot="show-more"],
|
||||
[data-slot="all-highlights"]
|
||||
) {
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--mono-size);
|
||||
font-weight: 400;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
[data-title="mono"] & :is([data-slot="area"], [data-slot="show-more"], [data-slot="group-label"]) {
|
||||
[data-title="mono"]
|
||||
&
|
||||
:is([data-slot="area"], [data-slot="show-more"], [data-slot="all-highlights"], [data-slot="group-label"]) {
|
||||
font-size: var(--fs-meta);
|
||||
line-height: 1.55;
|
||||
}
|
||||
@@ -484,9 +492,17 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* Under a filtered entry's highlights, a link to the release's page for the ones the filter left out. */
|
||||
[data-slot="all-highlights"] {
|
||||
margin-top: -1.25rem;
|
||||
}
|
||||
|
||||
[data-slot="show-more"] {
|
||||
align-self: flex-start;
|
||||
margin-inline-start: 7.5rem;
|
||||
}
|
||||
|
||||
:is([data-slot="show-more"], [data-slot="all-highlights"]) {
|
||||
align-self: flex-start;
|
||||
font-size: var(--fs-meta);
|
||||
color: var(--text-weak);
|
||||
text-decoration: none;
|
||||
@@ -563,6 +579,11 @@
|
||||
margin-block: -0.75rem;
|
||||
}
|
||||
|
||||
[data-slot="all-highlights"] {
|
||||
padding-block: 0.75rem;
|
||||
margin-block: -1.75rem -0.75rem;
|
||||
}
|
||||
|
||||
[data-slot="meta"] :is(a, [data-slot="copy-link"]) {
|
||||
padding-block: 0.75rem;
|
||||
margin-block: -0.75rem;
|
||||
|
||||
@@ -775,6 +775,11 @@ function EntryBody(props: { entry: ChangelogEntry; level: 1 | 2 | 3; media?: Sli
|
||||
</section>
|
||||
)}
|
||||
</For>
|
||||
<Show when={props.entry.hiddenFeatures > 0}>
|
||||
<a data-slot="all-highlights" href={page()}>
|
||||
{i18n.t("changelog.highlights.all", { tag: props.entry.releases[0].tag })}
|
||||
</a>
|
||||
</Show>
|
||||
|
||||
<Show when={props.entry.breaking.length > 0}>
|
||||
<section data-slot="group" data-kind="breaking">
|
||||
|
||||
@@ -282,6 +282,7 @@ export const dict = {
|
||||
"changelog.media.pause": "إيقاف مؤقت",
|
||||
"changelog.media.play": "تشغيل الفيديو",
|
||||
"changelog.list.more": "{{count}} أخرى",
|
||||
"changelog.highlights.all": "كل أبرز ما في {{tag}}",
|
||||
"site.newTab": "{{label}} (يفتح في علامة تبويب جديدة)",
|
||||
"site.products.label": "المنتجات",
|
||||
"site.products.gridLabel": "منتجات OpenCode",
|
||||
|
||||
@@ -284,6 +284,7 @@ export const dict = {
|
||||
"changelog.media.pause": "Pausar vídeo",
|
||||
"changelog.media.play": "Tocar vídeo",
|
||||
"changelog.list.more": "Mais {{count}}",
|
||||
"changelog.highlights.all": "Ver todos os destaques da {{tag}}",
|
||||
"site.newTab": "{{label}} (abre em uma nova aba)",
|
||||
"site.products.label": "Produtos",
|
||||
"site.products.gridLabel": "Produtos OpenCode",
|
||||
|
||||
@@ -285,6 +285,7 @@ export const dict = {
|
||||
"changelog.media.pause": "Pause",
|
||||
"changelog.media.play": "Afspil",
|
||||
"changelog.list.more": "{{count}} mere",
|
||||
"changelog.highlights.all": "Se alle højdepunkter i {{tag}}",
|
||||
"site.newTab": "{{label}} (åbner i en ny fane)",
|
||||
"site.products.label": "Produkter",
|
||||
"site.products.gridLabel": "OpenCode-produkter",
|
||||
|
||||
@@ -284,6 +284,7 @@ export const dict = {
|
||||
"changelog.media.pause": "Pausieren",
|
||||
"changelog.media.play": "Abspielen",
|
||||
"changelog.list.more": "{{count}} weitere",
|
||||
"changelog.highlights.all": "Alle Highlights in {{tag}} ansehen",
|
||||
"site.newTab": "{{label}} (öffnet in neuem Tab)",
|
||||
"site.products.label": "Produkte",
|
||||
"site.products.gridLabel": "OpenCode-Produkte",
|
||||
|
||||
@@ -284,6 +284,7 @@ export const dict = {
|
||||
"changelog.media.pause": "Pause video",
|
||||
"changelog.media.play": "Play video",
|
||||
"changelog.list.more": "{{count}} more",
|
||||
"changelog.highlights.all": "See all highlights in {{tag}}",
|
||||
"site.newTab": "{{label}} (opens in a new tab)",
|
||||
"site.products.label": "Products",
|
||||
"site.products.gridLabel": "OpenCode products",
|
||||
|
||||
@@ -285,6 +285,7 @@ export const dict = {
|
||||
"changelog.media.pause": "Pausar",
|
||||
"changelog.media.play": "Reproducir",
|
||||
"changelog.list.more": "{{count}} más",
|
||||
"changelog.highlights.all": "Ver todos los destacados de {{tag}}",
|
||||
"site.newTab": "{{label}} (se abre en una pestaña nueva)",
|
||||
"site.products.label": "Productos",
|
||||
"site.products.gridLabel": "Productos de OpenCode",
|
||||
|
||||
@@ -284,6 +284,7 @@ export const dict = {
|
||||
"changelog.media.pause": "Pause vidéo",
|
||||
"changelog.media.play": "Lire la vidéo",
|
||||
"changelog.list.more": "{{count}} de plus",
|
||||
"changelog.highlights.all": "Voir tous les points forts de {{tag}}",
|
||||
"site.newTab": "{{label}} (s'ouvre dans un nouvel onglet)",
|
||||
"site.products.label": "Produits",
|
||||
"site.products.gridLabel": "Produits OpenCode",
|
||||
|
||||
@@ -284,6 +284,7 @@ export const dict = {
|
||||
"changelog.media.pause": "Metti in pausa",
|
||||
"changelog.media.play": "Riproduci",
|
||||
"changelog.list.more": "Altri {{count}}",
|
||||
"changelog.highlights.all": "Vedi tutte le novità principali di {{tag}}",
|
||||
"site.newTab": "{{label}} (si apre in una nuova scheda)",
|
||||
"site.products.label": "Prodotti",
|
||||
"site.products.gridLabel": "Prodotti OpenCode",
|
||||
|
||||
@@ -284,6 +284,7 @@ export const dict = {
|
||||
"changelog.media.pause": "動画を一時停止",
|
||||
"changelog.media.play": "動画を再生",
|
||||
"changelog.list.more": "ほか{{count}}件",
|
||||
"changelog.highlights.all": "{{tag}} のハイライトをすべて見る",
|
||||
"site.newTab": "{{label}}(新しいタブで開きます)",
|
||||
"site.products.label": "製品",
|
||||
"site.products.gridLabel": "OpenCodeの製品",
|
||||
|
||||
@@ -285,6 +285,7 @@ export const dict = {
|
||||
"changelog.media.pause": "동영상 일시정지",
|
||||
"changelog.media.play": "동영상 재생",
|
||||
"changelog.list.more": "{{count}}개 더 보기",
|
||||
"changelog.highlights.all": "{{tag}}의 하이라이트 모두 보기",
|
||||
"site.newTab": "{{label}} (새 탭에서 열림)",
|
||||
"site.products.label": "제품",
|
||||
"site.products.gridLabel": "OpenCode 제품",
|
||||
|
||||
@@ -285,6 +285,7 @@ export const dict = {
|
||||
"changelog.media.pause": "Pause",
|
||||
"changelog.media.play": "Spill av",
|
||||
"changelog.list.more": "{{count}} til",
|
||||
"changelog.highlights.all": "Se alle høydepunkter i {{tag}}",
|
||||
"site.newTab": "{{label}} (åpnes i ny fane)",
|
||||
"site.products.label": "Produkter",
|
||||
"site.products.gridLabel": "OpenCode-produkter",
|
||||
|
||||
@@ -285,6 +285,7 @@ export const dict = {
|
||||
"changelog.media.pause": "Wstrzymaj",
|
||||
"changelog.media.play": "Odtwórz",
|
||||
"changelog.list.more": "Jeszcze {{count}}",
|
||||
"changelog.highlights.all": "Zobacz wszystkie najważniejsze zmiany w {{tag}}",
|
||||
"site.newTab": "{{label}} (otwiera się w nowej karcie)",
|
||||
"site.products.label": "Produkty",
|
||||
"site.products.gridLabel": "Produkty OpenCode",
|
||||
|
||||
@@ -281,6 +281,7 @@ export const dict = {
|
||||
"changelog.media.pause": "Пауза",
|
||||
"changelog.media.play": "Смотреть",
|
||||
"changelog.list.more": "Ещё {{count}}",
|
||||
"changelog.highlights.all": "Все главные изменения {{tag}}",
|
||||
"site.newTab": "{{label}} (откроется в новой вкладке)",
|
||||
"site.products.label": "Продукты",
|
||||
"site.products.gridLabel": "Продукты OpenCode",
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
"changelog.fixes": "3bifmqr5ao3y0",
|
||||
"changelog.hero.subtitle": "1dauy167rac9p",
|
||||
"changelog.hero.title": "2mneds13xamjs",
|
||||
"changelog.highlights.all": "2r9inwsci9o03",
|
||||
"changelog.improvements": "28n9u0fcxvske",
|
||||
"changelog.list.more": "ctoxownatowu",
|
||||
"changelog.media.label": "2v1244mw9mfts",
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
"changelog.fixes": "3bifmqr5ao3y0",
|
||||
"changelog.hero.subtitle": "1dauy167rac9p",
|
||||
"changelog.hero.title": "2mneds13xamjs",
|
||||
"changelog.highlights.all": "2r9inwsci9o03",
|
||||
"changelog.improvements": "28n9u0fcxvske",
|
||||
"changelog.list.more": "ctoxownatowu",
|
||||
"changelog.media.label": "2v1244mw9mfts",
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
"changelog.fixes": "3bifmqr5ao3y0",
|
||||
"changelog.hero.subtitle": "1dauy167rac9p",
|
||||
"changelog.hero.title": "2mneds13xamjs",
|
||||
"changelog.highlights.all": "2r9inwsci9o03",
|
||||
"changelog.improvements": "28n9u0fcxvske",
|
||||
"changelog.list.more": "ctoxownatowu",
|
||||
"changelog.media.label": "2v1244mw9mfts",
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
"changelog.fixes": "3bifmqr5ao3y0",
|
||||
"changelog.hero.subtitle": "1dauy167rac9p",
|
||||
"changelog.hero.title": "2mneds13xamjs",
|
||||
"changelog.highlights.all": "2r9inwsci9o03",
|
||||
"changelog.improvements": "28n9u0fcxvske",
|
||||
"changelog.list.more": "ctoxownatowu",
|
||||
"changelog.media.label": "2v1244mw9mfts",
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
"changelog.fixes": "3bifmqr5ao3y0",
|
||||
"changelog.hero.subtitle": "1dauy167rac9p",
|
||||
"changelog.hero.title": "2mneds13xamjs",
|
||||
"changelog.highlights.all": "2r9inwsci9o03",
|
||||
"changelog.improvements": "28n9u0fcxvske",
|
||||
"changelog.list.more": "ctoxownatowu",
|
||||
"changelog.media.label": "2v1244mw9mfts",
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
"changelog.fixes": "3bifmqr5ao3y0",
|
||||
"changelog.hero.subtitle": "1dauy167rac9p",
|
||||
"changelog.hero.title": "2mneds13xamjs",
|
||||
"changelog.highlights.all": "2r9inwsci9o03",
|
||||
"changelog.improvements": "28n9u0fcxvske",
|
||||
"changelog.list.more": "ctoxownatowu",
|
||||
"changelog.media.label": "2v1244mw9mfts",
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
"changelog.fixes": "3bifmqr5ao3y0",
|
||||
"changelog.hero.subtitle": "1dauy167rac9p",
|
||||
"changelog.hero.title": "2mneds13xamjs",
|
||||
"changelog.highlights.all": "2r9inwsci9o03",
|
||||
"changelog.improvements": "28n9u0fcxvske",
|
||||
"changelog.list.more": "ctoxownatowu",
|
||||
"changelog.media.label": "2v1244mw9mfts",
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
"changelog.fixes": "3bifmqr5ao3y0",
|
||||
"changelog.hero.subtitle": "1dauy167rac9p",
|
||||
"changelog.hero.title": "2mneds13xamjs",
|
||||
"changelog.highlights.all": "2r9inwsci9o03",
|
||||
"changelog.improvements": "28n9u0fcxvske",
|
||||
"changelog.list.more": "ctoxownatowu",
|
||||
"changelog.media.label": "2v1244mw9mfts",
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
"changelog.fixes": "3bifmqr5ao3y0",
|
||||
"changelog.hero.subtitle": "1dauy167rac9p",
|
||||
"changelog.hero.title": "2mneds13xamjs",
|
||||
"changelog.highlights.all": "2r9inwsci9o03",
|
||||
"changelog.improvements": "28n9u0fcxvske",
|
||||
"changelog.list.more": "ctoxownatowu",
|
||||
"changelog.media.label": "2v1244mw9mfts",
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
"changelog.fixes": "3bifmqr5ao3y0",
|
||||
"changelog.hero.subtitle": "1dauy167rac9p",
|
||||
"changelog.hero.title": "2mneds13xamjs",
|
||||
"changelog.highlights.all": "2r9inwsci9o03",
|
||||
"changelog.improvements": "28n9u0fcxvske",
|
||||
"changelog.list.more": "ctoxownatowu",
|
||||
"changelog.media.label": "2v1244mw9mfts",
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
"changelog.fixes": "3bifmqr5ao3y0",
|
||||
"changelog.hero.subtitle": "1dauy167rac9p",
|
||||
"changelog.hero.title": "2mneds13xamjs",
|
||||
"changelog.highlights.all": "2r9inwsci9o03",
|
||||
"changelog.improvements": "28n9u0fcxvske",
|
||||
"changelog.list.more": "ctoxownatowu",
|
||||
"changelog.media.label": "2v1244mw9mfts",
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
"changelog.fixes": "3bifmqr5ao3y0",
|
||||
"changelog.hero.subtitle": "1dauy167rac9p",
|
||||
"changelog.hero.title": "2mneds13xamjs",
|
||||
"changelog.highlights.all": "2r9inwsci9o03",
|
||||
"changelog.improvements": "28n9u0fcxvske",
|
||||
"changelog.list.more": "ctoxownatowu",
|
||||
"changelog.media.label": "2v1244mw9mfts",
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
"changelog.fixes": "3bifmqr5ao3y0",
|
||||
"changelog.hero.subtitle": "1dauy167rac9p",
|
||||
"changelog.hero.title": "2mneds13xamjs",
|
||||
"changelog.highlights.all": "2r9inwsci9o03",
|
||||
"changelog.improvements": "28n9u0fcxvske",
|
||||
"changelog.list.more": "ctoxownatowu",
|
||||
"changelog.media.label": "2v1244mw9mfts",
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
"changelog.fixes": "3bifmqr5ao3y0",
|
||||
"changelog.hero.subtitle": "1dauy167rac9p",
|
||||
"changelog.hero.title": "2mneds13xamjs",
|
||||
"changelog.highlights.all": "2r9inwsci9o03",
|
||||
"changelog.improvements": "28n9u0fcxvske",
|
||||
"changelog.list.more": "ctoxownatowu",
|
||||
"changelog.media.label": "2v1244mw9mfts",
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
"changelog.fixes": "3bifmqr5ao3y0",
|
||||
"changelog.hero.subtitle": "1dauy167rac9p",
|
||||
"changelog.hero.title": "2mneds13xamjs",
|
||||
"changelog.highlights.all": "2r9inwsci9o03",
|
||||
"changelog.improvements": "28n9u0fcxvske",
|
||||
"changelog.list.more": "ctoxownatowu",
|
||||
"changelog.media.label": "2v1244mw9mfts",
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
"changelog.fixes": "3bifmqr5ao3y0",
|
||||
"changelog.hero.subtitle": "1dauy167rac9p",
|
||||
"changelog.hero.title": "2mneds13xamjs",
|
||||
"changelog.highlights.all": "2r9inwsci9o03",
|
||||
"changelog.improvements": "28n9u0fcxvske",
|
||||
"changelog.list.more": "ctoxownatowu",
|
||||
"changelog.media.label": "2v1244mw9mfts",
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
"changelog.fixes": "3bifmqr5ao3y0",
|
||||
"changelog.hero.subtitle": "1dauy167rac9p",
|
||||
"changelog.hero.title": "2mneds13xamjs",
|
||||
"changelog.highlights.all": "2r9inwsci9o03",
|
||||
"changelog.improvements": "28n9u0fcxvske",
|
||||
"changelog.list.more": "ctoxownatowu",
|
||||
"changelog.media.label": "2v1244mw9mfts",
|
||||
|
||||
@@ -285,6 +285,7 @@ export const dict = {
|
||||
"changelog.media.pause": "หยุดวิดีโอชั่วคราว",
|
||||
"changelog.media.play": "เล่นวิดีโอ",
|
||||
"changelog.list.more": "อีก {{count}} รายการ",
|
||||
"changelog.highlights.all": "ดูไฮไลต์ทั้งหมดใน {{tag}}",
|
||||
"site.newTab": "{{label}} (เปิดในแท็บใหม่)",
|
||||
"site.products.label": "ผลิตภัณฑ์",
|
||||
"site.products.gridLabel": "ผลิตภัณฑ์ OpenCode",
|
||||
|
||||
@@ -285,6 +285,7 @@ export const dict = {
|
||||
"changelog.media.pause": "Durdur",
|
||||
"changelog.media.play": "Oynat",
|
||||
"changelog.list.more": "{{count}} tane daha",
|
||||
"changelog.highlights.all": "{{tag}} sürümünün tüm öne çıkanlarını gör",
|
||||
"site.newTab": "{{label}} (yeni sekmede açılır)",
|
||||
"site.products.label": "Ürünler",
|
||||
"site.products.gridLabel": "OpenCode ürünleri",
|
||||
|
||||
@@ -280,6 +280,7 @@ export const dict = {
|
||||
"changelog.media.pause": "Пауза",
|
||||
"changelog.media.play": "Дивитися",
|
||||
"changelog.list.more": "Ще {{count}}",
|
||||
"changelog.highlights.all": "Усі головні зміни {{tag}}",
|
||||
"site.newTab": "{{label}} (відкриється в новій вкладці)",
|
||||
"site.products.label": "Продукти",
|
||||
"site.products.gridLabel": "Продукти OpenCode",
|
||||
|
||||
@@ -284,6 +284,7 @@ export const dict = {
|
||||
"changelog.media.pause": "暂停视频",
|
||||
"changelog.media.play": "播放视频",
|
||||
"changelog.list.more": "还有 {{count}} 项",
|
||||
"changelog.highlights.all": "查看 {{tag}} 的全部亮点",
|
||||
"site.newTab": "{{label}}(在新标签页中打开)",
|
||||
"site.products.label": "产品",
|
||||
"site.products.gridLabel": "OpenCode 产品",
|
||||
|
||||
@@ -285,6 +285,7 @@ export const dict = {
|
||||
"changelog.media.pause": "暫停影片",
|
||||
"changelog.media.play": "播放影片",
|
||||
"changelog.list.more": "還有 {{count}} 項",
|
||||
"changelog.highlights.all": "查看 {{tag}} 的所有亮點",
|
||||
"site.newTab": "{{label}}(在新分頁中開啟)",
|
||||
"site.products.label": "產品",
|
||||
"site.products.gridLabel": "OpenCode 產品",
|
||||
|
||||
@@ -96,6 +96,11 @@ export function changelogEntryMarkdown(
|
||||
...entry.features.map((feature) =>
|
||||
blocks(`${heading}# ${feature.title}`, feature.body && `${feature.body}${refsMarkdown(feature)}`).trimEnd(),
|
||||
),
|
||||
entry.hiddenFeatures > 0 &&
|
||||
link(
|
||||
options.context.t("changelog.highlights.all", { tag: entry.releases[0].tag }),
|
||||
options.context.route(`/changelog/${entry.releases[0].tag}`),
|
||||
),
|
||||
entry.breaking.length > 0 &&
|
||||
blocks(
|
||||
`${heading}# ${options.context.t("changelog.breaking")}`,
|
||||
|
||||
@@ -36,6 +36,8 @@ export type ChangelogEntry = {
|
||||
improved: ChangelogNote[]
|
||||
fixed: ChangelogNote[]
|
||||
media: ChangelogMedia[]
|
||||
/** The release's highlights an area filter left out; the entry links to its page for them. */
|
||||
hiddenFeatures: number
|
||||
}
|
||||
|
||||
/** An area the notes are filed under: its key in `?area=`, and its label. */
|
||||
@@ -183,11 +185,15 @@ export function parseReleaseNotes(body: string): ReleaseNotes {
|
||||
export function changelogTimeline(releases: ChangelogRelease[], areas?: string[]) {
|
||||
const units = releases
|
||||
.filter((release) => /^v\d/.test(release.tag))
|
||||
.map((release) => ({
|
||||
date: release.date.slice(0, 10),
|
||||
releases: [{ tag: release.tag, url: release.url }],
|
||||
notes: areas ? onlyAreas(release.notes, areas) : release.notes,
|
||||
}))
|
||||
.map((release) => {
|
||||
const notes = areas ? onlyAreas(release.notes, areas) : release.notes
|
||||
return {
|
||||
date: release.date.slice(0, 10),
|
||||
releases: [{ tag: release.tag, url: release.url }],
|
||||
notes,
|
||||
hiddenFeatures: release.notes.features.length - notes.features.length,
|
||||
}
|
||||
})
|
||||
.filter((unit) => noteCount(unit.notes) > 0)
|
||||
const days = units.reduce<(typeof units)[]>((acc, unit) => {
|
||||
const last = acc.at(-1)
|
||||
@@ -374,9 +380,11 @@ export function bodyBlocks(body: string) {
|
||||
.blocks.filter((block) => block.type !== "p" || block.text)
|
||||
}
|
||||
|
||||
function entry(unit: { date: string; releases: { tag: string; url: string }[]; notes: ReleaseNotes }): ChangelogEntry {
|
||||
type Unit = { date: string; releases: { tag: string; url: string }[]; notes: ReleaseNotes; hiddenFeatures: number }
|
||||
|
||||
function entry(unit: Unit): ChangelogEntry {
|
||||
const notes = unit.notes
|
||||
const base = { date: unit.date, releases: unit.releases, ...notes }
|
||||
const base = { date: unit.date, releases: unit.releases, hiddenFeatures: unit.hiddenFeatures, ...notes }
|
||||
const lead = notes.features[0]
|
||||
if (lead)
|
||||
return {
|
||||
@@ -534,13 +542,11 @@ function tiny(notes: ReleaseNotes) {
|
||||
return notes.features.length === 0 && notes.breaking.length === 0 && notes.improved.length + notes.fixed.length <= 3
|
||||
}
|
||||
|
||||
function merge(
|
||||
a: { date: string; releases: { tag: string; url: string }[]; notes: ReleaseNotes },
|
||||
b: { date: string; releases: { tag: string; url: string }[]; notes: ReleaseNotes },
|
||||
) {
|
||||
function merge(a: Unit, b: Unit) {
|
||||
return {
|
||||
date: a.date,
|
||||
releases: [...a.releases, ...b.releases],
|
||||
hiddenFeatures: a.hiddenFeatures + b.hiddenFeatures,
|
||||
notes: {
|
||||
media: [...a.notes.media, ...b.notes.media],
|
||||
features: [...a.notes.features, ...b.notes.features],
|
||||
|
||||
@@ -54,12 +54,12 @@ function item(entry: ChangelogEntry) {
|
||||
<guid isPermaLink="true">${url}</guid>
|
||||
<pubDate>${new Date(`${entry.date}T12:00:00Z`).toUTCString()}</pubDate>
|
||||
<description>${escape(plain(entry.summary || entry.title))}</description>
|
||||
<content:encoded><![CDATA[${html(entry).replaceAll("]]>", "]]>")}]]></content:encoded>
|
||||
<content:encoded><![CDATA[${html(entry, url).replaceAll("]]>", "]]>")}]]></content:encoded>
|
||||
</item>`
|
||||
}
|
||||
|
||||
/** An entry as simple HTML for feed readers: summary, highlights, then the lists. */
|
||||
function html(entry: ChangelogEntry) {
|
||||
function html(entry: ChangelogEntry, url: string) {
|
||||
const list = (title: string, notes: ChangelogNote[]) =>
|
||||
notes.length > 0 &&
|
||||
`<h3>${title}</h3><ul>${notes
|
||||
@@ -73,6 +73,8 @@ function html(entry: ChangelogEntry) {
|
||||
...entry.features.map(
|
||||
(feature) => `<h3>${inline(feature.title)}</h3>${feature.body ? `<p>${inline(feature.body)}</p>` : ""}`,
|
||||
),
|
||||
entry.hiddenFeatures > 0 &&
|
||||
`<p><a href="${escape(url)}">See all highlights in ${escape(entry.releases[0].tag)}</a></p>`,
|
||||
entry.breaking.length > 0 &&
|
||||
`<h3>Breaking changes</h3>${entry.breaking.map((change) => `<p>${change.title ? `<strong>${inline(change.title)}</strong> ` : ""}${inline(change.body)}</p>`).join("")}`,
|
||||
list("Improvements", entry.improved),
|
||||
|
||||
@@ -3,8 +3,8 @@ import { extensionPages } from "~/component/site/extension-data"
|
||||
|
||||
const browse = extensionPages.find((extension) => extension.slug === "browse")!
|
||||
|
||||
// The browse page (anomalyco/browse): the Chrome Web Store is the main action, the standalone
|
||||
// installer is the command, and the product name is always lowercase.
|
||||
// The browse page (anomalyco/browse): the Chrome Web Store is the main action, the install leads
|
||||
// with the extension and then lists the CLI commands, and the product name is always lowercase.
|
||||
describe("browse", () => {
|
||||
test("is a product at /product/browse with the Chrome Web Store as its main action", () => {
|
||||
expect(browse.product).toBe(true)
|
||||
@@ -12,10 +12,12 @@ describe("browse", () => {
|
||||
expect(browse.source).toBe("https://github.com/anomalyco/browse")
|
||||
})
|
||||
|
||||
test("installs from the standalone script, with no Node.js requirement", () => {
|
||||
expect(browse.install.command).toBe("curl -fsSL https://opencode.ai/browse/install | bash")
|
||||
expect(browse.install.lead).toContain("https://opencode.ai/browse/install.ps1")
|
||||
expect(browse.install.lead).not.toMatch(/node\.?js|npx/i)
|
||||
test("leads the install with the extension, then lists the standalone scripts first", () => {
|
||||
expect(browse.install.lead).toContain("[Chrome extension]")
|
||||
expect(browse.install.commands?.slice(0, 2)).toEqual([
|
||||
{ method: "curl", command: "curl -fsSL https://opencode.ai/browse/install | bash" },
|
||||
{ method: "pwsh", command: "irm https://opencode.ai/browse/install.ps1 | iex" },
|
||||
])
|
||||
})
|
||||
|
||||
test("keeps the documented title and description lengths", () => {
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
import { describe, expect, test } from "bun:test"
|
||||
import type { ChangelogRelease } from "../src/lib/changelog"
|
||||
import { changelogEntryView, changelogView, parseReleaseNotes } from "../src/lib/changelog-notes"
|
||||
|
||||
const pr = (number: number) => `[#${number}](https://github.com/anomalyco/opencode/pull/${number})`
|
||||
|
||||
// The shape of v2.2.6: five highlights, each repeated as a note under the area it belongs to.
|
||||
const body = [
|
||||
"### Highlights",
|
||||
"",
|
||||
`- **Shared MCP Connections**: Share one connection across projects. (${pr(61185)})`,
|
||||
`- **GUI Plugin Themes and New Slots**: GUI plugins can add color themes. (${pr(61485)}, ${pr(61533)})`,
|
||||
`- **Mobile Composer Dock**: Composer actions sit in a touch dock. (${pr(61773)})`,
|
||||
`- **Scripted Runs in Worktrees**: Run \`opencode run -w\`. (${pr(60712)})`,
|
||||
`- **Automatically Archived Runs**: Add \`--archived\` to \`opencode run\`. (${pr(61270)})`,
|
||||
"",
|
||||
"### Core",
|
||||
"",
|
||||
"#### Improvements",
|
||||
"",
|
||||
`- Added opt-in MCP connection sharing through \`pool: true\`. (${pr(61185)})`,
|
||||
`- Added \`opencode run --archived\`. (${pr(61270)})`,
|
||||
`- Added \`opencode run --worktree\`. (${pr(60712)})`,
|
||||
`- Added Azure Messages support for Foundry Claude models. (${pr(60934)})`,
|
||||
"",
|
||||
"### Desktop",
|
||||
"",
|
||||
"#### Improvements",
|
||||
"",
|
||||
`- Added a mobile composer dock for touch devices. (${pr(61773)})`,
|
||||
`- Added close-other-tabs and close-tabs-after actions to the tab menu. (${pr(61631)})`,
|
||||
"",
|
||||
"### Extensions",
|
||||
"",
|
||||
"#### Improvements",
|
||||
"",
|
||||
`- GUI plugins can contribute themes through the \`Theme\` registry. (${pr(61485)})`,
|
||||
`- Added \`CodeBlock\` and \`CodeBlockAction\` GUI registries. (${pr(61533)})`,
|
||||
].join("\n")
|
||||
|
||||
const release: ChangelogRelease = {
|
||||
tag: "v2.2.6",
|
||||
name: "v2.2.6",
|
||||
date: "2026-11-06T23:37:20Z",
|
||||
url: "https://github.com/anomalyco/opencode/releases/tag/v2.2.6",
|
||||
highlights: [],
|
||||
sections: [],
|
||||
notes: parseReleaseNotes(body),
|
||||
}
|
||||
|
||||
describe("release highlights", () => {
|
||||
test("every highlight is read, with the areas of the notes that repeat it", () => {
|
||||
expect(release.notes.features.map((feature) => [feature.title, feature.areas])).toEqual([
|
||||
["Shared MCP Connections", ["Core"]],
|
||||
["GUI Plugin Themes and New Slots", ["Extensions"]],
|
||||
["Mobile Composer Dock", ["Desktop"]],
|
||||
["Scripted Runs in Worktrees", ["Core"]],
|
||||
["Automatically Archived Runs", ["Core"]],
|
||||
])
|
||||
})
|
||||
|
||||
test("all areas show every highlight: the first leads, the rest follow", () => {
|
||||
const entry = changelogView([release]).entries[0]
|
||||
expect(entry.title).toBe("Shared MCP Connections")
|
||||
expect(entry.features.map((feature) => feature.title)).toEqual([
|
||||
"GUI Plugin Themes and New Slots",
|
||||
"Mobile Composer Dock",
|
||||
"Scripted Runs in Worktrees",
|
||||
"Automatically Archived Runs",
|
||||
])
|
||||
expect(entry.hiddenFeatures).toBe(0)
|
||||
})
|
||||
|
||||
test("a release's own page shows every highlight", () => {
|
||||
const entry = changelogEntryView([release], "v2.2.6").entries[0]
|
||||
expect([entry.title, ...entry.features.map((feature) => feature.title)]).toHaveLength(5)
|
||||
expect(entry.hiddenFeatures).toBe(0)
|
||||
})
|
||||
|
||||
test("an area shows its own highlights and counts the ones it leaves out", () => {
|
||||
const desktop = changelogView([release], { filter: ["desktop"] }).entries[0]
|
||||
expect(desktop.title).toBe("Mobile Composer Dock")
|
||||
expect(desktop.features).toEqual([])
|
||||
expect(desktop.hiddenFeatures).toBe(4)
|
||||
|
||||
const core = changelogView([release], { filter: ["core"] }).entries[0]
|
||||
expect([core.title, ...core.features.map((feature) => feature.title)]).toEqual([
|
||||
"Shared MCP Connections",
|
||||
"Scripted Runs in Worktrees",
|
||||
"Automatically Archived Runs",
|
||||
])
|
||||
expect(core.hiddenFeatures).toBe(2)
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user