Rework content commands, add audio player

This commit is contained in:
Christoph Hagen
2024-12-14 16:31:40 +01:00
parent b3b8c9a610
commit be2aab2ea8
52 changed files with 1758 additions and 767 deletions

View File

@ -0,0 +1,94 @@
import Foundation
struct AudioPlayerCommandProcessor: CommandProcessor {
let commandType: ShorthandMarkdownKey = .audioPlayer
let content: Content
let results: PageGenerationResults
init(content: Content, results: PageGenerationResults) {
self.content = content
self.results = results
}
func process(_ arguments: [String], markdown: Substring) -> String {
guard arguments.count == 2 else {
results.invalid(command: .audioPlayer, "Invalid audio player arguments")
return ""
}
let fileId = arguments[0]
let titleText = arguments[1]
guard content.isValidIdForFile(fileId) else {
results.invalid(command: .audioPlayer, "Invalid file id \(fileId) for audio player")
return ""
}
guard let file = content.file(fileId) else {
results.missingFiles.insert(fileId)
return ""
}
let songs: [Song]
do {
let data = try file.dataContent()
songs = try JSONDecoder().decode([Song].self, from: data)
} catch {
results.issues.insert(.failedToLoadContent(error))
return ""
}
var playlist: [AudioPlayer.PlaylistItem] = []
var amplitude: [AmplitudeSong] = []
for song in songs {
guard let image = content.image(song.cover) else {
results.missing(file: song.cover, markdown: "Missing cover image \(song.cover) in \(file.id)")
continue
}
guard let audioFile = content.file(song.file) else {
results.missing(file: song.file, markdown: "Missing audio file \(song.file) in \(file.id)")
continue
}
#warning("Check if file is audio")
let coverUrl = image.absoluteUrl
let playlistItem = AudioPlayer.PlaylistItem(
index: playlist.count,
image: coverUrl,
name: song.name,
album: song.album,
track: song.track,
artist: song.artist)
let amplitudeSong = AmplitudeSong(
name: song.name,
artist: song.artist,
album: song.album,
track: "\(song.track)",
url: audioFile.absoluteUrl,
cover_art_url: coverUrl)
playlist.append(playlistItem)
amplitude.append(amplitudeSong)
}
let footerScript = AudioPlayerScript(items: amplitude).content
results.requiredFooters.insert(footerScript)
results.requiredHeaders.insert(.audioPlayerCss)
results.requiredHeaders.insert(.amplitude)
results.requiredIcons.formUnion([
.audioPlayerClose,
.audioPlayerPlaylist,
.audioPlayerNext,
.audioPlayerPrevious,
.audioPlayerPlay,
.audioPlayerPause
])
return AudioPlayer(playingText: titleText, items: playlist).content
}
}

View File

@ -0,0 +1,103 @@
struct ButtonCommandProcessor: CommandProcessor {
let commandType: ShorthandMarkdownKey = .buttons
let content: Content
let results: PageGenerationResults
init(content: Content, results: PageGenerationResults) {
self.content = content
self.results = results
}
/**
Format: `![buttons](<<fileId>,<text>,<download-filename?>;...)`
Format: `![buttons](type=<specification>;...)`
Types:
- Download: `download=<fileId>,<text>,<download-filename?>`
- External link: `external=<url>,<text>`
- Git: `git=<url>,<text>`
- Play: `play-circle=<text>,<click-action>`
*/
func process(_ arguments: [String], markdown: Substring) -> String {
let buttons = arguments.compactMap { convert(button: $0, markdown: markdown) }
return ContentButtons(items: buttons).content
}
private func convert(button: String, markdown: Substring) -> ContentButtons.Item? {
guard let type = PageIcon(rawValue: button.dropAfterFirst("=").trimmed) else {
results.invalid(command: commandType, markdown)
return nil
}
let parts = button.dropBeforeFirst("=").components(separatedBy: ",").map { $0.trimmed }
switch type {
case .buttonDownload:
return download(arguments: parts, markdown: markdown)
case .buttonGitLink:
return link(icon: .buttonGitLink, arguments: parts, markdown: markdown)
case .buttonExternalLink:
return link(icon: .buttonExternalLink, arguments: parts, markdown: markdown)
case .buttonPlay:
return play(arguments: parts, markdown: markdown)
default:
results.invalid(command: commandType, markdown)
return nil
}
}
private func download(arguments: [String], markdown: Substring) -> ContentButtons.Item? {
guard (2...3).contains(arguments.count) else {
results.invalid(command: commandType, markdown)
return nil
}
let fileId = arguments[0].trimmed
let title = arguments[1].trimmed
let downloadName = arguments.count > 2 ? arguments[2].trimmed : nil
guard let file = content.file(fileId) else {
results.missing(file: fileId, markdown: markdown)
return nil
}
results.files.insert(file)
results.requiredIcons.insert(.buttonDownload)
return ContentButtons.Item(
icon: .buttonDownload,
filePath: file.absoluteUrl,
text: title,
downloadFileName: downloadName)
}
private func link(icon: PageIcon, arguments: [String], markdown: Substring) -> ContentButtons.Item? {
guard arguments.count == 2 else {
results.invalid(command: .buttons, markdown)
return nil
}
let rawUrl = arguments[0].trimmed
guard let url = rawUrl.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else {
results.invalid(command: .buttons, markdown)
return nil
}
results.externalLinks.insert(rawUrl)
results.requiredIcons.insert(icon)
let title = arguments[1].trimmed
return .init(icon: icon, filePath: url, text: title)
}
private func play(arguments: [String], markdown: Substring) -> ContentButtons.Item? {
guard arguments.count == 2 else {
results.invalid(command: .buttons, markdown)
return nil
}
let text = arguments[0].trimmed
let event = arguments[1].trimmed
results.requiredIcons.insert(.buttonPlay)
return .init(icon: .buttonPlay, filePath: nil, text: text, onClickText: event)
}
}

View File

@ -0,0 +1,9 @@
protocol CommandProcessor {
var commandType: ShorthandMarkdownKey { get }
init(content: Content, results: PageGenerationResults)
func process(_ arguments: [String], markdown: Substring) -> String
}

View File

@ -0,0 +1,30 @@
struct LabelsCommandProcessor: CommandProcessor {
let commandType: ShorthandMarkdownKey = .labels
let content: Content
let results: PageGenerationResults
init(content: Content, results: PageGenerationResults) {
self.content = content
self.results = results
}
func process(_ arguments: [String], markdown: Substring) -> String {
let labels: [ContentLabel] = arguments.compactMap { arg in
let parts = arg.components(separatedBy: "=")
guard parts.count == 2 else {
results.invalid(command: .labels, markdown)
return nil
}
guard let icon = PageIcon(rawValue: parts[0].trimmed) else {
results.invalid(command: .labels, markdown)
return nil
}
return .init(icon: icon, value: parts[1])
}
return ContentLabels(labels: labels).content
}
}