init
This commit is contained in:
commit
7a8ce17237
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
node_modules/
|
||||
elm-stuff/
|
||||
dist/
|
||||
.elm-pages/
|
||||
functions/render/elm-pages-cli.js
|
26
app/Api.elm
Normal file
26
app/Api.elm
Normal file
@ -0,0 +1,26 @@
|
||||
module Api exposing (routes)
|
||||
|
||||
import ApiRoute exposing (ApiRoute)
|
||||
import BackendTask exposing (BackendTask)
|
||||
import FatalError exposing (FatalError)
|
||||
import Html exposing (Html)
|
||||
import Pages.Manifest as Manifest
|
||||
import Route exposing (Route)
|
||||
|
||||
|
||||
routes :
|
||||
BackendTask FatalError (List Route)
|
||||
-> (Maybe { indent : Int, newLines : Bool } -> Html Never -> String)
|
||||
-> List (ApiRoute ApiRoute.Response)
|
||||
routes getStaticRoutes htmlToString =
|
||||
[]
|
||||
|
||||
|
||||
manifest : Manifest.Config
|
||||
manifest =
|
||||
Manifest.init
|
||||
{ name = "Site Name"
|
||||
, description = "Description"
|
||||
, startUrl = Route.Index |> Route.toPath
|
||||
, icons = []
|
||||
}
|
155
app/Effect.elm
Normal file
155
app/Effect.elm
Normal file
@ -0,0 +1,155 @@
|
||||
module Effect exposing (Effect(..), batch, fromCmd, map, none, perform)
|
||||
|
||||
{-|
|
||||
|
||||
@docs Effect, batch, fromCmd, map, none, perform
|
||||
|
||||
-}
|
||||
|
||||
import Browser.Navigation
|
||||
import Form
|
||||
import Http
|
||||
import Json.Decode as Decode
|
||||
import Pages.Fetcher
|
||||
import Url exposing (Url)
|
||||
|
||||
|
||||
{-| -}
|
||||
type Effect msg
|
||||
= None
|
||||
| Cmd (Cmd msg)
|
||||
| Batch (List (Effect msg))
|
||||
| GetStargazers (Result Http.Error Int -> msg)
|
||||
| SetField { formId : String, name : String, value : String }
|
||||
| FetchRouteData
|
||||
{ data : Maybe FormData
|
||||
, toMsg : Result Http.Error Url -> msg
|
||||
}
|
||||
| Submit
|
||||
{ values : FormData
|
||||
, toMsg : Result Http.Error Url -> msg
|
||||
}
|
||||
| SubmitFetcher (Pages.Fetcher.Fetcher msg)
|
||||
|
||||
|
||||
{-| -}
|
||||
type alias RequestInfo =
|
||||
{ contentType : String
|
||||
, body : String
|
||||
}
|
||||
|
||||
|
||||
{-| -}
|
||||
none : Effect msg
|
||||
none =
|
||||
None
|
||||
|
||||
|
||||
{-| -}
|
||||
batch : List (Effect msg) -> Effect msg
|
||||
batch =
|
||||
Batch
|
||||
|
||||
|
||||
{-| -}
|
||||
fromCmd : Cmd msg -> Effect msg
|
||||
fromCmd =
|
||||
Cmd
|
||||
|
||||
|
||||
{-| -}
|
||||
map : (a -> b) -> Effect a -> Effect b
|
||||
map fn effect =
|
||||
case effect of
|
||||
None ->
|
||||
None
|
||||
|
||||
Cmd cmd ->
|
||||
Cmd (Cmd.map fn cmd)
|
||||
|
||||
Batch list ->
|
||||
Batch (List.map (map fn) list)
|
||||
|
||||
GetStargazers toMsg ->
|
||||
GetStargazers (toMsg >> fn)
|
||||
|
||||
FetchRouteData fetchInfo ->
|
||||
FetchRouteData
|
||||
{ data = fetchInfo.data
|
||||
, toMsg = fetchInfo.toMsg >> fn
|
||||
}
|
||||
|
||||
Submit fetchInfo ->
|
||||
Submit
|
||||
{ values = fetchInfo.values
|
||||
, toMsg = fetchInfo.toMsg >> fn
|
||||
}
|
||||
|
||||
SetField info ->
|
||||
SetField info
|
||||
|
||||
SubmitFetcher fetcher ->
|
||||
fetcher
|
||||
|> Pages.Fetcher.map fn
|
||||
|> SubmitFetcher
|
||||
|
||||
|
||||
{-| -}
|
||||
perform :
|
||||
{ fetchRouteData :
|
||||
{ data : Maybe FormData
|
||||
, toMsg : Result Http.Error Url -> pageMsg
|
||||
}
|
||||
-> Cmd msg
|
||||
, submit :
|
||||
{ values : FormData
|
||||
, toMsg : Result Http.Error Url -> pageMsg
|
||||
}
|
||||
-> Cmd msg
|
||||
, runFetcher :
|
||||
Pages.Fetcher.Fetcher pageMsg
|
||||
-> Cmd msg
|
||||
, fromPageMsg : pageMsg -> msg
|
||||
, key : Browser.Navigation.Key
|
||||
, setField : { formId : String, name : String, value : String } -> Cmd msg
|
||||
}
|
||||
-> Effect pageMsg
|
||||
-> Cmd msg
|
||||
perform ({ fromPageMsg, key } as helpers) effect =
|
||||
case effect of
|
||||
None ->
|
||||
Cmd.none
|
||||
|
||||
Cmd cmd ->
|
||||
Cmd.map fromPageMsg cmd
|
||||
|
||||
SetField info ->
|
||||
helpers.setField info
|
||||
|
||||
Batch list ->
|
||||
Cmd.batch (List.map (perform helpers) list)
|
||||
|
||||
GetStargazers toMsg ->
|
||||
Http.get
|
||||
{ url =
|
||||
"https://api.github.com/repos/dillonkearns/elm-pages"
|
||||
, expect = Http.expectJson (toMsg >> fromPageMsg) (Decode.field "stargazers_count" Decode.int)
|
||||
}
|
||||
|
||||
FetchRouteData fetchInfo ->
|
||||
helpers.fetchRouteData
|
||||
fetchInfo
|
||||
|
||||
Submit record ->
|
||||
helpers.submit record
|
||||
|
||||
SubmitFetcher record ->
|
||||
helpers.runFetcher record
|
||||
|
||||
|
||||
type alias FormData =
|
||||
{ fields : List ( String, String )
|
||||
, method : Form.Method
|
||||
, action : String
|
||||
, id : Maybe String
|
||||
}
|
81
app/ErrorPage.elm
Normal file
81
app/ErrorPage.elm
Normal file
@ -0,0 +1,81 @@
|
||||
module ErrorPage exposing (ErrorPage(..), Model, Msg, head, init, internalError, notFound, statusCode, update, view)
|
||||
|
||||
import Effect exposing (Effect)
|
||||
import Head
|
||||
import Html.Styled as Html
|
||||
import Html.Styled.Events exposing (onClick)
|
||||
import View exposing (View)
|
||||
|
||||
|
||||
type Msg
|
||||
= Increment
|
||||
|
||||
|
||||
type alias Model =
|
||||
{ count : Int
|
||||
}
|
||||
|
||||
|
||||
init : ErrorPage -> ( Model, Effect Msg )
|
||||
init errorPage =
|
||||
( { count = 0 }
|
||||
, Effect.none
|
||||
)
|
||||
|
||||
|
||||
update : ErrorPage -> Msg -> Model -> ( Model, Effect Msg )
|
||||
update errorPage msg model =
|
||||
case msg of
|
||||
Increment ->
|
||||
( { model | count = model.count + 1 }, Effect.none )
|
||||
|
||||
|
||||
head : ErrorPage -> List Head.Tag
|
||||
head errorPage =
|
||||
[]
|
||||
|
||||
|
||||
type ErrorPage
|
||||
= NotFound
|
||||
| InternalError String
|
||||
|
||||
|
||||
notFound : ErrorPage
|
||||
notFound =
|
||||
NotFound
|
||||
|
||||
|
||||
internalError : String -> ErrorPage
|
||||
internalError =
|
||||
InternalError
|
||||
|
||||
|
||||
view : ErrorPage -> Model -> View Msg
|
||||
view error model =
|
||||
{ body =
|
||||
[ Html.div []
|
||||
[ Html.p [] [ Html.text "Page not found. Maybe try another URL?" ]
|
||||
, Html.div []
|
||||
[ Html.button
|
||||
[ onClick Increment
|
||||
]
|
||||
[ Html.text
|
||||
(model.count
|
||||
|> String.fromInt
|
||||
)
|
||||
]
|
||||
]
|
||||
]
|
||||
]
|
||||
, title = "This is a NotFound Error"
|
||||
}
|
||||
|
||||
|
||||
statusCode : ErrorPage -> number
|
||||
statusCode error =
|
||||
case error of
|
||||
NotFound ->
|
||||
404
|
||||
|
||||
InternalError _ ->
|
||||
500
|
145
app/Route/Blog/Slug_.elm
Normal file
145
app/Route/Blog/Slug_.elm
Normal file
@ -0,0 +1,145 @@
|
||||
module Route.Blog.Slug_ exposing (ActionData, Data, Model, Msg, route)
|
||||
|
||||
import Article
|
||||
import BackendTask exposing (BackendTask)
|
||||
import Date exposing (Date)
|
||||
import FatalError exposing (FatalError)
|
||||
import Head
|
||||
import Head.Seo as Seo
|
||||
import Html.Styled exposing (..)
|
||||
import Json.Decode as Decode exposing (Decoder)
|
||||
import Json.Decode.Extra
|
||||
import Pages.Url
|
||||
import PagesMsg exposing (PagesMsg)
|
||||
import RouteBuilder exposing (App, StatelessRoute)
|
||||
import Shared
|
||||
import View exposing (View)
|
||||
|
||||
|
||||
import Markdown.Block
|
||||
import Markdown.Renderer
|
||||
import MarkdownCodec
|
||||
import TailwindMarkdownRenderer
|
||||
import Tailwind.Utilities as Tw
|
||||
|
||||
|
||||
type alias Model =
|
||||
{}
|
||||
|
||||
|
||||
type alias Msg =
|
||||
()
|
||||
|
||||
|
||||
type alias RouteParams =
|
||||
{ slug : String }
|
||||
|
||||
|
||||
route : StatelessRoute RouteParams Data ActionData
|
||||
route =
|
||||
RouteBuilder.preRender
|
||||
{ head = head
|
||||
, pages = pages
|
||||
, data = data
|
||||
}
|
||||
|> RouteBuilder.buildNoState { view = view }
|
||||
|
||||
|
||||
pages : BackendTask FatalError (List RouteParams)
|
||||
pages =
|
||||
Article.blogPostsGlob
|
||||
|> BackendTask.map
|
||||
(List.map
|
||||
(\globData ->
|
||||
{ slug = globData.slug }
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
type alias Data =
|
||||
{ metadata : ArticleMetadata
|
||||
, body : List Markdown.Block.Block
|
||||
}
|
||||
|
||||
type alias ActionData =
|
||||
{}
|
||||
|
||||
|
||||
data : RouteParams -> BackendTask FatalError Data
|
||||
data routeParams =
|
||||
MarkdownCodec.withFrontmatter Data
|
||||
frontmatterDecoder
|
||||
TailwindMarkdownRenderer.renderer
|
||||
("content/blog/" ++ routeParams.slug ++ ".md")
|
||||
|
||||
|
||||
type alias ArticleMetadata =
|
||||
{ title : String
|
||||
, description : String
|
||||
, published : Date
|
||||
-- , image : Pages.Url.Url
|
||||
, draft : Bool
|
||||
}
|
||||
|
||||
|
||||
frontmatterDecoder : Decoder ArticleMetadata
|
||||
frontmatterDecoder =
|
||||
Decode.map4 ArticleMetadata
|
||||
(Decode.field "title" Decode.string)
|
||||
(Decode.field "description" Decode.string)
|
||||
(Decode.field "published"
|
||||
(Decode.string
|
||||
|> Decode.andThen
|
||||
(\isoString ->
|
||||
Date.fromIsoString isoString
|
||||
|> Json.Decode.Extra.fromResult
|
||||
)
|
||||
)
|
||||
)
|
||||
-- (Decode.oneOf
|
||||
-- [ Decode.field "image" imageDecoder
|
||||
-- , Decode.field "unsplash" UnsplashImage.decoder |> Decode.map UnsplashImage.imagePath
|
||||
-- ]
|
||||
-- )
|
||||
(Decode.field "draft" Decode.bool
|
||||
|> Decode.maybe
|
||||
|> Decode.map (Maybe.withDefault False)
|
||||
)
|
||||
|
||||
head :
|
||||
App Data ActionData RouteParams
|
||||
-> List Head.Tag
|
||||
head app =
|
||||
Seo.summary
|
||||
{ canonicalUrlOverride = Nothing
|
||||
, siteName = "elm-pages"
|
||||
, image =
|
||||
{ url = Pages.Url.external "TODO"
|
||||
, alt = "elm-pages logo"
|
||||
, dimensions = Nothing
|
||||
, mimeType = Nothing
|
||||
}
|
||||
, description = "TODO"
|
||||
, locale = Nothing
|
||||
, title = "TODO title" -- metadata.title -- TODO
|
||||
}
|
||||
|> Seo.website
|
||||
|
||||
|
||||
view :
|
||||
App Data ActionData RouteParams
|
||||
-> Shared.Model
|
||||
-> View (PagesMsg Msg)
|
||||
view app shared =
|
||||
{ title = "title"
|
||||
, body =
|
||||
(app.data.body
|
||||
|> Markdown.Renderer.render TailwindMarkdownRenderer.renderer
|
||||
|> Result.withDefault []
|
||||
|> processReturn
|
||||
)
|
||||
}
|
||||
|
||||
processReturn : List (Html Msg) -> List (Html (PagesMsg Msg))
|
||||
processReturn =
|
||||
List.map (Html.Styled.map (PagesMsg.fromMsg))
|
94
app/Route/Index.elm
Normal file
94
app/Route/Index.elm
Normal file
@ -0,0 +1,94 @@
|
||||
module Route.Index exposing (ActionData, Data, Model, Msg, route)
|
||||
|
||||
import BackendTask exposing (BackendTask)
|
||||
import FatalError exposing (FatalError)
|
||||
import Head
|
||||
import Head.Seo as Seo
|
||||
import Html.Styled as Html
|
||||
import Link exposing (Link)
|
||||
import Pages.Url
|
||||
import PagesMsg exposing (PagesMsg)
|
||||
import UrlPath
|
||||
import Route
|
||||
import RouteBuilder exposing (App, StatelessRoute)
|
||||
import Shared
|
||||
import View exposing (View)
|
||||
|
||||
|
||||
type alias Model =
|
||||
{}
|
||||
|
||||
|
||||
type alias Msg =
|
||||
()
|
||||
|
||||
|
||||
type alias RouteParams =
|
||||
{}
|
||||
|
||||
|
||||
type alias Data =
|
||||
{ message : String
|
||||
}
|
||||
|
||||
|
||||
type alias ActionData =
|
||||
{}
|
||||
|
||||
|
||||
route : StatelessRoute RouteParams Data ActionData
|
||||
route =
|
||||
RouteBuilder.single
|
||||
{ head = head
|
||||
, data = data
|
||||
}
|
||||
|> RouteBuilder.buildNoState { view = view }
|
||||
|
||||
|
||||
data : BackendTask FatalError Data
|
||||
data =
|
||||
BackendTask.succeed Data
|
||||
|> BackendTask.andMap
|
||||
(BackendTask.succeed "Hello!")
|
||||
|
||||
|
||||
head :
|
||||
App Data ActionData RouteParams
|
||||
-> List Head.Tag
|
||||
head app =
|
||||
Seo.summary
|
||||
{ canonicalUrlOverride = Nothing
|
||||
, siteName = "elm-pages"
|
||||
, image =
|
||||
{ url = [ "images", "icon-png.png" ] |> UrlPath.join |> Pages.Url.fromPath
|
||||
, alt = "elm-pages logo"
|
||||
, dimensions = Nothing
|
||||
, mimeType = Nothing
|
||||
}
|
||||
, description = "Welcome to elm-pages!"
|
||||
, locale = Nothing
|
||||
, title = "elm-pages is running"
|
||||
}
|
||||
|> Seo.website
|
||||
|
||||
|
||||
view :
|
||||
App Data ActionData RouteParams
|
||||
-> Shared.Model
|
||||
-> View (PagesMsg Msg)
|
||||
view app shared =
|
||||
{ title = "elm-pages is running"
|
||||
, body =
|
||||
[ Html.h1 [] [ Html.text "elm-pages is up and running!" ]
|
||||
, Html.p []
|
||||
[ Html.text <| "The message is: " ++ app.data.message
|
||||
]
|
||||
, Link.link (Link.internal (Route.Blog__Slug_ { slug = "a" })) [] [ Html.text "My blog post" ]
|
||||
]
|
||||
|> processReturn
|
||||
}
|
||||
|
||||
|
||||
processReturn : List (Html.Html Msg) -> List (Html.Html (PagesMsg Msg))
|
||||
processReturn =
|
||||
List.map (Html.map (PagesMsg.fromMsg))
|
135
app/Shared.elm
Normal file
135
app/Shared.elm
Normal file
@ -0,0 +1,135 @@
|
||||
module Shared exposing (Data, Model, Msg(..), SharedMsg(..), template)
|
||||
|
||||
import BackendTask exposing (BackendTask)
|
||||
import Effect exposing (Effect)
|
||||
import FatalError exposing (FatalError)
|
||||
import Html exposing (Html)
|
||||
import Html.Styled
|
||||
import Html.Styled.Events
|
||||
import Pages.Flags
|
||||
import Pages.PageUrl exposing (PageUrl)
|
||||
import UrlPath exposing (UrlPath)
|
||||
import Route exposing (Route)
|
||||
import SharedTemplate exposing (SharedTemplate)
|
||||
import View exposing (View)
|
||||
|
||||
|
||||
template : SharedTemplate Msg Model Data msg
|
||||
template =
|
||||
{ init = init
|
||||
, update = update
|
||||
, view = view
|
||||
, data = data
|
||||
, subscriptions = subscriptions
|
||||
, onPageChange = Nothing
|
||||
}
|
||||
|
||||
|
||||
type Msg
|
||||
= SharedMsg SharedMsg
|
||||
| MenuClicked
|
||||
|
||||
|
||||
type alias Data =
|
||||
()
|
||||
|
||||
|
||||
type SharedMsg
|
||||
= NoOp
|
||||
|
||||
|
||||
type alias Model =
|
||||
{ showMenu : Bool
|
||||
}
|
||||
|
||||
|
||||
init :
|
||||
Pages.Flags.Flags
|
||||
->
|
||||
Maybe
|
||||
{ path :
|
||||
{ path : UrlPath
|
||||
, query : Maybe String
|
||||
, fragment : Maybe String
|
||||
}
|
||||
, metadata : route
|
||||
, pageUrl : Maybe PageUrl
|
||||
}
|
||||
-> ( Model, Effect Msg )
|
||||
init flags maybePagePath =
|
||||
( { showMenu = False }
|
||||
, Effect.none
|
||||
)
|
||||
|
||||
|
||||
update : Msg -> Model -> ( Model, Effect Msg )
|
||||
update msg model =
|
||||
case msg of
|
||||
SharedMsg globalMsg ->
|
||||
( model, Effect.none )
|
||||
|
||||
MenuClicked ->
|
||||
( { model | showMenu = not model.showMenu }, Effect.none )
|
||||
|
||||
|
||||
subscriptions : UrlPath -> Model -> Sub Msg
|
||||
subscriptions _ _ =
|
||||
Sub.none
|
||||
|
||||
|
||||
data : BackendTask FatalError Data
|
||||
data =
|
||||
BackendTask.succeed ()
|
||||
|
||||
|
||||
view :
|
||||
Data
|
||||
->
|
||||
{ path : UrlPath
|
||||
, route : Maybe Route
|
||||
}
|
||||
-> Model
|
||||
-> (Msg -> msg)
|
||||
-> View msg
|
||||
-> { body : List (Html msg), title : String }
|
||||
view tableOfContents page model toMsg pageView =
|
||||
{ body =
|
||||
[
|
||||
-- ((View.Header.view ToggleMobileMenu 123 page.path
|
||||
-- |> Html.Styled.map toMsg
|
||||
-- )
|
||||
-- :: TableOfContents.view model.showMobileMenu False Nothing tableOfContents
|
||||
pageView.body
|
||||
-- )
|
||||
|> Html.Styled.div []
|
||||
|> Html.Styled.toUnstyled
|
||||
]
|
||||
, title = pageView.title
|
||||
}
|
||||
-- view sharedData page model toMsg pageView =
|
||||
-- { body =
|
||||
-- [ Html.Styled.nav []
|
||||
-- [ Html.Styled.button
|
||||
-- [ Html.Styled.Events.onClick MenuClicked ]
|
||||
-- [ Html.Styled.text
|
||||
-- (if model.showMenu then
|
||||
-- "Close Menu"
|
||||
|
||||
-- else
|
||||
-- "Open Menu"
|
||||
-- )
|
||||
-- ]
|
||||
-- , if model.showMenu then
|
||||
-- Html.Styled.ul []
|
||||
-- [ Html.Styled.li [] [ Html.Styled.text "Menu item 1" ]
|
||||
-- , Html.Styled.li [] [ Html.Styled.text "Menu item 2" ]
|
||||
-- ]
|
||||
|
||||
-- else
|
||||
-- Html.Styled.text ""
|
||||
-- ]
|
||||
-- |> Html.Styled.map toMsg
|
||||
-- , Html.Styled.main_ [] pageView.body
|
||||
-- ]
|
||||
-- , title = pageView.title
|
||||
-- }
|
21
app/Site.elm
Normal file
21
app/Site.elm
Normal file
@ -0,0 +1,21 @@
|
||||
module Site exposing (config)
|
||||
|
||||
import BackendTask exposing (BackendTask)
|
||||
import FatalError exposing (FatalError)
|
||||
import Head
|
||||
import SiteConfig exposing (SiteConfig)
|
||||
|
||||
|
||||
config : SiteConfig
|
||||
config =
|
||||
{ canonicalUrl = "https://elm-pages.com"
|
||||
, head = head
|
||||
}
|
||||
|
||||
|
||||
head : BackendTask FatalError (List Head.Tag)
|
||||
head =
|
||||
[ Head.metaName "viewport" (Head.raw "width=device-width,initial-scale=1")
|
||||
, Head.sitemapLink "/sitemap.xml"
|
||||
]
|
||||
|> BackendTask.succeed
|
24
app/View.elm
Normal file
24
app/View.elm
Normal file
@ -0,0 +1,24 @@
|
||||
module View exposing (View, map)
|
||||
|
||||
{-|
|
||||
|
||||
@docs View, map
|
||||
|
||||
-}
|
||||
|
||||
import Html.Styled as Html exposing (Html)
|
||||
|
||||
|
||||
{-| -}
|
||||
type alias View msg =
|
||||
{ title : String
|
||||
, body : List (Html msg)
|
||||
}
|
||||
|
||||
|
||||
{-| -}
|
||||
map : (msg1 -> msg2) -> View msg1 -> View msg2
|
||||
map fn doc =
|
||||
{ title = doc.title
|
||||
, body = List.map (Html.map fn) doc.body
|
||||
}
|
18
codegen/elm.codegen.json
Normal file
18
codegen/elm.codegen.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"elm-codegen-version": "0.2.0",
|
||||
"codegen-helpers": {
|
||||
"packages": {
|
||||
"elm/core": "1.0.5",
|
||||
"dillonkearns/elm-form": "3.0.0",
|
||||
"elm/html": "1.0.0",
|
||||
"rtfeldman/elm-css": "18.0.0",
|
||||
"dillonkearns/elm-pages": "10.0.0",
|
||||
"elm/json": "1.1.3"
|
||||
},
|
||||
"local": [
|
||||
".elm-pages/",
|
||||
"app/",
|
||||
"src/"
|
||||
]
|
||||
}
|
||||
}
|
6
content/blog/a.md
Normal file
6
content/blog/a.md
Normal file
@ -0,0 +1,6 @@
|
||||
---
|
||||
title: hello world
|
||||
description: just hello
|
||||
published: "2023-10-24"
|
||||
---
|
||||
helloworld
|
3
custom-backend-task.ts
Normal file
3
custom-backend-task.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export async function hello(name) {
|
||||
return `Hello ${name}!`;
|
||||
}
|
18
elm-pages.config.mjs
Normal file
18
elm-pages.config.mjs
Normal file
@ -0,0 +1,18 @@
|
||||
import { defineConfig } from "vite";
|
||||
import adapter from "elm-pages/adapter/netlify.js";
|
||||
|
||||
export default {
|
||||
vite: defineConfig({}),
|
||||
adapter,
|
||||
headTagsTemplate(context) {
|
||||
return `
|
||||
<link rel="stylesheet" href="/style.css" />
|
||||
<meta name="generator" content="elm-pages v${context.cliVersion}" />
|
||||
`;
|
||||
},
|
||||
preloadTagForFile(file) {
|
||||
// add preload directives for JS assets and font assets, etc., skip for CSS files
|
||||
// this function will be called with each file that is procesed by Vite, including any files in your headTagsTemplate in your config
|
||||
return !file.endsWith(".css");
|
||||
},
|
||||
};
|
6
elm-tooling.json
Normal file
6
elm-tooling.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"tools": {
|
||||
"elm": "0.19.1",
|
||||
"elm-format": "0.8.5"
|
||||
}
|
||||
}
|
70
elm.json
Normal file
70
elm.json
Normal file
@ -0,0 +1,70 @@
|
||||
{
|
||||
"type": "application",
|
||||
"source-directories": [
|
||||
"src",
|
||||
"app",
|
||||
".elm-pages"
|
||||
],
|
||||
"elm-version": "0.19.1",
|
||||
"dependencies": {
|
||||
"direct": {
|
||||
"avh4/elm-color": "1.0.0",
|
||||
"danfishgold/base64-bytes": "1.1.0",
|
||||
"danyx23/elm-mimetype": "4.0.1",
|
||||
"dillonkearns/elm-bcp47-language-tag": "1.0.1",
|
||||
"dillonkearns/elm-form": "3.0.0",
|
||||
"dillonkearns/elm-markdown": "7.0.1",
|
||||
"dillonkearns/elm-oembed": "1.0.0",
|
||||
"dillonkearns/elm-pages": "10.0.1",
|
||||
"elm/browser": "1.0.2",
|
||||
"elm/bytes": "1.0.8",
|
||||
"elm/core": "1.0.5",
|
||||
"elm/html": "1.0.0",
|
||||
"elm/http": "2.0.0",
|
||||
"elm/json": "1.1.3",
|
||||
"elm/parser": "1.1.0",
|
||||
"elm/regex": "1.0.0",
|
||||
"elm/time": "1.0.0",
|
||||
"elm/url": "1.0.0",
|
||||
"elm/virtual-dom": "1.0.3",
|
||||
"elm-community/dict-extra": "2.4.0",
|
||||
"elm-community/json-extra": "4.3.0",
|
||||
"elm-community/list-extra": "8.7.0",
|
||||
"elm-community/result-extra": "2.4.0",
|
||||
"jluckyiv/elm-utc-date-strings": "1.0.0",
|
||||
"justinmimbs/date": "4.0.1",
|
||||
"matheus23/elm-default-tailwind-modules": "4.0.1",
|
||||
"mdgriffith/elm-codegen": "3.0.0",
|
||||
"miniBill/elm-codec": "2.0.0",
|
||||
"noahzgordon/elm-color-extra": "1.0.2",
|
||||
"pablohirafuji/elm-syntax-highlight": "3.5.0",
|
||||
"robinheghan/fnv1a": "1.0.0",
|
||||
"rtfeldman/elm-css": "18.0.0",
|
||||
"the-sett/elm-syntax-dsl": "6.0.2",
|
||||
"turboMaCk/non-empty-list-alias": "1.3.1",
|
||||
"vito/elm-ansi": "10.0.1"
|
||||
},
|
||||
"indirect": {
|
||||
"Chadtech/elm-bool-extra": "2.4.2",
|
||||
"dillonkearns/elm-cli-options-parser": "3.2.0",
|
||||
"dillonkearns/elm-date-or-date-time": "2.0.0",
|
||||
"elm/file": "1.0.5",
|
||||
"elm/random": "1.0.0",
|
||||
"elm-community/basics-extra": "4.1.0",
|
||||
"elm-community/maybe-extra": "5.3.0",
|
||||
"fredcy/elm-parseint": "2.0.1",
|
||||
"matheus23/elm-tailwind-modules-base": "1.0.0",
|
||||
"miniBill/elm-unicode": "1.0.3",
|
||||
"robinheghan/murmur3": "1.0.0",
|
||||
"rtfeldman/elm-hex": "1.0.0",
|
||||
"rtfeldman/elm-iso8601-date-strings": "1.1.4",
|
||||
"stil4m/elm-syntax": "7.2.9",
|
||||
"stil4m/structured-writer": "1.0.3",
|
||||
"the-sett/elm-pretty-printer": "3.0.0"
|
||||
}
|
||||
},
|
||||
"test-dependencies": {
|
||||
"direct": {},
|
||||
"indirect": {}
|
||||
}
|
||||
}
|
15
index.ts
Normal file
15
index.ts
Normal file
@ -0,0 +1,15 @@
|
||||
type ElmPagesInit = {
|
||||
load: (elmLoaded: Promise<unknown>) => Promise<void>;
|
||||
flags: unknown;
|
||||
};
|
||||
|
||||
const config: ElmPagesInit = {
|
||||
load: async function (elmLoaded) {
|
||||
await elmLoaded;
|
||||
},
|
||||
flags: function () {
|
||||
return "You can decode this in Shared.elm using Json.Decode.string!";
|
||||
},
|
||||
};
|
||||
|
||||
export default config;
|
13
netlify.toml
Normal file
13
netlify.toml
Normal file
@ -0,0 +1,13 @@
|
||||
[build]
|
||||
functions = "functions/"
|
||||
publish = "dist/"
|
||||
command = "mkdir bin && export PATH=\"/opt/build/repo/bin:$PATH\" && echo $PATH && curl https://static.lamdera.com/bin/linux/lamdera -o bin/lamdera && chmod a+x bin/lamdera && export ELM_HOME=\"$NETLIFY_BUILD_BASE/cache/elm\" && npm install && npm run build"
|
||||
|
||||
[dev]
|
||||
command = "npm start"
|
||||
targetPort = 1234
|
||||
autoLaunch = true
|
||||
framework = "#custom"
|
||||
|
||||
[functions]
|
||||
node_bundler = "esbuild"
|
9825
package-lock.json
generated
Normal file
9825
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
20
package.json
Normal file
20
package.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "elm-pages-app",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"postinstall": "elm-tooling install",
|
||||
"start": "elm-pages dev",
|
||||
"build": "elm-pages build"
|
||||
},
|
||||
"devDependencies": {
|
||||
"elm-codegen": "^0.3.0",
|
||||
"elm-optimize-level-2": "^0.3.5",
|
||||
"elm-pages": "^3.0.3",
|
||||
"elm-review": "^2.10.2",
|
||||
"elm-tooling": "^1.14.0",
|
||||
"vite": "^4.3.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@netlify/functions": "^1.4.0"
|
||||
}
|
||||
}
|
BIN
public/favicon.ico
Normal file
BIN
public/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 450 B |
3
script/custom-backend-task.ts
Normal file
3
script/custom-backend-task.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export async function hello(name) {
|
||||
return `Hello ${name}!`;
|
||||
}
|
61
script/elm.json
Normal file
61
script/elm.json
Normal file
@ -0,0 +1,61 @@
|
||||
{
|
||||
"type": "application",
|
||||
"source-directories": [
|
||||
"src",
|
||||
"../codegen"
|
||||
],
|
||||
"elm-version": "0.19.1",
|
||||
"dependencies": {
|
||||
"direct": {
|
||||
"dillonkearns/elm-cli-options-parser": "3.2.0",
|
||||
"dillonkearns/elm-pages": "10.0.0",
|
||||
"elm/bytes": "1.0.8",
|
||||
"elm/core": "1.0.5",
|
||||
"elm/html": "1.0.0",
|
||||
"elm/json": "1.1.3",
|
||||
"mdgriffith/elm-codegen": "3.0.0"
|
||||
},
|
||||
"indirect": {
|
||||
"Chadtech/elm-bool-extra": "2.4.2",
|
||||
"avh4/elm-color": "1.0.0",
|
||||
"danfishgold/base64-bytes": "1.1.0",
|
||||
"danyx23/elm-mimetype": "4.0.1",
|
||||
"dillonkearns/elm-bcp47-language-tag": "1.0.1",
|
||||
"dillonkearns/elm-date-or-date-time": "2.0.0",
|
||||
"dillonkearns/elm-form": "3.0.0",
|
||||
"elm/browser": "1.0.2",
|
||||
"elm/file": "1.0.5",
|
||||
"elm/http": "2.0.0",
|
||||
"elm/parser": "1.1.0",
|
||||
"elm/random": "1.0.0",
|
||||
"elm/regex": "1.0.0",
|
||||
"elm/time": "1.0.0",
|
||||
"elm/url": "1.0.0",
|
||||
"elm/virtual-dom": "1.0.3",
|
||||
"elm-community/basics-extra": "4.1.0",
|
||||
"elm-community/list-extra": "8.7.0",
|
||||
"elm-community/maybe-extra": "5.3.0",
|
||||
"fredcy/elm-parseint": "2.0.1",
|
||||
"jluckyiv/elm-utc-date-strings": "1.0.0",
|
||||
"justinmimbs/date": "4.0.1",
|
||||
"miniBill/elm-codec": "2.0.0",
|
||||
"miniBill/elm-unicode": "1.0.3",
|
||||
"noahzgordon/elm-color-extra": "1.0.2",
|
||||
"robinheghan/fnv1a": "1.0.0",
|
||||
"robinheghan/murmur3": "1.0.0",
|
||||
"rtfeldman/elm-css": "18.0.0",
|
||||
"rtfeldman/elm-hex": "1.0.0",
|
||||
"rtfeldman/elm-iso8601-date-strings": "1.1.4",
|
||||
"stil4m/elm-syntax": "7.2.9",
|
||||
"stil4m/structured-writer": "1.0.3",
|
||||
"the-sett/elm-pretty-printer": "3.0.0",
|
||||
"the-sett/elm-syntax-dsl": "6.0.2",
|
||||
"turboMaCk/non-empty-list-alias": "1.3.1",
|
||||
"vito/elm-ansi": "10.0.1"
|
||||
}
|
||||
},
|
||||
"test-dependencies": {
|
||||
"direct": {},
|
||||
"indirect": {}
|
||||
}
|
||||
}
|
312
script/src/AddRoute.elm
Normal file
312
script/src/AddRoute.elm
Normal file
@ -0,0 +1,312 @@
|
||||
module AddRoute exposing (run)
|
||||
|
||||
import BackendTask
|
||||
import Cli.Option as Option
|
||||
import Cli.OptionsParser as OptionsParser
|
||||
import Cli.Program as Program
|
||||
import Elm
|
||||
import Elm.Annotation as Type
|
||||
import Elm.Case
|
||||
import Elm.Declare
|
||||
import Elm.Let
|
||||
import Elm.Op
|
||||
import Gen.BackendTask
|
||||
import Gen.Effect as Effect
|
||||
import Gen.FatalError
|
||||
import Gen.Form as Form
|
||||
import Gen.Form.FieldView as FieldView
|
||||
import Gen.Html as Html
|
||||
import Gen.Html.Attributes as Attr
|
||||
import Gen.Json.Encode
|
||||
import Gen.List
|
||||
import Gen.Maybe
|
||||
import Gen.Pages.Form as PagesForm
|
||||
import Gen.Pages.Script
|
||||
import Gen.Server.Request as Request
|
||||
import Gen.Server.Response as Response
|
||||
import Gen.View
|
||||
import Pages.Script as Script exposing (Script)
|
||||
import Scaffold.Form
|
||||
import Scaffold.Route exposing (Type(..))
|
||||
|
||||
|
||||
type alias CliOptions =
|
||||
{ moduleName : List String
|
||||
, fields : List ( String, Scaffold.Form.Kind )
|
||||
}
|
||||
|
||||
|
||||
run : Script
|
||||
run =
|
||||
Script.withCliOptions program
|
||||
(\cliOptions ->
|
||||
cliOptions
|
||||
|> createFile
|
||||
|> Script.writeFile
|
||||
|> BackendTask.allowFatal
|
||||
)
|
||||
|
||||
|
||||
program : Program.Config CliOptions
|
||||
program =
|
||||
Program.config
|
||||
|> Program.add
|
||||
(OptionsParser.build CliOptions
|
||||
|> OptionsParser.with (Option.requiredPositionalArg "module" |> Scaffold.Route.moduleNameCliArg)
|
||||
|> OptionsParser.withRestArgs Scaffold.Form.restArgsParser
|
||||
)
|
||||
|
||||
|
||||
createFile : CliOptions -> { path : String, body : String }
|
||||
createFile { moduleName, fields } =
|
||||
let
|
||||
formHelpers :
|
||||
Maybe
|
||||
{ formHandlers : Elm.Expression
|
||||
, form : Elm.Expression
|
||||
, declarations : List Elm.Declaration
|
||||
}
|
||||
formHelpers =
|
||||
Scaffold.Form.provide
|
||||
{ fields = fields
|
||||
, elmCssView = False
|
||||
, view =
|
||||
\{ formState, params } ->
|
||||
Elm.Let.letIn
|
||||
(\fieldView ->
|
||||
Elm.list
|
||||
((params
|
||||
|> List.map
|
||||
(\{ name, kind, param } ->
|
||||
fieldView (Elm.string name) param
|
||||
)
|
||||
)
|
||||
++ [ Elm.ifThen formState.submitting
|
||||
(Html.button
|
||||
[ Attr.disabled True
|
||||
]
|
||||
[ Html.text "Submitting..."
|
||||
]
|
||||
)
|
||||
(Html.button []
|
||||
[ Html.text "Submit"
|
||||
]
|
||||
)
|
||||
]
|
||||
)
|
||||
)
|
||||
|> Elm.Let.fn2 "fieldView"
|
||||
( "label", Type.string |> Just )
|
||||
( "field", Nothing )
|
||||
(\label field ->
|
||||
Html.div []
|
||||
[ Html.label []
|
||||
[ Html.call_.text (Elm.Op.append label (Elm.string " "))
|
||||
, field |> FieldView.input []
|
||||
, errorsView.call formState.errors field
|
||||
]
|
||||
]
|
||||
)
|
||||
|> Elm.Let.toExpression
|
||||
}
|
||||
in
|
||||
Scaffold.Route.serverRender
|
||||
{ moduleName = moduleName
|
||||
, action =
|
||||
( Alias
|
||||
(Type.record
|
||||
(case formHelpers of
|
||||
Just _ ->
|
||||
[ ( "errors", Type.namedWith [ "Form" ] "ServerResponse" [ Type.string ] )
|
||||
]
|
||||
|
||||
Nothing ->
|
||||
[]
|
||||
)
|
||||
)
|
||||
, \routeParams request ->
|
||||
formHelpers
|
||||
|> Maybe.map
|
||||
(\justFormHelp ->
|
||||
Request.formData justFormHelp.formHandlers request
|
||||
|> Gen.Maybe.call_.map
|
||||
(Elm.fn ( "formData", Nothing )
|
||||
(\formData ->
|
||||
Elm.Case.tuple formData
|
||||
"response"
|
||||
"parsedForm"
|
||||
(\response parsedForm ->
|
||||
Elm.Case.custom parsedForm
|
||||
Type.int
|
||||
[ Elm.Case.branch1 "Form.Valid"
|
||||
( "validatedForm", Type.int )
|
||||
(\validatedForm ->
|
||||
Elm.Case.custom validatedForm
|
||||
Type.int
|
||||
[ Elm.Case.branch1 "Action"
|
||||
( "parsed", Type.int )
|
||||
(\parsed ->
|
||||
Scaffold.Form.recordEncoder parsed fields
|
||||
|> Gen.Json.Encode.encode 2
|
||||
|> Gen.Pages.Script.call_.log
|
||||
|> Gen.BackendTask.call_.map
|
||||
(Elm.fn ( "_", Nothing )
|
||||
(\_ ->
|
||||
Response.render
|
||||
(Elm.record
|
||||
[ ( "errors", response )
|
||||
]
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
]
|
||||
)
|
||||
, Elm.Case.branch2 "Form.Invalid"
|
||||
( "parsed", Type.int )
|
||||
( "error", Type.int )
|
||||
(\_ _ ->
|
||||
"Form validations did not succeed!"
|
||||
|> Gen.Pages.Script.log
|
||||
|> Gen.BackendTask.call_.map
|
||||
(Elm.fn ( "_", Nothing )
|
||||
(\_ ->
|
||||
Response.render
|
||||
(Elm.record
|
||||
[ ( "errors", response )
|
||||
]
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
]
|
||||
)
|
||||
)
|
||||
)
|
||||
|> Gen.Maybe.withDefault
|
||||
(Gen.BackendTask.fail
|
||||
(Gen.FatalError.fromString "Expected form post")
|
||||
)
|
||||
)
|
||||
|> Maybe.withDefault
|
||||
(Gen.BackendTask.succeed
|
||||
(Response.render
|
||||
(Elm.record [])
|
||||
)
|
||||
)
|
||||
)
|
||||
, data =
|
||||
( Alias (Type.record [])
|
||||
, \routeParams request ->
|
||||
Gen.BackendTask.succeed
|
||||
(Response.render
|
||||
(Elm.record [])
|
||||
)
|
||||
)
|
||||
, head = \app -> Elm.list []
|
||||
}
|
||||
|> Scaffold.Route.addDeclarations
|
||||
(formHelpers
|
||||
|> Maybe.map .declarations
|
||||
|> Maybe.map ((::) errorsView.declaration)
|
||||
|> Maybe.withDefault []
|
||||
)
|
||||
|> Scaffold.Route.buildWithLocalState
|
||||
{ view =
|
||||
\{ shared, model, app } ->
|
||||
Gen.View.make_.view
|
||||
{ title = moduleName |> String.join "." |> Elm.string
|
||||
, body =
|
||||
Elm.list
|
||||
(case formHelpers of
|
||||
Just justFormHelp ->
|
||||
[ Html.h2 [] [ Html.text "Form" ]
|
||||
, justFormHelp.form
|
||||
|> PagesForm.call_.renderHtml
|
||||
(Elm.list [])
|
||||
(Form.options "form"
|
||||
|> Form.withServerResponse
|
||||
(app
|
||||
|> Elm.get "action"
|
||||
|> Gen.Maybe.map (Elm.get "errors")
|
||||
)
|
||||
)
|
||||
app
|
||||
]
|
||||
|
||||
Nothing ->
|
||||
[ Html.h2 [] [ Html.text "New Page" ]
|
||||
]
|
||||
)
|
||||
}
|
||||
, update =
|
||||
\{ shared, app, msg, model } ->
|
||||
Elm.Case.custom msg
|
||||
(Type.named [] "Msg")
|
||||
[ Elm.Case.branch0 "NoOp"
|
||||
(Elm.tuple model
|
||||
Effect.none
|
||||
)
|
||||
]
|
||||
, init =
|
||||
\{ shared, app } ->
|
||||
Elm.tuple (Elm.record []) Effect.none
|
||||
, subscriptions =
|
||||
\{ routeParams, path, shared, model } ->
|
||||
Elm.val "Sub.none"
|
||||
, model =
|
||||
Alias (Type.record [])
|
||||
, msg =
|
||||
Custom [ Elm.variant "NoOp" ]
|
||||
}
|
||||
|
||||
|
||||
errorsView :
|
||||
{ declaration : Elm.Declaration
|
||||
, call : Elm.Expression -> Elm.Expression -> Elm.Expression
|
||||
, callFrom : List String -> Elm.Expression -> Elm.Expression -> Elm.Expression
|
||||
, value : List String -> Elm.Expression
|
||||
}
|
||||
errorsView =
|
||||
Elm.Declare.fn2 "errorsView"
|
||||
( "errors", Type.namedWith [ "Form" ] "Errors" [ Type.string ] |> Just )
|
||||
( "field"
|
||||
, Type.namedWith [ "Form", "Validation" ]
|
||||
"Field"
|
||||
[ Type.string
|
||||
, Type.var "parsed"
|
||||
, Type.var "kind"
|
||||
]
|
||||
|> Just
|
||||
)
|
||||
(\errors field ->
|
||||
Elm.ifThen
|
||||
(Gen.List.call_.isEmpty (Form.errorsForField field errors))
|
||||
(Html.div [] [])
|
||||
(Html.div
|
||||
[]
|
||||
[ Html.call_.ul (Elm.list [])
|
||||
(Gen.List.call_.map
|
||||
(Elm.fn ( "error", Nothing )
|
||||
(\error ->
|
||||
Html.li
|
||||
[ Attr.style "color" "red"
|
||||
]
|
||||
[ Html.call_.text error
|
||||
]
|
||||
)
|
||||
)
|
||||
(Form.errorsForField field errors)
|
||||
)
|
||||
]
|
||||
)
|
||||
|> Elm.withType
|
||||
(Type.namedWith [ "Html" ]
|
||||
"Html"
|
||||
[ Type.namedWith
|
||||
[ "PagesMsg" ]
|
||||
"PagesMsg"
|
||||
[ Type.named [] "Msg" ]
|
||||
]
|
||||
)
|
||||
)
|
42
script/src/Stars.elm
Normal file
42
script/src/Stars.elm
Normal file
@ -0,0 +1,42 @@
|
||||
module Stars exposing (run)
|
||||
|
||||
import BackendTask exposing (BackendTask)
|
||||
import BackendTask.Http
|
||||
import Cli.Option as Option
|
||||
import Cli.OptionsParser as OptionsParser
|
||||
import Cli.Program as Program
|
||||
import Json.Decode as Decode
|
||||
import Pages.Script as Script exposing (Script)
|
||||
|
||||
|
||||
run : Script
|
||||
run =
|
||||
Script.withCliOptions program
|
||||
(\{ username, repo } ->
|
||||
BackendTask.Http.getJson
|
||||
("https://api.github.com/repos/dillonkearns/" ++ repo)
|
||||
(Decode.field "stargazers_count" Decode.int)
|
||||
|> BackendTask.allowFatal
|
||||
|> BackendTask.andThen
|
||||
(\stars ->
|
||||
Script.log (String.fromInt stars)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
type alias CliOptions =
|
||||
{ username : String
|
||||
, repo : String
|
||||
}
|
||||
|
||||
|
||||
program : Program.Config CliOptions
|
||||
program =
|
||||
Program.config
|
||||
|> Program.add
|
||||
(OptionsParser.build CliOptions
|
||||
|> OptionsParser.with
|
||||
(Option.optionalKeywordArg "username" |> Option.withDefault "dillonkearns")
|
||||
|> OptionsParser.with
|
||||
(Option.optionalKeywordArg "repo" |> Option.withDefault "elm-pages")
|
||||
)
|
0
src/.gitkeep
Normal file
0
src/.gitkeep
Normal file
107
src/Article.elm
Normal file
107
src/Article.elm
Normal file
@ -0,0 +1,107 @@
|
||||
module Article exposing (..)
|
||||
|
||||
import BackendTask
|
||||
import BackendTask.File as File
|
||||
import BackendTask.Glob as Glob
|
||||
-- import Cloudinary
|
||||
import Date exposing (Date)
|
||||
import FatalError exposing (FatalError)
|
||||
import Json.Decode as Decode exposing (Decoder)
|
||||
import Pages.Url exposing (Url)
|
||||
import Route
|
||||
-- import UnsplashImage
|
||||
|
||||
|
||||
type alias BlogPost =
|
||||
{ filePath : String
|
||||
, slug : String
|
||||
}
|
||||
|
||||
|
||||
blogPostsGlob : BackendTask.BackendTask error (List { filePath : String, slug : String })
|
||||
blogPostsGlob =
|
||||
Glob.succeed BlogPost
|
||||
|> Glob.captureFilePath
|
||||
|> Glob.match (Glob.literal "content/blog/")
|
||||
|> Glob.capture Glob.wildcard
|
||||
|> Glob.match (Glob.literal ".md")
|
||||
|> Glob.toBackendTask
|
||||
|
||||
|
||||
allMetadata :
|
||||
BackendTask.BackendTask
|
||||
{ fatal : FatalError, recoverable : File.FileReadError Decode.Error }
|
||||
(List ( Route.Route, ArticleMetadata ))
|
||||
allMetadata =
|
||||
blogPostsGlob
|
||||
|> BackendTask.map
|
||||
(\paths ->
|
||||
paths
|
||||
|> List.map
|
||||
(\{ filePath, slug } ->
|
||||
BackendTask.map2 Tuple.pair
|
||||
(BackendTask.succeed <| Route.Blog__Slug_ { slug = slug })
|
||||
(File.onlyFrontmatter frontmatterDecoder filePath)
|
||||
)
|
||||
)
|
||||
|> BackendTask.resolve
|
||||
|> BackendTask.map
|
||||
(\articles ->
|
||||
articles
|
||||
|> List.filterMap
|
||||
(\( route, metadata ) ->
|
||||
if metadata.draft then
|
||||
Nothing
|
||||
|
||||
else
|
||||
Just ( route, metadata )
|
||||
)
|
||||
)
|
||||
|> BackendTask.map
|
||||
(List.sortBy
|
||||
(\( route, metadata ) -> -(Date.toRataDie metadata.published))
|
||||
)
|
||||
|
||||
|
||||
type alias ArticleMetadata =
|
||||
{ title : String
|
||||
, description : String
|
||||
, published : Date
|
||||
-- , image : Url
|
||||
, draft : Bool
|
||||
}
|
||||
|
||||
|
||||
frontmatterDecoder : Decoder ArticleMetadata
|
||||
frontmatterDecoder =
|
||||
Decode.map4 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.oneOf
|
||||
-- [ Decode.field "image" imageDecoder
|
||||
-- , Decode.field "unsplash" UnsplashImage.decoder |> Decode.map UnsplashImage.imagePath
|
||||
-- ]
|
||||
-- )
|
||||
(Decode.field "draft" Decode.bool
|
||||
|> Decode.maybe
|
||||
|> Decode.map (Maybe.withDefault False)
|
||||
)
|
||||
|
||||
|
||||
-- imageDecoder : Decoder Url
|
||||
-- imageDecoder =
|
||||
-- Decode.string
|
||||
-- |> Decode.map (\cloudinaryAsset -> Cloudinary.url cloudinaryAsset Nothing 800)
|
17
src/Ellie.elm
Normal file
17
src/Ellie.elm
Normal file
@ -0,0 +1,17 @@
|
||||
module Ellie exposing (outputTabElmCss)
|
||||
|
||||
import Html.Styled exposing (Html)
|
||||
import Html.Styled.Attributes as StyledAttr
|
||||
|
||||
|
||||
outputTabElmCss : String -> Html msg
|
||||
outputTabElmCss ellieId =
|
||||
Html.Styled.iframe
|
||||
[ StyledAttr.src <| "https://ellie-app.com/embed/" ++ ellieId ++ "?panel=output"
|
||||
, StyledAttr.style "width" "100%"
|
||||
, StyledAttr.style "height" "400px"
|
||||
, StyledAttr.style "border" "0"
|
||||
, StyledAttr.style "overflow" "hidden"
|
||||
, StyledAttr.attribute "sandbox" "allow-modals allow-forms allow-popups allow-scripts allow-same-origin"
|
||||
]
|
||||
[]
|
38
src/Link.elm
Normal file
38
src/Link.elm
Normal file
@ -0,0 +1,38 @@
|
||||
module Link exposing (Link, external, internal, link)
|
||||
|
||||
import Html.Styled exposing (Attribute, Html, a)
|
||||
import Html.Styled.Attributes as Attr
|
||||
import Route exposing (Route)
|
||||
|
||||
|
||||
external : String -> Link
|
||||
external =
|
||||
ExternalLink
|
||||
|
||||
|
||||
internal : Route -> Link
|
||||
internal =
|
||||
RouteLink
|
||||
|
||||
|
||||
type Link
|
||||
= RouteLink Route
|
||||
| ExternalLink String
|
||||
|
||||
|
||||
link : Link -> List (Attribute msg) -> List (Html msg) -> Html msg
|
||||
link link_ attrs children =
|
||||
case link_ of
|
||||
RouteLink route ->
|
||||
Route.toLink
|
||||
(\anchorAttrs ->
|
||||
a
|
||||
(List.map Attr.fromUnstyled anchorAttrs ++ attrs)
|
||||
children
|
||||
)
|
||||
route
|
||||
|
||||
ExternalLink string ->
|
||||
a
|
||||
(Attr.href string :: attrs)
|
||||
children
|
236
src/MarkdownCodec.elm
Normal file
236
src/MarkdownCodec.elm
Normal file
@ -0,0 +1,236 @@
|
||||
module MarkdownCodec exposing (isPlaceholder, noteTitle, titleAndDescription, withFrontmatter, withoutFrontmatter)
|
||||
|
||||
import BackendTask exposing (BackendTask)
|
||||
import BackendTask.File as StaticFile
|
||||
import FatalError exposing (FatalError)
|
||||
import Json.Decode as Decode exposing (Decoder)
|
||||
import Json.Decode.Extra
|
||||
import List.Extra
|
||||
import Markdown.Block as Block exposing (Block)
|
||||
import Markdown.Parser
|
||||
import Markdown.Renderer
|
||||
import MarkdownExtra
|
||||
|
||||
|
||||
isPlaceholder : String -> BackendTask FatalError (Maybe ())
|
||||
isPlaceholder filePath =
|
||||
filePath
|
||||
|> StaticFile.bodyWithoutFrontmatter
|
||||
|> BackendTask.allowFatal
|
||||
|> BackendTask.andThen
|
||||
(\rawContent ->
|
||||
Markdown.Parser.parse rawContent
|
||||
|> Result.mapError (\_ -> FatalError.fromString "Markdown error")
|
||||
|> Result.map
|
||||
(\blocks ->
|
||||
List.any
|
||||
(\block ->
|
||||
case block of
|
||||
Block.Heading _ inlines ->
|
||||
False
|
||||
|
||||
_ ->
|
||||
True
|
||||
)
|
||||
blocks
|
||||
|> not
|
||||
)
|
||||
|> BackendTask.fromResult
|
||||
)
|
||||
|> BackendTask.map
|
||||
(\bool ->
|
||||
if bool then
|
||||
Nothing
|
||||
|
||||
else
|
||||
Just ()
|
||||
)
|
||||
|
||||
|
||||
noteTitle : String -> BackendTask FatalError String
|
||||
noteTitle filePath =
|
||||
titleFromFrontmatter filePath
|
||||
|> BackendTask.andThen
|
||||
(\maybeTitle ->
|
||||
maybeTitle
|
||||
|> Maybe.map BackendTask.succeed
|
||||
|> Maybe.withDefault
|
||||
(StaticFile.bodyWithoutFrontmatter filePath
|
||||
|> BackendTask.allowFatal
|
||||
|> BackendTask.andThen
|
||||
(\rawContent ->
|
||||
Markdown.Parser.parse rawContent
|
||||
|> Result.mapError (\_ -> FatalError.fromString "Markdown error")
|
||||
|> Result.map
|
||||
(\blocks ->
|
||||
List.Extra.findMap
|
||||
(\block ->
|
||||
case block of
|
||||
Block.Heading Block.H1 inlines ->
|
||||
Just (Block.extractInlineText inlines)
|
||||
|
||||
_ ->
|
||||
Nothing
|
||||
)
|
||||
blocks
|
||||
)
|
||||
|> Result.andThen
|
||||
(Result.fromMaybe <|
|
||||
FatalError.fromString ("Expected to find an H1 heading for page " ++ filePath)
|
||||
)
|
||||
|> BackendTask.fromResult
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
titleAndDescription : String -> BackendTask FatalError { title : String, description : String }
|
||||
titleAndDescription filePath =
|
||||
filePath
|
||||
|> StaticFile.onlyFrontmatter
|
||||
(Decode.map2 (\title description -> { title = title, description = description })
|
||||
(Json.Decode.Extra.optionalField "title" Decode.string)
|
||||
(Json.Decode.Extra.optionalField "description" Decode.string)
|
||||
)
|
||||
|> BackendTask.allowFatal
|
||||
|> BackendTask.andThen
|
||||
(\metadata ->
|
||||
Maybe.map2 (\title description -> { title = title, description = description })
|
||||
metadata.title
|
||||
metadata.description
|
||||
|> Maybe.map BackendTask.succeed
|
||||
|> Maybe.withDefault
|
||||
(StaticFile.bodyWithoutFrontmatter filePath
|
||||
|> BackendTask.allowFatal
|
||||
|> BackendTask.andThen
|
||||
(\rawContent ->
|
||||
Markdown.Parser.parse rawContent
|
||||
|> Result.mapError (\_ -> FatalError.fromString "Markdown error")
|
||||
|> Result.map
|
||||
(\blocks ->
|
||||
Maybe.map
|
||||
(\title ->
|
||||
{ title = title
|
||||
, description =
|
||||
case metadata.description of
|
||||
Just description ->
|
||||
description
|
||||
|
||||
Nothing ->
|
||||
findDescription blocks
|
||||
}
|
||||
)
|
||||
(case metadata.title of
|
||||
Just title ->
|
||||
Just title
|
||||
|
||||
Nothing ->
|
||||
findH1 blocks
|
||||
)
|
||||
)
|
||||
|> Result.andThen (Result.fromMaybe <| FatalError.fromString <| "Expected to find an H1 heading for page " ++ filePath)
|
||||
|> BackendTask.fromResult
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
findH1 : List Block -> Maybe String
|
||||
findH1 blocks =
|
||||
List.Extra.findMap
|
||||
(\block ->
|
||||
case block of
|
||||
Block.Heading Block.H1 inlines ->
|
||||
Just (Block.extractInlineText inlines)
|
||||
|
||||
_ ->
|
||||
Nothing
|
||||
)
|
||||
blocks
|
||||
|
||||
|
||||
findDescription : List Block -> String
|
||||
findDescription blocks =
|
||||
blocks
|
||||
|> List.Extra.findMap
|
||||
(\block ->
|
||||
case block of
|
||||
Block.Paragraph inlines ->
|
||||
Just (MarkdownExtra.extractInlineText inlines)
|
||||
|
||||
_ ->
|
||||
Nothing
|
||||
)
|
||||
|> Maybe.withDefault ""
|
||||
|
||||
|
||||
titleFromFrontmatter : String -> BackendTask FatalError (Maybe String)
|
||||
titleFromFrontmatter filePath =
|
||||
StaticFile.onlyFrontmatter
|
||||
(Json.Decode.Extra.optionalField "title" Decode.string)
|
||||
filePath
|
||||
|> BackendTask.allowFatal
|
||||
|
||||
|
||||
withoutFrontmatter :
|
||||
Markdown.Renderer.Renderer view
|
||||
-> String
|
||||
-> BackendTask FatalError (List Block)
|
||||
withoutFrontmatter renderer filePath =
|
||||
(filePath
|
||||
|> StaticFile.bodyWithoutFrontmatter
|
||||
|> BackendTask.allowFatal
|
||||
|> BackendTask.andThen
|
||||
(\rawBody ->
|
||||
rawBody
|
||||
|> Markdown.Parser.parse
|
||||
|> Result.mapError (\_ -> FatalError.fromString "Couldn't parse markdown.")
|
||||
|> BackendTask.fromResult
|
||||
)
|
||||
)
|
||||
|> BackendTask.andThen
|
||||
(\blocks ->
|
||||
blocks
|
||||
|> Markdown.Renderer.render renderer
|
||||
-- we don't want to encode the HTML since it contains functions so it's not serializable
|
||||
-- but we can at least make sure there are no errors turning it into HTML before encoding it
|
||||
|> Result.map (\_ -> blocks)
|
||||
|> Result.mapError (\error -> FatalError.fromString error)
|
||||
|> BackendTask.fromResult
|
||||
)
|
||||
|
||||
|
||||
withFrontmatter :
|
||||
(frontmatter -> List Block -> value)
|
||||
-> Decoder frontmatter
|
||||
-> Markdown.Renderer.Renderer view
|
||||
-> String
|
||||
-> BackendTask FatalError value
|
||||
withFrontmatter constructor frontmatterDecoder_ renderer filePath =
|
||||
BackendTask.map2 constructor
|
||||
(StaticFile.onlyFrontmatter
|
||||
frontmatterDecoder_
|
||||
filePath
|
||||
|> BackendTask.allowFatal
|
||||
)
|
||||
(StaticFile.bodyWithoutFrontmatter
|
||||
filePath
|
||||
|> BackendTask.allowFatal
|
||||
|> BackendTask.andThen
|
||||
(\rawBody ->
|
||||
rawBody
|
||||
|> Markdown.Parser.parse
|
||||
|> Result.mapError (\_ -> FatalError.fromString "Couldn't parse markdown.")
|
||||
|> BackendTask.fromResult
|
||||
)
|
||||
|> BackendTask.andThen
|
||||
(\blocks ->
|
||||
blocks
|
||||
|> Markdown.Renderer.render renderer
|
||||
-- we don't want to encode the HTML since it contains functions so it's not serializable
|
||||
-- but we can at least make sure there are no errors turning it into HTML before encoding it
|
||||
|> Result.map (\_ -> blocks)
|
||||
|> Result.mapError (\error -> FatalError.fromString error)
|
||||
|> BackendTask.fromResult
|
||||
)
|
||||
)
|
114
src/MarkdownExtra.elm
Normal file
114
src/MarkdownExtra.elm
Normal file
@ -0,0 +1,114 @@
|
||||
module MarkdownExtra exposing (extractInlineText)
|
||||
|
||||
import Markdown.Block exposing (Block(..), Html(..), Inline(..), ListItem(..))
|
||||
|
||||
|
||||
extractInlineText : List Inline -> String
|
||||
extractInlineText inlines =
|
||||
List.foldl extractTextHelp "" inlines
|
||||
|
||||
|
||||
extractTextHelp : Inline -> String -> String
|
||||
extractTextHelp inline text =
|
||||
case inline of
|
||||
Text str ->
|
||||
text ++ str
|
||||
|
||||
HardLineBreak ->
|
||||
text ++ " "
|
||||
|
||||
CodeSpan str ->
|
||||
text ++ str
|
||||
|
||||
Link _ title inlines ->
|
||||
text ++ (title |> Maybe.withDefault (extractInlineText inlines))
|
||||
|
||||
Image _ _ inlines ->
|
||||
text ++ extractInlineText inlines
|
||||
|
||||
HtmlInline html ->
|
||||
case html of
|
||||
HtmlElement _ _ blocks ->
|
||||
blocks
|
||||
|> Markdown.Block.foldl
|
||||
(\block soFar ->
|
||||
soFar ++ extractInlineBlockText block
|
||||
)
|
||||
text
|
||||
|
||||
_ ->
|
||||
text
|
||||
|
||||
Strong inlines ->
|
||||
text ++ extractInlineText inlines
|
||||
|
||||
Emphasis inlines ->
|
||||
text ++ extractInlineText inlines
|
||||
|
||||
Strikethrough inlines ->
|
||||
text ++ extractInlineText inlines
|
||||
|
||||
|
||||
extractInlineBlockText : Block -> String
|
||||
extractInlineBlockText block =
|
||||
case block of
|
||||
Paragraph inlines ->
|
||||
extractInlineText inlines
|
||||
|
||||
HtmlBlock html ->
|
||||
case html of
|
||||
HtmlElement _ _ blocks ->
|
||||
blocks
|
||||
|> Markdown.Block.foldl
|
||||
(\nestedBlock soFar ->
|
||||
soFar ++ extractInlineBlockText nestedBlock
|
||||
)
|
||||
""
|
||||
|
||||
_ ->
|
||||
""
|
||||
|
||||
UnorderedList tight items ->
|
||||
items
|
||||
|> List.map
|
||||
(\(ListItem task blocks) ->
|
||||
blocks
|
||||
|> List.map extractInlineBlockText
|
||||
|> String.join "\n"
|
||||
)
|
||||
|> String.join "\n"
|
||||
|
||||
OrderedList tight int items ->
|
||||
items
|
||||
|> List.map
|
||||
(\blocks ->
|
||||
blocks
|
||||
|> List.map extractInlineBlockText
|
||||
|> String.join "\n"
|
||||
)
|
||||
|> String.join "\n"
|
||||
|
||||
BlockQuote blocks ->
|
||||
blocks
|
||||
|> List.map extractInlineBlockText
|
||||
|> String.join "\n"
|
||||
|
||||
Heading headingLevel inlines ->
|
||||
extractInlineText inlines
|
||||
|
||||
Table header rows ->
|
||||
[ header
|
||||
|> List.map .label
|
||||
|> List.map extractInlineText
|
||||
, rows
|
||||
|> List.map (List.map extractInlineText)
|
||||
|> List.concat
|
||||
]
|
||||
|> List.concat
|
||||
|> String.join "\n"
|
||||
|
||||
CodeBlock { body } ->
|
||||
body
|
||||
|
||||
ThematicBreak ->
|
||||
""
|
312
src/TailwindMarkdownRenderer.elm
Normal file
312
src/TailwindMarkdownRenderer.elm
Normal file
@ -0,0 +1,312 @@
|
||||
module TailwindMarkdownRenderer exposing (renderer)
|
||||
|
||||
import Css
|
||||
import Ellie
|
||||
import Html.Styled as Html
|
||||
import Html.Styled.Attributes as Attr exposing (css)
|
||||
import Markdown.Block as Block
|
||||
import Markdown.Html
|
||||
import Markdown.Renderer
|
||||
import Oembed
|
||||
import SyntaxHighlight
|
||||
import Tailwind.Theme as Theme
|
||||
import Tailwind.Utilities as Tw
|
||||
|
||||
|
||||
renderer : Markdown.Renderer.Renderer (Html.Html msg)
|
||||
renderer =
|
||||
{ heading = heading
|
||||
, paragraph = Html.p []
|
||||
, thematicBreak = Html.hr [] []
|
||||
, text = Html.text
|
||||
, strong = \content -> Html.strong [ css [ Tw.font_bold ] ] content
|
||||
, emphasis = \content -> Html.em [ css [ Tw.italic ] ] content
|
||||
, blockQuote = Html.blockquote []
|
||||
, codeSpan =
|
||||
\content ->
|
||||
Html.code
|
||||
[ css
|
||||
[ Tw.font_semibold
|
||||
, Tw.font_medium
|
||||
, Css.color (Css.rgb 226 0 124) |> Css.important
|
||||
]
|
||||
]
|
||||
[ Html.text content ]
|
||||
|
||||
--, codeSpan = code
|
||||
, link =
|
||||
\{ destination } body ->
|
||||
Html.a
|
||||
[ Attr.href destination
|
||||
, css
|
||||
[ Tw.underline
|
||||
]
|
||||
]
|
||||
body
|
||||
, hardLineBreak = Html.br [] []
|
||||
, image =
|
||||
\image ->
|
||||
case image.title of
|
||||
Just _ ->
|
||||
Html.img [ Attr.src image.src, Attr.alt image.alt ] []
|
||||
|
||||
Nothing ->
|
||||
Html.img [ Attr.src image.src, Attr.alt image.alt ] []
|
||||
, unorderedList =
|
||||
\items ->
|
||||
Html.ul []
|
||||
(items
|
||||
|> List.map
|
||||
(\item ->
|
||||
case item of
|
||||
Block.ListItem task children ->
|
||||
let
|
||||
checkbox =
|
||||
case task of
|
||||
Block.NoTask ->
|
||||
Html.text ""
|
||||
|
||||
Block.IncompleteTask ->
|
||||
Html.input
|
||||
[ Attr.disabled True
|
||||
, Attr.checked False
|
||||
, Attr.type_ "checkbox"
|
||||
]
|
||||
[]
|
||||
|
||||
Block.CompletedTask ->
|
||||
Html.input
|
||||
[ Attr.disabled True
|
||||
, Attr.checked True
|
||||
, Attr.type_ "checkbox"
|
||||
]
|
||||
[]
|
||||
in
|
||||
Html.li [] (checkbox :: children)
|
||||
)
|
||||
)
|
||||
, orderedList =
|
||||
\startingIndex items ->
|
||||
Html.ol
|
||||
(case startingIndex of
|
||||
1 ->
|
||||
[ Attr.start startingIndex ]
|
||||
|
||||
_ ->
|
||||
[]
|
||||
)
|
||||
(items
|
||||
|> List.map
|
||||
(\itemBlocks ->
|
||||
Html.li []
|
||||
itemBlocks
|
||||
)
|
||||
)
|
||||
, html =
|
||||
Markdown.Html.oneOf
|
||||
[ Markdown.Html.tag "oembed"
|
||||
(\url _ ->
|
||||
Oembed.view [] Nothing url
|
||||
|> Maybe.map Html.fromUnstyled
|
||||
|> Maybe.withDefault (Html.div [] [])
|
||||
)
|
||||
|> Markdown.Html.withAttribute "url"
|
||||
, Markdown.Html.tag "ellie-output"
|
||||
(\ellieId _ ->
|
||||
Ellie.outputTabElmCss ellieId
|
||||
)
|
||||
|> Markdown.Html.withAttribute "id"
|
||||
]
|
||||
, codeBlock = codeBlock
|
||||
|
||||
--\{ body, language } ->
|
||||
-- let
|
||||
-- classes =
|
||||
-- -- Only the first word is used in the class
|
||||
-- case Maybe.map String.words language of
|
||||
-- Just (actualLanguage :: _) ->
|
||||
-- [ Attr.class <| "language-" ++ actualLanguage ]
|
||||
--
|
||||
-- _ ->
|
||||
-- []
|
||||
-- in
|
||||
-- Html.pre []
|
||||
-- [ Html.code classes
|
||||
-- [ Html.text body
|
||||
-- ]
|
||||
-- ]
|
||||
, table =
|
||||
Html.table
|
||||
[ {-
|
||||
table-layout: auto;
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
margin-top: 2em;
|
||||
margin-bottom: 2em;
|
||||
-}
|
||||
css
|
||||
[--Tw.table_auto
|
||||
--, Tw.w_full
|
||||
--, Tw.mt_4
|
||||
--, Tw.mb_4
|
||||
]
|
||||
]
|
||||
, tableHeader = Html.thead []
|
||||
, tableBody = Html.tbody []
|
||||
, tableRow = Html.tr []
|
||||
, strikethrough =
|
||||
\children -> Html.del [] children
|
||||
, tableHeaderCell =
|
||||
\maybeAlignment ->
|
||||
let
|
||||
attrs =
|
||||
maybeAlignment
|
||||
|> Maybe.map
|
||||
(\alignment ->
|
||||
case alignment of
|
||||
Block.AlignLeft ->
|
||||
"left"
|
||||
|
||||
Block.AlignCenter ->
|
||||
"center"
|
||||
|
||||
Block.AlignRight ->
|
||||
"right"
|
||||
)
|
||||
|> Maybe.map Attr.align
|
||||
|> Maybe.map List.singleton
|
||||
|> Maybe.withDefault []
|
||||
in
|
||||
Html.th attrs
|
||||
, tableCell =
|
||||
\maybeAlignment ->
|
||||
let
|
||||
attrs =
|
||||
maybeAlignment
|
||||
|> Maybe.map
|
||||
(\alignment ->
|
||||
case alignment of
|
||||
Block.AlignLeft ->
|
||||
"left"
|
||||
|
||||
Block.AlignCenter ->
|
||||
"center"
|
||||
|
||||
Block.AlignRight ->
|
||||
"right"
|
||||
)
|
||||
|> Maybe.map Attr.align
|
||||
|> Maybe.map List.singleton
|
||||
|> Maybe.withDefault []
|
||||
in
|
||||
Html.td attrs
|
||||
}
|
||||
|
||||
|
||||
rawTextToId : String -> String
|
||||
rawTextToId rawText =
|
||||
rawText
|
||||
|> String.split " "
|
||||
|> String.join "-"
|
||||
|> String.toLower
|
||||
|
||||
|
||||
heading : { level : Block.HeadingLevel, rawText : String, children : List (Html.Html msg) } -> Html.Html msg
|
||||
heading { level, rawText, children } =
|
||||
case level of
|
||||
Block.H1 ->
|
||||
Html.h1
|
||||
[ css
|
||||
[ Tw.text_4xl
|
||||
, Tw.font_bold
|
||||
, Tw.tracking_tight
|
||||
, Tw.mt_2
|
||||
, Tw.mb_4
|
||||
]
|
||||
]
|
||||
children
|
||||
|
||||
Block.H2 ->
|
||||
Html.h2
|
||||
[ Attr.id (rawTextToId rawText)
|
||||
, Attr.attribute "name" (rawTextToId rawText)
|
||||
, css
|
||||
[ Tw.text_3xl
|
||||
, Tw.font_semibold
|
||||
, Tw.tracking_tight
|
||||
, Tw.mt_10
|
||||
, Tw.pb_1
|
||||
, Tw.border_b
|
||||
]
|
||||
]
|
||||
[ Html.a
|
||||
[ Attr.href <| "#" ++ rawTextToId rawText
|
||||
, css
|
||||
[ Tw.no_underline |> Css.important
|
||||
]
|
||||
]
|
||||
(children
|
||||
++ [ Html.span
|
||||
[ Attr.class "anchor-icon"
|
||||
, css
|
||||
[ Tw.ml_2
|
||||
, Tw.text_color Theme.gray_500
|
||||
, Tw.select_none
|
||||
]
|
||||
]
|
||||
[ Html.text "#" ]
|
||||
]
|
||||
)
|
||||
]
|
||||
|
||||
_ ->
|
||||
(case level of
|
||||
Block.H1 ->
|
||||
Html.h1
|
||||
|
||||
Block.H2 ->
|
||||
Html.h2
|
||||
|
||||
Block.H3 ->
|
||||
Html.h3
|
||||
|
||||
Block.H4 ->
|
||||
Html.h4
|
||||
|
||||
Block.H5 ->
|
||||
Html.h5
|
||||
|
||||
Block.H6 ->
|
||||
Html.h6
|
||||
)
|
||||
[ css
|
||||
[ Tw.font_bold
|
||||
, Tw.text_lg
|
||||
, Tw.mt_8
|
||||
, Tw.mb_4
|
||||
]
|
||||
]
|
||||
children
|
||||
|
||||
|
||||
|
||||
--code : String -> Element msg
|
||||
--code snippet =
|
||||
-- Element.el
|
||||
-- [ Element.Background.color
|
||||
-- (Element.rgba255 50 50 50 0.07)
|
||||
-- , Element.Border.rounded 2
|
||||
-- , Element.paddingXY 5 3
|
||||
-- , Font.family [ Font.typeface "Roboto Mono", Font.monospace ]
|
||||
-- ]
|
||||
-- (Element.text snippet)
|
||||
--
|
||||
--
|
||||
|
||||
|
||||
codeBlock : { body : String, language : Maybe String } -> Html.Html msg
|
||||
codeBlock details =
|
||||
SyntaxHighlight.elm details.body
|
||||
|> Result.map (SyntaxHighlight.toBlockHtml (Just 1))
|
||||
|> Result.map Html.fromUnstyled
|
||||
|> Result.withDefault (Html.pre [] [ Html.code [] [ Html.text details.body ] ])
|
Loading…
Reference in New Issue
Block a user