V2 #1
|
@ -610,7 +610,7 @@ type PostListItem =
|
||||||
tags : string list
|
tags : string list
|
||||||
|
|
||||||
/// Metadata for the post
|
/// Metadata for the post
|
||||||
meta : MetaItem list
|
metadata : MetaItem list
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a post list item from a post
|
/// 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}/")
|
text = if extra = "" then post.text else post.text.Replace ("href=\"/", $"href=\"{extra}/")
|
||||||
categoryIds = post.categoryIds |> List.map CategoryId.toString
|
categoryIds = post.categoryIds |> List.map CategoryId.toString
|
||||||
tags = post.tags
|
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 meta name = post.metadata |> List.tryFind (fun it -> it.name = name)
|
||||||
let value (item : MetaItem) = item.value
|
let value (item : MetaItem) = item.value
|
||||||
let epMediaUrl =
|
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 when link.StartsWith "http" -> link
|
||||||
| link -> WebLog.absoluteUrl webLog (Permalink link)
|
| link -> WebLog.absoluteUrl webLog (Permalink link)
|
||||||
let epMediaType =
|
let epMediaType =
|
||||||
match meta "mediaType", podcast.defaultMediaType with
|
match meta "episode_media_type", podcast.defaultMediaType with
|
||||||
| Some epType, _ -> Some epType.value
|
| Some epType, _ -> Some epType.value
|
||||||
| None, Some defType -> Some defType
|
| None, Some defType -> Some defType
|
||||||
| _ -> None
|
| _ -> None
|
||||||
let epImageUrl =
|
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 when link.StartsWith "http" -> link
|
||||||
| link -> WebLog.absoluteUrl webLog (Permalink link)
|
| link -> WebLog.absoluteUrl webLog (Permalink link)
|
||||||
let epExplicit =
|
let epExplicit =
|
||||||
try
|
try
|
||||||
(meta >> Option.map (value >> ExplicitRating.parse)) "explicit"
|
(meta >> Option.map (value >> ExplicitRating.parse)) "episode_explicit"
|
||||||
|> Option.defaultValue podcast.explicit
|
|> Option.defaultValue podcast.explicit
|
||||||
|> ExplicitRating.toString
|
|> ExplicitRating.toString
|
||||||
with :? ArgumentException -> ExplicitRating.toString podcast.explicit
|
with :? ArgumentException -> ExplicitRating.toString podcast.explicit
|
||||||
|
@ -144,7 +144,7 @@ let private addEpisode webLog (feed : CustomFeed) (post : Post) (item : Syndicat
|
||||||
let xmlDoc = XmlDocument ()
|
let xmlDoc = XmlDocument ()
|
||||||
let enclosure = xmlDoc.CreateElement "enclosure"
|
let enclosure = xmlDoc.CreateElement "enclosure"
|
||||||
enclosure.SetAttribute ("url", epMediaUrl)
|
enclosure.SetAttribute ("url", epMediaUrl)
|
||||||
meta "length" |> Option.iter (fun it -> enclosure.SetAttribute ("length", it.value))
|
meta "episode_media_length" |> Option.iter (fun it -> enclosure.SetAttribute ("length", it.value))
|
||||||
epMediaType |> Option.iter (fun typ -> enclosure.SetAttribute ("type", typ))
|
epMediaType |> Option.iter (fun typ -> enclosure.SetAttribute ("type", typ))
|
||||||
item.ElementExtensions.Add enclosure
|
item.ElementExtensions.Add enclosure
|
||||||
|
|
||||||
|
@ -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 ("summary", Namespace.iTunes, stripHtml post.text)
|
||||||
item.ElementExtensions.Add ("image", Namespace.iTunes, epImageUrl)
|
item.ElementExtensions.Add ("image", Namespace.iTunes, epImageUrl)
|
||||||
item.ElementExtensions.Add ("explicit", Namespace.iTunes, epExplicit)
|
item.ElementExtensions.Add ("explicit", Namespace.iTunes, epExplicit)
|
||||||
meta "subtitle" |> Option.iter (fun it -> item.ElementExtensions.Add ("subtitle", Namespace.iTunes, it.value))
|
meta "episode_subtitle"
|
||||||
meta "duration" |> Option.iter (fun it -> item.ElementExtensions.Add ("duration", Namespace.iTunes, it.value))
|
|> 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
|
if post.metadata |> List.exists (fun it -> it.name = "chapter") then
|
||||||
try
|
try
|
||||||
|
|
|
@ -232,10 +232,13 @@ let edit postId : HttpHandler = fun next ctx -> task {
|
||||||
match result with
|
match result with
|
||||||
| Some (title, post) ->
|
| 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!
|
return!
|
||||||
Hash.FromAnonymousObject {|
|
Hash.FromAnonymousObject {|
|
||||||
csrf = csrfToken ctx
|
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
|
page_title = title
|
||||||
categories = cats
|
categories = cats
|
||||||
|}
|
|}
|
||||||
|
|
|
@ -55,8 +55,14 @@
|
||||||
</button>
|
</button>
|
||||||
</legend>
|
</legend>
|
||||||
<div id="metaItemContainer" class="collapse">
|
<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">
|
<div id="metaItems" class="container">
|
||||||
{%- for meta in model.metadata %}
|
{%- for meta in metadata %}
|
||||||
<div id="meta_{{ meta[0] }}" class="row mb-3">
|
<div id="meta_{{ meta[0] }}" class="row mb-3">
|
||||||
<div class="col-1 text-center align-self-center">
|
<div class="col-1 text-center align-self-center">
|
||||||
<button type="button" class="btn btn-sm btn-danger" onclick="Admin.removeMetaItem({{ meta[0] }})">
|
<button type="button" class="btn btn-sm btn-danger" onclick="Admin.removeMetaItem({{ meta[0] }})">
|
||||||
|
@ -82,7 +88,7 @@
|
||||||
</div>
|
</div>
|
||||||
<button type="button" class="btn btn-sm btn-secondary" onclick="Admin.addMetaItem()">Add an Item</button>
|
<button type="button" class="btn btn-sm btn-secondary" onclick="Admin.addMetaItem()">Add an Item</button>
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener("DOMContentLoaded", () => Admin.setNextMetaIndex({{ model.metadata | size }}))
|
document.addEventListener("DOMContentLoaded", () => Admin.setNextMetaIndex({{ metadata | size }}))
|
||||||
</script>
|
</script>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
|
@ -33,8 +33,8 @@
|
||||||
#[i.fa.fa-clock-o(title='Clock' aria-hidden='true')] #[= readingTime(post.content, 'minutes', 175)]
|
#[i.fa.fa-clock-o(title='Clock' aria-hidden='true')] #[= readingTime(post.content, 'minutes', 175)]
|
||||||
{% endcomment %}
|
{% endcomment %}
|
||||||
</p>
|
</p>
|
||||||
{%- assign media = post.meta | value: "media" -%}
|
{%- assign media = post.metadata | value: "episode_media_file" -%}
|
||||||
{%- unless media == "-- media not found --" %}
|
{%- unless media == "-- episode_media_file not found --" %}
|
||||||
<aside class="podcast">
|
<aside class="podcast">
|
||||||
<p class="text-center"><strong>Listen While<br>You Read</strong></p>
|
<p class="text-center"><strong>Listen While<br>You Read</strong></p>
|
||||||
<audio controls onplaying="awftw.countPlay('{{ media }}')">
|
<audio controls onplaying="awftw.countPlay('{{ media }}')">
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<article class="item">
|
<article class="item">
|
||||||
<h1 class="item-heading">{{ post.title }}</h1>
|
<h1 class="item-heading">{{ post.title }}</h1>
|
||||||
{% assign media = post.meta | value: "media" %}
|
{% assign media = post.meta | value: "episode_media_file" %}
|
||||||
{%- unless media == "-- media not found --" %}
|
{%- unless media == "-- episode_media_file not found --" %}
|
||||||
<aside class="podcast">
|
<aside class="podcast">
|
||||||
<p class="text-center"><strong>Listen While<br>You Read</strong></p>
|
<p class="text-center"><strong>Listen While<br>You Read</strong></p>
|
||||||
<audio controls onplaying="awftw.countPlay('{{ media }}')">
|
<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() {
|
createMetaRemoveColumn() {
|
||||||
// Remove button
|
|
||||||
const removeBtn = document.createElement("button")
|
const removeBtn = document.createElement("button")
|
||||||
removeBtn.type = "button"
|
removeBtn.type = "button"
|
||||||
removeBtn.className = "btn btn-sm btn-danger"
|
removeBtn.className = "btn btn-sm btn-danger"
|
||||||
|
@ -36,49 +36,100 @@
|
||||||
removeCol.className = "col-1 text-center align-self-center"
|
removeCol.className = "col-1 text-center align-self-center"
|
||||||
removeCol.appendChild(removeBtn)
|
removeCol.appendChild(removeBtn)
|
||||||
|
|
||||||
// Name
|
return removeCol
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a metadata name field
|
||||||
|
* @returns {HTMLInputElement} The name input element
|
||||||
|
*/
|
||||||
|
createMetaNameField() {
|
||||||
const nameField = document.createElement("input")
|
const nameField = document.createElement("input")
|
||||||
|
|
||||||
nameField.type = "text"
|
nameField.type = "text"
|
||||||
nameField.name = "metaNames"
|
nameField.name = "metaNames"
|
||||||
nameField.id = `metaNames_${this.nextMetaIndex}`
|
nameField.id = `metaNames_${this.nextMetaIndex}`
|
||||||
nameField.className = "form-control"
|
nameField.className = "form-control"
|
||||||
nameField.placeholder = "Name"
|
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")
|
const nameLabel = document.createElement("label")
|
||||||
nameLabel.htmlFor = nameField.id
|
nameLabel.htmlFor = field.id
|
||||||
nameLabel.innerText = nameField.placeholder
|
nameLabel.innerText = field.placeholder
|
||||||
|
|
||||||
const nameFloat = document.createElement("div")
|
const nameFloat = document.createElement("div")
|
||||||
nameFloat.className = "form-floating"
|
nameFloat.className = "form-floating"
|
||||||
nameFloat.appendChild(nameField)
|
nameFloat.appendChild(field)
|
||||||
nameFloat.appendChild(nameLabel)
|
nameFloat.appendChild(nameLabel)
|
||||||
|
|
||||||
const nameCol = document.createElement("div")
|
const nameCol = document.createElement("div")
|
||||||
nameCol.className = "col-3"
|
nameCol.className = "col-3"
|
||||||
nameCol.appendChild(nameFloat)
|
nameCol.appendChild(nameFloat)
|
||||||
|
|
||||||
// Value
|
return nameCol
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a metadata value field
|
||||||
|
* @returns {HTMLInputElement} The metadata value field
|
||||||
|
*/
|
||||||
|
createMetaValueField() {
|
||||||
const valueField = document.createElement("input")
|
const valueField = document.createElement("input")
|
||||||
|
|
||||||
valueField.type = "text"
|
valueField.type = "text"
|
||||||
valueField.name = "metaValues"
|
valueField.name = "metaValues"
|
||||||
valueField.id = `metaValues_${this.nextMetaIndex}`
|
valueField.id = `metaValues_${this.nextMetaIndex}`
|
||||||
valueField.className = "form-control"
|
valueField.className = "form-control"
|
||||||
valueField.placeholder = "Value"
|
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")
|
const valueLabel = document.createElement("label")
|
||||||
valueLabel.htmlFor = valueField.id
|
valueLabel.htmlFor = field.id
|
||||||
valueLabel.innerText = valueField.placeholder
|
valueLabel.innerText = field.placeholder
|
||||||
|
|
||||||
const valueFloat = document.createElement("div")
|
const valueFloat = document.createElement("div")
|
||||||
valueFloat.className = "form-floating"
|
valueFloat.className = "form-floating"
|
||||||
valueFloat.appendChild(valueField)
|
valueFloat.appendChild(field)
|
||||||
valueFloat.appendChild(valueLabel)
|
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")
|
const valueCol = document.createElement("div")
|
||||||
valueCol.className = "col-8"
|
valueCol.className = "col-8"
|
||||||
valueCol.appendChild(valueFloat)
|
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")
|
const newRow = document.createElement("div")
|
||||||
newRow.className = "row mb-3"
|
newRow.className = "row mb-3"
|
||||||
newRow.id = `meta_${this.nextMetaIndex}`
|
newRow.id = `meta_${this.nextMetaIndex}`
|
||||||
|
@ -87,10 +138,23 @@
|
||||||
newRow.appendChild(valueCol)
|
newRow.appendChild(valueCol)
|
||||||
|
|
||||||
document.getElementById("metaItems").appendChild(newRow)
|
document.getElementById("metaItems").appendChild(newRow)
|
||||||
document.getElementById(nameField.id).focus()
|
|
||||||
this.nextMetaIndex++
|
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
|
* Add a new row for a permalink
|
||||||
*/
|
*/
|
||||||
|
@ -139,6 +203,33 @@
|
||||||
this.nextPermalink++
|
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
|
* Check to enable or disable podcast fields
|
||||||
*/
|
*/
|
||||||
|
|
Loading…
Reference in New Issue
Block a user