elm-pages-v3-beta/plugins/MarkdownCodec.elm
2023-05-19 08:14:56 -07:00

237 lines
9.6 KiB
Elm

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