Merge branch 'master' into generate-files

This commit is contained in:
Dillon Kearns 2020-01-23 20:03:18 -08:00
commit cae334486a
21 changed files with 551 additions and 127 deletions

View File

@ -9,6 +9,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
## [Unreleased]
## [1.1.2] - 2020-01-20
### Fixed
- "Missing content" message no longer flashes between pre-rendered HTML and the Elm app hydrating and taking over the page. See [#48](https://github.com/dillonkearns/elm-pages/pull/48).
## [1.1.1] - 2020-01-04
### Fixed

View File

@ -9,6 +9,20 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
## [Unreleased]
### Fixed
- Fix missing content flash (that was partially fixed with [#48](https://github.com/dillonkearns/elm-pages/pull/48)) for
some cases where paths weren't normalized correctly.
## [1.1.8] - 2020-01-20
### Fixed
- "Missing content" message no longer flashes between pre-rendered HTML and the Elm app hydrating and taking over the page. See [#48](https://github.com/dillonkearns/elm-pages/pull/48).
## [1.1.7] - 2020-01-12
### Fixed
- Newlines and escaped double quotes (`"`s) are handled properly in content frontmatter now. See [#41](https://github.com/dillonkearns/elm-pages/pull/41). Thank you [Luke](https://github.com/lukewestby)! 🎉🙏
## [1.1.6] - 2020-01-04
### Added

View File

@ -3,7 +3,7 @@
"name": "dillonkearns/elm-pages",
"summary": "A statically typed site generator.",
"license": "BSD-3-Clause",
"version": "1.1.1",
"version": "1.1.2",
"exposed-modules": [
"Head",
"Head.Seo",

View File

@ -73,7 +73,7 @@ Here are some links:
And here's the output:
<ellie-output id="6RCVwj43wQfa1" />
<ellie-output id="7PLvgQ2kSzja1" />
This is a nice way to abstract the presentation logic for team members' bios on an `about-us` page. We want richer presentation logic than plain markdown provides (for example, showing icons with the right dimensions, and displaying them in a row not column view, etc.) Also, since we're using Elm, we get pretty spoiled by explicit and precise error messages. So we'd like to get an error message if we don't provide a required attribute!
@ -148,7 +148,7 @@ Exposing the AST allows for a number of powerful use cases as well. And it does
Here are some use cases that this feature enables:
- Extract metadata before rendering, like building a table of contents data structure with proper links ([here's an Ellie demo of that!](https://ellie-app.com/6QtYW8pcCDna1))
- Extract metadata before rendering, like building a table of contents data structure with proper links ([here's an Ellie demo of that!](https://ellie-app.com/7LDzS6r48n8a1))
- Run a validation and turn it into an `Err`, for example, if there are multiple level 1 headings (having multiple `h1`s on a page causes accessibility problems)
- Transform the blocks by applying formatting rules, for example use a title casing function on all headings
- Transform the AST before rendering it, for example dropping each heading down one level (H1s become H2s, etc.)

View File

@ -0,0 +1,4 @@
---
title: elm-pages sites showcase
type: showcase
---

View File

@ -4,8 +4,8 @@ import Element exposing (Element)
import Element.Border as Border
import Element.Font
import Metadata exposing (Metadata)
import Pages.PagePath as PagePath exposing (PagePath)
import Pages
import Pages.PagePath as PagePath exposing (PagePath)
import Palette
@ -25,19 +25,10 @@ view currentPage posts =
|> List.filterMap
(\( path, metadata ) ->
case metadata of
Metadata.Page meta ->
Nothing
Metadata.Article meta ->
Nothing
Metadata.Author _ ->
Nothing
Metadata.Doc meta ->
Just ( currentPage == path, path, meta )
Metadata.BlogIndex ->
_ ->
Nothing
)
|> List.map postSummary

View File

@ -0,0 +1,18 @@
module FontAwesome exposing (icon, styledIcon)
import Element exposing (Element)
import Html
import Html.Attributes
styledIcon : String -> List (Element.Attribute msg) -> Element msg
styledIcon classString styles =
Html.i [ Html.Attributes.class classString ] []
|> Element.html
|> Element.el styles
icon : String -> Element msg
icon classString =
Html.i [ Html.Attributes.class classString ] []
|> Element.html

View File

@ -20,15 +20,6 @@ view posts =
|> List.filterMap
(\( path, metadata ) ->
case metadata of
Metadata.Page meta ->
Nothing
Metadata.Doc meta ->
Nothing
Metadata.Author _ ->
Nothing
Metadata.Article meta ->
if meta.draft then
Nothing
@ -36,7 +27,7 @@ view posts =
else
Just ( path, meta )
Metadata.BlogIndex ->
_ ->
Nothing
)
|> List.sortBy

View File

@ -8,9 +8,11 @@ import DocumentSvg
import Element exposing (Element)
import Element.Background
import Element.Border
import Element.Events
import Element.Font as Font
import Element.Region
import Feed
import FontAwesome
import Head
import Head.Seo as Seo
import Html exposing (Html)
@ -32,6 +34,7 @@ import Pages.Platform exposing (Page)
import Pages.StaticHttp as StaticHttp
import Palette
import Secrets
import Showcase
manifest : Manifest.Config Pages.PathKey
@ -97,23 +100,28 @@ markdownDocument =
type alias Model =
{}
{ showMobileMenu : Bool
}
init : Maybe (PagePath Pages.PathKey) -> ( Model, Cmd Msg )
init maybePagePath =
( Model, Cmd.none )
( Model False, Cmd.none )
type Msg
= OnPageChange (PagePath Pages.PathKey)
| ToggleMobileMenu
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
OnPageChange page ->
( model, Cmd.none )
( { model | showMobileMenu = False }, Cmd.none )
ToggleMobileMenu ->
( { model | showMobileMenu = not model.showMobileMenu }, Cmd.none )
subscriptions : Model -> Sub Msg
@ -133,17 +141,39 @@ view :
, head : List (Head.Tag Pages.PathKey)
}
view siteMetadata page =
StaticHttp.get (Secrets.succeed "https://api.github.com/repos/dillonkearns/elm-pages")
(D.field "stargazers_count" D.int)
|> StaticHttp.map
(\stars ->
{ view =
\model viewForPage ->
pageView stars model siteMetadata page viewForPage
|> wrapBody
, head = head page.frontmatter
}
)
case page.frontmatter of
Metadata.Showcase ->
StaticHttp.map2
(\stars showcaseData ->
{ view =
\model viewForPage ->
{ title = "elm-pages blog"
, body =
Element.column [ Element.width Element.fill ]
[ Element.column [ Element.padding 20, Element.centerX ] [ Showcase.view showcaseData ]
]
}
|> wrapBody stars page model
, head = head page.frontmatter
}
)
(StaticHttp.get (Secrets.succeed "https://api.github.com/repos/dillonkearns/elm-pages")
(D.field "stargazers_count" D.int)
)
Showcase.staticRequest
_ ->
StaticHttp.get (Secrets.succeed "https://api.github.com/repos/dillonkearns/elm-pages")
(D.field "stargazers_count" D.int)
|> StaticHttp.map
(\stars ->
{ view =
\model viewForPage ->
pageView stars model siteMetadata page viewForPage
|> wrapBody stars page model
, head = head page.frontmatter
}
)
@ -196,8 +226,7 @@ pageView stars model siteMetadata page viewForPage =
Metadata.Page metadata ->
{ title = metadata.title
, body =
[ header stars page.path
, Element.column
[ Element.column
[ Element.padding 50
, Element.spacing 60
, Element.Region.mainContent
@ -213,8 +242,7 @@ pageView stars model siteMetadata page viewForPage =
{ title = metadata.title
, body =
Element.column [ Element.width Element.fill ]
[ header stars page.path
, Element.column
[ Element.column
[ Element.padding 30
, Element.spacing 40
, Element.Region.mainContent
@ -244,8 +272,7 @@ pageView stars model siteMetadata page viewForPage =
Metadata.Doc metadata ->
{ title = metadata.title
, body =
[ header stars page.path
, Element.row []
[ Element.row []
[ DocSidebar.view page.path siteMetadata
|> Element.el [ Element.width (Element.fillPortion 2), Element.alignTop, Element.height Element.fill ]
, Element.column [ Element.width (Element.fillPortion 8), Element.padding 35, Element.spacing 15 ]
@ -274,8 +301,7 @@ pageView stars model siteMetadata page viewForPage =
Element.column
[ Element.width Element.fill
]
[ header stars page.path
, Element.column
[ Element.column
[ Element.padding 30
, Element.spacing 20
, Element.Region.mainContent
@ -293,15 +319,41 @@ pageView stars model siteMetadata page viewForPage =
{ title = "elm-pages blog"
, body =
Element.column [ Element.width Element.fill ]
[ header stars page.path
, Element.column [ Element.padding 20, Element.centerX ] [ Index.view siteMetadata ]
[ Element.column [ Element.padding 20, Element.centerX ] [ Index.view siteMetadata ]
]
}
Metadata.Showcase ->
{ title = "elm-pages blog"
, body =
Element.column [ Element.width Element.fill ]
[--, Element.column [ Element.padding 20, Element.centerX ] [ Showcase.view siteMetadata ]
]
}
wrapBody record =
wrapBody : Int -> { a | path : PagePath Pages.PathKey } -> Model -> { c | body : Element Msg, title : String } -> { body : Html Msg, title : String }
wrapBody stars page model record =
{ body =
record.body
(if model.showMobileMenu then
Element.column
[ Element.width Element.fill
, Element.padding 20
]
[ Element.row [ Element.width Element.fill, Element.spaceEvenly ]
[ logoLinkMobile
, FontAwesome.styledIcon "fas fa-bars" [ Element.Events.onClick ToggleMobileMenu ]
]
, Element.column [ Element.centerX, Element.spacing 20 ]
(navbarLinks stars page.path)
]
else
Element.column [ Element.width Element.fill ]
[ header stars page.path
, record.body
]
)
|> Element.layout
[ Element.width Element.fill
, Font.size 20
@ -320,51 +372,92 @@ articleImageView articleImage =
}
header : Int -> PagePath Pages.PathKey -> Element msg
header : Int -> PagePath Pages.PathKey -> Element Msg
header stars currentPath =
Element.column [ Element.width Element.fill ]
[ Element.el
[ Element.height (Element.px 4)
, Element.width Element.fill
, Element.Background.gradient
{ angle = 0.2
, steps =
[ Element.rgb255 0 242 96
, Element.rgb255 5 117 230
]
}
[ responsiveHeader
, Element.column
[ Element.width Element.fill
, Element.htmlAttribute (Attr.class "responsive-desktop")
]
Element.none
, Element.row
[ Element.paddingXY 25 4
, Element.spaceEvenly
, Element.width Element.fill
, Element.Region.navigation
, Element.Border.widthEach { bottom = 1, left = 0, right = 0, top = 0 }
, Element.Border.color (Element.rgba255 40 80 40 0.4)
]
[ Element.link []
{ url = "/"
, label =
Element.row
[ Font.size 30
, Element.spacing 16
, Element.htmlAttribute (Attr.id "navbar-title")
[ Element.el
[ Element.height (Element.px 4)
, Element.width Element.fill
, Element.Background.gradient
{ angle = 0.2
, steps =
[ Element.rgb255 0 242 96
, Element.rgb255 5 117 230
]
[ DocumentSvg.view
, Element.text "elm-pages"
]
}
, Element.row [ Element.spacing 15 ]
[ elmDocsLink
, githubRepoLink stars
, highlightableLink currentPath pages.docs.directory "Docs"
, highlightableLink currentPath pages.blog.directory "Blog"
}
]
Element.none
, Element.row
[ Element.paddingXY 25 4
, Element.spaceEvenly
, Element.width Element.fill
, Element.Region.navigation
, Element.Border.widthEach { bottom = 1, left = 0, right = 0, top = 0 }
, Element.Border.color (Element.rgba255 40 80 40 0.4)
]
[ logoLink
, Element.row [ Element.spacing 15 ] (navbarLinks stars currentPath)
]
]
]
logoLink =
Element.link []
{ url = "/"
, label =
Element.row
[ Font.size 30
, Element.spacing 16
, Element.htmlAttribute (Attr.id "navbar-title")
]
[ DocumentSvg.view
, Element.text "elm-pages"
]
}
logoLinkMobile =
Element.link []
{ url = "/"
, label =
Element.row
[ Font.size 30
, Element.spacing 16
, Element.htmlAttribute (Attr.id "navbar-title")
]
[ Element.text "elm-pages"
]
}
navbarLinks stars currentPath =
[ elmDocsLink
, githubRepoLink stars
, highlightableLink currentPath pages.docs.directory "Docs"
, highlightableLink currentPath pages.showcase.directory "Showcase"
, highlightableLink currentPath pages.blog.directory "Blog"
]
responsiveHeader =
Element.row
[ Element.width Element.fill
, Element.spaceEvenly
, Element.htmlAttribute (Attr.class "responsive-mobile")
, Element.width Element.fill
, Element.padding 20
]
[ logoLinkMobile
, FontAwesome.icon "fas fa-bars" |> Element.el [ Element.alignRight, Element.Events.onClick ToggleMobileMenu ]
]
highlightableLink :
PagePath Pages.PathKey
-> Directory Pages.PathKey Directory.WithIndex
@ -510,6 +603,22 @@ head metadata =
|> Seo.website
)
Metadata.Showcase ->
Seo.summaryLarge
{ canonicalUrlOverride = Nothing
, siteName = "elm-pages"
, image =
{ url = images.iconPng
, alt = "elm-pages logo"
, dimensions = Nothing
, mimeType = Nothing
}
, description = siteTagline
, locale = Nothing
, title = "elm-pages sites showcase"
}
|> Seo.website
canonicalSiteUrl : String
canonicalSiteUrl =

View File

@ -17,6 +17,7 @@ type Metadata
| Doc DocMetadata
| Author Data.Author.Author
| BlogIndex
| Showcase
type alias ArticleMetadata =
@ -54,6 +55,9 @@ decoder =
"blog-index" ->
Decode.succeed BlogIndex
"showcase" ->
Decode.succeed Showcase
"author" ->
Decode.map3 Data.Author.Author
(Decode.field "name" Decode.string)

View File

@ -0,0 +1,161 @@
module Showcase exposing (..)
import Element
import Element.Border
import Element.Font
import FontAwesome
import Json.Decode.Exploration as Decode
import Pages.Secrets as Secrets
import Pages.StaticHttp as StaticHttp
import Palette
import Url.Builder
view : List Entry -> Element.Element msg
view entries =
Element.column
[ Element.spacing 30
]
(submitShowcaseItemButton
:: List.map entryView entries
)
submitShowcaseItemButton =
Element.newTabLink
[ Element.Font.color Palette.color.primary
, Element.Font.underline
]
{ url = "https://airtable.com/shrPSenIW2EQqJ083"
, label = Element.text "Submit your site to the showcase"
}
entryView : Entry -> Element.Element msg
entryView entry =
Element.column
[ Element.spacing 15
, Element.Border.shadow { offset = ( 2, 2 ), size = 3, blur = 3, color = Element.rgba255 40 80 80 0.1 }
, Element.padding 40
, Element.width (Element.maximum 700 Element.fill)
]
[ Element.newTabLink [ Element.Font.size 14, Element.Font.color Palette.color.primary ]
{ url = entry.liveUrl
, label =
Element.image [ Element.width Element.fill ]
{ src = "https://image.thum.io/get/width/800/crop/800/" ++ entry.screenshotUrl
, description = "Site Screenshot"
}
}
, Element.text entry.displayName |> Element.el [ Element.Font.extraBold ]
, Element.newTabLink [ Element.Font.size 14, Element.Font.color Palette.color.primary ]
{ url = entry.liveUrl
, label = Element.text entry.liveUrl
}
, Element.paragraph [ Element.Font.size 14 ]
[ Element.text "By "
, Element.newTabLink [ Element.Font.color Palette.color.primary ]
{ url = entry.authorUrl
, label = Element.text entry.authorName
}
]
, Element.row [ Element.width Element.fill ]
[ categoriesView entry.categories
, Element.row [ Element.alignRight ]
[ case entry.repoUrl of
Just repoUrl ->
Element.newTabLink []
{ url = repoUrl
, label = FontAwesome.icon "fas fa-code-branch"
}
Nothing ->
Element.none
]
]
]
categoriesView : List String -> Element.Element msg
categoriesView categories =
categories
|> List.map
(\category ->
Element.text category
)
|> Element.wrappedRow
[ Element.spacing 7
, Element.Font.size 14
, Element.Font.color (Element.rgba255 0 0 0 0.6)
, Element.width (Element.fillPortion 8)
]
type alias Entry =
{ screenshotUrl : String
, displayName : String
, liveUrl : String
, authorName : String
, authorUrl : String
, categories : List String
, repoUrl : Maybe String
}
decoder : Decode.Decoder (List Entry)
decoder =
Decode.field "records" <|
Decode.list entryDecoder
entryDecoder : Decode.Decoder Entry
entryDecoder =
Decode.field "fields" <|
Decode.map7 Entry
(Decode.field "Screenshot URL" Decode.string)
(Decode.field "Site Display Name" Decode.string)
(Decode.field "Live URL" Decode.string)
(Decode.field "Author" Decode.string)
(Decode.field "Author URL" Decode.string)
(Decode.field "Categories" (Decode.list Decode.string))
(Decode.maybe (Decode.field "Repository URL" Decode.string))
staticRequest : StaticHttp.Request (List Entry)
staticRequest =
StaticHttp.request
(Secrets.succeed
(\airtableToken ->
{ url = "https://api.airtable.com/v0/appDykQzbkQJAidjt/elm-pages%20showcase?maxRecords=100&view=Grid%202"
, method = "GET"
, headers = [ ( "Authorization", "Bearer " ++ airtableToken ), ( "view", "viwayJBsr63qRd7q3" ) ]
, body = StaticHttp.emptyBody
}
)
|> Secrets.with "AIRTABLE_TOKEN"
)
decoder
allCategroies : List String
allCategroies =
[ "Documentation"
, "eCommerce"
, "Conference"
, "Consulting"
, "Education"
, "Entertainment"
, "Event"
, "Food"
, "Freelance"
, "Gallery"
, "Landing Page"
, "Music"
, "Nonprofit"
, "Podcast"
, "Portfolio"
, "Programming"
, "Sports"
, "Travel"
, "Blog"
]

View File

@ -1,4 +1,5 @@
@import url("https://fonts.googleapis.com/css?family=Montserrat:400,700|Roboto&display=swap");
@import url("https://use.fontawesome.com/releases/v5.9.0/css/all.css");
.dotted-line {
-webkit-animation: animation-yweh2o 400ms linear infinite;
@ -26,3 +27,14 @@
width: 20px;
}
}
@media (max-width: 600px) {
.responsive-desktop {
display: none !important;
}
}
@media (min-width: 600px) {
.responsive-mobile {
display: none !important;
}
}

View File

@ -2,6 +2,7 @@ const webpack = require("webpack");
const middleware = require("webpack-dev-middleware");
const path = require("path");
const HTMLWebpackPlugin = require("html-webpack-plugin");
const ScriptExtHtmlWebpackPlugin = require('script-ext-html-webpack-plugin');
const CopyPlugin = require("copy-webpack-plugin");
const PrerenderSPAPlugin = require("prerender-spa-plugin");
const merge = require("webpack-merge");
@ -161,6 +162,10 @@ function webpackOptions(
inject: "head",
template: path.resolve(__dirname, "template.html")
}),
new ScriptExtHtmlWebpackPlugin({
preload: /\.js$/,
defaultAttribute: 'defer'
}),
new FaviconsWebpackPlugin({
logo: path.resolve(process.cwd(), `./${manifestConfig.sourceIcon}`),
favicons: {

View File

@ -19,8 +19,8 @@ function toEntry(entry, includeBody) {
return `
( [${fullPath.join(", ")}]
, { frontMatter = """${entry.metadata}
""" , body = ${body(entry, includeBody)}
, { frontMatter = ${JSON.stringify(entry.metadata)}
, body = ${body(entry, includeBody)}
, extension = "${extension}"
} )
`;

View File

@ -1,7 +1,7 @@
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="preload" href="content.json" as="fetch" crossorigin />
<link rel="preload" href="./content.json" as="fetch" crossorigin />
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script>

View File

@ -7,9 +7,13 @@ module.exports = function pagesInit(
let prefetchedPages = [window.location.pathname];
document.addEventListener("DOMContentLoaded", function() {
httpGet(`${window.location.origin}${window.location.pathname}/content.json`, function (/** @type JSON */ contentJson) {
let app = mainElmModule.init({
flags: {
secrets: null
secrets: null,
isPrerendering: navigator.userAgent.indexOf("Headless") >= 0,
contentJson
}
});
@ -33,6 +37,9 @@ module.exports = function pagesInit(
document.dispatchEvent(new Event("prerender-trigger"));
});
})
});
function setupLinkPrefetching() {
@ -130,3 +137,14 @@ module.exports = function pagesInit(
document.getElementsByTagName("head")[0].appendChild(meta);
}
};
function httpGet(/** @type string */ theUrl, /** @type Function */ callback)
{
var xmlHttp = new XMLHttpRequest();
xmlHttp.onreadystatechange = function() {
if (xmlHttp.readyState == 4 && xmlHttp.status == 200)
callback(JSON.parse(xmlHttp.responseText));
}
xmlHttp.open("GET", theUrl, true); // true for asynchronous
xmlHttp.send(null);
}

10
package-lock.json generated
View File

@ -1,6 +1,6 @@
{
"name": "elm-pages",
"version": "1.1.6",
"version": "1.1.8",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -9220,6 +9220,14 @@
"ajv-keywords": "^3.1.0"
}
},
"script-ext-html-webpack-plugin": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/script-ext-html-webpack-plugin/-/script-ext-html-webpack-plugin-2.1.4.tgz",
"integrity": "sha512-7MAv3paAMfh9y2Rg+yQKp9jEGC5cEcmdge4EomRqri10qoczmliYEVPVNz0/5e9QQ202e05qDll9B8zZlY9N1g==",
"requires": {
"debug": "^4.1.1"
}
},
"scss-tokenizer": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz",

View File

@ -1,6 +1,6 @@
{
"name": "elm-pages",
"version": "1.1.6",
"version": "1.1.8",
"homepage": "http://elm-pages.com",
"description": "Type-safe static sites, written in pure elm with your own custom elm-markup syntax.",
"main": "index.js",
@ -42,6 +42,7 @@
"node-sass": "^4.12.0",
"prerender-spa-plugin": "^3.4.0",
"sass-loader": "^8.0.0",
"script-ext-html-webpack-plugin": "^2.1.4",
"style-loader": "^1.0.0",
"webpack": "^4.41.5",
"webpack-dev-middleware": "^3.7.0",

View File

@ -111,9 +111,10 @@ pagesWithErrors cache =
init :
Document metadata view
-> Content
-> Maybe { contentJson : ContentJson String, initialUrl : Url }
-> ContentCache metadata view
init document content =
parseMetadata document content
init document content maybeInitialPageContent =
parseMetadata maybeInitialPageContent document content
|> List.map
(\tuple ->
Tuple.mapSecond
@ -149,42 +150,75 @@ createBuildError path decodeError =
parseMetadata :
Document metadata view
Maybe { contentJson : ContentJson String, initialUrl : Url }
-> Document metadata view
-> List ( List String, { extension : String, frontMatter : String, body : Maybe String } )
-> List ( List String, Result String (Entry metadata view) )
parseMetadata document content =
parseMetadata maybeInitialPageContent document content =
content
|> List.map
(Tuple.mapSecond
(\{ frontMatter, extension, body } ->
let
maybeDocumentEntry =
Document.get extension document
in
case maybeDocumentEntry of
Just documentEntry ->
frontMatter
|> documentEntry.frontmatterParser
|> Result.map
(\metadata ->
-- TODO do I need to handle this case?
-- case body of
-- Just presentBody ->
-- Parsed metadata
-- { body = parseContent extension presentBody document
-- , staticData = ""
-- }
--
-- Nothing ->
NeedContent extension metadata
)
(\( path, { frontMatter, extension, body } ) ->
let
maybeDocumentEntry =
Document.get extension document
in
case maybeDocumentEntry of
Just documentEntry ->
frontMatter
|> documentEntry.frontmatterParser
|> Result.map
(\metadata ->
let
renderer =
\value ->
parseContent extension value document
in
case maybeInitialPageContent of
Just { contentJson, initialUrl } ->
if normalizePath initialUrl.path == (String.join "/" path |> normalizePath) then
Parsed metadata
{ body = renderer contentJson.body
, staticData = contentJson.staticData
}
Nothing ->
Err ("Could not find extension '" ++ extension ++ "'")
)
else
NeedContent extension metadata
Nothing ->
NeedContent extension metadata
)
|> Tuple.pair path
Nothing ->
Err ("Could not find extension '" ++ extension ++ "'")
|> Tuple.pair path
)
normalizePath : String -> String
normalizePath pathString =
let
hasPrefix =
String.startsWith "/" pathString
hasSuffix =
String.endsWith "/" pathString
in
String.concat
[ if hasPrefix then
""
else
"/"
, pathString
, if hasSuffix then
""
else
"/"
]
parseContent :
String
-> String
@ -327,8 +361,8 @@ lazyLoad document url cacheResult =
|> Task.map
(\downloadedContent ->
update cacheResult
(\thing ->
parseContent extension thing document
(\value ->
parseContent extension value document
)
url
downloadedContent

View File

@ -217,6 +217,12 @@ type alias Flags =
Decode.Value
type alias ContentJson =
{ body : String
, staticData : Dict String String
}
init :
pathKey
-> String
@ -249,11 +255,33 @@ init :
init pathKey canonicalSiteUrl document toJsPort viewFn content initUserModel flags url key =
let
contentCache =
ContentCache.init document content
ContentCache.init document content (Maybe.map (\cj -> { contentJson = cj, initialUrl = url }) contentJson)
contentJson =
flags
|> Decode.decodeValue (Decode.field "contentJson" contentJsonDecoder)
|> Result.toMaybe
contentJsonDecoder : Decode.Decoder ContentJson
contentJsonDecoder =
Decode.map2 ContentJson
(Decode.field "body" Decode.string)
(Decode.field "staticData" (Decode.dict Decode.string))
in
case contentCache of
Ok okCache ->
let
phase =
case Decode.decodeValue (Decode.field "isPrerendering" Decode.bool) flags of
Ok True ->
Prerender
Ok False ->
Client
Err _ ->
Client
( userModel, userCmd ) =
initUserModel maybePagePath
@ -284,6 +312,7 @@ init pathKey canonicalSiteUrl document toJsPort viewFn content initUserModel fla
, url = url
, userModel = userModel
, contentCache = contentCache
, phase = phase
}
, cmd
)
@ -297,6 +326,7 @@ init pathKey canonicalSiteUrl document toJsPort viewFn content initUserModel fla
, url = url
, userModel = userModel
, contentCache = contentCache
, phase = Client
}
, Cmd.batch
[ userCmd |> Cmd.map UserMsg
@ -333,9 +363,15 @@ type alias ModelDetails userModel metadata view =
, url : Url.Url
, contentCache : ContentCache metadata view
, userModel : userModel
, phase : Phase
}
type Phase
= Prerender
| Client
update :
String
->
@ -535,7 +571,20 @@ application config =
\msg outerModel ->
case outerModel of
Model model ->
update config.canonicalSiteUrl config.view config.pathKey config.onPageChange config.toJsPort config.document config.update msg model
let
userUpdate =
case model.phase of
Prerender ->
noOpUpdate
Client ->
config.update
noOpUpdate =
\userMsg userModel ->
( userModel, Cmd.none )
in
update config.canonicalSiteUrl config.view config.pathKey config.onPageChange config.toJsPort config.document userUpdate msg model
|> Tuple.mapFirst Model
|> Tuple.mapSecond (Cmd.map AppMsg)

View File

@ -196,7 +196,7 @@ cliApplication :
cliApplication cliMsgConstructor narrowMsg toModel fromModel config =
let
contentCache =
ContentCache.init config.document config.content
ContentCache.init config.document config.content Nothing
siteMetadata =
contentCache