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