diff --git a/INSTALL.md b/INSTALL.md
--- a/INSTALL.md
+++ b/INSTALL.md
@@ -215,66 +215,46 @@
     `pandoc` executable into `~/.local/bin`, which you should
     add to your `PATH`.  This process will take a while, and
     will consume a considerable amount of disk space.
+    If you also want the `pandoc-server` executable, add
+    `--flag pandoc:server` to the above command.
 
+
 ### Quick cabal method
 
-1.  Install the [Haskell platform].  This will give you [GHC] and
-    the [cabal-install] build tool.  Note that pandoc requires
-    GHC >= 7.10 and cabal >= 2.0.
+1.  Install [ghcup](https://www.haskell.org/ghcup/install/).
+    This will give you `ghc` and `cabal`.
 
 2.  Update your package database:
 
         cabal update
 
-3.  Check your cabal version with
-
-        cabal --version
-
-    If you have a version less than 2.0, install the latest with:
-
-        cabal install cabal-install
-
-4.  Use `cabal` to install pandoc and its dependencies:
+3.  Use `cabal` to install pandoc and its dependencies:
 
         cabal install pandoc
 
     This procedure will install the released version of pandoc,
     which will be downloaded automatically from HackageDB.
+    The `pandoc` executable will be placed in `$HOME/.cabal/bin`
+    on linux/unix/macOS and in `%APPDATA%\cabal\bin` on Windows.
+    Make sure this directory is in your path.
 
+    If you also want the `pandoc-server` executable, add
+    `-fserver` to the above command.
+
     If you want to install a modified or development version
     of pandoc instead, switch to the source directory and do
     as above, but without the 'pandoc':
 
         cabal install
 
-5.  Make sure the `$CABALDIR/bin` directory is in your path.  You should
-    now be able to run `pandoc`:
+4.  You should now be able to run `pandoc`:
 
         pandoc --help
 
-    [Not sure where `$CABALDIR` is?](https://wiki.haskell.org/Cabal-Install#The_cabal-install_configuration_file)
-
-5.  By default `pandoc` uses the "i;unicode-casemap" method
-    to sort bibliography entries (RFC 5051).  If you would like to
-    use the locale-sensitive unicode collation algorithm instead,
-    specify the `icu` flag (which affects the dependency `citeproc`):
-
-        cabal install pandoc -ficu
-
-    Note that this requires the `text-icu` library, which in turn
-    depends on the C library `icu4c`.  Installation directions
-    vary by platform.  Here is how it might work on macOS with Homebrew:
-
-        brew install icu4c
-        stack install pandoc \
-          --flag "citeproc:icu" \
-          --extra-lib-dirs=/usr/local/opt/icu4c/lib \
-          --extra-include-dirs=/usr/local/opt/icu4c/include
-
-6.  The `pandoc.1` man page will be installed automatically.  cabal shows
-    you where it is installed: you may need to set your `MANPATH`
-    accordingly. If `MANUAL.txt` has been modified, the man page can be
-    rebuilt: `make man/pandoc.1`.
+5.  Cabal does not install the `pandoc.1` man page, but you can
+    copy it from the `man/` directory of the source code to
+    `/usr/local/share/man/man1/` or wherever man pages go on
+    your system.
 
 
 ### Custom cabal method
@@ -312,6 +292,8 @@
 
     - `lua53`: embed support for Lua 5.3 instead of 5.4.
 
+    - `server`:  build the `pandoc-server` executable.
+
 3.  Build:
 
         cabal build
@@ -321,19 +303,6 @@
 
         cabal haddock --html-location=URL --hyperlink-source
 
-5.  Copy the files:
-
-        cabal copy --destdir=PATH
-
-    The default destdir is `/`.
-
-6.  Register pandoc as a GHC package:
-
-        cabal register
-
-    Package managers may want to use the `--gen-script` option to
-    generate a script that can be run to register the package at
-    install time.
 
 ### Creating a relocatable binary
 
diff --git a/MANUAL.txt b/MANUAL.txt
--- a/MANUAL.txt
+++ b/MANUAL.txt
@@ -1,7 +1,7 @@
 ---
 title: Pandoc User's Guide
 author: John MacFarlane
-date: August 3, 2022
+date: August 18, 2022
 ---
 
 # Synopsis
@@ -7050,6 +7050,25 @@
 Some document formats also include a unique identifier.  For
 EPUB, this can be set explicitly by setting the `identifier`
 metadata field (see [EPUB Metadata], above).
+
+# Running pandoc as a web server
+
+If you rename (or symlink) the pandoc executable to
+`pandoc-server`, it will start up a web server with a JSON
+API. This server exposes most of the conversion functionality
+of pandoc.  For full documentation, see the [pandoc-server]
+man page.
+
+If you rename (or symlink) the pandoc executable to
+`pandoc-server.cgi`, it will function as a CGI program
+exposing the same API as `pandoc-server`.
+
+`pandoc-server` is designed to be maximally secure; it uses
+Haskell's type system to provide strong guarantees that no I/O
+will be performed on the server during pandoc conversions.
+
+[pandoc-server]: https://github.com/jgm/pandoc/blob/master/doc/pandoc-server.md
+
 
 # A note on security
 
diff --git a/app/pandoc.hs b/app/pandoc.hs
--- a/app/pandoc.hs
+++ b/app/pandoc.hs
@@ -14,7 +14,20 @@
 import qualified Control.Exception as E
 import Text.Pandoc.App (convertWithOpts, defaultOpts, options, parseOptions)
 import Text.Pandoc.Error (handleError)
+import Text.Pandoc.Server (ServerOpts(..), parseServerOpts, app)
+import Safe (readDef)
+import System.Environment (getProgName, lookupEnv)
+import qualified Network.Wai.Handler.CGI as CGI
+import qualified Network.Wai.Handler.Warp as Warp
+import Network.Wai.Middleware.Timeout (timeout)
 
 main :: IO ()
-main = E.catch (parseOptions options defaultOpts >>= convertWithOpts)
-          (handleError . Left)
+main = E.handle (handleError . Left) $ do
+  prg <- getProgName
+  cgiTimeout <- maybe 2 (readDef 2) <$> lookupEnv "PANDOC_SERVER_TIMEOUT"
+  case prg of
+    "pandoc-server.cgi" -> CGI.run (timeout cgiTimeout app)
+    "pandoc-server" -> do
+      sopts <- parseServerOpts
+      Warp.run (serverPort sopts) (timeout (serverTimeout sopts) app)
+    _ -> parseOptions options defaultOpts >>= convertWithOpts
diff --git a/cabal.project b/cabal.project
--- a/cabal.project
+++ b/cabal.project
@@ -2,4 +2,3 @@
 tests: True
 flags: +embed_data_files
 constraints: aeson >= 2.0.1.0
-
diff --git a/changelog.md b/changelog.md
--- a/changelog.md
+++ b/changelog.md
@@ -1,5 +1,86 @@
 # Revision history for pandoc
 
+## pandoc 2.19.1 (2022-08-18)
+
+  * Add server capabilities.
+
+    + New exported module Text.Pandoc.Server [API change].
+    + The pandoc executable now starts up a web server when renamed or
+      symlinked as `pandoc-server`, and functions as a CGI program when
+      renamed or symlinked as `pandoc-server.cgi`.  See the man page for
+      `pandoc-server` for full documentation.
+
+  * Text.Pandoc.App.Opts: Redo `FromJSON` for `Opt` so that optional
+    values can be omitted (in which case the values from
+    `defaultOptions` are used).
+
+  * Org reader: treat "abstract" block as metadata (Albert Krewinkel, #8204).
+    A block of type "abstract" is assumed to define the document's abstract.
+    It is transferred from the main text to the metadata.
+
+  * Org template: add abstract from metadata as block of type "abstract"
+    (#8204).
+
+  * HTML writer: use `flex` property for column widths
+    (Albert Krewinkel, #8232).
+
+  * LaTeX writer:
+
+    + Add label to tables that have an identifier (Albert Krewinkel, #8219).
+      Tables with an identifier are marked with a `\label`. A caption is
+      always included in this case, even if the caption is empty.
+    + Use `\textquotesingle` for straight quotes in text.
+    + Fix widths of multicolumn cells (#8218).
+
+  * LaTeX template: fix behavior of `colorlinks` variable (Albert
+    Krewinkel, #8226). Fixes a regression in 2.19 that required the
+    `boxlinks` variable to be set in addition to the usual link coloring
+    variables. Otherwise links were never colored in LaTeX PDF output.
+
+  * Text.Pandoc.Highlighting: Export `lookupHighlightingStyle`
+    [API change]. Previously this lived in an unexported module
+    Text.Pandoc.App.CommandLineOptions, under the name
+    `lookupHighlightStyle`.
+
+  * Text.Pandoc.App:
+
+    + Remove unneeded MonadIO constraints in readSources.
+    + Factor out `convertWithOpts'` from `convertWithOpts`.
+      This runs in any PandocMonad, MonadIO, MonadMask instance.
+      So far it is not exported, but it might find a use later.
+
+  * Support `--strip-comments` in commonmark/gfm (#8222).
+    This change makes the commonmark reader sensitive to
+    `readerStripComments`.
+
+  * Lua: add function `pandoc.utils.citeproc` (Albert Krewinkel).
+    The function runs the *citeproc* processor on a Pandoc document.
+    Exposing this functionality to Lua allows to make citation processing
+    part of a filter or writer, simplifies the creation of multiple
+    bibliographies, and enables the use of varying citation styles in
+    different parts of a document.
+
+  * Refactor `linux/make_artifacts.sh`.
+
+  * Update INSTALL.md installation from source instructions.
+
+  * Use base64 package instead of base64-bytestring. It is supposed to be
+    faster and more standards-compliant.
+
+  * trypandoc improvements:
+
+    + Add dropdown with canned examples.
+    + Add citeproc support.
+    + Support csv, bibliographic and binary formats.
+    + Add load from file.
+    + Add permalink.  Don't always reload page.
+    + Use vanilla JS and CSS + the new `pandoc-server.cgi`.
+
+  * Allow haddock-library-1.11.0.
+
+  * Convert `tool/extract-changes.hs` to a Lua filter.
+
+
 ## pandoc 2.19 (2022-08-03)
 
   * Add `--embed-resources` flag (Elliot Bobrow, #7331).  This can be
diff --git a/data/templates/default.latex b/data/templates/default.latex
--- a/data/templates/default.latex
+++ b/data/templates/default.latex
@@ -403,10 +403,11 @@
   filecolor={$if(filecolor)$$filecolor$$else$Maroon$endif$},
   citecolor={$if(citecolor)$$citecolor$$else$Blue$endif$},
   urlcolor={$if(urlcolor)$$urlcolor$$else$Blue$endif$},
-$endif$
+$else$
 $if(boxlinks)$
 $else$
   hidelinks,
+$endif$
 $endif$
   pdfcreator={LaTeX via pandoc}}
 
diff --git a/data/templates/default.org b/data/templates/default.org
--- a/data/templates/default.org
+++ b/data/templates/default.org
@@ -13,6 +13,11 @@
 $header-includes$
 
 $endfor$
+$if(abstract)$
+#+begin_abstract
+$abstract$
+#+end_abstract
+$endif$
 $for(include-before)$
 $include-before$
 
diff --git a/man/pandoc.1 b/man/pandoc.1
--- a/man/pandoc.1
+++ b/man/pandoc.1
@@ -1,4 +1,4 @@
-.\" Automatically generated by Pandoc 2.18
+.\" Automatically generated by Pandoc 2.19
 .\"
 .\" Define V font for inline verbatim, using C font in formats
 .\" that render this, and otherwise B font.
@@ -14,7 +14,7 @@
 . ftr VB CB
 . ftr VBI CBI
 .\}
-.TH "Pandoc User\[cq]s Guide" "" "August 3, 2022" "pandoc 2.19" ""
+.TH "Pandoc User\[cq]s Guide" "" "August 18, 2022" "pandoc 2.19.1" ""
 .hy
 .SH NAME
 pandoc - general markup converter
@@ -7960,6 +7960,20 @@
 Some document formats also include a unique identifier.
 For EPUB, this can be set explicitly by setting the \f[V]identifier\f[R]
 metadata field (see EPUB Metadata, above).
+.SH RUNNING PANDOC AS A WEB SERVER
+.PP
+If you rename (or symlink) the pandoc executable to
+\f[V]pandoc-server\f[R], it will start up a web server with a JSON API.
+This server exposes most of the conversion functionality of pandoc.
+For full documentation, see the pandoc-server man page.
+.PP
+If you rename (or symlink) the pandoc executable to
+\f[V]pandoc-server.cgi\f[R], it will function as a CGI program exposing
+the same API as \f[V]pandoc-server\f[R].
+.PP
+\f[V]pandoc-server\f[R] is designed to be maximally secure; it uses
+Haskell\[cq]s type system to provide strong guarantees that no I/O will
+be performed on the server during pandoc conversions.
 .SH A NOTE ON SECURITY
 .IP "1." 3
 Although pandoc itself will not create or modify any files other than
diff --git a/pandoc.cabal b/pandoc.cabal
--- a/pandoc.cabal
+++ b/pandoc.cabal
@@ -1,6 +1,6 @@
 cabal-version:   2.4
 name:            pandoc
-version:         2.19
+version:         2.19.1
 build-type:      Simple
 license:         GPL-2.0-or-later
 license-file:    COPYING.md
@@ -28,7 +28,8 @@
                  - TeX formats (LaTeX, ConTeXt)
                  - XML formats (DocBook 4 and 5, JATS, TEI Simple, OpenDocument)
                  - Outline formats (OPML)
-                 - Bibliography formats (BibTeX, BibLaTeX, CSL JSON, CSL YAML)
+                 - Bibliography formats (BibTeX, BibLaTeX, CSL JSON, CSL YAML,
+                   RIS)
                  - Word processor formats (Docx, RTF, ODT)
                  - Interactive notebook formats (Jupyter notebook ipynb)
                  - Page layout formats (InDesign ICML)
@@ -36,7 +37,7 @@
                    Vimwiki, XWiki, ZimWiki, Jira wiki, Creole)
                  - Slide show formats (LaTeX Beamer, PowerPoint, Slidy,
                    reveal.js, Slideous, S5, DZSlides)
-                 - Data formats (CSV tables)
+                 - Data formats (CSV and TSV tables)
                  - PDF (via external programs such as pdflatex or wkhtmltopdf)
                  .
                  Pandoc can convert mathematical content in documents
@@ -205,8 +206,8 @@
                  man/pandoc.1.before
                  man/pandoc.1.after
                  -- trypandoc
-                 trypandoc/Makefile
                  trypandoc/index.html
+                 trypandoc/trypandoc.js
                  -- tests
                  test/bodybg.gif
                  test/*.native
@@ -428,10 +429,6 @@
   Description:   Embed Lua 5.3 instead of 5.4.
   Default:       False
 
-flag trypandoc
-  Description:   Build trypandoc cgi executable.
-  Default:       False
-
 flag nightly
   Description:   Add '-nightly-COMPILEDATE' to the output of '--version'.
   Default:       False
@@ -474,7 +471,6 @@
                  aeson-pretty          >= 0.8.9    && < 0.9,
                  array                 >= 0.5      && < 0.6,
                  attoparsec            >= 0.12     && < 0.15,
-                 base64-bytestring     >= 0.1      && < 1.3,
                  binary                >= 0.7      && < 0.11,
                  blaze-html            >= 0.9      && < 0.10,
                  blaze-markup          >= 0.8      && < 0.9,
@@ -491,12 +487,13 @@
                  directory             >= 1.2.3    && < 1.4,
                  doclayout             >= 0.4      && < 0.5,
                  doctemplates          >= 0.10     && < 0.11,
+                 base64                >= 0.4      && < 0.5,
                  emojis                >= 0.1      && < 0.2,
                  exceptions            >= 0.8      && < 0.11,
                  file-embed            >= 0.0      && < 0.1,
                  filepath              >= 1.1      && < 1.5,
-                 gridtables            >= 0.0.2    && < 0.1,
-                 haddock-library       >= 1.10     && < 1.11,
+                 gridtables            >= 0.0.3    && < 0.1,
+                 haddock-library       >= 1.10     && < 1.12,
                  hslua-module-doclayout>= 1.0.4    && < 1.1,
                  hslua-module-path     >= 1.0      && < 1.1,
                  hslua-module-system   >= 1.0      && < 1.1,
@@ -537,9 +534,12 @@
                  xml-types             >= 0.3      && < 0.4,
                  yaml                  >= 0.11     && < 0.12,
                  zip-archive           >= 0.2.3.4  && < 0.5,
-                 zlib                  >= 0.5      && < 0.7
+                 zlib                  >= 0.5      && < 0.7,
+                 servant-server,
+                 wai                   >= 0.3
+
   if !os(windows)
-    build-depends:  unix >= 2.4 && < 2.8
+    build-depends:  unix >= 2.4 && < 2.9
   if flag(nightly)
     cpp-options:    -DNIGHTLY
     build-depends:  template-haskell
@@ -563,6 +563,7 @@
                    Text.Pandoc.MediaBag,
                    Text.Pandoc.Error,
                    Text.Pandoc.Filter,
+                   Text.Pandoc.Server,
                    Text.Pandoc.Readers,
                    Text.Pandoc.Readers.HTML,
                    Text.Pandoc.Readers.LaTeX,
@@ -788,20 +789,9 @@
   main-is:         pandoc.hs
   buildable:       True
   other-modules:   Paths_pandoc
-
-executable trypandoc
-  import:          common-executable
-  main-is:         trypandoc.hs
-  hs-source-dirs:  trypandoc
-  if flag(trypandoc)
-    build-depends: aeson,
-                   http-types,
-                   text,
-                   wai >= 0.3,
-                   wai-extra >= 3.0.24
-    buildable:     True
-  else
-    buildable:     False
+  build-depends:   wai-extra             >= 3.0.24,
+                   warp,
+                   safe
 
 test-suite test-pandoc
   import:         common-executable
diff --git a/src/Text/Pandoc/App.hs b/src/Text/Pandoc/App.hs
--- a/src/Text/Pandoc/App.hs
+++ b/src/Text/Pandoc/App.hs
@@ -28,6 +28,7 @@
 import qualified Control.Exception as E
 import Control.Monad ( (>=>), when, forM_ )
 import Control.Monad.Trans ( MonadIO(..) )
+import Control.Monad.Catch ( MonadMask )
 import Control.Monad.Except (throwError, catchError)
 import qualified Data.ByteString as BS
 import qualified Data.ByteString.Char8 as B8
@@ -77,6 +78,7 @@
 
 convertWithOpts :: Opt -> IO ()
 convertWithOpts opts = do
+  let outputFile = fromMaybe "-" (optOutputFile opts)
   datadir <- case optDataDir opts of
                   Nothing   -> do
                     d <- defaultUserDataDir
@@ -86,292 +88,297 @@
                                 else Nothing
                   Just _    -> return $ optDataDir opts
 
-  let outputFile = fromMaybe "-" (optOutputFile opts)
-  let filters = optFilters opts
-  let verbosity = optVerbosity opts
-
   when (optDumpArgs opts) $
     do UTF8.hPutStrLn stdout (T.pack outputFile)
        mapM_ (UTF8.hPutStrLn stdout . T.pack)
              (fromMaybe ["-"] $ optInputFiles opts)
        exitSuccess
 
-  let sources = case optInputFiles opts of
-                     Just xs | not (optIgnoreArgs opts) -> xs
-                     _ -> ["-"]
 #ifdef _WINDOWS
   let istty = True
 #else
   istty <- liftIO $ queryTerminal stdOutput
 #endif
 
-  res <- runIO $ do
+  res <- runIO $ convertWithOpts' istty datadir opts
+  case res of
+    Left e -> E.throwIO e
+    Right (output, reports) -> do
+      case optLogFile opts of
+           Nothing      -> return ()
+           Just logfile -> BL.writeFile logfile (encodeLogMessages reports)
+      let isWarning msg = messageVerbosity msg == WARNING
+      when (optFailIfWarnings opts && any isWarning reports) $
+          E.throwIO PandocFailOnWarningError
+      let eol = case optEol opts of
+                     CRLF   -> IO.CRLF
+                     LF     -> IO.LF
+                     Native -> nativeNewline
+      case output of
+        TextOutput t    -> writerFn eol outputFile t
+        BinaryOutput bs -> writeFnBinary outputFile bs
 
-    setTrace (optTrace opts)
-    setVerbosity verbosity
-    setUserDataDir datadir
-    setResourcePath (optResourcePath opts)
+convertWithOpts' :: (PandocMonad m, MonadIO m, MonadMask m)
+                 => Bool
+                 -> Maybe FilePath
+                 -> Opt
+                 -> m (PandocOutput, [LogMessage])
+convertWithOpts' istty datadir opts = do
+  let outputFile = fromMaybe "-" (optOutputFile opts)
+  let filters = optFilters opts
+  let verbosity = optVerbosity opts
 
-    setInputFiles (fromMaybe ["-"] (optInputFiles opts))
-    setOutputFile (optOutputFile opts)
+  let sources = case optInputFiles opts of
+                     Just xs | not (optIgnoreArgs opts) -> xs
+                     _ -> ["-"]
+  setTrace (optTrace opts)
+  setVerbosity verbosity
+  setUserDataDir datadir
+  setResourcePath (optResourcePath opts)
 
-    -- assign reader and writer based on options and filenames
-    readerName <- case optFrom opts of
-                       Just f  -> return f
-                       Nothing -> case formatFromFilePaths sources of
-                           Just f' -> return f'
-                           Nothing | sources == ["-"] -> return "markdown"
-                                   | any (isURI . T.pack) sources -> return "html"
-                                   | otherwise -> do
-                             report $ CouldNotDeduceFormat
-                                 (map (T.pack . takeExtension) sources) "markdown"
-                             return "markdown"
+  setInputFiles (fromMaybe ["-"] (optInputFiles opts))
+  setOutputFile (optOutputFile opts)
 
-    let readerNameBase = T.takeWhile (\c -> c /= '+' && c /= '-') readerName
+  -- assign reader and writer based on options and filenames
+  readerName <- case optFrom opts of
+                     Just f  -> return f
+                     Nothing -> case formatFromFilePaths sources of
+                         Just f' -> return f'
+                         Nothing | sources == ["-"] -> return "markdown"
+                                 | any (isURI . T.pack) sources -> return "html"
+                                 | otherwise -> do
+                           report $ CouldNotDeduceFormat
+                               (map (T.pack . takeExtension) sources) "markdown"
+                           return "markdown"
 
-    let makeSandboxed pureReader =
-          let files = maybe id (:) (optReferenceDoc opts) .
-                      maybe id (:) (optEpubMetadata opts) .
-                      maybe id (:) (optEpubCoverImage opts) .
-                      maybe id (:) (optCSL opts) .
-                      maybe id (:) (optCitationAbbreviations opts) $
-                      optEpubFonts opts ++
-                      optBibliography opts
-           in  case pureReader of
-                 TextReader r -> TextReader $ \o t -> sandbox files (r o t)
-                 ByteStringReader r
-                            -> ByteStringReader $ \o t -> sandbox files (r o t)
+  let readerNameBase = T.takeWhile (\c -> c /= '+' && c /= '-') readerName
 
-    (reader, readerExts) <-
-      if ".lua" `T.isSuffixOf` readerName
-         then return (TextReader (readCustom (T.unpack readerName)), mempty)
-         else if optSandbox opts
-                 then case runPure (getReader readerName) of
-                        Left e -> throwError e
-                        Right (r, rexts) -> return (makeSandboxed r, rexts)
-                 else getReader readerName
+  let makeSandboxed pureReader =
+        let files = maybe id (:) (optReferenceDoc opts) .
+                    maybe id (:) (optEpubMetadata opts) .
+                    maybe id (:) (optEpubCoverImage opts) .
+                    maybe id (:) (optCSL opts) .
+                    maybe id (:) (optCitationAbbreviations opts) $
+                    optEpubFonts opts ++
+                    optBibliography opts
+         in  case pureReader of
+               TextReader r -> TextReader $ \o t -> sandbox files (r o t)
+               ByteStringReader r
+                          -> ByteStringReader $ \o t -> sandbox files (r o t)
 
-    outputSettings <- optToOutputSettings opts
-    let format = outputFormat outputSettings
-    let writer = outputWriter outputSettings
-    let writerName = outputWriterName outputSettings
-    let writerNameBase = T.takeWhile (\c -> c /= '+' && c /= '-') writerName
-    let writerOptions = outputWriterOptions outputSettings
+  (reader, readerExts) <-
+    if ".lua" `T.isSuffixOf` readerName
+       then return (TextReader (readCustom (T.unpack readerName)), mempty)
+       else if optSandbox opts
+               then case runPure (getReader readerName) of
+                      Left e -> throwError e
+                      Right (r, rexts) -> return (makeSandboxed r, rexts)
+               else getReader readerName
 
-    let pdfOutput = isJust $ outputPdfProgram outputSettings
+  outputSettings <- optToOutputSettings opts
+  let format = outputFormat outputSettings
+  let writer = outputWriter outputSettings
+  let writerName = outputWriterName outputSettings
+  let writerNameBase = T.takeWhile (\c -> c /= '+' && c /= '-') writerName
+  let writerOptions = outputWriterOptions outputSettings
 
-    let bibOutput = writerNameBase == "bibtex" ||
-                    writerNameBase == "biblatex" ||
-                    writerNameBase == "csljson"
+  let pdfOutput = isJust $ outputPdfProgram outputSettings
 
-    let standalone = optStandalone opts ||
-                     not (isTextFormat format) ||
-                     pdfOutput ||
-                     bibOutput
+  let bibOutput = writerNameBase == "bibtex" ||
+                  writerNameBase == "biblatex" ||
+                  writerNameBase == "csljson"
 
-    when (pdfOutput && readerNameBase == "latex") $
-      case optInputFiles opts of
-        Just (inputFile:_) -> report $ UnusualConversion $ T.pack $
-          "to convert a .tex file to PDF, you get better results by using pdflatex "
-            <> "(or lualatex or xelatex) directly, try `pdflatex " <> inputFile
-            <> "` instead of `pandoc " <> inputFile <> " -o " <> outputFile <> "`."
-        _ -> return ()
+  let standalone = optStandalone opts ||
+                   not (isTextFormat format) ||
+                   pdfOutput ||
+                   bibOutput
 
-    -- We don't want to send output to the terminal if the user
-    -- does 'pandoc -t docx input.txt'; though we allow them to
-    -- force this with '-o -'.  On posix systems, we detect
-    -- when stdout is being piped and allow output to stdout
-    -- in that case, but on Windows we can't.
-    when ((pdfOutput || not (isTextFormat format)) &&
-             istty && isNothing ( optOutputFile opts)) $
-      throwError $ PandocAppError $
-              "Cannot write " <> (if pdfOutput then "pdf" else format) <>
-              " output to terminal.\n" <>
-              "Specify an output file using the -o option, or " <>
-              "use '-o -' to force output to stdout."
+  when (pdfOutput && readerNameBase == "latex") $
+    case optInputFiles opts of
+      Just (inputFile:_) -> report $ UnusualConversion $ T.pack $
+        "to convert a .tex file to PDF, you get better results by using pdflatex "
+          <> "(or lualatex or xelatex) directly, try `pdflatex " <> inputFile
+          <> "` instead of `pandoc " <> inputFile <> " -o " <> outputFile <> "`."
+      _ -> return ()
 
+  -- We don't want to send output to the terminal if the user
+  -- does 'pandoc -t docx input.txt'; though we allow them to
+  -- force this with '-o -'.  On posix systems, we detect
+  -- when stdout is being piped and allow output to stdout
+  -- in that case, but on Windows we can't.
+  when ((pdfOutput || not (isTextFormat format)) &&
+           istty && isNothing ( optOutputFile opts)) $
+    throwError $ PandocAppError $
+            "Cannot write " <> (if pdfOutput then "pdf" else format) <>
+            " output to terminal.\n" <>
+            "Specify an output file using the -o option, or " <>
+            "use '-o -' to force output to stdout."
 
-    abbrevs <- Set.fromList . filter (not . T.null) . T.lines . UTF8.toText <$>
-               case optAbbreviations opts of
-                    Nothing -> readDataFile "abbreviations"
-                    Just f  -> readFileStrict f
 
-    case lookupMetaString "lang" (optMetadata opts) of
-           ""      -> setTranslations $ Lang "en" Nothing (Just "US") [] [] []
-           l       -> case parseLang l of
-                           Left _   -> report $ InvalidLang l
-                           Right l' -> setTranslations l'
+  abbrevs <- Set.fromList . filter (not . T.null) . T.lines . UTF8.toText <$>
+             case optAbbreviations opts of
+                  Nothing -> readDataFile "abbreviations"
+                  Just f  -> readFileStrict f
 
-    let readerOpts = def{
-            readerStandalone = standalone
-          , readerColumns = optColumns opts
-          , readerTabStop = optTabStop opts
-          , readerIndentedCodeClasses = optIndentedCodeClasses opts
-          , readerDefaultImageExtension =
-             optDefaultImageExtension opts
-          , readerTrackChanges = optTrackChanges opts
-          , readerAbbreviations = abbrevs
-          , readerExtensions = readerExts
-          , readerStripComments = optStripComments opts
-          }
+  case lookupMetaString "lang" (optMetadata opts) of
+         ""      -> setTranslations $ Lang "en" Nothing (Just "US") [] [] []
+         l       -> case parseLang l of
+                         Left _   -> report $ InvalidLang l
+                         Right l' -> setTranslations l'
 
-    metadataFromFile <-
-      case optMetadataFiles opts of
-        []    -> return mempty
-        paths -> do
-          -- If format is markdown or commonmark, use the enabled extensions,
-          -- otherwise treat metadata as pandoc markdown (see #7926, #6832)
-          let readerOptsMeta =
-                if readerNameBase == "markdown" || readerNameBase == "commonmark"
-                   then readerOpts
-                   else readerOpts{ readerExtensions = pandocExtensions }
-          mconcat <$> mapM
-                (\path -> do raw <- readMetadataFile path
-                             yamlToMeta readerOptsMeta (Just path) raw) paths
+  let readerOpts = def{
+          readerStandalone = standalone
+        , readerColumns = optColumns opts
+        , readerTabStop = optTabStop opts
+        , readerIndentedCodeClasses = optIndentedCodeClasses opts
+        , readerDefaultImageExtension =
+           optDefaultImageExtension opts
+        , readerTrackChanges = optTrackChanges opts
+        , readerAbbreviations = abbrevs
+        , readerExtensions = readerExts
+        , readerStripComments = optStripComments opts
+        }
 
-    let transforms = (case optShiftHeadingLevelBy opts of
-                          0             -> id
-                          x             -> (headerShift x :)) .
-                     (if optStripEmptyParagraphs opts
-                         then (stripEmptyParagraphs :)
-                         else id) .
-                     (if extensionEnabled Ext_east_asian_line_breaks
-                            readerExts &&
-                         not (extensionEnabled Ext_east_asian_line_breaks
-                              (writerExtensions writerOptions) &&
-                              writerWrapText writerOptions == WrapPreserve)
-                         then (eastAsianLineBreakFilter :)
-                         else id) .
-                     (case optIpynbOutput opts of
-                       _ | readerNameBase /= "ipynb" -> id
-                       IpynbOutputAll  -> id
-                       IpynbOutputNone -> (filterIpynbOutput Nothing :)
-                       IpynbOutputBest -> (filterIpynbOutput (Just $
-                                     if htmlFormat format
-                                        then Format "html"
-                                        else
-                                          case format of
-                                            "latex"  -> Format "latex"
-                                            "beamer" -> Format "latex"
-                                            _        -> Format format) :))
-                     $ []
+  metadataFromFile <-
+    case optMetadataFiles opts of
+      []    -> return mempty
+      paths -> do
+        -- If format is markdown or commonmark, use the enabled extensions,
+        -- otherwise treat metadata as pandoc markdown (see #7926, #6832)
+        let readerOptsMeta =
+              if readerNameBase == "markdown" || readerNameBase == "commonmark"
+                 then readerOpts
+                 else readerOpts{ readerExtensions = pandocExtensions }
+        mconcat <$> mapM
+              (\path -> do raw <- readMetadataFile path
+                           yamlToMeta readerOptsMeta (Just path) raw) paths
 
-    let convertTabs = tabFilter (if optPreserveTabs opts ||
-                                      readerNameBase == "t2t" ||
-                                      readerNameBase == "man" ||
-                                      readerNameBase == "tsv"
-                                    then 0
-                                    else optTabStop opts)
+  let transforms = (case optShiftHeadingLevelBy opts of
+                        0             -> id
+                        x             -> (headerShift x :)) .
+                   (if optStripEmptyParagraphs opts
+                       then (stripEmptyParagraphs :)
+                       else id) .
+                   (if extensionEnabled Ext_east_asian_line_breaks
+                          readerExts &&
+                       not (extensionEnabled Ext_east_asian_line_breaks
+                            (writerExtensions writerOptions) &&
+                            writerWrapText writerOptions == WrapPreserve)
+                       then (eastAsianLineBreakFilter :)
+                       else id) .
+                   (case optIpynbOutput opts of
+                     _ | readerNameBase /= "ipynb" -> id
+                     IpynbOutputAll  -> id
+                     IpynbOutputNone -> (filterIpynbOutput Nothing :)
+                     IpynbOutputBest -> (filterIpynbOutput (Just $
+                                   if htmlFormat format
+                                      then Format "html"
+                                      else
+                                        case format of
+                                          "latex"  -> Format "latex"
+                                          "beamer" -> Format "latex"
+                                          _        -> Format format) :))
+                   $ []
 
+  let convertTabs = tabFilter (if optPreserveTabs opts ||
+                                    readerNameBase == "t2t" ||
+                                    readerNameBase == "man" ||
+                                    readerNameBase == "tsv"
+                                  then 0
+                                  else optTabStop opts)
 
-    when (readerNameBase == "markdown_github" ||
-          writerNameBase == "markdown_github") $
-      report $ Deprecated "markdown_github" "Use gfm instead."
 
-    mapM_ (uncurry setRequestHeader) (optRequestHeaders opts)
+  when (readerNameBase == "markdown_github" ||
+        writerNameBase == "markdown_github") $
+    report $ Deprecated "markdown_github" "Use gfm instead."
 
-    setNoCheckCertificate (optNoCheckCertificate opts)
+  mapM_ (uncurry setRequestHeader) (optRequestHeaders opts)
 
-    let isPandocCiteproc (JSONFilter f) = takeBaseName f == "pandoc-citeproc"
-        isPandocCiteproc _              = False
+  setNoCheckCertificate (optNoCheckCertificate opts)
 
-    when (any isPandocCiteproc filters) $
-      report $ Deprecated "pandoc-citeproc filter"
-               "Use --citeproc instead."
+  let isPandocCiteproc (JSONFilter f) = takeBaseName f == "pandoc-citeproc"
+      isPandocCiteproc _              = False
 
-    let cslMetadata =
-          maybe id (setMeta "csl") (optCSL opts) .
-          (case optBibliography opts of
-             [] -> id
-             xs -> setMeta "bibliography" xs) .
-          maybe id (setMeta "citation-abbreviations")
-                         (optCitationAbbreviations opts) $ mempty
+  when (any isPandocCiteproc filters) $
+    report $ Deprecated "pandoc-citeproc filter"
+             "Use --citeproc instead."
 
-    let filterEnv = Environment readerOpts writerOptions
+  let cslMetadata =
+        maybe id (setMeta "csl") (optCSL opts) .
+        (case optBibliography opts of
+           [] -> id
+           xs -> setMeta "bibliography" xs) .
+        maybe id (setMeta "citation-abbreviations")
+                       (optCitationAbbreviations opts) $ mempty
 
-    inputs <- readSources sources
+  let filterEnv = Environment readerOpts writerOptions
 
-    doc <- (case reader of
-             TextReader r
-               | readerNameBase == "json" ->
-                   mconcat <$>
-                      mapM (inputToText convertTabs
-                             >=> r readerOpts . (:[])) inputs
-               | optFileScope opts  ->
-                   mconcat <$> mapM
-                      (inputToText convertTabs
-                             >=> r readerOpts . (:[]))
-                      inputs
-               | otherwise -> mapM (inputToText convertTabs) inputs
-                                >>= r readerOpts
-             ByteStringReader r ->
-               mconcat <$> mapM (r readerOpts . inputToLazyByteString) inputs)
-            >>= ( return . adjustMetadata (metadataFromFile <>)
-              >=> return . adjustMetadata (<> optMetadata opts)
-              >=> return . adjustMetadata (<> cslMetadata)
-              >=> applyTransforms transforms
-              >=> applyFilters filterEnv filters [T.unpack format]
-              >=> (if not (optSandbox opts) &&
-                      (isJust (optExtractMedia opts)
-                       || writerNameBase == "docx") -- for fallback pngs
-                   then fillMediaBag
-                   else return)
-              >=> maybe return extractMedia (optExtractMedia opts)
-              )
+  inputs <- readSources sources
 
-    when (writerNameBase == "docx" && not (optSandbox opts)) $ do
-      -- create fallback pngs for svgs
-      items <- mediaItems <$> getMediaBag
-      forM_ items $ \(fp, mt, bs) ->
-        case T.takeWhile (/=';') mt of
-          "image/svg+xml" -> do
-            res <- svgToPng (writerDpi writerOptions) bs
-            case res of
-              Right bs' -> do
-                let fp' = fp <> ".png"
-                insertMedia fp' (Just "image/png") bs'
-              Left e -> report $ CouldNotConvertImage (T.pack fp) (tshow e)
-          _ -> return ()
+  doc <- (case reader of
+           TextReader r
+             | readerNameBase == "json" ->
+                 mconcat <$>
+                    mapM (inputToText convertTabs
+                           >=> r readerOpts . (:[])) inputs
+             | optFileScope opts  ->
+                 mconcat <$> mapM
+                    (inputToText convertTabs
+                           >=> r readerOpts . (:[]))
+                    inputs
+             | otherwise -> mapM (inputToText convertTabs) inputs
+                              >>= r readerOpts
+           ByteStringReader r ->
+             mconcat <$> mapM (r readerOpts . inputToLazyByteString) inputs)
+          >>= ( return . adjustMetadata (metadataFromFile <>)
+            >=> return . adjustMetadata (<> optMetadata opts)
+            >=> return . adjustMetadata (<> cslMetadata)
+            >=> applyTransforms transforms
+            >=> applyFilters filterEnv filters [T.unpack format]
+            >=> (if not (optSandbox opts) &&
+                    (isJust (optExtractMedia opts)
+                     || writerNameBase == "docx") -- for fallback pngs
+                 then fillMediaBag
+                 else return)
+            >=> maybe return extractMedia (optExtractMedia opts)
+            )
 
-    output <- case writer of
-      ByteStringWriter f -> BinaryOutput <$> f writerOptions doc
-      TextWriter f -> case outputPdfProgram outputSettings of
-        Just pdfProg -> do
-                res <- makePDF pdfProg (optPdfEngineOpts opts) f
-                        writerOptions doc
-                case res of
-                     Right pdf -> return $ BinaryOutput pdf
-                     Left err' -> throwError $ PandocPDFError $
-                                     TL.toStrict (TE.decodeUtf8With TE.lenientDecode err')
+  when (writerNameBase == "docx" && not (optSandbox opts)) $ do
+    -- create fallback pngs for svgs
+    items <- mediaItems <$> getMediaBag
+    forM_ items $ \(fp, mt, bs) ->
+      case T.takeWhile (/=';') mt of
+        "image/svg+xml" -> do
+          res <- svgToPng (writerDpi writerOptions) bs
+          case res of
+            Right bs' -> do
+              let fp' = fp <> ".png"
+              insertMedia fp' (Just "image/png") bs'
+            Left e -> report $ CouldNotConvertImage (T.pack fp) (tshow e)
+        _ -> return ()
 
-        Nothing -> do
-                let ensureNl t
-                      | standalone = t
-                      | T.null t || T.last t /= '\n' = t <> T.singleton '\n'
-                      | otherwise = t
-                textOutput <- ensureNl <$> f writerOptions doc
-                if (optSelfContained opts || optEmbedResources opts) && htmlFormat format
-                   then TextOutput <$> makeSelfContained textOutput
-                   else return $ TextOutput textOutput
-    reports <- getLog
-    return (output, reports)
+  output <- case writer of
+    ByteStringWriter f -> BinaryOutput <$> f writerOptions doc
+    TextWriter f -> case outputPdfProgram outputSettings of
+      Just pdfProg -> do
+              res <- makePDF pdfProg (optPdfEngineOpts opts) f
+                      writerOptions doc
+              case res of
+                   Right pdf -> return $ BinaryOutput pdf
+                   Left err' -> throwError $ PandocPDFError $
+                                   TL.toStrict (TE.decodeUtf8With TE.lenientDecode err')
 
-  case res of
-    Left e -> E.throwIO e
-    Right (output, reports) -> do
-      case optLogFile opts of
-           Nothing      -> return ()
-           Just logfile -> BL.writeFile logfile (encodeLogMessages reports)
-      let isWarning msg = messageVerbosity msg == WARNING
-      when (optFailIfWarnings opts && any isWarning reports) $
-          E.throwIO PandocFailOnWarningError
-      let eol = case optEol opts of
-                     CRLF   -> IO.CRLF
-                     LF     -> IO.LF
-                     Native -> nativeNewline
-      case output of
-        TextOutput t    -> writerFn eol outputFile t
-        BinaryOutput bs -> writeFnBinary outputFile bs
+      Nothing -> do
+              let ensureNl t
+                    | standalone = t
+                    | T.null t || T.last t /= '\n' = t <> T.singleton '\n'
+                    | otherwise = t
+              textOutput <- ensureNl <$> f writerOptions doc
+              if (optSelfContained opts || optEmbedResources opts) && htmlFormat format
+                 then TextOutput <$> makeSelfContained textOutput
+                 else return $ TextOutput textOutput
+  reports <- getLog
+  return (output, reports)
 
 data PandocOutput = TextOutput Text | BinaryOutput BL.ByteString
   deriving (Show)
@@ -393,13 +400,13 @@
 applyTransforms :: Monad m => [Transform] -> Pandoc -> m Pandoc
 applyTransforms transforms d = return $ foldr ($) d transforms
 
-readSources :: (PandocMonad m, MonadIO m)
+readSources :: PandocMonad m
             => [FilePath] -> m [(FilePath, (BS.ByteString, Maybe MimeType))]
 readSources srcs =
   mapM (\fp -> do t <- readSource fp
                   return (if fp == "-" then "" else fp, t)) srcs
 
-readSource :: (PandocMonad m, MonadIO m)
+readSource :: PandocMonad m
            => FilePath -> m (BS.ByteString, Maybe MimeType)
 readSource "-" = (,Nothing) <$> readStdinStrict
 readSource src =
diff --git a/src/Text/Pandoc/App/CommandLineOptions.hs b/src/Text/Pandoc/App/CommandLineOptions.hs
--- a/src/Text/Pandoc/App/CommandLineOptions.hs
+++ b/src/Text/Pandoc/App/CommandLineOptions.hs
@@ -22,12 +22,10 @@
           , parseOptionsFromArgs
           , options
           , engines
-          , lookupHighlightStyle
           , setVariable
           ) where
 import Control.Monad
 import Control.Monad.Trans
-import Control.Monad.Except (throwError)
 import Control.Monad.State.Strict
 import Data.Aeson.Encode.Pretty (encodePretty', Config(..), keyOrder,
          defConfig, Indent(..), NumberFormat(..))
@@ -41,7 +39,7 @@
 import Data.Text (Text)
 import HsLua (Exception, getglobal, openlibs, peek, run, top)
 import Safe (tailDef)
-import Skylighting (Style, Syntax (..), defaultSyntaxMap, parseTheme)
+import Skylighting (Syntax (..), defaultSyntaxMap)
 import System.Console.GetOpt
 import System.Environment (getArgs, getProgName)
 import System.Exit (exitSuccess)
@@ -54,7 +52,7 @@
                             DefaultsState (..), applyDefaults,
                             fullDefaultsPath)
 import Text.Pandoc.Filter (Filter (..))
-import Text.Pandoc.Highlighting (highlightingStyles)
+import Text.Pandoc.Highlighting (highlightingStyles, lookupHighlightingStyle)
 import Text.Pandoc.Shared (ordNub, elemText, safeStrRead, defaultUserDataDir)
 import Text.Printf
 
@@ -946,7 +944,7 @@
                  (ReqArg
                   (\arg opt -> do
                      let write = maybe B.putStr B.writeFile $ optOutputFile opt
-                     sty <- runIOorExplode $ lookupHighlightStyle arg
+                     sty <- runIOorExplode $ lookupHighlightingStyle arg
                      write $ encodePretty'
                        defConfig{confIndent = Spaces 4
                                 ,confCompare = keyOrder
@@ -1058,20 +1056,6 @@
 
 splitField :: String -> (String, String)
 splitField = second (tailDef "true") . break (`elemText` ":=")
-
-lookupHighlightStyle :: PandocMonad m => String -> m Style
-lookupHighlightStyle s
-  | takeExtension s == ".theme" = -- attempt to load KDE theme
-    do contents <- readFileLazy s
-       case parseTheme contents of
-            Left _    -> throwError $ PandocOptionError $ T.pack $
-                           "Could not read highlighting theme " ++ s
-            Right sty -> return sty
-  | otherwise =
-  case lookup (T.toLower $ T.pack s) highlightingStyles of
-       Just sty -> return sty
-       Nothing  -> throwError $ PandocOptionError $ T.pack $
-                      "Unknown highlight-style " ++ s
 
 deprecatedOption :: String -> String -> IO ()
 deprecatedOption o msg =
diff --git a/src/Text/Pandoc/App/Opt.hs b/src/Text/Pandoc/App/Opt.hs
--- a/src/Text/Pandoc/App/Opt.hs
+++ b/src/Text/Pandoc/App/Opt.hs
@@ -54,7 +54,8 @@
 import qualified Data.Map as M
 import qualified Data.ByteString.Char8 as B8
 import Text.Pandoc.Definition (Meta(..), MetaValue(..))
-import Data.Aeson (defaultOptions, Options(..), Result(..), fromJSON, camelTo2)
+import Data.Aeson (defaultOptions, Options(..), Result(..), camelTo2,
+                   genericToJSON, fromJSON)
 import Data.Aeson.TH (deriveJSON)
 import Control.Applicative ((<|>))
 import Data.Yaml
@@ -156,8 +157,90 @@
     , optSandbox               :: Bool
     } deriving (Generic, Show)
 
-$(deriveJSON
-   defaultOptions{ fieldLabelModifier = camelTo2 '-' . drop 3 } ''Opt)
+instance FromJSON Opt where
+   parseJSON = withObject "Opt" $ \o ->
+     Opt
+       <$> o .:? "tab-stop" .!= optTabStop defaultOpts
+       <*> o .:? "preserve-tabs" .!= optPreserveTabs defaultOpts
+       <*> o .:? "standalone" .!= optStandalone defaultOpts
+       <*> o .:? "from"
+       <*> o .:? "to"
+       <*> o .:? "table-of-contents" .!= optTableOfContents defaultOpts
+       <*> o .:? "shift-heading-level-by" .!= optShiftHeadingLevelBy defaultOpts
+       <*> o .:? "template"
+       <*> o .:? "variables" .!= optVariables defaultOpts
+       <*> o .:? "metadata" .!= optMetadata defaultOpts
+       <*> o .:? "metadata-files" .!= optMetadataFiles defaultOpts
+       <*> o .:? "output-file"
+       <*> o .:? "input-files"
+       <*> o .:? "number-sections" .!= optNumberSections defaultOpts
+       <*> o .:? "number-offset" .!= optNumberOffset defaultOpts
+       <*> o .:? "section-divs" .!= optSectionDivs defaultOpts
+       <*> o .:? "incremental" .!= optIncremental defaultOpts
+       <*> o .:? "self-contained" .!= optSelfContained defaultOpts
+       <*> o .:? "embed-resources" .!= optEmbedResources defaultOpts
+       <*> o .:? "html-q-tags" .!= optHtmlQTags defaultOpts
+       <*> o .:? "highlight-style"
+       <*> o .:? "syntax-definitions" .!= optSyntaxDefinitions defaultOpts
+       <*> o .:? "top-level-division" .!= optTopLevelDivision defaultOpts
+       <*> o .:? "html-math-method" .!= optHTMLMathMethod defaultOpts
+       <*> o .:? "abbreviations"
+       <*> o .:? "reference-doc"
+       <*> o .:? "epub-subdirectory" .!= optEpubSubdirectory defaultOpts
+       <*> o .:? "epub-metadata"
+       <*> o .:? "epub-fonts" .!= optEpubFonts defaultOpts
+       <*> o .:? "epub-chapter-level" .!= optEpubChapterLevel defaultOpts
+       <*> o .:? "epub-cover-image"
+       <*> o .:? "toc-depth" .!= optTOCDepth defaultOpts
+       <*> o .:? "dump-args" .!= optDumpArgs defaultOpts
+       <*> o .:? "ignore-args" .!= optIgnoreArgs defaultOpts
+       <*> o .:? "verbosity" .!= optVerbosity defaultOpts
+       <*> o .:? "trace" .!= optTrace defaultOpts
+       <*> o .:? "log-file"
+       <*> o .:? "fail-if-warnings" .!= optFailIfWarnings defaultOpts
+       <*> o .:? "reference-links" .!= optReferenceLinks defaultOpts
+       <*> o .:? "reference-location" .!= optReferenceLocation defaultOpts
+       <*> o .:? "dpi" .!= optDpi defaultOpts
+       <*> o .:? "wrap" .!= optWrap defaultOpts
+       <*> o .:? "columns" .!= optColumns defaultOpts
+       <*> o .:? "filters" .!= optFilters defaultOpts
+       <*> o .:? "email-obfuscation" .!= optEmailObfuscation defaultOpts
+       <*> o .:? "identifier-prefix" .!= optIdentifierPrefix defaultOpts
+       <*> o .:? "strip-empty-paragraphs" .!= optStripEmptyParagraphs defaultOpts
+       <*> o .:? "indented-code-classes" .!= optIndentedCodeClasses defaultOpts
+       <*> o .:? "data-dir"
+       <*> o .:? "cite-method" .!= optCiteMethod defaultOpts
+       <*> o .:? "listings" .!= optListings defaultOpts
+       <*> o .:? "pdf-engine"
+       <*> o .:? "pdf-engine-opts" .!= optPdfEngineOpts defaultOpts
+       <*> o .:? "slide-level"
+       <*> o .:? "setext-headers" .!= optSetextHeaders defaultOpts
+       <*> o .:? "ascii" .!= optAscii defaultOpts
+       <*> o .:? "default-image-extension" .!= optDefaultImageExtension defaultOpts
+       <*> o .:? "extract-media"
+       <*> o .:? "track-changes" .!= optTrackChanges defaultOpts
+       <*> o .:? "file-scope" .!= optFileScope defaultOpts
+       <*> o .:? "title-prefix" .!= optTitlePrefix defaultOpts
+       <*> o .:? "css" .!= optCss defaultOpts
+       <*> o .:? "ipynb-output" .!= optIpynbOutput defaultOpts
+       <*> o .:? "include-before-body" .!= optIncludeBeforeBody defaultOpts
+       <*> o .:? "include-after-body" .!= optIncludeAfterBody defaultOpts
+       <*> o .:? "include-in-header" .!= optIncludeInHeader defaultOpts
+       <*> o .:? "resource-path" .!= optResourcePath defaultOpts
+       <*> o .:? "request-headers" .!= optRequestHeaders defaultOpts
+       <*> o .:? "no-check-certificate" .!= optNoCheckCertificate defaultOpts
+       <*> o .:? "eol" .!= optEol defaultOpts
+       <*> o .:? "strip-comments" .!= optStripComments defaultOpts
+       <*> o .:? "csl"
+       <*> o .:? "bibliography" .!= optBibliography defaultOpts
+       <*> o .:? "citation-abbreviations"
+       <*> o .:? "sandbox" .!= optSandbox defaultOpts
+
+instance ToJSON Opt where
+ toJSON = genericToJSON defaultOptions{
+                                 fieldLabelModifier = camelTo2 '-' . drop 3,
+                                 omitNothingFields = True }
+
 
 instance FromJSON (Opt -> Opt) where
   parseJSON (Object m) =
diff --git a/src/Text/Pandoc/App/OutputSettings.hs b/src/Text/Pandoc/App/OutputSettings.hs
--- a/src/Text/Pandoc/App/OutputSettings.hs
+++ b/src/Text/Pandoc/App/OutputSettings.hs
@@ -37,8 +37,8 @@
 import Text.Pandoc
 import Text.Pandoc.App.FormatHeuristics (formatFromFilePaths)
 import Text.Pandoc.App.Opt (Opt (..))
-import Text.Pandoc.App.CommandLineOptions (engines, lookupHighlightStyle,
-                                          setVariable)
+import Text.Pandoc.App.CommandLineOptions (engines, setVariable)
+import Text.Pandoc.Highlighting (lookupHighlightingStyle)
 import Text.Pandoc.Writers.Custom (writeCustom)
 import qualified Text.Pandoc.UTF8 as UTF8
 
@@ -128,7 +128,8 @@
   syntaxMap <- foldM addSyntaxMap defaultSyntaxMap
                      (optSyntaxDefinitions opts)
 
-  hlStyle <- traverse (lookupHighlightStyle . T.unpack) $ optHighlightStyle opts
+  hlStyle <- traverse (lookupHighlightingStyle . T.unpack) $
+               optHighlightStyle opts
 
   let setVariableM k v = return . setVariable k v
 
diff --git a/src/Text/Pandoc/Class/IO.hs b/src/Text/Pandoc/Class/IO.hs
--- a/src/Text/Pandoc/Class/IO.hs
+++ b/src/Text/Pandoc/Class/IO.hs
@@ -36,7 +36,7 @@
 
 import Control.Monad.Except (throwError)
 import Control.Monad.IO.Class (MonadIO, liftIO)
-import Data.ByteString.Base64 (decodeLenient)
+import Data.ByteString.Base64.URL (decodeBase64Lenient)
 import Data.ByteString.Lazy (toChunks)
 import Data.Text (Text, pack, unpack)
 import Data.Time (TimeZone, UTCTime)
@@ -125,7 +125,7 @@
      let mime     = T.takeWhile (/=',') u''
      let contents = UTF8.fromString $
                      unEscapeString $ T.unpack $ T.drop 1 $ T.dropWhile (/=',') u''
-     return (decodeLenient contents, Just mime)
+     return (decodeBase64Lenient contents, Just mime)
  | otherwise = do
      let toReqHeader (n, v) = (CI.mk (UTF8.fromText n), UTF8.fromText v)
      customHeaders <- map toReqHeader <$> getsCommonState stRequestHeaders
diff --git a/src/Text/Pandoc/Highlighting.hs b/src/Text/Pandoc/Highlighting.hs
--- a/src/Text/Pandoc/Highlighting.hs
+++ b/src/Text/Pandoc/Highlighting.hs
@@ -30,6 +30,7 @@
                                 , breezeDark
                                 , haddock
                                 , Style
+                                , lookupHighlightingStyle
                                 , fromListingsLanguage
                                 , toListingsLanguage
                                 ) where
@@ -39,6 +40,10 @@
 import qualified Data.Text as T
 import Skylighting
 import Text.Pandoc.Definition
+import Text.Pandoc.Class (PandocMonad, readFileLazy)
+import Text.Pandoc.Error (PandocError(..))
+import Control.Monad.Except (throwError)
+import System.FilePath (takeExtension)
 import Text.Pandoc.Shared (safeRead)
 
 highlightingStyles :: [(T.Text, Style)]
@@ -214,3 +219,21 @@
 -- | Determine skylighting language name from listings language name.
 fromListingsLanguage :: T.Text -> Maybe T.Text
 fromListingsLanguage lang = M.lookup lang listingsToLangMap
+
+-- | Lookup style from a name. If the name is a standard style,
+-- load it; if it ends in ".theme", attempt to load a KDE theme
+-- from the file path specified.
+lookupHighlightingStyle :: PandocMonad m => String -> m Style
+lookupHighlightingStyle s
+  | takeExtension s == ".theme" = -- attempt to load KDE theme
+    do contents <- readFileLazy s
+       case parseTheme contents of
+            Left _    -> throwError $ PandocOptionError $ T.pack $
+                           "Could not read highlighting theme " ++ s
+            Right sty -> return sty
+  | otherwise =
+  case lookup (T.toLower $ T.pack s) highlightingStyles of
+       Just sty -> return sty
+       Nothing  -> throwError $ PandocOptionError $ T.pack $
+                      "Unknown highlight-style " ++ s
+
diff --git a/src/Text/Pandoc/Lua/Module/Utils.hs b/src/Text/Pandoc/Lua/Module/Utils.hs
--- a/src/Text/Pandoc/Lua/Module/Utils.hs
+++ b/src/Text/Pandoc/Lua/Module/Utils.hs
@@ -25,7 +25,7 @@
 import Data.Version (Version)
 import HsLua as Lua
 import HsLua.Module.Version (peekVersionFuzzy, pushVersion)
-import Text.Pandoc.Citeproc (getReferences)
+import Text.Pandoc.Citeproc (getReferences, processCitations)
 import Text.Pandoc.Definition
 import Text.Pandoc.Error (PandocError)
 import Text.Pandoc.Lua.Marshal.AST
@@ -58,6 +58,16 @@
             "blocks" ""
       <#> opt (parameter (peekList peekInline) "list of inlines" "inline" "")
       =#> functionResult pushInlines "list of inlines" ""
+
+    , defun "citeproc"
+      ### unPandocLua . processCitations
+      <#> parameter peekPandoc "Pandoc" "doc" "document"
+      =#> functionResult pushPandoc "Pandoc" "processed document"
+      #?  T.unwords
+          [ "Process the citations in the file, replacing them with "
+          , "rendered citations and adding a bibliography. "
+          , "See the manual section on citation rendering for details."
+          ]
 
     , defun "equals"
       ### equal
diff --git a/src/Text/Pandoc/PDF.hs b/src/Text/Pandoc/PDF.hs
--- a/src/Text/Pandoc/PDF.hs
+++ b/src/Text/Pandoc/PDF.hs
@@ -58,6 +58,7 @@
 #endif
 import Data.List (isPrefixOf, find)
 import Text.Pandoc.Class (fillMediaBag, getVerbosity,
+                          readFileLazy, readFileStrict, fileExists,
                           report, extractMedia, PandocMonad)
 import Text.Pandoc.Logging
 
@@ -335,21 +336,21 @@
                 => Maybe String -> String
                 -> m (Maybe ByteString, Maybe ByteString)
 getResultingPDF logFile pdfFile = do
-    pdfExists <- liftIO $ doesFileExist pdfFile
+    pdfExists <- fileExists pdfFile
     pdf <- if pdfExists
               -- We read PDF as a strict bytestring to make sure that the
               -- temp directory is removed on Windows.
               -- See https://github.com/jgm/pandoc/issues/1192.
               then (Just . BL.fromChunks . (:[])) `fmap`
-                   liftIO (BS.readFile pdfFile)
+                   (readFileStrict pdfFile)
               else return Nothing
     -- Note that some things like Missing character warnings
     -- appear in the log but not on stderr, so we prefer the log:
     log' <- case logFile of
               Just logFile' -> do
-                logExists <- liftIO $ doesFileExist logFile'
+                logExists <- fileExists logFile'
                 if logExists
-                  then liftIO $ Just <$> BL.readFile logFile'
+                  then Just <$> readFileLazy logFile'
                   else return Nothing
               Nothing -> return Nothing
     return (log', pdf)
diff --git a/src/Text/Pandoc/Readers/CommonMark.hs b/src/Text/Pandoc/Readers/CommonMark.hs
--- a/src/Text/Pandoc/Readers/CommonMark.hs
+++ b/src/Text/Pandoc/Readers/CommonMark.hs
@@ -34,7 +34,10 @@
                             runF, defaultParserState, option, many1, anyChar,
                             Sources(..), ToSources(..), ParserT, Future,
                             sourceName, sourceLine, incSourceLine)
+import Text.Pandoc.Walk (walk)
 import qualified Data.Text as T
+import qualified Data.Attoparsec.Text as A
+import Control.Applicative ((<|>))
 
 -- | Parse a CommonMark formatted string into a 'Pandoc' structure.
 readCommonMark :: (PandocMonad m, ToSources a)
@@ -86,15 +89,41 @@
      Right (Cm bls :: Cm () Blocks) -> return $ return $ B.toMetaValue bls
 
 readCommonMarkBody :: PandocMonad m => ReaderOptions -> Sources -> [Tok] -> m Pandoc
-readCommonMarkBody opts s toks
-  | isEnabled Ext_sourcepos opts =
-    case runIdentity (parseCommonmarkWith (specFor opts) toks) of
-      Left err -> throwError $ PandocParsecError s err
-      Right (Cm bls :: Cm SourceRange Blocks) -> return $ B.doc bls
-  | otherwise =
-    case runIdentity (parseCommonmarkWith (specFor opts) toks) of
-      Left err -> throwError $ PandocParsecError s err
-      Right (Cm bls :: Cm () Blocks) -> return $ B.doc bls
+readCommonMarkBody opts s toks =
+  (if readerStripComments opts
+      then walk stripBlockComments . walk stripInlineComments
+      else id) <$>
+  if isEnabled Ext_sourcepos opts
+     then case runIdentity (parseCommonmarkWith (specFor opts) toks) of
+            Left err -> throwError $ PandocParsecError s err
+            Right (Cm bls :: Cm SourceRange Blocks) -> return $ B.doc bls
+     else case runIdentity (parseCommonmarkWith (specFor opts) toks) of
+            Left err -> throwError $ PandocParsecError s err
+            Right (Cm bls :: Cm () Blocks) -> return $ B.doc bls
+
+stripBlockComments :: Block -> Block
+stripBlockComments (RawBlock (B.Format "html") s) =
+  RawBlock (B.Format "html") (removeComments s)
+stripBlockComments x = x
+
+stripInlineComments :: Inline -> Inline
+stripInlineComments (RawInline (B.Format "html") s) =
+  RawInline (B.Format "html") (removeComments s)
+stripInlineComments x = x
+
+removeComments :: Text -> Text
+removeComments s =
+  either (const s) id $ A.parseOnly pRemoveComments s
+ where
+  pRemoveComments = mconcat <$> A.many'
+    ("" <$ (A.string "<!--" *> A.scan (0 :: Int) scanChar <* A.char '>') <|>
+     (A.takeWhile1 (/= '<')) <|>
+     (A.string "<"))
+  scanChar st c =
+    case c of
+      '-' -> Just (st + 1)
+      '>' | st >= 2 -> Nothing
+      _ -> Just 0
 
 specFor :: (Monad m, Typeable m, Typeable a,
             Rangeable (Cm a Inlines), Rangeable (Cm a Blocks))
diff --git a/src/Text/Pandoc/Readers/FB2.hs b/src/Text/Pandoc/Readers/FB2.hs
--- a/src/Text/Pandoc/Readers/FB2.hs
+++ b/src/Text/Pandoc/Readers/FB2.hs
@@ -25,7 +25,7 @@
 module Text.Pandoc.Readers.FB2 ( readFB2 ) where
 import Control.Monad.Except (throwError)
 import Control.Monad.State.Strict
-import Data.ByteString.Base64.Lazy
+import Data.ByteString.Lazy.Base64.URL
 import Data.Functor
 import Data.List (intersperse)
 import qualified Data.Map as M
@@ -202,7 +202,7 @@
       report $ IgnoredElement "binary without content-type attribute"
     (Just filename, contentType) ->
       insertMedia (T.unpack filename) contentType
-                    (decodeLenient
+                    (decodeBase64Lenient
                       (UTF8.fromTextLazy . TL.fromStrict . strContent $ e))
 
 -- * Type parsers
diff --git a/src/Text/Pandoc/Readers/HTML.hs b/src/Text/Pandoc/Readers/HTML.hs
--- a/src/Text/Pandoc/Readers/HTML.hs
+++ b/src/Text/Pandoc/Readers/HTML.hs
@@ -27,7 +27,7 @@
 import Control.Monad (guard, msum, mzero, unless, void)
 import Control.Monad.Except (throwError, catchError)
 import Control.Monad.Reader (ask, asks, lift, local, runReaderT)
-import Data.ByteString.Base64 (encode)
+import Data.Text.Encoding.Base64.URL (encodeBase64)
 import Data.Char (isAlphaNum, isLetter)
 import Data.Default (Default (..), def)
 import Data.Foldable (for_)
@@ -785,8 +785,7 @@
   contents <- many (notFollowedBy (pCloses "svg") >> pAny)
   closet <- TagClose "svg" <$ (pCloses "svg" <|> eof)
   let rawText = T.strip $ renderTags' (opent : contents ++ [closet])
-  let svgData = "data:image/svg+xml;base64," <>
-                   UTF8.toText (encode $ UTF8.fromText rawText)
+  let svgData = "data:image/svg+xml;base64," <> encodeBase64 rawText
   return $ B.imageWith (ident,cls,[]) svgData mempty mempty
 
 pCodeWithClass :: PandocMonad m => Text -> Text -> TagParser m Inlines
diff --git a/src/Text/Pandoc/Readers/Haddock.hs b/src/Text/Pandoc/Readers/Haddock.hs
--- a/src/Text/Pandoc/Readers/Haddock.hs
+++ b/src/Text/Pandoc/Readers/Haddock.hs
@@ -72,7 +72,17 @@
     DocHeader h -> B.header (headerLevel h)
                            (docHToInlines False $ headerTitle h)
     DocUnorderedList items -> B.bulletList (map docHToBlocks items)
+#if MIN_VERSION_haddock_library(1,11,0)
+    DocOrderedList items ->
+      B.orderedListWith attr (map (docHToBlocks . snd) items)
+     where
+      attr = (start, DefaultStyle, DefaultDelim)
+      start = case items of
+                [] -> 1
+                ((n,_):_) -> n
+#else
     DocOrderedList items -> B.orderedList (map docHToBlocks items)
+#endif
     DocDefList items -> B.definitionList (map (\(d,t) ->
                                (docHToInlines False d,
                                 [consolidatePlains $ docHToBlocks t])) items)
diff --git a/src/Text/Pandoc/Readers/Org/Blocks.hs b/src/Text/Pandoc/Readers/Org/Blocks.hs
--- a/src/Text/Pandoc/Readers/Org/Blocks.hs
+++ b/src/Text/Pandoc/Readers/Org/Blocks.hs
@@ -191,6 +191,7 @@
       "quote"   -> parseBlockLines (fmap B.blockQuote)
       "verse"   -> verseBlock
       "src"     -> codeBlock blockAttrs
+      "abstract"-> metadataBlock
       _         -> parseBlockLines $
                    let (ident, classes, kv) = attrFromBlockAttributes blockAttrs
                    in fmap $ B.divWith (ident, classes ++ [blkType], kv)
@@ -289,6 +290,16 @@
                       else B.str $ T.map (const '\160') initialSpaces
      line <- parseFromString inlines (indentedLine <> "\n")
      return (trimInlinesF $ pure nbspIndent <> line)
+
+-- | Parses an environment of the given name and adds the result to the document
+-- metadata under a key of the same name.
+metadataBlock :: PandocMonad m => Text -> OrgParser m (F Blocks)
+metadataBlock blockType = try $ do
+  content <- parseBlockLines id blockType
+  meta'   <- orgStateMeta <$> getState
+  updateState $ \st ->
+    st { orgStateMeta = B.setMeta blockType <$> content <*> meta' }
+  return mempty
 
 -- | Read a code block and the associated results block if present.  Which of
 -- the blocks is included in the output is determined using the "exports"
diff --git a/src/Text/Pandoc/SelfContained.hs b/src/Text/Pandoc/SelfContained.hs
--- a/src/Text/Pandoc/SelfContained.hs
+++ b/src/Text/Pandoc/SelfContained.hs
@@ -19,7 +19,7 @@
 import Control.Applicative ((<|>))
 import Control.Monad.Trans (lift)
 import Data.ByteString (ByteString)
-import Data.ByteString.Base64
+import Data.ByteString.Base64.URL (encodeBase64)
 import qualified Data.ByteString.Char8 as B
 import qualified Data.ByteString.Lazy as L
 import qualified Data.Text as T
@@ -45,7 +45,7 @@
 makeDataURI (mime, raw) =
   if textual
      then "data:" <> mime' <> "," <> T.pack (escapeURIString isOk (toString raw))
-     else "data:" <> mime' <> ";base64," <> toText (encode raw)
+     else "data:" <> mime' <> ";base64," <> encodeBase64 raw
   where textual = "text/" `T.isPrefixOf` mime
         mime' = if textual && T.any (== ';') mime
                    then mime <> ";charset=utf-8"
diff --git a/src/Text/Pandoc/Server.hs b/src/Text/Pandoc/Server.hs
new file mode 100644
--- /dev/null
+++ b/src/Text/Pandoc/Server.hs
@@ -0,0 +1,357 @@
+{-# LANGUAGE DataKinds       #-}
+{-# LANGUAGE TypeOperators   #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE OverloadedStrings #-}
+module Text.Pandoc.Server
+    ( app
+    , ServerOpts(..)
+    , Params(..)
+    , Blob(..)
+    , parseServerOpts
+    ) where
+
+import Data.Aeson
+import Network.Wai
+import Servant
+import Text.DocTemplates as DocTemplates
+import Text.Pandoc
+import Text.Pandoc.Citeproc (processCitations)
+import Text.Pandoc.Highlighting (lookupHighlightingStyle)
+import qualified Text.Pandoc.UTF8 as UTF8
+import Data.Text (Text)
+import qualified Data.Text as T
+import qualified Data.Text.Lazy as TL
+import qualified Data.Text.Lazy.Encoding as TLE
+import Data.Maybe (fromMaybe)
+import Data.Char (isAlphaNum)
+import qualified Data.ByteString as BS
+import qualified Data.ByteString.Lazy as BL
+import Data.ByteString.Base64 (decodeBase64, encodeBase64)
+import Data.Default
+import Control.Monad (when, foldM)
+import qualified Data.Set as Set
+import Skylighting (defaultSyntaxMap)
+import qualified Data.Map as M
+import System.Console.GetOpt
+import System.Environment (getArgs, getProgName)
+import qualified Control.Exception as E
+import Text.Pandoc.Shared (safeStrRead, headerShift, filterIpynbOutput,
+                           eastAsianLineBreakFilter, stripEmptyParagraphs)
+import Text.Pandoc.App.Opt ( IpynbOutput (..), Opt(..), defaultOpts )
+import Text.Pandoc.Filter (Filter(..))
+import Text.Pandoc.Builder (setMeta)
+import Text.Pandoc.SelfContained (makeSelfContained)
+import System.Exit
+
+data ServerOpts =
+  ServerOpts
+    { serverPort    :: Int
+    , serverTimeout :: Int }
+  deriving (Show)
+
+defaultServerOpts :: ServerOpts
+defaultServerOpts = ServerOpts { serverPort = 3030, serverTimeout = 2 }
+
+cliOptions :: [OptDescr (ServerOpts -> IO ServerOpts)]
+cliOptions =
+  [ Option ['p'] ["port"]
+      (ReqArg (\s opts -> case safeStrRead s of
+                            Just i -> return opts{ serverPort = i }
+                            Nothing ->
+                              E.throwIO $ PandocOptionError $ T.pack
+                                s <> " is not a number") "NUMBER")
+      "port number"
+  , Option ['t'] ["timeout"]
+      (ReqArg (\s opts -> case safeStrRead s of
+                            Just i -> return opts{ serverTimeout = i }
+                            Nothing ->
+                              E.throwIO $ PandocOptionError $ T.pack
+                                s <> " is not a number") "NUMBER")
+      "timeout (seconds)"
+
+  , Option ['h'] ["help"]
+      (NoArg (\_ -> do
+        prg <- getProgName
+        let header = "Usage: " <> prg <> " [OPTION...]"
+        putStrLn $ usageInfo header cliOptions
+        exitWith ExitSuccess))
+      "help message"
+
+  , Option ['v'] ["version"]
+      (NoArg (\_ -> do
+        prg <- getProgName
+        putStrLn $ prg <> " " <> T.unpack pandocVersion
+        exitWith ExitSuccess))
+      "version info"
+
+  ]
+
+parseServerOpts :: IO ServerOpts
+parseServerOpts = do
+  args <- getArgs
+  let handleUnknownOpt x = "Unknown option: " <> x
+  case getOpt' Permute cliOptions args of
+    (os, ns, unrecognizedOpts, es) -> do
+      when (not (null es) || not (null unrecognizedOpts)) $
+        E.throwIO $ PandocOptionError $ T.pack $
+          concat es ++ unlines (map handleUnknownOpt unrecognizedOpts) ++
+          ("Try --help for more information.")
+      when (not (null ns)) $
+        E.throwIO $ PandocOptionError $ T.pack $
+                     "Unknown arguments: " <> unwords ns
+      foldM (flip ($)) defaultServerOpts os
+
+newtype Blob = Blob BL.ByteString
+  deriving (Show, Eq)
+
+instance ToJSON Blob where
+  toJSON (Blob bs) = toJSON (encodeBase64 $ BL.toStrict bs)
+
+instance FromJSON Blob where
+ parseJSON = withText "Blob" $ \t -> do
+   let inp = UTF8.fromText t
+   case decodeBase64 inp of
+        Right bs -> return $ Blob $ BL.fromStrict bs
+        Left _ -> -- treat as regular text
+                    return $ Blob $ BL.fromStrict inp
+
+-- This is the data to be supplied by the JSON payload
+-- of requests.  Maybe values may be omitted and will be
+-- given default values.
+data Params = Params
+  { options               :: Opt
+  , text                  :: Text
+  , files                 :: Maybe (M.Map FilePath Blob)
+  } deriving (Show)
+
+instance Default Params where
+  def = Params
+    { options = defaultOpts
+    , text = mempty
+    , files = Nothing
+    }
+
+-- Automatically derive code to convert to/from JSON.
+instance FromJSON Params where
+ parseJSON = withObject "Params" $ \o ->
+   Params
+     <$> parseJSON (Object o)
+     <*> o .: "text"
+     <*> o .:? "files"
+
+
+-- This is the API.  The "/convert" endpoint takes a request body
+-- consisting of a JSON-encoded Params structure and responds to
+-- Get requests with either plain text or JSON, depending on the
+-- Accept header.
+type API =
+  ReqBody '[JSON] Params :> Post '[PlainText, JSON] Text
+  :<|>
+  ReqBody '[JSON] Params :> Post '[OctetStream] BS.ByteString
+  :<|>
+  "batch" :> ReqBody '[JSON] [Params] :> Post '[JSON] [Text]
+  :<|>
+  "babelmark" :> QueryParam' '[Required] "text" Text :> QueryParam "from" Text :> QueryParam "to" Text :> QueryFlag "standalone" :> Get '[JSON] Value
+  :<|>
+  "version" :> Get '[PlainText, JSON] Text
+
+app :: Application
+app = serve api server
+
+api :: Proxy API
+api = Proxy
+
+server :: Server API
+server = convert
+    :<|> convertBytes
+    :<|> mapM convert
+    :<|> babelmark  -- for babelmark which expects {"html": "", "version": ""}
+    :<|> pure pandocVersion
+ where
+  babelmark text' from' to' standalone' = do
+    res <- convert def{ text = text',
+                        options = defaultOpts{
+                          optFrom = from',
+                          optTo = to',
+                          optStandalone = standalone' }
+                      }
+    return $ toJSON $ object [ "html" .= res, "version" .= pandocVersion ]
+
+  -- We use runPure for the pandoc conversions, which ensures that
+  -- they will do no IO.  This makes the server safe to use.  However,
+  -- it will mean that features requiring IO, like RST includes, will not work.
+  -- Changing this to
+  --    handleErr =<< liftIO (runIO (convert' params))
+  -- will allow the IO operations.
+  convert params = handleErr $
+    runPure (convert' id (encodeBase64 . BL.toStrict) params)
+
+  convertBytes params = handleErr $
+    runPure (convert' UTF8.fromText BL.toStrict params)
+
+  convert' :: (Text -> a) -> (BL.ByteString -> a) -> Params -> PandocPure a
+  convert' textHandler bsHandler params = do
+    curtime <- getCurrentTime
+    -- put files params in ersatz file system
+    let addFile :: FilePath -> Blob -> FileTree -> FileTree
+        addFile fp (Blob lbs) =
+          insertInFileTree fp FileInfo{ infoFileMTime = curtime
+                                      , infoFileContents = BL.toStrict lbs }
+    case files params of
+      Nothing -> return ()
+      Just fs -> do
+        let filetree = M.foldrWithKey addFile mempty fs
+        modifyPureState $ \st -> st{ stFiles = filetree }
+
+    let opts = options params
+    let readerFormat = fromMaybe "markdown" $ optFrom opts
+    let writerFormat = fromMaybe "html" $ optTo opts
+    (readerSpec, readerExts) <- getReader readerFormat
+    (writerSpec, writerExts) <- getWriter writerFormat
+
+    let isStandalone = optStandalone opts
+    let toformat = T.toLower $ T.takeWhile isAlphaNum $ writerFormat
+    hlStyle <- traverse (lookupHighlightingStyle . T.unpack)
+                  $ optHighlightStyle opts
+
+    mbTemplate <- if isStandalone
+                     then case optTemplate opts of
+                            Nothing -> Just <$>
+                              compileDefaultTemplate toformat
+                            Just t  -> Just <$>
+                              compileCustomTemplate toformat t
+                     else return Nothing
+
+    abbrevs <- Set.fromList . filter (not . T.null) . T.lines . UTF8.toText <$>
+                 case optAbbreviations opts of
+                      Nothing -> readDataFile "abbreviations"
+                      Just f  -> readFileStrict f
+
+    let readeropts = def{ readerExtensions = readerExts
+                        , readerStandalone = isStandalone
+                        , readerTabStop = optTabStop opts
+                        , readerIndentedCodeClasses =
+                            optIndentedCodeClasses opts
+                        , readerAbbreviations = abbrevs
+                        , readerDefaultImageExtension =
+                            optDefaultImageExtension opts
+                        , readerTrackChanges = optTrackChanges opts
+                        , readerStripComments = optStripComments opts
+                        }
+    let writeropts =
+          def{ writerExtensions = writerExts
+             , writerTabStop = optTabStop opts
+             , writerWrapText = optWrap opts
+             , writerColumns = optColumns opts
+             , writerTemplate = mbTemplate
+             , writerSyntaxMap = defaultSyntaxMap
+             , writerVariables = optVariables opts
+             , writerTableOfContents = optTableOfContents opts
+             , writerIncremental = optIncremental opts
+             , writerHTMLMathMethod = optHTMLMathMethod opts
+             , writerNumberSections = optNumberSections opts
+             , writerNumberOffset = optNumberOffset opts
+             , writerSectionDivs = optSectionDivs opts
+             , writerReferenceLinks = optReferenceLinks opts
+             , writerDpi = optDpi opts
+             , writerEmailObfuscation = optEmailObfuscation opts
+             , writerIdentifierPrefix = optIdentifierPrefix opts
+             , writerCiteMethod = optCiteMethod opts
+             , writerHtmlQTags = optHtmlQTags opts
+             , writerSlideLevel = optSlideLevel opts
+             , writerTopLevelDivision = optTopLevelDivision opts
+             , writerListings = optListings opts
+             , writerHighlightStyle = hlStyle
+             , writerSetextHeaders = optSetextHeaders opts
+             , writerEpubSubdirectory = T.pack $ optEpubSubdirectory opts
+             , writerEpubMetadata = T.pack <$> optEpubMetadata opts
+             , writerEpubFonts = optEpubFonts opts
+             , writerEpubChapterLevel = optEpubChapterLevel opts
+             , writerTOCDepth = optTOCDepth opts
+             , writerReferenceDoc = optReferenceDoc opts
+             , writerReferenceLocation = optReferenceLocation opts
+             , writerPreferAscii = optAscii opts
+             }
+    let reader = case readerSpec of
+                TextReader r -> r readeropts
+                ByteStringReader r -> \t -> do
+                  let eitherbs = decodeBase64 $ UTF8.fromText t
+                  case eitherbs of
+                    Left errt -> throwError $ PandocSomeError errt
+                    Right bs -> r readeropts $ BL.fromStrict bs
+    let writer = case writerSpec of
+                TextWriter w ->
+                  fmap textHandler .
+                  (\d -> w writeropts d >>=
+                         if optEmbedResources opts && htmlFormat (optTo opts)
+                            then makeSelfContained
+                            else return)
+                ByteStringWriter w -> fmap bsHandler . w writeropts
+
+    let transforms :: Pandoc -> Pandoc
+        transforms = (case optShiftHeadingLevelBy opts of
+                        0             -> id
+                        x             -> headerShift x) .
+                   (case optStripEmptyParagraphs opts of
+                        True          -> stripEmptyParagraphs
+                        False         -> id) .
+                   (if extensionEnabled Ext_east_asian_line_breaks
+                          readerExts &&
+                       not (extensionEnabled Ext_east_asian_line_breaks
+                              writerExts &&
+                            optWrap opts == WrapPreserve)
+                       then eastAsianLineBreakFilter
+                       else id) .
+                   (case optIpynbOutput opts of
+                     IpynbOutputAll  -> id
+                     IpynbOutputNone -> filterIpynbOutput Nothing
+                     IpynbOutputBest -> filterIpynbOutput (Just $
+                       case optTo opts of
+                            Just "latex"  -> Format "latex"
+                            Just "beamer" -> Format "latex"
+                            Nothing       -> Format "html"
+                            Just f
+                              | htmlFormat (optTo opts) -> Format "html"
+                              | otherwise -> Format f))
+
+    let meta =   (case optBibliography opts of
+                   [] -> id
+                   fs -> setMeta "bibliography" (MetaList
+                            (map (MetaString . T.pack) fs))) .
+                 maybe id (setMeta "csl" . MetaString . T.pack)
+                   (optCSL opts) .
+                 maybe id (setMeta "citation-abbreviations" . MetaString .
+                              T.pack)
+                   (optCitationAbbreviations opts) $
+                 optMetadata opts
+
+    let addMetadata m' (Pandoc m bs) = Pandoc (m <> m') bs
+
+    let hasCiteprocFilter [] = False
+        hasCiteprocFilter (CiteprocFilter:_) = True
+        hasCiteprocFilter (_:xs) = hasCiteprocFilter xs
+
+    reader (text params) >>=
+      return . transforms . addMetadata meta >>=
+      (if hasCiteprocFilter (optFilters opts)
+          then processCitations
+          else return) >>=
+      writer
+
+  htmlFormat :: Maybe Text -> Bool
+  htmlFormat Nothing = True
+  htmlFormat (Just f) =
+    any (`T.isPrefixOf` f)
+      ["html","html4","html5","s5","slidy", "slideous","dzslides","revealjs"]
+
+  handleErr (Right t) = return t
+  handleErr (Left err) = throwError $
+    err500 { errBody = TLE.encodeUtf8 $ TL.fromStrict $ renderError err }
+
+  compileCustomTemplate toformat t = do
+    res <- runWithPartials $ compileTemplate ("custom." <> T.unpack toformat)
+               (T.pack t)
+    case res of
+      Left e -> throwError $ PandocTemplateError (T.pack e)
+      Right tpl -> return tpl
+
diff --git a/src/Text/Pandoc/Writers/FB2.hs b/src/Text/Pandoc/Writers/FB2.hs
--- a/src/Text/Pandoc/Writers/FB2.hs
+++ b/src/Text/Pandoc/Writers/FB2.hs
@@ -21,14 +21,13 @@
 import Control.Monad (zipWithM)
 import Control.Monad.Except (catchError, throwError)
 import Control.Monad.State.Strict (StateT, evalStateT, get, gets, lift, liftM, modify)
-import Data.ByteString.Base64 (encode)
+import Data.ByteString.Base64.URL (encodeBase64)
 import Data.Char (isAscii, isControl, isSpace)
 import Data.Either (lefts, rights)
 import Data.List (intercalate)
 import Data.Text (Text)
 import qualified Data.Text as T
 import qualified Data.Text.Lazy as TL
-import qualified Data.Text.Encoding as TE
 import Text.Pandoc.Network.HTTP (urlEncode)
 import Text.Pandoc.XML.Light as X
 
@@ -237,7 +236,7 @@
                                report $ CouldNotDetermineMimeType link
                                return Nothing
                              Just mime -> return $ Just (mime,
-                                                      TE.decodeUtf8 $ encode bs))
+                                                        encodeBase64 bs))
                     (\e ->
                        do report $ CouldNotFetchResource link (tshow e)
                           return Nothing)
diff --git a/src/Text/Pandoc/Writers/HTML.hs b/src/Text/Pandoc/Writers/HTML.hs
--- a/src/Text/Pandoc/Writers/HTML.hs
+++ b/src/Text/Pandoc/Writers/HTML.hs
@@ -876,7 +876,7 @@
   let isCslBibEntry = "csl-entry" `elem` classes
   let kvs = [(k,v) | (k,v) <- kvs'
                    , k /= "width" || "column" `notElem` classes] ++
-            [("style", "width:" <> w <> ";") | "column" `elem` classes
+            [("style", "flex:" <> w <> ";")  | "column" `elem` classes
                                              , ("width", w) <- kvs'] ++
             [("role", "doc-bibliography") | isCslBibBody && html5] ++
             [("role", "doc-biblioentry") | isCslBibEntry && html5]
diff --git a/src/Text/Pandoc/Writers/LaTeX/Table.hs b/src/Text/Pandoc/Writers/LaTeX/Table.hs
--- a/src/Text/Pandoc/Writers/LaTeX/Table.hs
+++ b/src/Text/Pandoc/Writers/LaTeX/Table.hs
@@ -33,6 +33,7 @@
 import Text.Pandoc.Writers.LaTeX.Types
   ( LW, WriterState (stBeamer, stExternalNotes, stInMinipage, stMultiRow
                     , stNotes, stTable) )
+import Text.Pandoc.Writers.LaTeX.Util (labelFor)
 import Text.Printf (printf)
 import qualified Text.Pandoc.Builder as B
 import qualified Text.Pandoc.Writers.AnnotatedTable as Ann
@@ -43,8 +44,8 @@
              -> Ann.Table
              -> LW m (Doc Text)
 tableToLaTeX inlnsToLaTeX blksToLaTeX tbl = do
-  let (Ann.Table _attr caption specs thead tbodies tfoot) = tbl
-  CaptionDocs capt captNotes <- captionToLaTeX inlnsToLaTeX caption
+  let (Ann.Table (ident, _, _) caption specs thead tbodies tfoot) = tbl
+  CaptionDocs capt captNotes <- captionToLaTeX inlnsToLaTeX caption ident
   let removeNote (Note _) = Span ("", [], []) []
       removeNote x        = x
   let colCount = ColumnCount $ length specs
@@ -144,16 +145,20 @@
 captionToLaTeX :: PandocMonad m
                => ([Inline] -> LW m (Doc Text))
                -> Caption
+               -> Text     -- ^ table identifier (label)
                -> LW m CaptionDocs
-captionToLaTeX inlnsToLaTeX (Caption _maybeShort longCaption) = do
+captionToLaTeX inlnsToLaTeX (Caption _maybeShort longCaption) ident = do
   let caption = blocksToInlines longCaption
-  (captionText, captForLof, captNotes) <- getCaption inlnsToLaTeX False caption
+  (captionText, captForLot, captNotes) <- getCaption inlnsToLaTeX False caption
+  label <- labelFor ident
   return $ CaptionDocs
     { captionNotes = captNotes
-    , captionCommand = if isEmpty captionText
+    , captionCommand = if isEmpty captionText && isEmpty label
                        then empty
-                       else "\\caption" <> captForLof <>
-                            braces captionText <> "\\tabularnewline"
+                       else "\\caption" <> captForLot <>
+                            braces captionText
+                            <> label
+                            <> "\\tabularnewline"
     }
 
 type BlocksWriter m = [Block] -> LW m (Doc Text)
@@ -330,20 +335,15 @@
       width = sum $ NonEmpty.map toWidth colWidths
 
       -- no column separators at beginning of first and end of last column.
-      numseps = (case colnum + 1 of
-                   1 -> 0  -- Not sure why this case is needed (tarleb)
-                   _ -> -- the final cell has only one tabcolsep
-                        if colnum + colspan == numcols
-                        then 1
-                        else 2) :: Int
       skipColSep = "@{}" :: String
   in T.pack $
-     printf "%s>{%s\\arraybackslash}p{%0.4f\\columnwidth - %d\\tabcolsep}%s"
+     printf "%s>{%s\\arraybackslash}p{(\\columnwidth - %d\\tabcolsep) * \\real{%0.4f} + %d\\tabcolsep}%s"
             (if colnum == 0 then skipColSep else "")
             (T.unpack (alignCommand align))
+            (2 * (numcols - 1))
             width
-            numseps
-            (if colnum + colspan == numcols then skipColSep else "")
+            (2 * (colspan - 1))
+            (if colnum + colspan >= numcols then skipColSep else "")
 
 -- | Perform a conversion, assuming that the context is a minipage.
 inMinipage :: Monad m => LW m a -> LW m a
diff --git a/src/Text/Pandoc/Writers/LaTeX/Util.hs b/src/Text/Pandoc/Writers/LaTeX/Util.hs
--- a/src/Text/Pandoc/Writers/LaTeX/Util.hs
+++ b/src/Text/Pandoc/Writers/LaTeX/Util.hs
@@ -120,7 +120,7 @@
          '>' -> emitcseq "\\textgreater"
          '[' -> emits "{[}"  -- to avoid interpretation as
          ']' -> emits "{]}"  -- optional arguments
-         '\'' | ctx == CodeString -> emitcseq "\\textquotesingle"
+         '\'' -> emitcseq "\\textquotesingle"
          '\160' -> emits "~"
          '\x200B' -> emits "\\hspace{0pt}"  -- zero-width space
          '\x202F' -> emits "\\,"
diff --git a/stack.yaml b/stack.yaml
--- a/stack.yaml
+++ b/stack.yaml
@@ -1,6 +1,5 @@
 flags:
   pandoc:
-    trypandoc: false
     embed_data_files: true
   QuickCheck:
     old-random: false
@@ -13,7 +12,7 @@
 - skylighting-format-latex-0.1
 - skylighting-format-blaze-html-0.1
 - emojis-0.1.2
-- gridtables-0.0.2.0
+- gridtables-0.0.3.0
 - lpeg-1.0.3
 - hslua-2.2.1
 - hslua-aeson-2.2.1
@@ -37,7 +36,7 @@
 - unicode-data-0.3.0
 - commonmark-pandoc-0.2.1.2
 - ipynb-0.2
-- pandoc-types-1.22.2
+- pandoc-types-1.22.2.1
 - commonmark-0.2.2
 - commonmark-extensions-0.2.3.2
 - doclayout-0.4
diff --git a/test/command/1710.md b/test/command/1710.md
--- a/test/command/1710.md
+++ b/test/command/1710.md
@@ -19,17 +19,17 @@
 <section id="slide-one" class="slide level1">
 <h1>Slide one</h1>
 <div class="columns">
-<div class="column" style="width:40%;">
+<div class="column" style="flex:40%;">
 <ul>
 <li>a</li>
 <li>b</li>
 </ul>
-</div><div class="column" style="width:40%;">
+</div><div class="column" style="flex:40%;">
 <ul>
 <li>c</li>
 <li>d</li>
 </ul>
-</div><div class="column" style="width:10%;">
+</div><div class="column" style="flex:10%;">
 <p>ok</p>
 </div>
 </div>
diff --git a/test/command/8204.md b/test/command/8204.md
new file mode 100644
--- /dev/null
+++ b/test/command/8204.md
@@ -0,0 +1,39 @@
+Include abstract in Org output.
+```
+% pandoc --to=org --standalone
+---
+abstract: |
+  This is an *abstract*.
+
+  It has multiple paragraphs.
+---
+^D
+#+begin_abstract
+This is an /abstract/.
+
+It has multiple paragraphs.
+
+#+end_abstract
+```
+
+Parse abstract environment as *abstract* metadata field.
+```
+% pandoc --from=org --to=markdown --standalone
+#+begin_abstract
+
+This is an /abstract/.
+
+It has multiple paragraphs.
+#+end_abstract
+
+Main text
+^D
+---
+abstract: |
+  This is an *abstract*.
+
+  It has multiple paragraphs.
+---
+
+Main text
+```
diff --git a/test/command/8216.md b/test/command/8216.md
new file mode 100644
--- /dev/null
+++ b/test/command/8216.md
@@ -0,0 +1,51 @@
+Misaligned separators in grid table
+```
+% pandoc -f markdown -t html
+: Grid Table
+
++-----------------+:-:+
+|Some text        |[text]{.class1 .class2 .class3}|
++-----------------+---+
+|Some text        |[text]{.class1 .class2 .class3}|
++-----------------+---+
+|Some text        |[text]{.class1 .class2 .class3}|
++-----------------+---+
+^D
+<table style="width:69%;">
+<caption>Grid Table</caption>
+<colgroup>
+<col style="width: 25%" />
+<col style="width: 44%" />
+</colgroup>
+<tbody>
+<tr class="odd">
+<td>Some text</td>
+<td><span class="class1 class2 class3">text</span></td>
+</tr>
+<tr class="even">
+<td>Some text</td>
+<td><span class="class1 class2 class3">text</span></td>
+</tr>
+<tr class="odd">
+<td>Some text</td>
+<td><span class="class1 class2 class3">text</span></td>
+</tr>
+</tbody>
+</table>
+```
+
+Missing cell
+
+```
+% pandoc -f markdown -t gfm
++------+
+| text |
++------+---+
+| text | 1 |
++------+---+
+^D
+|      |     |
+|------|-----|
+| text |     |
+| text | 1   |
+```
diff --git a/test/command/8219.md b/test/command/8219.md
new file mode 100644
--- /dev/null
+++ b/test/command/8219.md
@@ -0,0 +1,14 @@
+```
+% pandoc -f html -t latex
+<table id="test">
+  <tr><td>one</td><td>two</td></tr>
+</table>
+^D
+\begin{longtable}[]{@{}ll@{}}
+\caption{}\label{test}\tabularnewline
+\toprule()
+\endhead
+one & two \\
+\bottomrule()
+\end{longtable}
+```
diff --git a/test/tables/nordics.latex b/test/tables/nordics.latex
--- a/test/tables/nordics.latex
+++ b/test/tables/nordics.latex
@@ -3,7 +3,7 @@
   >{\raggedright\arraybackslash}p{(\columnwidth - 6\tabcolsep) * \real{0.3000}}
   >{\raggedright\arraybackslash}p{(\columnwidth - 6\tabcolsep) * \real{0.2000}}
   >{\raggedright\arraybackslash}p{(\columnwidth - 6\tabcolsep) * \real{0.2000}}@{}}
-\caption{States belonging to the \emph{Nordics.}}\tabularnewline
+\caption{States belonging to the \emph{Nordics.}}\label{nordics}\tabularnewline
 \toprule()
 \begin{minipage}[b]{\linewidth}\centering
 Name
diff --git a/test/tables/planets.latex b/test/tables/planets.latex
--- a/test/tables/planets.latex
+++ b/test/tables/planets.latex
@@ -1,20 +1,20 @@
 \begin{longtable}[]{@{}cclrrrrrrrrl@{}}
 \caption{Data about the planets of our solar system.}\tabularnewline
 \toprule()
-\multicolumn{2}{@{}>{\centering\arraybackslash}p{0.0000\columnwidth - 0\tabcolsep}}{%
+\multicolumn{2}{@{}>{\centering\arraybackslash}p{(\columnwidth - 22\tabcolsep) * \real{0.0000} + 2\tabcolsep}}{%
 } & Name & Mass (10\^{}24kg) & Diameter (km) & Density (kg/m\^{}3) & Gravity
 (m/s\^{}2) & Length of day (hours) & Distance from Sun (10\^{}6km) & Mean
 temperature (C) & Number of moons & Notes \\
 \midrule()
 \endfirsthead
 \toprule()
-\multicolumn{2}{@{}>{\centering\arraybackslash}p{0.0000\columnwidth - 0\tabcolsep}}{%
+\multicolumn{2}{@{}>{\centering\arraybackslash}p{(\columnwidth - 22\tabcolsep) * \real{0.0000} + 2\tabcolsep}}{%
 } & Name & Mass (10\^{}24kg) & Diameter (km) & Density (kg/m\^{}3) & Gravity
 (m/s\^{}2) & Length of day (hours) & Distance from Sun (10\^{}6km) & Mean
 temperature (C) & Number of moons & Notes \\
 \midrule()
 \endhead
-\multicolumn{2}{@{}>{\centering\arraybackslash}p{0.0000\columnwidth - 0\tabcolsep}}{%
+\multicolumn{2}{@{}>{\centering\arraybackslash}p{(\columnwidth - 22\tabcolsep) * \real{0.0000} + 2\tabcolsep}}{%
 \multirow{4}{*}{Terrestrial planets}} & Mercury & 0.330 & 4,879 & 5427 & 3.7 &
 4222.6 & 57.9 & 167 & 0 & Closest to the Sun \\
 & & Venus & 4.87 & 12,104 & 5243 & 8.9 & 2802.0 & 108.2 & 464 & 0 & \\
@@ -27,7 +27,7 @@
 & \multirow{2}{*}{Ice giants} & Uranus & 86.8 & 51,118 & 1271 & 8.7 & 17.2 &
 2872.5 & -195 & 27 & \\
 & & Neptune & 102 & 49,528 & 1638 & 11.0 & 16.1 & 4495.1 & -200 & 14 & \\
-\multicolumn{2}{@{}>{\centering\arraybackslash}p{0.0000\columnwidth - 0\tabcolsep}}{%
+\multicolumn{2}{@{}>{\centering\arraybackslash}p{(\columnwidth - 22\tabcolsep) * \real{0.0000} + 2\tabcolsep}}{%
 Dwarf planets} & Pluto & 0.0146 & 2,370 & 2095 & 0.7 & 153.3 & 5906.4 & -225 & 5
 & Declassified as a planet in 2006. \\
 \bottomrule()
diff --git a/test/tables/students.latex b/test/tables/students.latex
--- a/test/tables/students.latex
+++ b/test/tables/students.latex
@@ -1,7 +1,7 @@
 \begin{longtable}[]{@{}
   >{\raggedright\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{0.5000}}
   >{\raggedright\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{0.5000}}@{}}
-\caption{List of Students}\tabularnewline
+\caption{List of Students}\label{students}\tabularnewline
 \toprule()
 \begin{minipage}[b]{\linewidth}\centering
 Student ID
@@ -18,15 +18,15 @@
 \end{minipage} \\
 \midrule()
 \endhead
-\multicolumn{2}{@{}>{\raggedright\arraybackslash}p{1.0000\columnwidth - 0\tabcolsep}@{}}{%
+\multicolumn{2}{@{}>{\raggedright\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{1.0000} + 2\tabcolsep}@{}}{%
 Computer Science} \\
 3741255 & Jones, Martha \\
 4077830 & Pierce, Benjamin \\
 5151701 & Kirk, James \\
-\multicolumn{2}{@{}>{\raggedright\arraybackslash}p{1.0000\columnwidth - 0\tabcolsep}@{}}{%
+\multicolumn{2}{@{}>{\raggedright\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{1.0000} + 2\tabcolsep}@{}}{%
 Russian Literature} \\
 3971244 & Nim, Victor \\
-\multicolumn{2}{@{}>{\raggedright\arraybackslash}p{1.0000\columnwidth - 0\tabcolsep}@{}}{%
+\multicolumn{2}{@{}>{\raggedright\arraybackslash}p{(\columnwidth - 2\tabcolsep) * \real{1.0000} + 2\tabcolsep}@{}}{%
 Astrophysics} \\
 4100332 & Petrov, Alexandra \\
 4100332 & Toyota, Hiroko \\
diff --git a/trypandoc/Makefile b/trypandoc/Makefile
deleted file mode 100644
--- a/trypandoc/Makefile
+++ /dev/null
@@ -1,14 +0,0 @@
-CGIBIN=/home/website/cgi-bin
-TRYPANDOC=/home/website/pandoc.org/try/
-CGI=${CGIBIN}/trypandoc
-BIN=/home/jgm/.cabal/bin/trypandoc
-
-install: ${CGI} ${TRYPANDOC}/index.html
-
-${TRYPANDOC}/%: %
-	cp $< $@ && chown website:www-data $@ && chmod a+r $@
-
-${CGI}: ${BIN}
-	cp $< $@ && chown website:www-data $@ && chmod a+rx $@
-
-.PHONY: install
diff --git a/trypandoc/index.html b/trypandoc/index.html
--- a/trypandoc/index.html
+++ b/trypandoc/index.html
@@ -9,94 +9,55 @@
        <script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
        <script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
        <![endif]-->
-  <script src="https://code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script>
-  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
-  <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
-  <script>
-"use strict";
-(function($) { // https://stackoverflow.com/questions/901115/how-can-i-get-query-string-values
-	$.QueryString = (function(a) {
-	    if (a == "") return {};
-	    var b = {};
-	    for (var i = 0; i < a.length; ++i)
-	    {
-		var p=a[i].split('=');
-		if (p.length != 2) continue;
-		b[p[0]] = decodeURIComponent(p[1].replace(/\+/g, " "));
-	    }
-	    return b;
-        })(window.location.search.substr(1).split('&'))
-})(jQuery);
-
-function newpage() {
-  var input = $("#text").val();
-  var from = $("#from").val();
-  var to = $("#to").val();
-  var standalone = $("#standalone").is(':checked') ? "1" : "0";
-  var href = window.location.href;
-  window.location.href = href.replace(/([?].*)?$/,"?" + $.param({text: input, from: from, to: to, standalone: standalone}));
-};
-
-$(document).ready(function() {
-    var text = $.QueryString["text"];
-    $("#text").val(text);
-    var from = $.QueryString["from"] || "markdown";
-    $("#from").val(from);
-    var to = $.QueryString["to"] || "html";
-    $("#to").val(to);
-    var standalone = ($.QueryString["standalone"] === "1") ? "1" : "0";
-    $("#standalone").prop('checked', (standalone === "1"));
-    if (text && text != "") {
-       $.getJSON("/cgi-bin/trypandoc", { from: from, to: to, text: text, standalone: standalone },
-         function(res) {
-          $("#results").text(res.html);
-          $("#version").text(res.version);
-          var commandString = "pandoc"
-            + ((standalone === "1") ? " --standalone" : "")
-            + " --from " + from + " --to " + to;
-          $("#command").text(commandString);
-         });
-    };
-    $("#convert").click(newpage);
-    $("#from").change(newpage);
-    $("#to").change(newpage);
-});
-  </script>
   <style>
-    h1 { margin-bottom: 1em; }
-    body { margin: auto; }
-    textarea { height: auto; width: 100%; font-family: monospace; margin-top: 15px; }
+    body { margin: auto; font-family: sans-serif; font-size: 10pt; color: #555; }
+    #command { min-width: 26em; background-color: #555; color: #eee; border-radius: 9pt; margin: 6pt; padding: 6pt; }
+    h1 { margin-bottom: 1em; font-size: 166%; margin: 0; padding: 6pt; }
+    h1 a { text-decoration: none; color: inherit; }
+    label { font-weight: 600; }
+    textarea { height: auto; font-family: monospace; font-size: 10pt; margin-top: 15px; min-height: 65vh; width: 44vw; }
     div.alert { margin: 1em; }
-    h3 { margin-top: 0; margin-bottom: 0; padding: 0; font-size: 100%; }
-    pre#results { width: 100%; margin-top: 15px; }
+    pre#results { width: 100%; margin-top: 15px; min-height: 65vh; width: 44vw; }
     footer { color: #555; text-align: center; margin: 1em; }
     p.version { color: #555; }
+    .container { margin: 12px; }
+    .row { display: flex; flex-wrap: nowrap; justify-content: space-between; }
+    #frompane { flex: 1; padding: 1em; }
+    #topane { flex: 1; padding: 1em;  }
+    #command { font-family: monospace; padding: 1em; }
     button#convert { vertical-align: bottom; }
     pre#command { margin-top: 1em; background-color: transparent; border: none; }
   </style>
 </head>
 <body>
-<div class="container-fluid">
+<div class="container">
   <div class="row">
-    <div class="col-md-6">
-      <h1>Try pandoc!</h1>
+    <div id="title">
+    <h1>Try <a href="https://pandoc.org">pandoc</a>!</h1>
     </div>
-    <div class="col-md-6">
+    <div id="command">
       <pre id="command"></pre>
     </div>
   </div>
   <div class="row">
-    <div class="col-md-6">
-      <button class="btn btn-primary btn-xs" id="convert">Convert</button>
+    <div id="frompane">
+      <button id="convert">Convert</button>
       &nbsp;
       <label for="from">
       from
       </label>
       <select id="from">
+        <option value="bibtex">BibTeX</option>
+        <option value="biblatex">BibLaTeX</option>
         <option value="commonmark">CommonMark</option>
+        <option value="commonmark_x">CommonMark (extended)</option>
         <option value="creole">Creole</option>
+        <option value="csljson">CSL JSON</option>
+        <option value="csv">CSV</option>
         <option value="docbook">DocBook</option>
         <option value="dokuwiki">DokuWiki</option>
+	<option value="docx">Docx (Word)</option>
+	<option value="epub">EPUB</option>
         <option value="fb2">FB2</option>
         <option value="haddock">Haddock markup</option>
         <option value="html">HTML</option>
@@ -113,37 +74,53 @@
         <option value="markdown_mmd">MultiMarkdown</option>
         <option value="muse">Muse</option>
         <option value="native">Native (Pandoc AST)</option>
+	<option value="odt">ODT</option>
         <option value="opml">OPML</option>
         <option value="org">Org Mode</option>
+        <option value="ris">RIS</option>
         <option value="rst">reStructuredText</option>
         <option value="t2t">Txt2Tags</option>
         <option value="textile">Textile</option>
         <option value="tikiwiki">TikiWiki</option>
         <option value="twiki">TWiki</option>
         <option value="vimwiki">Vimwiki</option>
-      </select>
-      <input type="checkbox" id="standalone" name="standalone">
-      <label for="standalone">Generate standalone document
-        <a href="https://pandoc.org/MANUAL.html#option--standalone" target="_blank">(?)</a>
-      </label>
+      </select><br/>
+      <label for="loadfile">Load from file</label>
+      <input id="loadfile" type="file" />
       <br/>
-      <textarea id="text" maxlength="3000" rows="15"></textarea>
+      <textarea id="text" rows="15"></textarea>
+      <br/>
+      <label for="examples">
+      Examples
+      </label>
+      <select id="examples">
+        <option value="" selected disabled>Select an example</option>
+        <option value="https://pandoc.org/try/?text=%40BOOK%7BWurm2011-ho%2C%0A++title+++++%3D+%22%7BSubstanz+und+Qualität+%3A+Ein+Beitrag+zur+Interpretation+der%0A+++++++++++++++plotinischen+Traktate+VI%2C1%2C+2+und+3%7D%22%2C%0A++author++++%3D+%22Wurm%2C+Klaus%22%2C%0A++publisher+%3D+%22De+Gruyter%22%2C%0A++series++++%3D+%22Quellen+und+Studien+zur+Philosophie%22%2C%0A++edition+++%3D+%22Reprint+2011%22%2C%0A++year++++++%3D++2011%2C%0A++address+++%3D+%22Berlin%22%2C%0A++keywords++%3D+%22%21%21%21+Plotinus+translation%22%2C%0A++language++%3D+%22de%22%0A%7D%0A&from=bibtex&to=csljson&standalone=false">BibTeX to CSL JSON</option>
+        <option value="https://pandoc.org/try/?text=---%0Areferences%3A%0A-+author%3A%0A++-+family%3A+Salam%0A++++given%3A+Abdus%0A++container-title%3A+%22Elementary+particle+theory%3A+Relativistic+groups+and%0A++++analyticity.+Proceedings+of+the+eighth+Nobel+symposium%22%0A++editor%3A%0A++-+family%3A+Svartholm%0A++++given%3A+Nils%0A++event-date%3A+1968-05-19%2F1968-05-25%0A++event-place%3A+Aspenäsgarden%2C+Lerum%0A++id%3A+salam%0A++issued%3A+1968%0A++page%3A+367-377%0A++publisher%3A+Almquist+%26+Wiksell%0A++publisher-place%3A+Stockholm%0A++title%3A+Weak+and+electromagnetic+interactions%0A++type%3A+paper-conference%0A---%0A%0A%40salam+%5Bp.+370%5D+says+some+interesting+things.%0A&from=markdown&to=man&standalone=false&citeproc=true">Markdown to man with citation</option>
+        <option value="https://pandoc.org/try/?text=%3D%3D+Definition+%3D%3D%0AAlthough+seemingly+different%2C+the+various+approaches+to+defining+tensors+describe+the+same+geometric+concept+using+different+language+and+at+different+levels+of+abstraction.%0A%0A%3D%3D%3D+As+multidimensional+arrays+%3D%3D%3D%0AA+tensor+may+be+represented+as+an+array+%28potentially+multidimensional%29.+Just+as+a+%5B%5BVector+space%7Cvector%5D%5D+in+an+%7B%7Bmvar%7Cn%7D%7D-%5B%5Bdimension+%28vector+space%29%7Cdimensional%5D%5D+space+is+represented+by+a+one-dimensional+array+with+%7B%7Bmvar%7Cn%7D%7D+components+with+respect+to+a+given+%5B%5BBasis+%28linear+algebra%29%23Ordered+bases+and+coordinates%7Cbasis%5D%5D%2C+any+tensor+with+respect+to+a+basis+is+represented+by+a+multidimensional+array.++For+example%2C+a+%5B%5Blinear+operator%5D%5D+is+represented+in+a+basis+as+a+two-dimensional+square+%7B%7Bmath%7C%27%27n%27%27+×+%27%27n%27%27%7D%7D+array.++The+numbers+in+the+multidimensional+array+are+known+as+the+%27%27scalar+components%27%27+of+the+tensor+or+simply+its+%27%27components%27%27.++They+are+denoted+by+indices+giving+their+position+in+the+array%2C+as+%5B%5Bsubscript+and+superscript%7Csubscripts+and+superscripts%5D%5D%2C+following+the+symbolic+name+of+the+tensor.++For+example%2C+the+components+of+an+order+%7B%7Bmath%7C2%7D%7D+tensor+%7B%7Bmvar%7CT%7D%7D+could+be+denoted+%7B%7Bmath%7C%27%27T%27%27%3Csub%3E%27%27ij%27%27%3C%2Fsub%3E%7D%7D%E2%80%AF%2C+where+%7B%7Bmvar%7Ci%7D%7D+and+%7B%7Bmvar%7Cj%7D%7D+are+indices+running+from+%7B%7Bmath%7C1%7D%7D+to+%7B%7Bmvar%7Cn%7D%7D%2C+or+also+by+%7B%7Bmath%7C%27%27T%27%27%26thinsp%3B%7B%7Bsu%7Cb%3D%27%27j%27%27%7Cp%3D%27%27i%27%27%7D%7D%7D%7D.++Whether+an+index+is+displayed+as+a+superscript+or+subscript+depends+on+the+transformation+properties+of+the+tensor%2C+described+below.+Thus+while+%7B%7Bmath%7C%27%27T%27%27%3Csub%3E%27%27ij%27%27%3C%2Fsub%3E%7D%7D+and+%7B%7Bmath%7C%27%27T%27%27%26thinsp%3B%7B%7Bsu%7Cb%3D%27%27j%27%27%7Cp%3D%27%27i%27%27%7D%7D%7D%7D+can+both+be+expressed+as+%27%27n%27%27+by+%27%27n%27%27+matrices%2C+and+are+numerically+related+via+%5B%5BRaising+and+lowering+indices%7Cindex+juggling%5D%5D%2C+the+difference+in+their+transformation+laws+indicates+it+would+be+improper+to+add+them+together.+The+total+number+of+indices+required+to+identify+each+component+uniquely+is+equal+to+the+%5B%5BArray+data+structure%23Dimension%7Cdimension%5D%5D+of+the+array%2C+and+is+called+the+%27%27order%27%27%2C+%27%27degree%27%27+or+%27%27rank%27%27+of+the+tensor.++However%2C+the+term+%22rank%22+generally+has+%5B%5Btensor+rank%7Canother+meaning%5D%5D+in+the+context+of+matrices+and+tensors.%0A%0AJust+as+the+components+of+a+vector+change+when+we+change+the+%5B%5Bbasis+%28linear+algebra%29%7Cbasis%5D%5D+of+the+vector+space%2C+the+components+of+a+tensor+also+change+under+such+a+transformation.++Each+type+of+tensor+comes+equipped+with+a+%27%27transformation+law%27%27+that+details+how+the+components+of+the+tensor+respond+to+a+%5B%5Bchange+of+basis%5D%5D.++The+components+of+a+vector+can+respond+in+two+distinct+ways+to+a+%5B%5Bchange+of+basis%5D%5D+%28see+%5B%5Bcovariance+and+contravariance+of+vectors%5D%5D%29%2C+where+the+new+%5B%5Bbasis+vectors%5D%5D+%3Cmath%3E%5Cmathbf%7B%5Chat%7Be%7D%7D_i%3C%2Fmath%3E+are+expressed+in+terms+of+the+old+basis+vectors+%3Cmath%3E%5Cmathbf%7Be%7D_j%3C%2Fmath%3E+as%2C%0A%3A%3Cmath%3E%5Cmathbf%7B%5Chat%7Be%7D%7D_i+%3D+%5Csum_%7Bj%3D1%7D%5En+%5Cmathbf%7Be%7D_j+R%5Ej_i+%3D+%5Cmathbf%7Be%7D_j+R%5Ej_i+.%3C%2Fmath%3E%0A%0AHere+%27%27R%27%27%3Csup%3E%27%27+j%27%27%3C%2Fsup%3E%3Csub%3E%27%27i%27%27%3C%2Fsub%3E+are+the+entries+of+the+change+of+basis+matrix%2C+and+in+the+rightmost+expression+the+%5B%5Bsummation%5D%5D+sign+was+suppressed%3A+this+is+the+%5B%5BEinstein+summation+convention%5D%5D%2C+which+will+be+used+throughout+this+article.%3Cref+group%3D%22Note%22%3EThe+Einstein+summation+convention%2C+in+brief%2C+requires+the+sum+to+be+taken+over+all+values+of+the+index+whenever+the+same+symbol+appears+as+a+subscript+and+superscript+in+the+same+term.++For+example%2C+under+this+convention+%3Cmath%3EB_i+C%5Ei+%3D+B_1+C%5E1+%2B+B_2+C%5E2+%2B+%5Ccdots+B_n+C%5En%3C%2Fmath%3E%3C%2Fref%3E++The+components+%27%27v%27%27%3Csup%3E%27%27i%27%27%3C%2Fsup%3E+of+a+column+vector+%27%27%27v%27%27%27+transform+with+the+%5B%5Bmatrix+inverse%7Cinverse%5D%5D+of+the+matrix+%27%27R%27%27%2C%0A%3A%3Cmath%3E%5Chat%7Bv%7D%5Ei+%3D+%5Cleft%28R%5E%7B-1%7D%5Cright%29%5Ei_j+v%5Ej%2C%3C%2Fmath%3E%0A%0Awhere+the+hat+denotes+the+components+in+the+new+basis.++This+is+called+a+%27%27contravariant%27%27+transformation+law%2C+because+the+vector+components+transform+by+the+%27%27inverse%27%27+of+the+change+of+basis.++In+contrast%2C+the+components%2C+%27%27w%27%27%3Csub%3E%27%27i%27%27%3C%2Fsub%3E%2C+of+a+covector+%28or+row+vector%29%2C+%27%27%27w%27%27%27%2C+transform+with+the+matrix+%27%27R%27%27+itself%2C%0A%3A%3Cmath%3E%5Chat%7Bw%7D_i+%3D+w_j+R%5Ej_i+.%3C%2Fmath%3E&from=mediawiki&to=docx&standalone=false">MediaWiki to docx with equations</option>
+      </select>
+
     </div>
-    <div class="col-md-6">
+    <div id="topane">
       <label for="to">
       to
       </label>
       <select id="to">
-        <option value="S5">S5</option>
         <option value="asciidoc">AsciiDoc (original)</option>
         <option value="asciidoctor">AsciiDoc (asciidoctor-flavored)</option>
         <option value="beamer">LaTeX Beamer</option>
+        <option value="bibtex">BibTeX</option>
+        <option value="biblatex">BibLaTeX</option>
         <option value="commonmark">CommonMark</option>
         <option value="context">ConTeXt</option>
+        <option value="csljson">CSL JSON</option>
         <option value="docbook4">DocBook v4</option>
         <option value="docbook5">DocBook v5</option>
+        <option value="docx">Docx (Word)</option>
         <option value="dokuwiki">DokuWiki</option>
         <option value="dzslides">DZSlides</option>
+        <option value="epub2">EPUB v2</option>
+        <option value="epub3">EPUB v3</option>
         <option value="haddock">Haddock markup</option>
         <option value="html4">HTML 4</option>
         <option value="html5" selected>HTML 5</option>
@@ -163,13 +140,16 @@
         <option value="markdown_mmd">MultiMarkdown</option>
         <option value="muse">Muse</option>
         <option value="native">Native (Pandoc AST)</option>
+        <option value="odt">ODT</option>
         <option value="opendocument">OpenDocument</option>
         <option value="opml">OPML</option>
         <option value="org">Org Mode</option>
         <option value="plain">Plain text</option>
+        <option value="pptx">Powerpoint</option>
         <option value="revealjs">reveal.js</option>
         <option value="rst">reStructuredText</option>
         <option value="rtf">RTF</option>
+        <option value="S5">S5</option>
         <option value="slideous">Slideous</option>
         <option value="slidy">Slidy</option>
         <option value="tei">TEI</option>
@@ -177,14 +157,25 @@
         <option value="textile">Textile</option>
         <option value="zimwiki">ZimWiki</option>
       </select>
+      <input type="checkbox" id="standalone" name="standalone">
+      <label for="standalone">Standalone
+        <a href="https://pandoc.org/MANUAL.html#option--standalone" target="_blank">(?)</a>
+      </label>
+      <input type="checkbox" id="citeproc" name="citeproc">
+      <label for="citeproc">Citeproc
+        <a href="https://pandoc.org/MANUAL.html#option--citeproc" target="_blank">(?)</a>
+      </label>
       <br/>
+      <a id="permalink" title="link to this conversion" href="">permalink</a></br/>
       <pre id="results"></pre>
     </div>
   </div>
 </div>
 <footer>
- <p class="version">pandoc <span id="version"></span></p>
- <p><a href="https://pandoc.org">https://pandoc.org</a></p>
+ <p class="version">pandoc version <span id="version"></span></p>
 </footer>
+
+<script src="trypandoc.js?202208182155"></script>
+
 </body>
 </html>
diff --git a/trypandoc/trypandoc.hs b/trypandoc/trypandoc.hs
deleted file mode 100644
--- a/trypandoc/trypandoc.hs
+++ /dev/null
@@ -1,76 +0,0 @@
-{-# LANGUAGE OverloadedStrings #-}
-{- |
-   Module      : Main
-   Copyright   : © 2014-2022 John MacFarlane <jgm@berkeley.edu>
-   License     : GNU GPL, version 2 or above
-
-   Maintainer  : John MacFarlane <jgm@berkeley.edu>
-   Stability   : alpha
-   Portability : portable
-
-Provides a webservice which allows to try pandoc in the browser.
--}
-module Main where
-import Network.Wai.Handler.CGI
-import Network.Wai.Middleware.Timeout (timeout)
-import Network.Wai
-import Data.Maybe (fromMaybe)
-import Network.HTTP.Types.Status (status200)
-import Network.HTTP.Types.Header (hContentType)
-import Network.HTTP.Types.URI (queryToQueryText)
-import Text.Pandoc
-import Text.Pandoc.Highlighting (pygments)
-import Text.Pandoc.Shared (tabFilter)
-import Data.Aeson
-import qualified Data.Text as T
-import Data.Text (Text)
-
-main :: IO ()
-main = run $ timeout 2 app
-
-app :: Application
-app req respond = do
-  let query = queryToQueryText $ queryString req
-  let getParam x = maybe (error $ T.unpack x ++ " parameter not set")
-                       return $ lookup x query
-  text <- getParam "text" >>= checkLength . fromMaybe T.empty
-  fromFormat <- fromMaybe "" <$> getParam "from"
-  toFormat <- fromMaybe "" <$> getParam "to"
-  standalone <- (==) "1" . fromMaybe "" <$> getParam "standalone"
-  compiledTemplate <- runIO . compileDefaultTemplate $ toFormat
-  let template = if standalone then either (const Nothing) Just compiledTemplate else Nothing
-  let reader = case runPure $ getReader fromFormat of
-                    Right (TextReader r, es) -> r readerOpts{
-                       readerExtensions = es }
-                    _ -> error $ "could not find reader for "
-                                  ++ T.unpack fromFormat
-  let writer = case runPure $ getWriter toFormat of
-                    Right (TextWriter w, es) -> w writerOpts{
-                       writerExtensions = es, writerTemplate = template }
-                    _ -> error $ "could not find writer for " ++
-                           T.unpack toFormat
-  let result = case runPure $ reader (tabFilter 4 text) >>= writer of
-                    Right s   -> s
-                    Left  err -> error (show err)
-  let output = encode $ object [ "html" .= result
-                               , "name" .=
-                                  if fromFormat == "markdown_strict"
-                                     then T.pack "pandoc (strict)"
-                                     else T.pack "pandoc"
-                               , "version" .= pandocVersion]
-  respond $ responseLBS status200 [(hContentType,"text/json; charset=UTF-8")] output
-
-checkLength :: Text -> IO Text
-checkLength t =
-  if T.length t > 10000
-     then error "exceeds length limit of 10,000 characters"
-     else return t
-
-writerOpts :: WriterOptions
-writerOpts = def { writerReferenceLinks = True,
-                   writerEmailObfuscation = NoObfuscation,
-                   writerHTMLMathMethod = MathJax defaultMathJaxURL,
-                   writerHighlightStyle = Just pygments }
-
-readerOpts :: ReaderOptions
-readerOpts = def
diff --git a/trypandoc/trypandoc.js b/trypandoc/trypandoc.js
new file mode 100644
--- /dev/null
+++ b/trypandoc/trypandoc.js
@@ -0,0 +1,161 @@
+"use strict";
+
+var params = {
+  text: '"hello *world*"',
+  to: 'html5',
+  from: 'markdown',
+  standalone: false,
+  citeproc: false };
+
+function permalink() {
+  let input = document.getElementById("text").value;
+  let from = document.getElementById("from").value;
+  let to = document.getElementById("to").value;
+  let standalone = document.getElementById("standalone").checked ? true : false;
+  let citeproc = document.getElementById("citeproc").checked ? true : false;
+  let href = window.location.href;
+  const URLparams = new URLSearchParams(Object.entries({text: input, from: from, to: to, standalone: standalone, citeproc: citeproc}));
+  return href.replace(/([?].*)?$/,"?" + URLparams);
+}
+
+const binaryFormats = {
+   docx: { extension: "docx",
+           mime: "application/vnd.openxmlformats-officedocument.wordprocessingml.document" },
+    odt: { extension: "odt",
+           mime: "application/vnd.oasis.opendocument.text" },
+    pptx: { extension: "pptx",
+            mime: "application/vnd.openxmlformats-officedocument.presentationml.presentation" },
+    epub:  { extension: "epub",
+             mime: "application/epub+zip" },
+    epub2: { extension: "epub",
+             mime: "application/epub+zip" },
+    epub3: { extension: "epub",
+             mime: "application/epub+zip" }
+};
+
+const binaryMimeTypes = {
+  ["application/epub+zip"]: true,
+  ["application/vnd.openxmlformats-officedocument.wordprocessingml.document"]: true,
+  ["application/vnd.openxmlformats-officedocument.presentationml.presentation"]: true,
+  ["application/vnd.oasis.opendocument.text"]: true
+};
+
+function paramsFromURL() {
+  if (window.location.search.length > 0) {
+    const uparams = new URLSearchParams(window.location.search);
+    params.text = uparams.get("text") || "";
+    params.from = uparams.get("from") || "markdown";
+    params.to = uparams.get("to") || "html5";
+    params.standalone = uparams.get("standalone") === "true";
+    params.citeproc = uparams.get("citeproc") === "true";
+  }
+}
+
+function handleErrors(response) {
+    if (response.status == 503 || response.status == 500) {
+        throw Error("Conversion timed out.")
+    } else if (!response.ok) {
+        throw Error(response.statusText);
+    }
+    return response;
+}
+
+function convert() {
+    let text = document.getElementById("text").value;
+    let from = document.getElementById("from").value;
+    let to = document.getElementById("to").value;
+    let standalone = document.getElementById("standalone").checked;
+    let citeproc = document.getElementById("citeproc").checked;
+    params = { text: text, from: from, to: to, standalone: standalone,
+               citeproc: citeproc };
+
+    if (text && text != "") {
+       let commandString = "pandoc"
+         + " --from " + from + " --to " + to
+         + (standalone ? " --standalone" : "")
+         + (citeproc ? " --citeproc" : "") ;
+       document.getElementById("command").textContent = commandString;
+       fetch("/cgi-bin/pandoc-server.cgi", {
+         method: "POST",
+         headers: {"Content-Type": "application/json"},
+         body: JSON.stringify(params)
+        })
+       .then(handleErrors)
+       .then(response => response.text())
+       .then(restext => {
+            let binary = binaryFormats[to];
+            if (binary) {
+            document.getElementById("results").innerHTML =
+                '<a download="trypandoc.' + binary.extension +
+                '" href="data:' + binary.mime + ';base64,' + restext +
+                '">click to download trypandoc.' + binary.extension + '</a>';
+          } else {
+            document.getElementById("results").textContent = restext;
+          }
+          document.getElementById("permalink").href = permalink();
+       })
+       .catch(error => {
+         document.getElementById("results").textContent = error;
+       }
+       );
+    };
+}
+
+(function() {
+    paramsFromURL();
+    document.getElementById("text").value = params.text;
+    document.getElementById("from").value = params.from;
+    document.getElementById("to").value = params.to;
+    document.getElementById("standalone").checked = params.standalone;
+    document.getElementById("citeproc").checked = params.citeproc;
+
+    document.getElementById("convert").onclick = convert;
+    document.getElementById("from").onchange = convert;
+    document.getElementById("to").onchange = convert;
+    document.getElementById("standalone").onchange = convert;
+    document.getElementById("citeproc").onchange = convert;
+
+    document.getElementById("examples").onchange =
+      (e => window.location.href = e.target.value );
+
+    const fileInput = document.getElementById('loadfile');
+
+    // Listen for the change event so we can capture the file
+    fileInput.addEventListener('change', (e) => {
+      // Get a reference to the file
+      const file = e.target.files[0];
+      const mimetype = file.type;
+      let binary = binaryMimeTypes[mimetype];
+
+      // Encode the file using the FileReader API
+      const reader = new FileReader();
+      let inputtext = document.getElementById("text");
+      reader.onloadend = () => {
+        // Use a regex to remove data url part
+        if (binary) {
+          const base64String = reader.result
+           .replace('data:', '')
+           .replace(/^.+,/, '');
+          inputtext.value = base64String;
+	} else {
+          inputtext.value = reader.result;
+        }
+      };
+      if (binary) {
+        reader.readAsDataURL(file);
+      } else {
+        reader.readAsText(file);
+      }
+    });
+
+    fetch("/cgi-bin/pandoc-server.cgi/version")
+       .then(handleErrors)
+       .then(response => response.text())
+       .then(restext =>
+           document.getElementById("version").textContent = restext
+         );
+
+    convert();
+
+})();
+
