This commit is contained in:
parent
51706d2d11
commit
853e99ca5f
21 changed files with 1402 additions and 77 deletions
|
|
@ -32,6 +32,35 @@ function formatTimestamp(value: string): string {
|
|||
}).format(date);
|
||||
}
|
||||
|
||||
function initialsFor(value: string): string {
|
||||
const parts = value
|
||||
.trim()
|
||||
.split(/\s+|[-_]/)
|
||||
.filter(Boolean);
|
||||
const initials = parts
|
||||
.slice(0, 2)
|
||||
.map((part) => part[0]?.toUpperCase() || "")
|
||||
.join("");
|
||||
return initials || "?";
|
||||
}
|
||||
|
||||
function plainTextExcerpt(markdown: string, limit = 150): string {
|
||||
const text = markdown
|
||||
.replace(/```[\s\S]*?```/g, " ")
|
||||
.replace(/!\[[^\]]*]\([^)]+\)/g, " ")
|
||||
.replace(/\[([^\]]+)]\([^)]+\)/g, "$1")
|
||||
.replace(/[#>*_`-]/g, " ")
|
||||
.replace(/\s+/g, " ")
|
||||
.trim();
|
||||
if (!text) {
|
||||
return "No description yet. Open the thread to see the discussion.";
|
||||
}
|
||||
if (text.length <= limit) {
|
||||
return text;
|
||||
}
|
||||
return `${text.slice(0, limit).trim()}...`;
|
||||
}
|
||||
|
||||
function normalizePathname(pathname: string): string {
|
||||
if (!pathname || pathname === "/") {
|
||||
return "/";
|
||||
|
|
@ -256,6 +285,19 @@ function EmptyState(props: { copy: string }) {
|
|||
return <p className="empty-state">{props.copy}</p>;
|
||||
}
|
||||
|
||||
function Avatar(props: { name: string; imageUrl?: string; className?: string }) {
|
||||
const className = props.className ? `avatar ${props.className}` : "avatar";
|
||||
if (props.imageUrl) {
|
||||
return <img className={className} src={props.imageUrl} alt="" loading="lazy" />;
|
||||
}
|
||||
|
||||
return (
|
||||
<span className={className} aria-hidden="true">
|
||||
{initialsFor(props.name)}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
function canUseInteractiveAuth(auth: AuthState): boolean {
|
||||
return auth.authenticated && auth.can_reply;
|
||||
}
|
||||
|
|
@ -433,11 +475,21 @@ function PostItem(props: { post: PostCard; onOpenPost: (post: PostCard) => void
|
|||
onOpenPost(post);
|
||||
}}
|
||||
>
|
||||
<h3>{post.title}</h3>
|
||||
<p className="muted-copy">{post.summary}</p>
|
||||
<p className="meta-line">
|
||||
{post.repo} · {post.file_path || post.path}
|
||||
</p>
|
||||
<span className="post-card-author-row">
|
||||
<Avatar name={post.owner} imageUrl={post.owner_avatar_url} className="avatar-small" />
|
||||
<span className="post-card-author">{post.owner}</span>
|
||||
</span>
|
||||
<span className="post-card-main">
|
||||
<h3>{post.title}</h3>
|
||||
<p className="post-card-excerpt">{post.summary || plainTextExcerpt(post.body, 170)}</p>
|
||||
<span className="topic-meta-row">
|
||||
<span>{post.repo}</span>
|
||||
<span>{formatTimestamp(post.updated_at)}</span>
|
||||
{post.assets.length > 0 ? (
|
||||
<span className="topic-badge">{post.assets.length} downloads</span>
|
||||
) : null}
|
||||
</span>
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
|
@ -454,25 +506,89 @@ function EventItem(props: { event: EventCard }) {
|
|||
);
|
||||
}
|
||||
|
||||
function discussionBadges(discussion: DiscussionCard): string[] {
|
||||
const badges = new Set<string>();
|
||||
if (discussion.links.length > 0) {
|
||||
badges.add("linked");
|
||||
}
|
||||
if (discussion.repo.toLowerCase().endsWith("/general_forum")) {
|
||||
badges.add("general");
|
||||
}
|
||||
for (const label of discussion.labels.slice(0, 3)) {
|
||||
badges.add(label);
|
||||
}
|
||||
return Array.from(badges).slice(0, 4);
|
||||
}
|
||||
|
||||
function DiscussionPreviewItem(props: {
|
||||
discussion: DiscussionCard;
|
||||
onOpenDiscussion: (discussion: DiscussionCard) => void;
|
||||
compact?: boolean;
|
||||
}) {
|
||||
const { discussion, onOpenDiscussion } = props;
|
||||
const { discussion, onOpenDiscussion, compact = false } = props;
|
||||
const badges = discussionBadges(discussion);
|
||||
const cardClass = compact ? "discussion-preview-card compact" : "discussion-preview-card";
|
||||
const postedAt = discussion.created_at || discussion.updated_at;
|
||||
|
||||
if (compact) {
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className={cardClass}
|
||||
onClick={() => {
|
||||
onOpenDiscussion(discussion);
|
||||
}}
|
||||
>
|
||||
<span className="discussion-compact-meta">
|
||||
<Avatar
|
||||
name={discussion.author}
|
||||
imageUrl={discussion.author_avatar_url}
|
||||
className="avatar-small"
|
||||
/>
|
||||
<span className="discussion-compact-author">{discussion.author}</span>
|
||||
<span>{formatTimestamp(postedAt)}</span>
|
||||
</span>
|
||||
<span className="discussion-compact-preview">
|
||||
<span className="discussion-compact-title">{discussion.title}</span>
|
||||
<span className="topic-excerpt">{plainTextExcerpt(discussion.body)}</span>
|
||||
</span>
|
||||
<span className="discussion-compact-replies">{discussion.replies} replies</span>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className="discussion-preview-card"
|
||||
className={cardClass}
|
||||
onClick={() => {
|
||||
onOpenDiscussion(discussion);
|
||||
}}
|
||||
>
|
||||
<h3>{discussion.title}</h3>
|
||||
<p className="meta-line">
|
||||
{discussion.repo} · {discussion.author} · {formatTimestamp(discussion.updated_at)} ·{" "}
|
||||
{discussion.replies} replies
|
||||
</p>
|
||||
<Avatar name={discussion.author} imageUrl={discussion.author_avatar_url} />
|
||||
<span className="topic-main">
|
||||
<span className="topic-title-row">
|
||||
<h3>{discussion.title}</h3>
|
||||
<span className="status-pill">{discussion.state}</span>
|
||||
</span>
|
||||
<span className="topic-excerpt">{plainTextExcerpt(discussion.body)}</span>
|
||||
<span className="topic-meta-row">
|
||||
<span>{discussion.repo}</span>
|
||||
<span>{discussion.author}</span>
|
||||
{badges.map((badge) => (
|
||||
<span key={badge} className="topic-badge">
|
||||
{badge}
|
||||
</span>
|
||||
))}
|
||||
</span>
|
||||
</span>
|
||||
<span className="topic-stats">
|
||||
<span>
|
||||
<strong>{discussion.replies}</strong>
|
||||
replies
|
||||
</span>
|
||||
<span>{formatTimestamp(discussion.updated_at)}</span>
|
||||
</span>
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
|
@ -482,9 +598,12 @@ function DiscussionReplyCard(props: { reply: DiscussionReply }) {
|
|||
|
||||
return (
|
||||
<article className="reply-card">
|
||||
<p className="reply-author">{reply.author}</p>
|
||||
<p className="meta-line">{formatTimestamp(reply.created_at)}</p>
|
||||
<MarkdownContent markdown={reply.body} className="thread-copy" />
|
||||
<div className="reply-meta-row">
|
||||
<Avatar name={reply.author} imageUrl={reply.avatar_url} className="avatar-small" />
|
||||
<span className="reply-author">{reply.author}</span>
|
||||
<span>{formatTimestamp(reply.created_at)}</span>
|
||||
</div>
|
||||
<MarkdownContent markdown={reply.body} className="thread-copy reply-copy" />
|
||||
</article>
|
||||
);
|
||||
}
|
||||
|
|
@ -998,17 +1117,22 @@ function PostPage(props: {
|
|||
|
||||
return (
|
||||
<section className="thread-view">
|
||||
<article className="panel">
|
||||
<header className="thread-header">
|
||||
<article className="panel post-hero-panel">
|
||||
<header className="post-hero">
|
||||
<div className="post-hero-meta">
|
||||
<Avatar name={post.owner} imageUrl={post.owner_avatar_url} className="avatar-small" />
|
||||
<div className="topic-meta-row">
|
||||
<span>{post.owner}</span>
|
||||
<span>{post.repo}</span>
|
||||
<span>{formatTimestamp(post.updated_at)}</span>
|
||||
</div>
|
||||
</div>
|
||||
<h1>{post.title}</h1>
|
||||
<p className="meta-line">
|
||||
{post.repo} · {formatTimestamp(post.updated_at)}
|
||||
</p>
|
||||
{post.summary ? <p className="post-hero-summary">{post.summary}</p> : null}
|
||||
</header>
|
||||
{post.summary ? <p className="muted-copy">{post.summary}</p> : null}
|
||||
</article>
|
||||
|
||||
<article className="panel">
|
||||
<article className="panel post-reading-panel">
|
||||
<header className="subsection-header">
|
||||
<h2>Post</h2>
|
||||
</header>
|
||||
|
|
@ -1064,6 +1188,8 @@ function DiscussionPage(props: {
|
|||
onReplyCreated: (discussionId: number, reply: DiscussionReply) => void;
|
||||
}) {
|
||||
const { discussion, auth, onGoHome, onGoSignIn, onReplyCreated } = props;
|
||||
const postedAt = discussion.created_at || discussion.updated_at;
|
||||
const replyCount = discussion.comments.length || discussion.replies;
|
||||
|
||||
return (
|
||||
<section className="thread-view">
|
||||
|
|
@ -1071,21 +1197,26 @@ function DiscussionPage(props: {
|
|||
Back to discussions
|
||||
</button>
|
||||
|
||||
<article className="panel">
|
||||
<header className="thread-header">
|
||||
<article className="panel discussion-detail-panel">
|
||||
<header className="thread-header discussion-detail-header">
|
||||
<div className="discussion-detail-meta">
|
||||
<Avatar
|
||||
name={discussion.author}
|
||||
imageUrl={discussion.author_avatar_url}
|
||||
className="avatar-small"
|
||||
/>
|
||||
<span className="discussion-compact-author">{discussion.author}</span>
|
||||
<span>{formatTimestamp(postedAt)}</span>
|
||||
</div>
|
||||
<h1>{discussion.title}</h1>
|
||||
<p className="meta-line">
|
||||
{discussion.repo} · Issue #{discussion.number} · {discussion.author} ·{" "}
|
||||
{formatTimestamp(discussion.updated_at)}
|
||||
</p>
|
||||
</header>
|
||||
<MarkdownContent markdown={discussion.body} className="thread-copy" />
|
||||
<p className="discussion-detail-replies">{replyCount} replies</p>
|
||||
</article>
|
||||
|
||||
<article className="panel">
|
||||
<header className="subsection-header">
|
||||
<h2>Replies</h2>
|
||||
<p className="meta-line">{discussion.comments.length}</p>
|
||||
</header>
|
||||
{discussion.comments.length > 0 ? (
|
||||
<div className="reply-list">
|
||||
|
|
@ -1138,8 +1269,16 @@ function DiscussionsView(props: {
|
|||
onGoSignIn: () => void;
|
||||
onDiscussionCreated: (discussion: DiscussionCard) => void;
|
||||
showComposer?: boolean;
|
||||
compactItems?: boolean;
|
||||
}) {
|
||||
const { data, onOpenDiscussion, onGoSignIn, onDiscussionCreated, showComposer = true } = props;
|
||||
const {
|
||||
data,
|
||||
onOpenDiscussion,
|
||||
onGoSignIn,
|
||||
onDiscussionCreated,
|
||||
showComposer = true,
|
||||
compactItems = false,
|
||||
} = props;
|
||||
const generalDiscussionConfigured =
|
||||
data.discussion_settings?.general_discussion_configured ?? false;
|
||||
|
||||
|
|
@ -1175,11 +1314,12 @@ function DiscussionsView(props: {
|
|||
key={discussion.id}
|
||||
discussion={discussion}
|
||||
onOpenDiscussion={onOpenDiscussion}
|
||||
compact={compactItems}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<EmptyState copy="No visible Forgejo issues were returned for this account." />
|
||||
<EmptyState copy="No Forgejo issues labeled `discussion` were returned for this account." />
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
|
|
@ -1242,6 +1382,7 @@ function HomeView(props: {
|
|||
onGoSignIn={onGoSignIn}
|
||||
onDiscussionCreated={onDiscussionCreated}
|
||||
showComposer={false}
|
||||
compactItems={true}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
|
@ -1708,6 +1849,7 @@ function AppContent(props: AppContentProps) {
|
|||
onOpenDiscussion={props.onOpenDiscussion}
|
||||
onGoSignIn={props.onGoSignIn}
|
||||
onDiscussionCreated={props.onDiscussionCreated}
|
||||
compactItems={true}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,10 +4,14 @@
|
|||
line-height: 1.5;
|
||||
font-weight: 400;
|
||||
|
||||
--bg: linear-gradient(180deg, #fffaf0 0%, #f3ede1 100%);
|
||||
--bg:
|
||||
radial-gradient(circle at 14% 8%, rgba(15, 111, 143, 0.12), transparent 28rem),
|
||||
radial-gradient(circle at 84% 0%, rgba(191, 125, 22, 0.12), transparent 24rem),
|
||||
linear-gradient(180deg, #fffaf0 0%, #f3ede1 100%);
|
||||
--panel: #fffdf8;
|
||||
--panel-hover: #f1eadf;
|
||||
--card: #ffffff;
|
||||
--card-elevated: #fffffc;
|
||||
--border: #ded5c7;
|
||||
--text: #1f2933;
|
||||
--muted: #667085;
|
||||
|
|
@ -24,6 +28,8 @@
|
|||
--disabled-bg: #ece6dc;
|
||||
--disabled-text: #8a8174;
|
||||
--error: #a64234;
|
||||
--shadow-soft: 0 1.25rem 3.5rem rgba(80, 65, 42, 0.11);
|
||||
--shadow-row: 0 0.65rem 1.6rem rgba(80, 65, 42, 0.08);
|
||||
}
|
||||
|
||||
* {
|
||||
|
|
@ -60,9 +66,9 @@ textarea {
|
|||
}
|
||||
|
||||
.app-shell {
|
||||
width: min(72rem, 100%);
|
||||
width: min(76rem, 100%);
|
||||
margin: 0 auto;
|
||||
padding: 2rem 1rem 3rem;
|
||||
padding: 2.25rem 1rem 3.5rem;
|
||||
}
|
||||
|
||||
.topbar {
|
||||
|
|
@ -76,6 +82,7 @@ textarea {
|
|||
border-bottom: 0.0625rem solid var(--accent-border);
|
||||
padding: 0.75rem clamp(1rem, 4vw, 2rem);
|
||||
background: var(--topbar-bg);
|
||||
box-shadow: 0 0.45rem 1.5rem rgba(31, 41, 51, 0.06);
|
||||
backdrop-filter: blur(1rem);
|
||||
}
|
||||
|
||||
|
|
@ -190,7 +197,8 @@ textarea {
|
|||
.page-message {
|
||||
border: 0.0625rem solid var(--border);
|
||||
background: var(--panel);
|
||||
border-radius: 0.9rem;
|
||||
border-radius: 1rem;
|
||||
box-shadow: var(--shadow-soft);
|
||||
}
|
||||
|
||||
.page-header,
|
||||
|
|
@ -201,6 +209,8 @@ textarea {
|
|||
|
||||
.page-header {
|
||||
margin-bottom: 1rem;
|
||||
border-color: var(--accent-border);
|
||||
background: linear-gradient(135deg, rgba(15, 111, 143, 0.09), transparent 42%), var(--panel);
|
||||
}
|
||||
|
||||
.page-header h1,
|
||||
|
|
@ -258,8 +268,8 @@ textarea {
|
|||
.activity-card,
|
||||
.reply-card {
|
||||
border: 0.0625rem solid var(--border);
|
||||
border-radius: 0.75rem;
|
||||
background: var(--card);
|
||||
border-radius: 0.9rem;
|
||||
background: var(--card-elevated);
|
||||
}
|
||||
|
||||
.card,
|
||||
|
|
@ -276,11 +286,44 @@ textarea {
|
|||
color: inherit;
|
||||
}
|
||||
|
||||
.post-card-button {
|
||||
display: grid;
|
||||
gap: 0.55rem;
|
||||
min-height: 13rem;
|
||||
align-content: start;
|
||||
border-color: rgba(15, 111, 143, 0.22);
|
||||
background:
|
||||
linear-gradient(145deg, rgba(15, 111, 143, 0.09), transparent 58%), var(--card-elevated);
|
||||
transition:
|
||||
transform 160ms ease,
|
||||
box-shadow 160ms ease,
|
||||
background 160ms ease;
|
||||
}
|
||||
|
||||
.post-card-author-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.55rem;
|
||||
}
|
||||
|
||||
.post-card-main {
|
||||
display: grid;
|
||||
min-width: 0;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.post-card-author {
|
||||
color: var(--muted);
|
||||
font-size: 0.82rem;
|
||||
}
|
||||
|
||||
.course-card-button:hover,
|
||||
.course-card-button:focus-visible,
|
||||
.post-card-button:hover,
|
||||
.post-card-button:focus-visible {
|
||||
background: var(--panel-hover);
|
||||
box-shadow: var(--shadow-row);
|
||||
transform: translateY(-0.0625rem);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
|
|
@ -306,10 +349,18 @@ textarea {
|
|||
|
||||
.discussion-preview-card {
|
||||
width: 100%;
|
||||
padding: 1rem;
|
||||
display: grid;
|
||||
grid-template-columns: auto minmax(0, 1fr) minmax(6.5rem, auto);
|
||||
gap: 0.9rem;
|
||||
align-items: center;
|
||||
padding: 0.95rem;
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
color: inherit;
|
||||
transition:
|
||||
transform 160ms ease,
|
||||
box-shadow 160ms ease,
|
||||
background 160ms ease;
|
||||
}
|
||||
|
||||
.activity-card-button {
|
||||
|
|
@ -324,9 +375,177 @@ textarea {
|
|||
.activity-card-button:hover,
|
||||
.activity-card-button:focus-visible {
|
||||
background: var(--panel-hover);
|
||||
box-shadow: var(--shadow-row);
|
||||
transform: translateY(-0.0625rem);
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.discussion-preview-card.compact {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 0.65rem;
|
||||
align-items: stretch;
|
||||
padding: 0.9rem 0.95rem;
|
||||
}
|
||||
|
||||
.avatar {
|
||||
display: inline-grid;
|
||||
width: 2.75rem;
|
||||
height: 2.75rem;
|
||||
place-items: center;
|
||||
flex: 0 0 auto;
|
||||
border: 0.125rem solid var(--panel);
|
||||
border-radius: 999rem;
|
||||
color: #ffffff;
|
||||
background: linear-gradient(135deg, rgba(255, 255, 255, 0.2), transparent), var(--accent);
|
||||
box-shadow: 0 0.35rem 0.8rem rgba(15, 111, 143, 0.18);
|
||||
font-size: 0.9rem;
|
||||
font-weight: 800;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.avatar-small {
|
||||
width: 2.2rem;
|
||||
height: 2.2rem;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.topic-main {
|
||||
display: grid;
|
||||
min-width: 0;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
.topic-title-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.55rem;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.topic-title-row h1,
|
||||
.topic-title-row h3 {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.topic-title-row h3 {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.status-pill,
|
||||
.topic-badge {
|
||||
display: inline-flex;
|
||||
width: max-content;
|
||||
align-items: center;
|
||||
border-radius: 999rem;
|
||||
white-space: nowrap;
|
||||
font-size: 0.72rem;
|
||||
font-weight: 750;
|
||||
letter-spacing: 0.02em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.status-pill {
|
||||
border: 0.0625rem solid rgba(15, 111, 143, 0.18);
|
||||
padding: 0.12rem 0.45rem;
|
||||
color: var(--accent-strong);
|
||||
background: rgba(15, 111, 143, 0.08);
|
||||
}
|
||||
|
||||
.topic-excerpt {
|
||||
display: -webkit-box;
|
||||
overflow: hidden;
|
||||
color: var(--muted);
|
||||
font-size: 0.93rem;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
}
|
||||
|
||||
.topic-meta-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
color: var(--muted);
|
||||
font-size: 0.82rem;
|
||||
}
|
||||
|
||||
.topic-meta-row-spaced {
|
||||
margin-top: 0.75rem;
|
||||
}
|
||||
|
||||
.topic-badge {
|
||||
border: 0.0625rem solid var(--border);
|
||||
padding: 0.15rem 0.42rem;
|
||||
color: #5f513f;
|
||||
background: #f7efe2;
|
||||
}
|
||||
|
||||
.post-card-excerpt {
|
||||
display: -webkit-box;
|
||||
overflow: hidden;
|
||||
color: var(--muted);
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 4;
|
||||
}
|
||||
|
||||
.topic-stats {
|
||||
display: grid;
|
||||
gap: 0.3rem;
|
||||
justify-items: end;
|
||||
color: var(--muted);
|
||||
font-size: 0.82rem;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.topic-stats strong {
|
||||
display: block;
|
||||
color: var(--text);
|
||||
font-size: 1.25rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.discussion-compact-meta {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
color: var(--muted);
|
||||
font-size: 0.82rem;
|
||||
}
|
||||
|
||||
.discussion-compact-author {
|
||||
overflow: hidden;
|
||||
color: var(--text);
|
||||
font-weight: 750;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.discussion-compact-preview {
|
||||
display: grid;
|
||||
min-width: 0;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.discussion-compact-title {
|
||||
overflow: hidden;
|
||||
color: var(--text);
|
||||
font-weight: 800;
|
||||
line-height: 1.25;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.discussion-preview-card.compact .topic-excerpt {
|
||||
-webkit-line-clamp: 2;
|
||||
}
|
||||
|
||||
.discussion-compact-replies {
|
||||
color: var(--muted);
|
||||
font-size: 0.78rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.muted-copy,
|
||||
.meta-line,
|
||||
.empty-state {
|
||||
|
|
@ -407,6 +626,94 @@ textarea {
|
|||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.discussion-detail-panel {
|
||||
display: grid;
|
||||
gap: 0.95rem;
|
||||
}
|
||||
|
||||
.discussion-detail-header {
|
||||
display: grid;
|
||||
gap: 0.7rem;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.discussion-detail-meta {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
align-items: center;
|
||||
gap: 0.55rem;
|
||||
color: var(--muted);
|
||||
font-size: 0.86rem;
|
||||
}
|
||||
|
||||
.discussion-detail-replies {
|
||||
width: max-content;
|
||||
color: var(--muted);
|
||||
font-size: 0.82rem;
|
||||
font-weight: 750;
|
||||
}
|
||||
|
||||
.forum-thread-header {
|
||||
display: grid;
|
||||
grid-template-columns: auto minmax(0, 1fr) auto;
|
||||
gap: 1rem;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.forum-thread-title {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.thread-count-card {
|
||||
display: grid;
|
||||
min-width: 5rem;
|
||||
justify-items: center;
|
||||
border: 0.0625rem solid var(--accent-border);
|
||||
border-radius: 0.85rem;
|
||||
padding: 0.7rem;
|
||||
color: var(--muted);
|
||||
background: var(--accent-soft);
|
||||
font-size: 0.8rem;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.thread-count-card strong {
|
||||
color: var(--text);
|
||||
font-size: 1.5rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.post-hero-panel {
|
||||
overflow: hidden;
|
||||
border-color: var(--accent-border);
|
||||
background:
|
||||
radial-gradient(circle at 95% 0%, rgba(15, 111, 143, 0.16), transparent 15rem),
|
||||
radial-gradient(circle at 0% 100%, rgba(191, 125, 22, 0.12), transparent 14rem), var(--panel);
|
||||
}
|
||||
|
||||
.post-hero {
|
||||
display: grid;
|
||||
gap: 0.9rem;
|
||||
}
|
||||
|
||||
.post-hero-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.65rem;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.post-hero-summary {
|
||||
max-width: none;
|
||||
color: var(--muted);
|
||||
font-size: 1.08rem;
|
||||
}
|
||||
|
||||
.post-reading-panel {
|
||||
width: 100%;
|
||||
padding: clamp(1.25rem, 3vw, 2rem);
|
||||
}
|
||||
|
||||
.panel-actions {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
|
@ -417,7 +724,26 @@ textarea {
|
|||
}
|
||||
|
||||
.reply-author {
|
||||
font-weight: 600;
|
||||
color: var(--text);
|
||||
font-weight: 750;
|
||||
}
|
||||
|
||||
.reply-card {
|
||||
display: grid;
|
||||
gap: 0.8rem;
|
||||
}
|
||||
|
||||
.reply-meta-row {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
align-items: center;
|
||||
gap: 0.55rem;
|
||||
color: var(--muted);
|
||||
font-size: 0.86rem;
|
||||
}
|
||||
|
||||
.reply-copy {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.outline-list,
|
||||
|
|
@ -744,6 +1070,34 @@ textarea {
|
|||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.discussion-preview-card {
|
||||
grid-template-columns: auto minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.discussion-preview-card.compact {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.topic-stats {
|
||||
grid-column: 2;
|
||||
justify-items: start;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.topic-title-row {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.forum-thread-header {
|
||||
grid-template-columns: auto minmax(0, 1fr);
|
||||
}
|
||||
|
||||
.thread-count-card {
|
||||
grid-column: 1 / -1;
|
||||
width: 100%;
|
||||
justify-items: start;
|
||||
}
|
||||
|
||||
.compose-actions,
|
||||
.auth-bar,
|
||||
.signin-callout,
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ export interface ContentAsset {
|
|||
export interface CourseCard {
|
||||
title: string;
|
||||
owner: string;
|
||||
owner_avatar_url: string;
|
||||
name: string;
|
||||
repo: string;
|
||||
html_url: string;
|
||||
|
|
@ -60,6 +61,7 @@ export interface CourseLesson {
|
|||
export interface PostCard {
|
||||
title: string;
|
||||
owner: string;
|
||||
owner_avatar_url: string;
|
||||
name: string;
|
||||
repo: string;
|
||||
slug: string;
|
||||
|
|
@ -92,6 +94,7 @@ export interface DiscussionCard {
|
|||
state: string;
|
||||
body: string;
|
||||
number: number;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
html_url: string;
|
||||
labels: string[];
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue