Commit 8741b786 authored by Henrik Tramberend's avatar Henrik Tramberend
Browse files

Pandoc processing now happens in the decker monad

parent 86d76055
......@@ -7,6 +7,8 @@ module Common
, Disposition(..)
, MediaType(..)
, Decker
, needFile
, needFiles
, deckerVersion
) where
......@@ -14,6 +16,7 @@ import Control.Exception
import Control.Monad.State
import Data.Typeable
import Data.Version (showVersion)
import Development.Shake (Action, need)
import Network.URI as U
import Paths_decker (version)
......@@ -47,13 +50,27 @@ instance Show DeckerException where
show RsyncUrlException =
"attributes 'destinationRsyncHost' or 'destinationRsyncPath' not defined in meta data"
type Decker = StateT DeckerState Action
needFile :: FilePath -> Decker ()
needFile path = lift $ need [path]
needFiles :: [FilePath] -> Decker ()
needFiles pathes = lift $ need pathes
data DeckerState = DeckerState
{ disposition :: Disposition
, slideCount :: Int
, externalReferences :: [U.URI]
}
, scripts :: [Script]
} deriving (Eq, Show)
type Decker = StateT DeckerState IO
data Script
= ScriptURI { scriptLang :: String
, scriptUri :: U.URI }
| ScriptSource { scriptLang :: String
, scriptSource :: String }
deriving (Eq, Show)
data Layout
= Deck
......
......@@ -8,7 +8,6 @@ module Filter
, processPandoc
, hasAttrib
, blockClasses
, expandMacros
, makeSlides
, makeBoxes
, useCachedImages
......@@ -18,7 +17,6 @@ module Filter
, renderMediaTags
, transformImageSize
, lazyLoadImage
, isMacro
, iframeExtensions
, audioExtensions
, videoExtensions
......@@ -32,10 +30,11 @@ import qualified Data.ByteString.Lazy.Char8 as L8
import Data.Default ()
import Data.List
import Data.List.Split
import Data.Maybe
import qualified Data.Map as Map (Map, fromList, lookup)
import Data.Maybe
import Data.Maybe
import Debug.Trace
import Development.Shake (Action)
import Macro
import Network.HTTP.Conduit
import Network.HTTP.Simple
......@@ -57,135 +56,10 @@ import Text.Pandoc.Walk
import Text.Printf
import Text.Read
processPandoc :: Disposition -> Pandoc -> IO Pandoc
processPandoc disposition pandoc =
evalStateT (processPandoc_ pandoc) (DeckerState disposition 0 [])
processPandoc_ :: Pandoc -> Decker Pandoc
processPandoc_ pandoc@(Pandoc meta blocks) = do
disp <- gets disposition
case disp of
(Disposition Deck _) -> walkM (expandDeckerMacros meta) pandoc
(Disposition Page _) -> walkM (expandDeckerMacros meta) pandoc
(Disposition Handout _) -> walkM (expandDeckerMacros meta) pandoc
-- iframe resizing, see:
-- https://css-tricks.com/NetMag/FluidWidthVideo/Article-FluidWidthVideo.php
-- YouTube links: iv_load_policy=3 disables annotations, rel=0 disables related
-- videos. See:
-- https://developers.google.com/youtube/player_parameters?hl=de#IFrame_Player_API
embedYoutubeHtml :: [String] -> Attr -> Target -> Inline
embedYoutubeHtml args attr (vid, _) =
RawInline (Format "html") (renderHtml html)
where
url =
printf
"https://www.youtube.com/embed/%s?iv_load_policy=3&disablekb=1&rel=0&modestbranding=1&autohide=1"
vid :: String
vidWidthStr = macroArg 0 args "560"
vidHeightStr = macroArg 1 args "315"
vidWidth = readDefault 560.0 vidWidthStr :: Float
vidHeight = readDefault 315.0 vidHeightStr :: Float
wrapperStyle =
printf
"position:relative;padding-top:25px;padding-bottom:%f%%;height:0;"
(vidHeight / vidWidth * 100.0) :: String
iframeStyle =
"position:absolute;top:0;left:0;width:100%;height:100%;" :: String
figureStyle (_, _, kv) =
foldl (\s (k, v) -> s ++ printf "%s:%s;" k v :: String) "" kv
figureClass (_, cls, _) = unwords cls
html =
H.figure ! class_ (toValue (figureClass attr)) !
style (toValue (figureStyle attr)) $
H.div ! style (toValue wrapperStyle) $
iframe ! style (toValue iframeStyle) ! width (toValue vidWidthStr) !
height (toValue vidHeightStr) !
src (toValue url) !
customAttribute "frameborder" "0" !
customAttribute "allowfullscreen" "" $
H.p ""
youtube :: MacroFunc
youtube args attr target (Format f) _
| f `elem` ["html", "html5", "revealjs"] = embedYoutubeHtml args attr target
youtube _ attr (vid, _) _ _ =
Link nullAttr [Image attr [Str text] (imageUrl, "")] (videoUrl, "")
where
videoUrl =
printf
"https://www.youtube.com/embed/%s?iv_load_policy=3&disablekb=0&rel=0&modestbranding=1&autohide=1"
vid :: String
imageUrl =
printf "http://img.youtube.com/vi/%s/maxresdefault.jpg" vid :: String
text = printf "YouTube: %s" vid :: String
fontAwesome :: MacroFunc
fontAwesome _ _ (iconName, _) (Format f) _
| f `elem` ["html", "html5", "revealjs"] =
RawInline (Format "html") $ "<i class=\"fa fa-" ++ iconName ++ "\"></i>"
fontAwesome _ _ (iconName, _) _ _ = Str $ "[" ++ iconName ++ "]"
metaValue :: MacroFunc
metaValue _ _ (key, _) _ meta =
case splitOn "." key of
[] -> Str key
k:ks -> lookup' ks (lookupMeta k meta)
where
lookup' :: [String] -> Maybe MetaValue -> Inline
lookup' [] (Just (MetaString s)) = Str s
lookup' [] (Just (MetaInlines i)) = Span nullAttr i
lookup' (k:ks) (Just (MetaMap metaMap)) = lookup' ks (Map.lookup k metaMap)
lookup' _ _ = Strikeout [Str key]
type MacroMap = Map.Map String MacroFunc
macroMap :: MacroMap
macroMap =
Map.fromList [("meta", metaValue), ("youtube", youtube), ("fa", fontAwesome)]
readDefault :: Read a => a -> String -> a
readDefault default_ string = fromMaybe default_ (readMaybe string)
macroArg :: Int -> [String] -> String -> String
macroArg n args default_ =
if length args > n
then args !! n
else default_
parseMacro :: String -> Maybe [String]
parseMacro (pre:invocation)
| pre == ':' = Just (words invocation)
parseMacro _ = Nothing
isMacro :: String -> Bool
isMacro (pre:_) = pre == ':'
isMacro _ = False
onlyStrings :: [Inline] -> [String]
onlyStrings = reverse . foldl only []
where
only ss (Str s) = s : ss
only ss _ = ss
expand :: Inline -> Format -> Meta -> Maybe Inline
expand (Link attr text target) format meta =
expand_ attr text target format meta
expand x _ _ = Just x
expand_ :: Attr -> [Inline] -> Target -> Format -> Meta -> Maybe Inline
expand_ attr text target format meta = do
name:args <- parseMacro $ stringify text
func <- Map.lookup name macroMap
return (func args attr target format meta)
expandInlineMacros :: Format -> Meta -> Inline -> Inline
expandInlineMacros format meta inline =
fromMaybe inline (expand inline format meta)
expandMacros :: Format -> Pandoc -> Pandoc
expandMacros format doc@(Pandoc meta _) =
walk (expandInlineMacros format meta) doc
processPandoc ::
(Pandoc -> Decker Pandoc) -> Disposition -> Pandoc -> Action Pandoc
processPandoc transformation disposition pandoc =
evalStateT (transformation pandoc) (DeckerState disposition 0 [] [])
isSlideHeader :: Block -> Bool
isSlideHeader (Header 1 _ _) = True
......@@ -406,6 +280,22 @@ wrapNoteRevealjs slide = slide
type Slide = (Block, [Block])
splitSlides :: [Block] -> [Slide]
splitSlides blocks =
map extractHeader $
filter (not . null) $ split (keepDelimsL $ whenElt isSlideHeader) blocks
where
extractHeader (header@(Header 1 _ _):blocks) = (header, blocks)
extractHeader (rule@(HorizontalRule):blocks) = (rule, blocks)
extractHeader slide =
throw $
PandocException $ "Error extracting slide header: \n" ++ show slide
joinSlides :: [Slide] -> [Block]
joinSlides slides = concatMap prependHeader slides
where
prependHeader (header, blocks) = header : blocks
-- | Map over all slides in a deck. A slide has always a header followed by zero
-- or more blocks.
mapSlides :: (Slide -> Slide) -> Pandoc -> Pandoc
......@@ -421,13 +311,26 @@ mapSlides func (Pandoc meta blocks) =
PandocException $ "Error extracting slide header: \n" ++ show slide
prependHeader (header, blocks) = header : blocks
makeSlides :: Format -> Pandoc -> Pandoc
makeSlides (Format "revealjs") =
walk (mapSlides layoutSlides) .
walk (mapSlides splitJoinColumns) .
walk (mapSlides setSlideBackground) .
walk (mapSlides wrapBoxes) . walk (mapSlides wrapNoteRevealjs)
makeSlides _ = Prelude.id
makeSlides :: Pandoc -> Decker Pandoc
makeSlides pandoc = do
disp <- gets disposition
case disp of
Disposition Deck Html ->
return $
walk (mapSlides layoutSlides) $
walk (mapSlides splitJoinColumns) $
walk (mapSlides setSlideBackground) $
walk (mapSlides wrapBoxes) $ walk (mapSlides wrapNoteRevealjs) pandoc
Disposition Deck Pdf ->
return $
walk (mapSlides layoutSlides) $
walk (mapSlides splitJoinColumns) $
walk (mapSlides setSlideBackground) $ walk (mapSlides wrapBoxes) pandoc
Disposition _ _ ->
return $
walk (mapSlides splitJoinColumns) $
-- walk (mapSlides setSlideBackground) $
walk (mapSlides wrapBoxes) pandoc
makeBoxes :: Pandoc -> Pandoc
makeBoxes = walk (mapSlides wrapBoxes)
......@@ -484,8 +387,10 @@ cacheImageIO uri cacheDir = do
createDirectoryIfMissing True cacheDir
L8.writeFile cacheFile body
renderMediaTags :: Disposition -> Pandoc -> Pandoc
renderMediaTags disposition = walk (renderImageAudioVideoTag disposition)
renderMediaTags :: Pandoc -> Decker Pandoc
renderMediaTags pandoc = do
disp <- gets disposition
return $ walk (renderImageAudioVideoTag disp) pandoc
-- | File extensions that signify video content.
videoExtensions :: [String]
......
......@@ -3,13 +3,13 @@
module Macro
( expandDeckerMacros
, MacroFunc
) where
import Common
import Control.Monad.State
import Data.List.Split
import qualified Data.Map as Map (Map, fromList, lookup)
import Data.Maybe
import System.FilePath
import Text.Blaze (customAttribute)
import Text.Blaze.Html.Renderer.String
......@@ -25,11 +25,6 @@ import Text.Pandoc.Walk
import Text.Printf
import Text.Read
expandDeckerMacros :: Meta -> Inline -> Decker Inline
expandDeckerMacros meta inline = return inline
type MacroFunc = [String] -> Attr -> Target -> Format -> Meta -> Inline
type MacroAction = [String] -> Attr -> Target -> Meta -> Decker Inline
-- iframe resizing, see:
......@@ -81,14 +76,14 @@ embedYoutubePdf args attr (vid, _) =
printf "http://img.youtube.com/vi/%s/maxresdefault.jpg" vid :: String
text = printf "YouTube: %s" vid :: String
youtube :: MacroFunc
youtube :: MacroAction
youtube args attr target meta = do
disp <- gets disposition
case disp of
Disposition _ Html -> return $ embedYoutubeHtml args attr target
Disposition _ Pdf -> return $ embedYoutubePdf args attr target
fontAwesome :: MacroFunc
fontAwesome :: MacroAction
fontAwesome _ _ (iconName, _) _ = do
disp <- gets disposition
case disp of
......@@ -97,7 +92,7 @@ fontAwesome _ _ (iconName, _) _ = do
RawInline (Format "html") $ "<i class=\"fa fa-" ++ iconName ++ "\"></i>"
Disposition _ Pdf -> return $ Str $ "[" ++ iconName ++ "]"
metaValue :: MacroFunc
metaValue :: MacroAction
metaValue _ _ (key, _) meta =
case splitOn "." key of
[] -> return $ Str key
......@@ -109,7 +104,7 @@ metaValue _ _ (key, _) meta =
lookup' (k:ks) (Just (MetaMap metaMap)) = lookup' ks (Map.lookup k metaMap)
lookup' _ _ = Strikeout [Str key]
type MacroMap = Map.Map String MacroFunc
type MacroMap = Map.Map String MacroAction
macroMap :: MacroMap
macroMap =
......@@ -139,21 +134,15 @@ onlyStrings = reverse . foldl only []
only ss (Str s) = s : ss
only ss _ = ss
expand :: Inline -> Format -> Meta -> Maybe Inline
expand (Link attr text target) format meta =
expand_ attr text target format meta
expand x _ _ = Just x
expand_ :: Attr -> [Inline] -> Target -> Format -> Meta -> Maybe Inline
expand_ attr text target format meta = do
name:args <- parseMacro $ stringify text
func <- Map.lookup name macroMap
return (func args attr target format meta)
expandInlineMacros :: Format -> Meta -> Inline -> Inline
expandInlineMacros format meta inline =
fromMaybe inline (expand inline format meta)
expandMacros :: Format -> Pandoc -> Pandoc
expandMacros format doc@(Pandoc meta _) =
walk (expandInlineMacros format meta) doc
expandInlineMacros :: Meta -> Inline -> Decker Inline
expandInlineMacros meta inline@(Link attr text target) = do
case parseMacro $ stringify text of
Just (name:args) -> do
case Map.lookup name macroMap of
Just macro -> macro args attr target meta
Nothing -> return inline
Nothing -> return inline
expandInlineMacros meta inline = return inline
expandDeckerMacros :: Pandoc -> Decker Pandoc
expandDeckerMacros doc@(Pandoc meta _) = walkM (expandInlineMacros meta) doc
......@@ -7,6 +7,8 @@ module Render
) where
import CRC32
import Common
import Control.Monad.Trans.Class
import Context
import qualified Data.ByteString.Base64 as B64
import qualified Data.ByteString.Char8 as B
......@@ -24,7 +26,7 @@ import Text.Pandoc.Walk
import Text.Printf
-- | Evaluate code blocks
renderCodeBlocks :: Pandoc -> Action Pandoc
renderCodeBlocks :: Pandoc -> Decker Pandoc
renderCodeBlocks pandoc =
walk maybeRenderImage <$> walkM maybeRenderCodeBlock pandoc
......@@ -43,6 +45,7 @@ dotPrelude _ = ""
gnuplotPrelude :: String -> String
gnuplotPrelude _ = "set terminal svg;"
-- | The map of all rendering processors.
processors :: Map.Map String Processor
processors =
Map.fromList
......@@ -55,9 +58,13 @@ processors =
"\\end{document}")
]
-- | Calculates the list of all known file extensions that can be rendered into
-- an SVG image.
renderedCodeExtensions :: [String]
renderedCodeExtensions = Map.foldr (\p es -> (extensions p) ++ es) [] processors
-- | Selects a processor based on a list of CSS class names. The first processor
-- that is mentioned in that list is returned.
findProcessor :: [String] -> Maybe Processor
findProcessor classes =
if renderClass `elem` classes
......@@ -66,6 +73,9 @@ findProcessor classes =
where
matching = Map.restrictKeys processors (Set.fromList classes)
-- | Appends `.svg` to file urls with extensions that belong to a known render
-- processor. The dependeny for the new file url is established at a later
-- stage, along with the handling of the normal image file urls.
maybeRenderImage :: Inline -> Inline
maybeRenderImage image@(Image (id, classes, namevals) inlines (url, title)) =
if takeExtension url `elem` [".dot", ".gnuplot", ".tex"]
......@@ -74,7 +84,7 @@ maybeRenderImage image@(Image (id, classes, namevals) inlines (url, title)) =
else image
maybeRenderImage inline = inline
maybeRenderCodeBlock :: Block -> Action Block
maybeRenderCodeBlock :: Block -> Decker Block
maybeRenderCodeBlock code@(CodeBlock (id, classes, namevals) contents) =
case findProcessor classes of
Just processor -> do
......@@ -94,9 +104,9 @@ svgDataUrl :: String -> String
svgDataUrl svg =
"data:image/svg+xml;base64," ++ (B.unpack (B64.encode (B.pack svg)))
extractCodeIfChanged :: String -> Processor -> FilePath -> Action FilePath
extractCodeIfChanged :: String -> Processor -> FilePath -> Decker FilePath
extractCodeIfChanged basename processor code = do
projectDir <- project <$> getProjectDirs
projectDir <- project <$> (lift $ getProjectDirs)
let crc = printf "%08x" (calc_crc32 code)
let basepath =
"generated" </>
......@@ -104,15 +114,15 @@ extractCodeIfChanged basename processor code = do
let extension = (head . extensions) processor
let sourceFile = projectDir </> basepath <.> extension
let svgFile = projectDir </> basepath <.> extension <.> ".svg"
publicResource <- getPublicResource
withResource publicResource 1 $
publicResource <- lift $ getPublicResource
lift $ withResource publicResource 1 $
liftIO $
unlessM (System.Directory.doesFileExist svgFile) $ do
createDirectoryIfMissing True (takeDirectory sourceFile)
writeFile
sourceFile
((preamble processor) ++ "\n" ++ code ++ "\n" ++ (postamble processor))
need [svgFile]
needFile svgFile
return svgFile
defaultString :: String -> String -> String
......
......@@ -29,6 +29,7 @@ import Control.Arrow
import Control.Exception
import Control.Monad
import Control.Monad.Loops
import Control.Monad.Trans.Class
import qualified Data.ByteString.Char8 as B
import qualified Data.ByteString.Lazy as LB
import qualified Data.HashMap.Lazy as HashMap
......@@ -45,6 +46,7 @@ import Debug.Trace
import Development.Shake
import Development.Shake.FilePath as SFP
import Filter
import Macro
import Meta
import Network.URI
import Project
......@@ -180,9 +182,8 @@ markdownToHtmlDeck markdownFile out = do
]
, writerCiteMethod = Citeproc
}
pandoc <- readAndPreprocessMarkdown markdownFile (Disposition Deck Html)
processed <- processPandocDeck "revealjs" pandoc
writePandocString "revealjs" options out processed
readAndProcessMarkdown markdownFile (Disposition Deck Html) >>=
writePandocString "revealjs" options out
-- | Selects a matching pandoc string writer for the format string, or throws an
-- exception.
......@@ -212,6 +213,28 @@ versionCheck meta =
-- | Reads a markdownfile, expands the included files, and substitutes mustache
-- template variables and calls need.
readAndProcessMarkdown :: FilePath -> Disposition -> Action Pandoc
readAndProcessMarkdown markdownFile disposition = do
readMetaMarkdown markdownFile >>= processIncludes baseDir >>=
processPandoc pipeline disposition
where
baseDir = takeDirectory markdownFile
pipeline :: Pandoc -> Decker Pandoc
pipeline =
concatM
[ expandDeckerMacros
, renderMediaTags
, makeSlides
, renderCodeBlocks
, provisionResources baseDir
, processCitesWithDefault
]
-- Disable automatic caching of remote images for a while
-- >>= walkM (cacheRemoteImages (cache dirs))
-- | Reads a markdownfile, expands the included files, and substitutes mustache
-- template variables and calls need.
{--
readAndPreprocessMarkdown :: FilePath -> Disposition -> Action Pandoc
readAndPreprocessMarkdown markdownFile disposition = do
let baseDir = takeDirectory markdownFile
......@@ -220,10 +243,18 @@ readAndPreprocessMarkdown markdownFile disposition = do
versionCheck meta
let method = provisioningFromMeta meta
mapMetaResources (provisionMetaResource method baseDir) pandoc >>=
expandDeckerMacros >>=
renderCodeBlocks >>=
mapResources (provisionResource method baseDir)
-- Disable automatic caching of remote images for a while
-- >>= walkM (cacheRemoteImages (cache dirs))
--}
provisionResources :: FilePath -> Pandoc -> Decker Pandoc
provisionResources baseDir pandoc@(Pandoc meta _) =
lift $ do
let method = provisioningFromMeta meta
mapMetaResources (provisionMetaResource method baseDir) pandoc >>=
mapResources (provisionResource method baseDir)
lookupBool :: String -> Bool -> Meta -> Bool
lookupBool key def meta =
......@@ -290,22 +321,16 @@ markdownToHtmlPage markdownFile out = do
let options =
pandocWriterOpts
{ writerHtml5 = True
-- , writerStandalone = True
, writerTemplate = Just template
, writerHighlight = True
-- , writerHighlightStyle = pygments
, writerHTMLMathMethod =
MathJax
(supportDir </> "MathJax-2.7/MathJax.js?config=TeX-AMS_HTML")
-- ,writerHTMLMathMethod =
-- KaTeX (supportDir </> "katex-0.6.0/katex.min.js")
-- (supportDir </> "katex-0.6.0/katex.min.css")
, writerVariables = [("decker-support-dir", supportDir)]
, writerCiteMethod = Citeproc
}
pandoc <- readAndPreprocessMarkdown markdownFile (Disposition Page Html)
processed <- processPandocPage "html5" pandoc
writePandocString "html5" options out processed
readAndProcessMarkdown markdownFile (Disposition Page Html) >>=
writePandocString "html5" options out
-- | Write a markdown file to a PDF file using the handout template.
markdownToPdfPage :: FilePath -> FilePath -> Action ()
......@@ -318,14 +343,12 @@ markdownToPdfPage markdownFile out = do
, writerHighlight = True
, writerCiteMethod = Citeproc
}
pandoc <- readAndPreprocessMarkdown markdownFile (Disposition Page Pdf)
processed <- processPandocPage "latex" pandoc
putNormal $ "# pandoc (for " ++ out ++ ")"
pandocMakePdf options processed out
pandocMakePdf :: WriterOptions -> Pandoc -> FilePath -> Action ()
pandocMakePdf options processed out = do
result <- liftIO $ makePDF "pdflatex" writeLaTeX options processed
readAndProcessMarkdown markdownFile (Disposition Page Pdf) >>=
pandocMakePdf options out
pandocMakePdf :: WriterOptions -> FilePath -> Pandoc -> Action ()
pandocMakePdf options out pandoc = do
result <- liftIO $ makePDF "pdflatex" writeLaTeX options pandoc
case result of
Left err -> throw $ PandocException (show err)
Right pdf -> liftIO $ LB.writeFile out pdf
......@@ -334,8 +357,6 @@ pandocMakePdf options processed out = do
markdownToHtmlHandout :: FilePath -> FilePath -> Action ()
markdownToHtmlHandout markdownFile out = do
putCurrentDocument out
pandoc <- readAndPreprocessMarkdown markdownFile (Disposition Handout Html)
processed <- processPandocHandout "html" pandoc
supportDir <- getRelativeSupportDir out
template <- getTemplate "handout.html"
let options =
......@@ -349,14 +370,13 @@ markdownToHtmlHandout markdownFile out = do
, writerVariables = [("decker-support-dir", supportDir)]
, writerCiteMethod = Citeproc
}
writePandocString "html5" options out processed
readAndProcessMarkdown markdownFile (Disposition Handout Html) >>=
writePandocString "html5" options out
-- | Write a markdown file to a PDF file using the handout template.
markdownToPdfHandout :: FilePath -> FilePath -> Action ()
markdownToPdfHandout markdownFile out = do
putCurrentDocument out
pandoc <- readAndPreprocessMarkdown markdownFile (Disposition Handout Pdf)
processed <- processPandocHandout "latex" pandoc
template <- getTemplate "handout.tex"
let options =
pandocWriterOpts
......@@ -364,8 +384,8 @@ markdownToPdfHandout markdownFile out = do
, writerHighlight = True
, writerCiteMethod = Citeproc
}
putNormal $ "# pandoc (for " ++ out ++ ")"
pandocMakePdf options processed out
readAndProcessMarkdown markdownFile (Disposition Handout Pdf) >>=
pandocMakePdf options out
-- | Reads a markdown file and returns a pandoc document. Handles meta data
-- extraction and template substitution. All references to local resources are
......@@ -388,9 +408,9 @@ readMetaMarkdown markdownFile = do
let Pandoc _ blocks =
readMarkdownOrThrow pandocReaderOpts $ T.unpack substituted
let (MetaMap m) = combinedMeta
versionCheck (Meta m)
let pandoc = Pandoc (Meta m) blocks
-- adjust local media urls
-- mapResources (locateFileIfLocal (takeDirectory markdownFile)) pandoc
mapResources (urlToFilePathIfLocal (takeDirectory markdownFile)) pandoc
urlToFilePathIfLocal :: FilePath -> FilePath -> Action FilePath
......@@ -444,15 +464,12 @@ mapAttributes transform (ident, classes, kv) = do
else return kv