diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml
new file mode 100644
index 0000000..11b4fed
--- /dev/null
+++ b/.github/workflows/deploy.yml
@@ -0,0 +1,43 @@
+on:
+ push:
+ branches:
+ - main
+ pull_request:
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ if: github.ref != 'refs/heads/main'
+ steps:
+ - name: 'Checkout'
+ uses: actions/checkout@main
+ - run: touch site/.nojekyll
+ - uses: actions/setup-node@v4
+ with:
+ node-version: 20
+ - run: npm install
+ - run: npm run abridge
+ - name: 'Build only'
+ uses: shalzz/zola-deploy-action@0.19.1
+ env:
+ BUILD_DIR: .
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ BUILD_ONLY: true
+
+ build_and_deploy:
+ runs-on: ubuntu-latest
+ if: github.ref == 'refs/heads/main'
+ steps:
+ - name: 'Checkout'
+ uses: actions/checkout@main
+ - run: touch site/.nojekyll
+ - uses: actions/setup-node@v4
+ with:
+ node-version: 20
+ - run: npm install
+ - run: npm run abridge
+ - name: 'Build and deploy'
+ uses: shalzz/zola-deploy-action@0.19.1
+ env:
+ PAGES_BRANCH: gh-pages
+ BUILD_DIR: .
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..4929e66
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,15 @@
+.env
+public
+themes
+build
+storage
+node_modules
+package-lock.json
+static/demo.html
+static/tinysearch_engine.js
+static/tinysearch_engine.d.ts
+static/tinysearch_engine_bg.wasm.d.ts
+static/package.json
+static/js/pagefind.*.pf_meta
+static/js/index
+static/js/fragment
\ No newline at end of file
diff --git a/CNAME b/CNAME
new file mode 100644
index 0000000..e02bff5
--- /dev/null
+++ b/CNAME
@@ -0,0 +1 @@
+blog.haskell.org
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..1b19050
--- /dev/null
+++ b/README.md
@@ -0,0 +1,53 @@
+# Haskell Blog
+
+## Local installation
+
+The blog is made with [Zola], based on the [Abridge theme].
+
+You will need:
+ * The `zola` binary v0.19.1 or higher
+ * `npm`
+
+Once you have cloned this repository, run `npm install` and `npm run abridge` to initialise the front-end features like full-text search.
+
+## Contribute content
+
+### Blog post structure
+
+#### Name
+
+Blog posts are located in the `content` directory, as markdown files. The files themselves start with the (expected) date of publication
+and contain their title in a "slug" format (alphanumeric characters, words separated by a hyphen, all lowercase):
+
+```
+2024-08-03-documentation-best-practices.md
+```
+
+#### Front-matter
+
+The file itself must contain a front-matter in TOML format that has the following properties:
+
+```
++++
+title = "Title of the post"
+date = YYYY-MM-DD
+[taxonomies]
+authors = ["Author's Name"] # The author's name. Will be indexed.
+categories = ["Haddock"] # A minima should be the name of the team ("Haddock", "HLS", "Cabal"). Will be indexed.
+tags = ["Practices"] # Something more precise like "Release", "Practice", "JavaScript", can be aded. Will be indexed.
++++
+```
+
+#### Summary
+
+The eye-catcher that you wish to show on the front-page of the blog is the first paragraph.
+You can manually delimit where it ends by inserting `` on a newline between this paragraph and the rest of the content.
+
+Otherwise, in the absence of this comment, the first 150 characters will be used by Zola
+
+### Local preview
+
+Run `zola serve --drafts` in order to serve the website and automatically render it when files change.
+
+[Zola]: https://www.getzola.org/
+[Abridge theme]: https://abridge.pages.dev/overview-abridge/
diff --git a/config.toml b/config.toml
new file mode 100644
index 0000000..70cbef3
--- /dev/null
+++ b/config.toml
@@ -0,0 +1,311 @@
+# do NOT include a trailing slash on the base URL
+base_url = "https://blog.haskell.org"
+# Usable site directly from disk, Including Search: "/home/jieiku/.dev/abridge/public"
+# Also set index format = "elasticlunr_javascript", and in [extra] uglyurls = true, integrity = false
+# If you use the npm/node script then all you have to do is set offline = true, and everything else is automatic.
+
+title = "The Haskell Programming Language's blog"
+description = ""
+theme = "abridge"
+
+default_language = "en"
+compile_sass = true
+minify_html = false
+build_search_index = true
+generate_feeds = true
+taxonomies = [
+ {name = "categories", feed = true},
+ {name = "authors"}
+]
+
+#feed_filename = "atom.xml"
+#output_dir = "public"
+#ignored_content = ["*.{graphml,xlsx}", "temp.*"]
+#hard_link_static = false # set to true to hard link instead of copying, useful for very large files.
+
+[search] # Options specific to elasticlunr search.
+# index format can be: elasticlunr_json or elasticlunr_javascript or fuse_json
+index_format = "elasticlunr_json"
+include_title = true # include title of page/section in index
+include_description = true # include description of page/section in index
+include_content = true # include rendered content of page/section in index
+# truncate_content_length = 100 # Truncate at nth character. May be useful if index is getting too large.
+
+[markdown]
+highlight_code = true
+highlight_theme = "css"
+render_emoji = false
+external_links_target_blank = true # rel="noopener"
+external_links_no_follow = false # rel="nofollow"
+external_links_no_referrer = false # rel="noreferrer"
+smart_punctuation = false # `...` to `…`, `"quote"` to `“curly”` etc
+
+[extra]
+###############################################################################
+### Layout & Format
+### position: top, bottom, both, false(hidden)
+### size: s150, s140, s130, s120, s110, s95, s90, s85, s80, s75, s70, false(full size)
+### divider: " " "·" "•"
+###############################################################################
+
+menu = [
+ {url = "https://haskell.org", name = "Haskell", slash = false, size = "s110"},
+ {url = "archive", name = "Posts", slash = true, blank = false, size="s110"},
+ {url = "categories", name = "Categories", slash = true, blank = false, size="s110"},
+ {url = "about", name = "About", slash = true, blank = false, size="s110"},
+]
+
+toc = true
+recent = true # TOC / index
+recent_items = 15
+series = true
+series_items = 9 # Max number of items to display in series list, use 0 to disable
+#Series_parts = "$NUMBER_OF_PARTS part Series"
+
+#arrow_pagination = true # If set to true then the pagination will use the old arrows mode, be sure to also enable the icons.
+
+meta_index = { position="bottom", size="s90", author=false, readtime=false, readstring="min", date=true, updated=false, categories_tags=true, divider="" }
+meta_post = { position="top", size="s95", author=true, readtime=false, readstring="min read", date=true, updated=true, categories_tags=true, divider="" }
+
+hide_section_dates = false # hides the date for sections that use posts.html as their template.
+hide_page_nextprev_titles = false # hides the next/previous titles for pages that use page.html as their template.
+title_size_index = "s85"
+footer_size = "s80" # the size of the copyright and powered by text
+footer_credit = true
+#footer_credit_override = '
Powered by Zola & Abridge
'
+#archive_reverse = true # Set to True to sort posts chronologically per year instead of newest first on the archive page: example.com/archive/
+
+### Uncomment one of the below lines, or neither, depending on which type of logo you want to use:
+logo = { file="images/haskell.svg", width="40", height="28", alt="Haskell", text="Haskell Blog" }
+
+sitedesc = false # enables or disables the display of the site description below the logo.
+headhr = false # show or hide horizontal rule below header
+foothr = false # show or hide horizontal rule above footer
+
+### $CURRENT_YEAR and $SITE_TITLE can be used anywhere within the copyright, you can change their position or you can also delete them and type whatever you want instead.
+#copyright = false # set to false to disable the copyright.
+#copyright_override = '© 2019-$CURRENT_YEAR $SITE_TITLE'
+#copyright_override = '© $CURRENT_YEAR $SITE_TITLE • Website content is licensed CC BY 4.0 .'
+
+
+###############################################################################
+### meta/seo/analytic tags
+### To disable title_addition but keep built in page additions eg, Posts page: "Posts | Abridge" set title_addition to an empty string
+### To always set the page title exactly eg "Abridge" then comment out title_addition or set to false (not recommend, bad for SEO)
+###############################################################################
+
+title_separator = "|" # Separator between title and title_addition, set as |, -, _, etc
+title_addition = "Fast & Lightweight Zola Theme" # a default value for title addition
+author = "Haskell.org"
+keywords = "haskell, functional programming, blog" # used for the primary site index
+banner = "banner.png" # Used as default image for OpenGraph/Twitter if page specific image is undefined.
+seo = true # enable or disable seo-related meta tags: opengraph, facebook, twitter
+#dev = false # development mode, if true then robots.txt should prevent search indexing.
+
+#head_extra = ' '
+
+
+###############################################################################
+### Footer social links; these are used in macros/social.html
+### https://github.com/Jieiku/abridge/blob/master/templates/macros/social.html
+###############################################################################
+
+feed = true # this adds the RSS feed icon in the footer.
+mastodon = "https://fosstodon.org/@haskell"
+twitter = "haskellOrg"
+github = "haskell"
+
+
+###############################################################################
+### Resource Files
+### You can load extra css files if you need to, just separate by comma:
+### stylesheets = [ "abridge.css", "extra.css" ]
+### search_library, library to use. valid values:
+### false, "elasticlunr", "tinysearch", "stork", "pagefind"
+### offline: implies uglyurls=true and integrity=false, when true NPM/node will
+### automatically set the path for the base_url, it will build the site,
+### then set the base_url back to what it was. This is a way to build a completely
+### offline site, a feature not possible with Zola alone.
+### The PWA feature is another way to build an offline site, so there are now two
+### different ways to build an offline site with Abridge.
+###
+### For most people the value of online_url will be the same as base_url.
+### online_url is used to restore the base_url after generating an Offline site.
+### When you set offline = true and run the npm script, the base_url is set to the absolute path on disk.
+### Once you set offline = false, the base_url will be set back to the value of online_url when you run the npm script again.
+###############################################################################
+
+# do NOT include a trailing slash on the online URL
+online_indexformat = "elasticlunr_json"# used to restore your preferred index format when offline = false
+offline = false # implies uglyurls=true and integrity=false, when true NPM/node will automatically set the path for the base_url, it will build the site, then set the base_url back to what it was.
+
+uglyurls = false # if set to true then links are generated with the full path. eg https://abridge.netlify.app/index.html
+integrity = true # increases site security, should normally be true. (setting to false is useful during js development)
+js_bundle = true # multiple javascript files combined into a single file (setting to false is useful during js development)
+
+js_copycode = true # The copy button on code blocks that allows you to copy them to the clipboard.
+js_email_encode = true # obfuscates email address in footer
+js_prestyle = true # used to preload: FontAwesome, Katex, external Google Fonts
+js_switcher = false # The button that allows manually changing between light/dark mode.
+js_switcher_default = "light" # default nojs switcher mode: dark, light (make sure to also set $switcherDefault in abridge.scss)
+
+search_library = "elasticlunr"
+stylesheets = ["abridge.css"]
+
+webmanifest = "manifest.min.json" # Required for PWAs
+
+
+###############################################################################
+### PWA (Progressive Web Application)
+### By default Abridge has pwa_NORM_TTL and pwa_LONG_TTL set to 0, this essential turns the PWA cache strategy into network first.
+### Abridge uses cachebust hashing on js and css files, so anytime a page cache is updated, these resources would also get updated if changed.
+### Media files rarely change, especially font files, so it is a good idea cache indefinitely.
+### For pwa_TTL_EXEMPT indefinitely cached resources, you can force a new cache by incrementing the pwa_VER (cache version number).
+### If you would like to try a cache first strategy then set a value higher than 0 for pwa_NORM_TTL and pwa_LONG_TTL.
+### The options below other than pwa=true, only come into play when the npm/node script is ran.
+###############################################################################
+
+pwa = true # true to load the service worker
+pwa_VER = '3.11.0' # Service Worker cache version. (increment if you need to force a new cache)
+
+### 3600=1hour, 28800=8hours, 86400=1day, 604800=1week, 1209600=2weeks
+pwa_NORM_TTL = 0 # 86400 is reasonable. html, json, xml, anything else undefined
+pwa_LONG_TTL = 0 # 604800 is reasonable.
+
+### list of files that overrides TTL_LONG/TTL_EXEMPT to be a NORM TTL.
+pwa_TTL_NORM = '"sw.min.js", "sw_load.min.js"'
+
+### TTL_LONG file extensions will be cached for the LONG_TTL duration.
+pwa_TTL_LONG = '"jpg", "jpeg", "png", "gif", "webp", "avif", "ico", "svg", "xsl", "txt"'
+
+### TTL_EXEMPT file extensions will be cached indefinitely unless sw_load version is incremented, which would invalidate any existing cache. (and a new cache would be started)
+pwa_TTL_EXEMPT = '"js", "css", "otf", "eot", "ttf", "woff", "woff2", "mp4", "webm", "mp3", "ogg"'
+
+### If set to true then the entire site is cached. (useful for making an entire site usable while offline)
+pwa_cache_all = false
+
+### List of Files for the PWA to initially Cache, used if pwa_cache_all = false
+pwa_BASE_CACHE_FILES = "'/js/theme.min.js','/js/theme_light.min.js','/abridge.css','/js/abridge.min.js','/','/404.html','/offline/','/manifest.min.json'"
+
+
+###############################################################################
+### Favicons, comment out a line to disable loading some or all of these if needed.
+###############################################################################
+
+favicon_theme_color = "#333333"
+favicon_ms_color = "#333333"
+favicon_mask = "safari-pinned-tab.svg" # safari-pinned-tab.svg
+favicon_mask_color = "#ff9900"
+# favicon_svg = "favicon.svg" # favicon.svg
+favicon180 = "apple-touch-icon.png" # apple-touch-icon.png
+favicon32 = "favicon-32x32.png" # favicon-32x32.png
+favicon16 = "favicon-16x16.png" # favicon-16x16.png
+
+
+###############################################################################
+### Commenting System for visitors to leave comments on pages.
+### hyvor talk
+###############################################################################
+
+#comments.hyvor = "9366" # hyvor website id, comment out to disable.
+#comments.hyvorcolor = "os" # set the color property for hyvor
+
+
+###############################################################################
+### Icons
+### Loading the entire fontawesome icon collection will negatively impact your sites performance.
+### For a lightweight solution consider adding only the icons that you need.
+### you can load individual scss based svg icons by including them in the _extra.scss file
+### https://github.com/Jieiku/abridge/blob/master/COPY-TO-ROOT-SASS/_extra.scss
+###############################################################################
+
+### To disable any of these icons set them to "false" (will default to unicode icons instead)
+#icon_search = "svgs svgm search" # Search button in search box.
+#icon_adjust = "svgs adjust" # Theme Switcher button in top menu. (add class svgh to change colors on hover)
+#icon_first = "svgs svgh angll" # Pagination First Page.
+#icon_prev = "svgs svgh angl" # Pagination Previous Page.
+#icon_next = "svgs svgh angr" # Pagination Next Page.
+#icon_last = "svgs svgh angrr" # Pagination Last Page.
+#icon_top = "svgs svgh angu" # Back to Top Button.
+
+#icon_read = "svgs fa-solid fa-glasses" # displayed in metadata on index and below title on page.
+#icon_date = "svgs fa-solid fa-calendar" # displayed in metadata on index and below title on page.
+#icon_info = "svgs fa-solid fa-circle-info" # displayed in metadata on index and below title on page for categories/tags
+#icon_author = "svgs fa-solid fa-pen-fancy" # displayed in metadata on index and below title on page.
+
+### Uncomment below line to load fontawesome, eg:
+#fontawesome = "https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.1.1/css/all.min.css"
+#icon_read = "svgs fa-solid fa-glasses" # displayed in metadata on index and below title on page.
+#icon_date = "svgs fa-solid fa-calendar" # displayed in metadata on index and below title on page.
+#icon_info = "svgs fa-solid fa-circle-info" # displayed in metadata on index and below title on page for categories/tags
+#icon_author = "svgs fa-solid fa-pen-fancy" # displayed in metadata on index and below title on page.
+
+
+###############################################################################
+### Security Settings
+### Security Headers should preferably be set by your webserver (Nginx, Apache)
+### https://observatory.mozilla.org https://csp-evaluator.withgoogle.com/
+### If you are unable to load your security headers with your webserver, this will load a couple of them as meta tags.
+### There are many other security related headers most of which can only be set by the webserver method.
+### UnComment any of these lines to enable their meta tags.
+###############################################################################
+
+#security_header_referrer = "strict-origin-when-cross-origin"
+
+#security_header_csp = "default-src 'none'; object-src 'none'; base-uri 'self'; manifest-src 'self'; connect-src 'self'; form-action 'self'; script-src 'self'; img-src 'self' data: cdn.cloudflare.com; frame-src 'self' www.youtube-nocookie.com player.vimeo.com; media-src 'self' data: cdn.cloudflare.com www.youtube-nocookie.com player.vimeo.com; font-src 'self' cdn.cloudflare.com cdn.jsdelivr.net fonts.gstatic.com; style-src 'self' cdn.cloudflare.com cdn.jsdelivr.net fonts.googleapis.com;"
+
+
+###############################################################################
+### FONTS - Abridge by default uses the System Font Stack
+### https://css-tricks.com/snippets/css/system-font-stack/
+### However if you need you can load a specific font below,
+### make sure to have the relevant woff2 fonts in your static/fonts folder
+### I measured the least Cumulative Layout Shift with: Roboto, Lato, Arimo
+###############################################################################
+
+### For externally loaded Fonts, make sure to include the FULL url including the https prefix:
+
+#fonts = [ {url = "https://fonts.googleapis.com/css?family=Roboto:400,700,italic|Roboto+Mono:400,italic"} ]
+
+### Local fonts are defined in the css, https://github.com/Jieiku/abridge/tree/master/COPY-TO-ROOT-SASS/fonts/_Arimo.scss
+### To load a local font resource, look at the bottom of this file: https://github.com/Jieiku/abridge/blob/master/COPY-TO-ROOT-SASS/abridge.scss
+
+### After loading them in the CSS, You can also define them below and it will add the preload tag to the head.
+### preloading fonts will eliminate Content Layout Shift, but will hurt with page load time. (not recommended)
+
+fonts = [
+ {url = "fonts/source-sans-3-v9-latin-regular.woff2"},
+ {url = "fonts/ubuntu-mono-v15-latin-regular.woff2"},
+ {url = "fonts/raleway-v28-latin-700.woff2"},
+ {url = "fonts/raleway-v28-latin-900.woff2"},
+]
+
+###############################################################################
+### Katex - math js library, used to to render mathematical notations
+### It's best to enable katex on a per page bases as I did here:
+### https://abridge.netlify.app/overview-math/
+### https://github.com/Jieiku/abridge/blob/master/content/overview-math.md?plain=1#L11-L13
+### Otherwise you will load the katex related javascript on every page!
+###############################################################################
+
+#katex_options = "js/katexoptions.js"
+#katex_bundle = "js/katexbundle.min.js"
+
+### Load Katex Local Resources
+#katex_css = "katex.min.css" # Fonts - load the css/fonts locally
+#katex_js = "js/katex.min.js" # use local js, so that we dont have to whitelist cdn.jsdelivr.net for script src in CSP
+#mathtex_js = "js/mathtex-script-type.min.js" # use local js, so that we dont have to whitelist cdn.jsdelivr.net for script src in CSP
+#katex_autorender_js = "js/katex-auto-render.min.js"
+
+### Load Katex External Resources
+#katex_css = "https://cdn.jsdelivr.net/npm/katex@0.15.6/dist/katex.min.css" # Fonts - use remote fonts
+#katex_css_integrity = "sha384-ZPe7yZ91iWxYumsBEOn7ieg8q/o+qh/hQpSaPow8T6BwALcXSCS6C6fSRPIAnTQs"
+#katex_js = "https://cdn.jsdelivr.net/npm/katex@0.15.6/dist/katex.min.js"
+#katex_js_integrity = "sha384-ljao5I1l+8KYFXG7LNEA7DyaFvuvSCmedUf6Y6JI7LJqiu8q5dEivP2nDdFH31V4"
+#katex_autorender_js = "https://cdn.jsdelivr.net/npm/katex@0.15.6/dist/contrib/auto-render.min.js"
+#katex_autorender_js_integrity = "sha384-+XBljXPPiv+OzfbB3cVmLHf4hdUFHlWNZN5spNQ7rmHTXpd7WvJum6fIACpNNfIR"
+#mathtex_js = "https://cdn.jsdelivr.net/npm/katex@0.15.6/dist/contrib/mathtex-script-type.min.js"
+#mathtex_js_integrity = "sha384-jiBVvJ8NGGj5n7kJaiWwWp9AjC+Yh8rhZY3GtAX8yU28azcLgoRo4oukO87g7zDT"
+
+#math = false # Recommended setting false, and enable on per page bases instead.
+#math_auto_render = false # Recommended setting false, and enable on per page bases instead.
diff --git a/content/2024-08-03-documentation-best-practices.md b/content/2024-08-03-documentation-best-practices.md
new file mode 100644
index 0000000..33e70c3
--- /dev/null
+++ b/content/2024-08-03-documentation-best-practices.md
@@ -0,0 +1,137 @@
++++
+title = "Documentation Best Practices"
+date = 2024-08-02
+[taxonomies]
+authors = ["Hécate"]
+categories = ["Haddock"]
+tags = ["Practices"]
++++
+
+In the Haddock team, part of our mission is to help with writing documentation, and promoting best practices. This article will help you write the best documentation you can!
+
+
+
+We adapt documentation outside practices to our ecosystem, and leverage our own technologies to empower Haskell users with their documentation work.
+
+Let us see some of these techniques, and how the Haddock team can be of help.
+
+## Writing documentation for your software project
+
+### Justify yourself
+
+When you create software, there is a pipeline from your brain straight to your code. Your decisions — such as the libraries you’ve used,
+or your program architecture — shape how your code is structured and written.
+
+Unfortunately, simply writing the code isn’t enough.The reasoning behind the decisions you made is as important as the decisions themselves. In the short term, solving a problem may let you move ahead immediately, but what keeps you on the correct path is understanding what
+brought you to that solution.
+
+Indeed, your choices may not be as popular as you think they are! Of course, you decided on them because you already convinced yourself
+that they’re best. But you have a user base to convince as well, and they may not see things the same way you do.
+
+As such, it is vitally important to document which decisions you made and to justify why you made them. If it’s not immediately obvious
+why a behaviour or a pattern exists, future maintainers might be tempted to drop it — only to discover too late why it was needed.
+
+### The reference flow of documentation
+
+Not all types of documentation have the same life cycle. Different pieces of documentation are more or less stable, and this determines
+which can act as a conceptual and theoretical foundation for your project.
+
+Examples of stable documentation include:
+
+* A README without code
+* A vision statement
+* The value proposition and the core domain
+
+These ought not to change much, because they describe the basic problems that your code aims to address, solve or support in the long run.
+While it is normal to fiddle around with the boundaries of your project at the beginning, in general these should change infrequently.
+
+Some other documentation is called volatile, like:
+
+* Documentation generated at runtime
+* Code examples
+* Tests
+* Configuration
+
+These are *expected* to change frequently, as your project changes, your API evolves, and you change configuration options.
+Volatile documentation is expensive to maintain, but also very valuable, as it shows in a concrete way how the user can interact with
+your project.
+
+
+> “When you refer to something, make sure the direction of the reference is from the more volatile to the more stable elements”
+> -- Cyrille Martraire, Living Documentation, 2019
+
+
+As such, here is a simplified model of the documentation cascade for a typical Haskell project, from the most volatile to the most stable
+sources:
+
+```
+Haddocks of your library or a third-party library
+├──> Official specs for your domain
+├──> Architecture Document
+└─┬> Haddocks of a core library (base, text, vector, etc)
+ ├──> GHC Manual
+ ├──> Official specs for what the core libs provide
+ └──> Papers (without paywalls)
+```
+
+Keep in mind that while the Haddocks of a project can refer to the project specs, or to an architecture document, these documents should
+never refer to the project's current implementation. If you must refer to the code, point to where it's located.
+The (current, volatile) code cannot be the justification for the (planned, stable) architecture.
+
+The GHC manual is much more stable than the haddocks of a Core library, which is why documentation should flow from
+the library to the manual.
+
+Finally, papers serve the same purpose as architecture documents, where they describe techniques that may be implemented,
+but theyshould not point to code that is subject to change – lest they point to a library that has evolved so much
+that it no longer relates to the paper.
+
+#### Example: The Set data structure
+
+The [Haddocks for the `Set` datatype](https://hackage.haskell.org/package/containers-0.6.5.1/docs/Data-Set.html)
+(from the `containers` library) are an example of documentation which follows this model well:
+
+* They point to an overview of the API ([here](https://haskell-containers.readthedocs.io/en/latest/set.html): _volatile_)
+* They refer to the papers that have informed the design of its implementation: the absence of working links may be annoying,
+but the references can still be followed (_stable_)
+
+### Understand for whom you write
+
+This section introduces the Diátaxis Framework for documentation:
+
+
+
+> -- Diátaxis Framework, by Daniele Procida, https://diataxis.fr
+
+
+Diátaxis maps out the entire life cycle of one’s interaction with a system. Each of its four quadrants describes a different
+situation in which a user may find themselves.
+
+
+Diátaxis is not just about filling out all the quadrants like a checklist (although they are all good to have!).
+Instead, it is about understanding how each section focusses on a particular combination of user needs and situations.
+If a new user in need of actively acquiring some practice with the project, they can safely be pointed to the "Tutorials" part
+of your documentation, as it is the part that focuses on "_Acquisition_" of knowledge through "_Action_".
+The focus of the tutorial is to make a prospective user acquire basic competence in handling the software. It is an ice-breaker.
+
+However someone who is in need of a deeper – but perhaps less immediately applicable understanding of the project –
+will be better served by the Explanation, which serves the need for thought (or _Cognition_)
+
+
+In short, the message of Diátaxis is that you are not meant to write The One Documentation that covers everything —
+inevitably, this produces documentation which is shallow due to its breadth. Instead, focus on the strategic aspects of your documentation,
+and you will produce documentation of better quality, with a clear purpose that it can fulfill more easily.
+
+Through the lens of Diátaxis, the module API documentation produced by Haddock is a *Reference*.
+
+## Reach Out
+
+Should you need any help in writing or proof-reading documentation, please stop by the [Matrix chatroom](https://matrix.to/#/#haddock:matrix.org) of the Haddock team,
+or ping us with the [@haddock](https://gitlab.haskell.org/groups/haddock/-/group_members?sort=last_joined) group tag on the
+[Haskell Gitlab](https://gitlab.haskell.org/). We would be more than happy to lend you a hand and discuss how to best serve your users,
+you included.
+
+## Read More
+
+* [Haddock manual](https://haskell-haddock.readthedocs.io/latest/)
+* [The theory behind Diátaxis](https://diataxis.fr/theory/)
+* [How to contribute to Haddock](https://gitlab.haskell.org/ghc/ghc/-/blob/master/utils/haddock/CONTRIBUTING.md?ref_type=heads)
diff --git a/content/_index.md b/content/_index.md
new file mode 100644
index 0000000..a31a970
--- /dev/null
+++ b/content/_index.md
@@ -0,0 +1,5 @@
++++
+paginate_by = 3
+sort_by = "date"
+template = "index.html"
++++
diff --git a/content/archive/_index.md b/content/archive/_index.md
new file mode 100644
index 0000000..85198a5
--- /dev/null
+++ b/content/archive/_index.md
@@ -0,0 +1,6 @@
++++
+template = "archive.html"
+
+[extra]
+sec = ""
++++
diff --git a/content/pages/_index.md b/content/pages/_index.md
new file mode 100644
index 0000000..800a244
--- /dev/null
+++ b/content/pages/_index.md
@@ -0,0 +1,3 @@
++++
+render = false
++++
diff --git a/content/pages/about.md b/content/pages/about.md
new file mode 100644
index 0000000..8cb8848
--- /dev/null
+++ b/content/pages/about.md
@@ -0,0 +1,16 @@
++++
+title = "About this blog"
+path = "about"
+template = "pages.html"
+draft = false
++++
+
+## About this blog
+
+Welcome to the Haskell Project's blog!
+
+This is the place where the various teams that power the language and its ecosystem communicate about their progress, innovations,
+and new releases.
+
+The Haskell.org Committee is the publisher of this website. Please contact us at `committee haskell org` if you wish to
+signal content that goes against our [Guidelines For Respectful Communication](https://haskell.foundation/guidelines-for-respectful-communication/).
diff --git a/content/pages/privacy.md b/content/pages/privacy.md
new file mode 100644
index 0000000..1b7eec5
--- /dev/null
+++ b/content/pages/privacy.md
@@ -0,0 +1,15 @@
++++
+title = "Privacy Policy"
+description = "This page outlines the Privacy Policy for this site, and the date at which this policy was put into affect."
+path = "privacy"
+template = "pages.html"
+draft = false
++++
+
+## Privacy
+
+- This site does not set or use cookies.
+- This site does not store data in the browser to be shared, sent, or sold to third-parties.
+- No personal information is shared, sent, or sold to third-parties.
+
+**Effective Date:** _1st Jan 2022_
diff --git a/content/static/stork_toml.md b/content/static/stork_toml.md
new file mode 100644
index 0000000..e427a93
--- /dev/null
+++ b/content/static/stork_toml.md
@@ -0,0 +1,5 @@
++++
+path = "data_stork"
+template = "stork_toml.html"
+draft = true
++++
diff --git a/content/static/tinysearch_json.md b/content/static/tinysearch_json.md
new file mode 100644
index 0000000..c91a639
--- /dev/null
+++ b/content/static/tinysearch_json.md
@@ -0,0 +1,5 @@
++++
+path = "data_tinysearch"
+template = "tinysearch_json.html"
+draft = true
++++
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..954e327
--- /dev/null
+++ b/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "abridge-bundle",
+ "version": "2.0.0",
+ "description": "Abridge - set PWA cache files list, bundle and minify js",
+ "author": "Jake G <106644+Jieiku@users.noreply.github.com>",
+ "license": "MIT",
+ "homepage": "https://github.com/Jieiku/abridge",
+ "scripts": {
+ "abridge": "node -e \"if ( require('fs').existsSync('./themes/abridge/package_abridge.js')) {require('fs').copyFileSync('./themes/abridge/package_abridge.js', './package_abridge.js')}\" && node package_abridge.js",
+ "search:pagefind": "node -e \"if ( require('fs').existsSync('./themes/abridge/static/js/searchChange.js')) {require('fs').copyFileSync('./themes/abridge/static/js/searchChange.js', './static/js/searchChange.js')}\" && node ./static/js/searchChange.js --pagefind",
+ "search:elasticlunr": "node -e \"if ( require('fs').existsSync('./themes/abridge/static/js/searchChange.js')) {require('fs').copyFileSync('./themes/abridge/static/js/searchChange.js', './static/js/searchChange.js')}\" && node ./static/js/searchChange.js --elasticlunr"
+ },
+ "dependencies": {
+ "fast-toml": "^0.5.4",
+ "jsonminify": "^0.4.2",
+ "pagefind": "^1.1.0",
+ "replace-in-file": "^8.1.0",
+ "uglify-js": "^3.17.4"
+ }
+}
diff --git a/package_abridge.js b/package_abridge.js
new file mode 100644
index 0000000..70cb40c
--- /dev/null
+++ b/package_abridge.js
@@ -0,0 +1,419 @@
+const fs = require('fs');
+const path = require("path");
+const TOML = require('fast-toml');
+const UglifyJS = require('uglify-js');
+const jsonminify = require("jsonminify");
+const util = require("util");
+const { exec } = require("child_process");
+const { exit } = require('process');
+const execPromise = util.promisify(exec);
+
+if (!(fs.existsSync('config.toml'))) {
+ throw new Error('ERROR: cannot find config.toml!');
+}
+const tomlString = String(fs.readFileSync('config.toml'));
+const data = TOML.parse(tomlString);
+const js_prestyle = data.extra.js_prestyle;
+const js_switcher = data.extra.js_switcher;
+const js_email_encode = data.extra.js_email_encode;
+const js_copycode = data.extra.js_copycode;
+const search_library = data.extra.search_library;
+const index_format = data.search.index_format;
+const uglyurls = data.extra.uglyurls;
+const js_bundle = data.extra.js_bundle;
+const offline = data.extra.offline;
+const online_url = data.extra.online_url;
+const online_indexformat = data.extra.online_indexformat;
+const pwa = data.extra.pwa;
+const pwa_VER = data.extra.pwa_VER;
+const pwa_NORM_TTL = data.extra.pwa_NORM_TTL;
+const pwa_LONG_TTL = data.extra.pwa_LONG_TTL;
+const pwa_TTL_NORM = data.extra.pwa_TTL_NORM;
+const pwa_TTL_LONG = data.extra.pwa_TTL_LONG;
+const pwa_TTL_EXEMPT = data.extra.pwa_TTL_EXEMPT;
+const pwa_cache_all = data.extra.pwa_cache_all;
+const pwa_BASE_CACHE_FILES = data.extra.pwa_BASE_CACHE_FILES;
+
+// This is used to pass arguments to zola via npm, for example:
+// npm run abridge -- "--base-url https://abridge.pages.dev"
+const args = process.argv[2] ? ' '+process.argv[2] : '';
+
+async function execWrapper(cmd) {
+ const { stdout, stderr } = await execPromise(cmd);
+ if (stdout) {
+ console.log(stdout);
+ }
+ if (stderr) {
+ console.log('ERROR: '+stderr);
+ }
+}
+
+async function abridge() {
+ await sync();
+ const { replaceInFileSync } = await import('replace-in-file');
+ if (offline === false) {
+ if (typeof online_url !== 'undefined' && typeof online_indexformat !== 'undefined') {
+ replaceInFileSync({files: 'config.toml', from: /base_url.*=.*/g, to: "base_url = \""+online_url+"\""});
+ replaceInFileSync({files: 'config.toml', from: /index_format.*=.*/g, to: "index_format = \""+online_indexformat+"\""});
+ }
+ } else if (offline === true) {
+ if (typeof online_url !== 'undefined' && typeof online_indexformat !== 'undefined') {
+ replaceInFileSync({files: 'config.toml', from: /base_url.*=.*/g, to: "base_url = \""+__dirname+"\/public\""});
+ replaceInFileSync({files: 'config.toml', from: /index_format.*=.*/g, to: "index_format = \"elasticlunr_javascript\""});
+ } else {
+ throw new Error('ERROR: offline = true requires that online_url and online_indexformat are set in config.toml, so that the base_url and index_format can be restored if offline is later set to false.');
+ }
+ }
+
+ console.log('Zola Build to generate files for minification:');
+ await execWrapper('zola build'+args);
+
+ //check that static/js exists, do this after zola build, it will handle creating static if missing.
+ var jsdir = 'static/js';
+ try {
+ fs.mkdirSync(jsdir);
+ } catch(e) {
+ if (e.code != 'EEXIST') throw e;
+ }
+
+ // check if abridge is used directly or as a theme.
+ bpath = '';
+ if (fs.existsSync('./themes')) {
+ bpath = 'themes/abridge/';
+ }
+
+ base_url = data.base_url;
+ if (base_url.slice(-1) == "/") {
+ base_url = base_url.slice(0, -1);
+ }
+
+ if (search_library === 'elasticlunr') {
+ if (fs.existsSync('content/static/stork_toml.md')) {
+ replaceInFileSync({files: 'content/static/stork_toml.md', from: /draft.*=.*/g, to: "draft = true"});
+ }
+ if (fs.existsSync('content/static/tinysearch_json.md')) {
+ replaceInFileSync({files: 'content/static/tinysearch_json.md', from: /draft.*=.*/g, to: "draft = true"});
+ }
+ } else if (search_library === 'tinysearch') {
+ if (!fs.existsSync('content/static/tinysearch_json.md')) {// 'content/static/tinysearch_json.md' file is missing, copy from abridge theme.
+ fs.copyFileSync(bpath+'content/static/tinysearch_json.md', 'content/static/tinysearch_json.md',fs.constants.COPYFILE_EXCL);
+ }
+ if (fs.existsSync('content/static/stork_toml.md')) {
+ replaceInFileSync({files: 'content/static/stork_toml.md', from: /draft.*=.*/g, to: "draft = true"});
+ }
+ if (fs.existsSync('content/static/tinysearch_json.md')) {
+ replaceInFileSync({files: 'content/static/tinysearch_json.md', from: /draft.*=.*/g, to: "draft = false"});
+ }
+ // zola build && mkdir -p tmp && tinysearch --optimize --path tmp public/data_tinysearch/index.html && rsync -avz tmp/*.wasm static/ && rm -rf tmp
+ } else if (search_library === 'stork') {
+
+ if (!fs.existsSync('content/static/stork_toml.md')) {// 'content/static/stork_toml.md' file is missing, copy from abridge theme.
+ fs.copyFileSync(bpath+'content/static/stork_toml.md', 'content/static/stork_toml.md',fs.constants.COPYFILE_EXCL);
+ }
+ if (fs.existsSync('content/static/stork_toml.md')) {
+ replaceInFileSync({files: 'content/static/stork_toml.md', from: /draft.*=.*/g, to: "draft = false"});
+ }
+ if (fs.existsSync('content/static/tinysearch_json.md')) {
+ replaceInFileSync({files: 'content/static/tinysearch_json.md', from: /draft.*=.*/g, to: "draft = true"});
+ }
+ // zola build && stork build --input public/data_stork/index.html --output static/stork.st
+ } else if (search_library === 'pagefind') {
+ if (fs.existsSync('content/static/stork_toml.md')) {
+ replaceInFileSync({files: 'content/static/stork_toml.md', from: /draft.*=.*/g, to: "draft = true"});
+ }
+ if (fs.existsSync('content/static/tinysearch_json.md')) {
+ replaceInFileSync({files: 'content/static/tinysearch_json.md', from: /draft.*=.*/g, to: "draft = true"});
+ }
+
+ // Run the pagefind script to generate the index files.
+ // Has to happen at start otherwise, it happens too late asyncronously.
+ const createIndex = require('./static/js/pagefind.index.js'); // run the pagefind index.js script
+ await createIndex(); // makes program wait for pagefind build execution
+ }
+
+ if (pwa) {// Update pwa settings, file list, and hashes.
+ if (typeof pwa_VER !== 'undefined' && typeof pwa_NORM_TTL !== 'undefined' && typeof pwa_LONG_TTL !== 'undefined' && typeof pwa_TTL_NORM !== 'undefined' && typeof pwa_TTL_LONG !== 'undefined' && typeof pwa_TTL_EXEMPT !== 'undefined') {
+ // update from abridge theme.
+ fs.copyFileSync(bpath+'static/sw.js', 'static/sw.js');
+ fs.copyFileSync(bpath+'static/js/sw_load.js', 'static/js/sw_load.js');
+ // Update settings in PWA javascript file, using options parsed from config.toml. sw.min.js?v=3.10.0", "++"
+ if (fs.existsSync('static/js/sw_load.js')) {
+ sw_load_min = '.js?v=';
+ if (js_bundle) {
+ sw_load_min = '.min.js?v=';
+ }
+ replaceInFileSync({files: 'static/js/sw_load.js', from: /sw.*v=.*/g, to: "sw"+sw_load_min+pwa_VER+"\","});
+ }
+ if (fs.existsSync('static/sw.js')) {
+ replaceInFileSync({files: 'static/sw.js', from: /NORM_TTL.*=.*/g, to: "NORM_TTL = "+pwa_NORM_TTL+";"});
+ replaceInFileSync({files: 'static/sw.js', from: /LONG_TTL.*=.*/g, to: "LONG_TTL = "+pwa_LONG_TTL+";"});
+ replaceInFileSync({files: 'static/sw.js', from: /TTL_NORM.*=.*/g, to: "TTL_NORM = ["+pwa_TTL_NORM+"];"});
+ replaceInFileSync({files: 'static/sw.js', from: /TTL_LONG.*=.*/g, to: "TTL_LONG = ["+pwa_TTL_LONG+"];"});
+ replaceInFileSync({files: 'static/sw.js', from: /TTL_EXEMPT.*=.*/g, to: "TTL_EXEMPT = ["+pwa_TTL_EXEMPT+"];"});
+ }
+
+ if (pwa_cache_all === true) {
+ console.log('info: pwa_cache_all = true in config.toml, so caching the entire site.\n');
+ // Generate array from the list of files, for the entire site.
+
+ var dir = 'public';
+ try {
+ fs.mkdirSync(dir);
+ } catch(e) {
+ if (e.code != 'EEXIST') throw e;
+ }
+ const path = './public/';
+ cache = 'this.BASE_CACHE_FILES = [';
+ files = fs.readdirSync(path, { recursive: true, withFileTypes: false })
+ .forEach(
+ (file) => {
+ // check if is directory, if not then add the path/file
+ if (!fs.lstatSync(path+file).isDirectory()) {
+ // format output
+ item = "/"+file.replace(/index\.html$/i,'');// strip index.html from path
+ item = item.replace(/\\/g,'/');// replace backslash with forward slash for Windows
+ item = item.replace(/^\/sw(\.min)?\.js/i,'');// dont cache service worker
+
+ // if formatted output is not empty line then append it to cache var
+ if (item != '') {// skip empty lines
+ cache = cache+"'"+item+"',";
+ }
+ }
+ }
+ );
+ cache = cache.slice(0, -1)+'];'// remove the last comma and close the array
+ } else if (pwa_BASE_CACHE_FILES) {
+ cache = 'this.BASE_CACHE_FILES = ['+pwa_BASE_CACHE_FILES+'];';
+ }
+
+ // update the BASE_CACHE_FILES variable in the sw.js service worker file
+ results = replaceInFileSync({
+ files: 'static/sw.js',
+ from: /this\.BASE_CACHE_FILES =.*/g,
+ to: cache,
+ countMatches: true,
+ });
+ } else {
+ throw new Error('ERROR: pwa requires that pwa_VER, pwa_NORM_TTL, pwa_LONG_TTL, pwa_TTL_NORM, pwa_TTL_LONG, pwa_TTL_EXEMPT are set in config.toml.');
+ }
+ }
+
+ if (bpath === '') {// abridge used directly
+ // These are truely static js files, so they should only need to be updated by the abridge maintainer or contributors.
+ minify(['static/js/theme.js']);
+ minify(['static/js/theme_light.js']);
+ // Something went wrong with minifying katexbundle, so commenting this out for now
+ // minify(['static/js/katex.min.js','static/js/mathtex-script-type.min.js','static/js/katex-auto-render.min.js','static/js/katexoptions.js'],'static/js/katexbundle.min.js');
+ minify(['static/js/elasticlunr.min.js','static/js/search.js'],'static/js/search_elasticlunr.min.js');
+ minify(['static/js/stork.js','static/js/stork_config.js'],'static/js/search_stork.min.js');
+ minify(['static/js/tinysearch.js'],'static/js/search_tinysearch.min.js');
+ minify(['static/js/prestyle.js','static/js/theme_button.js','static/js/email.js','static/js/codecopy.js','static/js/sw_load.js'],'static/js/abridge_nosearch.min.js');
+ minify(['static/js/prestyle.js','static/js/theme_button.js','static/js/email.js','static/js/codecopy.js'],'static/js/abridge_nosearch_nopwa.min.js');
+ minify(['static/js/sw_load.js']);
+ minify(['static/sw.js']);
+ } else if (pwa) {
+ minify(['static/js/sw_load.js']);
+ minify(['static/sw.js']);
+ }
+
+ // if manifest.json is present, then minify it.
+ if (fs.existsSync('static/manifest.json')) {
+ let out;
+ try {
+ out = JSON.minify(fs.readFileSync('static/manifest.json', {encoding:"utf-8"}));
+ } catch(err) {
+ console.log(err);
+ }
+ fs.writeFileSync('static/manifest.min.json', out);
+ }
+
+ abridge_bundle = bundle(bpath,js_prestyle,js_switcher,js_email_encode,js_copycode,search_library,index_format,uglyurls,false);
+ minify(abridge_bundle,'static/js/abridge_nopwa.min.js');
+
+ abridge_bundle = bundle(bpath,js_prestyle,js_switcher,js_email_encode,js_copycode,search_library,index_format,uglyurls,pwa);
+ minify(abridge_bundle,'static/js/abridge.min.js');
+
+ console.log('Zola Build to generate new integrity hashes for the previously minified files:');
+ await execWrapper('zola build'+args);
+}
+
+function bundle(bpath,js_prestyle,js_switcher,js_email_encode,js_copycode,search_library,index_format,uglyurls,pwa) {
+ minify_files = [];
+
+ if (js_prestyle) {
+ minify_files.push(bpath+'static/js/prestyle.js');
+ }
+ if (js_switcher) {
+ minify_files.push(bpath+'static/js/theme_button.js');
+ }
+ if (js_email_encode) {
+ minify_files.push(bpath+'static/js/email.js');
+ }
+ if (js_copycode) {
+ minify_files.push(bpath+'static/js/codecopy.js');
+ }
+ if (search_library) {
+ if ((search_library === 'elasticlunr' && offline === true) || (search_library === 'elasticlunr' && index_format === 'elasticlunr_javascript' && uglyurls === true)) {
+ minify_files.push('public/search_index.en.js');
+ minify_files.push(bpath+'static/js/elasticlunr.min.js');
+ minify_files.push(bpath+'static/js/searchjavaugly.js');
+ } else if (search_library === 'elasticlunr' && index_format === 'elasticlunr_javascript') {
+ minify_files.push('public/search_index.en.js');
+ minify_files.push(bpath+'static/js/elasticlunr.min.js');
+ minify_files.push(bpath+'static/js/searchjava.js');
+ } else if (search_library === 'elasticlunr') {//abridge default
+ minify_files.push(bpath+'static/js/elasticlunr.min.js');
+ minify_files.push(bpath+'static/js/search.js');
+ } else if (search_library === 'stork') {
+ minify_files.push(bpath+'static/js/stork.js');
+ minify_files.push(bpath+'static/js/stork_config.js');
+ } else if (search_library === 'tinysearch') {
+ minify_files.push(bpath+'static/js/tinysearch.js');
+ } else if (search_library === 'pagefind') {
+ minify_files.push(bpath+'static/js/pagefind.js');
+ minify_files.push(bpath+'static/js/pagefind.search.js');
+ }
+ }
+ if (pwa) {
+ minify_files.push('static/js/sw_load.js');
+ }
+ return minify_files;
+}
+
+function minify(fileA,outfile) {
+ const options = {
+ mangle: true,
+ compress: {
+ //expression: true,//Parse a single expression, rather than a program (for parsing JSON).
+ //global_defs: false,// a way to pass parameters
+ //module: true,//Process input as ES module (implies --toplevel)
+ //toplevel: true,//Compress and/or mangle variables in top level scope.
+ hoist_funs: true,//hoist function declarations
+ unsafe: true,
+ unsafe_comps: true,
+ unsafe_Function: true,
+ unsafe_math: true,
+ unsafe_proto: true,
+ unsafe_regexp: true,
+ unsafe_undefined: true,
+ drop_console: true
+ }
+ }
+ if (!outfile) {// outfile parameter omitted, infer based on input
+ outfile = fileA[0].slice(0,-2)+'min.js';
+ }
+ var filesContents = fileA.map(function (file) {// array input to support multiple files
+ return fs.readFileSync(file, 'utf8');
+ });
+
+ result = UglifyJS.minify(filesContents, options);
+ fs.writeFileSync(outfile, result.code);
+
+}
+
+abridge();
+
+async function sync() {
+ // Check if the submodule is present, if not skip entire function
+ if (!fs.existsSync(path.join(__dirname, "themes/abridge"))) {
+ return;
+ }
+
+ // Checks for changes from local version in static, package.json and config.toml
+ // and if there are changes it sync from the submodule
+
+ // Check for changes in static
+ const staticFolder = path.join(__dirname, "static/js");
+ const submoduleFolder = path.join(__dirname, "themes/abridge/static/js");
+
+ const files = fs.readdirSync(staticFolder);
+
+ files.forEach((file) => {
+ if (file.endsWith(".js") && !file.endsWith(".min.js")) {
+ try {
+ const localFile = path.join(staticFolder, file);
+ const submoduleFile = path.join(submoduleFolder, file);
+ const localFileContent = fs.readFileSync(localFile, "utf-8");
+ const submoduleFileContent = fs.readFileSync(submoduleFile, "utf-8");
+
+ if (localFileContent !== submoduleFileContent) {
+ console.log(`Updating ${file} from submodule`);
+ fs.copyFileSync(submoduleFile, localFile);
+ }
+ } catch (error) {
+ console.log(`Skipping ${file} due to error: ${error}`);
+ }
+ }
+ });
+
+ // Check for changes in package.json
+ const packageJson = path.join(__dirname, "package.json");
+ const submodulePackageJson = path.join(
+ __dirname,
+ "themes/abridge/package.json"
+ );
+
+ const packageJsonContent = fs.readFileSync(packageJson, "utf-8");
+ const submodulePackageJsonContent = fs.readFileSync(
+ submodulePackageJson,
+ "utf-8"
+ );
+
+ // Check for changes in dependencies - prompting an npm update
+ let checkPackageVersion = function (content) {
+ let matches = content.match(/"dependencies": \{([^}]+)\}/)[1]; // Look in the dependencies section
+ return [...matches.matchAll(/"(\w+-\w+|\w+)": "[^0-9]*([0-9])/g)].map(match => ({ // Extract all packages and their major version number (aka for breaking changes which need an update)
+ name: match[1],
+ majorVersion: match[2]
+ })).sort((a, b) => a.name.localeCompare(b.name));
+ };
+
+ const packageVersionLocal = checkPackageVersion(packageJsonContent);
+ const packageVersionSubmodule = checkPackageVersion(submodulePackageJsonContent);
+ if (JSON.stringify(packageVersionLocal) !== JSON.stringify(packageVersionSubmodule)) {
+ console.log(
+ "\x1b[31m%s\x1b[0m",
+ "warning:",
+ "The packages are out of date, please run `npm install` to update them."
+ );
+ exit(1);
+ }
+ console.log(packageVersionLocal, packageVersionSubmodule);
+
+ if (packageJsonContent !== submodulePackageJsonContent) {
+ console.log("Updating package.json from submodule");
+ fs.copyFileSync(submodulePackageJson, packageJson);
+ }
+ const configToml = path.join(__dirname, "config.toml");
+ const submoduleConfigToml = path.join(
+ __dirname,
+ "themes/abridge/config.toml"
+ );
+
+ let adjustTomlContent = function (content) {
+ content = content.replace(/^\s+|\s+$|\s+(?=\s)/g, ""); // Remove all leading and trailing whitespaces and multiple whitespaces
+ content = content.replace(/(^#)(?=\s*\w+\s*=\s*)|[[:blank:]]*#.*$/gm, ""); // A regex to selectively remove all comments, and to uncomment all commented config lines
+ content = content.replace(/(\[([^]]*)\])|(\{([^}]*)\})/gs, ""); // A regex to remove all tables and arrays
+ content = content.replace(
+ /(^#.*$|(["']).*?\2|(?<=\s)#.*$|\btrue\b|\bfalse\b)/gm,
+ ""
+ ); // A regex to remove all user added content, (so you can tell if the .toml format has changed)
+ return content.trim(); // Finally remove any leading or trailing white spaces
+ };
+
+ const configTomlContent = adjustTomlContent(
+ fs.readFileSync(configToml, "utf-8")
+ );
+ const submoduleConfigTomlContent = adjustTomlContent(
+ fs.readFileSync(submoduleConfigToml, "utf-8")
+ );
+
+ if (configTomlContent !== submoduleConfigTomlContent) {
+ // This should say info: then the message in blue (which works in every terminal)
+ console.log(
+ "\x1b[34m%s\x1b[0m",
+ "info:",
+ "The config.toml file format may have changed, please update it manually."
+ );
+ }
+}
diff --git a/sass/_extra.scss b/sass/_extra.scss
new file mode 100644
index 0000000..3f86323
--- /dev/null
+++ b/sass/_extra.scss
@@ -0,0 +1,26 @@
+/******************************************************************************
+ * Extra - Put your extra SASS/CSS here, it will get bundled with abridge.css
+ *****************************************************************************/
+
+body {
+ font-size: 16px;
+}
+
+a {
+ color: #9E358F;
+ text-decoration: underline ;
+}
+
+a:hover, a:focus {
+ color: #65225b;
+ text-decoration: underline;
+}
+
+a:focus {
+ outline: 5px auto -webkit-focus-ring-color;
+ outline-offset: -2px;
+}
+
+h1 {
+ font-family: "Raleway",Helvetica,Arial,sans-serif;
+}
diff --git a/sass/abridge.scss b/sass/abridge.scss
new file mode 100644
index 0000000..f9844a0
--- /dev/null
+++ b/sass/abridge.scss
@@ -0,0 +1,181 @@
+@use '../themes/abridge/sass/abridge' with (
+ /// LINES HERE END WITH COMMA AFTER THE VALUE!
+ /// The things your less likely to need to override have been commented out.
+
+ /// Enable a centered viewport for , , inside
+ /// Fluid layout until a defined size, then becomes centered viewport.
+ //$enable-maxwidth: true,
+ $mw: 70%,// max-width
+ //$mb: 1200px,// value at which to switch from fluid layout to max-width
+
+ $abridgeMode: "switcher",//valid values: switcher, auto, dark, light
+ $syntax-mode: "auto",// Force syntax mode: auto, dark, light
+ $switcherDefault: "light",// default nojs switcher mode: dark, light (make sure to also set js_switcher_default in config.toml)
+
+ $color: "orange",// color template to use/override: orange, blue, blueshade
+
+ $color-syntax: "abridge",// syntax color template to use/override: abridge,
+ $syntax: true,//syntax highlighting for Code Blocks.
+
+ $enable-icons: true,// false disables ALL icons
+ $ic: true,// true for colorized icons, otherwise #888 is used.
+
+ $icon-rss: true,
+ $icon-mail: false,// e-mail
+ $icon-mastodon: false,
+ $icon-element: false,
+ $icon-matrix: false,
+ $icon-buymeacoffee: false,
+ $icon-kofi: false,
+ $icon-twitter: false,
+ $icon-facebook: false,
+ $icon-linkedin: false,
+ $icon-codeberg: false,
+ $icon-gitlab: false,
+ $icon-github: false,
+ $icon-github-sponsor: false,
+ $icon-bitbucket: false,
+ $icon-python: false,
+ $icon-docker: false,
+ $icon-stack: false,
+ $icon-instagram: false,
+ $icon-pixelfed: false,
+ $icon-pinterest: false,
+ $icon-discord: false,
+ $icon-twitch: false,
+ $icon-youtube: false,
+ $icon-peertube: false,
+ $icon-researchgate: false,
+
+ //$icon-x: true,// x symbol, used to close search results page.
+ //$icon-search: true,//search, spyglass search button in search box.
+ //$icon-adjust: true,//theme switcher dark/light toggle button.
+ //$icon-angll: true,//pagination, goto first page
+ //$icon-angl: true,//pagination, goto previous page
+ //$icon-angr: true,//pagination, goto next page
+ //$icon-angrr: true,//pagination, goto last page
+ //$icon-angu: true,//back to top button, appears after scrolling down.
+ //$icon-world: true,//language select menu
+ //$icon-copy: true,//copy to clipboard, for code blocks.
+
+ //$icon-ffolder: false,
+ //$icon-folder: false,// categories folder
+ //$icon-ftag: false,
+ //$icon-tag: false,// tag
+ //$icon-check: false,// check mark
+ //$icon-chevron: false,// chevron down
+ //$icon-clock: false,// time analog clock
+ //$icon-date: false,// calendar
+ //$icon-globe: false,
+ //$icon-home: false,
+ //$icon-minus: false,// minus symbol
+ //$icon-moon: false,// dark moon
+ //$icon-sun: false,// light sun
+
+ /// The colors below can be overrided individually as needed.
+
+ /// Light Colors
+ //$f1: #333,// Font Color Primary
+ //$f2: #222,// Font Color Headers
+ //$c1: #fff,// Background Color Primary
+ //$c2: #eee,// Background Color Secondary
+ //$c3: #ddd,// Table Rows, Block quote edge, Borders
+ //$c4: #555,// Disabled Buttons, Borders, mark background
+ $a1: #65225b,// link color
+ $a2: #65225b,// link hover/focus color
+ $a3: #65225b,// link h1-h2 hover/focus color
+ $a4: #65225b,// link visited color
+ //$cg: #373,// ins green, success
+ //$cr: #d33,// del red, errors
+
+ /// Light Syntax Colors
+ //$h0: #f7f7f7,// background color
+ //$h1: #222,// unstyled text
+ //$h2: #666,// comments
+ //$h3: #a21,// red, support function
+ //$h4: #930,// orange, punctuation, constant, variable, json-key
+ //$h5: #a50,// tan, entity/function name
+ //$h6: #350,// green, string
+ //$h7: #286,// teal, escape character
+ //$h8: #059,// blue, declaration, tag, property
+ //$h9: #a3c,// purple, operators
+ //$ha: 92%,// mark/highlight line
+
+ /// These lines find the spot at which to insert your appended fonts.
+ //$findFont-Main: "Segoe UI", // ← APPEND custom MAIN font(s) AFTER this
+ //$findFont-Code: "Segoe UI Mono",// ← APPEND custom CODE font(s) AFTER this
+
+ /// If you want to prepend the font list, then use null instead:
+ $findFont-Main: null, // ← PREPEND custom MAIN font(s)
+ $findFont-Code: null, // ← PREPEND custom CODE font(s)
+
+ // These lines specify the fonts to add.
+ $fontExt-Main: ("Source Sans 3"),// MAIN font(s) to add
+ $fontExt-Code: ("Ubuntu Mono"),// CODE font(s) to add
+
+ /// If relying on installed fonts alone, then the above is all you need, if the visiting system has the intended font installed then it will use it!
+ /// However, if you want to load the Font File resource to ensure it is loaded if they do not have it, then use @use at the VERY Bottom of this file.
+ /// For a Sans based system font stack, I measured the least Cumulative Layout Shift with: Roboto, Lato, Arimo
+
+ /// If prepending/appending fonts above, then no need to change them with the 2 below lines.
+ /// The following 2 below lines give a way to hard code a font list if you prefer.
+ //$font: Roboto system-ui -apple-system BlinkMacSystemFont "Segoe UI" Oxygen Ubuntu Cantarell "Fira Sans" "Droid Sans" "Helvetica Neue" "Noto Sans" Helvetica Arial sans-serif,
+ //$font-mono: ui-monospace Menlo Monaco Consolas "SF Mono" "Cascadia Mono" "Segoe UI Mono" "DejaVu Sans Mono" "Liberation Mono" "Roboto Mono" "Oxygen Mono" "Ubuntu Monospace" "Ubuntu Mono" "Source Code Pro" "Fira Mono" "Droid Sans Mono" "Courier New" Courier monospace,
+
+ /// Enable , , inside as a container
+ //$enable-semantic-container: true,
+
+ /// Enable responsive typography, Fixed root element size if disabled
+ //$enable-responsive-typography: true,
+
+ /// Enable responsive spacings for , , ,
+ //$enable-responsive-spacings: false,
+
+ /// Enable a centered viewport for , , inside
+ /// This option will only work if $enable-maxwidth: false
+ //$enable-viewport: false,
+
+ /// xs: Extra small (portrait phones)
+ /// sm: Small(landscape phones)
+ /// md: Medium(tablets)
+ /// lg: Large(desktops)
+ /// xl: Extra large (large desktops)
+
+ /// Breakpoints
+ //$breakpoints: (xs: 0, sm: 576px, md: 768px, lg: 992px, xl: 1200px),
+
+ /// Viewports
+ //$viewports: (sm: 510px, md: 700px, lg: 920px, xl: 1130px),
+
+ //$document: true,//Content-box & Responsive typography
+ //$typography: true,//a, headings, p, ul, blockquote
+ //$sectioning: true,//responsive Container, header, main, footer
+ //$nav: true,//Horizontal Navigation at top of page
+ //$embedded: true,//Embedded content, iframe, video, images, etc.
+ //$table: true,//table specific elements
+ //$code: true,//codeblocks, code, pre, kbd
+ //$hr: true,//Horizontal Rule
+ //$scroller: true,//Horizontal scroller ()
+ //$button: true,//Form elements (button)
+ //$form: true,//Form elements (non-button)
+ //$top: true,//back to top button using CSS
+ //$search: true,//search feature
+ //$blocks: true,//css classes for block formatting, eg: recent posts, table of contents, series navigation
+ //$series: true,//series navigation list
+ //$modifiers: true,//tiny modifier classes for sizing, spacing, etc.
+ //$misc: false,
+ //$grid: true,//Infinity Grid, column based layouts.
+ //$syntax: true,//syntax highlighting for code blocks
+);
+@use "extra";
+/******************************************************************************
+ * FONTS - Abridge by default uses the System Font Stack
+ * https://css-tricks.com/snippets/css/system-font-stack/
+ * However if you need you can load a specific font below,
+ * make sure to have the relevant woff2 fonts in your static/fonts folder
+ * I measured the least Cumulative Layout Shift with: Roboto, Lato, Arimo
+ *****************************************************************************/
+//@use "fonts/Roboto";
+@use "fonts/Ralewway.scss";
+@use "fonts/SourceSans.scss";
+@use "fonts/UbuntuMono.scss";
diff --git a/sass/fonts/_Ralewway.scss b/sass/fonts/_Ralewway.scss
new file mode 100644
index 0000000..15c1b14
--- /dev/null
+++ b/sass/fonts/_Ralewway.scss
@@ -0,0 +1,16 @@
+/* raleway-700 - latin */
+@font-face {
+ font-display: swap;
+ font-family: "Raleway";
+ font-style: normal;
+ font-weight: 700;
+ src: url("/fonts/raleway-v28-latin-700.woff2") format("woff2");
+}
+/* raleway-900 - latin */
+@font-face {
+ font-display: swap;
+ font-family: "Raleway";
+ font-style: normal;
+ font-weight: 900;
+ src: url("/fonts/raleway-v28-latin-900.woff2") format("woff2");
+}
diff --git a/sass/fonts/_SourceSans.scss b/sass/fonts/_SourceSans.scss
new file mode 100644
index 0000000..77eac9c
--- /dev/null
+++ b/sass/fonts/_SourceSans.scss
@@ -0,0 +1,8 @@
+/* source-sans-3-regular - latin */
+@font-face {
+ font-display: swap;
+ font-family: "Source Sans 3";
+ font-style: normal;
+ font-weight: 400;
+ src: url("/fonts/source-sans-3-v9-latin-regular.woff2") format("woff2");
+}
diff --git a/sass/fonts/_UbuntuMono.scss b/sass/fonts/_UbuntuMono.scss
new file mode 100644
index 0000000..6da43c0
--- /dev/null
+++ b/sass/fonts/_UbuntuMono.scss
@@ -0,0 +1,8 @@
+/* ubuntu-mono-regular - latin */
+@font-face {
+ font-display: swap;
+ font-family: "Ubuntu Mono";
+ font-style: normal;
+ font-weight: 400;
+ src: url("/fonts/ubuntu-mono-v15-latin-regular.woff2") format("woff2");
+}
diff --git a/static/CNAME b/static/CNAME
new file mode 100644
index 0000000..e02bff5
--- /dev/null
+++ b/static/CNAME
@@ -0,0 +1 @@
+blog.haskell.org
diff --git a/static/android-chrome-192x192.png b/static/android-chrome-192x192.png
new file mode 100644
index 0000000..7be7e98
Binary files /dev/null and b/static/android-chrome-192x192.png differ
diff --git a/static/android-chrome-512x512.png b/static/android-chrome-512x512.png
new file mode 100644
index 0000000..5df0787
Binary files /dev/null and b/static/android-chrome-512x512.png differ
diff --git a/static/apple-touch-icon.png b/static/apple-touch-icon.png
new file mode 100644
index 0000000..dd1f414
Binary files /dev/null and b/static/apple-touch-icon.png differ
diff --git a/static/browserconfig.xml b/static/browserconfig.xml
new file mode 100644
index 0000000..b3930d0
--- /dev/null
+++ b/static/browserconfig.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+ #da532c
+
+
+
diff --git a/static/favicon-32x32.png b/static/favicon-32x32.png
new file mode 100644
index 0000000..16e5366
Binary files /dev/null and b/static/favicon-32x32.png differ
diff --git a/static/favicon.ico b/static/favicon.ico
new file mode 100644
index 0000000..410bcd7
Binary files /dev/null and b/static/favicon.ico differ
diff --git a/static/favicon.svg b/static/favicon.svg
new file mode 100644
index 0000000..c67110e
--- /dev/null
+++ b/static/favicon.svg
@@ -0,0 +1,6 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/static/fonts/raleway-v28-latin-700.woff2 b/static/fonts/raleway-v28-latin-700.woff2
new file mode 100644
index 0000000..f507f64
Binary files /dev/null and b/static/fonts/raleway-v28-latin-700.woff2 differ
diff --git a/static/fonts/raleway-v28-latin-900.woff2 b/static/fonts/raleway-v28-latin-900.woff2
new file mode 100644
index 0000000..ee84172
Binary files /dev/null and b/static/fonts/raleway-v28-latin-900.woff2 differ
diff --git a/static/fonts/source-sans-3-v9-latin-regular.woff2 b/static/fonts/source-sans-3-v9-latin-regular.woff2
new file mode 100644
index 0000000..59d087a
Binary files /dev/null and b/static/fonts/source-sans-3-v9-latin-regular.woff2 differ
diff --git a/static/fonts/ubuntu-mono-v15-latin-regular.woff2 b/static/fonts/ubuntu-mono-v15-latin-regular.woff2
new file mode 100644
index 0000000..4df7d1e
Binary files /dev/null and b/static/fonts/ubuntu-mono-v15-latin-regular.woff2 differ
diff --git a/static/images/haskell.svg b/static/images/haskell.svg
new file mode 100644
index 0000000..d7b1862
--- /dev/null
+++ b/static/images/haskell.svg
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/static/js/abridge.min.js b/static/js/abridge.min.js
new file mode 100644
index 0000000..71801f2
--- /dev/null
+++ b/static/js/abridge.min.js
@@ -0,0 +1 @@
+let items=document.querySelectorAll(".preStyle"),changeIcon=(items.forEach(e=>{e.rel="stylesheet"}),!function(){for(var e=document.getElementsByClassName("m-protected"),t=0;t{e.classList.add(t),setTimeout(()=>e.classList.remove(t),2500)}),copyCodeAndChangeIcon=async(t,e)=>{e=(e.querySelector("table")?getTableCode:getNonTableCode)(e);try{await navigator.clipboard.writeText(e),changeIcon(t,"yes")}catch(e){changeIcon(t,"err")}},getNonTableCode=e=>[...e.querySelectorAll("code")].map(e=>e.textContent).join(""),getTableCode=e=>[...e.querySelectorAll("tr")].map(e=>e.querySelector("td:last-child")?.innerText??"").join("");document.querySelectorAll("pre").forEach(e=>{let t=document.createElement("div");t.className="cc svgs svgh copy",t.innerHTML=" ",e.prepend(t),t.addEventListener("click",()=>copyCodeAndChangeIcon(t,e))}),function(){function g(e){var t=new g.Index;return t.pipeline.add(g.trimmer,g.stopWordFilter,g.stemmer),e&&e.call(t,t),t}var l,c,e,t,d,h,f,p,m,v,y,S,x,b,w,I,E,C,D,F,k,_,N,L,n;g.version="0.9.5",((lunr=g).utils={}).warn=(n=this,function(e){n.console}),g.utils.toString=function(e){return null==e?"":e.toString()},(g.EventEmitter=function(){this.events={}}).prototype.addListener=function(){var e=[].slice.call(arguments),t=e.pop();if("function"!=typeof t)throw new TypeError("last argument must be a function");e.forEach(function(e){this.hasHandler(e)||(this.events[e]=[]),this.events[e].push(t)},this)},g.EventEmitter.prototype.removeListener=function(e,t){this.hasHandler(e)&&-1!=(t=this.events[e].indexOf(t))&&(this.events[e].splice(t,1),0==this.events[e].length)&&delete this.events[e]},g.EventEmitter.prototype.emit=function(e){var t;this.hasHandler(e)&&(t=[].slice.call(arguments,1),this.events[e].forEach(function(e){e.apply(void 0,t)},this))},g.EventEmitter.prototype.hasHandler=function(e){return e in this.events},(g.tokenizer=function(e){var t,n;return arguments.length&&null!=e?Array.isArray(e)?(t=(t=e.filter(function(e){return null!=e})).map(function(e){return g.utils.toString(e).toLowerCase()}),n=[],t.forEach(function(e){e=e.split(g.tokenizer.seperator);n=n.concat(e)},this),n):e.toString().trim().toLowerCase().split(g.tokenizer.seperator):[]}).defaultSeperator=/[\s\-]+/,g.tokenizer.seperator=g.tokenizer.defaultSeperator,g.tokenizer.setSeperator=function(e){null!=e&&"object"==typeof e&&(g.tokenizer.seperator=e)},g.tokenizer.resetSeperator=function(){g.tokenizer.seperator=g.tokenizer.defaultSeperator},g.tokenizer.getSeperator=function(){return g.tokenizer.seperator},(g.Pipeline=function(){this._queue=[]}).registeredFunctions={},g.Pipeline.registerFunction=function(e,t){t in g.Pipeline.registeredFunctions&&g.utils.warn("Overwriting existing registered function: "+t),e.label=t,g.Pipeline.registeredFunctions[t]=e},g.Pipeline.getRegisteredFunction=function(e){return e in g.Pipeline.registeredFunctions!=1?null:g.Pipeline.registeredFunctions[e]},g.Pipeline.warnIfFunctionNotRegistered=function(e){e.label&&e.label in this.registeredFunctions||g.utils.warn("Function is not registered with pipeline. This may cause problems when serialising the index.\n",e)},g.Pipeline.load=function(e){var n=new g.Pipeline;return e.forEach(function(e){var t=g.Pipeline.getRegisteredFunction(e);if(!t)throw Error("Cannot load un-registered function: "+e);n.add(t)}),n},g.Pipeline.prototype.add=function(){[].slice.call(arguments).forEach(function(e){g.Pipeline.warnIfFunctionNotRegistered(e),this._queue.push(e)},this)},g.Pipeline.prototype.after=function(e,t){g.Pipeline.warnIfFunctionNotRegistered(t);e=this._queue.indexOf(e);if(-1==e)throw Error("Cannot find existingFn");this._queue.splice(1+e,0,t)},g.Pipeline.prototype.before=function(e,t){g.Pipeline.warnIfFunctionNotRegistered(t);e=this._queue.indexOf(e);if(-1==e)throw Error("Cannot find existingFn");this._queue.splice(e,0,t)},g.Pipeline.prototype.remove=function(e){e=this._queue.indexOf(e);-1!=e&&this._queue.splice(e,1)},g.Pipeline.prototype.run=function(e){for(var t=[],n=e.length,o=this._queue.length,i=0;i=n&&r-1>=o;)s[n]!==u[o]?s[n]u[o]&&o++:(t.add(s[n]),n++,o++);return t},lunr.SortedSet.prototype.clone=function(){var e=new lunr.SortedSet;return e.elements=this.toArray(),e.length=e.elements.length,e},lunr.SortedSet.prototype.union=function(e){for(var t,e=this.length '.concat(document.getElementById("searchinput").value," ");return o.innerHTML=i,n.insertBefore(o,n.firstChild),e.innerHTML=n.outerHTML,t.innerHTML="",document.getElementById("searchinput").value="",document.body.contains(document.closeSearch)&&(document.closeSearch.onsubmit=function(){document.querySelector("main").innerHTML=window.main}),!1}function b(e){return[0,1,2,3,4][Math.ceil(parseInt(e,16).toString(2).length/8)]}var u,r,l,c,h;document.body.contains(document.goSearch)&&(document.goSearch.onsubmit=function(){return goSearchNow()},u=document.getElementById("suggestions"),r=document.getElementById("searchinput"),document.addEventListener("keydown",function(t){if(191===t.keyCode&&"INPUT"!==document.activeElement.tagName&&"TEXTAREA"!==document.activeElement.tagName&&(t.preventDefault(),r.focus(),u.classList.remove("d-none")),27===t.keyCode){r.blur(),u.classList.add("d-none");for(var e=document.getElementById("suggestions");e.firstChild;)e.removeChild(e.firstChild)}var n=u.querySelectorAll("a");if(!u.classList.contains("d-none")&&0!==n.length){var o=[...n],i=o.indexOf(document.activeElement);let e=0;38===t.keyCode?(t.preventDefault(),n[e=0 ",a=n.querySelector("a"),t=n.querySelector("span:first-child"),d=n.querySelector("span:nth-child(2)"),a.href=e.ref,t.textContent=e.doc.title,d.innerHTML=function(e,t){var n=t.map(function(e){return elasticlunr.stemmer(e.toLowerCase())}),o=!1,i=0,r=[],s=e.toLowerCase().split(". ");for(p in s){var u,a=s[p].split(/[\s\n]/),l=8;for(u in a){if(0<(S=a[u]).length){for(var c in n)elasticlunr.stemmer(S).startsWith(n[c])&&(l=40,o=!0);r.push([S,l,i]),l=2}i=i+S.length+1}i+=1}if(0===r.length)return void 0!==e.length&&300"),y=S[2]+S[0].length;40===S[1]||S[0].length<12||/^[\x00-\xff]+$/.test(S[0])?v.push(e.substring(S[2],y)):(x=function(e,t){for(var n="",o=!1,i=0,r=0,s=0,u=0;u")}return v.push("…"),v.join("")}(e.doc.body,s),u.appendChild(n))});i.length>r;)u.removeChild(i[0])},!0),u.addEventListener("click",function(){for(;u.lastChild;)u.removeChild(u.lastChild);return!1},!0),document.goSearch.onsubmit=e)},"serviceWorker"in navigator&&(navigator.serviceWorker.register("/sw.min.js?v=3.11.0",{scope:"/"}).then(()=>{},e=>{}),navigator.serviceWorker.ready.then(()=>{}));
\ No newline at end of file
diff --git a/static/js/abridge_nopwa.min.js b/static/js/abridge_nopwa.min.js
new file mode 100644
index 0000000..0f0be92
--- /dev/null
+++ b/static/js/abridge_nopwa.min.js
@@ -0,0 +1 @@
+let items=document.querySelectorAll(".preStyle"),changeIcon=(items.forEach(e=>{e.rel="stylesheet"}),!function(){for(var e=document.getElementsByClassName("m-protected"),t=0;t{e.classList.add(t),setTimeout(()=>e.classList.remove(t),2500)}),copyCodeAndChangeIcon=async(t,e)=>{e=(e.querySelector("table")?getTableCode:getNonTableCode)(e);try{await navigator.clipboard.writeText(e),changeIcon(t,"yes")}catch(e){changeIcon(t,"err")}},getNonTableCode=e=>[...e.querySelectorAll("code")].map(e=>e.textContent).join(""),getTableCode=e=>[...e.querySelectorAll("tr")].map(e=>e.querySelector("td:last-child")?.innerText??"").join("");document.querySelectorAll("pre").forEach(e=>{let t=document.createElement("div");t.className="cc svgs svgh copy",t.innerHTML=" ",e.prepend(t),t.addEventListener("click",()=>copyCodeAndChangeIcon(t,e))}),function(){function g(e){var t=new g.Index;return t.pipeline.add(g.trimmer,g.stopWordFilter,g.stemmer),e&&e.call(t,t),t}var a,c,e,t,d,h,f,p,m,v,y,S,x,b,w,I,E,C,D,F,_,k,N,L,n;g.version="0.9.5",((lunr=g).utils={}).warn=(n=this,function(e){n.console}),g.utils.toString=function(e){return null==e?"":e.toString()},(g.EventEmitter=function(){this.events={}}).prototype.addListener=function(){var e=[].slice.call(arguments),t=e.pop();if("function"!=typeof t)throw new TypeError("last argument must be a function");e.forEach(function(e){this.hasHandler(e)||(this.events[e]=[]),this.events[e].push(t)},this)},g.EventEmitter.prototype.removeListener=function(e,t){this.hasHandler(e)&&-1!=(t=this.events[e].indexOf(t))&&(this.events[e].splice(t,1),0==this.events[e].length)&&delete this.events[e]},g.EventEmitter.prototype.emit=function(e){var t;this.hasHandler(e)&&(t=[].slice.call(arguments,1),this.events[e].forEach(function(e){e.apply(void 0,t)},this))},g.EventEmitter.prototype.hasHandler=function(e){return e in this.events},(g.tokenizer=function(e){var t,n;return arguments.length&&null!=e?Array.isArray(e)?(t=(t=e.filter(function(e){return null!=e})).map(function(e){return g.utils.toString(e).toLowerCase()}),n=[],t.forEach(function(e){e=e.split(g.tokenizer.seperator);n=n.concat(e)},this),n):e.toString().trim().toLowerCase().split(g.tokenizer.seperator):[]}).defaultSeperator=/[\s\-]+/,g.tokenizer.seperator=g.tokenizer.defaultSeperator,g.tokenizer.setSeperator=function(e){null!=e&&"object"==typeof e&&(g.tokenizer.seperator=e)},g.tokenizer.resetSeperator=function(){g.tokenizer.seperator=g.tokenizer.defaultSeperator},g.tokenizer.getSeperator=function(){return g.tokenizer.seperator},(g.Pipeline=function(){this._queue=[]}).registeredFunctions={},g.Pipeline.registerFunction=function(e,t){t in g.Pipeline.registeredFunctions&&g.utils.warn("Overwriting existing registered function: "+t),e.label=t,g.Pipeline.registeredFunctions[t]=e},g.Pipeline.getRegisteredFunction=function(e){return e in g.Pipeline.registeredFunctions!=1?null:g.Pipeline.registeredFunctions[e]},g.Pipeline.warnIfFunctionNotRegistered=function(e){e.label&&e.label in this.registeredFunctions||g.utils.warn("Function is not registered with pipeline. This may cause problems when serialising the index.\n",e)},g.Pipeline.load=function(e){var n=new g.Pipeline;return e.forEach(function(e){var t=g.Pipeline.getRegisteredFunction(e);if(!t)throw Error("Cannot load un-registered function: "+e);n.add(t)}),n},g.Pipeline.prototype.add=function(){[].slice.call(arguments).forEach(function(e){g.Pipeline.warnIfFunctionNotRegistered(e),this._queue.push(e)},this)},g.Pipeline.prototype.after=function(e,t){g.Pipeline.warnIfFunctionNotRegistered(t);e=this._queue.indexOf(e);if(-1==e)throw Error("Cannot find existingFn");this._queue.splice(1+e,0,t)},g.Pipeline.prototype.before=function(e,t){g.Pipeline.warnIfFunctionNotRegistered(t);e=this._queue.indexOf(e);if(-1==e)throw Error("Cannot find existingFn");this._queue.splice(e,0,t)},g.Pipeline.prototype.remove=function(e){e=this._queue.indexOf(e);-1!=e&&this._queue.splice(e,1)},g.Pipeline.prototype.run=function(e){for(var t=[],n=e.length,o=this._queue.length,i=0;i=n&&r-1>=o;)s[n]!==u[o]?s[n]u[o]&&o++:(t.add(s[n]),n++,o++);return t},lunr.SortedSet.prototype.clone=function(){var e=new lunr.SortedSet;return e.elements=this.toArray(),e.length=e.elements.length,e},lunr.SortedSet.prototype.union=function(e){for(var t,e=this.length '.concat(document.getElementById("searchinput").value," ");return o.innerHTML=i,n.insertBefore(o,n.firstChild),e.innerHTML=n.outerHTML,t.innerHTML="",document.getElementById("searchinput").value="",document.body.contains(document.closeSearch)&&(document.closeSearch.onsubmit=function(){document.querySelector("main").innerHTML=window.main}),!1}function b(e){return[0,1,2,3,4][Math.ceil(parseInt(e,16).toString(2).length/8)]}var u,r,l,c,h;document.body.contains(document.goSearch)&&(document.goSearch.onsubmit=function(){return goSearchNow()},u=document.getElementById("suggestions"),r=document.getElementById("searchinput"),document.addEventListener("keydown",function(t){if(191===t.keyCode&&"INPUT"!==document.activeElement.tagName&&"TEXTAREA"!==document.activeElement.tagName&&(t.preventDefault(),r.focus(),u.classList.remove("d-none")),27===t.keyCode){r.blur(),u.classList.add("d-none");for(var e=document.getElementById("suggestions");e.firstChild;)e.removeChild(e.firstChild)}var n=u.querySelectorAll("a");if(!u.classList.contains("d-none")&&0!==n.length){var o=[...n],i=o.indexOf(document.activeElement);let e=0;38===t.keyCode?(t.preventDefault(),n[e=0 ",a=n.querySelector("a"),t=n.querySelector("span:first-child"),d=n.querySelector("span:nth-child(2)"),a.href=e.ref,t.textContent=e.doc.title,d.innerHTML=function(e,t){var n=t.map(function(e){return elasticlunr.stemmer(e.toLowerCase())}),o=!1,i=0,r=[],s=e.toLowerCase().split(". ");for(p in s){var u,l=s[p].split(/[\s\n]/),a=8;for(u in l){if(0<(S=l[u]).length){for(var c in n)elasticlunr.stemmer(S).startsWith(n[c])&&(a=40,o=!0);r.push([S,a,i]),a=2}i=i+S.length+1}i+=1}if(0===r.length)return void 0!==e.length&&300"),y=S[2]+S[0].length;40===S[1]||S[0].length<12||/^[\x00-\xff]+$/.test(S[0])?v.push(e.substring(S[2],y)):(x=function(e,t){for(var n="",o=!1,i=0,r=0,s=0,u=0;u")}return v.push("…"),v.join("")}(e.doc.body,s),u.appendChild(n))});i.length>r;)u.removeChild(i[0])},!0),u.addEventListener("click",function(){for(;u.lastChild;)u.removeChild(u.lastChild);return!1},!0),document.goSearch.onsubmit=e)};
\ No newline at end of file
diff --git a/static/js/searchChange.js b/static/js/searchChange.js
new file mode 100644
index 0000000..8f776ae
--- /dev/null
+++ b/static/js/searchChange.js
@@ -0,0 +1,68 @@
+const fs = require('fs');
+const path = require("path");
+// Path for config.toml
+const configTomlPath = path.join(__dirname, "../../config.toml");
+
+async function main() {
+ const { replaceInFileSync } = await import('replace-in-file');
+ // Process arguments to determine the search mode
+ const args = process.argv.slice(2); // Remove the first two default arguments
+
+ if (args.length > 0) {
+ // Check the search mode based on the argument provided
+ switch (args[0]) {
+ case '--pagefind':
+ console.log('Pagefind search mode activated.');
+ await swapToPagefind(replaceInFileSync);
+ break;
+ case '--elasticlunr':
+ console.log('Elasticlunr search mode activated.');
+ await swapToElasticlunr(replaceInFileSync);
+ break;
+ default:
+ console.log('Unknown search mode. Please use --pagefind or --elasticlunr.');
+ }
+ } else {
+ console.log('No search mode specified. Please use --pagefind or --elasticlunr.');
+ }
+}
+
+main();
+
+async function swapToPagefind(replaceInFileSync) {
+ // Edit the config.toml file
+ replaceInFileSync({
+ files: configTomlPath,
+ from: /search_library = ['|"]\w+['|"]/g,
+ to: 'search_library = "pagefind"',
+ });
+ replaceInFileSync({
+ files: configTomlPath,
+ from: /online_indexformat = ['|"]\w+['|"]/g,
+ to: 'online_indexformat = "fuse_json"',
+ });
+ replaceInFileSync({
+ files: configTomlPath,
+ from: /index_format = ['|"]\w+['|"]/g,
+ to: 'index_format = "fuse_json"',
+ });
+}
+
+async function swapToElasticlunr(replaceInFileSync) {
+ // Edit the config.toml file
+ replaceInFileSync({
+ files: configTomlPath,
+ from: /search_library = ['|"]\w+['|"]/g,
+ to: 'search_library = "elasticlunr"',
+ });
+ replaceInFileSync({
+ files: configTomlPath,
+ from: /online_indexformat = ['|"]\w+['|"]/g,
+ to: 'online_indexformat = "elasticlunr_json"',
+ });
+ replaceInFileSync({
+ files: configTomlPath,
+ from: /index_format = ['|"]\w+['|"]/g,
+ to: 'index_format = "elasticlunr_json"',
+ });
+}
\ No newline at end of file
diff --git a/static/js/sw_load.js b/static/js/sw_load.js
new file mode 100644
index 0000000..0759338
--- /dev/null
+++ b/static/js/sw_load.js
@@ -0,0 +1,14 @@
+if ("serviceWorker" in navigator) {
+ navigator.serviceWorker
+ .register("/sw.min.js?v=3.11.0",
+ { scope: "/" })
+ .then(() => {
+ console.info("SW Loaded");
+ }, err => console.error("SW error: ", err));
+
+ navigator.serviceWorker
+ .ready
+ .then(() => {
+ console.info("SW Ready");
+ });
+}
diff --git a/static/js/sw_load.min.js b/static/js/sw_load.min.js
new file mode 100644
index 0000000..13e7454
--- /dev/null
+++ b/static/js/sw_load.min.js
@@ -0,0 +1 @@
+"serviceWorker"in navigator&&(navigator.serviceWorker.register("/sw.min.js?v=3.11.0",{scope:"/"}).then(()=>{},e=>{}),navigator.serviceWorker.ready.then(()=>{}));
\ No newline at end of file
diff --git a/static/manifest.json b/static/manifest.json
new file mode 100644
index 0000000..79937cb
--- /dev/null
+++ b/static/manifest.json
@@ -0,0 +1,19 @@
+{
+ "name": "Haskell programming language's Blog",
+ "short_name": "Haskell Blog",
+ "icons": [
+ {
+ "src": "/android-chrome-192x192.png",
+ "sizes": "192x192",
+ "type": "image/png"
+ },
+ {
+ "src": "/android-chrome-512x512.png",
+ "sizes": "512x512",
+ "type": "image/png"
+ }
+ ],
+ "theme_color": "#ffffff",
+ "background_color": "#ffffff",
+ "display": "standalone"
+}
diff --git a/static/manifest.min.json b/static/manifest.min.json
new file mode 100644
index 0000000..3248061
--- /dev/null
+++ b/static/manifest.min.json
@@ -0,0 +1 @@
+{"name":"Haskell programming language's Blog","short_name":"Haskell Blog","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
\ No newline at end of file
diff --git a/static/mstile-150x150.png b/static/mstile-150x150.png
new file mode 100644
index 0000000..23adc12
Binary files /dev/null and b/static/mstile-150x150.png differ
diff --git a/static/safari-pinned-tab.svg b/static/safari-pinned-tab.svg
new file mode 100644
index 0000000..afabe95
--- /dev/null
+++ b/static/safari-pinned-tab.svg
@@ -0,0 +1,271 @@
+
+
+
+
+Created by potrace 1.14, written by Peter Selinger 2001-2017
+
+
+
+
+
+
+
+
diff --git a/static/sw.js b/static/sw.js
new file mode 100644
index 0000000..bd899cd
--- /dev/null
+++ b/static/sw.js
@@ -0,0 +1,191 @@
+class Pwa {
+
+ constructor(self) {
+ this.scope = self;
+ const Version = new URL(location).searchParams.get("v");
+ this.CACHE_VERSION = Version;
+ //this.BASE_CACHE_FILES=['/js/theme.min.js','/js/theme_light.min.js','/abridge.css','/js/abridge.min.js','/','/404.html','/offline/','/manifest.json'];
+ this.BASE_CACHE_FILES = ['/js/theme.min.js','/js/theme_light.min.js','/abridge.css','/js/abridge.min.js','/','/404.html','/offline/','/manifest.min.json'];
+ this.host = `${self.location.protocol}//${self.location.host}`;
+ console.info(`Host: ${this.host}`);
+ this.OFFLINE_PAGE = '/offline/';
+ this.NOT_FOUND_PAGE = '/404.html';
+ this.CACHE_NAME = `content-v${this.CACHE_VERSION}`;
+ // 3600=1hour, 28800=8hours, 86400=1day, 604800=1week, 1209600=2weeks
+ this.NORM_TTL = 0;
+ this.LONG_TTL = 0;
+ // keep the ttl on these lower:
+ this.TTL_NORM = ["sw.min.js", "sw_load.min.js"];
+ // rarely change, may be a good idea to periodically refresh, incase I change these and forget to increment service worker version:
+ this.TTL_LONG = ["jpg", "jpeg", "png", "gif", "webp", "avif", "ico", "svg", "xsl", "txt"];
+ // never change, cache forever unless service worker version is incremented:
+ this.TTL_EXEMPT = ["js", "css", "otf", "eot", "ttf", "woff", "woff2", "mp4", "webm", "mp3", "ogg"];
+ // skip these extensions so they expire same time as html: st,wasm,json(search), xml(sitemap,atom,rss)
+ }
+
+ canCache(url) {
+ if (url.startsWith("http://localhost")) {
+ return false;
+ }
+ const result = url.toString().startsWith(this.host);
+ return result;
+ }
+
+ getFileExtension(url) {
+ const extension = url.split('.').reverse()[0].split('?')[0];
+ return (extension.endsWith('/')) ? '/' : extension;
+ }
+ getFileName(url) {
+ const filename = url.substring(url.lastIndexOf('/') + 1).split('?')[0];
+ return (filename.endsWith('/')) ? '/' : filename;
+ }
+
+ getTTL(url) {
+ if (typeof url === 'string') {
+ const extension = this.getFileExtension(url);
+ const filename = this.getFileName(url);
+
+ if (this.TTL_NORM.indexOf(filename) > -1) {
+ console.info(url + ' contains a TTL_NORM filename');
+ return this.NORM_TTL;
+ }
+ if (this.TTL_LONG.indexOf(extension) > -1) {
+ console.info(url + ' contains a TTL_LONG extension');
+ return this.LONG_TTL;
+ }
+ if (this.TTL_EXEMPT.indexOf(extension) > -1) {
+ console.info(url + ' contains a TTL_EXEMPT extension');
+ return null;
+ }
+ console.info(url + ' TTL_NORM');
+ return this.NORM_TTL;
+ }
+ return null;
+ }
+
+ async installServiceWorker() {
+ try {
+ await caches.open(this.CACHE_NAME).then((cache) => {
+ return cache.addAll(this.BASE_CACHE_FILES);
+ }, err => console.error(`Error with ${this.CACHE_NAME}`, err));
+ return this.scope.skipWaiting();
+ }
+ catch (err) {
+ return console.error("Error with installation: ", err);
+ }
+ }
+
+ cleanupLegacyCache() {
+
+ const currentCaches = [this.CACHE_NAME];
+
+ return new Promise(
+ (resolve, reject) => {
+ caches.keys()
+ .then((keys) => keys.filter((key) => !~currentCaches.indexOf(key)))
+ .then((legacy) => {
+ if (legacy.length) {
+ Promise.all(legacy.map((legacyKey) => caches.delete(legacyKey))
+ ).then(() => resolve()).catch((err) => {
+ console.error("Error in legacy cleanup: ", err);
+ reject(err);
+ });
+ } else {
+ resolve();
+ }
+ }).catch((err) => {
+ console.error("Error in legacy cleanup: ", err);
+ reject(err);
+ });
+ });
+ }
+
+ async preCacheUrl(url) {
+ const cache = await caches.open(this.CACHE_NAME);
+ const response = await cache.match(url);
+ if (!response) {
+ return fetch(url).then(resp => cache.put(url, resp.clone()));
+ }
+ return null;
+ }
+
+ register() {
+ this.scope.addEventListener('install', event => {
+ event.waitUntil(
+ Promise.all([
+ this.installServiceWorker(),
+ this.scope.skipWaiting(),
+ ]));
+ console.info('SW Installed');
+ });
+
+ this.scope.addEventListener('activate', event => {
+ event.waitUntil(Promise.all(
+ [this.cleanupLegacyCache(),
+ this.scope.clients.claim(),
+ this.scope.skipWaiting()]).catch((err) => {
+ console.error("Activation error: ", err);
+ event.skipWaiting();
+ }));
+ });
+
+ this.scope.addEventListener('fetch', event => {
+ event.respondWith(
+ caches.open(this.CACHE_NAME).then(async cache => {
+ // check if this is NOT a resource we allow cacheing (some other domain), if so fetch it instead of cache.
+ if (!this.canCache(event.request.url)) {
+ return fetch(event.request);
+ }
+ // check the cache for the requested resource
+ const response = await cache.match(event.request);
+ if (response) {
+ const headers = response.headers.entries();
+ let date = null;
+ for (let pair of headers) {
+ if (pair[0] === 'date') {
+ date = new Date(pair[1]);
+ break;
+ }
+ }
+ // date is not working, so ignore TTL and just serve the cached resource.
+ if (!date) {
+ return response;
+ }
+ const age = parseInt(((new Date().getTime() - date.getTime()) / 1000).toString());
+ const ttl = this.getTTL(event.request.url);
+ if (ttl === null || (ttl && age < ttl)) {
+ // return the resource if it is not beyond the TTL
+ return response;
+ }
+ }
+ // if we made it here then we either did not have the cache, or the TTL was expired.
+ return fetch(event.request.clone()).then(resp => {
+ if (resp.status < 400) {
+ if (this.canCache(event.request.url)) {
+ cache.put(event.request, resp.clone());
+ }
+ return resp;
+ }
+ else {
+ return cache.match(this.NOT_FOUND_PAGE);
+ }
+ }).catch(err => {
+ // if we made it here then we were unable to fetch the resource.
+ // maybe we were only fetching because of expired TTL, so use the cache regardless of TTL:
+ if (typeof event.request.url === 'string') {
+ console.info("url: "+event.request.url)
+ }
+ if (response) {
+ return response;
+ }
+ // if we made it here then we were unable to fetch the resource and do not have it cached.
+ console.error(`Error fetching ${event.request.url} resulted in offline`, err);
+ return cache.match(this.OFFLINE_PAGE);
+ })
+ }));
+ });
+ }
+}
+
+const pwa = new Pwa(self);
+pwa.register();
diff --git a/static/sw.min.js b/static/sw.min.js
new file mode 100644
index 0000000..8812d76
--- /dev/null
+++ b/static/sw.min.js
@@ -0,0 +1 @@
+class Pwa{constructor(t){this.scope=t;var e=new URL(location).searchParams.get("v");this.CACHE_VERSION=e,this.BASE_CACHE_FILES=["/js/theme.min.js","/js/theme_light.min.js","/abridge.css","/js/abridge.min.js","/","/404.html","/offline/","/manifest.min.json"],this.host=t.location.protocol+"//"+t.location.host,this.OFFLINE_PAGE="/offline/",this.NOT_FOUND_PAGE="/404.html",this.CACHE_NAME="content-v"+this.CACHE_VERSION,this.NORM_TTL=0,this.LONG_TTL=0,this.TTL_NORM=["sw.min.js","sw_load.min.js"],this.TTL_LONG=["jpg","jpeg","png","gif","webp","avif","ico","svg","xsl","txt"],this.TTL_EXEMPT=["js","css","otf","eot","ttf","woff","woff2","mp4","webm","mp3","ogg"]}canCache(t){return!t.startsWith("http://localhost")&&t.toString().startsWith(this.host)}getFileExtension(t){t=t.split(".").reverse()[0].split("?")[0];return t.endsWith("/")?"/":t}getFileName(t){t=t.substring(1+t.lastIndexOf("/")).split("?")[0];return t.endsWith("/")?"/":t}getTTL(t){var e;return"string"==typeof t?(e=this.getFileExtension(t),t=this.getFileName(t),~this.TTL_NORM.indexOf(t)?this.NORM_TTL:~this.TTL_LONG.indexOf(e)?this.LONG_TTL:~this.TTL_EXEMPT.indexOf(e)?null:this.NORM_TTL):null}async installServiceWorker(){try{return await caches.open(this.CACHE_NAME).then(t=>t.addAll(this.BASE_CACHE_FILES),t=>{}),this.scope.skipWaiting()}catch(t){}}cleanupLegacyCache(){let i=[this.CACHE_NAME];return new Promise((e,s)=>{caches.keys().then(t=>t.filter(t=>!~i.indexOf(t))).then(t=>{t.length?Promise.all(t.map(t=>caches.delete(t))).then(()=>e()).catch(t=>{s(t)}):e()}).catch(t=>{s(t)})})}async preCacheUrl(e){let s=await caches.open(this.CACHE_NAME);return await s.match(e)?null:fetch(e).then(t=>s.put(e,t.clone()))}register(){this.scope.addEventListener("install",t=>{t.waitUntil(Promise.all([this.installServiceWorker(),this.scope.skipWaiting()]))}),this.scope.addEventListener("activate",e=>{e.waitUntil(Promise.all([this.cleanupLegacyCache(),this.scope.clients.claim(),this.scope.skipWaiting()]).catch(t=>{e.skipWaiting()}))}),this.scope.addEventListener("fetch",h=>{h.respondWith(caches.open(this.CACHE_NAME).then(async e=>{if(!this.canCache(h.request.url))return fetch(h.request);let s=await e.match(h.request);if(s){var i;let t=null;for(i of s.headers.entries())if("date"===i[0]){t=new Date(i[1]);break}if(!t)return s;var n=parseInt(""+((new Date).getTime()-t.getTime())/1e3),a=this.getTTL(h.request.url);if(null===a||a&&nt.status<400?(this.canCache(h.request.url)&&e.put(h.request,t.clone()),t):e.match(this.NOT_FOUND_PAGE)).catch(t=>(h.request.url,s||e.match(this.OFFLINE_PAGE)))}))})}}let pwa=new Pwa(self);pwa.register();
\ No newline at end of file
diff --git a/templates/authors/list.html b/templates/authors/list.html
new file mode 100644
index 0000000..447bec4
--- /dev/null
+++ b/templates/authors/list.html
@@ -0,0 +1,33 @@
+{% extends "base.html" %}
+{%- set uglyurls = config.extra.uglyurls | default(value=false) -%}
+{%- if config.extra.offline %}{% set uglyurls = true %}{% endif %}
+
+{%- block seo %}
+ {{- super() }}
+ {%- set title = "Authors" %}
+
+ {%- if config.title %}
+ {%- set title_addition = title_separator ~ config.title %}
+ {%- else %}
+ {%- set title_addition = "" %}
+ {%- endif %}
+
+ {%- set description = config.description %}
+
+ {{- macros_seo::seo(config=config, title=title, title_addition=title_addition, description=description, is_home=true) }}
+{%- endblock seo %}
+
+{%- block content %}
+
+
+
{{ terms | length }} {{ macros::translate(key="Authors", default="Authors", i18n=i18n) }}
+
{% for term in terms %} #{{ term.name }} {{ term.pages | length }} {% endfor %}
+
+ {%- for term in terms %}
+
+ {%- for page in term.pages %}
+
{{ page.title }} - {{ page.date | date(format="%F") }}
+ {%- endfor %}
+ {%- endfor %}
+
+{%- endblock content %}
diff --git a/templates/authors/single.html b/templates/authors/single.html
new file mode 100644
index 0000000..4d6f6e1
--- /dev/null
+++ b/templates/authors/single.html
@@ -0,0 +1,30 @@
+{% extends "base.html" %}
+{%- set uglyurls = config.extra.uglyurls | default(value=false) -%}
+{%- if config.extra.offline %}{% set uglyurls = true %}{% endif %}
+
+{%- block seo %}
+ {{- super() }}
+ {%- set title = term.name %}
+
+ {%- if config.title %}
+ {%- set title_addition = title_separator ~ config.title %}
+ {%- else %}
+ {%- set title_addition = "" %}
+ {%- endif %}
+
+ {%- set description = config.description %}
+
+ {{- macros_seo::seo(config=config, title=title, title_addition=title_addition, description=description, is_home=true) }}
+{%- endblock seo %}
+
+{%- block content %}
+
+
{{ term.name }}
+ {%- for year, posts in term.pages | group_by(attribute="year") %}
+
{{ year }}
+ {%- for page in posts %}
+
{{ page.title }} - {{ page.date | date(format="%F") }}
+ {%- endfor %}
+ {%- endfor %}
+
+{%- endblock content %}