diff --git a/CHDataManagement.xcodeproj/project.pbxproj b/CHDataManagement.xcodeproj/project.pbxproj index e1e7328..aeeffa9 100644 --- a/CHDataManagement.xcodeproj/project.pbxproj +++ b/CHDataManagement.xcodeproj/project.pbxproj @@ -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 = ""; }; E2FE0F0C2D268A09002963B7 /* PostListPageGeneratorSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostListPageGeneratorSource.swift; sourceTree = ""; }; E2FE0F0E2D268D4B002963B7 /* BoxCommandProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoxCommandProcessor.swift; sourceTree = ""; }; - E2FE0F102D268E78002963B7 /* PageCodeProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageCodeProcessor.swift; sourceTree = ""; }; + E2FE0F102D268E78002963B7 /* CodeBlockProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodeBlockProcessor.swift; sourceTree = ""; }; E2FE0F142D269188002963B7 /* PageHtmlProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageHtmlProcessor.swift; sourceTree = ""; }; E2FE0F162D2698D5002963B7 /* LocalizedPageId.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizedPageId.swift; sourceTree = ""; }; E2FE0F182D2723E3002963B7 /* ImageSet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageSet.swift; sourceTree = ""; }; @@ -429,6 +437,14 @@ E2FE0F292D2AFBE3002963B7 /* ImageCompareIcons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCompareIcons.swift; sourceTree = ""; }; E2FE0F2B2D2B1196002963B7 /* ImageCommandProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageCommandProcessor.swift; sourceTree = ""; }; E2FE0F302D2B1952002963B7 /* PartialSvgImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PartialSvgImage.swift; sourceTree = ""; }; + E2FE0F322D2B265F002963B7 /* AudioBlockProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioBlockProcessor.swift; sourceTree = ""; }; + E2FE0F352D2B27F6002963B7 /* BlockProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockProcessor.swift; sourceTree = ""; }; + E2FE0F372D2B32ED002963B7 /* SingleFilePlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingleFilePlayer.swift; sourceTree = ""; }; + E2FE0F392D2B3E4E002963B7 /* AudioPlayerSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioPlayerSettings.swift; sourceTree = ""; }; + E2FE0F3B2D2B3F42002963B7 /* AudioPlayerSettingsFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioPlayerSettingsFile.swift; sourceTree = ""; }; + E2FE0F3D2D2B4225002963B7 /* AudioSettingsDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudioSettingsDetailView.swift; sourceTree = ""; }; + E2FE0F3F2D2B45CD002963B7 /* SwiftProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftProcessor.swift; sourceTree = ""; }; + E2FE0F412D2B480B002963B7 /* OtherCodeProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OtherCodeProcessor.swift; sourceTree = ""; }; /* 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 = ""; @@ -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 = ""; }; + E2FE0F342D2B27E6002963B7 /* Blocks */ = { + isa = PBXGroup; + children = ( + E2FE0F412D2B480B002963B7 /* OtherCodeProcessor.swift */, + E2FE0F3F2D2B45CD002963B7 /* SwiftProcessor.swift */, + E2FE0F352D2B27F6002963B7 /* BlockProcessor.swift */, + E2FE0F322D2B265F002963B7 /* AudioBlockProcessor.swift */, + E2FE0F102D268E78002963B7 /* CodeBlockProcessor.swift */, + ); + path = Blocks; + sourceTree = ""; + }; /* 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 */, diff --git a/CHDataManagement/Extensions/String+Extensions.swift b/CHDataManagement/Extensions/String+Extensions.swift index a56b98a..4f58134 100644 --- a/CHDataManagement/Extensions/String+Extensions.swift +++ b/CHDataManagement/Extensions/String+Extensions.swift @@ -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)) + } } diff --git a/CHDataManagement/Generator/Blocks/AudioBlockProcessor.swift b/CHDataManagement/Generator/Blocks/AudioBlockProcessor.swift new file mode 100644 index 0000000..bd994b4 --- /dev/null +++ b/CHDataManagement/Generator/Blocks/AudioBlockProcessor.swift @@ -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 + } +} diff --git a/CHDataManagement/Generator/Blocks/BlockProcessor.swift b/CHDataManagement/Generator/Blocks/BlockProcessor.swift new file mode 100644 index 0000000..e005f63 --- /dev/null +++ b/CHDataManagement/Generator/Blocks/BlockProcessor.swift @@ -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) + } +} diff --git a/CHDataManagement/Generator/Blocks/CodeBlockProcessor.swift b/CHDataManagement/Generator/Blocks/CodeBlockProcessor.swift new file mode 100644 index 0000000..018b94f --- /dev/null +++ b/CHDataManagement/Generator/Blocks/CodeBlockProcessor.swift @@ -0,0 +1,33 @@ + +struct CodeBlockProcessor { + + private let codeHighlightFooter = "" + + 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) + } +} diff --git a/CHDataManagement/Generator/Blocks/OtherCodeProcessor.swift b/CHDataManagement/Generator/Blocks/OtherCodeProcessor.swift new file mode 100644 index 0000000..dc47131 --- /dev/null +++ b/CHDataManagement/Generator/Blocks/OtherCodeProcessor.swift @@ -0,0 +1,17 @@ + +struct OtherCodeProcessor { + + private let codeHighlightFooter = "" + + 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 + } +} diff --git a/CHDataManagement/Generator/Blocks/SwiftProcessor.swift b/CHDataManagement/Generator/Blocks/SwiftProcessor.swift new file mode 100644 index 0000000..0b1b8b4 --- /dev/null +++ b/CHDataManagement/Generator/Blocks/SwiftProcessor.swift @@ -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 "
" + swift.highlight(code) + "
" + } +} diff --git a/CHDataManagement/Generator/GenerationResults.swift b/CHDataManagement/Generator/GenerationResults.swift index 3631a13..ecfca78 100644 --- a/CHDataManagement/Generator/GenerationResults.swift +++ b/CHDataManagement/Generator/GenerationResults.swift @@ -31,6 +31,9 @@ final class GenerationResults: ObservableObject { @Published var invalidCommands: Set = [] + @Published + var invalidBlocks: Set = [] + @Published var warnings: Set = [] @@ -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) } } diff --git a/CHDataManagement/Generator/KnownHeaderElement.swift b/CHDataManagement/Generator/KnownHeaderElement.swift index 45ede26..8833a39 100644 --- a/CHDataManagement/Generator/KnownHeaderElement.swift +++ b/CHDataManagement/Generator/KnownHeaderElement.swift @@ -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: diff --git a/CHDataManagement/Generator/Page Content/AudioPlayerCommand.swift b/CHDataManagement/Generator/Page Content/AudioPlayerCommand.swift index eef3b96..95d2d01 100644 --- a/CHDataManagement/Generator/Page Content/AudioPlayerCommand.swift +++ b/CHDataManagement/Generator/Page Content/AudioPlayerCommand.swift @@ -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, diff --git a/CHDataManagement/Generator/Page Content/ButtonCommand.swift b/CHDataManagement/Generator/Page Content/ButtonCommand.swift index c2ca57d..1f3dec2 100644 --- a/CHDataManagement/Generator/Page Content/ButtonCommand.swift +++ b/CHDataManagement/Generator/Page Content/ButtonCommand.swift @@ -13,7 +13,6 @@ struct ButtonCommandProcessor: CommandProcessor { } /** - Format: `![buttons](<,,;...)` Format: `![buttons](type=;...)` Types: - Download: `download=,,` diff --git a/CHDataManagement/Generator/Page Content/PageCodeProcessor.swift b/CHDataManagement/Generator/Page Content/PageCodeProcessor.swift deleted file mode 100644 index 049d94f..0000000 --- a/CHDataManagement/Generator/Page Content/PageCodeProcessor.swift +++ /dev/null @@ -1,25 +0,0 @@ -import Splash - -struct PageCodeProcessor { - - private let codeHighlightFooter = "" - - 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 "
" + swift.highlight(code) + "
" - } -} diff --git a/CHDataManagement/Generator/PageContentGenerator.swift b/CHDataManagement/Generator/PageContentGenerator.swift index 012889e..b297bbe 100644 --- a/CHDataManagement/Generator/PageContentGenerator.swift +++ b/CHDataManagement/Generator/PageContentGenerator.swift @@ -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) } diff --git a/CHDataManagement/Generator/PageGenerationResults.swift b/CHDataManagement/Generator/PageGenerationResults.swift index c275059..2d68bae 100644 --- a/CHDataManagement/Generator/PageGenerationResults.swift +++ b/CHDataManagement/Generator/PageGenerationResults.swift @@ -66,7 +66,9 @@ final class PageGenerationResults: ObservableObject { /// The image versions required for this page private(set) var imagesToGenerate: Set - 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 @@ -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) diff --git a/CHDataManagement/Generator/ShorthandMarkdownKey.swift b/CHDataManagement/Generator/ShorthandMarkdownKey.swift index 29e120d..1241dac 100644 --- a/CHDataManagement/Generator/ShorthandMarkdownKey.swift +++ b/CHDataManagement/Generator/ShorthandMarkdownKey.swift @@ -17,8 +17,16 @@ enum ShorthandMarkdownKey: String { /// Format: `![video](;]` case video - /// A variable number of download buttons for file downloads - /// Format: `[buttons](type=<,,;...)` + /** + A variable number of buttons for file downloads, external links or other uses + + Format: `![buttons](type=;...)` + Types: + - Download: `download=,,` + - External link: `external=,` + - Git: `git=,` + - Play: `play-circle=,` + */ case buttons /// A box with a title and content diff --git a/CHDataManagement/Model/Settings/AudioPlayerSettings.swift b/CHDataManagement/Model/Settings/AudioPlayerSettings.swift new file mode 100644 index 0000000..ac2ee27 --- /dev/null +++ b/CHDataManagement/Model/Settings/AudioPlayerSettings.swift @@ -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) +} diff --git a/CHDataManagement/Model/Settings/PageSettings.swift b/CHDataManagement/Model/Settings/PageSettings.swift index 1654c8b..ea536be 100644 --- a/CHDataManagement/Model/Settings/PageSettings.swift +++ b/CHDataManagement/Model/Settings/PageSettings.swift @@ -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) diff --git a/CHDataManagement/Model/Settings/Settings.swift b/CHDataManagement/Model/Settings/Settings.swift index 12fbd18..9ac1161 100644 --- a/CHDataManagement/Model/Settings/Settings.swift +++ b/CHDataManagement/Model/Settings/Settings.swift @@ -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) } diff --git a/CHDataManagement/Page Elements/ContentElements/AudioPlayer/SingleFilePlayer.swift b/CHDataManagement/Page Elements/ContentElements/AudioPlayer/SingleFilePlayer.swift new file mode 100644 index 0000000..5801bea --- /dev/null +++ b/CHDataManagement/Page Elements/ContentElements/AudioPlayer/SingleFilePlayer.swift @@ -0,0 +1,45 @@ + +struct SingleFilePlayer: HtmlProducer { + + func populate(_ result: inout String) { + result += "
" + result += "
" + result += "
" + result += "
" + result += "" + result += "" + result += "
" + result += "" + result += "
" + result += "
" + result += "
" + result += "" + result += "" + result += "
" + result += "
" + result += "" + result += "" + result += "
" + result += "" + result += "" + result += "
" + } + + static func footer(name: String, artist: String, album: String, url: String, cover: String) -> String { + """ + + """ + } +} diff --git a/CHDataManagement/Storage/Model/Settings/AudioPlayerSettingsFile.swift b/CHDataManagement/Storage/Model/Settings/AudioPlayerSettingsFile.swift new file mode 100644 index 0000000..41cf55c --- /dev/null +++ b/CHDataManagement/Storage/Model/Settings/AudioPlayerSettingsFile.swift @@ -0,0 +1,11 @@ + +struct AudioPlayerSettingsFile: Codable { + + let playlistCoverImageSize: Int + + let smallCoverImageSize: Int + + let audioPlayerJsFile: String? + + let audioPlayerCssFile: String? +} diff --git a/CHDataManagement/Storage/Model/Settings/PageSettingsFile.swift b/CHDataManagement/Storage/Model/Settings/PageSettingsFile.swift index bc61a86..5a10b28 100644 --- a/CHDataManagement/Storage/Model/Settings/PageSettingsFile.swift +++ b/CHDataManagement/Storage/Model/Settings/PageSettingsFile.swift @@ -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) diff --git a/CHDataManagement/Storage/Model/Settings/SettingsFile.swift b/CHDataManagement/Storage/Model/Settings/SettingsFile.swift index 4ce17bf..8f746e9 100644 --- a/CHDataManagement/Storage/Model/Settings/SettingsFile.swift +++ b/CHDataManagement/Storage/Model/Settings/SettingsFile.swift @@ -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 ) diff --git a/CHDataManagement/Views/Settings/AudioSettingsDetailView.swift b/CHDataManagement/Views/Settings/AudioSettingsDetailView.swift new file mode 100644 index 0000000..f11dd23 --- /dev/null +++ b/CHDataManagement/Views/Settings/AudioSettingsDetailView.swift @@ -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() + } + } +} diff --git a/CHDataManagement/Views/Settings/Content/GenerationContentView.swift b/CHDataManagement/Views/Settings/Content/GenerationContentView.swift index 7f5d415..cf47d1e 100644 --- a/CHDataManagement/Views/Settings/Content/GenerationContentView.swift +++ b/CHDataManagement/Views/Settings/Content/GenerationContentView.swift @@ -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) diff --git a/CHDataManagement/Views/Settings/GenerationDetailView.swift b/CHDataManagement/Views/Settings/GenerationDetailView.swift index 42259e0..670559d 100644 --- a/CHDataManagement/Views/Settings/GenerationDetailView.swift +++ b/CHDataManagement/Views/Settings/GenerationDetailView.swift @@ -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) } } } diff --git a/CHDataManagement/Views/Settings/PageSettingsDetailView.swift b/CHDataManagement/Views/Settings/PageSettingsDetailView.swift index fef6ce6..476a38b 100644 --- a/CHDataManagement/Views/Settings/PageSettingsDetailView.swift +++ b/CHDataManagement/Views/Settings/PageSettingsDetailView.swift @@ -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", diff --git a/CHDataManagement/Views/Settings/SettingsSection.swift b/CHDataManagement/Views/Settings/SettingsSection.swift index a8e314e..4198ef7 100644 --- a/CHDataManagement/Views/Settings/SettingsSection.swift +++ b/CHDataManagement/Views/Settings/SettingsSection.swift @@ -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 } } }