Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions astro.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,13 @@ export default defineConfig({
},
expressiveCode: {
defaultProps: { wrap: true },
// Match the code-block frame to the card family (ChatExample, CommandInfo,
// VariableInfo): same system radius and hairline border, so an example's
// input code block and its output chat card read as siblings.
styleOverrides: {
borderRadius: 'var(--se-radius)',
borderColor: 'var(--sl-color-gray-5)',
},
shiki: {
langs: [streamelementsGrammar],
},
Expand Down
73 changes: 44 additions & 29 deletions src/components/ChatExample.astro
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
* { persona: 'bot', message: 'It is decidedly so.' },
* ]} />
*/
import { Icon } from '@astrojs/starlight/components';

interface Message {
persona?: 'user' | 'moderator' | 'vip' | 'broadcaster' | 'bot' | 'system';
message: string;
Expand Down Expand Up @@ -73,7 +75,11 @@ const resolved = messages.map((msg) => {
{
resolved.length > 0 && (
<figure class="chat-example not-content">
<figcaption>Example chat</figcaption>
<figcaption class="chat-header">
<Icon name="comment" class="chat-header-icon" />
<span>Example chat</span>
</figcaption>
<div class="chat-body">
{resolved.map((msg) => (
<div
class:list={[
Expand All @@ -98,68 +104,77 @@ const resolved = messages.map((msg) => {
<span class="chat-text">{msg.message}</span>
</div>
))}
</div>
</figure>
)
}

<style>
.chat-example {
position: relative;
overflow: hidden;
margin: 1rem 0;
padding: 1.4rem 0.8rem 0.8rem;
margin: 1.25rem 0;
border: 1px solid var(--sl-color-gray-5);
border-radius: 0.5rem;
background-color: var(--sl-color-black);
}
figcaption {
position: absolute;
top: 0;
right: 0;
padding: 0.1rem 0.5rem;
border-bottom-left-radius: 0.5rem;
border-radius: 0.625rem;
background-color: var(--sl-color-gray-6);
box-shadow: 0 1px 2px rgb(0 0 0 / 0.05);
}
.chat-header {
display: flex;
align-items: center;
gap: 0.4rem;
padding: 0.5rem 0.8rem;
border-bottom: 1px solid var(--sl-color-gray-5);
color: var(--sl-color-gray-2);
font-size: 0.65rem;
font-weight: 700;
letter-spacing: 0.5px;
font-size: var(--sl-text-xs);
font-weight: 600;
letter-spacing: 0.05em;
text-transform: uppercase;
user-select: none;
}
.chat-header-icon {
font-size: 0.95rem;
color: var(--sl-color-gray-3);
}
.chat-body {
display: flex;
flex-direction: column;
gap: 1px;
padding: 0.5rem;
}
.chat-message {
display: flex;
flex-wrap: wrap;
align-items: center;
padding: 4px 10px;
margin-bottom: 2px;
align-items: baseline;
gap: 0 0.35rem;
padding: 0.3rem 0.5rem;
border-radius: 0.35rem;
font-family: var(--sl-font, ui-sans-serif), sans-serif;
font-size: 0.9rem;
line-height: 1.4;
line-height: 1.5;
overflow-wrap: anywhere;
}
.chat-message-bot {
background-color: var(--sl-color-gray-6);
border-inline-start: 3px solid var(--sl-color-accent);
padding-inline-start: 7px;
background-color: var(--sl-color-accent-low);
box-shadow: inset 2px 0 0 var(--sl-color-accent);
}
.chat-message-system {
justify-content: center;
color: var(--sl-color-gray-3);
font-style: italic;
}
.chat-badges {
display: inline-flex;
align-items: center;
height: 1.1em;
margin-inline-end: 3px;
gap: 2px;
align-self: center;
}
.chat-badges img {
width: 1.1em;
height: 1.1em;
margin-inline-end: 3px;
width: 1.05em;
height: 1.05em;
border-radius: 2px;
}
.chat-username {
font-weight: 700;
margin-inline-end: 5px;
white-space: nowrap;
}
.chat-text {
Expand Down
108 changes: 108 additions & 0 deletions src/components/CommandInfo.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
---
/**
* Info row for chatbot command pages, driven by `aliases` / `access` /
* `module` / `cooldown` frontmatter. Rendered automatically via the PageTitle
* override. Mirrors VariableInfo / TopicInfo — plain label rows, no category
* colors (access tiers are state, not brand identity).
*/
interface Props {
aliases?: string[] | undefined;
access?:
| 'everyone'
| 'subscriber'
| 'regular'
| 'vip'
| 'moderator'
| 'super-moderator'
| 'broadcaster'
| undefined;
requires?: string | undefined;
cooldown?: string | undefined;
}

const ACCESS_LABELS = {
everyone: 'Everyone',
subscriber: 'Subscriber',
regular: 'Regular',
vip: 'VIP',
moderator: 'Moderator',
'super-moderator': 'Super Moderator',
broadcaster: 'Broadcaster',
} as const;

const { aliases, access, requires, cooldown } = Astro.props;
---

<dl class="command-info not-content">
{
access && (
<div>
<dt>Access</dt>
<dd>{ACCESS_LABELS[access]}</dd>
</div>
)
}
{
requires && (
<div>
<dt>Requires</dt>
<dd>{requires}</dd>
</div>
)
}
{
cooldown && (
<div>
<dt>Cooldown</dt>
<dd><code>{cooldown}</code></dd>
</div>
)
}
{
aliases && aliases.length > 0 && (
<div>
<dt>Aliases</dt>
<dd class="command-aliases">
{aliases.map((alias) => (
<code>{alias}</code>
))}
</dd>
</div>
)
}
</dl>

<style>
.command-info {
display: flex;
flex-wrap: wrap;
align-items: center;
gap: 0.5rem 1.5rem;
margin-top: 0.75rem;
padding: 0.6rem 0.9rem;
border: 1px solid var(--sl-color-gray-5);
border-radius: 0.5rem;
background-color: var(--sl-color-gray-6);
}
.command-info > div {
display: flex;
align-items: baseline;
gap: 0.5rem;
}
dt {
font-size: var(--sl-text-xs);
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.5px;
color: var(--sl-color-gray-3);
}
dd {
margin: 0;
font-size: var(--sl-text-sm);
}
.command-aliases {
display: flex;
flex-wrap: wrap;
gap: 0.3rem;
}
</style>
56 changes: 56 additions & 0 deletions src/components/CommandTable.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
---
/**
* Renders the default-command table for one category, generated from page
* frontmatter (see content.config.ts: category / access / summary). Used by
* the Default Commands overview so the table can't drift from the pages.
*/
import { getCollection } from 'astro:content';

interface Props {
category: string;
}

const { category } = Astro.props;

const ACCESS_LABELS: Record<string, string> = {
everyone: 'Everyone',
subscriber: 'Subscriber',
regular: 'Regular',
vip: 'VIP',
moderator: 'Moderator',
'super-moderator': 'Super Moderator',
broadcaster: 'Broadcaster',
};

const commands = (
await getCollection(
'docs',
(e) => e.id.startsWith('chatbot/commands/default/') && e.data.category === category,
)
).sort((a, b) => a.data.title.localeCompare(b.data.title, 'en'));
---

<table>
<thead>
<tr>
<th>Command</th>
<th>Access</th>
<th>Description</th>
</tr>
</thead>
<tbody>
{
commands.map((c) => (
<tr>
<td>
<a href={`/${c.id}`}>
<code>{c.data.title}</code>
</a>
</td>
<td>{c.data.access ? (ACCESS_LABELS[c.data.access] ?? c.data.access) : '—'}</td>
<td>{c.data.summary ?? c.data.description}</td>
</tr>
))
}
</tbody>
</table>
44 changes: 44 additions & 0 deletions src/components/VariableTable.astro
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
---
/**
* Renders the variable table for one category, generated from page frontmatter
* (see content.config.ts: category / summary). Used by the Variables overview
* so the table can't drift from the pages.
*/
import { getCollection } from 'astro:content';

interface Props {
category: string;
}

const { category } = Astro.props;

const variables = (
await getCollection(
'docs',
(e) => e.id.startsWith('chatbot/variables/') && e.data.category === category,
)
).sort((a, b) => a.data.title.localeCompare(b.data.title, 'en'));
---

<table>
<thead>
<tr>
<th>Variable</th>
<th>Description</th>
</tr>
</thead>
<tbody>
{
variables.map((v) => (
<tr>
<td>
<a href={`/${v.id}`}>
<code>{v.data.title}</code>
</a>
</td>
<td>{v.data.summary ?? v.data.description}</td>
</tr>
))
}
</tbody>
</table>
8 changes: 6 additions & 2 deletions src/components/overrides/PageTitle.astro
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,22 @@
/**
* PageTitle override: renders the default title, then frontmatter-driven
* extras — platform badges (`platforms`), websocket topic info (`topic`),
* and chatbot variable info (`syntax`).
* chatbot variable info (`syntax`), and chatbot command info (`access` etc.).
*/
import Default from '@astrojs/starlight/components/PageTitle.astro';
import PlatformBadges from '../PlatformBadges.astro';
import TopicInfo from '../TopicInfo.astro';
import VariableInfo from '../VariableInfo.astro';
import CommandInfo from '../CommandInfo.astro';

const { platforms, wsTopic, scope, status, syntax, arguments: args } =
const { platforms, wsTopic, scope, status, syntax, arguments: args, aliases, access, requires, cooldown } =
Astro.locals.starlightRoute.entry.data;
---

<Default><slot /></Default>
{platforms && platforms.length > 0 && <PlatformBadges platforms={platforms} />}
{wsTopic && <TopicInfo topic={wsTopic} scope={scope} status={status} />}
{syntax && <VariableInfo syntax={syntax} args={args} />}
{(access || requires || cooldown || (aliases && aliases.length > 0)) && (
<CommandInfo aliases={aliases} access={access} requires={requires} cooldown={cooldown} />
)}
Loading
Loading