Rework blog page, add latest news + unread

This commit is contained in:
Ben Visness 2024-07-05 15:50:11 -05:00
parent c624f722f7
commit 716014c607
11 changed files with 134 additions and 110 deletions

View File

@ -7164,6 +7164,7 @@ code {
--color: #000; --color: #000;
--link-color: #d12991; --link-color: #d12991;
--timeline-media-background: #b4b4b466; --timeline-media-background: #b4b4b466;
--unread-color: #9498ff;
--border-color: var(--c4); --border-color: var(--c4);
--border-color-focused: #4e55ff; --border-color-focused: #4e55ff;
--border-color-error: #ff3a3a; --border-color-error: #ff3a3a;
@ -8147,7 +8148,7 @@ input[type=submit]:not(.no-padding),
display: inline-block; display: inline-block;
border-radius: 1000em; border-radius: 1000em;
padding: 0 0.8em; padding: 0 0.8em;
font-size: 0.9em; font-size: 0.6em;
line-height: 1.8em; line-height: 1.8em;
font-weight: bold; font-weight: bold;
} }
@ -8924,6 +8925,14 @@ code .ss,
max-height: calc(100vh - 2rem); max-height: calc(100vh - 2rem);
} }
} }
.timeline-unread {
--size: 0.5rem;
display: inline-block;
width: var(--size);
height: var(--size);
background-color: var(--unread-color);
border-radius: 999px;
}
/* src/rawdata/scss/style.css */ /* src/rawdata/scss/style.css */
/*! TACHYONS v4.12.0 | http://tachyons.io */ /*! TACHYONS v4.12.0 | http://tachyons.io */

View File

@ -41,7 +41,7 @@
display: inline-block; display: inline-block;
border-radius: 1000em; border-radius: 1000em;
padding: 0 0.8em; padding: 0 0.8em;
font-size: 0.9em; font-size: 0.6em;
line-height: 1.8em; line-height: 1.8em;
font-weight: bold; font-weight: bold;

View File

@ -69,4 +69,13 @@
max-height: calc(100vh - 2rem); max-height: calc(100vh - 2rem);
} }
} }
}
.timeline-unread {
--size: 0.5rem;
display: inline-block;
width: var(--size);
height: var(--size);
background-color: var(--unread-color);
border-radius: 999px;
} }

View File

@ -39,6 +39,8 @@ $breakpoint-large: screen and (min-width: 60em)
--timeline-media-background: #b4b4b466; --timeline-media-background: #b4b4b466;
--unread-color: #9498ff;
--border-color: var(--c4); --border-color: var(--c4);
--border-color-focused: #4e55ff; --border-color-focused: #4e55ff;
--border-color-error: #ff3a3a; --border-color-error: #ff3a3a;

View File

@ -1 +1 @@
<svg viewBox="0 0 14 14" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M7,5.444l5.444,-5.444l1.556,1.556l-5.444,5.444l5.444,5.444l-1.556,1.556l-5.444,-5.444l-5.444,5.444l-1.556,-1.556l5.444,-5.444l-5.444,-5.444l1.556,-1.556l5.444,5.444Z"/></svg> <svg width="14" height="14" viewBox="0 0 14 14" fill="currentColor" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M7,5.444l5.444,-5.444l1.556,1.556l-5.444,5.444l5.444,5.444l-1.556,1.556l-5.444,-5.444l-5.444,5.444l-1.556,-1.556l5.444,-5.444l-5.444,-5.444l1.556,-1.556l5.444,5.444Z"/></svg>

Before

Width:  |  Height:  |  Size: 257 B

After

Width:  |  Height:  |  Size: 300 B

View File

