Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

User blog pages and RSS/Atom feeds #24

Merged
merged 7 commits into from
Jan 22, 2021
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
1 change: 1 addition & 0 deletions src/Hosted/Hosted.fsproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
<Content Include="404.html" />
<Content Include="assets\custom.css" />
<Content Include="assets\skin.css" />
<Content Include="userbloglist.html" />
<Content Include="contact.html" />
<Content Include="legal.html" />
<Content Include="trainings.html" />
Expand Down
142 changes: 97 additions & 45 deletions src/Hosted/Main.fs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ type EndPoint =
| [<EndPoint "GET /categories">] Categories
| [<EndPoint "GET /feed.atom">] AtomFeed
| [<EndPoint "GET /feed.rss">] RSSFeed
| [<EndPoint "GET /atom">] AtomFeedForUser of string
| [<EndPoint "GET /rss">] RSSFeedForUser of string
| [<EndPoint "GET /refresh">] Refresh
| [<EndPoint "GET /contact">] Contact
| [<EndPoint "GET /terms-of-use">] TermsOfUse
Expand Down Expand Up @@ -158,6 +160,11 @@ module Urls =
else
sprintf "/user/%s" user
let LANG (lang: string) = sprintf "/%s" lang
let RSS_URL user =
if String.IsNullOrEmpty user then
sprintf "/rss"
else
sprintf "/rss/%s.rss" user

module Helpers =
open System.IO
Expand Down Expand Up @@ -356,6 +363,7 @@ module Site =
type RedirectTemplate = Templating.Template<"../Hosted/redirect.html", serverLoad=Templating.ServerLoad.WhenChanged>
type TrainingsTemplate = Templating.Template<"../Hosted/trainings.html", serverLoad=Templating.ServerLoad.WhenChanged>
type BlogListTemplate = Templating.Template<"../Hosted/bloglist.html", serverLoad=Templating.ServerLoad.WhenChanged>
type UserBlogListTemplate = Templating.Template<"../Hosted/userbloglist.html", serverLoad=Templating.ServerLoad.WhenChanged>
type BlogPostTemplate = Templating.Template<"../Hosted/blogpost.html", serverLoad=Templating.ServerLoad.WhenChanged>
type ContactTemplate = Templating.Template<"../Hosted/contact.html", serverLoad=Templating.ServerLoad.WhenChanged>
type LegalTemplate = Templating.Template<"../Hosted/legal.html", serverLoad=Templating.ServerLoad.WhenChanged>
Expand Down Expand Up @@ -1006,11 +1014,78 @@ module Site =
.Cookie(Cookies.Banner false)
.Doc()
|> Content.Page
let USERBLOG_LISTING_NO_PAGING user f =
let templateFile = Path.Combine (__SOURCE_DIRECTORY__, sprintf @"../Hosted/userblog-%s.html" user)
if File.Exists templateFile then
UserBlogListTemplate(templateFile)
else
UserBlogListTemplate()
|> fun template ->
let name =
if String.IsNullOrEmpty(user) then
config.Value.MasterUserDisplayName
else
config.Value.Users.[user]
template
.Menubar(menubar config.Value)
.AuthorName(name)
.AuthorRSSUrl(Urls.RSS_URL user)
.ArticleList(Map.filter f articles.Value |> ARTICLES)
.Pagination(Doc.Empty)
.Footer(MainTemplate.Footer().Doc())
.Cookie(Cookies.Banner false)
.Doc()
|> Content.Page
let REDIRECT_TO (url: string) =
RedirectTemplate()
.Url(url)
.Doc()
|> Content.Page
let ARTICLES_BY_USEROPT (userOpt: string option) =
articles.Value |> Map.toList
// Filter by user, if given
|> List.filter (fun ((user, _), _) -> if userOpt.IsSome then user = userOpt.Value else true)
|> List.sortByDescending (fun (_, article: Article) -> article.Date.Ticks)
let ATOM_FEED userOpt =
let ns = XNamespace.Get "http://www.w3.org/2005/Atom"
let articles = ARTICLES_BY_USEROPT userOpt
X (ns + "feed") [] [
X (ns + "title") [] [TEXT config.Value.Title]
X (ns + "subtitle") [] [TEXT config.Value.Description]
X (ns + "link") ["href" => config.Value.ServerUrl] []
X (ns + "updated") [] [Helpers.ATOM_DATE DateTime.UtcNow]
for ((user, slug), article) in articles do
X (ns + "entry") [] [
X (ns + "title") [] [TEXT article.Title]
X (ns + "link") ["href" => config.Value.ServerUrl + Urls.POST_URL (user, slug)] []
X (ns + "id") [] [TEXT (user+slug)]
for category in article.Categories do
X (ns + "category") [] [TEXT category]
X (ns + "summary") [] [TEXT article.Abstract]
X (ns + "updated") [] [TEXT <| Helpers.ATOM_DATE article.Date]
]
]
let RSS_FEED userOpt =
let articles = ARTICLES_BY_USEROPT userOpt
X (N "rss") ["version" => "2.0"] [
X (N "channel") [] [
X (N "title") [] [TEXT config.Value.Title]
X (N "description") [] [TEXT config.Value.Description]
X (N "link") [] [TEXT config.Value.ServerUrl]
X (N "lastBuildDate") [] [Helpers.RSS_DATE DateTime.UtcNow]
for ((user, slug), article) in articles do
X (N "item") [] [
X (N "title") [] [TEXT article.Title]
X (N "link") [] [TEXT <| config.Value.ServerUrl + Urls.POST_URL (user, slug)]
X (N "guid") ["isPermaLink" => "false"] [TEXT (user+slug)]
for category in article.Categories do
X (N "category") [] [TEXT category]
X (N "description") [] [TEXT article.Abstract]
X (N "pubDate") [] [TEXT <| Helpers.RSS_DATE article.Date]
]
]
]

