V2 #1
|
@ -610,7 +610,7 @@ type PostListItem =
|
|||
tags : string list
|
||||
|
||||
/// Metadata for the post
|
||||
meta : MetaItem list
|
||||
metadata : MetaItem list
|
||||
}
|
||||
|
||||
/// Create a post list item from a post
|
||||
|
@ -627,7 +627,7 @@ type PostListItem =
|
|||
text = if extra = "" then post.text else post.text.Replace ("href=\"/", $"href=\"{extra}/")
|
||||
categoryIds = post.categoryIds |> List.map CategoryId.toString
|
||||
tags = post.tags
|
||||
meta = post.metadata
|
||||
metadata = post.metadata
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -122,21 +122,21 @@ let private addEpisode webLog (feed : CustomFeed) (post : Post) (item : Syndicat
|
|||
let meta name = post.metadata |> List.tryFind (fun it -> it.name = name)
|
||||
let value (item : MetaItem) = item.value
|
||||
let epMediaUrl =
|
||||
match (meta >> Option.get >> value) "media" with
|
||||
match (meta >> Option.get >> value) "episode_media_file" with
|
||||
| link when link.StartsWith "http" -> link
|
||||
| link -> WebLog.absoluteUrl webLog (Permalink link)
|
||||
let epMediaType =
|
||||
match meta "mediaType", podcast.defaultMediaType with
|
||||
match meta "episode_media_type", podcast.defaultMediaType with
|
||||
| Some epType, _ -> Some epType.value
|
||||
| None, Some defType -> Some defType
|
||||
| _ -> None
|
||||
let epImageUrl =
|
||||
match defaultArg ((meta >> Option.map value) "image") (Permalink.toString podcast.imageUrl) with
|
||||
match defaultArg ((meta >> Option.map value) "episode_image") (Permalink.toString podcast.imageUrl) with
|
||||
| link when link.StartsWith "http" -> link
|
||||
| link -> WebLog.absoluteUrl webLog (Permalink link)
|
||||
let epExplicit =
|
||||
try
|
||||
(meta >> Option.map (value >> ExplicitRating.parse)) "explicit"
|
||||
(meta >> Option.map (value >> ExplicitRating.parse)) "episode_explicit"
|
||||
|> Option.defaultValue podcast.explicit
|
||||
|> ExplicitRating.toString
|
||||
with :? ArgumentException -> ExplicitRating.toString podcast.explicit
|
||||
|
@ -144,8 +144,8 @@ let private addEpisode webLog (feed : CustomFeed) (post : Post) (item : Syndicat
|
|||
let xmlDoc = XmlDocument ()
|
||||
let enclosure = xmlDoc.CreateElement "enclosure"
|
||||
enclosure.SetAttribute ("url", epMediaUrl)
|
||||
meta "length" |> Option.iter (fun it -> enclosure.SetAttribute ("length", it.value))
|
||||
epMediaType |> Option.iter (fun typ -> enclosure.SetAttribute ("type", typ))
|
||||
meta "episode_media_length" |> Option.iter (fun it -> enclosure.SetAttribute ("length", it.value))
|
||||
epMediaType |> Option.iter (fun typ -> enclosure.SetAttribute ("type", typ))
|
||||
item.ElementExtensions.Add enclosure
|
||||
|
||||
item.ElementExtensions.Add ("creator", Namespace.dc, podcast.displayedAuthor)
|
||||
|
@ -153,8 +153,10 @@ let private addEpisode webLog (feed : CustomFeed) (post : Post) (item : Syndicat
|
|||
item.ElementExtensions.Add ("summary", Namespace.iTunes, stripHtml post.text)
|
||||
item.ElementExtensions.Add ("image", Namespace.iTunes, epImageUrl)
|
||||
item.ElementExtensions.Add ("explicit", Namespace.iTunes, epExplicit)
|
||||
meta "subtitle" |> Option.iter (fun it -> item.ElementExtensions.Add ("subtitle", Namespace.iTunes, it.value))
|
||||
meta "duration" |> Option.iter (fun it -> item.ElementExtensions.Add ("duration", Namespace.iTunes, it.value))
|
||||
meta "episode_subtitle"
|
||||
|> Option.iter (fun it -> item.ElementExtensions.Add ("subtitle", Namespace.iTunes, it.value))
|
||||
meta "episode_duration"
|
||||
|> Option.iter (fun it -> item.ElementExtensions.Add ("duration", Namespace.iTunes, it.value))
|
||||
|
||||
if post.metadata |> List.exists (fun it -> it.name = "chapter") then
|
||||
try
|
||||
|
|
|
@ -231,11 +231,14 @@ let edit postId : HttpHandler = fun next ctx -> task {
|
|||
}
|
||||
match result with
|
||||
| Some (title, post) ->
|
||||
let! cats = Data.Category.findAllForView webLog.id conn
|
||||
let! cats = Data.Category.findAllForView webLog.id conn
|
||||
let model = EditPostModel.fromPost webLog post
|
||||
return!
|
||||
Hash.FromAnonymousObject {|
|
||||
csrf = csrfToken ctx
|
||||
model = EditPostModel.fromPost webLog post
|
||||
model = model
|
||||
metadata = Array.zip model.metaNames model.metaValues
|
||||
|> Array.mapi (fun idx (name, value) -> [| string idx; name; value |])
|
||||
page_title = title
|
||||
categories = cats
|
||||
|}
|
||||
|
|
|
@ -55,8 +55,14 @@
|
|||
</button>
|
||||
</legend>
|
||||
<div id="metaItemContainer" class="collapse">
|
||||
{%- assign has_media = model.metaNames | where: "episode_media_file" | size -%}
|
||||
{%- unless has_media == 0 %}
|
||||
<button class="btn btn-sm btn-secondary" onclick="Admin.addPodcastFields()" id="addPodcastFieldButton">
|
||||
Add Podcast Episode Fields
|
||||
</button>
|
||||
{%- endunless %}
|
||||
<div id="metaItems" class="container">
|
||||
{%- for meta in model.metadata %}
|
||||
{%- for meta in metadata %}
|
||||
<div id="meta_{{ meta[0] }}" class="row mb-3">
|
||||
<div class="col-1 text-center align-self-center">
|
||||
<button type="button" class="btn btn-sm btn-danger" onclick="Admin.removeMetaItem({{ meta[0] }})">
|
||||
|
@ -82,7 +88,7 @@
|
|||
</div>
|
||||
<button type="button" class="btn btn-sm btn-secondary" onclick="Admin.addMetaItem()">Add an Item</button>
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", () => Admin.setNextMetaIndex({{ model.metadata | size }}))
|
||||
document.addEventListener("DOMContentLoaded", () => Admin.setNextMetaIndex({{ metadata | size }}))
|
||||
</script>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
|
|
@ -33,8 +33,8 @@
|
|||
#[i.fa.fa-clock-o(title='Clock' aria-hidden='true')] #[= readingTime(post.content, 'minutes', 175)]
|
||||
{% endcomment %}
|
||||
</p>
|
||||
{%- assign media = post.meta | value: "media" -%}
|
||||
{%- unless media == "-- media not found --" %}
|
||||
{%- assign media = post.metadata | value: "episode_media_file" -%}
|
||||
{%- unless media == "-- episode_media_file not found --" %}
|
||||
<aside class="podcast">
|
||||
<p class="text-center"><strong>Listen While<br>You Read</strong></p>
|
||||
<audio controls onplaying="awftw.countPlay('{{ media }}')">
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
<div class="content">
|
||||
<article class="item">
|
||||
<h1 class="item-heading">{{ post.title }}</h1>
|
||||
{% assign media = post.meta | value: "media" %}
|
||||
{%- unless media == "-- media not found --" %}
|
||||
{% assign media = post.meta | value: "episode_media_file" %}
|
||||
{%- unless media == "-- episode_media_file not found --" %}
|
||||
<aside class="podcast">
|
||||
<p class="text-center"><strong>Listen While<br>You Read</strong></p>
|
||||
<audio controls onplaying="awftw.countPlay('{{ media }}')">
|
||||
|
|
|
@ -22,10 +22,10 @@
|
|||
},
|
||||
|
||||
/**
|
||||
* Add a new row for metadata entry
|
||||
* Create a metadata remove button
|
||||
* @returns {HTMLDivElement} The column with the remove button
|
||||
*/
|
||||
addMetaItem() {
|
||||
// Remove button
|
||||
createMetaRemoveColumn() {
|
||||
const removeBtn = document.createElement("button")
|
||||
removeBtn.type = "button"
|
||||
removeBtn.className = "btn btn-sm btn-danger"
|
||||
|
@ -36,49 +36,100 @@
|
|||
removeCol.className = "col-1 text-center align-self-center"
|
||||
removeCol.appendChild(removeBtn)
|
||||
|
||||
// Name
|
||||
return removeCol
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a metadata name field
|
||||
* @returns {HTMLInputElement} The name input element
|
||||
*/
|
||||
createMetaNameField() {
|
||||
const nameField = document.createElement("input")
|
||||
|
||||
nameField.type = "text"
|
||||
nameField.name = "metaNames"
|
||||
nameField.id = `metaNames_${this.nextMetaIndex}`
|
||||
nameField.className = "form-control"
|
||||
nameField.placeholder = "Name"
|
||||
|
||||
return nameField
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a metadata name column using the given name input field
|
||||
* @param {HTMLInputElement} field The name field for the column
|
||||
* @returns {HTMLDivElement} The name column
|
||||
*/
|
||||
createMetaNameColumn(field) {
|
||||
const nameLabel = document.createElement("label")
|
||||
nameLabel.htmlFor = nameField.id
|
||||
nameLabel.innerText = nameField.placeholder
|
||||
nameLabel.htmlFor = field.id
|
||||
nameLabel.innerText = field.placeholder
|
||||
|
||||
const nameFloat = document.createElement("div")
|
||||
nameFloat.className = "form-floating"
|
||||
nameFloat.appendChild(nameField)
|
||||
nameFloat.appendChild(field)
|
||||
nameFloat.appendChild(nameLabel)
|
||||
|
||||
const nameCol = document.createElement("div")
|
||||
nameCol.className = "col-3"
|
||||
nameCol.appendChild(nameFloat)
|
||||
|
||||
// Value
|
||||
return nameCol
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a metadata value field
|
||||
* @returns {HTMLInputElement} The metadata value field
|
||||
*/
|
||||
createMetaValueField() {
|
||||
const valueField = document.createElement("input")
|
||||
|
||||
valueField.type = "text"
|
||||
valueField.name = "metaValues"
|
||||
valueField.id = `metaValues_${this.nextMetaIndex}`
|
||||
valueField.className = "form-control"
|
||||
valueField.placeholder = "Value"
|
||||
|
||||
return valueField
|
||||
},
|
||||
|
||||
/**
|
||||
* Create a metadata value column using the given input field
|
||||
* @param {HTMLInputElement} field The metadata value input field
|
||||
* @param {string|undefined} hintText Text to be added below the field
|
||||
* @returns {HTMLDivElement} The value column
|
||||
*/
|
||||
createMetaValueColumn(field, hintText) {
|
||||
const valueLabel = document.createElement("label")
|
||||
valueLabel.htmlFor = valueField.id
|
||||
valueLabel.innerText = valueField.placeholder
|
||||
valueLabel.htmlFor = field.id
|
||||
valueLabel.innerText = field.placeholder
|
||||
|
||||
const valueFloat = document.createElement("div")
|
||||
valueFloat.className = "form-floating"
|
||||
valueFloat.appendChild(valueField)
|
||||
valueFloat.appendChild(field)
|
||||
valueFloat.appendChild(valueLabel)
|
||||
|
||||
if (hintText) {
|
||||
const valueHint = document.createElement("div")
|
||||
valueHint.className = "form-text"
|
||||
valueHint.innerText = hintText
|
||||
valueFloat.appendChild(valueHint)
|
||||
}
|
||||
|
||||
const valueCol = document.createElement("div")
|
||||
valueCol.className = "col-8"
|
||||
valueCol.appendChild(valueFloat)
|
||||
|
||||
// Put it all together
|
||||
return valueCol
|
||||
},
|
||||
|
||||
/**
|
||||
* Construct and add a metadata item row
|
||||
* @param {HTMLDivElement} removeCol The column with the remove button
|
||||
* @param {HTMLDivElement} nameCol The column with the name field
|
||||
* @param {HTMLDivElement} valueCol The column with the value field
|
||||
*/
|
||||
createMetaRow(removeCol, nameCol, valueCol) {
|
||||
const newRow = document.createElement("div")
|
||||
newRow.className = "row mb-3"
|
||||
newRow.id = `meta_${this.nextMetaIndex}`
|
||||
|
@ -87,10 +138,23 @@
|
|||
newRow.appendChild(valueCol)
|
||||
|
||||
document.getElementById("metaItems").appendChild(newRow)
|
||||
document.getElementById(nameField.id).focus()
|
||||
this.nextMetaIndex++
|
||||
},
|
||||
|
||||
/**
|
||||
* Add a new row for metadata entry
|
||||
*/
|
||||
addMetaItem() {
|
||||
const nameField = this.createMetaNameField()
|
||||
|
||||
this.createMetaRow(
|
||||
this.createMetaRemoveColumn(),
|
||||
this.createMetaNameColumn(nameField),
|
||||
this.createMetaValueColumn(this.createMetaValueField(), undefined))
|
||||
|
||||
document.getElementById(nameField.id).focus()
|
||||
},
|
||||
|
||||
/**
|
||||
* Add a new row for a permalink
|
||||
*/
|
||||
|
@ -139,6 +203,33 @@
|
|||
this.nextPermalink++
|
||||
},
|
||||
|
||||
/**
|
||||
* Add podcast episode metadata fields
|
||||
*/
|
||||
addPodcastFields() {
|
||||
[ [ "episode_media_file", true, "required; relative URL will be appended to configured base media path" ],
|
||||
[ "episode_media_length", true, "required; file size in bytes" ],
|
||||
[ "episode_duration", false, "suggested; format is HH:MM:SS" ],
|
||||
[ "episode_media_type", false, "optional; blank uses podcast default" ],
|
||||
[ "episode_image", false, "optional; relative URLs are served from this web log" ],
|
||||
[ "episode_subtitle", false, "optional" ],
|
||||
[ "episode_explicit", false, "optional; blank uses podcast default"]
|
||||
].forEach(([fieldName, isRequired, hintText]) => {
|
||||
const nameField = this.createMetaNameField()
|
||||
nameField.value = fieldName
|
||||
nameField.readOnly = true
|
||||
|
||||
const valueField = this.createMetaValueField()
|
||||
valueField.required = isRequired
|
||||
|
||||
this.createMetaRow(
|
||||
this.createMetaRemoveColumn(),
|
||||
this.createMetaNameColumn(nameField),
|
||||
this.createMetaValueColumn(valueField, hintText))
|
||||
})
|
||||
document.getElementById("addPodcastFieldButton").remove()
|
||||
},
|
||||
|
||||
/**
|
||||
* Check to enable or disable podcast fields
|
||||
*/
|
||||
|
|
Loading…
Reference in New Issue
Block a user