@ -1,25 +1,64 @@
{{ template "base-2024.html" . }} {{ template "base-2024.html" . }}
{{ define "content" }} {{ define "content" }}
<div class="flex justify-center pa3"> <div class="m-center mw-site pv3 pv5-ns ph3 ph0-l flex flex-column g2">
<div class="mw-site post-content flex flex-column g2"> <h1 class="tc-ns f3 f2-ns lh-title">{{ .Thread.Title }}</h1>
<div> {{ with .MainPost }}
<h1>{{ .Thread.Title }}</h1> <div class="flex justify-center-ns items-center g2">
{{ with .MainPost }} <img class="avatar avatar-user avatar-small" src="{{ .Author.AvatarUrl }}">
<div class="flex justify-between items-center mt2 mb3"> <span class="f6 f5-ns">
<div class="flex items-center"> <a class="b link-normal" href="{{ .Author.ProfileUrl }}">{{ .Author.Name }}</a>
<div class="avatar contain bg-center" style="background-image:url('{{ .Author.AvatarUrl }}');"></div> — {{ timehtml (absoluteshortdate .PostDate) .PostDate }}
<div class="flex flex-column ml2"> {{ if and $.User (or (eq .Author.ID $.User.ID) $.User.IsStaff) }}
<div> (<a href="{{ .EditUrl }}" title="Edit">Edit</a>, <a href="{{ .DeleteUrl }}" title="Delete">Delete</a>)
<a class="username" href="{{ .Author.ProfileUrl }}" target="_blank">{{ .Author.Name }}</a> {{ end }}
</div> </span>
<div class="c--dim f7">{{ timehtml (absoluteshortdate .PostDate) .PostDate }}</div> </div>
{{ end }}
</div>
<div class="m-center mw-site-narrow ph3 ph0-l flex flex-column g2">
<div class="post-content overflow-x-auto {{ if .IsProjectPage }}mb3{{ end }}">
{{ .MainPost.Content }}
</div>
{{ if not .IsProjectPage }}
{{ template "newsletter_signup.html" . }}
{{ end }}
</div>
<hr class="m-center mw-site">
<div class="pt4 m-center mw-site-narrow ph3 ph0-l flex flex-column g2">
<div class="flex justify-between">
<h3 class="f4">Comments</h3>
{{ if .Project.HasBlog }}
<a class="flex items-center g2" href="{{ not (not $.User) | ternary .ReplyLink .LoginLink }}">{{ svg "add" }}<span>Leave a Comment</span></a>
{{ end }}
</div>
<div class="flex flex-column g2">
{{ range .Comments }}
<div class="bg3 pa3 flex flex-column g3">
<div class="flex link-normal">
<img class="avatar avatar-user mr2" src="{{ .Author.AvatarUrl }}">
<div class="flex flex-column g1">
<div class="flex items-center g2">
<a class="b" href="{{ .Author.ProfileUrl }}">{{ .Author.Name }}</a>
{{ if .Author.IsStaff }}
<div class="badge staff"></div>
{{ end }}
</div>
<div class="f6">
{{ timehtml (absoluteshortdate .PostDate) .PostDate }}
{{- if .Editor -}}
.
Edited by <a class="name" href="{{ .Editor.ProfileUrl }}">{{ coalesce .Editor.Name .Editor.Username }}</a>
on {{ timehtml (absolutedate .EditDate) .EditDate }}
{{ with .EditReason }}
Reason: {{ . }}
{{ end }}
</span>
{{ end }}
</div> </div>
</div>
<div class="di ph1">
{{ if .Author.IsStaff }}
<div class="badge staff"></div>
{{ end }}
</div> </div>
<div class="flex-grow-1"></div> <div class="flex-grow-1"></div>
<div> <div>
@ -35,97 +74,29 @@
{{ end }} {{ end }}
<a class="reply action button" href="{{ .ReplyUrl }}" title="Reply">&hookrightarrow;</a>&nbsp; <a class="reply action button" href="{{ .ReplyUrl }}" title="Reply">&hookrightarrow;</a>&nbsp;
{{ end }} {{ end }}
<span class="postid">
<a name="{{ .ID }}" href="{{ .Url }}">#{{ .ID }}</a>
</span>
</div> </div>
{{ end }} {{ end }}
</div> </div>
</div> </div>
{{ end }}
<!-- Main post -->
<div class="{{ if .IsProjectPage }}mb3{{ end }}">
<div class="post-content overflow-x-auto"> <div class="post-content overflow-x-auto">
{{ .MainPost.Content }} {{ .Content }}
</div> </div>
</div> </div>
</div> {{ else }}
<div class="bg3 tc pv4 f7">
{{ if not .IsProjectPage }} No comments yet.
{{ template "newsletter_signup.html" . }} </div>
{{ end }} {{ end }}
<div class="bb ba1"></div>
{{ range .Comments }}
<div class="pa2 flex items-start background-even">
<div>
<div class="avatar contain bg-center" style="background-image:url('{{ .Author.AvatarUrl }}');"></div>
</div>
<div class="pl3 flex flex-column w-100">
<div class="flex justify-between">
<div>
<div>
<a class="username" href="{{ .Author.ProfileUrl }}" target="_blank">{{ .Author.Name }}</a>
</div>
<div class="c--dim f7">
{{ timehtml (relativedate .PostDate) .PostDate }}
{{ if .Editor }}
<span class="pl3">
Edited by
<a class="name" href="{{ .Editor.ProfileUrl }}" target="_blank">{{ coalesce .Editor.Name .Editor.Username }}</a>
on {{ timehtml (absolutedate .EditDate) .EditDate }}
{{ with .EditReason }}
Reason: {{ . }}
{{ end }}
</span>
{{ end }}
</div>
</div>
<div class="di ph1">
{{ if .Author.IsStaff }}
<div class="badge staff"></div>
{{ end }}
</div>
<div class="flex-grow-1"></div>
<div>
{{ if and $.User $.Project.HasBlog }}
<div class="flex">
{{ if or (eq .Author.ID $.User.ID) $.User.IsStaff }}
<a class="delete action button" href="{{ .DeleteUrl }}" title="Delete">&#10006;</a>&nbsp;
<a class="edit action button" href="{{ .EditUrl }}" title="Edit">&#9998;</a>&nbsp;
{{ end }}
{{ if or (not $.Thread.Locked) $.User.IsStaff }}
{{ if $.Thread.Locked }}
WARNING: locked thread - use power responsibly!
{{ end }}
<a class="reply action button" href="{{ .ReplyUrl }}" title="Reply">&hookrightarrow;</a>&nbsp;
{{ end }}
<span class="postid">
<a name="{{ .ID }}" href="{{ .Url }}">#{{ .ID }}</a>
</span>
</div>
{{ end }}
</div>
</div>
<div class="w-100 pt3">
<div class="post-content overflow-x-auto">
{{ .Content }}
</div>
</div>
</div>
</div>
{{ end }}
<div class="bb ba1"></div>
<div>
{{ if .Project.HasBlog }}
{{ if $.User }}
<a href="{{ .ReplyLink }}"><span class="big pr1">+</span> Add Comment</a>
{{ else }}
<a href="{{ .LoginLink }}">Log in to comment</a>
{{ end }}
{{ end }}
</div>
</div> </div>
{{ if .Project.HasBlog }}
{{ if gt (len .Comments) 0 }}
<div class="flex justify-end">
<a class="flex items-center g2" href="{{ not (not $.User) | ternary .ReplyLink .LoginLink }}">{{ svg "add" }}<span>Leave a Comment</span></a>
</div>
{{ end }}
{{ end }}
</div> </div>
{{ end }} {{ end }}

