mirror of
https://github.com/dillonkearns/elm-pages-v3-beta.git
synced 2024-11-28 06:05:31 +03:00
Merge branch 'master' into generate-files
This commit is contained in:
commit
cae334486a
@ -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
|
||||
|
@ -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
|
||||
|
2
elm.json
2
elm.json
@ -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",
|
||||
|
@ -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.)
|
||||
|
4
examples/docs/content/showcase/index.md
Normal file
4
examples/docs/content/showcase/index.md
Normal file
@ -0,0 +1,4 @@
|
||||
---
|
||||
title: elm-pages sites showcase
|
||||
type: showcase
|
||||
---
|
@ -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
|
||||
|
18
examples/docs/src/FontAwesome.elm
Normal file
18
examples/docs/src/FontAwesome.elm
Normal 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
|
@ -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
|
||||
|
@ -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 =
|
||||
|
@ -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)
|
||||
|
161
examples/docs/src/Showcase.elm
Normal file
161
examples/docs/src/Showcase.elm
Normal 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"
|
||||
]
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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: {
|
||||
|
@ -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}"
|
||||
} )
|
||||
`;
|
||||
|
@ -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>
|
||||
|
20
index.js
20
index.js
@ -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
10
package-lock.json
generated
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user