diff --git a/Sources/Generator/Content/Element+LocalizedMetadata.swift b/Sources/Generator/Content/Element+LocalizedMetadata.swift index fcf6279..40c0f79 100644 --- a/Sources/Generator/Content/Element+LocalizedMetadata.swift +++ b/Sources/Generator/Content/Element+LocalizedMetadata.swift @@ -233,27 +233,3 @@ extension Element.LocalizedMetadata { } } } - -// MARK: Thumbnails - -extension Element { - - static let defaultThumbnailName = "thumbnail.jpg" - - static func localizedThumbnailName(for language: String) -> String { - "thumbnail-\(language).jpg" - } - - static func findThumbnail(for language: String, in folder: URL) -> String? { - let localizedThumbnail = localizedThumbnailName(for: language) - let localizedThumbnailUrl = folder.appendingPathComponent(localizedThumbnail) - if localizedThumbnailUrl.exists { - return localizedThumbnail - } - let defaultThumbnailUrl = folder.appendingPathComponent(defaultThumbnailName) - if defaultThumbnailUrl.exists { - return defaultThumbnailName - } - return nil - } -} diff --git a/Sources/Generator/Content/Element.swift b/Sources/Generator/Content/Element.swift index 1e83b45..6b7d1fa 100644 --- a/Sources/Generator/Content/Element.swift +++ b/Sources/Generator/Content/Element.swift @@ -85,6 +85,15 @@ struct Element { */ let images: [ManualImage] + /** + The path to the thumbnail file. + + This property is optional, and defaults to ``GenericMetadata.defaultThumbnailName``. + Note: The generator first looks for localized versions of the thumbnail by appending `-[lang]` to the file name, + e.g. `customThumb-en.jpg`. If no file is found, then the specified file is tried. + */ + let thumbnailPath: String + /** The style of thumbnail to use when generating overviews. @@ -168,6 +177,7 @@ struct Element { self.externalFiles = metadata.externalFiles ?? [] self.requiredFiles = metadata.requiredFiles ?? [] // Paths are already relative to root self.images = metadata.images?.compactMap { ManualImage(input: $0, path: "") } ?? [] + self.thumbnailPath = metadata.thumbnailPath ?? Element.defaultThumbnailName self.thumbnailStyle = log.unused(metadata.thumbnailStyle, "thumbnailStyle", source: source) ?? .large self.useManualSorting = log.unused(metadata.useManualSorting, "useManualSorting", source: source) ?? true self.overviewItemCount = metadata.overviewItemCount ?? Element.overviewItemCountDefault @@ -237,6 +247,7 @@ struct Element { self.externalFiles = Element.rootPaths(for: metadata.externalFiles, path: path) self.requiredFiles = Element.rootPaths(for: metadata.requiredFiles, path: path) self.images = metadata.images?.compactMap { ManualImage(input: $0, path: path) } ?? [] + self.thumbnailPath = metadata.thumbnailPath ?? Element.defaultThumbnailName self.thumbnailStyle = log.thumbnailStyle(metadata.thumbnailStyle, source: source) self.useManualSorting = metadata.useManualSorting ?? false self.overviewItemCount = metadata.overviewItemCount ?? parent.overviewItemCount @@ -420,17 +431,6 @@ extension Element { extension Element { - /** - Get the full path of the thumbnail image for the language (relative to the root folder). - */ - func thumbnailFilePath(for language: String) -> String { - guard let thumbnailFile = Element.findThumbnail(for: language, in: inputFolder) else { - log.add(error: "Missing thumbnail", source: path) - return Element.defaultThumbnailName - } - return pathRelativeToRootForContainedInputFile(thumbnailFile) - } - /** The full url (relative to root) for the localized page - Parameter language: The language of the page where the url should point @@ -497,7 +497,7 @@ extension Element { } func linkPreviewImage(for language: String) -> String? { - localized(for: language).linkPreviewImage + localized(for: language).linkPreviewImage ?? thumbnailFileName(for: language) } } @@ -663,3 +663,57 @@ extension Element { return node } } + +// MARK: Thumbnails + +extension Element { + + static let defaultThumbnailName = "thumbnail.jpg" + + /** + Find the thumbnail for the element. + + This function uses either the custom thumbnail path from the metadata or the default name + to find a thumbnail. It first checks if a localized version of the thumbnail exists, or returns the + generic version. If no thumbnail image could be found on disk, then an error is logged and the + generic path is returned. + + - Parameter language: The language of the thumbnail + - Returns: The thumbnail (either the localized or the generic version) + */ + func thumbnailFilePath(for language: String) -> (source: String, destination: String) { + let localizedThumbnail = thumbnailPath.insert("-\(language)", beforeLast: ".") + let localizedThumbnailUrl = inputFolder.appendingPathComponent(localizedThumbnail) + + if localizedThumbnailUrl.exists { + let source = pathRelativeToRootForContainedInputFile(localizedThumbnail) + let ext = thumbnailPath.lastComponentAfter(".") + let destination = pathRelativeToRootForContainedInputFile("thumbnail-\(language).\(ext)") + return (source, destination) + } + let thumbnailUrl = inputFolder.appendingPathComponent(thumbnailPath) + if !thumbnailUrl.exists { + log.add(error: "Missing thumbnail", source: path) + } + let source = pathRelativeToRootForContainedInputFile(thumbnailPath) + let ext = thumbnailPath.lastComponentAfter(".") + let destination = pathRelativeToRootForContainedInputFile("thumbnail.\(ext)") + return (source, destination) + } + + private func thumbnailFileName(for language: String) -> String? { + let localizedThumbnailName = thumbnailPath.insert("-\(language)", beforeLast: ".") + let localizedThumbnail = pathRelativeToRootForContainedInputFile(localizedThumbnailName) + let localizedThumbnailUrl = inputFolder.appendingPathComponent(localizedThumbnail) + + if localizedThumbnailUrl.exists { + return localizedThumbnailName + } + + let thumbnailUrl = inputFolder.appendingPathComponent(thumbnailPath) + if !thumbnailUrl.exists { + return nil + } + return thumbnailPath + } +} diff --git a/Sources/Generator/Content/GenericMetadata.swift b/Sources/Generator/Content/GenericMetadata.swift index be7d4bf..a1afd84 100644 --- a/Sources/Generator/Content/GenericMetadata.swift +++ b/Sources/Generator/Content/GenericMetadata.swift @@ -84,6 +84,15 @@ struct GenericMetadata { */ let images: Set? + /** + The path to the thumbnail file. + + This property is optional, and defaults to ``Element.defaultThumbnailName``. + Note: The generator first looks for localized versions of the thumbnail by appending `-[lang]` to the file name, + e.g. `customThumb-en.jpg`. If no file is found, then the specified file is tried. + */ + let thumbnailPath: String? + /** The style of thumbnail to use when generating overviews. @@ -136,6 +145,7 @@ extension GenericMetadata: Codable { .externalFiles, .requiredFiles, .images, + .thumbnailPath, .thumbnailStyle, .useManualSorting, .overviewItemCount, @@ -207,6 +217,7 @@ extension GenericMetadata { externalFiles: [], requiredFiles: [], images: [], + thumbnailPath: "", thumbnailStyle: "", useManualSorting: false, overviewItemCount: 6, diff --git a/Sources/Generator/Files/ValidationLog.swift b/Sources/Generator/Files/ValidationLog.swift index cddd293..ae44739 100644 --- a/Sources/Generator/Files/ValidationLog.swift +++ b/Sources/Generator/Files/ValidationLog.swift @@ -116,24 +116,20 @@ final class ValidationLog { } func linkPreviewThumbnail(customFile: String?, for language: String, in folder: URL, source: String) -> String? { - if let customFile = customFile { - let customFileUrl: URL - if customFile.starts(with: "/") { - customFileUrl = URL(fileURLWithPath: customFile) - } else { - customFileUrl = folder.appendingPathComponent(customFile) - } - guard customFileUrl.exists else { - missing(customFile, requiredBy: "property 'linkPreviewImage' in metadata of \(source)") - return nil - } - return customFile - } - guard let thumbnail = Element.findThumbnail(for: language, in: folder) else { - // Link preview images are not necessarily required + guard let customFile = customFile else { return nil } - return thumbnail + let customFileUrl: URL + if customFile.starts(with: "/") { + customFileUrl = URL(fileURLWithPath: customFile) + } else { + customFileUrl = folder.appendingPathComponent(customFile) + } + guard customFileUrl.exists else { + missing(customFile, requiredBy: "property 'linkPreviewImage' in metadata of \(source)") + return nil + } + return customFile } func moreLinkText(_ elementText: String?, parent parentText: String?, source: String) -> String { diff --git a/Sources/Generator/Generators/MarkdownProcessor.swift b/Sources/Generator/Generators/MarkdownProcessor.swift index 8f1ee90..daa481a 100644 --- a/Sources/Generator/Generators/MarkdownProcessor.swift +++ b/Sources/Generator/Generators/MarkdownProcessor.swift @@ -265,7 +265,7 @@ struct PageContentGenerator { content[.title] = linkedPage.title(for: language) - let fullThumbnailPath = linkedPage.thumbnailFilePath(for: language) + let fullThumbnailPath = linkedPage.thumbnailFilePath(for: language).destination let relativeImageUrl = page.relativePathToOtherSiteElement(file: fullThumbnailPath) let metadata = linkedPage.localized(for: language) diff --git a/Sources/Generator/Generators/PageHeadGenerator.swift b/Sources/Generator/Generators/PageHeadGenerator.swift index 7495370..50d9337 100644 --- a/Sources/Generator/Generators/PageHeadGenerator.swift +++ b/Sources/Generator/Generators/PageHeadGenerator.swift @@ -21,7 +21,7 @@ struct PageHeadGenerator { // Note: Generate separate destination link for the image, // since we don't want a single large image for thumbnails. // Warning: Link preview source path must be relative to root - let linkPreviewImageName = image.insert("-link", beforeLast: ".") + let linkPreviewImageName = "thumbnail-link.\(image.lastComponentAfter("."))" let sourceImagePath = page.pathRelativeToRootForContainedInputFile(image) let destinationImagePath = page.pathRelativeToRootForContainedInputFile(linkPreviewImageName) files.requireImage( diff --git a/Sources/Generator/Generators/ThumbnailListGenerator.swift b/Sources/Generator/Generators/ThumbnailListGenerator.swift index f45f648..3238ab7 100644 --- a/Sources/Generator/Generators/ThumbnailListGenerator.swift +++ b/Sources/Generator/Generators/ThumbnailListGenerator.swift @@ -14,8 +14,8 @@ struct ThumbnailListGenerator { } private func itemContent(_ item: Element, parent: Element, language: String, style: ThumbnailStyle) -> String { - let fullThumbnailPath = item.thumbnailFilePath(for: language) - let relativeImageUrl = parent.relativePathToFileWithPath(fullThumbnailPath) + let (thumbnailSourcePath, thumbnailDestPath) = item.thumbnailFilePath(for: language) + let relativeImageUrl = parent.relativePathToFileWithPath(thumbnailDestPath) let metadata = item.localized(for: language) var content = [ThumbnailKey : String]() @@ -37,16 +37,16 @@ struct ThumbnailListGenerator { } files.requireImage( - source: fullThumbnailPath, - destination: fullThumbnailPath, + source: thumbnailSourcePath, + destination: thumbnailDestPath, requiredBy: item.path, width: style.width, desiredHeight: style.height) // Create image version for high-resolution screens files.requireImage( - source: fullThumbnailPath, - destination: fullThumbnailPath.insert("@2x", beforeLast: "."), + source: thumbnailSourcePath, + destination: thumbnailDestPath.insert("@2x", beforeLast: "."), requiredBy: item.path, width: style.width * 2, desiredHeight: style.height * 2)