View File

@ -8,17 +8,17 @@
</a> </a>
{{ end }} {{ end }}
{{ if .ForumLayout }} {{ if .ForumLayout }}
<div class="overflow-hidden flex-grow-1 flex flex-column g1 justify-center"> <div class="overflow-hidden flex-grow-1 flex flex-column g1 justify-center link-normal">
{{ with .Breadcrumbs }} {{ with .Breadcrumbs }}
{{ template "breadcrumbs.html" . }} {{ template "breadcrumbs.html" . }}
{{ end }} {{ end }}
{{ if .Title }} {{ if .Title }}
<div class="f5 lh-title {{ if not .AllowTitleWrap }}nowrap truncate{{ end }}"> <div class="f5 lh-title {{ if not .AllowTitleWrap }}nowrap truncate{{ end }}">
{{ with .TypeTitle }}<b class="dn di-ns">{{ . }}:</b>{{ end }} {{ with .TypeTitle }}<b class="dn di-ns">{{ . }}:</b>{{ end }}
<a href="{{ .Url }}">{{ .Title }}</a> <a class="{{ if not .TypeTitle }}b{{ end }}" href="{{ .Url }}">{{ .Title }}</a>
</div> </div>
{{ end }} {{ end }}
<div class="details link-normal"> <div class="details">
<a class="user" href="{{ .OwnerUrl }}">{{ .OwnerName }}</a> <a class="user" href="{{ .OwnerUrl }}">{{ .OwnerName }}</a>
&mdash; {{ timehtml (absoluteshortdate .Date) .Date }} &mdash; {{ timehtml (absoluteshortdate .Date) .Date }}
</div> </div>
@ -80,10 +80,10 @@
{{ end }} {{ end }}
{{ if .Description }} {{ if .Description }}
<div class="mt3 overflow-hidden relative {{ if .TruncateDescription }}maxh-5{{ end }}"> <div class="mt3 overflow-hidden relative {{ if .TruncateDescription }}maxh-4{{ end }}">
<div class="post-content">{{ trim .Description }}</div> <div class="post-content">{{ trim .Description }}</div>
{{ if .TruncateDescription }} {{ if .TruncateDescription }}
<div class="excerpt-fade absolute w-100 h4 bottom-0 z-999"></div> <div class="excerpt-fade absolute w-100 h4 bottom-0 z-1"></div>
{{ end }} {{ end }}
</div> </div>
{{ if .TruncateDescription }} {{ if .TruncateDescription }}

