summaryrefslogtreecommitdiffstats
path: root/apps/base
diff options
context:
space:
mode:
Diffstat (limited to 'apps/base')
-rw-r--r--apps/base/builder.scm95
-rw-r--r--apps/base/templates/about.scm57
-rw-r--r--apps/base/templates/components.scm486
-rw-r--r--apps/base/templates/contact.scm72
-rw-r--r--apps/base/templates/hfge-about.scm52
-rw-r--r--apps/base/templates/hfge-download.scm36
-rw-r--r--apps/base/templates/hfge-git.scm37
-rw-r--r--apps/base/templates/home.scm121
-rw-r--r--apps/base/templates/menu.scm20
-rw-r--r--apps/base/templates/theme.scm143
-rw-r--r--apps/base/types.scm124
-rw-r--r--apps/base/utils.scm224
12 files changed, 1467 insertions, 0 deletions
diff --git a/apps/base/builder.scm b/apps/base/builder.scm
new file mode 100644
index 0000000..4e1d194
--- /dev/null
+++ b/apps/base/builder.scm
@@ -0,0 +1,95 @@
+;;; Harmonic Flow web site
+
+(define-module (apps base builder)
+ #:use-module (apps base templates about)
+ #:use-module (apps base templates contact)
+ #:use-module (apps base templates hfge-about)
+ #:use-module (apps base templates hfge-download)
+ #:use-module (apps base templates hfge-git)
+ #:use-module (apps base templates home)
+ #:use-module (apps base templates menu)
+ #:use-module (apps base types)
+ #:use-module (apps blog utils)
+ #:use-module (apps media data)
+ #:use-module (haunt html)
+ #:use-module (haunt artifact)
+ #:use-module (haunt post)
+ #:use-module (haunt utils)
+ #:use-module (srfi srfi-1)
+ #:export (builder))
+
+
+;;;
+;;; Application builder.
+;;;
+
+(define (builder site posts)
+ "Return the list of web resources that compose the app.
+
+ This procedure is a Haunt builder procedure.
+
+ SITE (<site>)
+ A site object that defines all the properties of the website. See
+ Haunt <site> objects for more information.
+
+ POSTS (list of <post>)
+ A list of post objects that represent articles from the blog. See
+ Haunt <post> objects for more information.
+
+ RETURN (list of <page>)
+ A list of page objects that represent the web resources of the
+ application. See Haunt <page> objects for more information."
+ (flatten
+ (list (menu-builder)
+ (home-builder site posts)
+ (about-builder)
+ (contact-builder)
+ (hfge-about-builder)
+ (hfge-download-builder)
+ (hfge-git-builder))))
+
+
+
+;;;
+;;; Helper builders.
+;;;
+
+
+(define (about-builder)
+ "Return a Haunt page representing the About page of the website."
+ (serialized-artifact "about/index.html" (about-t) sxml->html))
+
+
+(define (contact-builder)
+ "Return a Haunt page representing the Contact page of the website."
+ (serialized-artifact "contact/index.html" (contact-t) sxml->html))
+
+
+(define (hfge-about-builder)
+ "Return a Haunt page representing the HFGE about page of the website."
+ (serialized-artifact "hfge-about/index.html" (hfge-about-t) sxml->html))
+
+
+(define (hfge-download-builder)
+ "Return a Haunt page representing the HFGE download page of the website."
+ (serialized-artifact "hfge-download/index.html" (hfge-download-t) sxml->html))
+
+
+(define (hfge-git-builder)
+ "Return a Haunt page representing the HFGE git page of the website."
+ (serialized-artifact "hfge-git/index.html" (hfge-git-t) sxml->html))
+
+
+(define (home-builder site posts)
+ "Return a Haunt page representing the Home page of the website."
+ (let ((context
+ (list
+ (cons "screenshots" screenshots)
+ (cons "posts" (posts/latest posts 3))
+ )))
+ (serialized-artifact "index.html" (home-t context) sxml->html)))
+
+
+(define (menu-builder)
+ "Return a Haunt page representing the website menu."
+ (serialized-artifact "menu/index.html" (menu-t) sxml->html))
diff --git a/apps/base/templates/about.scm b/apps/base/templates/about.scm
new file mode 100644
index 0000000..bb7e91d
--- /dev/null
+++ b/apps/base/templates/about.scm
@@ -0,0 +1,57 @@
+;;; Harmonic Flow web site
+
+(define-module (apps base templates about)
+ #:use-module (apps base templates components)
+ #:use-module (apps base templates theme)
+ #:use-module (apps base types)
+ #:use-module (apps base utils)
+ #:use-module (apps i18n)
+ #:export (about-t))
+
+
+(define (about-t)
+ "Return the About page in SHTML."
+ (theme
+ #:title (C_ "webpage title" '("About the Site |"))
+ #:description
+ (G_ "This website is dedicated to free software primarily in the area of graphics engines but also free software in general.")
+ #:keywords
+ (string-split ;TRANSLATORS: |-separated list of webpage keywords
+ (G_ "About|Harmonic Flow") #\|)
+ #:active-menu-item (C_ "website menu" "About Site")
+ #:css (list
+ (hfweb-url "static/base/css/page.css"))
+ #:crumbs (list (crumb (C_ "website menu" "About Site") "./"))
+ #:content
+ `(main
+ (section
+ (@ (class "page centered-block limit-width"))
+ ,(G_ `(h2 "About the Site"))
+
+ ,(G_
+ `(p
+ "This website is dedicated to free software primarily in the area of
+ graphics engines but also free software in general."
+ ))
+
+ ,(G_
+ `(p
+ "The "
+ ,(G_ `(a (@ (href ,(hfweb-url "blog/"))) " Harmonic Flow Blog"))
+ " will cover any topics that is of interest (mostly related to free
+ software in some way)."
+ ))
+
+ ,(G_
+ `(p
+ "Checkout "
+ ,(G_ `(a (@ (href ,(hfweb-url "hfge-about/"))) " HFGE 3D graphics engine"))
+ " for more information about a brand new cross platform 3D graphics
+ engine currently in development."
+ ))
+
+ ,(G_
+ `(p
+ "Please use the "
+ ,(G_ `(a (@ (href ,(hfweb-url "contact/"))) " contact page"))
+ " for contact information."))))))
diff --git a/apps/base/templates/components.scm b/apps/base/templates/components.scm
new file mode 100644
index 0000000..8d7d521
--- /dev/null
+++ b/apps/base/templates/components.scm
@@ -0,0 +1,486 @@
+;;; Harmonic Flow web site
+
+(define-module (apps base templates components)
+ #:use-module (apps aux lists)
+ #:use-module (apps aux strings)
+ #:use-module (apps aux sxml)
+ #:use-module (apps aux web)
+ #:use-module (apps base types)
+ #:use-module (apps base utils)
+ #:use-module (apps i18n)
+ #:use-module (srfi srfi-1)
+ #:use-module (ice-9 match)
+ #:export (breadcrumbs
+ button-big
+ button-little
+ contact-preview
+ contact->shtml
+ horizontal-line
+ horizontal-separator
+ horizontal-skip
+ link-more
+ link-subtle
+ link-yellow
+ manual-href
+ manual-devel-href
+ manual-link-yellow
+ navbar
+ page-indicator
+ page-selector))
+
+
+;;;
+;;; Components.
+;;;
+
+(define (breadcrumbs crumbs)
+ "Return an SHTML nav element representing the breadcrumbs.
+
+ CRUMBS (list)
+ A non-empty list of <crumb> objects as defined in
+ (apps base types)."
+ `(nav
+ (@ (class "breadcrumbs"))
+ ,(G_ `(h2 (@ (class "a11y-offset")) "Your location:"))
+
+ ,(G_ `(a (@ (class "crumb") (href ,(hfweb-url))) "Home")) (span " → ")
+ ,@(separate (crumbs->shtml crumbs) '(span " → "))))
+
+
+(define (crumbs->shtml crumbs)
+ "Return the list of CRUMBS as list of SHTML a elements.
+
+ CRUMBS (list)
+ A non-empty list of <crumb> objects as defined in
+ (apps base types)."
+ (cond ((= (length crumbs) 1)
+ (cons
+ `(a
+ (@ (class "crumb crumb-active")
+ (href ,(crumb-url (first crumbs))))
+ ,(crumb-label (first crumbs)))
+ '()))
+ (else
+ (cons
+ `(a
+ (@ (class "crumb")
+ (href ,(crumb-url (first crumbs))))
+ ,(crumb-label (first crumbs)))
+ (crumbs->shtml (rest crumbs))))))
+
+
+(define* (button-big #:key (label "Button") (url "#") (light #false))
+ "Return an SHTML a element that looks like a big button.
+
+ LABEL (string)
+ The text for the button. For example: 'Download!'.
+
+ URL (string)
+ A URL to use for the href attribute of the a element. If not
+ specified, the value defaults to #.
+
+ LIGHT (boolean)
+ True if the button is going to be used on a dark background; false
+ otherwise (this is the default)."
+ `(a
+ (@ (class ,(string-append "button-big" (if light " button-light" "")))
+ (href ,url))
+ ,label))
+
+
+(define* (button-little #:key (label "Button") (url "#") (active #false))
+ "Return an SHTML a element that looks like a little button.
+
+ LABEL (string)
+ The text for the button. For example: 'Next'.
+
+ URL (string)
+ A URL to use for the href attribute of the a element. If not
+ specified, the value defaults to #.
+
+ ACTIVE (boolean)
+ True if the button should be highlighted as active (on)."
+ `(a
+ (@ (class ,(string-append "button-little"
+ (if active " button-little-active" "")))
+ (href ,url))
+ ,label))
+
+
+(define (contact-preview contact)
+ "Return an SHTML preview of the given contact object.
+
+ CONTACT (<contact>)
+ A contact object as defined in (apps base types)."
+ `(a
+ (@ (class "item-preview")
+ (href ,(contact-url contact)))
+ (h3 ,(contact-name contact))
+ (p
+ ,(string-summarize
+ (sxml->string*
+ (match (contact-description contact)
+ ((and multilingual (((? string?) (? string?)) ...))
+ (let ((code %current-lang))
+ (match (assoc code multilingual)
+ ((code blurb) blurb)
+ (else (assoc "en" multilingual)))))
+ (blurb
+ blurb)))
+ 30)
+ "…")))
+
+
+(define (language-tag lang)
+ `(span (@ (class "button-little button-little-active")
+ (style "text-align: center; width: 20px; vertical-align: middle"))
+ ,lang))
+
+(define (contact->shtml contact)
+ "Return an SHTML representation of the given contact object.
+
+ CONTACT (<contact>)
+ A contact object as defined in (apps base types)."
+ `(div
+ (@ (class "contact-medium"))
+ (a (@ (href ,(contact-url contact))) (b ,(contact-name contact)))
+ ,(if (string=? (contact-log contact) "")
+ ""
+ `(small
+ " (" ,(G_ `(a (@ (href ,(contact-log contact))) "archive")) ") "))
+
+ ;; The description can be a list of language/blurb pairs.
+ ,(match (contact-description contact)
+ ((((? string? languages) blurbs) ...)
+ `(div (@ (id "help-hfweb-mailing-list-description"))
+ ,@(map (lambda (language blurb)
+ `(div (@ (style "display: flex; align-items: center; margin: 0 10px 10px 0"))
+ ,(language-tag language)
+ (div (@ (lang ,language) (style "flex: 1")) ,blurb)))
+ languages
+ blurbs)))
+ (blurb
+ blurb))))
+
+
+(define* (horizontal-separator #:key (light #false))
+ "Return an SHTML img element that works as a separator.
+
+ LIGHT (boolean)
+ True if the separator is going to be used on a dark background;
+ false otherwise (this is the default)."
+ `(img
+ (@ (class "h-separator")
+ ,(if light
+ `(src ,(hfweb-url "static/base/img/h-separator.png"))
+ `(src ,(hfweb-url "static/base/img/h-separator-dark.png")))
+ (alt ""))))
+
+(define (horizontal-skip)
+ "Return SHTML for a small horizontal space."
+ `(span (@ (class "hskip"))))
+
+(define (horizontal-line)
+ "Return SHTML for a visible separator to be used in a dropdown menu
+like a menu item."
+ `(img (@ (class "hline")
+ (src ,(hfweb-url "static/base/img/h-separator.png"))
+ (alt ""))))
+
+
+(define* (link-more #:key (label "More") (url "#") (light #false))
+ "Return an SHTML a element that looks like a 'more →' link.
+
+ LABEL (string)
+ The text for the link. For example: 'Read the manual'.
+
+ URL (string)
+ A URL to use for the href attribute of the a element. If not
+ specified, the value defaults to #.
+
+ LIGHT (boolean)
+ True if the link is going to be used on a dark background; false
+ otherwise (this is the default)."
+ `(a
+ (@ (class ,(string-append "link-more" (if light " link-more-light" "")))
+ (href ,url))
+ ,label))
+
+
+(define* (link-subtle #:key (label "link") (url "#"))
+ "Return an SHTML a element that does not stand too much on white backgrounds.
+
+ LABEL (string)
+ The text for the link. For example: 'Additional notes'.
+
+ URL (string)
+ The URL of the link. If not specified, the value defaults to #."
+ `(a (@ (class "link-subtle") (href ,url)) ,label))
+
+
+(define* (link-yellow #:key (label "link") (url "#"))
+ "Return a yellow SHTML a element to use on dark backgrounds.
+
+ LABEL (string)
+ The text for the link. For example: 'read the manual'.
+
+ URL (string)
+ The URL of the link. If not specified, the value defaults to #."
+ `(a (@ (class "link-yellow") (href ,url)) ,label))
+
+
+
+
+(define (manual-href label manual-lang _1 subpath _2)
+ "Return an HTML a element with its href attribute pointing to the
+manual. It can be marked for translation as:
+
+ (G_ (manual-href \"some-text\" (G_ \"en\") (G_ \"Some-section.html\")))
+
+ LABEL (string)
+ The content of the a element.
+
+ MANUAL-LANG (string)
+ The normalized language for the Guix manual as produced by
+'doc/build.scm' in the Guix source tree, i.e. \"en\" for the English
+manual.
+
+ SUBPATH (string)
+ The same as in the manual-url procedure."
+ ;; The _ arguments are placeholders for args added by G_, cf. i18n-howto.txt.
+ `(a (@ (href ,(manual-url subpath #:language manual-lang))) ,label))
+
+(define (manual-devel-href label manual-lang _1 subpath _2)
+ "Similar to 'manual-href', but link to the development manual."
+ ;; The _ arguments are placeholders for args added by G_, cf. i18n-howto.txt.
+ `(a (@ (href ,(manual-devel-url subpath #:language manual-lang))) ,label))
+
+(define* (manual-link-yellow label manual-lang _1 #:optional (subpath "") _2)
+ "Return a link-yellow component pointing to the manual. It can be
+used like this:
+
+ (manual-link-yellow \"some-text\" (G_ \"en\") \"Package-Management.html\")
+
+ LABEL (string)
+ The label of the link-yellow.
+
+ MANUAL-LANG (string)
+ The normalized language for the Guix manual as produced by
+'doc/build.scm' in the Guix source tree, i.e. \"en\" for the English
+manual.
+
+ SUBPATH (string)
+ The same as in the manual-url procedure."
+ ;; The _ arguments are placeholders for args added by G_, cf. i18n-howto.txt.
+ (link-yellow
+ #:label label
+ #:url (manual-url subpath #:language manual-lang)))
+
+
+
+
+(define* (menu-dropdown #:key (label "Item") (active-item "") (url "#") (items '()))
+ "Return an SHTML li element representing a dropdown for the navbar.
+
+ LABEL (string)
+ The text for the dropdown. For example: 'About'.
+
+ ACTIVE-ITEM (string)
+ A string representing the label of the current active item in the
+ navigation bar. If the values of LABEL and ACTIVE-ITEM are the
+ same, the dropdown is highlighted.
+
+ URL (string)
+ The URL of the web resource referenced by the dropdown. Any
+ value used for an HTML a element is valid. If not specified, the
+ value defaults to #.
+
+ ITEMS (list of menu items)
+ A list of menu items as returned by the menu-item procedure in this
+ same module. If not provided, the value defaults to an empty list."
+ (let ((label-hash (number->string (string-hash label))))
+ `(li
+ (@ (class ,(if (string=? (string-downcase label)
+ (string-downcase active-item))
+ "menu-item menu-item-active dropdown dropdown-btn"
+ "menu-item dropdown dropdown-btn")))
+ ,@(let ((id (string-append "visible-dropdown-" label-hash)))
+ `(;; show dropdown when button is checked:
+ (style ,(string-append "#" id ":checked ~ #submenu-" label-hash "
+{
+ min-width: 150px;
+ width: max-content;
+
+ /* reset to initial values: */
+ height: auto;
+ overflow: visible;
+}"))
+ ;; show uncheck version of button iff button is checked
+ (style ,(string-append "#" id ":checked \
+~ label[for=all-dropdowns-hidden]
+{
+ display: inline;
+}"))
+ (style "label[for=all-dropdowns-hidden]
+{
+ display: none;
+}")
+ ;; show check version of button iff button is unchecked
+ (style ,(string-append "#" id ":checked ~ label[for=" id "]
+{
+ display: none;
+}"))
+ (input (@ (class "menu-hidden-input")
+ (type "radio")
+ (name "dropdown")
+ (id ,id)))
+ (label
+ (@ (for ,id))
+ ,label)
+ (label
+ (@ (for "all-dropdowns-hidden"))
+ ,label)))
+ (div
+ (@ (class "submenu")
+ (id ,(string-append "submenu-" label-hash)))
+ (div (@ (class "submenu-triangle"))
+ " ")
+ (ul ,@items)))))
+
+
+(define* (menu-item #:key (label "Item") (active-item "") (url "#"))
+ "Return an SHTML li element representing an item for the navbar.
+
+ LABEL (string)
+ The text for the item. For example: 'About'.
+
+ ACTIVE-ITEM (string)
+ A string representing the label of the current active item in the
+ navigation bar. If the values of LABEL and ACTIVE-ITEM are the
+ same, the menu item is highlighted.
+
+ URL (string)
+ The URL of the web resource referenced by the menu item. Any
+ value used for an HTML a element is valid. If not specified, the
+ value defaults to #."
+ `(li
+ (a
+ (@ (class
+ ,(if (string=? (string-downcase label) (string-downcase active-item))
+ "menu-item menu-item-active"
+ "menu-item"))
+ (href ,url))
+ ,label)))
+
+
+(define* (navbar #:key (active-item "About"))
+ "Return an SHTML header element with the given ACTIVE ITEM highlighted."
+ `(header
+ (@ (class "navbar"))
+
+ ;; Branding.
+ (h1
+ (a
+ (@ (class "branding") (href ,(hfweb-url)))
+ ,(C_ "website menu" `(span (@ (class "a11y-offset")) "Harmonic Flow"))))
+
+ ;; Menu.
+ (nav (@ (class "menu"))
+ ,(G_ `(h2 (@ (class "a11y-offset")) "website menu:"))
+ (input (@ (class "menu-hidden-input")
+ (type "radio")
+ (name "dropdown")
+ (id "all-dropdowns-hidden")))
+ (ul
+ ,(C_ "website menu" (menu-item #:label "Overview" #:active-item active-item #:url (hfweb-url)))
+
+ ,(menu-dropdown #:label (C_ "website menu" "HFGE")
+ #:active-item active-item
+ #:items
+ (list
+ (C_ "website menu"
+ (menu-item #:label "About"
+ #:active-item active-item
+ #:url (hfweb-url "hfge-about/")))
+ (C_ "website menu"
+ (menu-item #:label "Download"
+ #:active-item active-item
+ #:url (hfweb-url "hfge-download/")))
+ (C_ "website menu"
+ (menu-item #:label "Git"
+ #:active-item active-item
+ #:url (hfweb-url "hfge-git/")))))
+
+ ,(menu-dropdown #:label (C_ "website menu" "Media")
+ #:active-item active-item
+ #:items
+ (list
+ (C_ "website menu"
+ (menu-item #:label "Screenshots"
+ #:active-item active-item
+ #:url (hfweb-url "screenshots/")))))
+
+ ,(C_ "website menu" (menu-item #:label "Blog" #:active-item active-item #:url (hfweb-url "blog/")))
+
+ ,(menu-dropdown #:label (C_ "website menu" "About") #:active-item active-item
+ #:items
+ (list
+ (C_ "website menu" (menu-item #:label "About Site" #:active-item active-item #:url (hfweb-url "about/")))
+ (horizontal-line)
+ (C_ "website menu" (menu-item #:label "Contact" #:active-item active-item #:url (hfweb-url "contact/")))))
+ ,(horizontal-skip)))
+
+
+ ;; Menu button.
+ (a
+ (@ (class "menu-btn")
+ (href ,(hfweb-url "menu/"))) "")))
+
+(define (page-indicator page-number total-pages)
+ "Return an SHTML span element in the form 'page X of Y' if there is
+ more than one page. Otherwise, return an empty string.
+
+ PAGE-NUMBER (number)
+ The number of the page that the user is seeing.
+
+ TOTAL-PAGES (number)
+ The total number of pages that should be displayed."
+ (if (> total-pages 1)
+ (G_ `(span
+ (@ (class "page-number-indicator"))
+ " (Page " ,(number->string page-number)
+ " of " ,(number->string total-pages) ")"))
+ ""))
+
+
+(define (page-selector pages active-page base-url)
+ "Return an SHTML nav element representing a page selection widget.
+
+ PAGES (number)
+ The total number of pages that should be displayed.
+
+ ACTIVE-PAGE (number)
+ The number of the page that should be displayed as active.
+
+ BASE-URL (string)
+ Absolute URL path to prepend to page numbers. For example:
+ '/en/blog'. This would result in URLs like: '/en/blog/page/N',
+ where N is the number of the page."
+ `(nav
+ (@ (class "page-selector"))
+ (h3
+ (@ (class "a11y-offset"))
+ ,(G_ (string-append "Page " (number->string active-page) " of "
+ (number->string pages) ". Go to another page: ")))
+ ,(if (> pages 1)
+ (map
+ (lambda (page-number)
+ (list
+ (button-little
+ #:label page-number
+ #:url (url-path-join base-url "page"
+ (number->string page-number) "")
+ #:active (= page-number active-page))
+ " ")) ; NOTE: Force space for readability in non-CSS browsers.
+ (iota pages 1))
+ "")))
diff --git a/apps/base/templates/contact.scm b/apps/base/templates/contact.scm
new file mode 100644
index 0000000..88ab2a8
--- /dev/null
+++ b/apps/base/templates/contact.scm
@@ -0,0 +1,72 @@
+;;; Harmonic Flow web site
+
+(define-module (apps base templates contact)
+ #:use-module (apps base templates components)
+ #:use-module (apps base templates theme)
+ #:use-module (apps base types)
+ #:use-module (apps base utils)
+ #:use-module (apps i18n)
+ #:export (contact-t))
+
+
+(define (contact-t)
+ "Return the Contact page in SHTML."
+ (theme
+ #:title (C_ "webpage title" '("Contact |"))
+ #:description
+ (G_ "A list of channels to communicate with Harmonic Flow directly about anything you want.")
+ #:keywords
+ (string-split ;TRANSLATORS: |-separated list of webpage keywords
+ (G_ "Contact|Harmonic Flow") #\|)
+ #:active-menu-item (C_ "website menu" "Contact")
+ #:css (list
+ (hfweb-url "static/base/css/page.css"))
+ #:crumbs (list (crumb (C_ "website menu" "Contact") "./"))
+ #:content
+ `(main
+ (section
+ (@ (class "page centered-block limit-width"))
+ ,(G_ `(h2 "Contact"))
+
+ ,(G_
+ `(p
+ "Contact info email / xmpp: andreas at harmonicflow dot o r g"))
+
+ ,(G_
+ `(p
+ "Please download & import the following "
+ ,(G_ `(a (@ (href ,(hfweb-url "static/gnupg-pubkeys/andreas-pubkey.asc"))) " GPG key"))
+ " if you want to send me encrypted email:"
+ ))
+
+ ,(G_
+ `(pre
+ "gpg --import /path/to/andreas-pubkey.asc"))
+ ,(G_
+ `(p
+ ".. or by using the following command:"
+ ))
+
+ ,(G_
+ `(pre
+ "gpg --recv-keys 0x14121B58362B5F22"))
+
+ ,(G_
+ `(p
+ "The public key fingerprint should be:"))
+
+ ,(G_
+ `(pre
+ "9ECF C528 9712 3654 F76B 63FD 1412 1B58 362B 5F22"))
+
+ ,(G_
+ `(p
+ "For more information about encryption and GnuPG, see: "
+ ,(G_ `(a (@ (href "https://www.gnupg.org"))
+ "GnuPG website."))
+ ))
+
+ ,(G_
+ `(p
+ "The above public key is also used for signing releases."))
+ ))))
diff --git a/apps/base/templates/hfge-about.scm b/apps/base/templates/hfge-about.scm
new file mode 100644
index 0000000..ab0b795
--- /dev/null
+++ b/apps/base/templates/hfge-about.scm
@@ -0,0 +1,52 @@
+;;; Harmonic Flow web site
+
+(define-module (apps base templates hfge-about)
+ #:use-module (apps base templates components)
+ #:use-module (apps base templates theme)
+ #:use-module (apps base types)
+ #:use-module (apps base utils)
+ #:use-module (apps i18n)
+ #:export (hfge-about-t))
+
+
+(define (hfge-about-t)
+ "Return the About HFGE page in SHTML."
+ (theme
+ #:title (C_ "webpage title" '("About HFGE |"))
+ #:description
+ (G_ "HFGE is a cross platform 3D game engine written in C++ and distributed under the zlib license.")
+ #:keywords
+ (string-split ;TRANSLATORS: |-separated list of webpage keywords
+ (G_ "HFGE|Harmonic Flow|Graphics Engine|Game Engine|3D game engine|cross platform|Free Software|C++|Libre software|Graphics Programming|Vulkan|Download") #\|)
+ #:active-menu-item (C_ "website menu" "About")
+ #:css (list
+ (hfweb-url "static/base/css/page.css"))
+ #:crumbs (list (crumb (C_ "website menu" "About HFGE") "./"))
+ #:content
+ `(main
+ (section
+ (@ (class "page centered-block limit-width"))
+ ,(G_ `(h2 "About HFGE"))
+
+ ,(G_
+ `(p
+ "HFGE is a new cross platform 3D engine currently in development. HFGE
+ is written in C++ and will initially support Windows and GNU/Linux
+ platforms. HFGE will use a modern renderer in the form of Vulkan.
+ "))
+
+ ,(G_
+ `(p
+ "HFGE is free / libre software and will be distributed under the zlib
+ license. This license allows you to use HFGE freely in any software.
+ "))
+
+ ,(G_
+ `(p
+ "More information, "
+ ,(G_ `(a (@ (href ,(hfweb-url "hfge-download/"))) " downloads"))
+ " and"
+ ,(G_ `(a (@ (href ,(hfweb-url "hfge-git/"))) " git repoistory"))
+ " will be available once it's ready for public release."
+ ))
+ ))))
diff --git a/apps/base/templates/hfge-download.scm b/apps/base/templates/hfge-download.scm
new file mode 100644
index 0000000..0e91657
--- /dev/null
+++ b/apps/base/templates/hfge-download.scm
@@ -0,0 +1,36 @@
+;;; Harmonic Flow web site
+
+(define-module (apps base templates hfge-download)
+ #:use-module (apps base templates components)
+ #:use-module (apps base templates theme)
+ #:use-module (apps base types)
+ #:use-module (apps base utils)
+ #:use-module (apps i18n)
+ #:export (hfge-download-t))
+
+
+(define (hfge-download-t)
+ "Return the Download HFGE page in SHTML."
+ (theme
+ #:title (C_ "webpage title" '("Download HFGE |"))
+ #:description
+ (G_ "Download the latest version of HFGE. Source code is available.")
+ #:keywords
+ (string-split ;TRANSLATORS: |-separated list of webpage keywords
+ (G_ "Download|HFGE|Source Code") #\|)
+ #:active-menu-item (C_ "website menu" "Download")
+ #:css (list
+ (hfweb-url "static/base/css/page.css"))
+ #:crumbs (list (crumb (C_ "website menu" "Download HFGE") "./"))
+ #:content
+ `(main
+ (section
+ (@ (class "page centered-block limit-width"))
+ ,(G_ `(h2 "Download HFGE"))
+
+ ,(G_
+ `(p
+ "HFGE is a new cross platform 3D engine currently in development. Once
+ HFGE is released you'll be able to download HFGE from here :)
+ "))
+ )))) \ No newline at end of file
diff --git a/apps/base/templates/hfge-git.scm b/apps/base/templates/hfge-git.scm
new file mode 100644
index 0000000..7cc9266
--- /dev/null
+++ b/apps/base/templates/hfge-git.scm
@@ -0,0 +1,37 @@
+;;; Harmonic Flow web site
+
+(define-module (apps base templates hfge-git)
+ #:use-module (apps base templates components)
+ #:use-module (apps base templates theme)
+ #:use-module (apps base types)
+ #:use-module (apps base utils)
+ #:use-module (apps i18n)
+ #:export (hfge-git-t))
+
+
+(define (hfge-git-t)
+ "Return the HFGE git page in SHTML."
+ (theme
+ #:title (C_ "webpage title" '("HFGE git repository |"))
+ #:description
+ (G_ "Get the latest development updates through HFGE git repository.")
+ #:keywords
+ (string-split ;TRANSLATORS: |-separated list of webpage keywords
+ (G_ "Git|Repository|HFGE") #\|)
+ #:active-menu-item (C_ "website menu" "Git")
+ #:css (list
+ (hfweb-url "static/base/css/page.css"))
+ #:crumbs (list (crumb (C_ "website menu" "HFGE Git repository") "./"))
+ #:content
+ `(main
+ (section
+ (@ (class "page centered-block limit-width"))
+ ,(G_ `(h2 "HFGE Git repository"))
+
+ ,(G_
+ `(p
+ "HFGE is a new cross platform 3D engine currently in development. Once
+ HFGE is released you'll be able to get access to the latest
+ development updates through public Git repository from here :)
+ "))
+ )))) \ No newline at end of file
diff --git a/apps/base/templates/home.scm b/apps/base/templates/home.scm
new file mode 100644
index 0000000..464cb0f
--- /dev/null
+++ b/apps/base/templates/home.scm
@@ -0,0 +1,121 @@
+;;; Harmonic Flow web site
+
+(define-module (apps base templates home)
+ #:use-module (apps base templates components)
+ #:use-module (apps base templates theme)
+ #:use-module (apps base types)
+ #:use-module (apps base utils)
+ #:use-module (apps blog templates components)
+ #:use-module (apps media templates components)
+ #:use-module (apps media types)
+ #:use-module (apps i18n)
+ #:export (home-t))
+
+
+(define (home-t context)
+ "Return the Home page in SHTML using the data in CONTEXT."
+ (theme
+ #:title (C_ "webpage title"
+ '("HFGE 3D engine, coding and free software |"))
+ #:description
+ (G_ "This website is dedicated to free software primarily in the area of graphics engines but also free software in general.")
+ #:keywords
+ (string-split ;TRANSLATORS: |-separated list of webpage keywords
+ (G_ "Harmonic Flow|HFGE|Free Software|C++|Libre software|Engine|Game Engine|Graphics|3D|2D|Programming|Vulkan") #\|)
+ #:active-menu-item (C_ "website menu" "Overview")
+ #:css (list
+ (hfweb-url "static/base/css/item-preview.css")
+ (hfweb-url "static/base/css/index.css"))
+ #:content
+ `(main
+ ;; Featured content.
+ (section
+ (@ (class "featured-content"))
+ ,(G_ `(h2 (@ (class "a11y-offset")) "Summary"))
+ (ul
+ ;; Libre:
+ ,(G_
+ `(li
+ ,(G_ `(b "Libre."))
+ ,(G_ (link-yellow
+ #:label " HFGE"
+ #:url (hfweb-url "hfge-about/")))
+ " will be distributed under the zlib license. This license allows you
+ to use HFGE freely in any software (it respects your freedom). "
+ ))
+ ;; Vulkan:
+ ,(G_
+ `(li
+ ,(G_ `(b "Vulkan."))
+ ,(G_ (link-yellow
+ #:label " HFGE"
+ #:url (hfweb-url "hfge-about/")))
+ " comes with a modern, low level, low overhead, cross platform
+ renderer in the form of Vulkan (additional renderers can be added as
+ plugins if needed)."
+ ))
+ ;; Cross platform:
+ ,(G_
+ `(li
+ ,(G_ `(b "Cross platform."))
+ ,(G_ (link-yellow
+ #:label " HFGE"
+ #:url (hfweb-url "hfge-about/")))
+ " will initially support Windows and GNU/Linux. More platforms such
+ as Android will be added later on."
+ ))
+ ;; Tools:
+ ,(G_
+ `(li
+ ,(G_ `(b "Tools."))
+ ,(G_ (link-yellow
+ #:label " HFGE"
+ #:url (hfweb-url "hfge-about/")))
+ " comes with a few useful tools for generating bitmap fonts, texture
+ atlases, resource archives and a hash checksum tool."
+ ))
+ )
+
+ (div
+ (@ (class "action-box centered-text"))
+ ,(button-big
+ #:label (C_ "button" "MORE INFO")
+ #:url (hfweb-url "hfge-about/")
+ #:light #true)
+ " " ; A space for readability in non-CSS browsers.
+ ,(button-big
+ #:label (C_ "button" "DOWNLOAD")
+ #:url (hfweb-url "hfge-download/")
+ #:light #true)))
+
+ ;; Screenshots:
+ (section
+ (@ (class "discovery-box"))
+ ,(G_ `(h2 "Screenshots"))
+
+ ,(G_
+ `(p
+ (@ (class "limit-width centered-block"))
+ "A few early development screenshots of "
+ ,(G_ (link-yellow #:label "HFGE"
+ #:url (hfweb-url "hfge-about/")))
+ " in action."))
+
+ ,(screenshots-box (context-datum context "screenshots"))
+
+ ;;,(horizontal-separator #:light #true)
+ )
+
+ ;; Latest Blog posts:
+ (section
+ (@ (class "centered-text"))
+ ,(G_ `(h2 "Blog"))
+
+ ,@(map post-preview (context-datum context "posts"))
+
+ (div
+ (@ (class "action-box centered-text"))
+ ,(button-big
+ #:label (C_ "button" "ALL POSTS")
+ #:url (hfweb-url "blog/"))))
+ )))
diff --git a/apps/base/templates/menu.scm b/apps/base/templates/menu.scm
new file mode 100644
index 0000000..734ec50
--- /dev/null
+++ b/apps/base/templates/menu.scm
@@ -0,0 +1,20 @@
+;;; Harmonic Flow web site
+
+(define-module (apps base templates menu)
+ #:use-module (apps base templates components)
+ #:use-module (apps base templates theme)
+ #:use-module (apps base utils)
+ #:use-module (apps i18n)
+ #:export (menu-t))
+
+
+(define (menu-t)
+ "Return the Menu page in SHTML."
+ (theme
+ #:title (C_ "webpage title" '("Menu"))
+ #:description (G_ "Website menu.")
+ #:keywords
+ (string-split ;TRANSLATORS: |-separated list of webpage keywords
+ (G_ "Harmonic Flow|HFGE|Free Software|C++|Libre software|Graphics Programming|Vulkan") #\|)
+ #:active-menu-item (C_ "website menu" "Menu")
+ #:css (list (hfweb-url "static/base/css/menu.css"))))
diff --git a/apps/base/templates/theme.scm b/apps/base/templates/theme.scm
new file mode 100644
index 0000000..a944077
--- /dev/null
+++ b/apps/base/templates/theme.scm
@@ -0,0 +1,143 @@
+;;; Harmonic Flow web site
+
+(define-module (apps base templates theme)
+ #:use-module (apps base templates components)
+ #:use-module (apps base utils)
+ #:use-module (apps i18n)
+ #:export (theme))
+
+
+(define* (theme #:key
+ (lang-tag %current-ietf-tag)
+ (title '())
+ (description "")
+ (keywords '())
+ (index? #true)
+ (active-menu-item (C_ "website menu" "About"))
+ (css '())
+ (scripts '())
+ (crumbs '())
+ (content '(div "")))
+ "Return an SHTML document using the website's theme.
+
+ LANG-TAG (string)
+ IETF language tag. This is used to specify the language of the
+ document. For example: en, en-CA. If not provided, the value
+ defaults to the currently built language, i.e. the
+ %current-ietf-tag from (apps i18n).
+
+ TITLE (list)
+ A list of strings to form the value of the title element of the
+ document. The elements of the list are joined together with em
+ dashes as separators between them. For example, a list with two
+ strings like 'Hello', and 'Blog' will result in a title like
+ 'Hello — Blog — Guix'.
+
+ DESCRIPTION (string)
+ The description of the document. This is the value used for the
+ description meta element.
+
+ KEYWORDS (list)
+ A list of keyword strings that will be used as the value for
+ the keywords meta element of the document.
+
+ INDEX? (boolean)
+ Indicate whether the page should be indexed by Internet robots,
+ such as search engine robots. If not provided, it defaults to
+ true.
+
+ ACTIVE-MENU-ITEM (string)
+ The label of the menu item in the navigation bar that should be
+ highlighted to indicate the current section of the website that
+ is being browsed. If not provided, the value defaults to 'About'.
+
+ CSS (list)
+ A list of strings that represent absolute URL paths to additional
+ style sheets. For example: '/static/app/css/style.css'. If not
+ provided, the value defaults to an empty list.
+
+ SCRIPTS (list)
+ A list of strings that represent absolute URL paths to additional
+ script files. For example: '/static/app/js/builds.js'. If not
+ provided, the value defaults to an empty list.
+
+ CRUMBS (list)
+ A list of <crumb> objects as defined in (apps base types). This
+ objects are used to form the breadcrumbs of the website.
+
+ CONTENT (SHTML)
+ A main element with the content of the page. For example:
+ '(main (h2 'Hello World!') (p 'Once upon a time...'))."
+ `((doctype "html")
+
+ (html
+ (@ (lang ,lang-tag))
+
+ (head
+ ,(if (null? title)
+ `(title ,(C_ "webpage title" "Harmonic Flow"))
+ `(title ,(string-join (append title
+ (C_ "webpage title" '("Harmonic Flow"))))))
+ (meta (@ (charset "UTF-8")))
+ (meta (@ (name "keywords") (content ,(string-join keywords ", "))))
+ (meta (@ (name "description") (content ,description)))
+ (meta (@ (name "viewport") (content "width=device-width, initial-scale=1.0")))
+ ;; Info for Internet robots.
+ ,(if index?
+ ""
+ '(meta (@ (name "robots") (content "noindex"))))
+ ;; Menu prefetch.
+ (link (@ (rel "prefetch") (href ,(hfweb-url "menu/index.html"))))
+ ;; Base CSS.
+ (link (@ (rel "stylesheet") (href ,(hfweb-url "static/base/css/elements.css"))))
+ (link (@ (rel "stylesheet") (href ,(hfweb-url "static/base/css/common.css"))))
+ (link (@ (rel "stylesheet") (href ,(hfweb-url "static/base/css/messages.css"))))
+ (link (@ (rel "stylesheet") (href ,(hfweb-url "static/base/css/navbar.css"))))
+ (link (@ (rel "stylesheet") (href ,(hfweb-url "static/base/css/breadcrumbs.css"))))
+ (link (@ (rel "stylesheet") (href ,(hfweb-url "static/base/css/buttons.css"))))
+ (link (@ (rel "stylesheet") (href ,(hfweb-url "static/base/css/footer.css"))))
+ (link (@ (rel "stylesheet") (href ,(hfweb-url "static/base/highlight/styles/stackoverflow-light.min.css"))))
+ ;; Additional CSS.
+ ,@(map (lambda (style-sheet)
+ `(link (@ (rel "stylesheet") (href ,style-sheet))))
+ css)
+ ;; Feeds.
+ (link (@ (type "application/atom+xml") (rel "alternate")
+ (title ,(C_ "webpage title" "Harmonic Flow — Activity Feed"))
+ (href ,(hfweb-url "feeds/blog.atom"))))
+ (link (@ (rel "icon") (type "image/png")
+ (href ,(hfweb-url "static/base/img/icon.png"))))
+ (link (@ (rel "icon") (type "image/svg+xml") (sizes "any")
+ (href ,(hfweb-url "static/base/img/icon.svg"))))
+ ;; Scripts:
+ (script (@ (src ,(hfweb-url"static/base/highlight/highlight.min.js"))))
+ (script (("hljs.highlightAll();")))
+ ;; Additional scripts.
+ ,@(map (lambda (script)
+ `(script (@ (src ,script)) ""))
+ scripts))
+
+ (body
+ ,(navbar #:active-item active-menu-item)
+
+ ,(if (null? crumbs) "" (breadcrumbs crumbs))
+
+ ,content
+ ,(G_
+ `(footer
+ "Powered by "
+ ,(G_ `(a
+ (@ (class "link-yellow")
+ (href ,(gnu-url "software/guile/")))
+ "GNU Guile"))
+ ". Website "
+ ,(G_ `(a
+ (@ (class "link-yellow")
+ (href "https://git.harmonicflow.org/cgi-bin/cgit.cgi/hf-web.git/"))
+ " Source code"))
+ " is licensed the "
+ ,(G_ `(a
+ (@ (class "link-yellow")
+ (href ,(gnu-url "licenses/agpl-3.0.html")))
+ "GNU AGPL"))
+ "."))))))
diff --git a/apps/base/types.scm b/apps/base/types.scm
new file mode 100644
index 0000000..91e9729
--- /dev/null
+++ b/apps/base/types.scm
@@ -0,0 +1,124 @@
+;;; Harmonic Flow web site
+
+(define-module (apps base types)
+ #:use-module (srfi srfi-9)
+ #:export (contact
+ contact?
+ contact-description
+ contact-log
+ contact-name
+ contact-url
+ context-datum
+ crumb
+ crumb?
+ crumb-label
+ crumb-url))
+
+
+;;;
+;;; Data types.
+;;;
+
+;;; Contact (record type)
+;;; ---------------------
+;;;
+;;; A contact object represents a contact medium such as a mailing
+;;; list, IRC channel, email address, etc.
+;;;
+;;; Objects of this type can be created with the "contact"
+;;; procedure as well (see Helper procedures below).
+;;;
+;;; Fields:
+;;;
+;;; name (string)
+;;; The name of the contact medium. For example:
+;;; "Development mailing list".
+;;;
+;;; description (SXML)
+;;; A short description. For example:
+;;; '(p "Discussion about the development of Guix.").
+;;;
+;;; url (string)
+;;; A URL to the main page of the contact medium.
+;;;
+;;; log (string)
+;;; A URL to the archive or log of previous public communications
+;;; help on the contact medium (empty string if there is no log).
+;;;
+(define-record-type <contact>
+ (make-contact name description url log)
+ contact?
+ (name contact-name)
+ (description contact-description)
+ (url contact-url)
+ (log contact-log))
+
+;;; Helper procedures.
+
+(define* (contact #:key (name "") (description "") (url "") (log ""))
+ "Return a <contact> object with the given attributes."
+ (make-contact name description url log))
+
+
+
+;;; Context (association list)
+;;; --------------------------
+;;;
+;;; A context object is a collection of data to be rendered in the
+;;; template of a web resource.
+;;;
+;;; A context can have any number of custom keys depending on the
+;;; requirements of a given template.
+;;;
+;;; The following is an example of a context object to be used with an
+;;; SHTML template:
+;;;
+(define some-context
+ (list
+ (cons "LANGUAGE" "es")
+ (cons "CHARSET" "UTF-8")
+ (cons "AUTHOR" "Jane Roe")
+ (cons "FRIENDS" (list "John Doe" "Nyoro N." "Jack the Lad"))))
+
+;;; Helper procedures.
+
+(define (context-datum context key)
+ "Return the value of KEY in the given CONTEXT.
+
+ CONTEXT (Context)
+ See more information about the Context type in (apps base types).
+
+ KEY (atom)
+ Any atomic value allowed for association list keys."
+ (assoc-ref context key))
+
+
+
+;;; Crumb (record type)
+;;; -------------------
+;;;
+;;; A crumb object represents one of the parts of a breadcrumbs
+;;; component of a website.
+;;;
+;;; Objects of this type can be created with the "crumb" procedure as
+;;; well (see Helper procedures below).
+;;;
+;;; Fields:
+;;;
+;;; label (string)
+;;; A human readable name for the crumb. For example: "Blog".
+;;;
+;;; url (string)
+;;; The URL to the web resource related to the crumb.
+;;;
+(define-record-type <crumb>
+ (make-crumb label url)
+ crumb?
+ (label crumb-label)
+ (url crumb-url))
+
+;;; Helper procedures.
+
+(define (crumb label url)
+ "Return a <crumb> object with the given attributes."
+ (make-crumb label url))
diff --git a/apps/base/utils.scm b/apps/base/utils.scm
new file mode 100644
index 0000000..31e18ea
--- /dev/null
+++ b/apps/base/utils.scm
@@ -0,0 +1,224 @@
+;;; Harmonic Flow web site
+
+(define-module (apps base utils)
+ #:use-module (apps aux lists)
+ #:use-module (apps aux system)
+ #:use-module (apps base types)
+ #:use-module (apps i18n)
+ #:use-module (haunt artifact)
+ #:use-module (ice-9 i18n)
+ #:use-module (ice-9 match)
+ #:use-module (srfi srfi-1)
+ #:export (gnu-url
+ hfweb-url
+ locale-display-name
+ manual-url
+ manual-devel-url
+ manual-url-with-language
+ number*
+ paginate
+ packages-url))
+
+
+;;;
+;;; Harmonic Flow variables.
+;;;
+
+(define* (gnu-url #:optional (path ""))
+ "Append PATH to GNU.org URL.
+
+ PATH (string)
+ An optional relative URL path to a resource. For example:
+ 'software/guile/'.
+
+ RETURN VALUE (string)
+ A URL. For example: https://gnu.org/software/guile/."
+ (string-append "https://gnu.org/" path))
+
+(define hfweb-root-url-path
+ ;; Path to hfge site:
+ (let ((path (match (getenv "HFGE_WEB_SITE_LOCAL")
+ ;; If we are trying out the website locally, use "/" as the root.
+ ("yes" "/")
+ (else (or (getenv "HFGE_WEB_SITE_ROOT_PATH") "/")))))
+ (make-parameter
+ path
+ ;; When setting hfweb-root-url-path, make it end in a slash.
+ (lambda (path)
+ (if (string-suffix? "/" path)
+ path
+ (string-append path "/"))))))
+
+(define (locale-display-name)
+ "Return the display name of the current locale."
+ ;; TRANSLATORS: The locale’s display name; please include a country
+ ;; code like in English (US) *only* if there are multiple
+ ;; translations for the same language.
+ (let ((str '(G_ "English")))
+ ;;; XXX: If we ever add a separate English (UK) translation, then
+ ;;; change the display name to English (US), but maybe do not
+ ;;; change str, because it would break translated PO files.
+ (gettext (cadr str))))
+
+
+
+;;;
+;;; URL linking.
+;;;
+
+
+(define* (hfweb-url #:optional (subpath "") #:key (localize #t))
+ "Append SUBPATH to Harmonic Flow root URL path (see hfweb-root-url-path).
+
+ SUBPATH (string)
+ An optional relative URL path to a resource in the Harmonic Flwo path.
+ For example: 'indeed/something-XYZ/'.
+
+ LOCALIZE (boolean)
+ Whether to prepend the result of 'localized-root-path' to the URL path.
+
+ RETURN VALUE (string)
+ A URL path. For example: /indeed/something/-XYZ/."
+ (string-append (hfweb-root-url-path)
+ (if localize (localized-root-path subpath) "")
+ subpath))
+
+
+(define* (manual-url #:optional (subpath "")
+ #:key (language "en"))
+ "Append SUBPATH to the Harmonic Flow manual URL path.
+
+ SUBPATH (string)
+ An optional relative URL path to a section of the manual.
+ For example: 'SomeFile.html'.
+
+ RETURN VALUE (string)
+ A URL path. For example:
+ /indeed/SomeFile.html."
+ (string-append
+ (hfweb-url (string-append (string-append "manual/" language
+ "/html_node/")
+ subpath) #:localize #f)))
+
+(define* (manual-devel-url #:optional (subpath "")
+ #:key (language "en"))
+ "Similar to 'manual-url', but link to the development manual."
+ (string-append
+ (hfweb-url (string-append (string-append "manual/devel/" language
+ "/html_node/")
+ subpath) #:localize #f)))
+
+(define* (manual-url-with-language _ language #:optional (subpath ""))
+ "Shorthand for manual-url without keywords for prettier output
+PO files when marked for translation. It can be marked for translation
+as:
+
+ (G_ (manual-url-with-language (G_ \"en\") (G_ \"Some-section.html\")))
+
+ LANGUAGE (string)
+ Normalized language for the Guix manual as produced by
+'doc/build.scm' in the Guix source tree, i.e. \"en\" for the English
+manual.
+
+ SUBPATH (string)
+ Like manual-url.
+
+ RETURN VALUE (string)
+ A URL path. For example:
+ /software/guix/manual/en/html_node/System-installation.html."
+ ;; The _ argument is a placeholder for an arg added by G_, cf. i18n-howto.txt.
+ (manual-url subpath #:language language))
+
+
+;;;
+;;; Helper procedures.
+;;;
+
+(define (number* number)
+ "Return NUMBER correctly formatting according to English conventions."
+ (number->locale-string number 0
+ (or (false-if-exception
+ (make-locale LC_ALL "en_US.utf8"))
+ (make-locale LC_ALL "en_US.UTF-8"))))
+
+
+(define* (paginate #:key dataset (limit 30) base-path template (context '()) writer)
+ "Distribute the objects of the DATASET in pages.
+
+ DATASET (list)
+ A list with any kind of object.
+
+ LIMIT (integer)
+ The maximum number of objects that should appear in a page.
+
+ The limit is optional. If not provided, it defaults to 30.
+
+ BASE-PATH (string)
+ A system path relative to the website directory where all the
+ pages will be written to. For example: 'blog' or 'blog/tags'.
+
+ In the latter example, pages would be written to files in a path
+ like 'blog/tags/page/PAGE_NUMBER/index.html'.
+
+ TEMPLATE (procedure)
+ A procedure that accepts a context and returns an SXML tree.
+
+ CONTEXT (context)
+ A context object as defined in (apps base types). The context
+ holds additional data to insert into the TEMPLATE.
+
+ The context is optional, and will always be extended to include
+ the following data that can be used in the TEMPLATE:
+
+ items (list)
+ The list of items to insert into the page.
+
+ total-pages (integer)
+ The number of pages generated to distribute all items.
+
+ page-number (integer)
+ The number of the page.
+
+ WRITER
+ A procedure that writes the page into a given format. See Haunt's
+ 'sxml->html' writer in the (haunt html) module, for example.
+
+ RETURN VALUE (list)
+ A list of <page> objects as defined in (haunt page) module."
+ (let* ((grouped-data (list-group dataset limit))
+ (total-pages (cons "total-pages" (length grouped-data))))
+ ;; Read the following like (cons Page ListOfPages):
+ (cons
+ ;; Page
+ ;; This is the cover of the pages. For example, the resource
+ ;; located in a path such as /blog/, which is identical to the
+ ;; resource available in /blog/page/1/.
+ (let* ((page-number (cons "page-number" 1))
+ (path (path-join base-path "index.html"))
+ (items
+ (match grouped-data
+ (()
+ (cons "items" '()))
+ ((head _ ...)
+ (cons "items" head))))
+ (new-context
+ (append context
+ (list items page-number total-pages))))
+
+ (serialized-artifact path (template new-context) writer))
+ ;; ListOfPages
+ ;; This is a list of pages that are the actual ordered pages
+ ;; located in paths such as /blog/page/NUMBER/.
+ (map
+ (lambda (index)
+ (let* ((page-number (cons "page-number" (+ index 1)))
+ (path (path-join base-path
+ "page"
+ (number->string (+ index 1))
+ "index.html"))
+ (items (cons "items" (list-ref grouped-data index)))
+ (new-context
+ (append context (list items page-number total-pages))))
+ (serialized-artifact path (template new-context) writer)))
+
+ (iota (length grouped-data))))))