Merge pull request #109 from dillonkearns/template-modules

Try file structure with each template in its own module
This commit is contained in:
Dillon Kearns 2020-10-26 11:52:55 -07:00 committed by GitHub
commit 73cf428b19
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
45 changed files with 3235 additions and 882 deletions

View File

@ -9,37 +9,49 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
## [Unreleased]
## [7.0.0] - 2020-10-26
### Fixed
- Fixed a bug where using `ImagePath.external` in any `Head` tags would prepend the canonical site URL to the external URL, creating an invalid URL. Now it will only prepend the canonical site URL for local images, and it will use external image URLs directly with no modifications.
- StaticHttp performance improvements - whether you use the new beta build or the existing `elm-pages build` or `elm-pages develop` commands, you should see significantly faster StaticHttp any place you combined multiple StaticHttp results together. I would welcome getting any before/after performance numbers!
## [6.0.0] - 2020-07-14
### Fixed
- Fixed missing content message flash for pages that are hosted on a sub path: https://github.com/dillonkearns/elm-pages/issues/106.
## [5.0.2] - 2020-06-16
### Fixed
- Fixed issue where CLI would hang when fetching StaticHttp data for `generateFiles` functions. The problem was a looping condition for completing the CLI process to fetch StaticHttp data.
See [#120](https://github.com/dillonkearns/elm-pages/pull/120).
## [5.0.1] - 2020-05-13
### Fixed
- Make sure the build fails when there are `Err` results in any markdown content. Fixes [#102](https://github.com/dillonkearns/elm-pages/issues/102).
This fix also means that any markdown errors will cause the error overlay in the dev server to show.
## [5.0.0] - 2020-05-11
### Changed
- Use builder pattern to build application. In place of the old `Pages.Platform.application`, you now start building an application config with `Pages.Platform.init`, and complete it with `Pages.Platform.toProgram`. You can chain on some calls to your application builder. This is handy for creating plugins that generate some files and add some head tags using `withGlobalHeadTags`.
- The `documents` key is now a List of records. The `Pages.Document` module has been removed entirely in place of a simplified API. `elm-markup` files no longer have any special handling
and the direct dependency was removed from `elm-pages`. Instead, to use `elm-markup` with `elm-pages`, you now wire it in as you would with a markdown parser or any other document handler.
- Replaced `generateFiles` field in `Pages.Platform.application` with the `Pages.Platform.withFileGenerator` function.
- Instead of using the `zwilias/json-decode-exploration` package directly to build up optimizable decoders, you now use the `OptimizedDecoder` API. It provides the same drop-in replacement,
with the same underlying package. But it now uses a major optimization where in your production build, it will run a plain `elm/json` decoder
with the same underlying package. But it now uses a major optimization where in your production build, it will run a plain `elm/json` decoder
(on the optimized JSON asset that was produced in the build step) to improve performance.
### Added
- Added `Head.Seo.structuredData`. Check out Google's [structured data gallery](https://developers.google.com/search/docs/guides/search-gallery) to see some examples of what structured
data looks like in rich search results that it provides. Right now, the API takes a simple `Json.Encode.Value`. In the `elm-pages` repo, I have an example API that I use,
data looks like in rich search results that it provides. Right now, the API takes a simple `Json.Encode.Value`. In the `elm-pages` repo, I have an example API that I use,
but it's not public yet because I want to refine the API before releasing it (and it's a large undertaking!). But for now, you can add whatever structured data you need,
you'll just have to be careful to build up a valid format according to schema.org.
- `Pages.Directory.basePath` and `Pages.Directory.basePathToString` helpers.
@ -49,6 +61,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
## [4.0.1] - 2020-03-28
### Added
- You can now host your `elm-pages` site in a sub-directory. For example, you could host it at mysite.com/blog, where the top-level mysite.com/ is hosting a different app.
This works using [HTML `<base>` tags](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base). The paths you get from `PagePath.toString` and `ImagePath.toString`
will use relative paths (e.g. `blog/my-article`) instead of absolute URLs (e.g. `/blog/my-article`), so you can take advantage of this functionality by just making sure you
@ -57,9 +70,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
## [4.0.0] - 2020-03-04
### Changed
- `StaticHttp.stringBody` now takes an argument for the MIME type.
### Added
- `StaticHttp.unoptimizedRequest` allows you to decode responses of any type by passing in a `StaticHttp.Expect`.
- `StaticHttp.expectString` can be used to parse any values, like XML or plaintext. Note that the payload won't be stripped
down so be sure to check the asset sizes that you're fetching carefully.
@ -67,21 +82,24 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
## [3.0.2] - 2020-02-03
### Fixed
- Fixed an issue where "Missing content" message flashed for the root page.
- Scroll up to the top of the page on page navigations (Elm's core Browser.application doesn't do this automatically). This change
preserves the behavior for navigating to anchor links, so you can still go to a fragment and it will take you to the appropriate part
of the page without scrolling to the top in those cases.
preserves the behavior for navigating to anchor links, so you can still go to a fragment and it will take you to the appropriate part
of the page without scrolling to the top in those cases.
## [3.0.1] - 2020-01-30
### Changed
- Pass allRoutes into pre-rendering for https://github.com/dillonkearns/elm-pages/pull/60.
## [3.0.0] - 2020-01-25
### Changed
- Added URL query and fragment in addition to the PagePath provided by `init` and `onPageChange`.
See [#39](https://github.com/dillonkearns/elm-pages/pull/39). The new data structure used looks like this:
See [#39](https://github.com/dillonkearns/elm-pages/pull/39). The new data structure used looks like this:
```elm
{ path : PagePath Pages.PathKey
@ -93,50 +111,55 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
## [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.
- 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.
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
* Don't reload pages when clicking a link to the exact same URL as current URL. Fixes [#29](https://github.com/dillonkearns/elm-pages/issues/29).
- Don't reload pages when clicking a link to the exact same URL as current URL. Fixes [#29](https://github.com/dillonkearns/elm-pages/issues/29).
## [1.1.0] - 2020-01-03
Check out [this upgrade checklist](https://github.com/dillonkearns/elm-pages/blob/master/docs/upgrade-guide.md#upgrading-to-elm-package-110-and-npm-package-113) for more details and steps for upgrading your project.
### Added
- There's a new StaticHttp API. Read more about it in [this `StaticHttp` announcement blog post](http://elm-pages.com/blog/static-http)!
- The generated `Pages.elm` module now includes `builtAt : Time.Posix`. Make sure you have `elm/time` as a dependency in your project!
You can use this when you make API requests to filter based on a date range starting with the current date.
If you want a random seed that changes on each build (or every week, or every month, etc.), then you can use this time stamp
(and perform modulo arithemtic based on the date for each week, month, etc.) and use that number as a random seed.
You can use this when you make API requests to filter based on a date range starting with the current date.
If you want a random seed that changes on each build (or every week, or every month, etc.), then you can use this time stamp
(and perform modulo arithemtic based on the date for each week, month, etc.) and use that number as a random seed.
### Changed
- Instead of initializing an application using `Pages.application` from the generated `Pages` module, you now initialize the app
using `Pages.Platform.application` which is part of the published Elm package. So now it's easier to browse the docs.
You pass in some internal data from the generated `Pages.elm` module now by including
this in the application config record: `Pages.Platform.application { internals = Pages.internals, ... <other fields> }`.
- Add init argument and user Msg for initial PagePath and page changes (see [#4](https://github.com/dillonkearns/elm-pages/issues/4)).
- Instead of initializing an application using `Pages.application` from the generated `Pages` module, you now initialize the app
using `Pages.Platform.application` which is part of the published Elm package. So now it's easier to browse the docs.
You pass in some internal data from the generated `Pages.elm` module now by including
this in the application config record: `Pages.Platform.application { internals = Pages.internals, ... <other fields> }`.
- Add init argument and user Msg for initial PagePath and page changes (see [#4](https://github.com/dillonkearns/elm-pages/issues/4)).
## [1.0.1] - 2019-11-04
### Fixed
- Generate files for extensions other than `.md` and `.emu` (fixes [#16](https://github.com/dillonkearns/elm-pages/issues/16)).
As always, be sure to also use the latest NPM package.
As always, be sure to also use the latest NPM package.

View File

@ -0,0 +1,98 @@
# 7.0.0 Elm package upgrade guide
Please ensure that you're on the latest elm-pages version of both the Elm package _and_ the NPM package before following these steps.
There are two new beta features, which you can opt into by running a different build command (see 2) or calling a new generated function (see 3).
There are 3 broad areas of change in this release.
1. Breaking API changes
2. Beta build command
3. Beta Template Modules feature
You can ignore (2) and (3) if you aren't interested in beta features. And even if you do choose to try these beta features, I recommend starting with (1) and getting things compiling without using any beta features first.
## 1 - Breaking API changes
### Manifest.Config now has `icons`
- The `icons` field in the manifest config will only be used for the beta, no-webpack build (see section 2). If you aren't using it, you can simply pass in an empty list for icons. The new field in the Manifest.Config has this type `icons : List.List (Pages.Manifest.Icon pathKey)`.
- `Program model msg metadata view` changed to `Program model msg metadata view pathKey`. That means there is a new type variable in `Pages.Platform.Program`. You can fix this by adding `Pages.PathKey` (a type defined in the generated Pages.elm module) as the last type variable wherever you had an annotation using the `Pages.Platform.Program` type.
The following functions in `Pages.Platform.init` have also changed:
```elm
, onPageChange :
Maybe
(
{ path : PagePath pathKey
, query : Maybe String
, fragment : Maybe String
}
-> msg
)
, onPageChange :
Maybe.Maybe
(
{ path : Pages.PagePath.PagePath pathKey
, query : Maybe.Maybe String.String
, fragment : Maybe.Maybe String.String
, metadata : metadata
}
-> msg
)
, init :
Maybe
{ path : PagePath pathKey
, query : Maybe String
, fragment : Maybe String
}
-> ( model, Cmd msg )
, init :
Maybe.Maybe
{ path :
{ path : Pages.PagePath.PagePath pathKey
, query : Maybe.Maybe String.String
, fragment : Maybe.Maybe String.String
}
, metadata : metadata
}
-> ( model, Platform.Cmd.Cmd msg )
, subscriptions : model -> Sub msg
, subscriptions :
metadata
-> Pages.PagePath.PagePath pathKey
-> model
-> Platform.Sub.Sub msg
```
## 2 - Beta build command
You can run the regular build command and the beta build command side by side, and have the beta entrypoints living next to the current JS entrypoint you have (index.js). Hopefully that makes it easy to try out the beta and experiment with it without needing to change over right away.
- `elm-pages build` and `elm-pages develop` use the `index.js` entrypoint.
- A new command, `elm-pages-beta` (doesn't take any arguments) uses the `beta-index.js` and `beta-style.css` entrypoints.
Note that before you would use webpack to import CSS from the JS entrypoint (or something that was imported from there). Now there are separate entrypoints for JS and CSS.
Some key points about the no-webpack build:
- Whether you're using the beta no-webpack build or the previous build, you will see significant performance improvements for StaticHttp
- You can continue using the current elm-pages build and elm-pages develop commands. If you do that, you can just pass in icons = [] for the manifest config as the icons are only read for the new beta build.
- For the beta build, you can use the Manifest config's icons to set the PWA icon set, and you can set the favicon set using head tags an example here: https://github.com/dillonkearns/elm-pages/blob/5ad85cad0d5de9631ea06f98bba8ef1c96b1908a/examples/simple/src/Main.elm#L41-L130. Note that the beta build does not generate icons for you (I'm using cloudinary in the example, and it works way better and doesn't slow down the build). The beta build also doesn't do all of these things to remove bloat and give the user more control, while also making the elm-pages build more focused on doing a great job with the Elm code: https://github.com/dillonkearns/elm-pages/issues/148
- To use the elm-pages-beta, you just need to create beta-index.js (using ES module syntax, see this example: https://github.com/dillonkearns/elm-pages/blob/elm-to-html/examples/simple/beta-index.js), and a beta-style.css entrypoint (this only using @import syntax, see https://github.com/dillonkearns/elm-pages/blob/elm-to-html/examples/simple/beta-style.css - you can bundle CSS code to this entrypoint if you need CSS bundling).
---- Head - MINOR ----
Added:
appleTouchIcon :
Maybe.Maybe Basics.Int
-> Pages.ImagePath.ImagePath pathKey
-> Head.Tag pathKey
icon :
List.List ( Basics.Int, Basics.Int )
-> MimeType.MimeImage
-> Pages.ImagePath.ImagePath pathKey
-> Head.Tag pathKey
## 3 - Beta Template Modules feature

View File

@ -17,6 +17,9 @@ your files (both code and content).
├── index.js
├── package.json
└── src/
└── Template/
├── Bio.elm # user-defined template modules
└── Catalog.elm
└── Main.elm
```
@ -74,6 +77,74 @@ markdownDocument =
```
## Metadata
## Modules
You define how your metadata is parsed
### Templates
`src/Template/*.elm`
A template represents a type of page. For example, a BlogPost template could live in `src/Template/BlogPost.elm`. Any files in your `content/` folder with frontmatter that you decode into type `TemplateType.BlogPost` will be rendered using your `BlogPost` template.
Think of each template as having its own mini `elm-pages architecture` lifecycle.
Imagine you have a site called thegreatcomposers.com that lists the greatest works of Classical composers.
Let's say you have a file called `content/catalog/sibelius.md` with these contents:
```markdown
---
template: catalog
composer: Sibelius
---
## Symphony 2, Op. 47
### Notable Recordings
Bernstein Vienna Philharmonic
```
You have a metadata decoder like this:
```elm
module Metadata exposing (Metadata, decoder)
type Metadata = Catalog Composer | Bio Composer
type Composer = Sibelius | Mozart
decoder =
Decode.string
|> Decode.field "template"
|> Decode.andThen (\template ->
case template of
"catalog" -> Decode.map Catalog decodeComposer
"bio" -> Decode.map Bio decodeComposer
)
```
Now say you navigate to `/catalog/sibelius`. Let's look at the `elm-pages architecture` lifecycle steps that kick in.
### Build
* `staticData` - When you build your site (using `elm-pages build` for prod or `elm-pages develop` in dev mode), the `staticData` will be fetched for this page. Your `staticData` request has access to the page's `Metadata`. So if you wanted to request `api.composers.com/portrait-images/<composer-name>` to get the list of images for each composer's catalog page, you could. Behind the scenes, `elm-pages` will make sure this data is loaded for you in the browser so you have access to this data, even though the API is only hit during the initial build and then stored as a JSON asset for your site.
#### Page Load
* `init` - the page for Sibelius' catalog has its own state. Let's display a Carousel that shows photos of the composer. `init` is called when you navigate to this page. If you navigate to another composer's catalog page, like Mozart, it will call the same `init` function to get a fresh Model for the new page, passing in the metadata for the Mozart page (from the frontmatter in `content/catalog/mozart`.
* `view` given the page's state, metadata, and StaticHttp data, you can render the catalog for Sibelius.
#### Page Interaction
* `update` - if you click the Carousel, the page's state gets updated.
### Shared
`src/Shared.elm`
* `staticData` (loaded per-app, not per-page)
* `View` - the data type that pages render to in your app
* `view` - the top-level view function for your app
### Build
`src/Build.elm`
* `staticData` (build-only)
* `manifest`
* `generateFiles`
### Global Metadata
`src/TemplateType.elm`
This module must define a variant for each template module.

View File

@ -0,0 +1,8 @@
{
"name": "dillonkearns/elm-pages-local",
"summary": "Elm Pages local docs",
"version": "1.0.0",
"exposed-modules": [
"Template"
]
}

View File

@ -7,11 +7,12 @@ import Metadata exposing (Metadata)
import Pages
import Pages.PagePath as PagePath exposing (PagePath)
import Palette
import TemplateType exposing (TemplateType)
view :
PagePath Pages.PathKey
-> List ( PagePath Pages.PathKey, Metadata )
-> List ( PagePath Pages.PathKey, TemplateType )
-> Element msg
view currentPage posts =
Element.column
@ -25,7 +26,7 @@ view currentPage posts =
|> List.filterMap
(\( path, metadata ) ->
case metadata of
Metadata.Doc meta ->
TemplateType.Documentation meta ->
Just ( currentPage == path, path, meta )
_ ->

View File

@ -1,19 +1,21 @@
module Index exposing (view)
--import Pages.Metadata as Metadata exposing (Metadata)
import Data.Author
import Date
import Element exposing (Element)
import Element.Border
import Element.Font
import Metadata exposing (Metadata)
import Pages
import Pages.ImagePath as ImagePath exposing (ImagePath)
import Pages.PagePath as PagePath exposing (PagePath)
import Pages.Platform exposing (Page)
import TemplateMetadata exposing (BlogPost)
import TemplateType exposing (TemplateType)
view :
List ( PagePath Pages.PathKey, Metadata )
List ( PagePath Pages.PathKey, TemplateType )
-> Element msg
view posts =
Element.column [ Element.spacing 20 ]
@ -21,7 +23,7 @@ view posts =
|> List.filterMap
(\( path, metadata ) ->
case metadata of
Metadata.Article meta ->
TemplateType.BlogPost meta ->
if meta.draft then
Nothing
@ -33,16 +35,14 @@ view posts =
)
|> List.sortBy
(\( path, metadata ) ->
metadata.published
|> Date.toRataDie
-(metadata.published |> Date.toRataDie)
)
|> List.reverse
|> List.map postSummary
)
postSummary :
( PagePath Pages.PathKey, Metadata.ArticleMetadata )
( PagePath Pages.PathKey, BlogPost )
-> Element msg
postSummary ( postPath, post ) =
articleIndex post |> linkToPost postPath
@ -66,7 +66,7 @@ title text =
]
articleIndex : Metadata.ArticleMetadata -> Element msg
articleIndex : BlogPost -> Element msg
articleIndex metadata =
Element.el
[ Element.centerX
@ -86,7 +86,7 @@ grey =
Element.Font.color (Element.rgba255 0 0 0 0.5)
postPreview : Metadata.ArticleMetadata -> Element msg
postPreview : BlogPost -> Element msg
postPreview post =
Element.textColumn
[ Element.centerX

View File

@ -2,65 +2,24 @@ module Main exposing (main)
import Cloudinary
import Color
import Data.Author as Author
import Date
import DocSidebar
import DocumentSvg
import Element exposing (Element)
import Element.Background
import Element.Border
import Element.Events
import Element.Font as Font
import Element.Region
import FontAwesome
import Data.Author
import Head
import Head.Seo as Seo
import Html exposing (Html)
import Html.Attributes as Attr
import Index
import Json.Decode as Decode exposing (Decoder)
import Json.Encode
import MarkdownRenderer
import Metadata exposing (Metadata)
import MetadataNew
import MimeType
import MySitemap
import OptimizedDecoder as D
import Pages exposing (images, pages)
import Pages.Directory as Directory exposing (Directory)
import Pages.ImagePath as ImagePath exposing (ImagePath)
import Pages.ImagePath exposing (ImagePath)
import Pages.Manifest as Manifest
import Pages.Manifest.Category
import Pages.PagePath as PagePath exposing (PagePath)
import Pages.Platform exposing (Page)
import Pages.StaticHttp as StaticHttp
import Palette
import Rss
import RssPlugin
import Secrets
import Showcase
import StructuredData
manifest : Manifest.Config Pages.PathKey
manifest =
{ backgroundColor = Just Color.white
, categories = [ Pages.Manifest.Category.education ]
, displayMode = Manifest.Standalone
, orientation = Manifest.Portrait
, description = "elm-pages - A statically typed site generator."
, iarcRatingId = Nothing
, name = "elm-pages docs"
, themeColor = Just Color.white
, startUrl = pages.index
, shortName = Just "elm-pages"
, sourceIcon = images.iconPng
, icons =
[ icon webp 192
, icon webp 512
, icon MimeType.Png 192
, icon MimeType.Png 512
]
}
import Shared
import Site
import TemplateModulesBeta
import TemplateType exposing (TemplateType)
webp : MimeType.MimeImage
@ -93,37 +52,30 @@ socialIcon =
Cloudinary.urlSquare "v1603234028/elm-pages/elm-pages-icon" Nothing 250
type alias View =
( MarkdownRenderer.TableOfContents, List (Element Msg) )
--main : Pages.Platform.Program Model Msg Metadata View Pages.PathKey
main : Pages.Platform.Program Model Msg Metadata View Pages.PathKey
main : Pages.Platform.Program TemplateModulesBeta.Model TemplateModulesBeta.Msg TemplateType Shared.RenderedBody Pages.PathKey
main =
Pages.Platform.init
{ init = init
, view = view
, update = update
, subscriptions = subscriptions
, documents =
TemplateModulesBeta.mainTemplate
{ documents =
[ { extension = "md"
, metadata = Metadata.decoder
, body = MarkdownRenderer.view
, metadata = MetadataNew.decoder -- metadata parser/decoder?
, body = MarkdownRenderer.view -- body parser?
}
]
, onPageChange = Just OnPageChange
, manifest = manifest
, canonicalSiteUrl = canonicalSiteUrl
, internals = Pages.internals
, site = Site.config
}
|> RssPlugin.generate
{ siteTagline = siteTagline
, siteUrl = canonicalSiteUrl
{ siteTagline = Site.tagline
, siteUrl = Site.canonicalUrl
, title = "elm-pages Blog"
, builtAt = Pages.builtAt
, indexPage = Pages.pages.blog.index
}
metadataToRssItem
|> MySitemap.install { siteUrl = canonicalSiteUrl } metadataToSitemapEntry
|> MySitemap.install { siteUrl = Site.canonicalUrl } metadataToSitemapEntry
|> Pages.Platform.withGlobalHeadTags
[ Head.icon [ ( 32, 32 ) ] MimeType.Png (cloudinaryIcon MimeType.Png 32)
, Head.icon [ ( 16, 16 ) ] MimeType.Png (cloudinaryIcon MimeType.Png 16)
@ -135,24 +87,24 @@ main =
metadataToRssItem :
{ path : PagePath Pages.PathKey
, frontmatter : Metadata
, frontmatter : TemplateType
, body : String
}
-> Maybe Rss.Item
metadataToRssItem page =
case page.frontmatter of
Metadata.Article article ->
if article.draft then
TemplateType.BlogPost blogPost ->
if blogPost.draft then
Nothing
else
Just
{ title = article.title
, description = article.description
{ title = blogPost.title
, description = blogPost.description
, url = PagePath.toString page.path
, categories = []
, author = article.author.name
, pubDate = Rss.Date article.published
, author = blogPost.author.name
, pubDate = Rss.Date blogPost.published
, content = Nothing
}
@ -163,7 +115,7 @@ metadataToRssItem page =
metadataToSitemapEntry :
List
{ path : PagePath Pages.PathKey
, frontmatter : Metadata
, frontmatter : TemplateType
, body : String
}
-> List { path : String, lastMod : Maybe String }
@ -172,8 +124,8 @@ metadataToSitemapEntry siteMetadata =
|> List.filter
(\page ->
case page.frontmatter of
Metadata.Article articleData ->
not articleData.draft
TemplateType.BlogPost blogPost ->
not blogPost.draft
_ ->
True
@ -182,570 +134,3 @@ metadataToSitemapEntry siteMetadata =
(\page ->
{ path = PagePath.toString page.path, lastMod = Nothing }
)
type alias Model =
{ showMobileMenu : Bool
}
init :
Maybe
{ path : PagePath Pages.PathKey
, query : Maybe String
, fragment : Maybe String
}
-> ( Model, Cmd Msg )
init maybePagePath =
( Model False, Cmd.none )
type Msg
= OnPageChange
{ path : PagePath Pages.PathKey
, query : Maybe String
, fragment : Maybe String
}
| ToggleMobileMenu
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
OnPageChange page ->
( { model | showMobileMenu = False }, Cmd.none )
ToggleMobileMenu ->
( { model | showMobileMenu = not model.showMobileMenu }, Cmd.none )
subscriptions : Model -> Sub Msg
subscriptions _ =
Sub.none
view :
List ( PagePath Pages.PathKey, Metadata )
->
{ path : PagePath Pages.PathKey
, frontmatter : Metadata
}
->
StaticHttp.Request
{ view : Model -> View -> { title : String, body : Html Msg }
, head : List (Head.Tag Pages.PathKey)
}
view siteMetadata page =
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.path 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.path page.frontmatter
}
)
pageView :
Int
-> Model
-> List ( PagePath Pages.PathKey, Metadata )
-> { path : PagePath Pages.PathKey, frontmatter : Metadata }
-> ( MarkdownRenderer.TableOfContents, List (Element Msg) )
-> { title : String, body : Element Msg }
pageView stars model siteMetadata page viewForPage =
case page.frontmatter of
Metadata.Page metadata ->
{ title = metadata.title
, body =
[ Element.column
[ Element.padding 50
, Element.spacing 60
, Element.Region.mainContent
]
(Tuple.second viewForPage)
]
|> Element.textColumn
[ Element.width Element.fill
]
}
Metadata.Article metadata ->
{ title = metadata.title
, body =
Element.column [ Element.width Element.fill ]
[ Element.column
[ Element.padding 30
, Element.spacing 40
, Element.Region.mainContent
, Element.width (Element.fill |> Element.maximum 800)
, Element.centerX
]
(Element.column [ Element.spacing 10 ]
[ Element.row [ Element.spacing 10 ]
[ Author.view [] metadata.author
, Element.column [ Element.spacing 10, Element.width Element.fill ]
[ Element.paragraph [ Font.bold, Font.size 24 ]
[ Element.text metadata.author.name
]
, Element.paragraph [ Font.size 16 ]
[ Element.text metadata.author.bio ]
]
]
]
:: (publishedDateView metadata |> Element.el [ Font.size 16, Font.color (Element.rgba255 0 0 0 0.6) ])
:: Palette.blogHeading metadata.title
:: articleImageView metadata.image
:: Tuple.second viewForPage
)
]
}
Metadata.Doc metadata ->
{ title = metadata.title
, body =
[ 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 ]
[ Palette.heading 1 [ Element.text metadata.title ]
, Element.column [ Element.spacing 20 ]
[ tocView (Tuple.first viewForPage)
, Element.column
[ Element.padding 50
, Element.spacing 30
, Element.Region.mainContent
]
(Tuple.second viewForPage)
]
]
]
]
|> Element.textColumn
[ Element.width Element.fill
, Element.height Element.fill
]
}
Metadata.Author author ->
{ title = author.name
, body =
Element.column
[ Element.width Element.fill
]
[ Element.column
[ Element.padding 30
, Element.spacing 20
, Element.Region.mainContent
, Element.width (Element.fill |> Element.maximum 800)
, Element.centerX
]
[ Palette.blogHeading author.name
, Author.view [] author
, Element.paragraph [ Element.centerX, Font.center ] (Tuple.second viewForPage)
]
]
}
Metadata.BlogIndex ->
{ title = "elm-pages blog"
, body =
Element.column [ Element.width Element.fill ]
[ 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 : Int -> { a | path : PagePath Pages.PathKey } -> Model -> { c | body : Element Msg, title : String } -> { body : Html Msg, title : String }
wrapBody stars page model 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
, Font.family [ Font.typeface "Roboto" ]
, Font.color (Element.rgba255 0 0 0 0.8)
]
, title = record.title
}
articleImageView : ImagePath Pages.PathKey -> Element msg
articleImageView articleImage =
Element.image [ Element.width Element.fill ]
{ src = ImagePath.toString articleImage
, description = "Article cover photo"
}
header : Int -> PagePath Pages.PathKey -> Element Msg
header stars currentPath =
Element.column [ Element.width Element.fill ]
[ responsiveHeader
, Element.column
[ Element.width Element.fill
, Element.htmlAttribute (Attr.class "responsive-desktop")
]
[ 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
]
}
]
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.class "navbar-title")
]
[ DocumentSvg.view
, Element.text "elm-pages"
]
}
logoLinkMobile =
Element.link []
{ url = "/"
, label =
Element.row
[ Font.size 30
, Element.spacing 16
, Element.htmlAttribute (Attr.class "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
-> String
-> Element msg
highlightableLink currentPath linkDirectory displayName =
let
isHighlighted =
currentPath |> Directory.includes linkDirectory
in
Element.link
(if isHighlighted then
[ Font.underline
, Font.color Palette.color.primary
]
else
[]
)
{ url = linkDirectory |> Directory.indexPath |> PagePath.toString
, label = Element.text displayName
}
{-| <https://developer.twitter.com/en/docs/tweets/optimize-with-cards/overview/abouts-cards>
<https://htmlhead.dev>
<https://html.spec.whatwg.org/multipage/semantics.html#standard-metadata-names>
<https://ogp.me/>
-}
head : PagePath Pages.PathKey -> Metadata -> List (Head.Tag Pages.PathKey)
head currentPath metadata =
case metadata of
Metadata.Page meta ->
Seo.summary
{ canonicalUrlOverride = Nothing
, siteName = "elm-pages"
, image =
{ url = socialIcon
, alt = "elm-pages logo"
, dimensions = Nothing
, mimeType = Nothing
}
, description = siteTagline
, locale = Nothing
, title = meta.title
}
|> Seo.website
Metadata.Doc meta ->
Seo.summary
{ canonicalUrlOverride = Nothing
, siteName = "elm-pages"
, image =
{ url = socialIcon
, alt = "elm pages logo"
, dimensions = Nothing
, mimeType = Nothing
}
, locale = Nothing
, description = siteTagline
, title = meta.title
}
|> Seo.website
Metadata.Article meta ->
Head.structuredData
(StructuredData.article
{ title = meta.title
, description = meta.description
, author = StructuredData.person { name = meta.author.name }
, publisher = StructuredData.person { name = "Dillon Kearns" }
, url = canonicalSiteUrl ++ "/" ++ PagePath.toString currentPath
, imageUrl = canonicalSiteUrl ++ "/" ++ ImagePath.toString meta.image
, datePublished = Date.toIsoString meta.published
, mainEntityOfPage =
StructuredData.softwareSourceCode
{ codeRepositoryUrl = "https://github.com/dillonkearns/elm-pages"
, description = "A statically typed site generator for Elm."
, author = "Dillon Kearns"
, programmingLanguage = StructuredData.elmLang
}
}
)
:: (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 )
[ 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
}
Metadata.BlogIndex ->
Seo.summary
{ canonicalUrlOverride = Nothing
, siteName = "elm-pages"
, image =
{ url = socialIcon
, alt = "elm-pages logo"
, dimensions = Nothing
, mimeType = Nothing
}
, description = siteTagline
, locale = Nothing
, title = "elm-pages blog"
}
|> Seo.website
Metadata.Showcase ->
Seo.summary
{ canonicalUrlOverride = Nothing
, siteName = "elm-pages"
, image =
{ url = socialIcon
, alt = "elm-pages logo"
, dimensions = Nothing
, mimeType = Nothing
}
, description = "See some neat sites built using elm-pages! (Or submit yours!)"
, locale = Nothing
, title = "elm-pages sites showcase"
}
|> Seo.website
canonicalSiteUrl : String
canonicalSiteUrl =
"https://elm-pages.com"
siteTagline : String
siteTagline =
"A statically typed site generator - elm-pages"
tocView : MarkdownRenderer.TableOfContents -> Element msg
tocView toc =
Element.column [ Element.alignTop, Element.spacing 20 ]
[ Element.el [ Font.bold, Font.size 22 ] (Element.text "Table of Contents")
, Element.column [ Element.spacing 10 ]
(toc
|> List.map
(\heading ->
Element.link [ Font.color (Element.rgb255 100 100 100) ]
{ url = "#" ++ heading.anchorId
, label = Element.text heading.name
}
)
)
]
publishedDateView metadata =
Element.text
(metadata.published
|> Date.format "MMMM ddd, yyyy"
)
githubRepoLink : Int -> Element msg
githubRepoLink starCount =
Element.newTabLink []
{ url = "https://github.com/dillonkearns/elm-pages"
, label =
Element.row [ Element.spacing 5 ]
[ Element.image
[ Element.width (Element.px 22)
, Font.color Palette.color.primary
]
{ src = ImagePath.toString Pages.images.github, description = "Github repo" }
, Element.text <| String.fromInt starCount
]
}
elmDocsLink : Element msg
elmDocsLink =
Element.newTabLink []
{ url = "https://package.elm-lang.org/packages/dillonkearns/elm-pages/latest/"
, label =
Element.image
[ Element.width (Element.px 22)
, Font.color Palette.color.primary
]
{ src = ImagePath.toString Pages.images.elmLogo, description = "Elm Package Docs" }
}

View File

@ -16,7 +16,6 @@ type Metadata
= Page PageMetadata
| Article ArticleMetadata
| Doc DocMetadata
| Author Data.Author.Author
| BlogIndex
| Showcase
@ -59,13 +58,6 @@ decoder =
"showcase" ->
Decode.succeed Showcase
"author" ->
Decode.map3 Data.Author.Author
(Decode.field "name" Decode.string)
(Decode.field "avatar" imageDecoder)
(Decode.field "bio" Decode.string)
|> Decode.map Author
"blog" ->
Decode.map6 ArticleMetadata
(Decode.field "title" Decode.string)

View File

@ -0,0 +1,89 @@
module MetadataNew exposing (DocMetadata, PageMetadata, decoder)
import Cloudinary
import Data.Author
import Date exposing (Date)
import Json.Decode as Decode exposing (Decoder)
import Pages
import Pages.ImagePath exposing (ImagePath)
import Template.BlogPost
import Template.Page
import Template.Showcase
import TemplateType exposing (TemplateType)
type alias DocMetadata =
{ title : String
}
type alias PageMetadata =
{ title : String }
type alias ArticleMetadata =
{ title : String
, description : String
, published : Date
, author : Data.Author.Author
, image : ImagePath Pages.PathKey
, draft : Bool
}
decoder : Decoder TemplateType
decoder =
Decode.field "type" Decode.string
|> Decode.andThen
(\pageType ->
case pageType of
"doc" ->
Decode.field "title" Decode.string
|> Decode.map (\title -> TemplateType.Documentation { title = title })
"page" ->
Template.Page.decoder
|> Decode.map TemplateType.Page
"blog-index" ->
Decode.succeed {}
|> Decode.map TemplateType.BlogIndex
"showcase" ->
Template.Showcase.decoder
|> Decode.map TemplateType.Showcase
"blog" ->
Decode.map6 ArticleMetadata
(Decode.field "title" Decode.string)
(Decode.field "description" Decode.string)
(Decode.field "published"
(Decode.string
|> Decode.andThen
(\isoString ->
case Date.fromIsoString isoString of
Ok date ->
Decode.succeed date
Err error ->
Decode.fail error
)
)
)
(Decode.field "author" Data.Author.decoder)
(Decode.field "image" imageDecoder)
(Decode.field "draft" Decode.bool
|> Decode.maybe
|> Decode.map (Maybe.withDefault False)
)
|> Decode.map TemplateType.BlogPost
_ ->
Decode.fail <| "Unexpected page \"type\" " ++ pageType
)
imageDecoder : Decoder (ImagePath Pages.PathKey)
imageDecoder =
Decode.string
|> Decode.map (\cloudinaryAsset -> Cloudinary.url cloudinaryAsset Nothing 800)

View File

View File

@ -0,0 +1,348 @@
module Shared exposing (Model, Msg(..), PageView, RenderedBody, SharedMsg(..), StaticData, template)
import DocumentSvg
import Element exposing (Element)
import Element.Background
import Element.Border
import Element.Events
import Element.Font as Font
import Element.Region
import FontAwesome
import Html exposing (Html)
import Html.Attributes as Attr
import MarkdownRenderer
import OptimizedDecoder as D
import Pages exposing (pages)
import Pages.Directory as Directory exposing (Directory)
import Pages.ImagePath as ImagePath
import Pages.PagePath as PagePath exposing (PagePath)
import Pages.StaticHttp as StaticHttp
import Palette
import Secrets
import TemplateType exposing (TemplateType)
type alias SharedTemplate templateDemuxMsg msg1 msg2 =
{ init :
Maybe
{ path :
{ path : PagePath Pages.PathKey
, query : Maybe String
, fragment : Maybe String
}
, metadata : TemplateType
}
-> ( Model, Cmd Msg )
, update : Msg -> Model -> ( Model, Cmd Msg )
, view :
StaticData
->
{ path : PagePath Pages.PathKey
, frontmatter : TemplateType
}
-> Model
-> (Msg -> templateDemuxMsg)
-> PageView templateDemuxMsg
-> { body : Html templateDemuxMsg, title : String }
, map : (msg1 -> msg2) -> PageView msg1 -> PageView msg2
, staticData : List ( PagePath Pages.PathKey, TemplateType ) -> StaticHttp.Request StaticData
, subscriptions : TemplateType -> PagePath Pages.PathKey -> Model -> Sub Msg
, onPageChange :
Maybe
({ path : PagePath Pages.PathKey
, query : Maybe String
, fragment : Maybe String
}
-> Msg
)
}
template : SharedTemplate msg msg1 msg2
template =
{ init = init
, update = update
, view = view
, map = map
, staticData = staticData
, subscriptions = subscriptions
, onPageChange = Just OnPageChange
}
type alias RenderedBody =
( MarkdownRenderer.TableOfContents, List (Element Never) )
type alias PageView msg =
{ title : String, body : List (Element msg) }
type Msg
= OnPageChange
{ path : PagePath Pages.PathKey
, query : Maybe String
, fragment : Maybe String
}
| ToggleMobileMenu
| Increment
| SharedMsg SharedMsg
type alias StaticData =
Int
type SharedMsg
= IncrementFromChild
type alias Model =
{ showMobileMenu : Bool
, counter : Int
}
map : (msg1 -> msg2) -> PageView msg1 -> PageView msg2
map fn doc =
{ title = doc.title
, body = List.map (Element.map fn) doc.body
}
init :
Maybe
{ path :
{ path : PagePath Pages.PathKey
, query : Maybe String
, fragment : Maybe String
}
, metadata : TemplateType
}
-> ( Model, Cmd Msg )
init maybePagePath =
( { showMobileMenu = False
, counter = 0
}
, Cmd.none
)
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
OnPageChange page ->
( { model | showMobileMenu = False }, Cmd.none )
ToggleMobileMenu ->
( { model | showMobileMenu = not model.showMobileMenu }, Cmd.none )
Increment ->
( { model | counter = model.counter + 1 }, Cmd.none )
SharedMsg globalMsg ->
case globalMsg of
IncrementFromChild ->
( { model | counter = model.counter + 1 }, Cmd.none )
subscriptions : TemplateType -> PagePath Pages.PathKey -> Model -> Sub Msg
subscriptions _ _ _ =
Sub.none
staticData : a -> StaticHttp.Request StaticData
staticData siteMetadata =
StaticHttp.get (Secrets.succeed "https://api.github.com/repos/dillonkearns/elm-pages")
(D.field "stargazers_count" D.int)
view :
StaticData
->
{ path : PagePath Pages.PathKey
, frontmatter : TemplateType
}
-> Model
-> (Msg -> msg)
-> PageView msg
-> { body : Html msg, title : String }
view stars page model toMsg pageView =
{ body =
(if model.showMobileMenu then
Element.column
[ Element.width Element.fill
, Element.padding 20
]
[ Element.row [ Element.width Element.fill, Element.spaceEvenly ]
[ logoLinkMobile |> Element.map toMsg
, FontAwesome.styledIcon "fas fa-bars" [ Element.Events.onClick ToggleMobileMenu ]
|> Element.map toMsg
]
, Element.column [ Element.centerX, Element.spacing 20 ]
(navbarLinks stars page.path)
]
else
Element.column [ Element.width Element.fill ]
(List.concat
[ [ header stars page.path |> Element.map toMsg
--, incrementView model |> Element.map toMsg
]
, pageView.body
]
)
)
|> Element.layout
[ Element.width Element.fill
, Font.size 20
, Font.family [ Font.typeface "Roboto" ]
, Font.color (Element.rgba255 0 0 0 0.8)
]
, title = pageView.title
}
incrementView : Model -> Element Msg
incrementView model =
Element.el [ Element.Events.onClick Increment ] (Element.text <| "Shared count: " ++ String.fromInt model.counter)
logoLinkMobile =
Element.link []
{ url = "/"
, label =
Element.row
[ Font.size 30
, Element.spacing 16
, Element.htmlAttribute (Attr.class "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"
]
header : Int -> PagePath Pages.PathKey -> Element Msg
header stars currentPath =
Element.column [ Element.width Element.fill ]
[ responsiveHeader
, Element.column
[ Element.width Element.fill
, Element.htmlAttribute (Attr.class "responsive-desktop")
]
[ 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
]
}
]
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.class "navbar-title")
]
[ DocumentSvg.view
, Element.text "elm-pages"
]
}
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 ]
]
githubRepoLink : Int -> Element msg
githubRepoLink starCount =
Element.newTabLink []
{ url = "https://github.com/dillonkearns/elm-pages"
, label =
Element.row [ Element.spacing 5 ]
[ Element.image
[ Element.width (Element.px 22)
, Font.color Palette.color.primary
]
{ src = ImagePath.toString Pages.images.github, description = "Github repo" }
, Element.text <| String.fromInt starCount
]
}
elmDocsLink : Element msg
elmDocsLink =
Element.newTabLink []
{ url = "https://package.elm-lang.org/packages/dillonkearns/elm-pages/latest/"
, label =
Element.image
[ Element.width (Element.px 22)
, Font.color Palette.color.primary
]
{ src = ImagePath.toString Pages.images.elmLogo, description = "Elm Package Docs" }
}
highlightableLink :
PagePath Pages.PathKey
-> Directory Pages.PathKey Directory.WithIndex
-> String
-> Element msg
highlightableLink currentPath linkDirectory displayName =
let
isHighlighted =
currentPath |> Directory.includes linkDirectory
in
Element.link
(if isHighlighted then
[ Font.underline
, Font.color Palette.color.primary
]
else
[]
)
{ url = linkDirectory |> Directory.indexPath |> PagePath.toString
, label = Element.text displayName
}

View File

@ -0,0 +1,98 @@
module Site exposing (canonicalUrl, config, tagline)
import Cloudinary
import Color
import MimeType
import Pages exposing (images, pages)
import Pages.ImagePath exposing (ImagePath)
import Pages.Manifest as Manifest
import Pages.Manifest.Category
import Pages.PagePath exposing (PagePath)
import Pages.StaticHttp as StaticHttp
import TemplateType exposing (TemplateType)
type alias SiteConfig =
{ canonicalUrl : String
, manifest : Manifest.Config Pages.PathKey
}
config : SiteConfig
config =
{ canonicalUrl = canonicalUrl
, manifest = manifest
}
type alias StaticData =
()
staticData :
List ( PagePath Pages.PathKey, TemplateType )
-> StaticHttp.Request StaticData
staticData siteMetadata =
StaticHttp.succeed ()
canonicalUrl : String
canonicalUrl =
"https://elm-pages.com"
manifest : Manifest.Config Pages.PathKey
manifest =
{ backgroundColor = Just Color.white
, categories = [ Pages.Manifest.Category.education ]
, displayMode = Manifest.Standalone
, orientation = Manifest.Portrait
, description = "elm-pages - A statically typed site generator."
, iarcRatingId = Nothing
, name = "elm-pages docs"
, themeColor = Just Color.white
, startUrl = pages.index
, shortName = Just "elm-pages"
, sourceIcon = images.iconPng
, icons =
[ icon webp 192
, icon webp 512
, icon MimeType.Png 192
, icon MimeType.Png 512
]
}
tagline : String
tagline =
"A statically typed site generator - elm-pages"
webp : MimeType.MimeImage
webp =
MimeType.OtherImage "webp"
icon :
MimeType.MimeImage
-> Int
-> Manifest.Icon pathKey
icon format width =
{ src = cloudinaryIcon format width
, sizes = [ ( width, width ) ]
, mimeType = format |> Just
, purposes = [ Manifest.IconPurposeAny, Manifest.IconPurposeMaskable ]
}
cloudinaryIcon :
MimeType.MimeImage
-> Int
-> ImagePath pathKey
cloudinaryIcon mimeType width =
Cloudinary.urlSquare "v1603234028/elm-pages/elm-pages-icon" (Just mimeType) width
socialIcon : ImagePath pathKey
socialIcon =
Cloudinary.urlSquare "v1603234028/elm-pages/elm-pages-icon" Nothing 250

View File

@ -0,0 +1,98 @@
module Template.BlogIndex exposing (Model, Msg, template)
import Element exposing (Element)
import Head
import Head.Seo as Seo
import Index
import Pages exposing (images)
import Pages.PagePath as PagePath exposing (PagePath)
import Pages.StaticHttp as StaticHttp
import Shared
import Showcase
import Site
import Template exposing (StaticPayload, TemplateWithState)
import TemplateMetadata exposing (BlogIndex)
import TemplateType exposing (TemplateType)
type Msg
= Msg
template : TemplateWithState BlogIndex StaticData Model Msg
template =
Template.withStaticData
{ head = head
, staticData = staticData
}
|> Template.buildWithLocalState
{ view = view
, init = init
, update = update
, subscriptions = \_ _ _ -> Sub.none
}
staticData :
List ( PagePath Pages.PathKey, TemplateType )
-> StaticHttp.Request StaticData
staticData siteMetadata =
Showcase.staticRequest
type alias StaticData =
List Showcase.Entry
init : BlogIndex -> ( Model, Cmd Msg )
init metadata =
( Model, Cmd.none )
update :
BlogIndex
-> Msg
-> Model
-> ( Model, Cmd Msg )
update metadata msg model =
( model, Cmd.none )
type alias Model =
{}
view :
Model
-> List ( PagePath Pages.PathKey, TemplateType )
-> StaticPayload BlogIndex StaticData
-> Shared.RenderedBody
-> Shared.PageView Msg
view model allMetadata staticPayload rendered =
{ title = "elm-pages blog"
, body =
[ Element.column [ Element.width Element.fill ]
[ Element.column [ Element.padding 20, Element.centerX ]
[ Index.view allMetadata
]
]
]
}
head : StaticPayload BlogIndex StaticData -> List (Head.Tag Pages.PathKey)
head staticPayload =
Seo.summary
{ canonicalUrlOverride = Nothing
, siteName = "elm-pages"
, image =
{ url = images.iconPng
, alt = "elm-pages logo"
, dimensions = Nothing
, mimeType = Nothing
}
, description = Site.tagline
, locale = Nothing
, title = "elm-pages blog"
}
|> Seo.website

View File

@ -0,0 +1,180 @@
module Template.BlogPost exposing (Model, Msg, decoder, template)
import Data.Author as Author exposing (Author)
import Date exposing (Date)
import Element exposing (Element)
import Element.Font as Font
import Element.Region
import Head
import Head.Seo as Seo
import Json.Decode as Decode
import List.Extra
import Pages
import Pages.ImagePath as ImagePath exposing (ImagePath)
import Pages.PagePath as PagePath exposing (PagePath)
import Palette
import Shared
import Site
import StructuredData
import Template exposing (StaticPayload, Template, TemplateWithState)
import TemplateMetadata exposing (BlogPost)
import TemplateType exposing (TemplateType)
type alias Model =
()
type alias Msg =
Never
template : Template BlogPost ()
template =
Template.noStaticData { head = head }
|> Template.buildNoState { view = view }
decoder : Decode.Decoder BlogPost
decoder =
Decode.map6 BlogPost
(Decode.field "title" Decode.string)
(Decode.field "description" Decode.string)
(Decode.field "published"
(Decode.string
|> Decode.andThen
(\isoString ->
case Date.fromIsoString isoString of
Ok date ->
Decode.succeed date
Err error ->
Decode.fail error
)
)
)
(Decode.field "author" Author.decoder)
(Decode.field "image" imageDecoder)
(Decode.field "draft" Decode.bool
|> Decode.maybe
|> Decode.map (Maybe.withDefault False)
)
imageDecoder : Decode.Decoder (ImagePath Pages.PathKey)
imageDecoder =
Decode.string
|> Decode.andThen
(\imageAssetPath ->
case findMatchingImage imageAssetPath of
Nothing ->
Decode.fail "Couldn't find image."
Just imagePath ->
Decode.succeed imagePath
)
findMatchingImage : String -> Maybe (ImagePath Pages.PathKey)
findMatchingImage imageAssetPath =
List.Extra.find (\image -> ImagePath.toString image == imageAssetPath) Pages.allImages
view :
List ( PagePath Pages.PathKey, TemplateType )
-> StaticPayload BlogPost ()
-> Shared.RenderedBody
-> Shared.PageView msg
view allMetadata { metadata } rendered =
{ title = metadata.title
, body =
[ Element.column [ Element.width Element.fill ]
[ Element.column
[ Element.padding 30
, Element.spacing 40
, Element.Region.mainContent
, Element.width (Element.fill |> Element.maximum 800)
, Element.centerX
]
(Element.column [ Element.spacing 10 ]
[ Element.row [ Element.spacing 10 ]
[ Author.view [] metadata.author
, Element.column [ Element.spacing 10, Element.width Element.fill ]
[ Element.paragraph [ Font.bold, Font.size 24 ]
[ Element.text metadata.author.name
]
, Element.paragraph [ Font.size 16 ]
[ Element.text metadata.author.bio ]
]
]
]
:: (publishedDateView metadata |> Element.el [ Font.size 16, Font.color (Element.rgba255 0 0 0 0.6) ])
:: Palette.blogHeading metadata.title
:: articleImageView metadata.image
:: Tuple.second rendered
|> List.map (Element.map never)
)
]
]
}
head :
StaticPayload BlogPost ()
-> List (Head.Tag Pages.PathKey)
head { metadata, path } =
Head.structuredData
(StructuredData.article
{ title = metadata.title
, description = metadata.description
, author = StructuredData.person { name = metadata.author.name }
, publisher = StructuredData.person { name = "Dillon Kearns" }
, url = Site.canonicalUrl ++ "/" ++ PagePath.toString path
, imageUrl = Site.canonicalUrl ++ "/" ++ ImagePath.toString metadata.image
, datePublished = Date.toIsoString metadata.published
, mainEntityOfPage =
StructuredData.softwareSourceCode
{ codeRepositoryUrl = "https://github.com/dillonkearns/elm-pages"
, description = "A statically typed site generator for Elm."
, author = "Dillon Kearns"
, programmingLanguage = StructuredData.elmLang
}
}
)
:: (Seo.summaryLarge
{ canonicalUrlOverride = Nothing
, siteName = "elm-pages"
, image =
{ url = metadata.image
, alt = metadata.description
, dimensions = Nothing
, mimeType = Nothing
}
, description = metadata.description
, locale = Nothing
, title = metadata.title
}
|> Seo.article
{ tags = []
, section = Nothing
, publishedTime = Just (Date.toIsoString metadata.published)
, modifiedTime = Nothing
, expirationTime = Nothing
}
)
publishedDateView : { a | published : Date.Date } -> Element msg
publishedDateView metadata =
Element.text
(metadata.published
|> Date.format "MMMM ddd, yyyy"
)
articleImageView : ImagePath Pages.PathKey -> Element msg
articleImageView articleImage =
Element.image [ Element.width Element.fill ]
{ src = ImagePath.toString articleImage
, description = "Article cover photo"
}

View File

@ -0,0 +1,146 @@
module Template.Documentation exposing (Model, Msg, decoder, template)
import DocSidebar
import Element exposing (Element)
import Element.Events
import Element.Font as Font
import Element.Region
import Head
import Head.Seo as Seo
import Json.Decode as Decode
import MarkdownRenderer
import Pages exposing (images)
import Pages.PagePath as PagePath exposing (PagePath)
import Pages.StaticHttp as StaticHttp
import Palette
import Shared
import Site
import Template exposing (StaticPayload, TemplateWithState)
import TemplateMetadata exposing (Documentation)
import TemplateType exposing (TemplateType)
type alias StaticData =
()
type alias Model =
{}
type Msg
= Increment
template : TemplateWithState Documentation StaticData Model Msg
template =
Template.noStaticData { head = head }
|> Template.buildWithSharedState
{ view = view
, init = init
, update = update
, subscriptions = \_ _ _ _ -> Sub.none
}
init : Documentation -> ( Model, Cmd Msg )
init metadata =
( {}, Cmd.none )
update : Documentation -> Msg -> Model -> Shared.Model -> ( Model, Cmd Msg, Maybe Shared.SharedMsg )
update metadata msg model sharedModel =
case msg of
Increment ->
( model, Cmd.none, Just Shared.IncrementFromChild )
staticData :
List ( PagePath Pages.PathKey, TemplateType )
-> StaticHttp.Request StaticData
staticData siteMetadata =
StaticHttp.succeed ()
decoder : Decode.Decoder Documentation
decoder =
Decode.map Documentation
(Decode.field "title" Decode.string)
head : StaticPayload Documentation StaticData -> List (Head.Tag Pages.PathKey)
head staticPayload =
Seo.summary
{ canonicalUrlOverride = Nothing
, siteName = "elm-pages"
, image =
{ url = images.iconPng
, alt = "elm-pages logo"
, dimensions = Nothing
, mimeType = Nothing
}
, description = Site.tagline
, locale = Nothing
, title = staticPayload.metadata.title
}
|> Seo.website
view :
Model
-> Shared.Model
-> List ( PagePath Pages.PathKey, TemplateType )
-> StaticPayload Documentation StaticData
-> Shared.RenderedBody
-> Shared.PageView Msg
view model sharedModel allMetadata staticPayload rendered =
{ title = staticPayload.metadata.title
, body =
[ [ Element.row []
[ --counterView sharedModel,
DocSidebar.view
staticPayload.path
allMetadata
|> 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 ]
[ Palette.heading 1 [ Element.text staticPayload.metadata.title ]
, Element.column [ Element.spacing 20 ]
[ tocView (Tuple.first rendered)
, Element.column
[ Element.padding 50
, Element.spacing 30
, Element.Region.mainContent
]
(Tuple.second rendered |> List.map (Element.map never))
]
]
]
]
|> Element.textColumn
[ Element.width Element.fill
, Element.height Element.fill
]
]
}
counterView : Shared.Model -> Element Msg
counterView sharedModel =
Element.el [ Element.Events.onClick Increment ] (Element.text <| "Docs count: " ++ String.fromInt sharedModel.counter)
tocView : MarkdownRenderer.TableOfContents -> Element msg
tocView toc =
Element.column [ Element.alignTop, Element.spacing 20 ]
[ Element.el [ Font.bold, Font.size 22 ] (Element.text "Table of Contents")
, Element.column [ Element.spacing 10 ]
(toc
|> List.map
(\heading ->
Element.link [ Font.color (Element.rgb255 100 100 100) ]
{ url = "#" ++ heading.anchorId
, label = Element.text heading.name
}
)
)
]

View File

@ -0,0 +1,76 @@
module Template.Page exposing (Model, Msg, decoder, template)
import Element exposing (Element)
import Element.Region
import Head
import Head.Seo as Seo
import Json.Decode as Decode
import Pages exposing (images)
import Pages.PagePath exposing (PagePath)
import Shared
import Site
import Template exposing (StaticPayload, Template, TemplateWithState)
import TemplateMetadata exposing (Page)
import TemplateType exposing (TemplateType)
type alias Model =
()
type alias Msg =
Never
template : Template Page ()
template =
Template.noStaticData { head = head }
|> Template.buildNoState { view = view }
decoder : Decode.Decoder Page
decoder =
Decode.map Page
(Decode.field "title" Decode.string)
head :
StaticPayload Page ()
-> List (Head.Tag Pages.PathKey)
head { metadata } =
Seo.summary
{ canonicalUrlOverride = Nothing
, siteName = "elm-pages"
, image =
{ url = images.iconPng
, alt = "elm-pages logo"
, dimensions = Nothing
, mimeType = Nothing
}
, description = Site.tagline
, locale = Nothing
, title = metadata.title
}
|> Seo.website
view :
List ( PagePath Pages.PathKey, TemplateType )
-> StaticPayload Page ()
-> Shared.RenderedBody
-> Shared.PageView msg
view allMetadata { metadata } rendered =
{ title = metadata.title
, body =
[ [ Element.column
[ Element.padding 50
, Element.spacing 60
, Element.Region.mainContent
]
(Tuple.second rendered |> List.map (Element.map never))
]
|> Element.textColumn
[ Element.width Element.fill
]
]
}

View File

@ -0,0 +1,80 @@
module Template.Showcase exposing (Model, Msg, decoder, template)
import Element exposing (Element)
import Head
import Head.Seo as Seo
import Json.Decode as Decode exposing (Decoder)
import Pages exposing (images)
import Pages.PagePath as PagePath exposing (PagePath)
import Pages.StaticHttp as StaticHttp
import Shared
import Showcase
import Template exposing (StaticPayload, TemplateWithState)
import TemplateMetadata exposing (Showcase)
import TemplateType exposing (TemplateType)
type alias Model =
()
type alias Msg =
Never
template : TemplateWithState Showcase StaticData () Msg
template =
Template.withStaticData
{ head = head
, staticData = staticData
}
|> Template.buildNoState { view = view }
decoder : Decoder Showcase
decoder =
Decode.succeed Showcase
staticData :
List ( PagePath Pages.PathKey, TemplateType )
-> StaticHttp.Request StaticData
staticData siteMetadata =
Showcase.staticRequest
type alias StaticData =
List Showcase.Entry
view :
List ( PagePath Pages.PathKey, TemplateType )
-> StaticPayload Showcase StaticData
-> Shared.RenderedBody
-> Shared.PageView msg
view allMetadata static rendered =
{ title = "elm-pages blog"
, body =
[ Element.column [ Element.width Element.fill ]
[ Element.column [ Element.padding 20, Element.centerX ] [ Showcase.view static.static ]
]
]
}
head : StaticPayload Showcase StaticData -> List (Head.Tag Pages.PathKey)
head staticPayload =
Seo.summary
{ canonicalUrlOverride = Nothing
, siteName = "elm-pages"
, image =
{ url = images.iconPng
, alt = "elm-pages logo"
, dimensions = Nothing
, mimeType = Nothing
}
, description = "See some neat sites built using elm-pages! (Or submit yours!)"
, locale = Nothing
, title = "elm-pages sites showcase"
}
|> Seo.website

View File

@ -0,0 +1,52 @@
module TemplateHardcoded exposing (..)
import Head
import Pages
import Pages.PagePath exposing (PagePath)
import Pages.StaticHttp as StaticHttp
import TemplateType
template :
{ staticData :
List ( PagePath Pages.PathKey, TemplateType.Metadata )
-> StaticHttp.Request staticData
, view :
List ( PagePath Pages.PathKey, TemplateType.Metadata )
-> staticData
-> model
-> metadata
-> renderedTemplate
-> view
, head :
staticData
-> PagePath Pages.PathKey
-> metadata
-> List (Head.Tag Pages.PathKey)
, init : metadata -> ( model, Cmd templateMsg )
, update : metadata -> templateMsg -> model -> ( model, Cmd templateMsg )
}
-> Template metadata renderedTemplate staticData model view templateMsg
template config =
config
type alias Template metadata renderedTemplate staticData model view templateMsg =
{ staticData :
List ( PagePath Pages.PathKey, TemplateType.Metadata )
-> StaticHttp.Request staticData
, view :
List ( PagePath Pages.PathKey, TemplateType.Metadata )
-> staticData
-> model
-> metadata
-> renderedTemplate
-> view
, head :
staticData
-> PagePath Pages.PathKey
-> metadata
-> List (Head.Tag Pages.PathKey)
, init : metadata -> ( model, Cmd templateMsg )
, update : metadata -> templateMsg -> model -> ( model, Cmd templateMsg )
}

View File

@ -0,0 +1,32 @@
module TemplateMetadata exposing (..)
import Data.Author exposing (Author)
import Date exposing (Date)
import Pages
import Pages.ImagePath exposing (ImagePath)
type alias BlogIndex =
{}
type alias BlogPost =
{ title : String
, description : String
, published : Date
, author : Author
, image : ImagePath Pages.PathKey
, draft : Bool
}
type alias Page =
{ title : String }
type alias Showcase =
{}
type alias Documentation =
{ title : String }

View File

@ -0,0 +1,11 @@
module TemplateType exposing (TemplateType(..))
import TemplateMetadata
type TemplateType
= BlogPost TemplateMetadata.BlogPost
| Showcase TemplateMetadata.Showcase
| Page TemplateMetadata.Page
| BlogIndex TemplateMetadata.BlogIndex
| Documentation TemplateMetadata.Documentation

View File

@ -157,9 +157,12 @@ type alias Model =
init :
Maybe
{ path : PagePath Pages.PathKey
, query : Maybe String
, fragment : Maybe String
{ path :
{ path : PagePath Pages.PathKey
, query : Maybe String
, fragment : Maybe String
}
, metadata : Metadata
}
-> ( Model, Cmd Msg )
init maybePagePath =
@ -189,8 +192,8 @@ update msg model =
( { model | counter = model.counter + 1 }, Cmd.none )
subscriptions : Model -> Sub Msg
subscriptions _ =
subscriptions : Metadata -> PagePath Pages.PathKey -> Model -> Sub Msg
subscriptions _ _ _ =
Time.every 1000 (\_ -> Tick)

208
generator/src/Template.elm Normal file
View File

@ -0,0 +1,208 @@
module Template exposing
( Builder(..)
, StaticPayload
, withStaticData, noStaticData
, Template, buildNoState
, TemplateWithState, buildWithLocalState, buildWithSharedState
)
{-|
## Building a Template
@docs Builder
## Static Data
Every template will have access to a `StaticPayload`.
@docs StaticPayload
Since this data is _static_, you have access to it before the user has loaded the page, including at build time.
An example of dynamic data would be keyboard input from the user, query params, or any other data that comes from the app running in the browser.
But before the user even requests the page, we have the following data:
- `path` - these paths are static. In other words, we know every single path when we build an elm-pages site.
- `metadata` - we have a decoded Elm value for the page's metadata.
- `sharedStatic` - we can access any shared data between pages. For example, you may have fetched the name of a blog ("Jane's Blog") from the API for a Content Management System (CMS).
- `static` - this is the static data for this specific page. If you use `noStaticData`, then this will be `()`, meaning there is no page-specific static data.
@docs withStaticData, noStaticData
## Stateless Templates
@docs Template, buildNoState
## Stateful Templates
@docs TemplateWithState, buildWithLocalState, buildWithSharedState
-}
import Head
import Pages
import Pages.PagePath exposing (PagePath)
import Pages.StaticHttp as StaticHttp
import Shared
import TemplateType exposing (TemplateType)
{-| -}
type alias TemplateWithState templateMetadata templateStaticData templateModel templateMsg =
{ staticData :
List ( PagePath Pages.PathKey, TemplateType )
-> StaticHttp.Request templateStaticData
, view :
templateModel
-> Shared.Model
-> List ( PagePath Pages.PathKey, TemplateType )
-> StaticPayload templateMetadata templateStaticData
-> Shared.RenderedBody
-> Shared.PageView templateMsg
, head :
StaticPayload templateMetadata templateStaticData
-> List (Head.Tag Pages.PathKey)
, init : templateMetadata -> ( templateModel, Cmd templateMsg )
, update : templateMetadata -> templateMsg -> templateModel -> Shared.Model -> ( templateModel, Cmd templateMsg, Maybe Shared.SharedMsg )
, subscriptions : templateMetadata -> PagePath Pages.PathKey -> templateModel -> Shared.Model -> Sub templateMsg
}
{-| -}
type alias Template templateMetadata staticData =
TemplateWithState templateMetadata staticData () Never
{-| -}
type alias StaticPayload metadata staticData =
{ static : staticData -- local
, sharedStatic : Shared.StaticData -- share
, metadata : metadata
, path : PagePath Pages.PathKey
}
{-| -}
type Builder templateMetadata templateStaticData
= WithStaticData
{ staticData :
List ( PagePath Pages.PathKey, TemplateType )
-> StaticHttp.Request templateStaticData
, head :
StaticPayload templateMetadata templateStaticData
-> List (Head.Tag Pages.PathKey)
}
{-| -}
buildNoState :
{ view :
List ( PagePath Pages.PathKey, TemplateType )
-> StaticPayload templateMetadata templateStaticData
-> Shared.RenderedBody
-> Shared.PageView Never
}
-> Builder templateMetadata templateStaticData
-> TemplateWithState templateMetadata templateStaticData () Never
buildNoState { view } builderState =
case builderState of
WithStaticData record ->
{ view = \() _ -> view
, head = record.head
, staticData = record.staticData
, init = \_ -> ( (), Cmd.none )
, update = \_ _ _ _ -> ( (), Cmd.none, Nothing )
, subscriptions = \_ _ _ _ -> Sub.none
}
{-| -}
buildWithLocalState :
{ view :
templateModel
-> List ( PagePath Pages.PathKey, TemplateType )
-> StaticPayload templateMetadata templateStaticData
-> Shared.RenderedBody
-> Shared.PageView templateMsg
, init : templateMetadata -> ( templateModel, Cmd templateMsg )
, update : templateMetadata -> templateMsg -> templateModel -> ( templateModel, Cmd templateMsg )
, subscriptions : templateMetadata -> PagePath Pages.PathKey -> templateModel -> Sub templateMsg
}
-> Builder templateMetadata templateStaticData
-> TemplateWithState templateMetadata templateStaticData templateModel templateMsg
buildWithLocalState config builderState =
case builderState of
WithStaticData record ->
{ view =
\model sharedModel allMetadata staticPayload rendered ->
config.view model allMetadata staticPayload rendered
, head = record.head
, staticData = record.staticData
, init = config.init
, update =
\metadata msg templateModel sharedModel_ ->
let
( updatedModel, cmd ) =
config.update metadata msg templateModel
in
( updatedModel, cmd, Nothing )
, subscriptions =
\templateMetadata path templateModel sharedModel ->
config.subscriptions templateMetadata path templateModel
}
{-| -}
buildWithSharedState :
{ view :
templateModel
-> Shared.Model
-> List ( PagePath Pages.PathKey, TemplateType )
-> StaticPayload templateMetadata templateStaticData
-> Shared.RenderedBody
-> Shared.PageView templateMsg
, init : templateMetadata -> ( templateModel, Cmd templateMsg )
, update : templateMetadata -> templateMsg -> templateModel -> Shared.Model -> ( templateModel, Cmd templateMsg, Maybe Shared.SharedMsg )
, subscriptions : templateMetadata -> PagePath Pages.PathKey -> templateModel -> Shared.Model -> Sub templateMsg
}
-> Builder templateMetadata templateStaticData
-> TemplateWithState templateMetadata templateStaticData templateModel templateMsg
buildWithSharedState config builderState =
case builderState of
WithStaticData record ->
{ view = config.view
, head = record.head
, staticData = record.staticData
, init = config.init
, update = config.update
, subscriptions = config.subscriptions
}
{-| -}
withStaticData :
{ staticData : List ( PagePath Pages.PathKey, TemplateType ) -> StaticHttp.Request templateStaticData
, head : StaticPayload templateMetadata templateStaticData -> List (Head.Tag Pages.PathKey)
}
-> Builder templateMetadata templateStaticData
withStaticData { staticData, head } =
WithStaticData
{ staticData = staticData
, head = head
}
{-| -}
noStaticData :
{ head : StaticPayload templateMetadata () -> List (Head.Tag Pages.PathKey) }
-> Builder templateMetadata ()
noStaticData { head } =
WithStaticData
{ staticData = \_ -> StaticHttp.succeed ()
, head = head
}

View File

@ -0,0 +1,69 @@
#!/usr/bin/env node
const fs = require("./dir-helpers.js");
if (process.argv.length === 3) {
const moduleName = process.argv[2];
if (!moduleName.match(/[A-Z][A-Za-z0-9]*/)) {
console.error("Invalid module name.");
process.exit(1);
}
const content = fileContent(moduleName);
fs.tryMkdir("src/Template");
fs.writeFile(`src/Template/${moduleName}.elm`, content);
} else {
console.error(`Unexpected CLI options: ${process.argv}`);
process.exit(1);
}
function fileContent(templateName) {
return `
module Template.${templateName} exposing (Model, Msg, template)
import Head
import Pages
import Pages.PagePath exposing (PagePath)
import Shared
import Template exposing (StaticPayload, Template)
import TemplateMetadata exposing (${templateName})
import TemplateType exposing (TemplateType)
type alias Model =
()
type alias Msg =
Never
type alias StaticData =
()
template : Template ${templateName} StaticData
template =
Template.noStaticData { head = head }
|> Template.buildNoState { view = view }
head :
StaticPayload ${templateName} StaticData
-> List (Head.Tag Pages.PathKey)
head { metadata } =
[]
view :
List ( PagePath Pages.PathKey, TemplateType )
-> StaticPayload ${templateName} StaticData
-> Shared.RenderedBody
-> Shared.PageView msg
view allMetadata static rendered =
{ title = Debug.todo "Add title."
, body =
[]
}
`;
}