View File

@ -141,6 +141,14 @@
<!-- Feed --> <!-- Feed -->
<div class="flex flex-column flex-grow-1 overflow-hidden"> <div class="flex flex-column flex-grow-1 overflow-hidden">
{{ $latestNews := (index .NewsItems 0) }}
<div id="latest_news" class="mb2 bg3 link-normal" data-id="{{ $latestNews.ID }}" {{ if $latestNews.Unread }}data-unread{{ end }}>
<div class="flex justify-between">
<h2 class="pt3 ph3 f4">Latest News</h2>
<a class="pt3 ph3" href="#" onclick="closeLatestNews(event)">{{ svg "close" }}</a>
</div>
{{ template "timeline_item.html" $latestNews }}
</div>
<div id="landing-tabs"> <div id="landing-tabs">
<div class="bb mb2 flex f6"> <div class="bb mb2 flex f6">
{{ if .User }} {{ if .User }}
@ -148,7 +156,7 @@
{{ end }} {{ end }}
<div data-tab-button="featured" class="tab-button ph3 pv1 pointer">Featured</div> <div data-tab-button="featured" class="tab-button ph3 pv1 pointer">Featured</div>
<div data-tab-button="recent" class="tab-button ph3 pv1 pointer">Recent</div> <div data-tab-button="recent" class="tab-button ph3 pv1 pointer">Recent</div>
<div data-tab-button="news" class="tab-button ph3 pv1 pointer">News</div> <div data-tab-button="news" class="tab-button ph3 pv1 pointer">News {{ if $latestNews.Unread }}<span class="timeline-unread"></span>{{ end }}</div>
</div> </div>
<div> <div>
{{ if .User }} {{ if .User }}
@ -206,6 +214,27 @@
selectTab(tab, { sendEvent: false }); selectTab(tab, { sendEvent: false });
} }
}); });
// Latest news
const latestNews = document.querySelector("#latest_news");
const latestNewsPostID = latestNews.getAttribute("data-id");
const latestNewsClosedKey = "latest_news_closed";
function closeLatestNews(e) {
e.preventDefault();
localStorage.setItem(latestNewsClosedKey, latestNewsPostID);
hideLatestNewsIfClosedOrRead();
}
function hideLatestNewsIfClosedOrRead() {
const isUnread = latestNews.hasAttribute("data-unread");
const closedID = localStorage.getItem(latestNewsClosedKey);
if (!isUnread || closedID === latestNewsPostID) {
latestNews.hidden = true;
}
}
hideLatestNewsIfClosedOrRead();
</script> </script>
{{ end }} {{ end }}

View File

@ -354,6 +354,8 @@ type TimelineItem struct {
Media []TimelineItemMedia Media []TimelineItemMedia
Unread bool
ForumLayout bool ForumLayout bool
AllowTitleWrap bool AllowTitleWrap bool
TruncateDescription bool TruncateDescription bool

View File

@ -99,6 +99,7 @@ func Index(c *RequestContext) ResponseData {
item.Description = template.HTML(t.FirstPostCurrentVersion.TextParsed) item.Description = template.HTML(t.FirstPostCurrentVersion.TextParsed)
item.AllowTitleWrap = true item.AllowTitleWrap = true
item.TruncateDescription = true item.TruncateDescription = true
item.Unread = t.Unread
newsItems = append(newsItems, item) newsItems = append(newsItems, item)
} }

View File

@ -167,6 +167,7 @@ func PostToTimelineItem(
ownerTmpl := templates.UserToTemplate(owner) ownerTmpl := templates.UserToTemplate(owner)
item := templates.TimelineItem{ item := templates.TimelineItem{
ID: strconv.Itoa(post.ID),
Date: post.PostDate, Date: post.PostDate,
Title: thread.Title, Title: thread.Title,
Breadcrumbs: GenericThreadBreadcrumbs(urlContext, lineageBuilder, thread), Breadcrumbs: GenericThreadBreadcrumbs(urlContext, lineageBuilder, thread),