This commit is contained in:
nganhkhoa 2023-10-24 05:23:25 +07:00
commit 7a8ce17237
33 changed files with 11927 additions and 0 deletions

5
.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
node_modules/
elm-stuff/
dist/
.elm-pages/
functions/render/elm-pages-cli.js

1
README.md Normal file
View File

@ -0,0 +1 @@
# README

26
app/Api.elm Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View File

@ -0,0 +1,6 @@
---
title: hello world
description: just hello
published: "2023-10-24"
---
helloworld

3
custom-backend-task.ts Normal file
View File

@ -0,0 +1,3 @@
export async function hello(name) {
return `Hello ${name}!`;
}

18
elm-pages.config.mjs Normal file
View 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
View File

@ -0,0 +1,6 @@
{
"tools": {
"elm": "0.19.1",
"elm-format": "0.8.5"
}
}

70
elm.json Normal file
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

20
package.json Normal file
View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 450 B

View File

@ -0,0 +1,3 @@
export async function hello(name) {
return `Hello ${name}!`;
}

61
script/elm.json Normal file
View 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
View 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
View 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
View File

107
src/Article.elm Normal file
View 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
View 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
View 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
View 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
View 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 ->
""

View 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 ] ])

4
style.css Normal file
View File

@ -0,0 +1,4 @@
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial,
sans-serif, "Apple Color Emoji", "Segoe UI Emoji";
}