init
This commit is contained in:
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 ] ])
|
Reference in New Issue
Block a user