diff --git a/CHDataManagement.xcodeproj/project.pbxproj b/CHDataManagement.xcodeproj/project.pbxproj index 79eac55..b81e7e2 100644 --- a/CHDataManagement.xcodeproj/project.pbxproj +++ b/CHDataManagement.xcodeproj/project.pbxproj @@ -212,6 +212,7 @@ E2FE0F1B2D274FDF002963B7 /* LinkPreviewItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F1A2D274FDA002963B7 /* LinkPreviewItem.swift */; }; E2FE0F1E2D281AE1002963B7 /* TagOverviewGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F1D2D281ACE002963B7 /* TagOverviewGenerator.swift */; }; E2FE0F202D29A70E002963B7 /* Array+Remove.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F1F2D29A709002963B7 /* Array+Remove.swift */; }; + E2FE0F222D2A84A0002963B7 /* VideoCommandProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2FE0F212D2A849B002963B7 /* VideoCommandProcessor.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -415,6 +416,7 @@ E2FE0F1A2D274FDA002963B7 /* LinkPreviewItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkPreviewItem.swift; sourceTree = ""; }; E2FE0F1D2D281ACE002963B7 /* TagOverviewGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagOverviewGenerator.swift; sourceTree = ""; }; E2FE0F1F2D29A709002963B7 /* Array+Remove.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array+Remove.swift"; sourceTree = ""; }; + E2FE0F212D2A849B002963B7 /* VideoCommandProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoCommandProcessor.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -578,6 +580,7 @@ E29D31B62D0DAC030051B7F4 /* Page Content */ = { isa = PBXGroup; children = ( + E2FE0F212D2A849B002963B7 /* VideoCommandProcessor.swift */, E2FE0F142D269188002963B7 /* PageHtmlProcessor.swift */, E2FE0F102D268E78002963B7 /* PageCodeProcessor.swift */, E2FE0F0E2D268D4B002963B7 /* BoxCommandProcessor.swift */, @@ -1140,6 +1143,7 @@ E229903C2D0F8A7B009F8D77 /* OptionalTextFieldPropertyView.swift in Sources */, E25DA5932D023B3C00AEF16D /* PageSettingsFile.swift in Sources */, E29D31222D0363FD0051B7F4 /* ContentButtons.swift in Sources */, + E2FE0F222D2A84A0002963B7 /* VideoCommandProcessor.swift in Sources */, E2FE0F192D2723E3002963B7 /* ImageSet.swift in Sources */, E2A21C362CB9A3D70060935B /* PathSettingsView.swift in Sources */, E29D31362D0435430051B7F4 /* TabSelection.swift in Sources */, diff --git a/CHDataManagement/Generator/ImageVersion.swift b/CHDataManagement/Generator/ImageVersion.swift index cced42d..daac315 100644 --- a/CHDataManagement/Generator/ImageVersion.swift +++ b/CHDataManagement/Generator/ImageVersion.swift @@ -37,7 +37,7 @@ struct ImageVersion { "\(maximumWidth)-\(maximumHeight)-\(type.fileExtension!)" } - /// The path of the generated image version in the output folder (without leading slash) + /// The path of the generated image version in the output folder (including leading slash) var outputPath: String { image.outputPath(width: maximumWidth, height: maximumHeight, type: type) } diff --git a/CHDataManagement/Generator/Page Content/VideoCommandProcessor.swift b/CHDataManagement/Generator/Page Content/VideoCommandProcessor.swift new file mode 100644 index 0000000..bedaaea --- /dev/null +++ b/CHDataManagement/Generator/Page Content/VideoCommandProcessor.swift @@ -0,0 +1,79 @@ + +struct VideoCommandProcessor: CommandProcessor { + + let commandType: ShorthandMarkdownKey = .video + + let content: Content + + let results: PageGenerationResults + + init(content: Content, results: PageGenerationResults) { + self.content = content + self.results = results + } + + /** + Format: `![video](;]` + */ + func process(_ arguments: [String], markdown: Substring) -> String { + guard arguments.count >= 1 else { + results.invalid(command: .video, markdown) + return "" + } + let fileId = arguments[0].trimmed + + let options = arguments.dropFirst().compactMap { convertVideoOption($0, markdown: markdown) } + + guard let file = content.file(fileId) else { + results.missing(file: fileId, source: "Video command") + return "" + } + #warning("Create/specify video alternatives") + results.require(file: file) + + guard let videoType = file.type.htmlType else { + results.invalid(command: .video, markdown) + return "" + } + + return ContentPageVideo( + filePath: file.absoluteUrl, + videoType: videoType, + options: options) + .content + } + + private func convertVideoOption(_ videoOption: String, markdown: Substring) -> VideoOption? { + guard let optionText = videoOption.trimmed.nonEmpty else { + return nil + } + guard let option = VideoOption(rawValue: optionText) else { + results.invalid(command: .video, markdown) + return nil + } + switch option { + case .poster(let imageId): + if let image = content.image(imageId) { + let width = 2 * content.settings.pages.contentWidth + let version = image.imageVersion(width: width, height: width, type: .jpg) + results.require(image: version) + return .poster(image: version.outputPath) + } else { + results.missing(file: imageId, source: "Video command poster") + return nil // Image file not present, so skip the option + } + case .src(let videoId): + if let video = content.video(videoId) { + results.used(file: video) + let link = video.absoluteUrl + return .src(link) + } else { + results.missing(file: videoId, source: "Video command source") + return nil // Video file not present, so skip the option + } + default: + return option + } + } + +} diff --git a/CHDataManagement/Generator/PageContentGenerator.swift b/CHDataManagement/Generator/PageContentGenerator.swift index 46b532a..b9f0a94 100644 --- a/CHDataManagement/Generator/PageContentGenerator.swift +++ b/CHDataManagement/Generator/PageContentGenerator.swift @@ -23,6 +23,8 @@ final class PageContentParser { private let html: PageHtmlProcessor + private let video: VideoCommandProcessor + // MARK: Other handlers private let inlineLink: InlineLinkProcessor @@ -47,6 +49,7 @@ final class PageContentParser { self.icons = .init(content: content, results: results) self.box = .init(content: content, results: results) self.html = .init(content: content, results: results) + self.video = .init(content: content, results: results) self.inlineLink = .init(content: content, results: results, language: language) self.code = .init(results: results) @@ -116,7 +119,7 @@ final class PageContentParser { case .buttons: return buttonHandler.process(arguments, markdown: markdown) case .video: - return handleVideo(arguments, markdown: markdown) + return video.process(arguments, markdown: markdown) case .pageLink: return handlePageLink(arguments, markdown: markdown) case .includedHtml: @@ -173,70 +176,6 @@ final class PageContentParser { caption: caption).content } - /** - Format: `![video](;]` - */ - private func handleVideo(_ arguments: [String], markdown: Substring) -> String { - guard arguments.count >= 1 else { - results.invalid(command: .video, markdown) - return "" - } - let fileId = arguments[0].trimmed - - let options = arguments.dropFirst().compactMap { convertVideoOption($0, markdown: markdown) } - - guard let file = content.file(fileId) else { - results.missing(file: fileId, source: "Video command") - return "" - } - #warning("Create/specify video alternatives") - results.require(file: file) - - guard let videoType = file.type.htmlType else { - results.invalid(command: .video, markdown) - return "" - } - - return ContentPageVideo( - filePath: file.absoluteUrl, - videoType: videoType, - options: options) - .content - } - - private func convertVideoOption(_ videoOption: String, markdown: Substring) -> VideoOption? { - guard let optionText = videoOption.trimmed.nonEmpty else { - return nil - } - guard let option = VideoOption(rawValue: optionText) else { - results.invalid(command: .video, markdown) - return nil - } - switch option { - case .poster(let imageId): - if let image = content.image(imageId) { - let width = 2*thumbnailWidth - let version = image.imageVersion(width: width, height: width, type: .jpg) - results.require(image: version) - return .poster(image: version.outputPath) - } else { - results.missing(file: imageId, source: "Video command poster") - return nil // Image file not present, so skip the option - } - case .src(let videoId): - if let video = content.video(videoId) { - results.used(file: video) - let link = video.absoluteUrl - return .src(link) - } else { - results.missing(file: videoId, source: "Video command source") - return nil // Video file not present, so skip the option - } - default: - return option - } - } - /** Format: `![page]()` */ diff --git a/CHDataManagement/Generator/VideoOption.swift b/CHDataManagement/Generator/VideoOption.swift index f950a9d..9e329a8 100644 --- a/CHDataManagement/Generator/VideoOption.swift +++ b/CHDataManagement/Generator/VideoOption.swift @@ -96,7 +96,7 @@ enum VideoOption { case .height(let height): return "height='\(height)'" case .width(let width): return "width='\(width)'" case .preload(let option): return "preload='\(option)'" - case .poster(let image): return "poster='/\(image)'" + case .poster(let image): return "poster='\(image)'" case .src(let url): return "src='\(url)'" } }