struct ButtonsBlock: BlockLineProcessor { static let blockId: ContentBlock = .buttons let content: Content let results: PageGenerationResults let language: ContentLanguage private let buttons: ButtonBlock init(content: Content, results: PageGenerationResults, language: ContentLanguage) { self.content = content self.results = results self.language = language self.buttons = .init(content: content, results: results, language: language) } func process(_ lines: [String], markdown: Substring) -> String { let buttons = lines.split(separator: "").compactMap { buttonLines in makeButton(buttonLines, markdown: markdown) } return ContentButtons(items: buttons).content } func makeButton(_ lines: ArraySlice, markdown: Substring) -> ContentButtons.Item? { let arguments: [ButtonBlock.Key : String] = lines.reduce(into: [:]) { dict, line in guard line.trimmed != "" else { return } let (rawKey, rawValue) = line.splitAtFirst(":") guard let key = ButtonBlock.Key(rawValue: rawKey.trimmed) else { print("Invalid key \(rawKey)") invalid(markdown) return } dict[key] = rawValue.trimmed } return self.buttons.process(arguments, markdown: markdown) } } struct ButtonBlock: KeyedBlockProcessor { enum Key: String, Equatable { case icon case file case text case name case url case event } static let blockId: ContentBlock = .button 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 button = process(arguments, markdown: markdown) else { return "" } return ContentButtons(items: [button]).content } func process(_ arguments: [Key : String], markdown: Substring) -> ContentButtons.Item? { guard let rawIcon = arguments[.icon], let text = arguments[.text], let icon = PageIcon(rawValue: rawIcon) else { invalid(markdown) return nil } if let file = arguments[.file] { let name = arguments[.name] return download(fileId: file, icon: icon, text: text, filename: name) } if let url = arguments[.url] { return link(url: url, icon: icon, text: text, markdown: markdown) } if let event = arguments[.event] { return action(event: event, icon: icon, text: text) } invalid(markdown) return nil } private func download(fileId: String, icon: PageIcon, text: String, filename: String?) -> ContentButtons.Item? { guard let file = content.file(fileId) else { results.missing(file: fileId, source: "Download button") return nil } results.require(file: file) results.require(icon: icon) return ContentButtons.Item( icon: icon, filePath: file.absoluteUrl, text: text, downloadFileName: filename) } private func link(url: String, icon: PageIcon, text: String, markdown: Substring) -> ContentButtons.Item? { guard let encodedUrl = url.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else { invalid(markdown) return nil } results.externalLink(to: url) results.require(icon: icon) return .init(icon: icon, filePath: encodedUrl, text: text) } private func action(event: String, icon: PageIcon, text: String) -> ContentButtons.Item? { results.require(icon: icon) return .init(icon: icon, filePath: nil, text: text, onClickText: event) } }