Add / update / delete custom feed complete

This commit is contained in:
Daniel J. Summers 2022-05-29 22:44:51 -04:00
parent 7757bd8394
commit f99623d1cb
4 changed files with 147 additions and 71 deletions

View File

@ -3,6 +3,15 @@
open System open System
open MyWebLog open MyWebLog
/// Helper functions for view models
[<AutoOpen>]
module private Helpers =
/// Create a string option if a string is blank
let noneIfBlank (it : string) =
match it.Trim () with "" -> None | trimmed -> Some trimmed
/// Details about a category, used to display category lists /// Details about a category, used to display category lists
[<NoComparison; NoEquality>] [<NoComparison; NoEquality>]
type DisplayCategory = type DisplayCategory =
@ -259,6 +268,7 @@ type EditCustomFeedModel =
summary = p.summary summary = p.summary
displayedAuthor = p.displayedAuthor displayedAuthor = p.displayedAuthor
email = p.email email = p.email
imageUrl = Permalink.toString p.imageUrl
itunesCategory = p.iTunesCategory itunesCategory = p.iTunesCategory
itunesSubcategory = defaultArg p.iTunesSubcategory "" itunesSubcategory = defaultArg p.iTunesSubcategory ""
explicit = ExplicitRating.toString p.explicit explicit = ExplicitRating.toString p.explicit
@ -266,7 +276,31 @@ type EditCustomFeedModel =
mediaBaseUrl = defaultArg p.mediaBaseUrl "" mediaBaseUrl = defaultArg p.mediaBaseUrl ""
} }
| None -> rss | None -> rss
/// Update a feed with values from this model
member this.updateFeed (feed : CustomFeed) =
{ feed with
source = if this.sourceType = "tag" then Tag this.sourceValue else Category (CategoryId this.sourceValue)
path = Permalink this.path
podcast =
if this.isPodcast then
Some {
title = this.title
subtitle = noneIfBlank this.subtitle
itemsInFeed = this.itemsInFeed
summary = this.summary
displayedAuthor = this.displayedAuthor
email = this.email
imageUrl = Permalink this.imageUrl
iTunesCategory = this.itunesCategory
iTunesSubcategory = noneIfBlank this.itunesSubcategory
explicit = ExplicitRating.parse this.explicit
defaultMediaType = noneIfBlank this.defaultMediaType
mediaBaseUrl = noneIfBlank this.mediaBaseUrl
}
else
None
}
/// View model to edit a page /// View model to edit a page
[<CLIMutable; NoComparison; NoEquality>] [<CLIMutable; NoComparison; NoEquality>]
@ -427,7 +461,7 @@ type EditRssModel =
itemsInFeed = if this.itemsInFeed = 0 then None else Some this.itemsInFeed itemsInFeed = if this.itemsInFeed = 0 then None else Some this.itemsInFeed
categoryEnabled = this.categoryEnabled categoryEnabled = this.categoryEnabled
tagEnabled = this.tagEnabled tagEnabled = this.tagEnabled
copyright = if this.copyright.Trim () = "" then None else Some (this.copyright.Trim ()) copyright = noneIfBlank this.copyright
} }

View File

