From b24c17b8af0a711cffbb1907a6d614d91437a453 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9cate=20Kleidukos?= Date: Fri, 2 Aug 2024 23:23:45 +0200 Subject: [PATCH] Prepare the blog --- .github/workflows/deploy.yml | 58 +++ .gitignore | 14 + .gitmodules | 3 + CNAME | 1 + README.md | 66 +++ config.toml | 312 +++++++++++++ content/_index.md | 5 + content/archive/_index.md | 6 + .../flow-of-documentation.png | Bin 0 -> 39152 bytes content/documentation-best-practices/index.md | 160 +++++++ content/pages/_index.md | 3 + content/pages/about.md | 16 + content/pages/privacy.md | 15 + content/static/stork_toml.md | 5 + content/static/tinysearch_json.md | 5 + package.json | 20 + package_abridge.js | 419 ++++++++++++++++++ sass/_extra.scss | 26 ++ sass/abridge.scss | 181 ++++++++ sass/fonts/_Ralewway.scss | 16 + sass/fonts/_SourceSans.scss | 8 + sass/fonts/_UbuntuMono.scss | 8 + static/CNAME | 1 + static/android-chrome-192x192.png | Bin 0 -> 2832 bytes static/android-chrome-512x512.png | Bin 0 -> 22233 bytes static/apple-touch-icon.png | Bin 0 -> 2605 bytes static/browserconfig.xml | 9 + static/favicon-16x16.png | Bin 0 -> 843 bytes static/favicon-32x32.png | Bin 0 -> 1105 bytes static/favicon-48x48.png | Bin 0 -> 1631 bytes static/favicon.ico | Bin 0 -> 15086 bytes static/favicon.svg | 6 + static/fonts/raleway-v28-latin-700.woff2 | Bin 0 -> 21440 bytes static/fonts/raleway-v28-latin-900.woff2 | Bin 0 -> 20696 bytes .../source-sans-3-v9-latin-regular.woff2 | Bin 0 -> 15008 bytes .../fonts/ubuntu-mono-v15-latin-regular.woff2 | Bin 0 -> 27384 bytes static/images/haskell.svg | 6 + static/js/abridge.min.js | 1 + static/js/abridge_nopwa.min.js | 1 + static/js/searchChange.js | 68 +++ static/js/sw_load.js | 14 + static/js/sw_load.min.js | 1 + static/manifest.json | 19 + static/manifest.min.json | 1 + static/mstile-150x150.png | Bin 0 -> 1608 bytes static/safari-pinned-tab.svg | 271 +++++++++++ static/site.webmanifest | 19 + static/sw.js | 191 ++++++++ static/sw.min.js | 1 + templates/authors/list.html | 33 ++ templates/authors/single.html | 30 ++ themes/abridge | 1 + 52 files changed, 2020 insertions(+) create mode 100644 .github/workflows/deploy.yml create mode 100644 .gitignore create mode 100644 .gitmodules create mode 100644 CNAME create mode 100644 README.md create mode 100644 config.toml create mode 100644 content/_index.md create mode 100644 content/archive/_index.md create mode 100644 content/documentation-best-practices/flow-of-documentation.png create mode 100644 content/documentation-best-practices/index.md create mode 100644 content/pages/_index.md create mode 100644 content/pages/about.md create mode 100644 content/pages/privacy.md create mode 100644 content/static/stork_toml.md create mode 100644 content/static/tinysearch_json.md create mode 100644 package.json create mode 100644 package_abridge.js create mode 100644 sass/_extra.scss create mode 100644 sass/abridge.scss create mode 100644 sass/fonts/_Ralewway.scss create mode 100644 sass/fonts/_SourceSans.scss create mode 100644 sass/fonts/_UbuntuMono.scss create mode 100644 static/CNAME create mode 100644 static/android-chrome-192x192.png create mode 100644 static/android-chrome-512x512.png create mode 100644 static/apple-touch-icon.png create mode 100644 static/browserconfig.xml create mode 100644 static/favicon-16x16.png create mode 100644 static/favicon-32x32.png create mode 100644 static/favicon-48x48.png create mode 100644 static/favicon.ico create mode 100644 static/favicon.svg create mode 100644 static/fonts/raleway-v28-latin-700.woff2 create mode 100644 static/fonts/raleway-v28-latin-900.woff2 create mode 100644 static/fonts/source-sans-3-v9-latin-regular.woff2 create mode 100644 static/fonts/ubuntu-mono-v15-latin-regular.woff2 create mode 100644 static/images/haskell.svg create mode 100644 static/js/abridge.min.js create mode 100644 static/js/abridge_nopwa.min.js create mode 100644 static/js/searchChange.js create mode 100644 static/js/sw_load.js create mode 100644 static/js/sw_load.min.js create mode 100644 static/manifest.json create mode 100644 static/manifest.min.json create mode 100644 static/mstile-150x150.png create mode 100644 static/safari-pinned-tab.svg create mode 100644 static/site.webmanifest create mode 100644 static/sw.js create mode 100644 static/sw.min.js create mode 100644 templates/authors/list.html create mode 100644 templates/authors/single.html create mode 160000 themes/abridge diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..db4fa24 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,58 @@ +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 + with: + submodules: "recursive" + - uses: actions/setup-node@v4 + with: + node-version: 20 + - run: | + sudo apt-get update && sudo apt-get install -y wget git + - run: | + wget -q -O - \ + "https://github.com/getzola/zola/releases/download/v0.19.1/zola-v0.19.1-x86_64-unknown-linux-gnu.tar.gz" \ + | sudo tar xzf - -C /usr/local/bin + + - run: npm install + - run: npm run abridge + - name: 'Build only' + uses: shalzz/zola-deploy-action@v0.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 + with: + submodules: "recursive" + - uses: actions/setup-node@v4 + with: + node-version: 20 + - run: | + sudo apt-get update && sudo apt-get install -y wget git + - run: | + wget -q -O - \ + "https://github.com/getzola/zola/releases/download/v0.19.1/zola-v0.19.1-x86_64-unknown-linux-gnu.tar.gz" \ + | sudo tar xzf - -C /usr/local/bin + - run: npm install + - run: npm run abridge + - name: 'Build and deploy' + uses: shalzz/zola-deploy-action@v0.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..39123d9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +.env +public +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 diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..93e4bd7 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "themes/abridge"] + path = themes/abridge + url = https://github.com/jieiku/abridge.git 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..73c1543 --- /dev/null +++ b/README.md @@ -0,0 +1,66 @@ +# 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 contain +their title in a "slug" format (alphanumeric characters, words separated by a hyphen, all lowercase): + +``` +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 + +#### Images and other files + +If you want to add images to your blog post, create a folder named after the blog post, and write the content of the post to an `index.md` +file within it. Put your associated files in the same directory. + +For instance: + +``` +documentation-best-practices +├── flow-of-documentation.png +└── index.md +``` + +### 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..7b738a0 --- /dev/null +++ b/config.toml @@ -0,0 +1,312 @@ +# 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 = "tags"}, + {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/_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/documentation-best-practices/flow-of-documentation.png b/content/documentation-best-practices/flow-of-documentation.png new file mode 100644 index 0000000000000000000000000000000000000000..46b28c1516a106c2e4a01df1ca1ee60620023d56 GIT binary patch literal 39152 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 bU;JleyR7oD)0Hcm0SG)@{an^LB{Ts5e%)%$ literal 0 HcmV?d00001 diff --git a/content/documentation-best-practices/index.md b/content/documentation-best-practices/index.md new file mode 100644 index 0000000..32c3716 --- /dev/null +++ b/content/documentation-best-practices/index.md @@ -0,0 +1,160 @@ ++++ +title = "Documentation Best Practices" +date = 2024-08-19 +[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. + +#### Stable documentation + +* 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. + +#### Volatile documentation +* 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 + + +#### Documentation cascade + +Here is a simplified model of the documentation cascade for a typical Haskell project, from the most volatile to the most stable +sources: + +flow of documentation + +
Code for this diagram + +```mermaid +flowchart TD + A[Docs of your project] + B[Architecture document] + C[Official specs for your domain] + D["Docs of a core library (base, text, containers, etc)"] + E[GHC Manual] + F[Official specs for what the core libraries provide] + G[Papers] + + A --> B + A --> D + A --> C + + D --> E + D --> F + D --> G +``` +
+ +> The Haddocks of your library or a third-party library have a dependency on the official specs for the domain, on an architecture document, +> and on haddocks from the core libraries (`base`, `text`, `containers`, etc.). +> The haddocks of these core libraries depend on the GHC manual, official specs for their own domain, and papers. + +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 (_stable_) + +### Understand for whom you write + +It is of utmost importance that documentation answers the needs of the users, and for that we must understand these needs. +Users need specific kinds of documentation depending on the situation they are in. + +A common framework used for the classification of documentation is the Diátaxis Framework. It defines four types of documentation +where each are a combination of _Acquisition_ or _Application_, and _Action_ or _Cognition_. + +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: 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_) + +Here is the quadrant: + + + +> -- Diátaxis Framework, by Daniele Procida, https://diataxis.fr + + +Diátaxis maps out the entire life cycle of one’s interaction with a system. + +But 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 focuses on a particular combination of user needs and situations. + +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/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
,
,