Add single file audio player, introduce blocks
This commit is contained in:
parent
c78c359819
commit
245534e989
@ -205,7 +205,7 @@
|
||||
E2FE0F0B2D2689FF002963B7 /* FeedGeneratorSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F0A2D2689FF002963B7 /* FeedGeneratorSource.swift */; };
|
||||
E2FE0F0D2D268A09002963B7 /* PostListPageGeneratorSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F0C2D268A09002963B7 /* PostListPageGeneratorSource.swift */; };
|
||||
E2FE0F0F2D268D4F002963B7 /* BoxCommandProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F0E2D268D4B002963B7 /* BoxCommandProcessor.swift */; };
|
||||
E2FE0F112D268E7E002963B7 /* PageCodeProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F102D268E78002963B7 /* PageCodeProcessor.swift */; };
|
||||
E2FE0F112D268E7E002963B7 /* CodeBlockProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F102D268E78002963B7 /* CodeBlockProcessor.swift */; };
|
||||
E2FE0F152D26918F002963B7 /* PageHtmlProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F142D269188002963B7 /* PageHtmlProcessor.swift */; };
|
||||
E2FE0F172D2698D5002963B7 /* LocalizedPageId.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F162D2698D5002963B7 /* LocalizedPageId.swift */; };
|
||||
E2FE0F192D2723E3002963B7 /* ImageSet.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F182D2723E3002963B7 /* ImageSet.swift */; };
|
||||
@ -219,6 +219,14 @@
|
||||
E2FE0F2A2D2AFBE6002963B7 /* ImageCompareIcons.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F292D2AFBE3002963B7 /* ImageCompareIcons.swift */; };
|
||||
E2FE0F2C2D2B119A002963B7 /* ImageCommandProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F2B2D2B1196002963B7 /* ImageCommandProcessor.swift */; };
|
||||
E2FE0F312D2B1952002963B7 /* PartialSvgImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F302D2B1952002963B7 /* PartialSvgImage.swift */; };
|
||||
E2FE0F332D2B2665002963B7 /* AudioBlockProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F322D2B265F002963B7 /* AudioBlockProcessor.swift */; };
|
||||
E2FE0F362D2B27F9002963B7 /* BlockProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F352D2B27F6002963B7 /* BlockProcessor.swift */; };
|
||||
E2FE0F382D2B32F4002963B7 /* SingleFilePlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F372D2B32ED002963B7 /* SingleFilePlayer.swift */; };
|
||||
E2FE0F3A2D2B3E4F002963B7 /* AudioPlayerSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F392D2B3E4E002963B7 /* AudioPlayerSettings.swift */; };
|
||||
E2FE0F3C2D2B3F45002963B7 /* AudioPlayerSettingsFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F3B2D2B3F42002963B7 /* AudioPlayerSettingsFile.swift */; };
|
||||
E2FE0F3E2D2B4225002963B7 /* AudioSettingsDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F3D2D2B4225002963B7 /* AudioSettingsDetailView.swift */; };
|
||||
E2FE0F402D2B45D3002963B7 /* SwiftProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F3F2D2B45CD002963B7 /* SwiftProcessor.swift */; };
|
||||
E2FE0F422D2B4821002963B7 /* OtherCodeProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F412D2B480B002963B7 /* OtherCodeProcessor.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
@ -415,7 +423,7 @@
|
||||
E2FE0F0A2D2689FF002963B7 /* FeedGeneratorSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeedGeneratorSource.swift; sourceTree = "<group>"; };
|
||||
E2FE0F0C2D268A09002963B7 /* PostListPageGeneratorSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostListPageGeneratorSource.swift; sourceTree = "<group>"; };
|
||||
E2FE0F0E2D268D4B002963B7 /* BoxCommandProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoxCommandProcessor.swift; sourceTree = "<group>"; };
|
||||
E2FE0F102D268E78002963B7 /* PageCodeProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageCodeProcessor.swift; sourceTree = "<group>"; };
|
||||
E2FE0F102D268E78002963B7 /* CodeBlockProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodeBlockProcessor.swift; sourceTree = "<group>"; };
|
||||
E2FE0F142D269188002963B7 /* PageHtmlProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageHtmlProcessor.swift; sourceTree = "<group>"; };
|
||||
E2FE0F162D2698D5002963B7 /* LocalizedPageId.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizedPageId.swift; sourceTree = "<group>"; };
|
||||
E2FE0F182D2723E3002963B7 /* ImageSet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageSet.swift; sourceTree = "<group>"; };
|
||||
@ -429,6 +437,14 @@
|
||||
E2FE0F292D2AFBE3002963B7 /* ImageCompareIcons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCompareIcons.swift; sourceTree = "<group>"; };
|
||||
E2FE0F2B2D2B1196002963B7 /* ImageCommandProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCommandProcessor.swift; sourceTree = "<group>"; };
|
||||
E2FE0F302D2B1952002963B7 /* PartialSvgImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PartialSvgImage.swift; sourceTree = "<group>"; };
|
||||
E2FE0F322D2B265F002963B7 /* AudioBlockProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioBlockProcessor.swift; sourceTree = "<group>"; };
|
||||
E2FE0F352D2B27F6002963B7 /* BlockProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockProcessor.swift; sourceTree = "<group>"; };
|
||||
E2FE0F372D2B32ED002963B7 /* SingleFilePlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleFilePlayer.swift; sourceTree = "<group>"; };
|
||||
E2FE0F392D2B3E4E002963B7 /* AudioPlayerSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioPlayerSettings.swift; sourceTree = "<group>"; };
|
||||
E2FE0F3B2D2B3F42002963B7 /* AudioPlayerSettingsFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioPlayerSettingsFile.swift; sourceTree = "<group>"; };
|
||||
E2FE0F3D2D2B4225002963B7 /* AudioSettingsDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioSettingsDetailView.swift; sourceTree = "<group>"; };
|
||||
E2FE0F3F2D2B45CD002963B7 /* SwiftProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftProcessor.swift; sourceTree = "<group>"; };
|
||||
E2FE0F412D2B480B002963B7 /* OtherCodeProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OtherCodeProcessor.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@ -479,6 +495,7 @@
|
||||
E25DA5322D0041C400AEF16D /* Settings */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E2FE0F3B2D2B3F42002963B7 /* AudioPlayerSettingsFile.swift */,
|
||||
E2FE0EFF2D266E0A002963B7 /* LocalizedNavigationSettingsFile.swift */,
|
||||
E2FE0EFD2D266DA1002963B7 /* NavigationSettingsFile.swift */,
|
||||
E29D31972D0C19300051B7F4 /* PathSettingsFile.swift */,
|
||||
@ -493,6 +510,7 @@
|
||||
E25DA53B2D0042EA00AEF16D /* Settings */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E2FE0F392D2B3E4E002963B7 /* AudioPlayerSettings.swift */,
|
||||
E2FE0F012D266FCB002963B7 /* LocalizedNavigationSettings.swift */,
|
||||
E21850362CFCA5580090B18B /* LocalizedPostSettings.swift */,
|
||||
E2FE0EFB2D266D18002963B7 /* NavigationSettings.swift */,
|
||||
@ -507,6 +525,7 @@
|
||||
E25DA5782D01C56200AEF16D /* Generator */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E2FE0F342D2B27E6002963B7 /* Blocks */,
|
||||
E2FE0F072D2689DC002963B7 /* Post Lists */,
|
||||
E29D31B62D0DAC030051B7F4 /* Page Content */,
|
||||
E2FE0F1C2D281A7B002963B7 /* Page Generators */,
|
||||
@ -594,18 +613,17 @@
|
||||
E29D31B62D0DAC030051B7F4 /* Page Content */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E29D31BD2D0DB8560051B7F4 /* AudioPlayerCommand.swift */,
|
||||
E2FE0F0E2D268D4B002963B7 /* BoxCommandProcessor.swift */,
|
||||
E29D31B72D0DAC1D0051B7F4 /* ButtonCommand.swift */,
|
||||
E29D31BB2D0DB5110051B7F4 /* CommandProcessor.swift */,
|
||||
E2FE0EF72D1D810C002963B7 /* IconCommandProcessor.swift */,
|
||||
E2FE0F2B2D2B1196002963B7 /* ImageCommandProcessor.swift */,
|
||||
E2FE0F252D2AF9AA002963B7 /* ImageCompareCommand.swift */,
|
||||
E2FE0F212D2A849B002963B7 /* VideoCommandProcessor.swift */,
|
||||
E2FE0F142D269188002963B7 /* PageHtmlProcessor.swift */,
|
||||
E2FE0F102D268E78002963B7 /* PageCodeProcessor.swift */,
|
||||
E2FE0F0E2D268D4B002963B7 /* BoxCommandProcessor.swift */,
|
||||
E2FE0EF72D1D810C002963B7 /* IconCommandProcessor.swift */,
|
||||
E2FE0EED2D1C22EF002963B7 /* InlineLinkProcessor.swift */,
|
||||
E29D31BD2D0DB8560051B7F4 /* AudioPlayerCommand.swift */,
|
||||
E29D31BB2D0DB5110051B7F4 /* CommandProcessor.swift */,
|
||||
E29D31B92D0DB4EF0051B7F4 /* LabelsCommand.swift */,
|
||||
E29D31B72D0DAC1D0051B7F4 /* ButtonCommand.swift */,
|
||||
E2FE0F142D269188002963B7 /* PageHtmlProcessor.swift */,
|
||||
E2FE0F212D2A849B002963B7 /* VideoCommandProcessor.swift */,
|
||||
);
|
||||
path = "Page Content";
|
||||
sourceTree = "<group>";
|
||||
@ -613,6 +631,7 @@
|
||||
E29D31C12D0DBED70051B7F4 /* AudioPlayer */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E2FE0F372D2B32ED002963B7 /* SingleFilePlayer.swift */,
|
||||
E29D31A92D0CEE3C0051B7F4 /* AudioPlayer.swift */,
|
||||
E29D31BF2D0DB9ED0051B7F4 /* AudioPlayerContent.swift */,
|
||||
);
|
||||
@ -636,6 +655,7 @@
|
||||
E2A21C342CB9A3CA0060935B /* Settings */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E2FE0F3D2D2B4225002963B7 /* AudioSettingsDetailView.swift */,
|
||||
E29D318C2D0B2E5E0051B7F4 /* Content */,
|
||||
E29D316E2D0822720051B7F4 /* SettingsListView.swift */,
|
||||
E29D31702D08234D0051B7F4 /* GenerationDetailView.swift */,
|
||||
@ -894,6 +914,18 @@
|
||||
path = Images;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
E2FE0F342D2B27E6002963B7 /* Blocks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
E2FE0F412D2B480B002963B7 /* OtherCodeProcessor.swift */,
|
||||
E2FE0F3F2D2B45CD002963B7 /* SwiftProcessor.swift */,
|
||||
E2FE0F352D2B27F6002963B7 /* BlockProcessor.swift */,
|
||||
E2FE0F322D2B265F002963B7 /* AudioBlockProcessor.swift */,
|
||||
E2FE0F102D268E78002963B7 /* CodeBlockProcessor.swift */,
|
||||
);
|
||||
path = Blocks;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
@ -1006,12 +1038,14 @@
|
||||
E2FE0EF42D1D6D2E002963B7 /* GeneralIcons.swift in Sources */,
|
||||
E25DA5172CFF00F500AEF16D /* Content+Save.swift in Sources */,
|
||||
E229902E2D0F7280009F8D77 /* IdPropertyView.swift in Sources */,
|
||||
E2FE0F3C2D2B3F45002963B7 /* AudioPlayerSettingsFile.swift in Sources */,
|
||||
E29D31AD2D0DA5360051B7F4 /* AudioPlayerIcons.swift in Sources */,
|
||||
E25DA5452D00952E00AEF16D /* SettingsSection.swift in Sources */,
|
||||
E2A21C462CBAE2E60060935B /* FeedEntryContent.swift in Sources */,
|
||||
E2A37D1D2CEA922D0000979F /* LocalizedPost.swift in Sources */,
|
||||
E21850172CEE55FC0090B18B /* FileType.swift in Sources */,
|
||||
E2FE0F042D267206002963B7 /* LocalizedNavigationBarSettingsView.swift in Sources */,
|
||||
E2FE0F382D2B32F4002963B7 /* SingleFilePlayer.swift in Sources */,
|
||||
E29D31B12D0DA5510051B7F4 /* ContentIcon.swift in Sources */,
|
||||
E2A37D112CE537800000979F /* PageFile.swift in Sources */,
|
||||
E242520A2C52C9260029FF16 /* ContentLanguage.swift in Sources */,
|
||||
@ -1031,6 +1065,7 @@
|
||||
E2A21C282CB29B2A0060935B /* FeedEntryData.swift in Sources */,
|
||||
E29D31942D0B7D280051B7F4 /* SimpleImage.swift in Sources */,
|
||||
E29D31632D06E95D0051B7F4 /* NavigationIcon.swift in Sources */,
|
||||
E2FE0F362D2B27F9002963B7 /* BlockProcessor.swift in Sources */,
|
||||
E22990462D10B7A7009F8D77 /* SecurityScopeStatus.swift in Sources */,
|
||||
E29D31512D06168E0051B7F4 /* PostListView.swift in Sources */,
|
||||
E22990342D0F77E9009F8D77 /* PagePropertyView.swift in Sources */,
|
||||
@ -1050,6 +1085,7 @@
|
||||
E29D31BE2D0DB85A0051B7F4 /* AudioPlayerCommand.swift in Sources */,
|
||||
E29D31552D06D2CE0051B7F4 /* TagListView.swift in Sources */,
|
||||
E29D31982D0C19340051B7F4 /* PathSettingsFile.swift in Sources */,
|
||||
E2FE0F3A2D2B3E4F002963B7 /* AudioPlayerSettings.swift in Sources */,
|
||||
E2A37D152CE68BEC0000979F /* PostFile.swift in Sources */,
|
||||
E2A21C032CB16C290060935B /* Environment+Language.swift in Sources */,
|
||||
E2FE0F092D2689F0002963B7 /* TagPageGeneratorSource.swift in Sources */,
|
||||
@ -1070,10 +1106,12 @@
|
||||
E2A37D252CEBD7A10000979F /* PageListView.swift in Sources */,
|
||||
E2FE0F172D2698D5002963B7 /* LocalizedPageId.swift in Sources */,
|
||||
E2FE0F0D2D268A09002963B7 /* PostListPageGeneratorSource.swift in Sources */,
|
||||
E2FE0F402D2B45D3002963B7 /* SwiftProcessor.swift in Sources */,
|
||||
E25DA58B2D020C9500AEF16D /* PageImage.swift in Sources */,
|
||||
E21850232CF10C850090B18B /* TagSelectionView.swift in Sources */,
|
||||
E2A21C332CB5BCAC0060935B /* PageContentView.swift in Sources */,
|
||||
E22990402D0F95EC009F8D77 /* FolderOnDiskPropertyView.swift in Sources */,
|
||||
E2FE0F422D2B4821002963B7 /* OtherCodeProcessor.swift in Sources */,
|
||||
E21850332CFAFA2F0090B18B /* Settings.swift in Sources */,
|
||||
E29D31892D0AED1F0051B7F4 /* ModelViewer.swift in Sources */,
|
||||
E29D31412D04887F0051B7F4 /* SelectedDetailView.swift in Sources */,
|
||||
@ -1113,6 +1151,7 @@
|
||||
E29D314B2D04FC950051B7F4 /* FileToAdd.swift in Sources */,
|
||||
E2E06DFB2CA4A65E0019C2AF /* Content.swift in Sources */,
|
||||
E29D31B52D0DA8490051B7F4 /* PageIcon.swift in Sources */,
|
||||
E2FE0F332D2B2665002963B7 /* AudioBlockProcessor.swift in Sources */,
|
||||
E25DA51D2CFF135E00AEF16D /* GenericPage.swift in Sources */,
|
||||
E29D319B2D0C452B0051B7F4 /* PageIssue.swift in Sources */,
|
||||
E218500B2CEE02FD0090B18B /* Content+Mock.swift in Sources */,
|
||||
@ -1139,7 +1178,7 @@
|
||||
E229902A2D0F5A14009F8D77 /* DetailTitle.swift in Sources */,
|
||||
E29D31532D0618740051B7F4 /* AddPageView.swift in Sources */,
|
||||
E2FE0F2C2D2B119A002963B7 /* ImageCommandProcessor.swift in Sources */,
|
||||
E2FE0F112D268E7E002963B7 /* PageCodeProcessor.swift in Sources */,
|
||||
E2FE0F112D268E7E002963B7 /* CodeBlockProcessor.swift in Sources */,
|
||||
E22990202D0ECBE5009F8D77 /* TagOverviewDetailView.swift in Sources */,
|
||||
E29D31C02D0DB9F20051B7F4 /* AudioPlayerContent.swift in Sources */,
|
||||
E22990192D0E3546009F8D77 /* ItemType.swift in Sources */,
|
||||
@ -1167,6 +1206,7 @@
|
||||
E229903A2D0F7E48009F8D77 /* GenericPropertyView.swift in Sources */,
|
||||
E29D31AA2D0CEE3F0051B7F4 /* AudioPlayer.swift in Sources */,
|
||||
E25DA5152CFF00C100AEF16D /* Content+Load.swift in Sources */,
|
||||
E2FE0F3E2D2B4225002963B7 /* AudioSettingsDetailView.swift in Sources */,
|
||||
E25DA58F2D02368D00AEF16D /* PageSettings.swift in Sources */,
|
||||
E25DA50D2CFD9BA200AEF16D /* PostTagAssignmentView.swift in Sources */,
|
||||
E22990362D0F79D2009F8D77 /* OptionalStringPropertyView.swift in Sources */,
|
||||
|
@ -9,6 +9,13 @@ extension String {
|
||||
.replacingOccurrences(of: ">", with: ">")
|
||||
}
|
||||
|
||||
func percentDecoded() -> String {
|
||||
guard let decoded = removingPercentEncoding else {
|
||||
return self
|
||||
}
|
||||
return decoded
|
||||
}
|
||||
|
||||
var removingSurroundingQuotes: String {
|
||||
if hasPrefix("\"") && hasSuffix("\"") {
|
||||
return dropBeforeFirst("\"").dropAfterLast("\"")
|
||||
@ -85,6 +92,14 @@ extension String {
|
||||
func between(_ start: String, and end: String) -> String {
|
||||
dropBeforeFirst(start).dropAfterFirst(end)
|
||||
}
|
||||
|
||||
/**
|
||||
Split the string at the first occurence of the separator
|
||||
*/
|
||||
func splitAtFirst(_ separator: String) -> (String, String) {
|
||||
let parts = components(separatedBy: separator)
|
||||
return (parts.first!, parts.dropFirst().joined(separator: separator))
|
||||
}
|
||||
}
|
||||
|
||||
extension String {
|
||||
@ -129,4 +144,12 @@ extension Substring {
|
||||
func last(after: String) -> String {
|
||||
components(separatedBy: after).last!
|
||||
}
|
||||
|
||||
/**
|
||||
Split the string at the first occurence of the separator
|
||||
*/
|
||||
func splitAtFirst(_ separator: String) -> (String, String) {
|
||||
let parts = components(separatedBy: separator)
|
||||
return (parts.first!, parts.dropFirst().joined(separator: separator))
|
||||
}
|
||||
}
|
||||
|
62
CHDataManagement/Generator/Blocks/AudioBlockProcessor.swift
Normal file
62
CHDataManagement/Generator/Blocks/AudioBlockProcessor.swift
Normal file
@ -0,0 +1,62 @@
|
||||
|
||||
struct AudioBlockProcessor: KeyedBlockProcessor {
|
||||
|
||||
enum Key: String {
|
||||
case name
|
||||
case artist
|
||||
case album
|
||||
case file
|
||||
case cover
|
||||
}
|
||||
|
||||
static let blockId: ContentBlock = .audio
|
||||
|
||||
let content: Content
|
||||
|
||||
let results: PageGenerationResults
|
||||
|
||||
let language: ContentLanguage
|
||||
|
||||
init(content: Content, results: PageGenerationResults, language: ContentLanguage) {
|
||||
self.content = content
|
||||
self.results = results
|
||||
self.language = language
|
||||
}
|
||||
|
||||
func process(_ arguments: [Key : String], markdown: Substring) -> String {
|
||||
guard let name = arguments[.name],
|
||||
let artist = arguments[.artist],
|
||||
let album = arguments[.album],
|
||||
let fileId = arguments[.file],
|
||||
let cover = arguments[.cover] else {
|
||||
invalid(markdown)
|
||||
return ""
|
||||
}
|
||||
|
||||
guard let image = content.image(cover) else {
|
||||
results.missing(file: cover, source: "Audio Block")
|
||||
return ""
|
||||
}
|
||||
|
||||
guard let file = content.file(fileId) else {
|
||||
results.missing(file: fileId, source: "Audio Block")
|
||||
return ""
|
||||
}
|
||||
|
||||
let coverSize = 2 * content.settings.audioPlayer.smallCoverImageSize
|
||||
let coverImage = image.imageVersion(width: coverSize, height: coverSize, type: image.type)
|
||||
let footer = SingleFilePlayer.footer(
|
||||
name: name,
|
||||
artist: artist,
|
||||
album: album,
|
||||
url: file.absoluteUrl,
|
||||
cover: coverImage.outputPath)
|
||||
|
||||
results.require(image: coverImage)
|
||||
results.require(footer: footer)
|
||||
results.require(headers: .audioPlayerJs, .audioPlayerCss)
|
||||
results.require(icons: .audioPlayerPlay, .audioPlayerPause)
|
||||
|
||||
return SingleFilePlayer().content
|
||||
}
|
||||
}
|
86
CHDataManagement/Generator/Blocks/BlockProcessor.swift
Normal file
86
CHDataManagement/Generator/Blocks/BlockProcessor.swift
Normal file
@ -0,0 +1,86 @@
|
||||
|
||||
enum ContentBlock: String, CaseIterable {
|
||||
|
||||
case audio
|
||||
|
||||
case swift
|
||||
|
||||
var processor: BlockProcessor.Type {
|
||||
switch self {
|
||||
case .audio: return AudioBlockProcessor.self
|
||||
case .swift: return SwiftBlockProcessor.self
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protocol BlockProcessor {
|
||||
|
||||
static var blockId: ContentBlock { get }
|
||||
|
||||
var results: PageGenerationResults { get }
|
||||
|
||||
init(content: Content, results: PageGenerationResults, language: ContentLanguage)
|
||||
|
||||
func process(_ markdown: Substring) -> String
|
||||
}
|
||||
|
||||
extension BlockProcessor {
|
||||
|
||||
func invalid(_ markdown: Substring) {
|
||||
results.invalid(block: Self.blockId, markdown)
|
||||
}
|
||||
}
|
||||
|
||||
protocol BlockLineProcessor: BlockProcessor {
|
||||
|
||||
func process(_ lines: [String], markdown: Substring) -> String
|
||||
}
|
||||
|
||||
extension BlockLineProcessor {
|
||||
|
||||
func process(_ markdown: Substring) -> String {
|
||||
let lines = markdown
|
||||
.between("```\(Self.blockId.self)", and: "```")
|
||||
.components(separatedBy: "\n")
|
||||
return process(lines, markdown: markdown)
|
||||
}
|
||||
}
|
||||
|
||||
protocol OrderedKeyBlockProcessor: BlockLineProcessor {
|
||||
|
||||
associatedtype Key: Hashable, RawRepresentable where Key.RawValue == String
|
||||
|
||||
func process(_ arguments: [(key: Key, value: String)], markdown: Substring) -> String
|
||||
}
|
||||
|
||||
extension OrderedKeyBlockProcessor {
|
||||
|
||||
func process(_ lines: [String], markdown: Substring) -> String {
|
||||
let result: [(key: Key, value: String)] = lines.compactMap { line in
|
||||
guard line.trimmed != "" else {
|
||||
return nil
|
||||
}
|
||||
let (rawKey, rawValue) = line.splitAtFirst(":")
|
||||
guard let key = Key(rawValue: rawKey.trimmed) else {
|
||||
print("Invalid key \(rawKey)")
|
||||
invalid(markdown)
|
||||
return nil
|
||||
}
|
||||
return (key, rawValue.trimmed)
|
||||
}
|
||||
return process(result, markdown: markdown)
|
||||
}
|
||||
}
|
||||
|
||||
protocol KeyedBlockProcessor: OrderedKeyBlockProcessor {
|
||||
|
||||
func process(_ arguments: [Key : String], markdown: Substring) -> String
|
||||
}
|
||||
|
||||
extension KeyedBlockProcessor {
|
||||
|
||||
func process(_ arguments: [(key: Key, value: String)], markdown: Substring) -> String {
|
||||
let result = arguments.reduce(into: [:]) { $0[$1.key] = $1.value }
|
||||
return process(result, markdown: markdown)
|
||||
}
|
||||
}
|
33
CHDataManagement/Generator/Blocks/CodeBlockProcessor.swift
Normal file
33
CHDataManagement/Generator/Blocks/CodeBlockProcessor.swift
Normal file
@ -0,0 +1,33 @@
|
||||
|
||||
struct CodeBlockProcessor {
|
||||
|
||||
private let codeHighlightFooter = "<script>hljs.highlightAll();</script>"
|
||||
|
||||
let results: PageGenerationResults
|
||||
|
||||
private let blocks: [ContentBlock : BlockProcessor]
|
||||
|
||||
private let other: OtherCodeProcessor
|
||||
|
||||
init(content: Content, results: PageGenerationResults, language: ContentLanguage) {
|
||||
self.results = results
|
||||
self.other = .init(results: results)
|
||||
|
||||
self.blocks = ContentBlock.allCases.reduce(into: [:]) { blocks, block in
|
||||
blocks[block] = block.processor.init(content: content, results: results, language: language)
|
||||
}
|
||||
}
|
||||
|
||||
func process(_ html: String, markdown: Substring) -> String {
|
||||
let input = String(markdown)
|
||||
let rawBlockId = input.dropAfterFirst("\n").dropBeforeFirst("```").trimmed
|
||||
guard let blockId = ContentBlock(rawValue: rawBlockId) else {
|
||||
return other.process(html: html)
|
||||
}
|
||||
guard let processor = self.blocks[blockId] else {
|
||||
results.invalid(block: blockId, markdown)
|
||||
return ""
|
||||
}
|
||||
return processor.process(markdown)
|
||||
}
|
||||
}
|
17
CHDataManagement/Generator/Blocks/OtherCodeProcessor.swift
Normal file
17
CHDataManagement/Generator/Blocks/OtherCodeProcessor.swift
Normal file
@ -0,0 +1,17 @@
|
||||
|
||||
struct OtherCodeProcessor {
|
||||
|
||||
private let codeHighlightFooter = "<script>hljs.highlightAll();</script>"
|
||||
|
||||
let results: PageGenerationResults
|
||||
|
||||
init(results: PageGenerationResults) {
|
||||
self.results = results
|
||||
}
|
||||
|
||||
func process(html: String) -> String {
|
||||
results.require(header: .codeHightlighting)
|
||||
results.require(footer: codeHighlightFooter)
|
||||
return html // Just use normal code highlighting
|
||||
}
|
||||
}
|
26
CHDataManagement/Generator/Blocks/SwiftProcessor.swift
Normal file
26
CHDataManagement/Generator/Blocks/SwiftProcessor.swift
Normal file
@ -0,0 +1,26 @@
|
||||
import Splash
|
||||
|
||||
struct SwiftBlockProcessor: BlockProcessor {
|
||||
|
||||
static let blockId: ContentBlock = .swift
|
||||
|
||||
let content: Content
|
||||
|
||||
let results: PageGenerationResults
|
||||
|
||||
let language: ContentLanguage
|
||||
|
||||
private let swift = SyntaxHighlighter(format: HTMLOutputFormat())
|
||||
|
||||
init(content: Content, results: PageGenerationResults, language: ContentLanguage) {
|
||||
self.content = content
|
||||
self.results = results
|
||||
self.language = language
|
||||
}
|
||||
|
||||
func process(_ markdown: Substring) -> String {
|
||||
// Highlight swift code using Splash
|
||||
let code = markdown.between("```swift", and: "```").trimmed
|
||||
return "<pre><code>" + swift.highlight(code) + "</pre></code>"
|
||||
}
|
||||
}
|
@ -31,6 +31,9 @@ final class GenerationResults: ObservableObject {
|
||||
@Published
|
||||
var invalidCommands: Set<String> = []
|
||||
|
||||
@Published
|
||||
var invalidBlocks: Set<String> = []
|
||||
|
||||
@Published
|
||||
var warnings: Set<String> = []
|
||||
|
||||
@ -105,6 +108,8 @@ final class GenerationResults: ObservableObject {
|
||||
update { self.imagesToGenerate = imagesToGenerate }
|
||||
let invalidCommands = cache.values.map { $0.invalidCommands.map { $0.markdown }}.union()
|
||||
update { self.invalidCommands = invalidCommands }
|
||||
let invalidBlocks = cache.values.map { $0.invalidBlocks.map { $0.markdown }}.union()
|
||||
update { self.invalidBlocks = invalidBlocks }
|
||||
let warnings = cache.values.map { $0.warnings }.union()
|
||||
update { self.warnings = warnings }
|
||||
let unsavedOutputFiles = cache.values.map { $0.unsavedOutputFiles.keys }.union()
|
||||
@ -163,6 +168,10 @@ final class GenerationResults: ObservableObject {
|
||||
update { self.invalidCommands.insert(markdown) }
|
||||
}
|
||||
|
||||
func invalidBlock(_ markdown: String) {
|
||||
update { self.invalidBlocks.insert(markdown) }
|
||||
}
|
||||
|
||||
func warning(_ warning: String) {
|
||||
update { self.warnings.insert(warning) }
|
||||
}
|
||||
|
@ -26,11 +26,11 @@ enum KnownHeaderElement: Int {
|
||||
return HeaderElement.jsModule(file)
|
||||
}
|
||||
case .audioPlayerCss:
|
||||
if let file = content.settings.pages.audioPlayerCssFile {
|
||||
if let file = content.settings.audioPlayer.audioPlayerCssFile {
|
||||
return .css(file: file, order: HeaderElement.audioPlayerCssOrder)
|
||||
}
|
||||
case .audioPlayerJs:
|
||||
if let file = content.settings.pages.audioPlayerJsFile {
|
||||
if let file = content.settings.audioPlayer.audioPlayerJsFile {
|
||||
return .js(file: file, defer: true)
|
||||
}
|
||||
case .imageCompareJs:
|
||||
|
@ -46,7 +46,7 @@ struct AudioPlayerCommandProcessor: CommandProcessor {
|
||||
var amplitude: [AmplitudeSong] = []
|
||||
|
||||
for song in songs {
|
||||
guard let image = content.image(song.cover) else {
|
||||
guard let image = content.file(song.cover) else {
|
||||
results.missing(file: song.cover, containedIn: file)
|
||||
continue
|
||||
}
|
||||
@ -63,7 +63,11 @@ struct AudioPlayerCommandProcessor: CommandProcessor {
|
||||
results.warning("Song '\(song.file)' in file \(fileId) is not an audio file")
|
||||
continue
|
||||
}
|
||||
let coverUrl = image.absoluteUrl
|
||||
|
||||
let coverSize = 2 * content.settings.audioPlayer.playlistCoverImageSize
|
||||
let coverImage = image.imageVersion(width: coverSize, height: coverSize, type: image.type)
|
||||
let coverUrl = coverImage.outputPath
|
||||
results.require(image: coverImage)
|
||||
|
||||
let playlistItem = AudioPlayer.PlaylistItem(
|
||||
index: playlist.count,
|
||||
|
@ -13,7 +13,6 @@ struct ButtonCommandProcessor: CommandProcessor {
|
||||
}
|
||||
|
||||
/**
|
||||
Format: ``
|
||||
Format: ``
|
||||
Types:
|
||||
- Download: `download=<fileId>,<text>,<download-filename?>`
|
||||
|
@ -1,25 +0,0 @@
|
||||
import Splash
|
||||
|
||||
struct PageCodeProcessor {
|
||||
|
||||
private let codeHighlightFooter = "<script>hljs.highlightAll();</script>"
|
||||
|
||||
private let swift = SyntaxHighlighter(format: HTMLOutputFormat())
|
||||
|
||||
let results: PageGenerationResults
|
||||
|
||||
init(results: PageGenerationResults) {
|
||||
self.results = results
|
||||
}
|
||||
|
||||
func process(_ html: String, markdown: Substring) -> String {
|
||||
guard markdown.starts(with: "```swift") else {
|
||||
results.require(header: .codeHightlighting)
|
||||
results.require(footer: codeHighlightFooter)
|
||||
return html // Just use normal code highlighting
|
||||
}
|
||||
// Highlight swift code using Splash
|
||||
let code = markdown.between("```swift", and: "```").trimmed
|
||||
return "<pre><code>" + swift.highlight(code) + "</pre></code>"
|
||||
}
|
||||
}
|
@ -33,7 +33,7 @@ final class PageContentParser {
|
||||
|
||||
private let inlineLink: InlineLinkProcessor
|
||||
|
||||
private let code: PageCodeProcessor
|
||||
private let code: CodeBlockProcessor
|
||||
|
||||
init(content: Content, language: ContentLanguage, results: PageGenerationResults) {
|
||||
self.content = content
|
||||
@ -50,7 +50,7 @@ final class PageContentParser {
|
||||
self.images = .init(content: content, results: results, language: language)
|
||||
|
||||
self.inlineLink = .init(content: content, results: results, language: language)
|
||||
self.code = .init(results: results)
|
||||
self.code = .init(content: content, results: results, language: language)
|
||||
}
|
||||
|
||||
func generatePage(from content: String) -> String {
|
||||
@ -84,21 +84,12 @@ final class PageContentParser {
|
||||
return parts[0] + " id=\"\(id)\">" + parts.dropFirst().joined(separator: ">")
|
||||
}
|
||||
|
||||
private func percentDecoded(_ string: String) -> String {
|
||||
guard let decoded = string.removingPercentEncoding else {
|
||||
print("Invalid string: \(string)")
|
||||
return string
|
||||
}
|
||||
return decoded
|
||||
}
|
||||
|
||||
private func processMarkdownImage(html: String, markdown: Substring) -> String {
|
||||
//
|
||||
let argumentList = percentDecoded(markdown.between(first: "](", andLast: ")"))
|
||||
let argumentList = markdown.between(first: "](", andLast: ")").percentDecoded()
|
||||
let arguments = argumentList.components(separatedBy: ";")
|
||||
|
||||
|
||||
let rawCommand = percentDecoded(markdown.between("![", and: "]").trimmed)
|
||||
let rawCommand = markdown.between("![", and: "]").trimmed.percentDecoded()
|
||||
guard rawCommand != "" else {
|
||||
return images.process(arguments, markdown: markdown)
|
||||
}
|
||||
|
@ -66,7 +66,9 @@ final class PageGenerationResults: ObservableObject {
|
||||
/// The image versions required for this page
|
||||
private(set) var imagesToGenerate: Set<ImageVersion>
|
||||
|
||||
private(set) var invalidCommands: [(command: ShorthandMarkdownKey?, markdown: String)] = []
|
||||
private(set) var invalidCommands: [(command: ShorthandMarkdownKey?, markdown: String)]
|
||||
|
||||
private(set) var invalidBlocks: [(block: ContentBlock?, markdown: String)]
|
||||
|
||||
private(set) var warnings: Set<String>
|
||||
|
||||
@ -94,6 +96,7 @@ final class PageGenerationResults: ObservableObject {
|
||||
requiredFiles = []
|
||||
imagesToGenerate = []
|
||||
invalidCommands = []
|
||||
invalidBlocks = []
|
||||
warnings = []
|
||||
unsavedOutputFiles = [:]
|
||||
pageIsEmpty = false
|
||||
@ -116,6 +119,7 @@ final class PageGenerationResults: ObservableObject {
|
||||
requiredFiles = []
|
||||
imagesToGenerate = []
|
||||
invalidCommands = []
|
||||
invalidBlocks = []
|
||||
warnings = []
|
||||
unsavedOutputFiles = [:]
|
||||
pageIsEmpty = false
|
||||
@ -134,6 +138,12 @@ final class PageGenerationResults: ObservableObject {
|
||||
delegate.invalidCommand(markdown)
|
||||
}
|
||||
|
||||
func invalid(block: ContentBlock?, _ markdown: Substring) {
|
||||
let markdown = String(markdown)
|
||||
invalidBlocks.append((block, markdown))
|
||||
delegate.invalidBlock(markdown)
|
||||
}
|
||||
|
||||
func missing(page: String, source: String) {
|
||||
missingLinkedPages[page, default: []].insert(source)
|
||||
delegate.missing(page: page)
|
||||
|
@ -17,8 +17,16 @@ enum ShorthandMarkdownKey: String {
|
||||
/// Format: ``
|
||||
/**
|
||||
A variable number of buttons for file downloads, external links or other uses
|
||||
|
||||
Format: ``
|
||||
Types:
|
||||
- Download: `download=<fileId>,<text>,<download-filename?>`
|
||||
- External link: `external=<url>,<text>`
|
||||
- Git: `git=<url>,<text>`
|
||||
- Play: `play-circle=<text>,<click-action>`
|
||||
*/
|
||||
case buttons
|
||||
|
||||
/// A box with a title and content
|
||||
|
49
CHDataManagement/Model/Settings/AudioPlayerSettings.swift
Normal file
49
CHDataManagement/Model/Settings/AudioPlayerSettings.swift
Normal file
@ -0,0 +1,49 @@
|
||||
import Foundation
|
||||
|
||||
final class AudioPlayerSettings: ObservableObject {
|
||||
|
||||
@Published
|
||||
var playlistCoverImageSize: Int
|
||||
|
||||
@Published
|
||||
var smallCoverImageSize: Int
|
||||
|
||||
@Published
|
||||
var audioPlayerJsFile: FileResource?
|
||||
|
||||
@Published
|
||||
var audioPlayerCssFile: FileResource?
|
||||
|
||||
init(playlistCoverImageSize: Int,
|
||||
smallCoverImageSize: Int,
|
||||
audioPlayerJsFile: FileResource?,
|
||||
audioPlayerCssFile: FileResource?) {
|
||||
self.playlistCoverImageSize = playlistCoverImageSize
|
||||
self.smallCoverImageSize = smallCoverImageSize
|
||||
self.audioPlayerJsFile = audioPlayerJsFile
|
||||
self.audioPlayerCssFile = audioPlayerCssFile
|
||||
}
|
||||
|
||||
init(file: AudioPlayerSettingsFile, files: [String : FileResource]) {
|
||||
self.playlistCoverImageSize = file.playlistCoverImageSize
|
||||
self.smallCoverImageSize = file.smallCoverImageSize
|
||||
self.audioPlayerJsFile = file.audioPlayerJsFile.map { files[$0] }
|
||||
self.audioPlayerCssFile = file.audioPlayerCssFile.map { files[$0] }
|
||||
}
|
||||
|
||||
var file: AudioPlayerSettingsFile {
|
||||
.init(playlistCoverImageSize: playlistCoverImageSize,
|
||||
smallCoverImageSize: smallCoverImageSize,
|
||||
audioPlayerJsFile: audioPlayerJsFile?.id,
|
||||
audioPlayerCssFile: audioPlayerCssFile?.id)
|
||||
}
|
||||
}
|
||||
|
||||
extension AudioPlayerSettings {
|
||||
|
||||
static let `default`: AudioPlayerSettings = .init(
|
||||
playlistCoverImageSize: 280,
|
||||
smallCoverImageSize: 78,
|
||||
audioPlayerJsFile: nil,
|
||||
audioPlayerCssFile: nil)
|
||||
}
|
@ -17,12 +17,6 @@ final class PageSettings: ObservableObject {
|
||||
@Published
|
||||
var codeHighlightingJsFile: FileResource?
|
||||
|
||||
@Published
|
||||
var audioPlayerJsFile: FileResource?
|
||||
|
||||
@Published
|
||||
var audioPlayerCssFile: FileResource?
|
||||
|
||||
@Published
|
||||
var modelViewerJsFile: FileResource?
|
||||
|
||||
@ -38,8 +32,6 @@ final class PageSettings: ObservableObject {
|
||||
self.pageLinkImageSize = file.pageLinkImageSize
|
||||
self.defaultCssFile = file.defaultCssFile.map { files[$0] }
|
||||
self.codeHighlightingJsFile = file.codeHighlightingJsFile.map { files[$0] }
|
||||
self.audioPlayerJsFile = file.audioPlayerJsFile.map { files[$0] }
|
||||
self.audioPlayerCssFile = file.audioPlayerCssFile.map { files[$0] }
|
||||
self.modelViewerJsFile = file.modelViewerJsFile.map { files[$0] }
|
||||
self.imageCompareCssFile = file.imageCompareCssFile.map { files[$0] }
|
||||
self.imageCompareJsFile = file.imageCompareJsFile.map { files[$0] }
|
||||
@ -51,8 +43,6 @@ final class PageSettings: ObservableObject {
|
||||
pageLinkImageSize: pageLinkImageSize,
|
||||
defaultCssFile: defaultCssFile?.id,
|
||||
codeHighlightingJsFile: codeHighlightingJsFile?.id,
|
||||
audioPlayerJsFile: audioPlayerJsFile?.id,
|
||||
audioPlayerCssFile: audioPlayerCssFile?.id,
|
||||
modelViewerJsFile: modelViewerJsFile?.id,
|
||||
imageCompareJsFile: imageCompareJsFile?.id,
|
||||
imageCompareCssFile: imageCompareCssFile?.id)
|
||||
|
@ -15,17 +15,28 @@ final class Settings: ObservableObject {
|
||||
@Published
|
||||
var pages: PageSettings
|
||||
|
||||
@Published
|
||||
var audioPlayer: AudioPlayerSettings
|
||||
|
||||
@Published
|
||||
var german: LocalizedPostSettings
|
||||
|
||||
@Published
|
||||
var english: LocalizedPostSettings
|
||||
|
||||
init(paths: PathSettings, navigation: NavigationSettings, posts: PostSettings, pages: PageSettings, german: LocalizedPostSettings, english: LocalizedPostSettings) {
|
||||
|
||||
init(paths: PathSettings,
|
||||
navigation: NavigationSettings,
|
||||
posts: PostSettings,
|
||||
pages: PageSettings,
|
||||
audioPlayer: AudioPlayerSettings,
|
||||
german: LocalizedPostSettings,
|
||||
english: LocalizedPostSettings) {
|
||||
self.paths = paths
|
||||
self.navigation = navigation
|
||||
self.posts = posts
|
||||
self.pages = pages
|
||||
self.audioPlayer = audioPlayer
|
||||
self.german = german
|
||||
self.english = english
|
||||
}
|
||||
@ -43,6 +54,7 @@ final class Settings: ObservableObject {
|
||||
self.posts = PostSettings(file: file.posts, files: files)
|
||||
self.pages = PageSettings(file: file.pages, files: files)
|
||||
self.paths = PathSettings(file: file.paths)
|
||||
self.audioPlayer = .init(file: file.audioPlayer, files: files)
|
||||
|
||||
self.german = .init(file: file.german)
|
||||
self.english = .init(file: file.english)
|
||||
@ -54,6 +66,7 @@ final class Settings: ObservableObject {
|
||||
navigation: navigation.file,
|
||||
posts: posts.file,
|
||||
pages: pages.file,
|
||||
audioPlayer: audioPlayer.file,
|
||||
german: german.file,
|
||||
english: english.file)
|
||||
}
|
||||
@ -66,6 +79,7 @@ extension Settings {
|
||||
navigation: .default,
|
||||
posts: .default,
|
||||
pages: .default,
|
||||
audioPlayer: .default,
|
||||
german: .german,
|
||||
english: .english)
|
||||
}
|
||||
|
@ -0,0 +1,45 @@
|
||||
|
||||
struct SingleFilePlayer: HtmlProducer {
|
||||
|
||||
func populate(_ result: inout String) {
|
||||
result += "<div class='song-container'>"
|
||||
result += "<div class='song-player'>"
|
||||
result += "<div class='song-cover-container amplitude-play-pause amplitude-paused'>"
|
||||
result += "<div class='song-player-button'>"
|
||||
result += "<svg class='play-icon'><use href='#\(AudioPlayerPlayIcon.name)'></use></svg>"
|
||||
result += "<svg class='pause-icon'><use href='#\(AudioPlayerPauseIcon.name)'></use></svg>"
|
||||
result += "</div>"
|
||||
result += "<img data-amplitude-song-info='cover_art_url' class='article-thumbnail-image' width='78' height='78'></img>"
|
||||
result += "</div>"
|
||||
result += "<div class='song-info'>"
|
||||
result += "<div class='song-timeline'>"
|
||||
result += "<span class='amplitude-current-time song-current-time'></span>"
|
||||
result += "<span class='amplitude-duration-time song-duration-time'></span>"
|
||||
result += "</div>"
|
||||
result += "<div class='song-progress'>"
|
||||
result += "<input type='range' class='amplitude-song-slider song-slider'>"
|
||||
result += "<progress id='song-played-progress' class=' amplitude-song-played-progress song-progress-bar'></progress>"
|
||||
result += "</div>"
|
||||
result += "<span data-amplitude-song-info='name'></span>"
|
||||
result += "<span data-amplitude-song-info='album' style='font-weight: 500;'></span>"
|
||||
result += "</div></div></div>"
|
||||
}
|
||||
|
||||
static func footer(name: String, artist: String, album: String, url: String, cover: String) -> String {
|
||||
"""
|
||||
<script>
|
||||
window.onload = () => {
|
||||
Amplitude.init({
|
||||
"songs": [{
|
||||
"name": "\(name)",
|
||||
"artist": "\(artist)",
|
||||
"album": "\(album)",
|
||||
"url": "\(url)",
|
||||
"cover_art_url": "\(cover)"
|
||||
}]
|
||||
});
|
||||
};
|
||||
</script>
|
||||
"""
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
|
||||
struct AudioPlayerSettingsFile: Codable {
|
||||
|
||||
let playlistCoverImageSize: Int
|
||||
|
||||
let smallCoverImageSize: Int
|
||||
|
||||
let audioPlayerJsFile: String?
|
||||
|
||||
let audioPlayerCssFile: String?
|
||||
}
|
@ -11,10 +11,6 @@ struct PageSettingsFile {
|
||||
|
||||
let codeHighlightingJsFile: String?
|
||||
|
||||
let audioPlayerJsFile: String?
|
||||
|
||||
let audioPlayerCssFile: String?
|
||||
|
||||
let modelViewerJsFile: String?
|
||||
|
||||
let imageCompareJsFile: String?
|
||||
@ -34,8 +30,6 @@ extension PageSettingsFile {
|
||||
pageLinkImageSize: 180,
|
||||
defaultCssFile: nil,
|
||||
codeHighlightingJsFile: nil,
|
||||
audioPlayerJsFile: nil,
|
||||
audioPlayerCssFile: nil,
|
||||
modelViewerJsFile: nil,
|
||||
imageCompareJsFile: nil,
|
||||
imageCompareCssFile: nil)
|
||||
|
@ -11,6 +11,8 @@ struct SettingsFile {
|
||||
|
||||
let pages: PageSettingsFile
|
||||
|
||||
let audioPlayer: AudioPlayerSettingsFile
|
||||
|
||||
let german: LocalizedPostSettingsFile
|
||||
|
||||
let english: LocalizedPostSettingsFile
|
||||
@ -26,6 +28,7 @@ extension SettingsFile {
|
||||
navigation: .default,
|
||||
posts: .default,
|
||||
pages: .default,
|
||||
audioPlayer: AudioPlayerSettings.default.file,
|
||||
german: .default,
|
||||
english: .default
|
||||
)
|
||||
|
@ -0,0 +1,43 @@
|
||||
import SwiftUI
|
||||
|
||||
struct AudioSettingsDetailView: View {
|
||||
|
||||
@Environment(\.language)
|
||||
private var language
|
||||
|
||||
@ObservedObject
|
||||
var audioPlayer: AudioPlayerSettings
|
||||
|
||||
var body: some View {
|
||||
ScrollView {
|
||||
VStack(alignment: .leading) {
|
||||
DetailTitle(
|
||||
title: "Audio Player Settings",
|
||||
text: "Configure the files and settings for the audio player components")
|
||||
|
||||
IntegerPropertyView(
|
||||
title: "Playlist Cover Image Size",
|
||||
value: $audioPlayer.playlistCoverImageSize,
|
||||
footer: "The maximum size of the album cover image in a playlist audio player (in pixels)")
|
||||
|
||||
IntegerPropertyView(
|
||||
title: "Small Album Cover Image Size",
|
||||
value: $audioPlayer.smallCoverImageSize,
|
||||
footer: "The maximum size of the album cover image in a single file audio player (in pixels)")
|
||||
|
||||
FilePropertyView(
|
||||
title: "Audio Player CSS File",
|
||||
footer: "The CSS file to provide the style for the audio player",
|
||||
selectedFile: $audioPlayer.audioPlayerCssFile,
|
||||
allowedType: .asset)
|
||||
|
||||
FilePropertyView(
|
||||
title: "Audio Player JavaScript File",
|
||||
footer: "The CSS file to provide the functionality for the audio player",
|
||||
selectedFile: $audioPlayer.audioPlayerJsFile,
|
||||
allowedType: .asset)
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
}
|
@ -17,10 +17,10 @@ struct GenerationContentView: View {
|
||||
|
||||
var body: some View {
|
||||
switch selectedSection {
|
||||
case .folders, .navigationBar, .postFeed, .tagOverview:
|
||||
generationView
|
||||
case .pages:
|
||||
PageSettingsContentView()
|
||||
default:
|
||||
generationView
|
||||
}
|
||||
}
|
||||
|
||||
@ -96,6 +96,11 @@ struct GenerationContentView: View {
|
||||
Text(markdown)
|
||||
}
|
||||
}
|
||||
Section("Invalid blocks") {
|
||||
ForEach(content.results.invalidBlocks.sorted(), id: \.self) { markdown in
|
||||
Text(markdown)
|
||||
}
|
||||
}
|
||||
Section("Warnings") {
|
||||
ForEach(content.results.warnings.sorted(), id: \.self) { warning in
|
||||
Text(warning)
|
||||
|
@ -4,6 +4,9 @@ struct GenerationDetailView: View {
|
||||
|
||||
let section: SettingsSection
|
||||
|
||||
@EnvironmentObject
|
||||
private var content: Content
|
||||
|
||||
var body: some View {
|
||||
switch section {
|
||||
case .folders:
|
||||
@ -16,6 +19,8 @@ struct GenerationDetailView: View {
|
||||
PageSettingsDetailView()
|
||||
case .tagOverview:
|
||||
TagOverviewDetailView()
|
||||
case .audioPlayer:
|
||||
AudioSettingsDetailView(audioPlayer: content.settings.audioPlayer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -42,18 +42,6 @@ struct PageSettingsDetailView: View {
|
||||
selectedFile: $content.settings.pages.codeHighlightingJsFile,
|
||||
allowedType: .asset)
|
||||
|
||||
FilePropertyView(
|
||||
title: "Audio Player CSS File",
|
||||
footer: "The CSS file to provide the style for the audio player",
|
||||
selectedFile: $content.settings.pages.audioPlayerCssFile,
|
||||
allowedType: .asset)
|
||||
|
||||
FilePropertyView(
|
||||
title: "Audio Player JavaScript File",
|
||||
footer: "The CSS file to provide the functionality for the audio player",
|
||||
selectedFile: $content.settings.pages.audioPlayerJsFile,
|
||||
allowedType: .asset)
|
||||
|
||||
FilePropertyView(
|
||||
title: "3D Model Viewer File",
|
||||
footer: "The JavaScript file to provide the functionality for the 3D model viewer",
|
||||
|
@ -12,6 +12,8 @@ enum SettingsSection: String {
|
||||
|
||||
case tagOverview = "Tag Overview"
|
||||
|
||||
case audioPlayer = "Audio Player"
|
||||
|
||||
}
|
||||
|
||||
extension SettingsSection {
|
||||
@ -23,6 +25,7 @@ extension SettingsSection {
|
||||
case .postFeed: return .rectangleGrid1x2
|
||||
case .pages: return .docRichtext
|
||||
case .tagOverview: return .tag
|
||||
case .audioPlayer: return .waveform
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user