View File

@ -1,6 +1,9 @@
const fs = require("fs");
const copyModifiedElmJson = require("./rewrite-elm-json.js");
const { elmPagesCliFile, elmPagesUiFile } = require("./elm-file-constants.js");
const {
generateTemplateModuleConnector,
} = require("./generate-template-module-connector.js");
const path = require("path");
const { ensureDirSync, deleteIfExists } = require("./file-helpers.js");
const globby = require("globby");
@ -44,12 +47,18 @@ async function writeFiles(markdownContent) {
ensureDirSync("./elm-stuff");
ensureDirSync("./gen");
ensureDirSync("./elm-stuff/elm-pages");
fs.copyFileSync(path.join(__dirname, `./Template.elm`), `./gen/Template.elm`);
fs.copyFileSync(
path.join(__dirname, `./Template.elm`),
`./elm-stuff/elm-pages/Template.elm`
);
// prevent compilation errors if migrating from previous elm-pages version
deleteIfExists("./elm-stuff/elm-pages/Pages/ContentCache.elm");
deleteIfExists("./elm-stuff/elm-pages/Pages/Platform.elm");
const uiFileContent = elmPagesUiFile(staticRoutes, markdownContent);
const templateConnectorFile = generateTemplateModuleConnector();
fs.writeFileSync("./gen/Pages.elm", uiFileContent);
@ -58,6 +67,11 @@ async function writeFiles(markdownContent) {
"./elm-stuff/elm-pages/Pages.elm",
elmPagesCliFile(staticRoutes, markdownContent)
);
fs.writeFileSync(
"./elm-stuff/elm-pages/TemplateModulesBeta.elm",
templateConnectorFile
);
fs.writeFileSync("./gen/TemplateModulesBeta.elm", templateConnectorFile);
// write modified elm.json to elm-stuff/elm-pages/
copyModifiedElmJson();

View File

@ -2,16 +2,14 @@ const fs = require("fs");
const runElm = require("./compile-elm.js");
const copyModifiedElmJson = require("./rewrite-elm-json.js");
const { elmPagesCliFile, elmPagesUiFile } = require("./elm-file-constants.js");
const {
generateTemplateModuleConnector,
} = require("./generate-template-module-connector.js");
const path = require("path");
const { ensureDirSync, deleteIfExists } = require('./file-helpers.js')
let wasEqualBefore = false
const { ensureDirSync, deleteIfExists } = require("./file-helpers.js");
let wasEqualBefore = false;
module.exports = function run(
mode,
staticRoutes,
markdownContent
) {
module.exports = function run(mode, staticRoutes, markdownContent) {
ensureDirSync("./elm-stuff");
ensureDirSync("./gen");
ensureDirSync("./elm-stuff/elm-pages");
@ -20,35 +18,36 @@ module.exports = function run(
deleteIfExists("./elm-stuff/elm-pages/Pages/ContentCache.elm");
deleteIfExists("./elm-stuff/elm-pages/Pages/Platform.elm");
const uiFileContent = elmPagesUiFile(staticRoutes, markdownContent)
const uiFileContent = elmPagesUiFile(staticRoutes, markdownContent);
const templateConnectorFile = generateTemplateModuleConnector();
// TODO should just write it once, but webpack doesn't seem to pick up the changes
// so this wasEqualBefore code causes it to get written twice to make sure the changes come through for HMR
if (wasEqualBefore) {
fs.writeFileSync(
"./gen/Pages.elm",
uiFileContent
);
fs.writeFileSync("./gen/Pages.elm", uiFileContent);
}
if (global.previousUiFileContent === uiFileContent) {
wasEqualBefore = false
wasEqualBefore = false;
} else {
wasEqualBefore = true
fs.writeFileSync(
"./gen/Pages.elm",
uiFileContent
);
wasEqualBefore = true;
fs.writeFileSync("./gen/Pages.elm", uiFileContent);
fs.writeFileSync("./gen/TemplateModulesBeta.elm", templateConnectorFile);
}
global.previousUiFileContent = uiFileContent
global.previousUiFileContent = uiFileContent;
fs.copyFileSync(path.join(__dirname, `./Template.elm`), `./gen/Template.elm`);
// write `Pages.elm` with cli interface
fs.writeFileSync(
"./elm-stuff/elm-pages/Pages.elm",
elmPagesCliFile(staticRoutes, markdownContent)
);
fs.writeFileSync(
"./elm-stuff/elm-pages/TemplateModulesBeta.elm",
templateConnectorFile
);
// write modified elm.json to elm-stuff/elm-pages/
copyModifiedElmJson();

View File

@ -0,0 +1,313 @@
const globby = require("globby");
const path = require("path");
function generateTemplateModuleConnector() {
const templates = globby
.sync(["src/Template/*.elm"], {})
.map((file) => path.basename(file, ".elm"));
return `module TemplateModulesBeta exposing (..)
import Browser
import Pages.Manifest as Manifest
import Shared
import TemplateType as M exposing (TemplateType)
import Head
import Html exposing (Html)
import Pages
import Pages.PagePath exposing (PagePath)
import Pages.Platform
import Pages.StaticHttp as StaticHttp
${templates.map((name) => `import Template.${name}`).join("\n")}
type alias Model =
{ global : Shared.Model
, page : TemplateModel
, current :
Maybe
{ path :
{ path : PagePath Pages.PathKey
, query : Maybe String
, fragment : Maybe String
}
, metadata : TemplateType
}
}
type TemplateModel
= ${templates
.map((name) => `Model${name} Template.${name}.Model\n`)
.join(" | ")}
| NotFound
type Msg
= MsgGlobal Shared.Msg
| OnPageChange
{ path : PagePath Pages.PathKey
, query : Maybe String
, fragment : Maybe String
, metadata : TemplateType
}
| ${templates
.map((name) => `Msg${name} Template.${name}.Msg\n`)
.join(" | ")}
view :
List ( PagePath Pages.PathKey, TemplateType )
->
{ path : PagePath Pages.PathKey
, frontmatter : TemplateType
}
->
StaticHttp.Request
{ view : Model -> Shared.RenderedBody -> { title : String, body : Html Msg }
, head : List (Head.Tag Pages.PathKey)
}
view siteMetadata page =
case page.frontmatter of
${templates
.map(
(name) =>
`M.${name} metadata ->
StaticHttp.map2
(\\data globalData ->
{ view =
\\model rendered ->
case model.page of
Model${name} subModel ->
Template.${name}.template.view
subModel
model.global
siteMetadata
{ static = data
, sharedStatic = globalData
, metadata = metadata
, path = page.path
}
rendered
|> (\\{ title, body } ->
Shared.template.view
globalData
page
model.global
MsgGlobal
({ title = title, body = body }
|> Shared.template.map Msg${name}
)
)
_ ->
{ title = "", body = Html.text "" }
, head = Template.${name}.template.head
{ static = data
, sharedStatic = globalData
, metadata = metadata
, path = page.path
}
}
)
(Template.${name}.template.staticData siteMetadata)
(Shared.template.staticData siteMetadata)
`
)
.join("\n\n ")}
init :
Maybe Shared.Model
->
Maybe
{ path :
{ path : PagePath Pages.PathKey
, query : Maybe String
, fragment : Maybe String
}
, metadata : TemplateType
}
-> ( Model, Cmd Msg )
init currentGlobalModel maybePagePath =
let
( sharedModel, globalCmd ) =
currentGlobalModel |> Maybe.map (\\m -> ( m, Cmd.none )) |> Maybe.withDefault (Shared.template.init maybePagePath)
( templateModel, templateCmd ) =
case maybePagePath |> Maybe.map .metadata of
Nothing ->
( NotFound, Cmd.none )
Just meta ->
case meta of
${templates
.map(
(name) => `M.${name} metadata ->
Template.${name}.template.init metadata
|> Tuple.mapBoth Model${name} (Cmd.map Msg${name})
`
)
.join("\n ")}
in
( { global = sharedModel
, page = templateModel
, current = maybePagePath
}
, Cmd.batch
[ templateCmd
, globalCmd |> Cmd.map MsgGlobal
]
)
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
MsgGlobal msg_ ->
let
( sharedModel, globalCmd ) =
Shared.template.update msg_ model.global
in
( { model | global = sharedModel }
, globalCmd |> Cmd.map MsgGlobal
)
OnPageChange record ->
(init (Just model.global) <|
Just
{ path =
{ path = record.path
, query = record.query
, fragment = record.fragment
}
, metadata = record.metadata
}
)
|> (\\( updatedModel, cmd ) ->
case Shared.template.onPageChange of
Nothing ->
( updatedModel, cmd )
Just thingy ->
let
( updatedGlobalModel, globalCmd ) =
Shared.template.update
(thingy
{ path = record.path
, query = record.query
, fragment = record.fragment
}
)
model.global
in
( { updatedModel
| global = updatedGlobalModel
}
, Cmd.batch [ cmd, Cmd.map MsgGlobal globalCmd ]
)
)
${templates
.map(
(name) => `
Msg${name} msg_ ->
let
( updatedPageModel, pageCmd, ( newGlobalModel, newGlobalCmd ) ) =
case ( model.page, model.current |> Maybe.map .metadata ) of
( Model${name} pageModel, Just (M.${name} metadata) ) ->
Template.${name}.template.update
metadata
msg_
pageModel
model.global
|> mapBoth Model${name} (Cmd.map Msg${name})
|> (\\( a, b, c ) ->
case c of
Just sharedMsg ->
( a, b, Shared.template.update (Shared.SharedMsg sharedMsg) model.global )
Nothing ->
( a, b, ( model.global, Cmd.none ) )
)
_ ->
( model.page, Cmd.none, ( model.global, Cmd.none ) )
in
( { model | page = updatedPageModel, global = newGlobalModel }
, Cmd.batch [ pageCmd, newGlobalCmd |> Cmd.map MsgGlobal ]
)
`
)
.join("\n ")}
type alias SiteConfig =
{ canonicalUrl : String
, manifest : Manifest.Config Pages.PathKey
}
templateSubscriptions : TemplateType -> PagePath Pages.PathKey -> Model -> Sub Msg
templateSubscriptions metadata path model =
case model.page of
${templates
.map(
(name) => `
Model${name} templateModel ->
case metadata of
M.${name} templateMetadata ->
Template.${name}.template.subscriptions
templateMetadata
path
templateModel
model.global
|> Sub.map Msg${name}
_ ->
Sub.none
`
)
.join("\n ")}
NotFound ->
Sub.none
mainTemplate { documents, site } =
Pages.Platform.init
{ init = init Nothing
, view = view
, update = update
, subscriptions =
\\metadata path model ->
Sub.batch
[ Shared.template.subscriptions metadata path model.global |> Sub.map MsgGlobal
, templateSubscriptions metadata path model
]
, documents = documents
, onPageChange = Just OnPageChange
, manifest = site.manifest
, canonicalSiteUrl = site.canonicalUrl
, internals = Pages.internals
}
mapDocument : Browser.Document Never -> Browser.Document mapped
mapDocument document =
{ title = document.title
, body = document.body |> List.map (Html.map never)
}
mapBoth fnA fnB ( a, b, c ) =
( fnA a, fnB b, c )
`;
}
module.exports = { generateTemplateModuleConnector };

View File

@ -0,0 +1,769 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`generate UI file 1`] = `
"port module Pages exposing (PathKey, allPages, allImages, internals, images, isValidRoute, pages, builtAt)
import Color exposing (Color)
import Pages.Internal
import Head
import Html exposing (Html)
import Json.Decode
import Json.Encode
import Pages.Platform
import Pages.Manifest exposing (DisplayMode, Orientation)
import Pages.Manifest.Category as Category exposing (Category)
import Url.Parser as Url exposing ((</>), s)
import Pages.ImagePath as ImagePath exposing (ImagePath)
import Pages.PagePath as PagePath exposing (PagePath)
import Pages.Directory as Directory exposing (Directory)
import Time
builtAt : Time.Posix
builtAt =
Time.millisToPosix 1589734402000
type PathKey
= PathKey
buildImage : List String -> ImagePath.Dimensions -> ImagePath PathKey
buildImage path dimensions =
ImagePath.build PathKey (\\"images\\" :: path) dimensions
buildPage : List String -> PagePath PathKey
buildPage path =
PagePath.build PathKey path
directoryWithIndex : List String -> Directory PathKey Directory.WithIndex
directoryWithIndex path =
Directory.withIndex PathKey allPages path
directoryWithoutIndex : List String -> Directory PathKey Directory.WithoutIndex
directoryWithoutIndex path =
Directory.withoutIndex PathKey allPages path
port toJsPort : Json.Encode.Value -> Cmd msg
port fromJsPort : (Json.Decode.Value -> msg) -> Sub msg
internals : Pages.Internal.Internal PathKey
internals =
{ applicationType = Pages.Internal.Browser
, toJsPort = toJsPort
, fromJsPort = fromJsPort identity
, content = content
, pathKey = PathKey
}
allPages : List (PagePath PathKey)
allPages =
[
]
pages =
{ directory = directoryWithoutIndex []
}
images =
{ staticHttpError = (buildImage [ \\"static-http-error.png\\" ] { width = 919, height = 105 })
, directory = directoryWithoutIndex []
}
allImages : List (ImagePath PathKey)
allImages =
[(buildImage [ \\"static-http-error.png\\" ] { width = 919, height = 105 })
]
isValidRoute : String -> Result String ()
isValidRoute route =
let
validRoutes =
List.map PagePath.toString allPages
in
if
(route |> String.startsWith \\"http://\\")
|| (route |> String.startsWith \\"https://\\")
|| (route |> String.startsWith \\"#\\")
|| (validRoutes |> List.member route)
then
Ok ()
else
(\\"Valid routes:\\\\n\\"
++ String.join \\"\\\\n\\\\n\\" validRoutes
)
|> Err
content : List ( List String, { extension: String, frontMatter : String, body : Maybe String } )
content =
[
]
"
`;
exports[`generate template module connector 1`] = `
"module TemplateModulesBeta exposing (..)
import Browser
import Pages.Manifest as Manifest
import Shared
import TemplateType as M exposing (TemplateType)
import Head
import Html exposing (Html)
import Pages
import Pages.PagePath exposing (PagePath)
import Pages.Platform
import Pages.StaticHttp as StaticHttp
import Template.BlogIndex
import Template.BlogPost
import Template.Documentation
import Template.Page
import Template.Showcase
type alias Model =
{ global : Shared.Model
, page : TemplateModel
, current :
Maybe
{ path :
{ path : PagePath Pages.PathKey
, query : Maybe String
, fragment : Maybe String
}
, metadata : TemplateType
}
}
type TemplateModel
= ModelBlogIndex Template.BlogIndex.Model
| ModelBlogPost Template.BlogPost.Model
| ModelDocumentation Template.Documentation.Model
| ModelPage Template.Page.Model
| ModelShowcase Template.Showcase.Model
| NotFound
type Msg
= MsgGlobal Shared.Msg
| OnPageChange
{ path : PagePath Pages.PathKey
, query : Maybe String
, fragment : Maybe String
, metadata : TemplateType
}
| MsgBlogIndex Template.BlogIndex.Msg
| MsgBlogPost Template.BlogPost.Msg
| MsgDocumentation Template.Documentation.Msg
| MsgPage Template.Page.Msg
| MsgShowcase Template.Showcase.Msg
view :
List ( PagePath Pages.PathKey, TemplateType )
->
{ path : PagePath Pages.PathKey
, frontmatter : TemplateType
}
->
StaticHttp.Request
{ view : Model -> Shared.RenderedBody -> { title : String, body : Html Msg }
, head : List (Head.Tag Pages.PathKey)
}
view siteMetadata page =
case page.frontmatter of
M.BlogIndex metadata ->
StaticHttp.map2
(\\\\data globalData ->
{ view =
\\\\model rendered ->
case model.page of
ModelBlogIndex subModel ->
Template.BlogIndex.template.view
subModel
model.global
siteMetadata
{ static = data
, sharedStatic = globalData
, metadata = metadata
, path = page.path
}
rendered
|> (\\\\{ title, body } ->
Shared.template.view
globalData
page
model.global
MsgGlobal
({ title = title, body = body }
|> Shared.template.map MsgBlogIndex
)
)
_ ->
{ title = \\"\\", body = Html.text \\"\\" }
, head = Template.BlogIndex.template.head
{ static = data
, sharedStatic = globalData
, metadata = metadata
, path = page.path
}
}
)
(Template.BlogIndex.template.staticData siteMetadata)
(Shared.template.staticData siteMetadata)
M.BlogPost metadata ->
StaticHttp.map2
(\\\\data globalData ->
{ view =
\\\\model rendered ->
case model.page of
ModelBlogPost subModel ->
Template.BlogPost.template.view
subModel
model.global
siteMetadata
{ static = data
, sharedStatic = globalData
, metadata = metadata
, path = page.path
}
rendered
|> (\\\\{ title, body } ->
Shared.template.view
globalData
page
model.global
MsgGlobal
({ title = title, body = body }
|> Shared.template.map MsgBlogPost
)
)
_ ->
{ title = \\"\\", body = Html.text \\"\\" }
, head = Template.BlogPost.template.head
{ static = data
, sharedStatic = globalData
, metadata = metadata
, path = page.path
}
}
)
(Template.BlogPost.template.staticData siteMetadata)
(Shared.template.staticData siteMetadata)
M.Documentation metadata ->
StaticHttp.map2
(\\\\data globalData ->
{ view =
\\\\model rendered ->
case model.page of
ModelDocumentation subModel ->
Template.Documentation.template.view
subModel
model.global
siteMetadata
{ static = data
, sharedStatic = globalData
, metadata = metadata
, path = page.path
}
rendered
|> (\\\\{ title, body } ->
Shared.template.view
globalData
page
model.global
MsgGlobal
({ title = title, body = body }
|> Shared.template.map MsgDocumentation
)
)
_ ->
{ title = \\"\\", body = Html.text \\"\\" }
, head = Template.Documentation.template.head
{ static = data
, sharedStatic = globalData
, metadata = metadata
, path = page.path
}
}
)
(Template.Documentation.template.staticData siteMetadata)
(Shared.template.staticData siteMetadata)
M.Page metadata ->
StaticHttp.map2
(\\\\data globalData ->
{ view =
\\\\model rendered ->
case model.page of
ModelPage subModel ->
Template.Page.template.view
subModel
model.global
siteMetadata
{ static = data
, sharedStatic = globalData
, metadata = metadata
, path = page.path
}
rendered
|> (\\\\{ title, body } ->
Shared.template.view
globalData
page
model.global
MsgGlobal
({ title = title, body = body }
|> Shared.template.map MsgPage
)
)
_ ->
{ title = \\"\\", body = Html.text \\"\\" }
, head = Template.Page.template.head
{ static = data
, sharedStatic = globalData
, metadata = metadata
, path = page.path
}
}
)
(Template.Page.template.staticData siteMetadata)
(Shared.template.staticData siteMetadata)
M.Showcase metadata ->
StaticHttp.map2
(\\\\data globalData ->
{ view =
\\\\model rendered ->
case model.page of
ModelShowcase subModel ->
Template.Showcase.template.view
subModel
model.global
siteMetadata
{ static = data
, sharedStatic = globalData
, metadata = metadata
, path = page.path
}
rendered
|> (\\\\{ title, body } ->
Shared.template.view
globalData
page
model.global
MsgGlobal
({ title = title, body = body }
|> Shared.template.map MsgShowcase
)
)
_ ->
{ title = \\"\\", body = Html.text \\"\\" }
, head = Template.Showcase.template.head
{ static = data
, sharedStatic = globalData
, metadata = metadata
, path = page.path
}
}
)
(Template.Showcase.template.staticData siteMetadata)
(Shared.template.staticData siteMetadata)
init :
Maybe Shared.Model
->
Maybe
{ path :
{ path : PagePath Pages.PathKey
, query : Maybe String
, fragment : Maybe String
}
, metadata : TemplateType
}
-> ( Model, Cmd Msg )
init currentGlobalModel maybePagePath =
let
( sharedModel, globalCmd ) =
currentGlobalModel |> Maybe.map (\\\\m -> ( m, Cmd.none )) |> Maybe.withDefault (Shared.template.init maybePagePath)
( templateModel, templateCmd ) =
case maybePagePath |> Maybe.map .metadata of
Nothing ->
( NotFound, Cmd.none )
Just meta ->
case meta of
M.BlogIndex metadata ->
Template.BlogIndex.template.init metadata
|> Tuple.mapBoth ModelBlogIndex (Cmd.map MsgBlogIndex)
M.BlogPost metadata ->
Template.BlogPost.template.init metadata
|> Tuple.mapBoth ModelBlogPost (Cmd.map MsgBlogPost)
M.Documentation metadata ->
Template.Documentation.template.init metadata
|> Tuple.mapBoth ModelDocumentation (Cmd.map MsgDocumentation)
M.Page metadata ->
Template.Page.template.init metadata
|> Tuple.mapBoth ModelPage (Cmd.map MsgPage)
M.Showcase metadata ->
Template.Showcase.template.init metadata
|> Tuple.mapBoth ModelShowcase (Cmd.map MsgShowcase)
in
( { global = sharedModel
, page = templateModel
, current = maybePagePath
}
, Cmd.batch
[ templateCmd
, globalCmd |> Cmd.map MsgGlobal
]
)
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
case msg of
MsgGlobal msg_ ->
let
( sharedModel, globalCmd ) =
Shared.template.update msg_ model.global
in
( { model | global = sharedModel }
, globalCmd |> Cmd.map MsgGlobal
)
OnPageChange record ->
(init (Just model.global) <|
Just
{ path =
{ path = record.path
, query = record.query
, fragment = record.fragment
}
, metadata = record.metadata
}
)
|> (\\\\( updatedModel, cmd ) ->
case Shared.template.onPageChange of
Nothing ->
( updatedModel, cmd )
Just thingy ->
let
( updatedGlobalModel, globalCmd ) =
Shared.template.update
(thingy
{ path = record.path
, query = record.query
, fragment = record.fragment
}
)
model.global
in
( { updatedModel
| global = updatedGlobalModel
}
, Cmd.batch [ cmd, Cmd.map MsgGlobal globalCmd ]
)
)
MsgBlogIndex msg_ ->
let
( updatedPageModel, pageCmd, ( newGlobalModel, newGlobalCmd ) ) =
case ( model.page, model.current |> Maybe.map .metadata ) of
( ModelBlogIndex pageModel, Just (M.BlogIndex metadata) ) ->
Template.BlogIndex.template.update
metadata
msg_
pageModel
model.global
|> mapBoth ModelBlogIndex (Cmd.map MsgBlogIndex)
|> (\\\\( a, b, c ) ->
case c of
Just sharedMsg ->
( a, b, Shared.template.update (Shared.SharedMsg sharedMsg) model.global )
Nothing ->
( a, b, ( model.global, Cmd.none ) )
)
_ ->
( model.page, Cmd.none, ( model.global, Cmd.none ) )
in
( { model | page = updatedPageModel, global = newGlobalModel }
, Cmd.batch [ pageCmd, newGlobalCmd |> Cmd.map MsgGlobal ]
)
MsgBlogPost msg_ ->
let
( updatedPageModel, pageCmd, ( newGlobalModel, newGlobalCmd ) ) =
case ( model.page, model.current |> Maybe.map .metadata ) of
( ModelBlogPost pageModel, Just (M.BlogPost metadata) ) ->
Template.BlogPost.template.update
metadata
msg_
pageModel
model.global
|> mapBoth ModelBlogPost (Cmd.map MsgBlogPost)
|> (\\\\( a, b, c ) ->
case c of
Just sharedMsg ->
( a, b, Shared.template.update (Shared.SharedMsg sharedMsg) model.global )
Nothing ->
( a, b, ( model.global, Cmd.none ) )
)
_ ->
( model.page, Cmd.none, ( model.global, Cmd.none ) )
in
( { model | page = updatedPageModel, global = newGlobalModel }
, Cmd.batch [ pageCmd, newGlobalCmd |> Cmd.map MsgGlobal ]
)
MsgDocumentation msg_ ->
let
( updatedPageModel, pageCmd, ( newGlobalModel, newGlobalCmd ) ) =
case ( model.page, model.current |> Maybe.map .metadata ) of
( ModelDocumentation pageModel, Just (M.Documentation metadata) ) ->
Template.Documentation.template.update
metadata
msg_
pageModel
model.global
|> mapBoth ModelDocumentation (Cmd.map MsgDocumentation)
|> (\\\\( a, b, c ) ->
case c of
Just sharedMsg ->
( a, b, Shared.template.update (Shared.SharedMsg sharedMsg) model.global )
Nothing ->
( a, b, ( model.global, Cmd.none ) )
)
_ ->
( model.page, Cmd.none, ( model.global, Cmd.none ) )
in
( { model | page = updatedPageModel, global = newGlobalModel }
, Cmd.batch [ pageCmd, newGlobalCmd |> Cmd.map MsgGlobal ]
)
MsgPage msg_ ->
let
( updatedPageModel, pageCmd, ( newGlobalModel, newGlobalCmd ) ) =
case ( model.page, model.current |> Maybe.map .metadata ) of
( ModelPage pageModel, Just (M.Page metadata) ) ->
Template.Page.template.update
metadata
msg_
pageModel
model.global
|> mapBoth ModelPage (Cmd.map MsgPage)
|> (\\\\( a, b, c ) ->
case c of
Just sharedMsg ->
( a, b, Shared.template.update (Shared.SharedMsg sharedMsg) model.global )
Nothing ->
( a, b, ( model.global, Cmd.none ) )
)
_ ->
( model.page, Cmd.none, ( model.global, Cmd.none ) )
in
( { model | page = updatedPageModel, global = newGlobalModel }
, Cmd.batch [ pageCmd, newGlobalCmd |> Cmd.map MsgGlobal ]
)
MsgShowcase msg_ ->
let
( updatedPageModel, pageCmd, ( newGlobalModel, newGlobalCmd ) ) =
case ( model.page, model.current |> Maybe.map .metadata ) of
( ModelShowcase pageModel, Just (M.Showcase metadata) ) ->
Template.Showcase.template.update
metadata
msg_
pageModel
model.global
|> mapBoth ModelShowcase (Cmd.map MsgShowcase)
|> (\\\\( a, b, c ) ->
case c of
Just sharedMsg ->
( a, b, Shared.template.update (Shared.SharedMsg sharedMsg) model.global )
Nothing ->
( a, b, ( model.global, Cmd.none ) )
)
_ ->
( model.page, Cmd.none, ( model.global, Cmd.none ) )
in
( { model | page = updatedPageModel, global = newGlobalModel }
, Cmd.batch [ pageCmd, newGlobalCmd |> Cmd.map MsgGlobal ]
)
type alias SiteConfig =
{ canonicalUrl : String
, manifest : Manifest.Config Pages.PathKey
}
templateSubscriptions : TemplateType -> PagePath Pages.PathKey -> Model -> Sub Msg
templateSubscriptions metadata path model =
case model.page of
ModelBlogIndex templateModel ->
case metadata of
M.BlogIndex templateMetadata ->
Template.BlogIndex.template.subscriptions
templateMetadata
path
templateModel
model.global
|> Sub.map MsgBlogIndex
_ ->
Sub.none
ModelBlogPost templateModel ->
case metadata of
M.BlogPost templateMetadata ->
Template.BlogPost.template.subscriptions
templateMetadata
path
templateModel
model.global
|> Sub.map MsgBlogPost
_ ->
Sub.none
ModelDocumentation templateModel ->
case metadata of
M.Documentation templateMetadata ->
Template.Documentation.template.subscriptions
templateMetadata
path
templateModel
model.global
|> Sub.map MsgDocumentation
_ ->
Sub.none
ModelPage templateModel ->
case metadata of
M.Page templateMetadata ->
Template.Page.template.subscriptions
templateMetadata
path
templateModel
model.global
|> Sub.map MsgPage
_ ->
Sub.none
ModelShowcase templateModel ->
case metadata of
M.Showcase templateMetadata ->
Template.Showcase.template.subscriptions
templateMetadata
path
templateModel
model.global
|> Sub.map MsgShowcase
_ ->
Sub.none
NotFound ->
Sub.none
mainTemplate { documents, site } =
Pages.Platform.init
{ init = init Nothing
, view = view
, update = update
, subscriptions =
\\\\metadata path model ->
Sub.batch
[ Shared.template.subscriptions metadata path model.global |> Sub.map MsgGlobal
, templateSubscriptions metadata path model
]
, documents = documents
, onPageChange = Just OnPageChange
, manifest = site.manifest
, canonicalSiteUrl = site.canonicalUrl
, internals = Pages.internals
}
mapDocument : Browser.Document Never -> Browser.Document mapped
mapDocument document =
{ title = document.title
, body = document.body |> List.map (Html.map never)
}
mapBoth fnA fnB ( a, b, c ) =
( fnA a, fnB b, c )
"
`;

View File

@ -0,0 +1,18 @@
const { elmPagesCliFile, elmPagesUiFile } = require("../generator/src/elm-file-constants.js");
const { generateTemplateModuleConnector } = require("../generator/src/generate-template-module-connector.js");
const generateRecords = require("../generator/src/generate-records.js");
test('generate UI file', async () => {
process.chdir(__dirname);
const staticRoutes = await generateRecords();
global.builtAt = new Date("Sun, 17 May 2020 16:53:22 GMT");
expect(elmPagesUiFile(staticRoutes, [])).toMatchSnapshot();
});
test('generate template module connector', async () => {
process.chdir(__dirname);
const generated = await generateTemplateModuleConnector();
expect(generated).toMatchSnapshot();
});

View File

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

View File

@ -0,0 +1,190 @@
module Template.BlogPost exposing (Model, Msg, decoder, template)
import Data.Author as Author exposing (Author)
import Date exposing (Date)
import Element exposing (Element)
import Element.Font as Font
import Element.Region
import Head
import Head.Seo as Seo
import Json.Decode as Decode
import List.Extra
import OptimizedDecoder as D
import Pages
import Pages.ImagePath as ImagePath exposing (ImagePath)
import Pages.PagePath as PagePath exposing (PagePath)
import Pages.StaticHttp as StaticHttp
import Palette
import Secrets
import Site
import StructuredData
import Template
import Template.Metadata exposing (BlogPost)
import TemplateDocument exposing (TemplateDocument)
import TemplateType
type alias Model =
()
type alias Msg =
Never
template : TemplateDocument BlogPost StaticData Model Msg
template =
Template.withStaticData staticData head
|> Template.buildNoState { view = view }
decoder : Decode.Decoder BlogPost
decoder =
Decode.map6 BlogPost
(Decode.field "title" Decode.string)
(Decode.field "description" Decode.string)
(Decode.field "published"
(Decode.string
|> Decode.andThen
(\isoString ->
case Date.fromIsoString isoString of
Ok date ->
Decode.succeed date
Err error ->
Decode.fail error
)
)
)
(Decode.field "author" Author.decoder)
(Decode.field "image" imageDecoder)
(Decode.field "draft" Decode.bool
|> Decode.maybe
|> Decode.map (Maybe.withDefault False)
)
imageDecoder : Decode.Decoder (ImagePath Pages.PathKey)
imageDecoder =
Decode.string
|> Decode.andThen
(\imageAssetPath ->
case findMatchingImage imageAssetPath of
Nothing ->
Decode.fail "Couldn't find image."
Just imagePath ->
Decode.succeed imagePath
)
findMatchingImage : String -> Maybe (ImagePath Pages.PathKey)
findMatchingImage imageAssetPath =
List.Extra.find
(\image -> ImagePath.toString image == imageAssetPath)
Pages.allImages
staticData : a -> StaticHttp.Request StaticData
staticData siteMetadata =
StaticHttp.get (Secrets.succeed "https://api.github.com/repos/dillonkearns/elm-pages")
(D.field "stargazers_count" D.int)
type alias StaticData =
Int
view : List ( PagePath Pages.PathKey, TemplateType.Metadata ) -> StaticData -> Model -> BlogPost -> ( a, List (Element msg) ) -> { title : String, body : Element msg }
view allMetadata static model blogPost rendered =
{ title = blogPost.title
, body =
Element.column [ Element.width Element.fill ]
[ Element.column
[ Element.padding 30
, Element.spacing 40
, Element.Region.mainContent
, Element.width (Element.fill |> Element.maximum 800)
, Element.centerX
]
(Element.column [ Element.spacing 10 ]
[ Element.row [ Element.spacing 10 ]
[ Author.view [] blogPost.author
, Element.column [ Element.spacing 10, Element.width Element.fill ]
[ Element.paragraph [ Font.bold, Font.size 24 ]
[ Element.text blogPost.author.name
]
, Element.paragraph [ Font.size 16 ]
[ Element.text blogPost.author.bio ]
]
]
]
:: (publishedDateView blogPost |> Element.el [ Font.size 16, Font.color (Element.rgba255 0 0 0 0.6) ])
:: Palette.blogHeading blogPost.title
:: articleImageView blogPost.image
:: Tuple.second rendered
)
]
}
head : StaticData -> PagePath.PagePath Pages.PathKey -> BlogPost -> List (Head.Tag Pages.PathKey)
head static currentPath meta =
Head.structuredData
(StructuredData.article
{ title = meta.title
, description = meta.description
, author = StructuredData.person { name = meta.author.name }
, publisher = StructuredData.person { name = "Dillon Kearns" }
, url = Site.canonicalUrl ++ "/" ++ PagePath.toString currentPath
, imageUrl = Site.canonicalUrl ++ "/" ++ ImagePath.toString meta.image
, datePublished = Date.toIsoString meta.published
, mainEntityOfPage =
StructuredData.softwareSourceCode
{ codeRepositoryUrl = "https://github.com/dillonkearns/elm-pages"
, description = "A statically typed site generator for Elm."
, author = "Dillon Kearns"
, programmingLanguage = StructuredData.elmLang
}
}
)
:: (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
}
)
publishedDateView : { a | published : Date.Date } -> Element msg
publishedDateView metadata =
Element.text
(metadata.published
|> Date.format "MMMM ddd, yyyy"
)
articleImageView : ImagePath Pages.PathKey -> Element msg
articleImageView articleImage =
Element.image [ Element.width Element.fill ]
{ src = ImagePath.toString articleImage
, description = "Article cover photo"
}

View File

@ -0,0 +1 @@
module Main exposing (..)

View File

@ -0,0 +1 @@
module Main exposing (..)

View File

@ -7,7 +7,7 @@
"main": "index.js",
"scripts": {
"build": "cd generator && elm make src/Main.elm --output src/Main.js --optimize",
"test": "elm-test && jest"
"test": "elm-test && jest --rootDir jest-tests"
},
"repository": "https://github.com/dillonkearns/elm-pages",
"keywords": [
@ -75,6 +75,7 @@
],
"bin": {
"elm-pages": "generator/src/elm-pages.js",
"elm-pages-beta": "generator/src/cli.js"
"elm-pages-beta": "generator/src/cli.js",
"elm-pages-generate": "generator/src/codegen-template-module.js"
}
}

View File

@ -113,8 +113,8 @@ To get that data, you would write this in your `elm-pages` head tags:
encodeArticle :
{ title : String
, description : String
, author : StructuredData { authorMemberOf | personOrOrganization : () } authorPossibleFields
, publisher : StructuredData { publisherMemberOf | personOrOrganization : () } publisherPossibleFields
, author : StructuredDataHelper { authorMemberOf | personOrOrganization : () } authorPossibleFields
, publisher : StructuredDataHelper { publisherMemberOf | personOrOrganization : () } publisherPossibleFields
, url : String
, imageUrl : String
, datePublished : String

View File

@ -288,9 +288,12 @@ init :
-> Content
->
(Maybe
{ path : PagePath pathKey
, query : Maybe String
, fragment : Maybe String
{ metadata : metadata
, path :
{ path : PagePath pathKey
, query : Maybe String
, fragment : Maybe String
}
}
-> ( userModel, Cmd userMsg )
)
@ -356,14 +359,18 @@ init pathKey canonicalSiteUrl document toJsPort viewFn content initUserModel fla
DevClient False
( userModel, userCmd ) =
maybePagePath
|> Maybe.map
(\pagePath ->
Maybe.map2
(\pagePath metadata ->
{ path =
{ path = pagePath
, query = url.query
, fragment = url.fragment
}
)
, metadata = metadata
}
)
maybePagePath
maybeMetadata
|> initUserModel
cmd =
@ -491,6 +498,7 @@ update :
({ path : PagePath pathKey
, query : Maybe String
, fragment : Maybe String
, metadata : metadata
}
-> userMsg
)
@ -615,14 +623,42 @@ update content allRoutes canonicalSiteUrl viewFunction pathKey maybeOnPageChange
( userModel, userCmd ) =
case maybeOnPageChangeMsg of
Just onPageChangeMsg ->
userUpdate
(onPageChangeMsg
{ path = urlToPagePath pathKey url model.baseUrl
, query = url.query
, fragment = url.fragment
let
urls =
{ currentUrl = url
, baseUrl = model.baseUrl
}
)
model.userModel
maybeMetadata =
case ContentCache.lookup pathKey updatedCache urls of
Just ( pagePath, entry ) ->
case entry of
ContentCache.Parsed metadata rawBody viewResult ->
Just metadata
ContentCache.NeedContent string metadata ->
Nothing
ContentCache.Unparsed string metadata contentJson ->
Nothing
Nothing ->
Nothing
in
maybeMetadata
|> Maybe.map
(\metadata ->
userUpdate
(onPageChangeMsg
{ path = urlToPagePath pathKey url model.baseUrl
, query = url.query
, fragment = url.fragment
, metadata = metadata
}
)
model.userModel
)
|> Maybe.withDefault ( model.userModel, Cmd.none )
_ ->
( model.userModel, Cmd.none )
@ -683,13 +719,16 @@ type HmrStatus
application :
{ init :
Maybe
{ path : PagePath pathKey
, query : Maybe String
, fragment : Maybe String
{ path :
{ path : PagePath pathKey
, query : Maybe String
, fragment : Maybe String
}
, metadata : metadata
}
-> ( userModel, Cmd userMsg )
, update : userMsg -> userModel -> ( userModel, Cmd userMsg )
, subscriptions : userModel -> Sub userMsg
, subscriptions : metadata -> PagePath pathKey -> userModel -> Sub userMsg
, view :
List ( PagePath pathKey, metadata )
->
@ -728,6 +767,7 @@ application :
({ path : PagePath pathKey
, query : Maybe String
, fragment : Maybe String
, metadata : metadata
}
-> userMsg
)
@ -783,10 +823,33 @@ application config =
\outerModel ->
case outerModel of
Model model ->
let
urls =
{ currentUrl = model.url
, baseUrl = model.baseUrl
}
( maybePagePath, maybeMetadata ) =
case ContentCache.lookupMetadata config.pathKey model.contentCache urls of
Just ( pagePath, metadata ) ->
( Just pagePath, Just metadata )
Nothing ->
( Nothing, Nothing )
userSub =
Maybe.map2
(\metadata path ->
config.subscriptions metadata path model.userModel
|> Sub.map UserMsg
|> Sub.map AppMsg
)
maybeMetadata
maybePagePath
|> Maybe.withDefault Sub.none
in
Sub.batch
[ config.subscriptions model.userModel
|> Sub.map UserMsg
|> Sub.map AppMsg
[ userSub
, config.fromJsPort
|> Sub.map
(\decodeValue ->
@ -815,13 +878,16 @@ application config =
cliApplication :
{ init :
Maybe
{ path : PagePath pathKey
, query : Maybe String
, fragment : Maybe String
{ path :
{ path : PagePath pathKey
, query : Maybe String
, fragment : Maybe String
}
, metadata : metadata
}
-> ( userModel, Cmd userMsg )
, update : userMsg -> userModel -> ( userModel, Cmd userMsg )
, subscriptions : userModel -> Sub userMsg
, subscriptions : metadata -> PagePath pathKey -> userModel -> Sub userMsg
, view :
List ( PagePath pathKey, metadata )
->
@ -860,6 +926,7 @@ cliApplication :
({ path : PagePath pathKey
, query : Maybe String
, fragment : Maybe String
, metadata : metadata
}
-> userMsg
)

View File

@ -78,13 +78,16 @@ type Msg
type alias Config pathKey userMsg userModel metadata view =
{ init :
Maybe
{ path : PagePath pathKey
, query : Maybe String
, fragment : Maybe String
{ path :
{ path : PagePath pathKey
, query : Maybe String
, fragment : Maybe String
}
, metadata : metadata
}
-> ( userModel, Cmd userMsg )
, update : userMsg -> userModel -> ( userModel, Cmd userMsg )
, subscriptions : userModel -> Sub userMsg
, subscriptions : metadata -> PagePath pathKey -> userModel -> Sub userMsg
, view :
List ( PagePath pathKey, metadata )
->
@ -123,6 +126,7 @@ type alias Config pathKey userMsg userModel metadata view =
({ path : PagePath pathKey
, query : Maybe String
, fragment : Maybe String
, metadata : metadata
}
-> userMsg
)
@ -757,9 +761,12 @@ sendSinglePageProgress toJsPayload siteMetadata config contentCache model =
pageModel =
config.init
(Just
{ path = currentPage.path
, query = Nothing
, fragment = Nothing
{ path =
{ path = currentPage.path
, query = Nothing
, fragment = Nothing
}
, metadata = metadata
}
)
|> Tuple.first

View File

@ -83,13 +83,16 @@ type Builder pathKey model msg metadata view
= Builder
{ init :
Maybe
{ path : PagePath pathKey
, query : Maybe String
, fragment : Maybe String
{ path :
{ path : PagePath pathKey
, query : Maybe String
, fragment : Maybe String
}
, metadata : metadata
}
-> ( model, Cmd msg )
, update : msg -> model -> ( model, Cmd msg )
, subscriptions : model -> Sub msg
, subscriptions : metadata -> PagePath pathKey -> model -> Sub msg
, view :
List ( PagePath pathKey, metadata )
->
@ -123,6 +126,7 @@ type Builder pathKey model msg metadata view
({ path : PagePath pathKey
, query : Maybe String
, fragment : Maybe String
, metadata : metadata
}
-> msg
)
@ -161,9 +165,12 @@ Here's a basic example.
init :
{ init :
Maybe
{ path : PagePath pathKey
, query : Maybe String
, fragment : Maybe String
{ path :
{ path : PagePath pathKey
, query : Maybe String
, fragment : Maybe String
}
, metadata : metadata
}
-> ( model, Cmd msg )
, update : msg -> model -> ( model, Cmd msg )
@ -178,7 +185,7 @@ init :
{ view : model -> view -> { title : String, body : Html msg }
, head : List (Head.Tag pathKey)
}
, subscriptions : model -> Sub msg
, subscriptions : metadata -> PagePath pathKey -> model -> Sub msg
, documents :
List
{ extension : String
@ -190,6 +197,7 @@ init :
({ path : PagePath pathKey
, query : Maybe String
, fragment : Maybe String
, metadata : metadata
}
-> msg
)
@ -313,13 +321,16 @@ toProgram (Builder config) =
application :
{ init :
Maybe
{ path : PagePath pathKey
, query : Maybe String
, fragment : Maybe String
{ path :
{ path : PagePath pathKey
, query : Maybe String
, fragment : Maybe String
}
, metadata : metadata
}
-> ( model, Cmd msg )
, update : msg -> model -> ( model, Cmd msg )
, subscriptions : model -> Sub msg
, subscriptions : metadata -> PagePath pathKey -> model -> Sub msg
, view :
List ( PagePath pathKey, metadata )
->
@ -353,6 +364,7 @@ application :
({ path : PagePath pathKey
, query : Maybe String
, fragment : Maybe String
, metadata : metadata
}
-> msg
)

View File

@ -132,7 +132,7 @@ encode (StructuredData typeName fields) =
--example : StructuredData { personOrOrganization : () } { address : (), affiliation : () }
--example : StructuredDataHelper { personOrOrganization : () } { address : (), affiliation : () }
example =
@ -143,12 +143,12 @@ example =
--organization :
-- {}
-- -> StructuredData { personOrOrganization : () }
-- -> StructuredDataHelper { personOrOrganization : () }
--organization info =
-- StructuredData "Organization" []
--needsPersonOrOrg : StructuredData {}
-- StructuredDataHelper "Organization" []
--needsPersonOrOrg : StructuredDataHelper {}
--needsPersonOrOrg =
-- StructuredData "" []
-- StructuredDataHelper "" []
{-|

View File

@ -169,7 +169,7 @@ startLowLevel generateFiles documentBodyResult staticHttpCache pages =
Nothing ->
Debug.todo "Couldn't find page"
, subscriptions = \_ -> Sub.none
, subscriptions = \_ _ _ -> Sub.none
, document = document
, content =
[ ( [ "elm-pages" ]

View File

@ -852,7 +852,7 @@ startLowLevel generateFiles documentBodyResult staticHttpCache pages =
Nothing ->
Debug.todo "Couldn't find page"
, subscriptions = \_ -> Sub.none
, subscriptions = \_ _ _ -> Sub.none
, document = document
, content = []
, canonicalSiteUrl = canonicalSiteUrl

View File

@ -1,115 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`generate UI file 1`] = `
"port module Pages exposing (PathKey, allPages, allImages, internals, images, isValidRoute, pages, builtAt)
import Color exposing (Color)
import Pages.Internal
import Head
import Html exposing (Html)
import Json.Decode
import Json.Encode
import Pages.Platform
import Pages.Manifest exposing (DisplayMode, Orientation)
import Pages.Manifest.Category as Category exposing (Category)
import Url.Parser as Url exposing ((</>), s)
import Pages.ImagePath as ImagePath exposing (ImagePath)
import Pages.PagePath as PagePath exposing (PagePath)
import Pages.Directory as Directory exposing (Directory)
import Time
builtAt : Time.Posix
builtAt =
Time.millisToPosix 1589734402000
type PathKey
= PathKey
buildImage : List String -> ImagePath.Dimensions -> ImagePath PathKey
buildImage path dimensions =
ImagePath.build PathKey (\\"images\\" :: path) dimensions
buildPage : List String -> PagePath PathKey
buildPage path =
PagePath.build PathKey path
directoryWithIndex : List String -> Directory PathKey Directory.WithIndex
directoryWithIndex path =
Directory.withIndex PathKey allPages path
directoryWithoutIndex : List String -> Directory PathKey Directory.WithoutIndex
directoryWithoutIndex path =
Directory.withoutIndex PathKey allPages path
port toJsPort : Json.Encode.Value -> Cmd msg
port fromJsPort : (Json.Decode.Value -> msg) -> Sub msg
internals : Pages.Internal.Internal PathKey
internals =
{ applicationType = Pages.Internal.Browser
, toJsPort = toJsPort
, fromJsPort = fromJsPort identity
, content = content
, pathKey = PathKey
}
allPages : List (PagePath PathKey)
allPages =
[
]
pages =
{ directory = directoryWithoutIndex []
}
images =
{ staticHttpError = (buildImage [ \\"static-http-error.png\\" ] { width = 919, height = 105 })
, directory = directoryWithoutIndex []
}
allImages : List (ImagePath PathKey)
allImages =
[(buildImage [ \\"static-http-error.png\\" ] { width = 919, height = 105 })
]
isValidRoute : String -> Result String ()
isValidRoute route =
let
validRoutes =
List.map PagePath.toString allPages
in
if
(route |> String.startsWith \\"http://\\")
|| (route |> String.startsWith \\"https://\\")
|| (route |> String.startsWith \\"#\\")
|| (validRoutes |> List.member route)
then
Ok ()
else
(\\"Valid routes:\\\\n\\"
++ String.join \\"\\\\n\\\\n\\" validRoutes
)
|> Err
content : List ( List String, { extension: String, frontMatter : String, body : Maybe String } )
content =
[
]
"
`;

View File

@ -1,4 +1,5 @@
const { elmPagesCliFile, elmPagesUiFile } = require("../generator/src/elm-file-constants.js");
const { generateTemplateModuleConnector } = require("../generator/src/generate-template-module-connector.js");
const generateRecords = require("../generator/src/generate-records.js");
test('generate UI file', async () => {
@ -8,3 +9,10 @@ test('generate UI file', async () => {
global.builtAt = new Date("Sun, 17 May 2020 16:53:22 GMT");
expect(elmPagesUiFile(staticRoutes, [])).toMatchSnapshot();
});
test('generate template module connector', async () => {
process.chdir(__dirname);
const generated = await generateTemplateModuleConnector();
expect(generated).toMatchSnapshot();
});