@ -316,7 +316,7 @@ let saveSettings : HttpHandler = fun next ctx -> task {
let editCustomFeed feedId : HttpHandler = fun next ctx -> task { let editCustomFeed feedId : HttpHandler = fun next ctx -> task {
let customFeed = let customFeed =
match feedId with match feedId with
| "new" -> Some CustomFeed.empty | "new" -> Some { CustomFeed.empty with id = CustomFeedId "new" }
| _ -> ctx.WebLog.rss.customFeeds |> List.tryFind (fun f -> f.id = CustomFeedId feedId) | _ -> ctx.WebLog.rss.customFeeds |> List.tryFind (fun f -> f.id = CustomFeedId feedId)
match customFeed with match customFeed with
| Some f -> | Some f ->
@ -332,8 +332,28 @@ let editCustomFeed feedId : HttpHandler = fun next ctx -> task {
// POST: /admin/rss/save // POST: /admin/rss/save
let saveCustomFeed : HttpHandler = fun next ctx -> task { let saveCustomFeed : HttpHandler = fun next ctx -> task {
// TODO: stub let conn = ctx.Conn
return! Error.notFound next ctx match! Data.WebLog.findById ctx.WebLog.id conn with
| Some webLog ->
let! model = ctx.BindFormAsync<EditCustomFeedModel> ()
let theFeed =
match model.id with
| "new" -> Some { CustomFeed.empty with id = CustomFeedId.create () }
| _ -> webLog.rss.customFeeds |> List.tryFind (fun it -> CustomFeedId.toString it.id = model.id)
match theFeed with
| Some feed ->
let feeds = model.updateFeed feed :: (webLog.rss.customFeeds |> List.filter (fun it -> it.id <> feed.id))
let webLog = { webLog with rss = { webLog.rss with customFeeds = feeds } }
do! Data.WebLog.updateRssOptions webLog conn
WebLogCache.set webLog
do! addMessage ctx {
UserMessage.success with
message = $"""Successfully {if model.id = "new" then "add" else "sav"}ed custom feed"""
}
let nextUrl = $"admin/settings/rss/{CustomFeedId.toString feed.id}/edit"
return! redirectToGet (WebLog.relativeUrl webLog (Permalink nextUrl)) next ctx
| None -> return! Error.notFound next ctx
| None -> return! Error.notFound next ctx
} }
// POST /admin/rss/{id}/delete // POST /admin/rss/{id}/delete

View File

@ -2,28 +2,59 @@
<article> <article>
<form action="{{ "admin/settings/rss/save" | relative_link }}" method="post"> <form action="{{ "admin/settings/rss/save" | relative_link }}" method="post">
<input type="hidden" name="{{ csrf.form_field_name }}" value="{{ csrf.request_token }}"> <input type="hidden" name="{{ csrf.form_field_name }}" value="{{ csrf.request_token }}">
{%- assign is_cat = model.source_type == "category" -%} <input type="hidden" name="id" value="{{ model.id }}">
{%- assign typ = model.source_type -%}
<div class="container"> <div class="container">
<div class="row pb-3"> <div class="row pb-3">
<div class="col-12 col-xl-10 offset-xl-1"> <div class="col">
<a href="{{ "admin/settings/rss" | relative_link }}">&laquo; Back to RSS Settings</a>
</div>
</div>
<div class="row pb-3">
<div class="col-12 col-lg-6">
<fieldset class="container pb-0">
<legend>Identification</legend>
<div class="row">
<div class="col">
<div class="form-floating">
<input type="text" name="path" id="path" class="form-control" placeholder="Relative Feed Path"
value="{{ model.path }}">
<label for="path">Relative Feed Path</label>
<span class="form-text fst-italic">Appended to {{ web_log.url_base }}/</span>
</div>
</div>
</div>
<div class="row">
<div class="col py-3 d-flex align-self-center justify-content-center">
<div class="form-check form-switch">
<input type="checkbox" name="isPodcast" id="isPodcast" class="form-check-input" value="true"
{%- if model.is_podcast %} checked="checked"{% endif %} onclick="Admin.checkPodcast()">
<label for="isPodcast" class="form-check-label">This Is a Podcast Feed</label>
</div>
</div>
</div>
</fieldset>
</div>
<div class="col-12 col-lg-6">
<fieldset class="container pb-0"> <fieldset class="container pb-0">
<legend>Feed Source</legend> <legend>Feed Source</legend>
<div class="row d-flex align-items-center"> <div class="row d-flex align-items-center">
<div class="col-1 d-flex justify-content-end pb-3"> <div class="col-1 d-flex justify-content-end pb-3">
<div class="form-check form-check-inline me-0"> <div class="form-check form-check-inline me-0">
<input type="radio" name="sourceType" id="sourceTypeCat" class="form-check-input" value="category" <input type="radio" name="sourceType" id="sourceTypeCat" class="form-check-input" value="category"
{% if is_cat %}checked="checked"{% endif %} onclick="Admin.customFeedBy('category')"> {%- unless typ == "tag" %} checked="checked" {% endunless -%}
onclick="Admin.customFeedBy('category')">
<label for="sourceTypeCat" class="form-check-label d-none">Category</label> <label for="sourceTypeCat" class="form-check-label d-none">Category</label>
</div> </div>
</div> </div>
<div class="col-11 col-lg-5 pb-3"> <div class="col-11 pb-3">
<div class="form-floating"> <div class="form-floating">
<select name="sourceValue" id="sourceValueCat" class="form-control" required <select name="sourceValue" id="sourceValueCat" class="form-control" required
{% unless is_cat %}disabled="disabled"{% endunless %}> {%- if typ == "tag" %} disabled="disabled"{% endif %}>
<option value="">&ndash; Select Category &ndash;</option> <option value="">&ndash; Select Category &ndash;</option>
{% for cat in categories -%} {% for cat in categories -%}
<option value="{{ cat.id }}" <option value="{{ cat.id }}"
{%- if is_cat and model.source_value == cat.id %} selected="selected"{% endif -%}> {%- if typ != "tag" and model.source_value == cat.id %} selected="selected"{% endif -%}>
{% for it in cat.parent_names %}{{ it }} &rang; {% endfor %}{{ cat.name }} {% for it in cat.parent_names %}{{ it }} &rang; {% endfor %}{{ cat.name }}
</option> </option>
{%- endfor %} {%- endfor %}
@ -34,15 +65,15 @@
<div class="col-1 d-flex justify-content-end pb-3"> <div class="col-1 d-flex justify-content-end pb-3">
<div class="form-check form-check-inline me-0"> <div class="form-check form-check-inline me-0">
<input type="radio" name="sourceType" id="sourceTypeTag" class="form-check-input" value="tag" <input type="radio" name="sourceType" id="sourceTypeTag" class="form-check-input" value="tag"
{%- unless is_cat %} checked="checked"{% endunless %} onclick="Admin.customFeedBy('tag')"> {%- if typ == "tag" %} checked="checked"{% endif %} onclick="Admin.customFeedBy('tag')">
<label for="sourceTypeTag" class="form-check-label d-none">Tag</label> <label for="sourceTypeTag" class="form-check-label d-none">Tag</label>
</div> </div>
</div> </div>
<div class="col-11 col-lg-5 pb-3"> <div class="col-11 pb-3">
<div class="form-floating"> <div class="form-floating">
<input type="text" name="sourceValue" id="sourceValueTag" class="form-control" placeholder="Tag" <input type="text" name="sourceValue" id="sourceValueTag" class="form-control" placeholder="Tag"
{%- if is_cat %} disabled="disabled"{% endif %} required {%- unless typ == "tag" %} disabled="disabled"{% endunless %} required
{%- unless is_cat %} value="{{ model.source_value }}"{% endunless %}> {%- if typ == "tag" %} value="{{ model.source_value }}"{% endif %}>
<label for="sourceValueTag">Tag</label> <label for="sourceValueTag">Tag</label>
</div> </div>
</div> </div>
@ -50,70 +81,26 @@
</fieldset> </fieldset>
</div> </div>
</div> </div>
<div class="row">
<div class="col-12 col-md-6 col-lg-5 offset-lg-1 col-xl-4 offset-xl-2 pb-3">
<div class="form-floating">
<input type="text" name="path" id="path" class="form-control" placeholder="Relative Feed Path"
value="{{ model.path }}">
<label for="path">Relative Feed Path</label>
<span class="form-text fst-italic">Appended to {{ web_log.url_base }}/</span>
</div>
</div>
<div class="col-12 col-md-6 col-lg-5 col-xl-4 pb-3 d-flex align-self-center justify-content-center">
<div class="form-check form-switch">
<input type="checkbox" name="isPodcast" id="isPodcast" class="form-check-input" value="true"
{%- if model.is_podcast %} checked="checked"{% endif %} onclick="Admin.checkPodcast()">
<label for="isPodcast" class="form-check-label">Is Podcast Feed</label>
</div>
</div>
</div>
<div class="row pb-3"> <div class="row pb-3">
<div class="col"> <div class="col">
<fieldset class="container" id="podcastFields"{% unless model.is_podcast %} disabled="disabled"{%endunless%}> <fieldset class="container" id="podcastFields"{% unless model.is_podcast %} disabled="disabled"{%endunless%}>
<legend>Podcast Settings</legend> <legend>Podcast Settings</legend>
<div class="row"> <div class="row">
<div class="col-12 col-lg-6 pb-3"> <div class="col-12 col-md-5 col-lg-4 offset-lg-1 pb-3">
<div class="form-floating"> <div class="form-floating">
<input type="text" name="title" id="title" class="form-control" placeholder="Title" required <input type="text" name="title" id="title" class="form-control" placeholder="Title" required
value="{{ model.title }}"> value="{{ model.title }}">
<label for="title">Title</label> <label for="title">Title</label>
</div> </div>
</div> </div>
<div class="col-12 col-lg-6 pb-3"> <div class="col-12 col-md-4 col-lg-4 pb-3">
<div class="form-floating"> <div class="form-floating">
<input type="text" name="subtitle" id="subtitle" class="form-control" placeholder="Subtitle" <input type="text" name="subtitle" id="subtitle" class="form-control" placeholder="Subtitle"
value="{{ model.subtitle }}"> value="{{ model.subtitle }}">
<label for="subtitle">Podcast Subtitle</label> <label for="subtitle">Podcast Subtitle</label>
</div> </div>
</div> </div>
</div> <div class="col-12 col-md-3 col-lg-2 pb-3">
<div class="row pb-3">
<div class="col">
<div class="form-floating">
<input type="text" name="summary" id="summary" class="form-control" placeholder="Summary" required
value="{{ model.summary }}">
<label for="summary">Summary</label>
<span class="form-text fst-italic">Displayed in podcast directories</span>
</div>
</div>
</div>
<div class="row">
<div class="col-12 col-lg-5 pb-3">
<div class="form-floating">
<input type="text" name="displayedAuthor" id="displayedAuthor" class="form-control"
placeholder="Author" required value="{{ model.displayed_author }}">
<label for="displayedAuthor">Displayed Author</label>
</div>
</div>
<div class="col-12 col-lg-5 pb-3">
<div class="form-floating">
<input type="text" name="email" id="email" class="form-control" placeholder="Email" required
value="{{ model.email }}">
<label for="email">Author E-mail</label>
<span class="form-text fst-italic">For iTunes, must match registered e-mail</span>
</div>
</div>
<div class="col-12 col-lg-2 pb-3">
<div class="form-floating"> <div class="form-floating">
<input type="number" name="itemsInFeed" id="itemsInFeed" class="form-control" placeholder="Items" <input type="number" name="itemsInFeed" id="itemsInFeed" class="form-control" placeholder="Items"
required value="{{ model.items_in_feed }}"> required value="{{ model.items_in_feed }}">
@ -122,7 +109,7 @@
</div> </div>
</div> </div>
<div class="row"> <div class="row">
<div class="col-12 col-lg-6 pb-3"> <div class="col-12 col-md-5 col-lg-4 offset-lg-1 pb-3">
<div class="form-floating"> <div class="form-floating">
<input type="text" name="itunesCategory" id="itunesCategory" class="form-control" <input type="text" name="itunesCategory" id="itunesCategory" class="form-control"
placeholder="iTunes Category" required value="{{ model.itunes_category }}"> placeholder="iTunes Category" required value="{{ model.itunes_category }}">
@ -135,16 +122,14 @@
</span> </span>
</div> </div>
</div> </div>
<div class="col-12 col-lg-6 pb-3"> <div class="col-12 col-md-4 pb-3">
<div class="form-floating"> <div class="form-floating">
<input type="text" name="itunesSubcategory" id="itunesSubcategory" class="form-control" <input type="text" name="itunesSubcategory" id="itunesSubcategory" class="form-control"
placeholder="iTunes Subcategory" value="{{ model.itunes_subcategory }}"> placeholder="iTunes Subcategory" value="{{ model.itunes_subcategory }}">
<label for="itunesSubcategory">iTunes Subcategory</label> <label for="itunesSubcategory">iTunes Subcategory</label>
</div> </div>
</div> </div>
</div> <div class="col-12 col-md-3 col-lg-2 pb-3">
<div class="row">
<div class="col-12 col-sm-6 col-lg-4">
<div class="form-floating"> <div class="form-floating">
<select name="explicit" id="explicit" class="form-control" required> <select name="explicit" id="explicit" class="form-control" required>
<option value="yes"{% if model.explicit == "yes" %} selected="selected"{% endif %}>Yes</option> <option value="yes"{% if model.explicit == "yes" %} selected="selected"{% endif %}>Yes</option>
@ -156,7 +141,24 @@
<label for="explicit">Explicit Rating</label> <label for="explicit">Explicit Rating</label>
</div> </div>
</div> </div>
<div class="col-12 col-sm-6 col-lg-4"> </div>
<div class="row">
<div class="col-12 col-md-6 col-lg-4 offset-xxl-1 pb-3">
<div class="form-floating">
<input type="text" name="displayedAuthor" id="displayedAuthor" class="form-control"
placeholder="Author" required value="{{ model.displayed_author }}">
<label for="displayedAuthor">Displayed Author</label>
</div>
</div>
<div class="col-12 col-md-6 col-lg-4 pb-3">
<div class="form-floating">
<input type="email" name="email" id="email" class="form-control" placeholder="Email" required
value="{{ model.email }}">
<label for="email">Author E-mail</label>
<span class="form-text fst-italic">For iTunes, must match registered e-mail</span>
</div>
</div>
<div class="col-12 col-sm-5 col-md-4 col-lg-4 col-xl-3 offset-xl-1 col-xxl-2 offset-xxl-0">
<div class="form-floating"> <div class="form-floating">
<input type="text" name="defaultMediaType" id="defaultMediaType" class="form-control" <input type="text" name="defaultMediaType" id="defaultMediaType" class="form-control"
placeholder="Media Type" value="{{ model.default_media_type }}"> placeholder="Media Type" value="{{ model.default_media_type }}">
@ -164,7 +166,27 @@
<span class="form-text fst-italic">Optional; blank for no default</span> <span class="form-text fst-italic">Optional; blank for no default</span>
</div> </div>
</div> </div>
<div class="col-12 col-sm-6 col-lg-4"> <div class="col-12 col-sm-7 col-md-8 col-lg-10 offset-lg-1">
<div class="form-floating">
<input type="text" name="imageUrl" id="imageUrl" class="form-control" placeholder="Image URL" required
value="{{ model.image_url }}">
<label for="imageUrl">Image URL</label>
<span class="form-text fst-italic">Relative URL will be appended to {{ web_log.url_base }}/</span>
</div>
</div>
</div>
<div class="row pb-3">
<div class="col-12 col-lg-10 offset-lg-1">
<div class="form-floating">
<input type="text" name="summary" id="summary" class="form-control" placeholder="Summary" required
value="{{ model.summary }}">
<label for="summary">Summary</label>
<span class="form-text fst-italic">Displayed in podcast directories</span>
</div>
</div>
</div>
<div class="row">
<div class="col-12 col-lg-10 offset-lg-1">
<div class="form-floating"> <div class="form-floating">
<input type="text" name="mediaBaseUrl" id="mediaBaseUrl" class="form-control" <input type="text" name="mediaBaseUrl" id="mediaBaseUrl" class="form-control"
placeholder="Media Base URL" value="{{ model.media_base_url }}"> placeholder="Media Base URL" value="{{ model.media_base_url }}">

View File

@ -85,10 +85,10 @@
<small> <small>
<a href="{{ feed.path | relative_link }}" target="_blank">View Feed</a> <a href="{{ feed.path | relative_link }}" target="_blank">View Feed</a>
<span class="text-muted"> &bull; </span> <span class="text-muted"> &bull; </span>
{%- capture feed_edit %}admin/rss/{{ feed.id }}/edit{% endcapture -%} {%- capture feed_edit %}admin/settings/rss/{{ feed.id }}/edit{% endcapture -%}
<a href="{{ feed_edit | relative_link }}">Edit</a> <a href="{{ feed_edit | relative_link }}">Edit</a>
<span class="text-muted"> &bull; </span> <span class="text-muted"> &bull; </span>
{%- capture feed_del %}admin/rss/{{ feed.id }}/delete{% endcapture -%} {%- capture feed_del %}admin/settings/rss/{{ feed.id }}/delete{% endcapture -%}
{%- capture feed_del_link %}{{ feed_del | relative_link }}{% endcapture -%} {%- capture feed_del_link %}{{ feed_del | relative_link }}{% endcapture -%}
<a href="{{ feed_del_link }}" class="text-danger" <a href="{{ feed_del_link }}" class="text-danger"
onclick="return Admin.deleteCustomFeed('{{ feed.source }}', '{{ feed_del_link }}')"> onclick="return Admin.deleteCustomFeed('{{ feed.source }}', '{{ feed_del_link }}')">