diff --git a/CHANGELOG-ELM.md b/CHANGELOG-ELM.md
index 3a900894..a5225600 100644
--- a/CHANGELOG-ELM.md
+++ b/CHANGELOG-ELM.md
@@ -9,6 +9,27 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
## [Unreleased]
+## [2.0.0] - 2020-01-25
+
+### Added
+- There's a new `generateFiles` endpoint. You pass in a function that takes a page's path,
+ page metadata, and page body, and that returns a list representing the files to generate.
+ You can see a working example for elm-pages.com, here's the [entry point](https://github.com/dillonkearns/elm-pages/blob/master/examples/docs/src/Main.elm#L76-L92), and here's where it
+ [generates the RSS feed](https://github.com/dillonkearns/elm-pages/blob/master/examples/docs/src/Feed.elm).
+ You can pass in a no-op function like `\pages -> []` to not generate any files.
+
+
+## [1.1.3] - 2020-01-23
+
+### 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.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
diff --git a/CHANGELOG-NPM.md b/CHANGELOG-NPM.md
index 2840e4ea..3debb86b 100644
--- a/CHANGELOG-NPM.md
+++ b/CHANGELOG-NPM.md
@@ -9,6 +9,22 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
## [Unreleased]
+## [1.2.0] - 2020-01-20
+
+### Changed
+- Changed the CLI generator to expect code from the new Elm package from the new
+ `generateFiles` hook in `Pages.Platform.application`.
+
+## [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
diff --git a/elm.json b/elm.json
index 800f82b8..cfd8073b 100644
--- a/elm.json
+++ b/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": "2.0.0",
"exposed-modules": [
"Head",
"Head.Seo",
diff --git a/examples/docs/content/blog/extensible-markdown-parsing-in-elm.md b/examples/docs/content/blog/extensible-markdown-parsing-in-elm.md
index 8ba645fd..e6c72e09 100644
--- a/examples/docs/content/blog/extensible-markdown-parsing-in-elm.md
+++ b/examples/docs/content/blog/extensible-markdown-parsing-in-elm.md
@@ -73,7 +73,7 @@ Here are some links:
And here's the output:
-
+
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.)
diff --git a/examples/docs/content/showcase/index.md b/examples/docs/content/showcase/index.md
new file mode 100644
index 00000000..0157c4af
--- /dev/null
+++ b/examples/docs/content/showcase/index.md
@@ -0,0 +1,4 @@
+---
+title: elm-pages sites showcase
+type: showcase
+---
diff --git a/examples/docs/elm.json b/examples/docs/elm.json
index 6422dfd0..b7bd01f2 100644
--- a/examples/docs/elm.json
+++ b/examples/docs/elm.json
@@ -9,8 +9,12 @@
"dependencies": {
"direct": {
"avh4/elm-color": "1.0.0",
+ "billstclair/elm-xml-eeue56": "1.0.1",
"dillonkearns/elm-markdown": "1.1.3",
"dillonkearns/elm-oembed": "1.0.0",
+ "dillonkearns/elm-rss": "1.0.0",
+ "dillonkearns/elm-sitemap": "1.0.0",
+ "dmy/elm-imf-date-time": "1.0.1",
"elm/browser": "1.0.2",
"elm/core": "1.0.2",
"elm/html": "1.0.0",
@@ -42,7 +46,10 @@
"elm/regex": "1.0.0",
"elm/virtual-dom": "1.0.2",
"fredcy/elm-parseint": "2.0.1",
- "mgold/elm-nonempty-list": "4.0.2"
+ "justinmimbs/time-extra": "1.1.0",
+ "lazamar/dict-parser": "1.0.2",
+ "mgold/elm-nonempty-list": "4.0.2",
+ "ryannhg/date-format": "2.3.0"
}
},
"test-dependencies": {
diff --git a/examples/docs/src/DocSidebar.elm b/examples/docs/src/DocSidebar.elm
index a6fad82b..8eb136ff 100644
--- a/examples/docs/src/DocSidebar.elm
+++ b/examples/docs/src/DocSidebar.elm
@@ -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
diff --git a/examples/docs/src/Feed.elm b/examples/docs/src/Feed.elm
new file mode 100644
index 00000000..b09dcc0e
--- /dev/null
+++ b/examples/docs/src/Feed.elm
@@ -0,0 +1,72 @@
+module Feed exposing (fileToGenerate)
+
+import Metadata exposing (Metadata(..))
+import Pages
+import Pages.PagePath as PagePath exposing (PagePath)
+import Rss
+
+
+fileToGenerate :
+ { siteTagline : String
+ , siteUrl : String
+ }
+ ->
+ List
+ { path : PagePath Pages.PathKey
+ , frontmatter : Metadata
+ , body : String
+ }
+ ->
+ { path : List String
+ , content : String
+ }
+fileToGenerate config siteMetadata =
+ { path = [ "blog", "feed.xml" ]
+ , content = generate config siteMetadata
+ }
+
+
+generate :
+ { siteTagline : String
+ , siteUrl : String
+ }
+ ->
+ List
+ { path : PagePath Pages.PathKey
+ , frontmatter : Metadata
+ , body : String
+ }
+ -> String
+generate { siteTagline, siteUrl } siteMetadata =
+ Rss.generate
+ { title = "elm-pages Blog"
+ , description = siteTagline
+ , url = "https://elm-pages.com/blog"
+ , lastBuildTime = Pages.builtAt
+ , generator = Just "elm-pages"
+ , items = siteMetadata |> List.filterMap metadataToRssItem
+ , siteUrl = siteUrl
+ }
+
+
+metadataToRssItem :
+ { path : PagePath Pages.PathKey
+ , frontmatter : Metadata
+ , body : String
+ }
+ -> Maybe Rss.Item
+metadataToRssItem page =
+ case page.frontmatter of
+ Article article ->
+ Just
+ { title = article.title
+ , description = article.description
+ , url = PagePath.toString page.path
+ , categories = []
+ , author = article.author.name
+ , pubDate = Rss.Date article.published
+ , content = Nothing
+ }
+
+ _ ->
+ Nothing
diff --git a/examples/docs/src/FontAwesome.elm b/examples/docs/src/FontAwesome.elm
new file mode 100644
index 00000000..a8150b77
--- /dev/null
+++ b/examples/docs/src/FontAwesome.elm
@@ -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
diff --git a/examples/docs/src/Index.elm b/examples/docs/src/Index.elm
index eec52f9c..ac439780 100644
--- a/examples/docs/src/Index.elm
+++ b/examples/docs/src/Index.elm
@@ -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
diff --git a/examples/docs/src/Main.elm b/examples/docs/src/Main.elm
index 97ca1542..9b2d9371 100644
--- a/examples/docs/src/Main.elm
+++ b/examples/docs/src/Main.elm
@@ -8,8 +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)
@@ -19,6 +22,7 @@ import Json.Decode as Decode exposing (Decoder)
import Json.Decode.Exploration as D
import MarkdownRenderer
import Metadata exposing (Metadata)
+import MySitemap
import Pages exposing (images, pages)
import Pages.Directory as Directory exposing (Directory)
import Pages.Document
@@ -30,6 +34,7 @@ import Pages.Platform exposing (Page)
import Pages.StaticHttp as StaticHttp
import Palette
import Secrets
+import Showcase
manifest : Manifest.Config Pages.PathKey
@@ -62,11 +67,31 @@ main =
, documents = [ markdownDocument ]
, manifest = manifest
, canonicalSiteUrl = canonicalSiteUrl
+ , generateFiles = generateFiles
, onPageChange = OnPageChange
, internals = Pages.internals
}
+generateFiles :
+ List
+ { path : PagePath Pages.PathKey
+ , frontmatter : Metadata
+ , body : String
+ }
+ ->
+ List
+ (Result String
+ { path : List String
+ , content : String
+ }
+ )
+generateFiles siteMetadata =
+ [ Feed.fileToGenerate { siteTagline = siteTagline, siteUrl = canonicalSiteUrl } siteMetadata |> Ok
+ , MySitemap.build { siteUrl = canonicalSiteUrl } siteMetadata |> Ok
+ ]
+
+
markdownDocument : ( String, Pages.Document.DocumentHandler Metadata ( MarkdownRenderer.TableOfContents, List (Element Msg) ) )
markdownDocument =
Pages.Document.parser
@@ -77,7 +102,8 @@ markdownDocument =
type alias Model =
- {}
+ { showMobileMenu : Bool
+ }
init :
@@ -88,7 +114,7 @@ init :
}
-> ( Model, Cmd Msg )
init maybePagePath =
- ( Model, Cmd.none )
+ ( Model False, Cmd.none )
type Msg
@@ -97,13 +123,17 @@ type Msg
, query : Maybe String
, fragment : Maybe String
}
+ | 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
@@ -123,17 +153,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
+ }
+ )
@@ -186,8 +238,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
@@ -203,8 +254,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
@@ -234,8 +284,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 ]
@@ -264,8 +313,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
@@ -283,15 +331,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
@@ -310,51 +384,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
@@ -379,6 +494,13 @@ highlightableLink currentPath linkDirectory displayName =
}
+commonHeadTags : List (Head.Tag Pages.PathKey)
+commonHeadTags =
+ [ Head.rssLink "/blog/feed.xml"
+ , Head.sitemapLink "/sitemap.xml"
+ ]
+
+
{-|
@@ -386,111 +508,129 @@ highlightableLink currentPath linkDirectory displayName =
-}
head : Metadata -> List (Head.Tag Pages.PathKey)
head metadata =
- case metadata of
- Metadata.Page meta ->
- Seo.summaryLarge
- { canonicalUrlOverride = Nothing
- , siteName = "elm-pages"
- , image =
- { url = images.iconPng
- , alt = "elm-pages logo"
- , dimensions = Nothing
- , mimeType = Nothing
- }
- , description = siteTagline
- , locale = Nothing
- , title = meta.title
- }
- |> Seo.website
+ commonHeadTags
+ ++ (case metadata of
+ Metadata.Page meta ->
+ Seo.summaryLarge
+ { canonicalUrlOverride = Nothing
+ , siteName = "elm-pages"
+ , image =
+ { url = images.iconPng
+ , alt = "elm-pages logo"
+ , dimensions = Nothing
+ , mimeType = Nothing
+ }
+ , description = siteTagline
+ , locale = Nothing
+ , title = meta.title
+ }
+ |> Seo.website
- Metadata.Doc meta ->
- Seo.summaryLarge
- { canonicalUrlOverride = Nothing
- , siteName = "elm-pages"
- , image =
- { url = images.iconPng
- , alt = "elm pages logo"
- , dimensions = Nothing
- , mimeType = Nothing
- }
- , locale = Nothing
- , description = siteTagline
- , title = meta.title
- }
- |> Seo.website
+ Metadata.Doc meta ->
+ Seo.summaryLarge
+ { canonicalUrlOverride = Nothing
+ , siteName = "elm-pages"
+ , image =
+ { url = images.iconPng
+ , alt = "elm pages logo"
+ , dimensions = Nothing
+ , mimeType = Nothing
+ }
+ , locale = Nothing
+ , description = siteTagline
+ , title = meta.title
+ }
+ |> Seo.website
- Metadata.Article meta ->
- Seo.summaryLarge
- { canonicalUrlOverride = Nothing
- , siteName = "elm-pages"
- , image =
- { url = meta.image
- , alt = meta.description
- , dimensions = Nothing
- , mimeType = Nothing
- }
- , description = meta.description
- , locale = Nothing
- , title = meta.title
- }
- |> Seo.article
- { tags = []
- , section = Nothing
- , publishedTime = Just (Date.toIsoString meta.published)
- , modifiedTime = Nothing
- , expirationTime = Nothing
- }
+ Metadata.Article meta ->
+ Seo.summaryLarge
+ { canonicalUrlOverride = Nothing
+ , siteName = "elm-pages"
+ , image =
+ { url = meta.image
+ , alt = meta.description
+ , dimensions = Nothing
+ , mimeType = Nothing
+ }
+ , description = meta.description
+ , locale = Nothing
+ , title = meta.title
+ }
+ |> Seo.article
+ { tags = []
+ , section = Nothing
+ , publishedTime = Just (Date.toIsoString meta.published)
+ , modifiedTime = Nothing
+ , expirationTime = Nothing
+ }
- Metadata.Author meta ->
- let
- ( firstName, lastName ) =
- case meta.name |> String.split " " of
- [ first, last ] ->
- ( first, last )
+ Metadata.Author meta ->
+ let
+ ( firstName, lastName ) =
+ case meta.name |> String.split " " of
+ [ first, last ] ->
+ ( first, last )
- [ first, middle, last ] ->
- ( first ++ " " ++ middle, last )
+ [ first, middle, last ] ->
+ ( first ++ " " ++ middle, last )
- [] ->
- ( "", "" )
+ [] ->
+ ( "", "" )
- _ ->
- ( meta.name, "" )
- in
- Seo.summary
- { canonicalUrlOverride = Nothing
- , siteName = "elm-pages"
- , image =
- { url = meta.avatar
- , alt = meta.name ++ "'s elm-pages articles."
- , dimensions = Nothing
- , mimeType = Nothing
- }
- , description = meta.bio
- , locale = Nothing
- , title = meta.name ++ "'s elm-pages articles."
- }
- |> Seo.profile
- { firstName = firstName
- , lastName = lastName
- , username = Nothing
- }
+ _ ->
+ ( meta.name, "" )
+ in
+ Seo.summary
+ { canonicalUrlOverride = Nothing
+ , siteName = "elm-pages"
+ , image =
+ { url = meta.avatar
+ , alt = meta.name ++ "'s elm-pages articles."
+ , dimensions = Nothing
+ , mimeType = Nothing
+ }
+ , description = meta.bio
+ , locale = Nothing
+ , title = meta.name ++ "'s elm-pages articles."
+ }
+ |> Seo.profile
+ { firstName = firstName
+ , lastName = lastName
+ , username = Nothing
+ }
- Metadata.BlogIndex ->
- 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 blog"
- }
- |> Seo.website
+ Metadata.BlogIndex ->
+ 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 blog"
+ }
+ |> 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
diff --git a/examples/docs/src/Metadata.elm b/examples/docs/src/Metadata.elm
index 47182df4..81010743 100644
--- a/examples/docs/src/Metadata.elm
+++ b/examples/docs/src/Metadata.elm
@@ -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)
diff --git a/examples/docs/src/MySitemap.elm b/examples/docs/src/MySitemap.elm
new file mode 100644
index 00000000..331c4868
--- /dev/null
+++ b/examples/docs/src/MySitemap.elm
@@ -0,0 +1,32 @@
+module MySitemap exposing (..)
+
+import Metadata exposing (Metadata(..))
+import Pages
+import Pages.PagePath as PagePath exposing (PagePath)
+import Sitemap
+
+
+build :
+ { siteUrl : String
+ }
+ ->
+ List
+ { path : PagePath Pages.PathKey
+ , frontmatter : Metadata
+ , body : String
+ }
+ ->
+ { path : List String
+ , content : String
+ }
+build config siteMetadata =
+ { path = [ "sitemap.xml" ]
+ , content =
+ Sitemap.build config
+ (siteMetadata
+ |> List.map
+ (\page ->
+ { path = PagePath.toString page.path, lastMod = Nothing }
+ )
+ )
+ }
diff --git a/examples/docs/src/Showcase.elm b/examples/docs/src/Showcase.elm
new file mode 100644
index 00000000..c9d266f2
--- /dev/null
+++ b/examples/docs/src/Showcase.elm
@@ -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"
+ ]
diff --git a/examples/docs/style.css b/examples/docs/style.css
index 71e52056..bd5c322a 100644
--- a/examples/docs/style.css
+++ b/examples/docs/style.css
@@ -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;
+ }
+}
diff --git a/generator/src/add-files-plugin.js b/generator/src/add-files-plugin.js
index 66ed8fcf..6594a310 100644
--- a/generator/src/add-files-plugin.js
+++ b/generator/src/add-files-plugin.js
@@ -20,8 +20,11 @@ function unpackFile(filePath) {
}
module.exports = class AddFilesPlugin {
- constructor(data) {
+ constructor(data, filesToGenerate) {
this.pagesWithRequests = data;
+ this.filesToGenerate = filesToGenerate;
+ console.log('this.filesToGenerate', this.filesToGenerate);
+
}
apply(compiler) {
compiler.hooks.emit.tap("AddFilesPlugin", compilation => {
@@ -52,6 +55,18 @@ module.exports = class AddFilesPlugin {
size: () => rawContents.length
};
});
+
+ this.filesToGenerate.forEach(file => {
+ // Couldn't find this documented in the webpack docs,
+ // but I found the example code for it here:
+ // https://github.com/jantimon/html-webpack-plugin/blob/35a154186501fba3ecddb819b6f632556d37a58f/index.js#L470-L478
+ compilation.assets[file.path] = {
+ source: () => file.content,
+ size: () => file.content.length
+ };
+ });
+
+
});
}
};
diff --git a/generator/src/develop.js b/generator/src/develop.js
index 4382e1d2..775daece 100644
--- a/generator/src/develop.js
+++ b/generator/src/develop.js
@@ -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");
@@ -15,11 +16,12 @@ const ClosurePlugin = require("closure-webpack-plugin");
const readline = require("readline");
module.exports = { start, run };
-function start({ routes, debug, customPort, manifestConfig, routesWithRequests }) {
+function start({ routes, debug, customPort, manifestConfig, routesWithRequests, filesToGenerate }) {
const config = webpackOptions(false, routes, {
debug,
manifestConfig,
- routesWithRequests
+ routesWithRequests,
+ filesToGenerate
});
const compiler = webpack(config);
@@ -65,12 +67,13 @@ function start({ routes, debug, customPort, manifestConfig, routesWithRequests }
// app.use(express.static(__dirname + "/path-to-static-folder"));
}
-function run({ routes, manifestConfig, routesWithRequests }, callback) {
+function run({ routes, manifestConfig, routesWithRequests, filesToGenerate }, callback) {
webpack(
webpackOptions(true, routes, {
debug: false,
manifestConfig,
- routesWithRequests
+ routesWithRequests,
+ filesToGenerate
})
).run((err, stats) => {
if (err) {
@@ -118,12 +121,12 @@ function printProgress(progress, message) {
function webpackOptions(
production,
routes,
- { debug, manifestConfig, routesWithRequests }
+ { debug, manifestConfig, routesWithRequests, filesToGenerate }
) {
const common = {
mode: production ? "production" : "development",
plugins: [
- new AddFilesPlugin(routesWithRequests),
+ new AddFilesPlugin(routesWithRequests, filesToGenerate),
new CopyPlugin([
{
from: "static/**/*",
@@ -159,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: {
diff --git a/generator/src/elm-pages.js b/generator/src/elm-pages.js
index 09a2c687..8d892ec6 100755
--- a/generator/src/elm-pages.js
+++ b/generator/src/elm-pages.js
@@ -86,6 +86,7 @@ function run() {
markdownContent,
content,
function(payload) {
+ console.log('@@@@@@@@@ filesToGenerate', payload.filesToGenerate);
if (contents.watch) {
startWatchIfNeeded();
if (!devServerRunning) {
@@ -94,7 +95,9 @@ function run() {
routes,
debug: contents.debug,
manifestConfig: payload.manifest,
- routesWithRequests: payload.pages
+ routesWithRequests: payload.pages,
+ filesToGenerate: payload.filesToGenerate,
+ customPort: contents.customPort
});
}
} else {
@@ -106,7 +109,8 @@ function run() {
{
routes,
manifestConfig: payload.manifest,
- routesWithRequests: payload.pages
+ routesWithRequests: payload.pages,
+ filesToGenerate: payload.filesToGenerate
},
() => {}
);
diff --git a/generator/src/generate-raw-content.js b/generator/src/generate-raw-content.js
index d7e6c716..8aa84438 100644
--- a/generator/src/generate-raw-content.js
+++ b/generator/src/generate-raw-content.js
@@ -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}"
} )
`;
diff --git a/generator/src/service-worker-template.js b/generator/src/service-worker-template.js
index 5404bcea..70e8971d 100644
--- a/generator/src/service-worker-template.js
+++ b/generator/src/service-worker-template.js
@@ -4,7 +4,7 @@ workbox.precaching.precacheAndRoute(self.__precacheManifest);
workbox.routing.registerNavigationRoute(
workbox.precaching.getCacheKeyForURL("/index.html"),
{
- blacklist: [/admin/]
+ blacklist: [/admin/, /\./]
}
);
workbox.routing.registerRoute(
diff --git a/generator/src/template.html b/generator/src/template.html
index 5859a472..08520225 100644
--- a/generator/src/template.html
+++ b/generator/src/template.html
@@ -1,7 +1,7 @@
-
+