First version

This commit is contained in:
Christoph Hagen
2024-10-14 19:22:32 +02:00
parent 7c812de089
commit 0989f06d87
51 changed files with 2477 additions and 234 deletions

View 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 {
}

View 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 `![page](page_id)`
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 {
}

View 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 {
}

View 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)!
}
}