Created
May 17, 2021 14:48
-
-
Save Ze0nC/6696c87d5324bbe7b7713e26e5cd8403 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
import Publish | |
import Plot | |
extension Theme where Site == CzjIo { | |
static var czj: Self { | |
Theme( | |
htmlFactory: CzjHTMLFactory(), | |
resourcePaths: ["Sources/CzjIo/styles.css"] | |
) | |
} | |
internal struct CzjHTMLFactory: HTMLFactory { | |
func makeIndexHTML(for index: Index, | |
context: PublishingContext<Site>) throws -> HTML { | |
HTML( | |
.lang(index.language!), | |
.head(for: index, on: context.site, stylesheetPaths: ["/styles.css"]), | |
.body { | |
SiteHeader(location: index, context: context, selectedSelectionID: nil) | |
Wrapper { | |
H2("\("Photos and More".localized(in: index.language!))") | |
ItemList( | |
items: context.allItems(in: index.language!), | |
site: context.site, | |
filter: { $0.sectionID.rawValue == "album" }, | |
column: 3, | |
max: 6 | |
) | |
Link("\("Browse all photos".localized(in: index.language!))", url: "/\(context.site.path(for: context.section(.album, in: index.language!)))") | |
.class("browse-all") | |
H2("\("Working with Publish".localized(in: index.language!))") | |
ItemList( | |
items: context.allItems(in: index.language!), | |
site: context.site, | |
filter: { (item) -> Bool in | |
item.tags.contains(Tag("Publish")) | |
}, | |
column: 3, | |
max: 6 | |
) | |
H2("\("Writing in LaTeX".localized(in: index.language!))") | |
ItemList( | |
items: context.allItems(in: index.language!), | |
site: context.site, | |
filter: { (item) -> Bool in | |
item.tags.contains(Tag("LaTeX")) && !item.tags.contains(Tag("macOS")) | |
}, | |
column: 3, | |
max: 6 | |
) | |
H2("\("Research in Chemistry".localized(in: index.language!))") | |
ItemList( | |
items: context.allItems(in: index.language!), | |
site: context.site, | |
filter: { (item) -> Bool in | |
item.tags.map{$0.normalizedString()}.contains("Academic".localizedTag(in: index.language!)) | |
}, | |
column: 3, | |
max: 6 | |
) | |
H2("\("And More".localized(in: index.language!))") | |
ItemList( | |
items: context.allItems(in: index.language!), | |
site: context.site, | |
filter: { (item) -> Bool in | |
!item.tags.contains(Tag("\("Publish".localizedTag(in: index.language!))")) && !(item.tags.contains(Tag("\("LaTeX".localizedTag(in: index.language!))")) && !item.tags.contains(Tag("\("macOS".localizedTag(in: index.language!))"))) && !(item.tags.contains(Tag("\("Academic".localizedTag(in: index.language!))"))) && | |
item.sectionID != .album | |
}, | |
column: 3, | |
max: 12 | |
) | |
Link("\("Browse all articles".localized(in: index.language!))", url: "/\(context.site.path(for: context.section(.article, in: index.language!)))") | |
.class("browse-all") | |
} | |
SiteFooter(site: context.site, language: index.language!) | |
} | |
) | |
} | |
func makeAlbumSectionHTML(for section: Section<Site>, | |
context: PublishingContext<Site>) throws -> HTML { | |
let photos = context.sections.filter{$0.id.rawValue == "album"}.first!.items(in: section.language!).sorted(by: { $0.date > $1.date} ).prefix(6).reversed() | |
return | |
HTML( | |
.lang(section.language!), | |
.head(for: section, on: context.site, stylesheetPaths: ["/styles.css", "/gallery.css"]), | |
.body { | |
SiteHeader(location: section, context: context, selectedSelectionID: section.id, title: section.id.rawValue.localized(in: section.language!)) | |
Div { | |
for photo in photos { | |
Image("\(photo.imagePath!)") | |
Paragraph(photo.title) | |
} | |
} | |
.id("cf4a") | |
.class("index-background") | |
Div { | |
ItemList( | |
items: context.allItems(in: section.language!), | |
site: context.site, | |
filter: { $0.sectionID == .album }, | |
column: 5 | |
) | |
} | |
.class("wrapper") | |
.style("max-width: 6000px;") | |
SiteFooter(site: context.site, language: section.language!) | |
} | |
) | |
} | |
func makeAboutSectionHTML(for section: Section<Site>, | |
context: PublishingContext<Site>) throws -> HTML { | |
HTML( | |
.lang(section.language!), | |
.head(for: section, on: context.site, stylesheetPaths: ["/styles.css"]), | |
.body { | |
SiteHeader(location: section, context: context, selectedSelectionID: section.id, title: section.id.rawValue.localized(in: section.language!)) | |
Wrapper { | |
// H1(.text(section.title)) | |
Article { | |
Div(section.body) | |
.class("content") | |
LinkList() | |
.style(" margin: 12px;") | |
if section.language! == .chinese { | |
Paragraph("邮箱:[email protected]") | |
} else if section.language! == .english { | |
Paragraph("Contact me: [email protected]") | |
} else if section.language! == .japanese { | |
Paragraph("メールアドレス:[email protected]") | |
} | |
AboutList(language: section.language!) | |
} | |
ItemList( | |
items: context.allItems(in: section.language!), | |
site: context.site, | |
filter: { $0.sectionID == section.id }, | |
column: 3 | |
) | |
} | |
SiteFooter(site: context.site, language: section.language!) | |
} | |
) | |
} | |
func makeSectionHTML(for section: Section<Site>, | |
context: PublishingContext<Site>) throws -> HTML { | |
if section.id.rawValue == "album" { | |
return try makeAlbumSectionHTML(for: section, context: context) | |
} | |
if section.id.rawValue == "about" { | |
return try makeAboutSectionHTML(for: section, context: context) | |
} | |
return | |
HTML( | |
.lang(section.language!), | |
.head(for: section, on: context.site, stylesheetPaths: ["/styles.css"]), | |
.body { | |
SiteHeader(location: section, context: context, selectedSelectionID: section.id, title: section.id.rawValue.localized(in: section.language!)) | |
Wrapper { | |
Article { | |
Div(section.body) | |
.class("content") | |
} | |
ItemList( | |
items: context.allItems(in: section.language!), | |
site: context.site, | |
filter: { $0.sectionID == section.id }, | |
column: 3 | |
) | |
} | |
SiteFooter(site: context.site, language: section.language!) | |
} | |
) | |
} | |
func makeItemHTML(for item: Item<Site>, | |
context: PublishingContext<Site>) throws -> HTML { | |
let allItems = context.allItems(in: item.language!).filter({$0.sectionID == item.sectionID}) | |
let itemIndex = allItems.firstIndex(of: item) | |
let previous: Node<HTML.BodyContext> | |
let next: Node<HTML.BodyContext> | |
if allItems.first != item { | |
let previousItem = allItems[allItems.index(before: itemIndex!)] | |
previous = .a( | |
.href(context.site.path(for: previousItem)), | |
.text("← " + previousItem.title), | |
.style("left: 0px;") | |
) | |
} else { | |
previous = .empty | |
} | |
if allItems.last != item { | |
let nextItem = allItems[allItems.index(after: itemIndex!)] | |
next = .a( | |
.href(context.site.path(for: nextItem)), | |
.text(nextItem.title + " →"), | |
.style("right: 0px;") | |
) | |
} else { | |
next = .empty | |
} | |
return HTML( | |
.lang(item.language!), | |
.head(for: item, on: context.site, stylesheetPaths: ["/styles.css", "/gallery.css"]), | |
.body { | |
SiteHeader(location: item, context: context, selectedSelectionID: item.sectionID, backgroundImage: item.imagePath?.string) | |
Wrapper { | |
Article { | |
H1(item.title) | |
.class("hide-wide") | |
Div(item.body) | |
.class("content") | |
Span("\("Tagged with: ".localized(in: item.language!))") | |
ItemTagList(item: item, site: context.site) | |
Div { | |
previous | |
next | |
} | |
.class("prevNextItem") | |
} | |
} | |
SiteFooter(site: context.site, language: item.language!) | |
} | |
//.class("item-page") | |
) | |
} | |
func makePageHTML(for page: Page, | |
context: PublishingContext<Site>) throws -> HTML { | |
HTML( | |
.lang(page.language!), | |
.head(for: page, on: context.site, stylesheetPaths: ["/styles.css", "/gallery.css"]), | |
.body { | |
SiteHeader(location: page, context: context, selectedSelectionID: nil) | |
Wrapper { | |
H1(page.title) | |
.class("hide-wide") | |
page.body | |
//Node<HTML.BodyContext>.raw(page.content.body.html) | |
} | |
SiteFooter(site: context.site, language: page.language!) | |
} | |
) | |
} | |
func makeTagListHTML(for page: TagListPage, | |
context: PublishingContext<Site>) throws -> HTML? { | |
HTML( | |
.lang(page.language!), | |
.head(for: page, on: context.site, stylesheetPaths: ["/styles.css"]), | |
.body { | |
SiteHeader(location: page, context: context, selectedSelectionID: nil, title: "\("All tags".localized(in: page.language!))") | |
Wrapper { | |
List(page.tags.sorted(by: { (tag1, tag2) -> Bool in | |
tag1.string.lowercased() < tag2.string.lowercased() | |
})) { tag in | |
ListItem { | |
Link(tag.string, url: "\(context.site.path(for: tag, in: page.language!))") | |
} | |
.class(tag.colorfiedClass(in: page.language!)) | |
} | |
.listStyle(.unordered) | |
.class("all-tags") | |
} | |
SiteFooter(site: context.site, language: page.language!) | |
} | |
) | |
} | |
func makeTagDetailsHTML(for page: TagDetailsPage, | |
context: PublishingContext<Site>) throws -> HTML? { | |
HTML( | |
.lang(page.language!), | |
.head(for: page, on: context.site, stylesheetPaths: ["/styles.css"]), | |
.body { | |
SiteHeader(location: page, context: context, selectedSelectionID: nil) | |
Wrapper { | |
H1 { | |
Text("\("Tagged with: ".localized(in: page.language!))") | |
Span(page.tag.string) | |
.class(page.tag.colorfiedClass(in: page.language!)) | |
} | |
ItemList( | |
items: context.items( | |
taggedWith: page.tag, | |
sortedBy: \.date, | |
in: page.language!, | |
order: .descending | |
), | |
site: context.site, | |
column: 2 | |
) | |
} | |
SiteFooter(site: context.site, language: page.language!) | |
} | |
) | |
} | |
} | |
} | |
extension Theme.CzjHTMLFactory { | |
private struct Wrapper: ComponentContainer { | |
@ComponentBuilder var content: ContentProvider | |
var body: Component { | |
Div(content: content).class("wrapper") | |
} | |
} | |
private struct SiteHeader<Site: MultiLanguageWebsite>: Component { | |
var location: Location | |
var context: PublishingContext<Site> | |
var selectedSelectionID: Site.SectionID? | |
var backgroundImage: String? = nil | |
var title: String? = nil | |
var body: Component { | |
Header { | |
Div{ | |
Wrapper { | |
Link(url: "/\(location.language! == .english ? "" : context.site.pathPrefix(for: location.language!) + "/")") { | |
CZJLogo() | |
} | |
.class("header-item site-name") | |
Div { | |
if location is Index { | |
Div("ChenZhijin") | |
.style("font-size: 34px; line-height: 24px;") | |
Div(".com") | |
.style("font-size: 24px; line-height: 20px;") | |
} else if location is Section<Site> { | |
Span(title ?? location.title) | |
} else { | |
Span(title ?? location.title) | |
.class("show-wide") | |
Div { | |
Div("ChenZhijin") | |
.style("font-size: 34px; line-height: 24px;") | |
Div(".com") | |
.style("font-size: 24px; line-height: 20px;") | |
} | |
.class("hide-wide") | |
} | |
} | |
.class("page-title") | |
.style("font-weight: 280;") | |
Navigator(location: location, context: context, selectedSection: selectedSelectionID) | |
} | |
} | |
.class("blurred") | |
if backgroundImage != nil { | |
Image(backgroundImage!) | |
.class("header-background") | |
} | |
} | |
} | |
} | |
private struct CZJLogo: Component { | |
var body: Component { | |
Node<Any>.raw("<svg><circle cx=\"24\" cy=\"24\" r=\"22.5\"/><path d=\"M40,9H18L10,18H23L7,37 8,39H30L38,30 25,30 41,11M35 18H45M3 30H13\"/></svg>") | |
} | |
} | |
private struct Navigator<Site: MultiLanguageWebsite>: Component { | |
var location: Location | |
var context: PublishingContext<Site> | |
var selectedSection: Site.SectionID? | |
var body: Component { | |
var altnativeLinks = context.alternateLinks(for: location) | |
var languages: [Language] = altnativeLinks.keys.sorted(by: { $0.rawValue < $1.rawValue }) | |
var paths: [Path] = languages.map{ altnativeLinks[$0]! } | |
if altnativeLinks.count != context.site.languages.count, let tagDetailsPage = location as? TagDetailsPage { | |
let alternatives = LocalizedTag.alternatives(for: tagDetailsPage.tag.string, in: tagDetailsPage.language!) | |
for language in alternatives.keys.sorted(by: { $0.rawValue < $1.rawValue }) { | |
languages.append(language) | |
paths.append(context.site.path(for: Tag(alternatives[language]!) , in: language)) | |
} | |
} | |
return Navigation { | |
Input(type: .checkbox, name: "section", value: "show-section") | |
.id("section-icon") | |
Label("") { | |
Div { | |
Node<HTML.BodyContext>.raw("<svg><path d=\"M4,14H24 M4,6H24 M4,22H24\"/></svg>") | |
} | |
.class("dropbtn") | |
Div().class("hspacer") | |
Div { | |
for section in context.sections.ids { | |
Link(context.sections[section].id.rawValue.localized(in: location.language!), url: "/\(context.site.path(for: context.section(section, in: location.language!)))") | |
.class(section == selectedSection ? "selected" : "") | |
Div().class("line") | |
} | |
} | |
.class("dropdown-content nav-background") | |
} | |
.attribute(named: "for", value: "section-icon") | |
.class("dropdown header-item") | |
if languages.count > 1 { | |
Div().class("line") | |
Input(type: .checkbox, name: "language", value: "show-language") | |
.id("language-icon") | |
Label("") { | |
Div { | |
Node<HTML.BodyContext>.raw("<svg><circle cx=\"14\" cy=\"14\" r=\"12\"/><path d=\"M2,14 H26 M14,2 V26 M4,7 C8,8 11,8 14,8 C17,8 20,8 24,7 M4,21 C8,20 11,20 14,20 C17,20 20,20 24,21\"/><ellipse cx=\"14\" cy=\"14\" rx=\"6\" ry=\"12\"/></svg>") | |
} | |
.class("dropbtn") | |
Div().class("hspacer") | |
Div { | |
for (language, path) in zip(languages, paths) { | |
Link(context.site.name(for: language), url: language == location.language ? "#" : "/\(path)") | |
Div().class("line") | |
} | |
} | |
.class("dropdown-content nav-background") | |
} | |
.attribute(named: "for", value: "language-icon") | |
.class("dropdown header-item") | |
} | |
} | |
.class("section-nav") | |
} | |
} | |
struct ArticleItem<Site: MultiLanguageWebsite>: Component { | |
var item: Item<Site> | |
var site: Site | |
var body: Component { | |
Article { | |
H1 { | |
Link(item.title, url: "/\(site.path(for: item))") | |
} | |
ItemTagList(item: item, site: site) | |
Paragraph(item.description) | |
} | |
.class("article-item") | |
} | |
} | |
struct AlbumItem<Site: MultiLanguageWebsite>: Component { | |
var item: Item<Site> | |
var site: Site | |
var body: Component { | |
Article { | |
Link(url: "/\(site.path(for: item))") { | |
Image("\(item.imagePath!.string.replacingOccurrences(of: ".jpg", with: "-thumbnail.jpg"))") | |
.class("item-background") | |
H1(item.title) | |
} | |
} | |
.class(item.sectionID.rawValue == "album" ? "album-item" : "article-item") | |
} | |
} | |
struct ItemList<Site: MultiLanguageWebsite>: Component { | |
var items: [Item<Site>] | |
var site: Site | |
var filter: (Item<Site>) -> Bool = {_ in true} | |
var column: Int = 1 | |
var max: Int = 0 | |
private let columnClasses = ["", "single-column", "double-column", "triple-column", "quadruple-column", "quintuple-column"] | |
var body: Component { | |
List(items.filter(filter).prefix(max == 0 ? Int.max : max)) { item in | |
ListItem { | |
if item.sectionID.rawValue.contains("album") { | |
AlbumItem(item: item, site: site) | |
} else { | |
ArticleItem(item: item, site: site) | |
} | |
} | |
.listStyle(.unordered) | |
} | |
.class("item-list flex \(columnClasses[column])") | |
} | |
} | |
struct ItemTagList<Site: MultiLanguageWebsite>: Component { | |
var item: Item<Site> | |
var site: Site | |
var body: Component { | |
List(item.tags) { tag in | |
ListItem { | |
Link(tag.string, url: "/\(site.path(for: tag, in: item.language ?? site.language))") | |
} | |
.class(tag.colorfiedClass(in: item.language ?? site.language)) | |
} | |
.listStyle(.unordered) | |
.class("tag-list") | |
} | |
} | |
struct SiteFooter<Site: MultiLanguageWebsite>: Component { | |
var site: Site | |
var language: Language | |
var body: Component { | |
Footer { | |
Link("\("Browse all tags".localized(in: language))", url: "/\(site.tagListPath(in: language))") | |
.class("browse-all") | |
Paragraph { | |
Text("©2021 Zhijin Chen") | |
} | |
Paragraph { | |
Text("\("Generated using ".localized(in: language))") | |
Link("Publish", url: "https://github.com/johnsundell/publish") | |
Text("\(". 100% JavaScript-free.".localized(in: language))") | |
} | |
Paragraph { | |
Link("\("RSS feed".localized(in: language))", url: "/\(site.pathPrefix(for: language))/feed.rss") | |
} | |
} | |
} | |
} | |
struct ItemBadge<Site: MultiLanguageWebsite>: Component { | |
var key: String | |
var value: String | |
var keyClass: String? = nil | |
var valueClass: String? = nil | |
var body: Component { | |
Div { | |
Div { | |
Text(key) | |
} | |
.class("badge-key \(keyClass ?? "variant-medium")") | |
Div { | |
Text(value.replacingCharacters(in: value.range(of: String(value.first!))!, with: value.first!.uppercased())) | |
} | |
.class("badge-value \(valueClass ?? "variant-\(Int.random(in: 0..<12))")") | |
} | |
.class("badge") | |
} | |
} | |
struct LinkList: Component { | |
var body: Component { | |
List { | |
ListItem(Link(url: "https://github.com/ze0nc") {Node<HTML.BodyContext>.raw("<svg><use href=\"/images/github.svg#github\"></use></svg>")}) | |
ListItem(Link(url: "https://www.linkedin.com/in/chenzhijin/") {Node<HTML.BodyContext>.raw("<svg><use href=\"/images/linkedin.svg#linkedin\"></use></svg>")}) | |
ListItem(Link(url: "https://researchmap.jp/chenzhijin") {Node<HTML.BodyContext>.raw("<svg><use href=\"/images/researchmap.svg#researchmap\"></use></svg>")}) | |
ListItem(Link(url: "https://www.researchgate.net/profile/Zhijin_Chen3") {Node<HTML.BodyContext>.raw("<svg><use href=\"/images/researchgate.svg#researchgate\"></use></svg>")}) | |
ListItem(Link(url: "https://www.instagram.com/chenzhijin1992/") {Node<HTML.BodyContext>.raw("<svg><use href=\"/images/instagram.svg#instagram\"></use></svg>")}) | |
ListItem(Link(url: "https://twitter.com/chenzhijin") {Node<HTML.BodyContext>.raw("<svg><use href=\"/images/twitter.svg#twitter\"></use></svg>")}) | |
} | |
.listStyle(.unordered) | |
.class("social-list") | |
} | |
} | |
struct AboutList: Component { | |
var language: Language | |
var body: Component { | |
List { | |
ListItem { | |
H2("Chemist".localized(in: language)) | |
.style("padding: 0px;margin: 6px 12px;border-top: none;") | |
if language == .chinese { | |
Paragraph("我在2020年获得了化学博士学位。研究方向:") | |
} else if language == .japanese { | |
Paragraph("2020年に化学の博士号を取得しました。 研究領域:") | |
} else if language == .english { | |
Paragraph("In 2020 I got my Ph.D. degree in chemistry. Research interest:") | |
} | |
List { | |
if language == .chinese { | |
ListItem("卟啉化学") | |
ListItem("分子电子学") | |
ListItem("隧道扫描显微镜(STM)") | |
ListItem("单分子导电性") | |
ListItem("有机合成") | |
ListItem("高分子合成") | |
ListItem("化学领域的机器学习") | |
} else if language == .japanese { | |
ListItem("ポルフィリン化学") | |
ListItem("分子エレクトロニクス") | |
ListItem("走査型トンネル顕微鏡(STM)") | |
ListItem("単分子電気伝導性") | |
ListItem("有機合成") | |
ListItem("高分子合成") | |
ListItem("化学分野における機械学習") | |
} else if language == .english { | |
ListItem("Porphyrin") | |
ListItem("Molecular Electronics") | |
ListItem("Scanning Tunneling Miscroscopy (STM)") | |
ListItem("Single Molecular Conductance") | |
ListItem("Organic Synthesis") | |
ListItem("Macromolecular Synthesis") | |
ListItem("Machine Learning in Chemistry") | |
} | |
} | |
.listStyle(.unordered) | |
} | |
ListItem { | |
H2("Photographer".localized(in: language)) | |
.style("padding: 0px;margin: 6px 12px;border-top: none;") | |
if language == .chinese { | |
Paragraph { | |
Text("在线相册:") | |
Link("chenzhijin.com", url: "https://chenzhijin.com/zh/album") | |
} | |
} else if language == .japanese { | |
Paragraph { | |
Text("写真ギャラリー:") | |
Link("chenzhijin.com", url: "https://chenzhijin.com/ja/album") | |
} | |
} else if language == .english { | |
Paragraph { | |
Text("My Gallery: ") | |
Link("chenzhijin.com", url: "https://chenzhijin.com/en/album") | |
} | |
} | |
} | |
ListItem { | |
H2("Developer".localized(in: language)) | |
.style("padding: 0px;margin: 6px 12px;border-top: none;") | |
if language == .chinese { | |
H3("大阪大学班车时刻表App (iPhone, iPad, Mac)") | |
.style(" margin: 12px;") | |
} else if language == .japanese { | |
H3("大阪大学班车时刻表App (iPhone, iPad, Mac)") | |
.style(" margin: 12px;") | |
} else if language == .english { | |
H3("Osaka University Shuttle Bus App (iPhone, iPad, Mac)") | |
.style(" margin: 12px;") | |
} | |
Image("/images/article/HandaiBus-Icon512.png") | |
.style("width:80px; height:80px; margin: 12px; display: inline-block; vertical-align: middle;") | |
Link(url: "https://apps.apple.com/app/id755385889") { | |
Image("/images/article/Download_on_the_App_Store_Badge_US-UK_RGB_blk_092917.svg") | |
} | |
.style(" margin: 12px; display: inline-block; vertical-align: middle;") | |
H3("Chemy (iPhone, iPad, Mac)") | |
.style(" margin: 12px;") | |
Image("/images/article/Chemy-Icon512.png") | |
.style("width:80px; height:80px; margin: 12px; display: inline-block; vertical-align: middle;") | |
Link(url: "https://apps.apple.com/app/id1542390153") { | |
Image("/images/article/Download_on_the_App_Store_Badge_US-UK_RGB_blk_092917.svg") | |
} | |
.style(" margin: 12px; display: inline-block; vertical-align: middle;") | |
} | |
} | |
.listStyle(.unordered) | |
.class("item-list flex triple-column") | |
.style("margin: 0px;") | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Foundation | |
import Publish | |
import Plot | |
extension Theme where Site == CzjIo { | |
static var czj: Self { | |
Theme( | |
htmlFactory: CzjHTMLFactory(), | |
resourcePaths: ["Sources/CzjIo/styles.css"] | |
) | |
} | |
internal struct CzjHTMLFactory: HTMLFactory where Site: MultiLanguageWebsite { | |
func makeIndexHTML(for index: Index, | |
context: PublishingContext<CzjIo>) throws -> HTML { | |
HTML( | |
.lang(index.language!), | |
.head(for: index, on: context.site, stylesheetPaths: ["/styles.css"]), | |
.body( | |
.header(for: index, for: context, selectedSection: nil), | |
.wrapper( | |
.h2("\("Photos and More".localized(in: index.language!))"), | |
.itemList( | |
for: context.allItems( | |
sortedBy: \.date, | |
in: index.language!, | |
order: .descending | |
), | |
on: context.site, | |
filter: { $0.sectionID == .album }, | |
column: 3, | |
max: 6 | |
), | |
.a( | |
.class("browse-all"), | |
.text("\("Browse all photos".localized(in: index.language!))"), | |
.href( | |
context.site.path( | |
for: context.section(.album, in: index.language!) | |
) | |
) | |
), | |
.h2("\("Working with Publish".localized(in: index.language!))"), | |
.itemList( | |
for: context.allItems( | |
sortedBy: \.date, | |
in: index.language!, | |
order: .descending | |
), | |
on: context.site, | |
filter: { (item) -> Bool in | |
item.tags.contains(Tag("Publish")) | |
}, | |
column: 3, | |
max: 6 | |
), | |
.h2("\("Writing in LaTeX".localized(in: index.language!))"), | |
.itemList( | |
for: context.allItems( | |
sortedBy: \.date, | |
in: index.language!, | |
order: .descending | |
), | |
on: context.site, | |
filter: { (item) -> Bool in | |
item.tags.contains(Tag("LaTeX")) && !item.tags.contains(Tag("macOS")) //&& item.language == language | |
}, | |
column: 3, | |
max: 6 | |
), | |
.h2("\("Research in Chemistry".localized(in: index.language!))"), | |
.itemList( | |
for: context.allItems( | |
sortedBy: \.date, | |
in: index.language!, | |
order: .descending | |
), | |
on: context.site, | |
filter: { (item) -> Bool in | |
item.tags.map{$0.normalizedString()}.contains("Academic".localizedTag(in: index.language!)) //&& item.language == language | |
}, | |
column: 3, | |
max: 6 | |
), | |
.h2("\("And More".localized(in: index.language!))"), | |
.itemList( | |
for: context.allItems( | |
sortedBy: \.date, | |
in: index.language!, | |
order: .descending | |
), | |
on: context.site, | |
filter: { (item) -> Bool in | |
!item.tags.contains(Tag("\("Publish".localizedTag(in: index.language!))")) && !(item.tags.contains(Tag("\("LaTeX".localizedTag(in: index.language!))")) && !item.tags.contains(Tag("\("macOS".localizedTag(in: index.language!))"))) && !(item.tags.contains(Tag("\("Academic".localizedTag(in: index.language!))"))) && | |
item.sectionID != .album | |
}, | |
column: 3, | |
max: 12 | |
), | |
.a( | |
.class("browse-all"), | |
.text("\("Browse all articles".localized(in: index.language!))"), | |
.href( | |
context.site.path( | |
for: context.section(.article, in: index.language!) | |
) | |
) | |
) | |
), | |
.footer(for: context.site, in: index.language!) | |
) | |
) | |
} | |
func makeAlbumSectionHTML(for section: Section<Site>, | |
context: PublishingContext<Site>) throws -> HTML { | |
let photos = context.sections[.album].items(in: section.language!).sorted(by: { $0.date > $1.date} ).prefix(6).reversed() | |
return | |
HTML( | |
.lang(section.language!), | |
.head(for: section, on: context.site, stylesheetPaths: ["/styles.css", "/gallery.css"]), | |
.body( | |
.header( | |
for: section, | |
for: context, | |
selectedSection: section.id, | |
backgroundImage: nil, | |
title: section.id.rawValue.localized(in: section.language!) | |
), | |
.div( | |
.class("index-background"), | |
.id("cf4a"), | |
.forEach( | |
photos, | |
{ | |
.group( | |
.img( | |
.src($0.imagePath!) | |
), | |
.p( | |
.text($0.title) | |
) | |
) | |
} | |
) | |
), | |
.div( | |
.class("wrapper"), | |
.style("max-width: 3840px;"), | |
.itemList( | |
for: context.allItems( | |
sortedBy: \.date, | |
in: section.language!, | |
order: .descending | |
), | |
on: context.site, | |
filter: { $0.sectionID == .album }, | |
column: 5 | |
) | |
), | |
.footer(for: context.site, in: section.language!) | |
) | |
) | |
} | |
func makeSectionHTML(for section: Section<Site>, | |
context: PublishingContext<Site>) throws -> HTML where Site == CzjIo { | |
if section.id == .album { | |
return try makeAlbumSectionHTML(for: section, context: context) | |
} | |
return | |
HTML( | |
.lang(section.language!), | |
.head(for: section, on: context.site, stylesheetPaths: ["/styles.css"]), | |
.body( | |
.header( | |
for: section, | |
for: context, | |
selectedSection: section.id, | |
title: section.id.rawValue.localized(in: section.language!) | |
), | |
.wrapper( | |
.if(section.id == .album, .style("max-width: 3840px;")), | |
.article( | |
.div( | |
.class("content"), | |
.contentBody(section.body) | |
) | |
), | |
.itemList( | |
for: context.allItems( | |
sortedBy: \.date, | |
in: section.language!, | |
order: .descending | |
), | |
on: context.site, | |
filter: { $0.sectionID == section.id }, | |
column: section.id == .album ? 5 : 2 | |
) | |
), | |
.footer(for: context.site, in: section.language!) | |
) | |
) | |
} | |
func makeItemHTML(for item: Item<Site>, | |
context: PublishingContext<Site>) throws -> HTML { | |
return HTML( | |
.lang(item.language!), | |
.head(for: item, on: context.site, stylesheetPaths: ["/styles.css", "/gallery.css"]), | |
.body( | |
.class("item-page"), | |
.header( | |
for: item, | |
for: context, | |
selectedSection: item.sectionID, | |
backgroundImage: item.imagePath?.string | |
), | |
.wrapper( | |
.article( | |
.div( | |
.class("content"), | |
.contentBody(item.body) | |
), | |
.span("\("Tagged with: ".localized(in: item.language!))"), | |
.tagList(for: item, on: context.site), | |
.div( | |
.class("prevNextItem"), | |
{ | |
let allItems = context.allItems(sortedBy: \.date, in: item.language!, order: .descending).filter({$0.sectionID == item.sectionID}) | |
let itemIndex = allItems.firstIndex(of: item) | |
let previous: Node<HTML.BodyContext> | |
let next: Node<HTML.BodyContext> | |
if allItems.first != item { | |
let previousItem = allItems[allItems.index(before: itemIndex!)] | |
previous = .a( | |
.href(context.site.path(for: previousItem)), | |
.text("← " + previousItem.title), | |
.style("left: 0px;") | |
) | |
} else { | |
previous = .empty | |
} | |
if allItems.last != item { | |
let nextItem = allItems[allItems.index(after: itemIndex!)] | |
next = .a( | |
.href(context.site.path(for: nextItem)), | |
.text(nextItem.title + " →"), | |
.style("right: 0px;") | |
) | |
} else { | |
next = .empty | |
} | |
return .group(previous, next) | |
}() | |
) | |
) | |
), | |
.footer(for: context.site, in: item.language!) | |
) | |
) | |
} | |
func makePageHTML(for page: Page, | |
context: PublishingContext<Site>) throws -> HTML { | |
HTML( | |
.lang(page.language!), | |
.head(for: page, on: context.site, stylesheetPaths: ["/styles.css", "/gallery.css"]), | |
.body( | |
.header(for: page, for: context, selectedSection: nil), | |
.wrapper( | |
.raw(page.content.body.html) | |
), | |
.footer(for: context.site, in: page.language!) | |
) | |
) | |
} | |
func makeTagListHTML(for page: TagListPage, | |
context: PublishingContext<Site>) throws -> HTML? { | |
HTML( | |
.lang(page.language!), | |
.head(for: page, on: context.site, stylesheetPaths: ["/styles.css"]), | |
.body( | |
.header(for: page, for: context, selectedSection: nil, title: "\("All tags".localized(in: page.language!))"), | |
.wrapper( | |
.ul( | |
.class("all-tags"), | |
.forEach(page.tags.sorted(by: { (tag1, tag2) -> Bool in | |
tag1.string.lowercased() < tag2.string.lowercased() | |
})) { tag in | |
.li( | |
.class(tag.colorfiedClass(in: page.language!)), | |
.a( | |
.href(context.site.path(for: tag, in: page.language!)), | |
.text(tag.string) | |
) | |
) | |
} | |
) | |
), | |
.footer(for: context.site, in: page.language!) | |
) | |
) | |
} | |
func makeTagDetailsHTML(for page: TagDetailsPage, | |
context: PublishingContext<Site>) throws -> HTML? { | |
HTML( | |
.lang(page.language!), | |
.head(for: page, on: context.site, stylesheetPaths: ["/styles.css"]), | |
.body( | |
.header(for: page, for: context, selectedSection: nil), | |
.wrapper( | |
.h1( | |
"\("Tagged with: ".localized(in: page.language!))", | |
.span( | |
.class(page.tag.colorfiedClass(in: page.language!)), | |
.text(page.tag.string) | |
) | |
), | |
.itemList( | |
for: context.items( | |
taggedWith: page.tag, | |
sortedBy: \.date, | |
in: page.language!, | |
order: .descending | |
), | |
on: context.site, | |
column: 2 | |
) | |
), | |
.footer(for: context.site, in: page.language!) | |
) | |
) | |
} | |
} | |
} | |
extension Node where Context == HTML.BodyContext { | |
static func altnativeLinks(for location: Location, for context: PublishingContext<CzjIo>) -> Node<HTML.BodyContext> { | |
var altnativeLinks = context.alternateLinks(for: location) | |
if altnativeLinks.count != context.site.languages.count, let tagDetailsPage = location as? TagDetailsPage { | |
let alternatives = LocalizedTag.alternatives(for: tagDetailsPage.tag.string, in: tagDetailsPage.language!) | |
for language in alternatives.keys { | |
altnativeLinks[language] = context.site.path(for: Tag(alternatives[language]!) , in: language) | |
} | |
} | |
if altnativeLinks.count <= 1 { | |
return .empty | |
} | |
return | |
.group( | |
.input( | |
.type(.checkbox), | |
.id("language-icon"), | |
.name("language"), | |
.value("show-language") | |
), | |
.label( | |
.class("dropdown header-item nav-background"), | |
.for("language-icon"), | |
.div( | |
.class("dropbtn"), | |
.raw("<svg><use href=\"/images/language.svg#language\"></use></svg>") | |
), | |
.div(.class("hspacer")), | |
.nav( | |
.class("dropdown-content nav-background"), | |
.forEach(altnativeLinks.sorted(by: {$0.0.rawValue < $1.0.rawValue})) { | |
.group( | |
.a( | |
.if( | |
$0.0 != location.language!, | |
.href($0.1) | |
), | |
.text(context.site.name(for: $0.0)) | |
), | |
.div(.class("vline")) | |
) | |
} | |
) | |
) | |
) | |
} | |
} | |
extension Node where Context == HTML.BodyContext { | |
static func wrapper(_ nodes: Node...) -> Node { | |
.div(.class("wrapper"), .group(nodes)) | |
} | |
static func sectionNav( | |
for location: Location, | |
for context: PublishingContext<CzjIo>, | |
selectedSection: CzjIo.SectionID? | |
) -> Node { | |
let sectionIDs = CzjIo.SectionID.allCases | |
return | |
.nav( | |
.class("section-nav nav-background"), | |
.forEach(sectionIDs.filter({$0 != .academic})) { section in | |
.group( | |
.a( | |
.class(section == selectedSection ? "selected" : ""), | |
.href( | |
context.site.path( | |
for: context.section(section, in: location.language!) | |
) | |
), | |
.text(context.sections[section].id.rawValue.localized(in: location.language!)) | |
), | |
.div(.class("vline")) | |
) | |
} | |
) | |
} | |
static func navigator( | |
for location: Location, | |
for context: PublishingContext<CzjIo>, | |
selectedSection: CzjIo.SectionID? | |
) -> Node { | |
var altnativeLinks = context.alternateLinks(for: location) | |
if altnativeLinks.count != context.site.languages.count, let tagDetailsPage = location as? TagDetailsPage { | |
let alternatives = LocalizedTag.alternatives(for: tagDetailsPage.tag.string, in: tagDetailsPage.language!) | |
for language in alternatives.keys { | |
altnativeLinks[language] = context.site.path(for: Tag(alternatives[language]!) , in: language) | |
} | |
} | |
return | |
.nav( | |
.class("section-nav"), | |
.input( | |
.type(.checkbox), | |
.id("section-icon"), | |
.name("section"), | |
.value("show-section") | |
), | |
.label( | |
.class("dropdown header-item"), | |
.for("section-icon"), | |
.div( | |
.class("dropbtn"), | |
.raw("<svg><path d=\"M4,14H24 M4,6H24 M4,22H24\"/></svg>") | |
), | |
.div(.class("hspacer")), | |
.div( | |
.class("dropdown-content nav-background"), | |
.forEach(context.sections.ids) { section in | |
.group( | |
.a( | |
.class( section == selectedSection ? "selected" : ""), | |
.href( | |
context.site.path( | |
for: context.section(section, in: location.language!) | |
) | |
), | |
.text(context.sections[section].id.rawValue.localized(in: location.language!)) | |
), | |
.div(.class("line")) | |
) | |
} | |
) | |
), | |
.if(altnativeLinks.count > 1, | |
.group( | |
.div(.class("line")), | |
.input( | |
.type(.checkbox), | |
.id("language-icon"), | |
.name("language"), | |
.value("show-language") | |
), | |
.label( | |
.class("dropdown header-item"), | |
.for("language-icon"), | |
.div( | |
.class("dropbtn"), | |
.raw("<svg><circle cx=\"14\" cy=\"14\" r=\"12\"/><path d=\"M2,14 H26 M14,2 V26 M4,7 C8,8 11,8 14,8 C17,8 20,8 24,7 M4,21 C8,20 11,20 14,20 C17,20 20,20 24,21\"/><ellipse cx=\"14\" cy=\"14\" rx=\"6\" ry=\"12\"/></svg>") | |
), | |
.div(.class("hspacer")), | |
.div( | |
.class("dropdown-content nav-background"), | |
.forEach(altnativeLinks.sorted(by: {$0.0.rawValue < $1.0.rawValue})) { | |
.group( | |
.a( | |
.if( | |
$0.0 != location.language!, | |
.href($0.1) | |
), | |
.text(context.site.name(for: $0.0)) | |
), | |
.div(.class("line")) | |
) | |
} | |
) | |
) | |
) | |
) | |
) | |
} | |
static func header( | |
for location: Location, | |
for context: PublishingContext<CzjIo>, | |
selectedSection: CzjIo.SectionID?, | |
backgroundImage: String? = nil,//"/images/header.png", | |
title: String? = nil | |
) -> Node { | |
return .header( | |
.div( | |
.class("blurred"), | |
.wrapper( | |
.a( | |
.class("header-item site-name"), | |
.href("/\(location.language! == .english ? "" : context.site.pathPrefix(for: location.language!) + "/")"), | |
.raw(""" | |
<svg><circle cx="24" cy="24" r="22.5"/><path d="M40,9H18L10,18H23L7,37 8,39H30L38,30 25,30 41,11M35 18H45M3 30H13"/></svg> | |
""") | |
), | |
.div( | |
.class("page-title"), | |
.text(title != nil ? title! : (location is Index ? "CZJ" : location.title)) | |
), | |
.navigator(for: location, for: context, selectedSection: selectedSection) | |
) | |
), | |
.if( | |
backgroundImage != nil, | |
.img( | |
.src(backgroundImage ?? ""), | |
.class("header-background") | |
) | |
) | |
) | |
return .header( | |
.div( | |
.class("blurred"), | |
.wrapper( | |
.a( | |
.class("header-item site-name"), | |
.href("/\(location.language! == .english ? "" : context.site.pathPrefix(for: location.language!) + "/")"), | |
.element( | |
named: "svg", | |
nodes: [ | |
.element( | |
named: "use", | |
nodes: [.href("/images/logo.svg#logo")] | |
)] | |
) | |
), | |
.sectionNav(for: location, for: context, selectedSection: selectedSection), | |
.altnativeLinks( | |
for: location, | |
for: context | |
) | |
) | |
), | |
.if( | |
backgroundImage != nil, | |
.img( | |
.src(backgroundImage ?? ""), | |
.attribute(named: "style", value: "height: 100%; width:100%; position: absolute; top:0px; left:0px; opacity: 0.6; z-index: -1000") | |
) | |
) | |
) | |
} | |
static func badge(key: String, value: String, keyClass: String? = nil, valueClass: String? = nil) -> Node { | |
return .div( | |
.class("badge"), | |
.div( | |
.class("badge-key \(keyClass ?? "variant-medium")"), | |
.text(key) | |
), | |
.div( | |
.class("badge-value \(valueClass ?? "variant-\(Int.random(in: 0..<12))")"), | |
.text(value.replacingCharacters(in: value.range(of: String(value.first!))!, with: value.first!.uppercased())) | |
) | |
) | |
} | |
static func itemList<T: MultiLanguageWebsite>(for items: [Item<T>], on site: T, filter: (Item<T>) -> Bool = {_ in true}, column: Int = 1, max: Int = 0) -> Node { | |
let columnClass: String = ["", "single-column", "double-column", "triple-column", "quadruple-column", "quintuple-column"][column] | |
return .ul( | |
.class("item-list flex \(columnClass)"), | |
.forEach( | |
items.filter(filter).prefix(max == 0 ? Int.max : max) | |
) { item in | |
if item.sectionID.rawValue == "album" { | |
return albumItem(for: item, on: site) | |
} else { | |
return articleItem(for: item, on: site) | |
} | |
} | |
) | |
} | |
static func articleItem<T: MultiLanguageWebsite>(for item: Item<T>, on site: T) -> Node<HTML.ListContext> { | |
Node<HTML.ListContext> | |
.li( | |
.article( | |
.class("article-item"), | |
.h1( | |
.a( | |
.href(site.path(for: item)), | |
.text(item.title) | |
) | |
), | |
.tagList(for: item, on: site), | |
.p( .text(item.description) ) | |
) | |
) | |
} | |
static func albumItem<T: MultiLanguageWebsite>(for item: Item<T>, on site: T) -> Node<HTML.ListContext> { | |
Node<HTML.ListContext>.li( | |
.article( | |
.class("album-item"), | |
.a( | |
.href(site.path(for: item)), | |
.img( | |
.src(item.imagePath!.string.replacingOccurrences(of: ".jpg", with: "-thumbnail.jpg")), | |
.class("item-background") | |
), | |
.h1( | |
.text(item.title) | |
) | |
), | |
.p(.text(item.description)) | |
)) | |
} | |
static func tagList<T: MultiLanguageWebsite>(for item: Item<T>, on site: T) -> Node { | |
return .ul(.class("tag-list"), .forEach(item.tags) { tag in | |
.li( | |
.class(tag.colorfiedClass(in: item.language ?? site.language)), | |
.a( | |
.href(site.path(for: tag, in: item.language ?? site.language)), | |
.text(tag.string) | |
) | |
) | |
}) | |
} | |
static func footer<T: MultiLanguageWebsite>(for site: T, in language: Language) -> Node { | |
return .footer( | |
.a( | |
.class("browse-all"), | |
.text("\("Browse all tags".localized(in: language))"), | |
.href(site.tagListPath(in: language)) | |
), | |
.p( | |
.text("©2020 Zhijin Chen") | |
), | |
.p( | |
.text("\("Generated using ".localized(in: language))"), | |
.a( | |
.text("Publish"), | |
.href("https://github.com/johnsundell/publish") | |
), | |
.text("\(". 100% JavaScript-free.".localized(in: language))") | |
), | |
.p(.a( | |
.text("\("RSS feed".localized(in: language))"), | |
.href("/\(site.pathPrefix(for: language))/feed.rss") | |
)) | |
) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment