Improve video and image handling in markdown
This commit is contained in:
@@ -3,9 +3,12 @@ import Ink
|
||||
|
||||
struct PageContentGenerator {
|
||||
|
||||
private let factory: TemplateFactory
|
||||
|
||||
private let files: FileProcessor
|
||||
|
||||
init(files: FileProcessor) {
|
||||
init(factory: TemplateFactory, files: FileProcessor) {
|
||||
self.factory = factory
|
||||
self.files = files
|
||||
}
|
||||
|
||||
@@ -19,11 +22,9 @@ struct PageContentGenerator {
|
||||
var hasCodeContent = false
|
||||
|
||||
let imageModifier = Modifier(target: .images) { html, markdown in
|
||||
let result = processMarkdownImage(markdown: markdown, html: html, page: page)
|
||||
switch result {
|
||||
case .success(let content):
|
||||
return content
|
||||
case .failure(let error):
|
||||
do {
|
||||
return try processMarkdownImage(markdown: markdown, html: html, page: page)
|
||||
} catch {
|
||||
errorToThrow = error
|
||||
return ""
|
||||
}
|
||||
@@ -36,9 +37,13 @@ struct PageContentGenerator {
|
||||
hasCodeContent = true
|
||||
return html
|
||||
}
|
||||
let parser = MarkdownParser(modifiers: [imageModifier, codeModifier])
|
||||
let linkModifier = Modifier(target: .links) { html, markdown in
|
||||
#warning("Check links in markdown for (missing) files to copy")
|
||||
return html
|
||||
}
|
||||
|
||||
let parser = MarkdownParser(modifiers: [imageModifier, codeModifier, linkModifier])
|
||||
|
||||
#warning("Check links in markdown for (missing) files to copy")
|
||||
if hasCodeContent {
|
||||
#warning("Automatically add hljs hightlighting if code samples are found")
|
||||
}
|
||||
@@ -50,81 +55,68 @@ struct PageContentGenerator {
|
||||
return result
|
||||
}
|
||||
|
||||
private func processMarkdownImage(markdown: Substring, html: String, page: Page) -> Result<String, Error> {
|
||||
let fileAndTitle = markdown
|
||||
.components(separatedBy: "(").last!
|
||||
.components(separatedBy: ")").first!
|
||||
private func processMarkdownImage(markdown: Substring, html: String, page: Page) throws -> String {
|
||||
// Split the markdown 
|
||||
// For images: 
|
||||
// For videos: 
|
||||
// For files: ?
|
||||
let fileAndTitle = markdown.between("(", and: ")")
|
||||
let file = fileAndTitle.dropAfterFirst(" \"")
|
||||
let title = fileAndTitle.contains(" \"") ? fileAndTitle.between("\"", and: "\"").nonEmpty : nil
|
||||
let alt = markdown.between("[", and: "]").nonEmpty
|
||||
|
||||
let file = fileAndTitle.components(separatedBy: " \"").first! // Remove title
|
||||
let rightSubtitle: String?
|
||||
if fileAndTitle.contains(" \"") {
|
||||
rightSubtitle = fileAndTitle.dropBeforeFirst("\"").dropAfterLast("\"")
|
||||
} else {
|
||||
rightSubtitle = nil
|
||||
let fileExtension = file.lastComponentAfter(".").lowercased()
|
||||
switch files.mediaType(forExtension: fileExtension) {
|
||||
case .image:
|
||||
return try handleImage(page: page, file: file, rightTitle: title, leftTitle: alt)
|
||||
case .video:
|
||||
return try handleVideo(page: page, file: file, optionString: alt)
|
||||
case .file:
|
||||
#warning("Handle other files in markdown")
|
||||
print("[WARN] Unhandled file \(file) with extension \(fileExtension)")
|
||||
return ""
|
||||
}
|
||||
let leftSubtitle = markdown
|
||||
.components(separatedBy: "]").first!
|
||||
.components(separatedBy: "[").last!.nonEmpty
|
||||
}
|
||||
|
||||
private func handleImage(page: Page, file: String, rightTitle: String?, leftTitle: String?) throws -> String {
|
||||
let imagePath = page.pathRelativeToRootForContainedInputFile(file)
|
||||
|
||||
#warning("Specify page image width in configuration")
|
||||
let pageImageWidth = 748
|
||||
let size: NSSize
|
||||
let imagePath = page.pathRelativeToRootForContainedInputFile(file)
|
||||
do {
|
||||
size = try files.requireImage(
|
||||
source: imagePath,
|
||||
destination: imagePath,
|
||||
width: pageImageWidth,
|
||||
desiredHeight: nil,
|
||||
createDoubleVersion: true)
|
||||
} catch {
|
||||
return .failure(error)
|
||||
}
|
||||
let size = try files.requireImage(source: imagePath, destination: imagePath, width: pageImageWidth)
|
||||
|
||||
let imagePath2x = imagePath.insert("@2x", beforeLast: ".")
|
||||
let file2x = file.insert("@2x", beforeLast: ".")
|
||||
#warning("Move HTML code to single location")
|
||||
let result = articelImage(
|
||||
image: file,
|
||||
image2x: file2x,
|
||||
width: size.width,
|
||||
height: size.height,
|
||||
rightSubtitle: rightSubtitle,
|
||||
leftSubtitle: leftSubtitle)
|
||||
return .success(result)
|
||||
try files.requireImage(source: imagePath, destination: imagePath2x, width: 2 * pageImageWidth)
|
||||
|
||||
let content: [PageImageTemplate.Key : String] = [
|
||||
.image: file,
|
||||
.image2x: file2x,
|
||||
.width: "\(Int(size.width))",
|
||||
.height: "\(Int(size.height))",
|
||||
.leftText: leftTitle ?? "",
|
||||
.rightText: rightTitle ?? ""]
|
||||
return factory.image.generate(content)
|
||||
}
|
||||
|
||||
private func articelImage(image: String, image2x: String, width: CGFloat, height: CGFloat, rightSubtitle: String?, leftSubtitle: String?) -> String {
|
||||
let subtitleCode = subtitle(left: leftSubtitle, right: rightSubtitle)
|
||||
return fullImageCode(image: image, image2x: image2x, width: width, height: height, subtitle: subtitleCode)
|
||||
}
|
||||
private func handleVideo(page: Page, file: String, optionString: String?) throws -> String {
|
||||
let options: [PageVideoTemplate.VideoOption] = optionString.unwrapped { string in
|
||||
string.components(separatedBy: " ").compactMap { optionText in
|
||||
guard let optionText = optionText.trimmed.nonEmpty else {
|
||||
return nil
|
||||
}
|
||||
guard let option = PageVideoTemplate.VideoOption(rawValue: optionText) else {
|
||||
print("[WARN] Unknown video option \(optionText) in page \(page.path)")
|
||||
return nil
|
||||
}
|
||||
return option
|
||||
}
|
||||
} ?? []
|
||||
#warning("Check page folder for alternative video versions")
|
||||
let sources: [PageVideoTemplate.VideoSource] = [(url: file, type: .mp4)]
|
||||
|
||||
private func articleImageWithoutSubtitle(image: String, image2x: String, width: CGFloat, height: CGFloat) -> String {
|
||||
"""
|
||||
<span class="image">
|
||||
<img src="\(image)" srcset="\(image2x) 2x" width="\(Int(width))" height="\(Int(height))" loading="lazy"/>
|
||||
</span>
|
||||
"""
|
||||
}
|
||||
|
||||
private func subtitle(left: String?, right: String?) -> String {
|
||||
guard left != nil || right != nil else {
|
||||
return ""
|
||||
}
|
||||
let leftCode = left.unwrapped { "<span class=\"left\">\($0)</span>" } ?? ""
|
||||
let rightCode = right.unwrapped { "<span class=\"right\">\($0)</span>" } ?? ""
|
||||
return """
|
||||
<div class="subtitle">
|
||||
\(leftCode)
|
||||
\(rightCode)
|
||||
</div>
|
||||
"""
|
||||
}
|
||||
|
||||
private func fullImageCode(image: String, image2x: String, width: CGFloat, height: CGFloat, subtitle: String) -> String {
|
||||
"""
|
||||
<span class="image">
|
||||
<img src="\(image)" srcset="\(image2x) 2x" width="\(Int(width))" height="\(Int(height))" loading="lazy"/>
|
||||
\(subtitle)
|
||||
</span>
|
||||
"""
|
||||
let filePath = page.pathRelativeToRootForContainedInputFile(file)
|
||||
files.require(file: filePath)
|
||||
return factory.video.generate(sources: sources, options: options)
|
||||
}
|
||||
}
|
||||
|
@@ -55,7 +55,8 @@ struct PageGenerator {
|
||||
return factory.placeholder
|
||||
}
|
||||
print("Generated page \(page.path)")
|
||||
return try PageContentGenerator(files: files).generate(page: page, language: language, at: url)
|
||||
return try PageContentGenerator(factory: factory.factory, files: files)
|
||||
.generate(page: page, language: language, at: url)
|
||||
}
|
||||
|
||||
private func makeHead(page: Page, language: String) throws -> String {
|
||||
|
Reference in New Issue
Block a user