Application.MultiPage (fun (ctx: Context<_>) -> function
| Trainings ->
TRAININGS ()
Expand Down Expand Up @@ -1050,10 +1125,7 @@ module Site =
ARTICLE ("", p)
// All articles by a given user
| UserArticle (user, "") ->
BLOG_LISTING_NO_PAGING
<| BlogListTemplate.BlogCategoryBanner()
.Category(user)
.Doc()
USERBLOG_LISTING_NO_PAGING user
<| fun (u, _) _ -> user = u
| UserArticle (user, p) ->
ARTICLE (user, p)
Expand Down Expand Up @@ -1083,54 +1155,31 @@ module Site =
Status = Http.Status.Ok,
Headers = [Http.Header.Custom "content-type" "application/atom+xml"],
WriteBody = fun stream ->
let ns = XNamespace.Get "http://www.w3.org/2005/Atom"
let articles =
articles.Value |> Map.toList |> List.sortByDescending (fun (_, article: Article) -> article.Date.Ticks)
let doc =
X (ns + "feed") [] [
X (ns + "title") [] [TEXT config.Value.Title]
X (ns + "subtitle") [] [TEXT config.Value.Description]
X (ns + "link") ["href" => config.Value.ServerUrl] []
X (ns + "updated") [] [Helpers.ATOM_DATE DateTime.UtcNow]
for ((user, slug), article) in articles do
X (ns + "entry") [] [
X (ns + "title") [] [TEXT article.Title]
X (ns + "link") ["href" => config.Value.ServerUrl + Urls.POST_URL (user, slug)] []
X (ns + "id") [] [TEXT (user+slug)]
for category in article.Categories do
X (ns + "category") [] [TEXT category]
X (ns + "summary") [] [TEXT article.Abstract]
X (ns + "updated") [] [TEXT <| Helpers.ATOM_DATE article.Date]
]
]
let doc = ATOM_FEED None
doc.Save(stream)
)
| AtomFeedForUser user ->
Content.Custom (
Status = Http.Status.Ok,
Headers = [Http.Header.Custom "content-type" "application/atom+xml"],
WriteBody = fun stream ->
let doc = ATOM_FEED (Some user)
doc.Save(stream)
)
| RSSFeed ->
Content.Custom (
Status = Http.Status.Ok,
Headers = [Http.Header.Custom "content-type" "application/rss+xml"],
WriteBody = fun stream ->
let articles =
articles.Value |> Map.toList |> List.sortByDescending (fun (_, article: Article) -> article.Date.Ticks)
let doc =
X (N "rss") ["version" => "2.0"] [
X (N "channel") [] [
X (N "title") [] [TEXT config.Value.Title]
X (N "description") [] [TEXT config.Value.Description]
X (N "link") [] [TEXT config.Value.ServerUrl]
X (N "lastBuildDate") [] [Helpers.RSS_DATE DateTime.UtcNow]
for ((user, slug), article) in articles do
X (N "item") [] [
X (N "title") [] [TEXT article.Title]
X (N "link") [] [TEXT <| config.Value.ServerUrl + Urls.POST_URL (user, slug)]
X (N "guid") ["isPermaLink" => "false"] [TEXT (user+slug)]
for category in article.Categories do
X (N "category") [] [TEXT category]
X (N "description") [] [TEXT article.Abstract]
X (N "pubDate") [] [TEXT <| Helpers.RSS_DATE article.Date]
]
]
]
let doc = RSS_FEED None
doc.Save(stream)
)
| RSSFeedForUser user ->
Content.Custom (
Status = Http.Status.Ok,
Headers = [Http.Header.Custom "content-type" "application/rss+xml"],
WriteBody = fun stream ->
let doc = RSS_FEED (Some user)
doc.Save(stream)
)
| Refresh ->
Expand Down Expand Up @@ -1221,6 +1270,9 @@ type Website() =
// Generate the RSS/Atom feeds
RSSFeed
AtomFeed
for user in users do
RSSFeedForUser user
AtomFeedForUser user
// Generate 404 page
Error404
// Generate legal pages
Expand Down
35 changes: 35 additions & 0 deletions src/Hosted/assets/custom.css
Original file line number Diff line number Diff line change
Expand Up @@ -370,3 +370,38 @@ pre:not(.hljs) {
border-color: #464646;
color: #FFF;
}

.form-ajax .success-box, .form-ajax .error-box {
display: none;
margin-top: 20px;
}
.user-information {
text-align: center;
margin-top: 60px;
}

.user-name {
font-size: 400%;
margin-bottom: 20px;
line-height: 100%;
color: black;
}

.user-social i {
color: rgb(102, 102, 102);
}

.user-top a:not(:first-child) {
margin-left: 10px;
}

.user-profile-picture {
max-width: 200px;
margin-bottom: 20px;
margin-left: auto;
margin-right: auto;
}

.user-profile-picture img {
border-radius: 50%;
}
4 changes: 4 additions & 0 deletions src/Hosted/themekit/media/icons/fontawesome/icons.css
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,8 @@

.fa-spinner:before {
content: "\f110";
}

.fa-rss:before {
content: "\f09e";
}
Loading