First version
This commit is contained in:
153
CHDataManagement/Import/GenericMetadata+Localized.swift
Normal file
153
CHDataManagement/Import/GenericMetadata+Localized.swift
Normal file
@ -0,0 +1,153 @@
|
||||
import Foundation
|
||||
|
||||
extension GenericMetadata {
|
||||
|
||||
/**
|
||||
Metadata localized for a specific language.
|
||||
*/
|
||||
struct LocalizedMetadata {
|
||||
|
||||
/**
|
||||
The language for which the content is specified.
|
||||
- Note: This field is mandatory
|
||||
*/
|
||||
let language: String?
|
||||
|
||||
/**
|
||||
The title used in the page header.
|
||||
- Note: This field is mandatory
|
||||
*/
|
||||
let title: String?
|
||||
|
||||
/**
|
||||
The subtitle used in the page header.
|
||||
*/
|
||||
let subtitle: String?
|
||||
|
||||
/**
|
||||
The description text used in the page header
|
||||
*/
|
||||
let description: String?
|
||||
|
||||
/**
|
||||
The title to use for the link preview.
|
||||
|
||||
If `nil` is specified, then the localized element `title` is used.
|
||||
*/
|
||||
let linkPreviewTitle: String?
|
||||
|
||||
/**
|
||||
The file name of the link preview image.
|
||||
- Note: The image must be located in the element folder.
|
||||
- Note: If `nil` is specified, then the (localized) thumbnail is used.
|
||||
*/
|
||||
let linkPreviewImage: String?
|
||||
|
||||
/**
|
||||
The description text for the link preview.
|
||||
- Note: If `nil` is specified, then first the (localized) element `subtitle` is used.
|
||||
If this is `nil` too, then the localized `description` of the element is used.
|
||||
*/
|
||||
let linkPreviewDescription: String?
|
||||
|
||||
/**
|
||||
The text on the link to show the section page when previewing multiple sections on an overview page.
|
||||
- Note: If this value is inherited from the parent, if it is not defined. There must be at least one
|
||||
element in the path that defines this property.
|
||||
*/
|
||||
let moreLinkText: String?
|
||||
|
||||
/**
|
||||
The text on the back navigation link of **contained** elements.
|
||||
|
||||
This text does not appear on the section page, but on the pages contained within the section.
|
||||
- Note: If this property is not specified, then the root `backLinkText` is used.
|
||||
- Note: The root element must specify this property.
|
||||
*/
|
||||
let backLinkText: String?
|
||||
|
||||
/**
|
||||
The text to show as a title for placeholder boxes
|
||||
|
||||
Placeholders are included in missing pages.
|
||||
- Note: If no value is specified, then this property is inherited from the parent. The root element must specify this property.
|
||||
*/
|
||||
let placeholderTitle: String?
|
||||
|
||||
/**
|
||||
The text to show as a description for placeholder boxes
|
||||
|
||||
Placeholders are included in missing pages.
|
||||
- Note: If no value is specified, then this property is inherited from the parent. The root element must specify this property.
|
||||
*/
|
||||
let placeholderText: String?
|
||||
|
||||
/**
|
||||
An optional suffix to add to the title on a page.
|
||||
|
||||
This can be useful to express a different author, project grouping, etc.
|
||||
*/
|
||||
let titleSuffix: String?
|
||||
|
||||
/**
|
||||
An optional suffix to add to the thumbnail title of a page.
|
||||
|
||||
This can be useful to express a different author, project grouping, etc.
|
||||
*/
|
||||
let thumbnailSuffix: String?
|
||||
|
||||
/**
|
||||
A text to place in the top right corner of a large thumbnail.
|
||||
|
||||
The text should be a very short string to fit into the corner, like `soon`, or `draft`
|
||||
|
||||
- Note: This property is ignored if `thumbnailStyle` is not `large`.
|
||||
*/
|
||||
let cornerText: String?
|
||||
|
||||
/**
|
||||
The external url to use instead of automatically generating the page.
|
||||
|
||||
This property can be used for links to other parts of the site, like additional services.
|
||||
It can also be set to manually write a page.
|
||||
*/
|
||||
let externalUrl: String?
|
||||
|
||||
/**
|
||||
The text to display for content related to the current page.
|
||||
|
||||
This property is mandatory at root level, and is propagated to child elements.
|
||||
*/
|
||||
let relatedContentText: String?
|
||||
|
||||
/**
|
||||
The text to display on a navigation element pointing to this element as the previous page.
|
||||
|
||||
This property is mandatory at root level, and is propagated to child elements.
|
||||
*/
|
||||
let navigationTextAsPreviousPage: String?
|
||||
|
||||
/**
|
||||
The text to display on the navigation element pointing to this element as the next page.
|
||||
|
||||
This property is mandatory at root level, and is propagated to child elements.
|
||||
*/
|
||||
let navigationTextAsNextPage: String?
|
||||
|
||||
/**
|
||||
The text to display above a slideshow for most recent items.
|
||||
Only used for elements that define `showMostRecentSection = true`
|
||||
*/
|
||||
let mostRecentTitle: String?
|
||||
|
||||
/**
|
||||
The text to display above a slideshow for featured items.
|
||||
Only used for elements that define `showFeaturedSection = true`
|
||||
*/
|
||||
let featuredTitle: String?
|
||||
}
|
||||
}
|
||||
|
||||
extension GenericMetadata.LocalizedMetadata: Codable {
|
||||
|
||||
}
|
137
CHDataManagement/Import/GenericMetadata.swift
Normal file
137
CHDataManagement/Import/GenericMetadata.swift
Normal file
@ -0,0 +1,137 @@
|
||||
import Foundation
|
||||
|
||||
/**
|
||||
The metadata for all site elements.
|
||||
*/
|
||||
struct GenericMetadata {
|
||||
|
||||
/**
|
||||
A custom id to uniquely identify the element on the site.
|
||||
|
||||
The id is used for short-hand links to pages, in the form of ``
|
||||
for thumbnail previews or `[text](page:page_id)` for simple links.
|
||||
|
||||
If no custom id is set, then the name of the element folder is used.
|
||||
*/
|
||||
let customId: String?
|
||||
|
||||
/**
|
||||
The author of the content.
|
||||
|
||||
If no author is set, then the author from the parent element is used.
|
||||
*/
|
||||
let author: String?
|
||||
|
||||
/**
|
||||
The (start) date of the element.
|
||||
|
||||
The date is printed on content pages and may also used for sorting elements,
|
||||
depending on the `useManualSorting` property of the parent.
|
||||
*/
|
||||
let date: String?
|
||||
|
||||
/**
|
||||
The end date of the element.
|
||||
|
||||
This property can be used to specify a date range for a content page.
|
||||
*/
|
||||
let endDate: String?
|
||||
|
||||
/**
|
||||
The deployment state of the page.
|
||||
|
||||
- Note: This property defaults to ``PageState.standard`
|
||||
*/
|
||||
let state: String?
|
||||
|
||||
/**
|
||||
The sort index of the page for manual sorting.
|
||||
|
||||
- Note: This property is only used (and must be set) if `useManualSorting` option of the parent is set.
|
||||
*/
|
||||
let sortIndex: Int?
|
||||
|
||||
/**
|
||||
All files which may occur in content but is stored externally.
|
||||
|
||||
Missing files which would otherwise produce a warning are ignored when included here.
|
||||
- Note: This property defaults to an empty set.
|
||||
*/
|
||||
let externalFiles: Set<String>?
|
||||
|
||||
/**
|
||||
Specifies additional files which should be copied to the destination when generating the content.
|
||||
- Note: This property defaults to an empty set.
|
||||
*/
|
||||
let requiredFiles: Set<String>?
|
||||
|
||||
/**
|
||||
Additional images required by the element.
|
||||
|
||||
These images are specified as: `source_name destination_name width (height)`.
|
||||
*/
|
||||
let images: Set<String>?
|
||||
|
||||
/**
|
||||
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.
|
||||
|
||||
- Note: This property is only relevant for sections.
|
||||
- Note: This property is inherited from the parent if not specified.
|
||||
*/
|
||||
let thumbnailStyle: String?
|
||||
|
||||
/**
|
||||
Sort the child elements by their `sortIndex` property when generating overviews, instead of using the `date`.
|
||||
|
||||
- Note: This property is only relevant for sections.
|
||||
- Note: This property defaults to `false`
|
||||
*/
|
||||
let useManualSorting: Bool?
|
||||
|
||||
/**
|
||||
The number of items to show when generating overviews of this element.
|
||||
- Note: This property is only relevant for sections.
|
||||
- Note: This property is inherited from the parent if not specified.
|
||||
*/
|
||||
let overviewItemCount: Int?
|
||||
|
||||
/**
|
||||
Indicate the header type to be generated automatically.
|
||||
|
||||
If this option is set to `none`, then custom header code should be present in the page source files
|
||||
- Note: If not specified, this property defaults to `left`.
|
||||
- Note: Overview pages are always using `center`.
|
||||
*/
|
||||
let headerType: String?
|
||||
|
||||
/**
|
||||
Indicate that the overview section should contain a `Newest Content` section before the other sections.
|
||||
- Note: If not specified, this property defaults to `false`
|
||||
*/
|
||||
let showMostRecentSection: Bool?
|
||||
|
||||
/**
|
||||
Indicate that the overview section should contain a `Featured Content` section before the other sections.
|
||||
The elements are the page ids of the elements contained in the feature.
|
||||
- Note: If not specified, this property defaults to `false`
|
||||
*/
|
||||
let featuredPages: [String]?
|
||||
|
||||
/**
|
||||
The localized metadata for each language.
|
||||
*/
|
||||
let languages: [LocalizedMetadata]?
|
||||
}
|
||||
|
||||
extension GenericMetadata: Codable {
|
||||
|
||||
}
|
33
CHDataManagement/Import/ImportableTag.swift
Normal file
33
CHDataManagement/Import/ImportableTag.swift
Normal file
@ -0,0 +1,33 @@
|
||||
import Foundation
|
||||
|
||||
struct ImportableTag {
|
||||
|
||||
let languages: [TagLanguage]
|
||||
|
||||
func info(for language: ContentLanguage) -> TagLanguage? {
|
||||
languages.first { $0.language == language.rawValue }
|
||||
}
|
||||
}
|
||||
|
||||
extension ImportableTag: Codable {
|
||||
|
||||
}
|
||||
|
||||
struct TagLanguage {
|
||||
|
||||
let language: String
|
||||
|
||||
let title: String
|
||||
|
||||
let subtitle: String?
|
||||
|
||||
let description: String?
|
||||
|
||||
let moreLinkText: String?
|
||||
|
||||
let backLinkText: String?
|
||||
}
|
||||
|
||||
extension TagLanguage: Codable {
|
||||
|
||||
}
|
134
CHDataManagement/Import/Importer.swift
Normal file
134
CHDataManagement/Import/Importer.swift
Normal file
@ -0,0 +1,134 @@
|
||||
import Foundation
|
||||
|
||||
struct ImportedContent {
|
||||
|
||||
let posts: [Post]
|
||||
|
||||
let categories: [Tag]
|
||||
}
|
||||
|
||||
final class Importer {
|
||||
|
||||
var posts: [Post] = []
|
||||
|
||||
var pages: [Page] = []
|
||||
|
||||
var tags: [Tag] = []
|
||||
|
||||
var images: [ImageResource] = []
|
||||
|
||||
var foldersToSearch: [(path: String, tag: String)] = [
|
||||
("/Users/ch/Downloads/Website/projects/electronics", "electronics"),
|
||||
("/Users/ch/Downloads/Website/projects/endeavor", "endeavor"),
|
||||
("/Users/ch/Downloads/Website/projects/furniture", "furniture"),
|
||||
("/Users/ch/Downloads/Website/projects/lighting", "lighting"),
|
||||
("/Users/ch/Downloads/Website/projects/other", "other"),
|
||||
("/Users/ch/Downloads/Website/projects/sewing", "sewing"),
|
||||
("/Users/ch/Downloads/Website/projects/software", "software"),
|
||||
("/Users/ch/Downloads/Website/articles", "articles"),
|
||||
("/Users/ch/Downloads/Website/photography", "photography"),
|
||||
("/Users/ch/Downloads/Website/travel", "travel")
|
||||
]
|
||||
|
||||
func importOldContent() throws {
|
||||
for (folder, tagName) in foldersToSearch {
|
||||
let url = URL(filePath: folder)
|
||||
let tag = try importTag(name: tagName, folder: url)
|
||||
try importEntries(in: url, tag: tag)
|
||||
tags.append(tag)
|
||||
}
|
||||
posts.sort { $0.startDate > $1.startDate }
|
||||
//pages.sort { $0.startDate > $1.startDate }
|
||||
tags.sort()
|
||||
}
|
||||
|
||||
private func importTag(name: String, folder: URL) throws -> Tag {
|
||||
let metadataUrl = folder.appending(path: "metadata.json", directoryHint: .notDirectory)
|
||||
let data = try Data(contentsOf: metadataUrl)
|
||||
let meta = try JSONDecoder().decode(ImportableTag.self, from: data)
|
||||
|
||||
return .init(
|
||||
en: meta.info(for: .english)!.title,
|
||||
de: meta.info(for: .german)!.title)
|
||||
}
|
||||
|
||||
private func importEntries(in folder: URL, tag: Tag) throws {
|
||||
try FileManager.default
|
||||
.contentsOfDirectory(at: folder, includingPropertiesForKeys: [.isDirectoryKey])
|
||||
.filter { $0.hasDirectoryPath }
|
||||
.forEach { try importEntry(at: $0, tag: tag) }
|
||||
}
|
||||
|
||||
private func importEntry(at url: URL, tag: Tag) throws {
|
||||
let metadataUrl = url.appending(path: "metadata.json", directoryHint: .notDirectory)
|
||||
guard FileManager.default.fileExists(atPath: metadataUrl.path()) else {
|
||||
//print("No entry at \(url.path())")
|
||||
return
|
||||
}
|
||||
let data = try Data(contentsOf: metadataUrl)
|
||||
let meta = try JSONDecoder().decode(GenericMetadata.self, from: data)
|
||||
|
||||
let page = Page(
|
||||
id: meta.customId ?? url.lastPathComponent,
|
||||
isDraft: meta.state == "draft",
|
||||
metadata: meta.languages!.map(convertPageContent),
|
||||
externalFiles: meta.externalFiles ?? [],
|
||||
requiredFiles: meta.requiredFiles ?? [],
|
||||
images: meta.images ?? [])
|
||||
pages.append(page)
|
||||
|
||||
let de = meta.languages!.first { $0.language == "de" }!
|
||||
let en = meta.languages!.first { $0.language == "en" }!
|
||||
|
||||
let thumbnailImageName = meta.thumbnailPath ?? "thumbnail.jpg"
|
||||
let thumbnailImageUrl = url.appending(path: thumbnailImageName, directoryHint: .notDirectory)
|
||||
var images: [ImageResource] = []
|
||||
if tag.id != "articles" {
|
||||
if FileManager.default.fileExists(atPath: thumbnailImageUrl.path()) {
|
||||
let thumbnail = ImageResource(
|
||||
uniqueId: meta.customId ?? url.lastPathComponent,
|
||||
altText: .init(en: "An image about \(en.title!)", de: "Ein Bild zu \(de.title!)"),
|
||||
fileUrl: thumbnailImageUrl)
|
||||
images.append(thumbnail)
|
||||
self.images.append(thumbnail)
|
||||
} else {
|
||||
print("Thumbnail \(thumbnailImageUrl.path()) not found")
|
||||
}
|
||||
}
|
||||
|
||||
let lastPostId = posts.last?.id ?? 0
|
||||
|
||||
let post = Post(
|
||||
id: lastPostId + 1,
|
||||
isDraft: meta.state == "draft" || meta.state == "hidden",
|
||||
startDate: meta.date!.toDate(),
|
||||
endDate: meta.endDate?.toDate(),
|
||||
title: .init(en: en.linkPreviewTitle ?? en.title!,
|
||||
de: de.linkPreviewTitle ?? de.title!),
|
||||
text: .init(en: en.linkPreviewDescription ?? en.description ?? "No description",
|
||||
de: de.linkPreviewDescription ?? de.description ?? "Keine Beschreibung"),
|
||||
tags: [tag],
|
||||
images: images)
|
||||
|
||||
posts.append(post)
|
||||
}
|
||||
|
||||
private func convertPageContent(_ meta: GenericMetadata.LocalizedMetadata) -> LocalizedPage {
|
||||
.init(language: ContentLanguage(rawValue: meta.language!)!,
|
||||
urlString: nil,
|
||||
headline: meta.title!)
|
||||
}
|
||||
}
|
||||
|
||||
private extension String {
|
||||
|
||||
private static let metadataDate: DateFormatter = {
|
||||
let df = DateFormatter()
|
||||
df.dateFormat = "dd.MM.yy"
|
||||
return df
|
||||
}()
|
||||
|
||||
func toDate() -> Date {
|
||||
String.metadataDate.date(from: self)!
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user