mirror of
https://github.com/dillonkearns/elm-pages-v3-beta.git
synced 2024-12-28 22:37:08 +03:00
Merge pull request #109 from dillonkearns/template-modules
Try file structure with each template in its own module
This commit is contained in:
commit
73cf428b19
@ -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.
|
||||
|
98
docs/7.0.0-elm-package-upgrade-guide.md
Normal file
98
docs/7.0.0-elm-package-upgrade-guide.md
Normal 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
|
@ -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.
|
8
examples/docs/elm-application.json
Normal file
8
examples/docs/elm-application.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "dillonkearns/elm-pages-local",
|
||||
"summary": "Elm Pages local docs",
|
||||
"version": "1.0.0",
|
||||
"exposed-modules": [
|
||||
"Template"
|
||||
]
|
||||
}
|
@ -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 )
|
||||
|
||||
_ ->
|
||||
|
@ -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
|
||||
|
@ -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" }
|
||||
}
|
||||
|
@ -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)
|
||||
|
89
examples/docs/src/MetadataNew.elm
Normal file
89
examples/docs/src/MetadataNew.elm
Normal 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)
|
0
examples/docs/src/MetadataThing.elm
Normal file
0
examples/docs/src/MetadataThing.elm
Normal file
348
examples/docs/src/Shared.elm
Normal file
348
examples/docs/src/Shared.elm
Normal 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
|
||||
}
|
98
examples/docs/src/Site.elm
Normal file
98
examples/docs/src/Site.elm
Normal 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
|
98
examples/docs/src/Template/BlogIndex.elm
Normal file
98
examples/docs/src/Template/BlogIndex.elm
Normal 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
|
180
examples/docs/src/Template/BlogPost.elm
Normal file
180
examples/docs/src/Template/BlogPost.elm
Normal 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"
|
||||
}
|
146
examples/docs/src/Template/Documentation.elm
Normal file
146
examples/docs/src/Template/Documentation.elm
Normal 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
|
||||
}
|
||||
)
|
||||
)
|
||||
]
|
76
examples/docs/src/Template/Page.elm
Normal file
76
examples/docs/src/Template/Page.elm
Normal 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
|
||||
]
|
||||
]
|
||||
}
|
80
examples/docs/src/Template/Showcase.elm
Normal file
80
examples/docs/src/Template/Showcase.elm
Normal 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
|
52
examples/docs/src/TemplateHardcoded.elm
Normal file
52
examples/docs/src/TemplateHardcoded.elm
Normal 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 )
|
||||
}
|
32
examples/docs/src/TemplateMetadata.elm
Normal file
32
examples/docs/src/TemplateMetadata.elm
Normal 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 }
|
11
examples/docs/src/TemplateType.elm
Normal file
11
examples/docs/src/TemplateType.elm
Normal 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
|
@ -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
208
generator/src/Template.elm
Normal 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
|
||||
}
|
0
generator/src/codegen-template-builder.js
Normal file
0
generator/src/codegen-template-builder.js
Normal file
69
generator/src/codegen-template-module.js
Executable file
69
generator/src/codegen-template-module.js
Executable 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 =
|
||||
[]
|
||||
}
|
||||
|
||||
`;
|
||||
}
|
@ -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();
|
||||
|
@ -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();
|
||||
|
||||
|
313
generator/src/generate-template-module-connector.js
Normal file
313
generator/src/generate-template-module-connector.js
Normal 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 };
|
769
jest-tests/__snapshots__/generated-files.test.js.snap
Normal file
769
jest-tests/__snapshots__/generated-files.test.js.snap
Normal 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 )
|
||||
"
|
||||
`;
|
18
jest-tests/generated-files.test.js
Normal file
18
jest-tests/generated-files.test.js
Normal 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();
|
||||
});
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 20 KiB |
0
jest-tests/src/Template/BlogIndex.elm
Normal file
0
jest-tests/src/Template/BlogIndex.elm
Normal file
190
jest-tests/src/Template/BlogPost.elm
Normal file
190
jest-tests/src/Template/BlogPost.elm
Normal 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"
|
||||
}
|
0
jest-tests/src/Template/Documentation.elm
Normal file
0
jest-tests/src/Template/Documentation.elm
Normal file
1
jest-tests/src/Template/Page.elm
Normal file
1
jest-tests/src/Template/Page.elm
Normal file
@ -0,0 +1 @@
|
||||
module Main exposing (..)
|
1
jest-tests/src/Template/Showcase.elm
Normal file
1
jest-tests/src/Template/Showcase.elm
Normal file
@ -0,0 +1 @@
|
||||
module Main exposing (..)
|
@ -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"
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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
|
||||
)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
)
|
||||
|
@ -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 "" []
|
||||
|
||||
|
||||
{-|
|
||||
|
@ -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" ]
|
||||
|
@ -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
|
||||
|
@ -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 =
|
||||
[
|
||||
]
|
||||
"
|
||||
`;
|
@ -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();
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user