diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index e8350138..981225f6 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -44,6 +44,11 @@ # instructions. These carry legal and financial statements. /content/pybcn_association/ @pybcn/web +# The code of conduct itself. The page under content/pybcn_association/ +# renders this file through the repo-markdown shortcode, so the legal text +# lives at the root and not in content/, and the rule above does not reach it. +/CODE_OF_CONDUCT.md @pybcn/web + # The custom domain of the GitHub Pages site. A change to this file points # pybcn.org somewhere else. /CNAME @pybcn/web diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..ff87fee2 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,35 @@ +# Code of Conduct + +PyBCN is dedicated to providing a respectful, harassment-free community for +everyone. We do not tolerate harassment or bullying of any community member in +any form. This does not only extend to members to local PyBCN communities, +but to anyone who chooses to become involved in the larger PyBCN community of +users, developers and integrators through events or interactions. + +Harassment includes offensive verbal/electronic comments related to personal +characteristics or choices, sexual images or comments in public or online +spaces, deliberate intimidation, bullying, stalking, following, harassing +photography or recording, sustained disruption of talks, IRC chats, electronic +meetings, physical meetings or other events, inappropriate physical contact, or +unwelcome sexual attention. Participants asked to stop any harassing or +bullying behavior are expected to comply immediately. + +If a participant engages in harassing behavior, representatives of the +community may take reasonable action they deem appropriate, including warning +the offender, expulsion from any PyBCN event, or expulsion from mailing +lists, IRC chats, discussion boards and other electronic communications +channels to resolve the issue. This may include expulsion from PyBCN Meetup +group membership. + +If you are being harassed, notice that someone else is being harassed, or have +any other concerns, please act to intercede or ask for help from any member of +the PyBCN community, IRC chat admins, website admins, or +organizers/representatives of any physical events put on under the auspices of +PyBCN. + +This Code of Conduct has been adapted from the +[PyLadies](https://www.pyladies.com/CodeOfConduct/) one, which is licensed +under a +[Creative Commons Attribution-Share Alike 3.0 Unported license](https://creativecommons.org/licenses/by-sa/3.0/), +and is licensed under a +[Creative Commons Attribution-ShareAlike 4.0 International license](https://creativecommons.org/licenses/by-sa/4.0/). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..152a533f --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,127 @@ +# Contributing to the PyBCN site + +This repository holds the source of [pybcn.org](https://pybcn.org/), the site +of the Python Barcelona association, built with [Hugo](https://gohugo.io). A +change goes through a pull request against the `edition` branch, and this +guide says how. The [README](README.md) documents the site itself: the content +fields, the layouts, and what each check looks for. + +## Code of conduct + +The PyBCN [Code of Conduct](CODE_OF_CONDUCT.md) applies to every contribution: +a pull request, an issue, a review, and the discussion around them. It is the +same text the site publishes at +[pybcn.org/pybcn_association/coc/](https://pybcn.org/pybcn_association/coc/), +which renders that file. + +## Licence + +The code of the site is under the [MIT License](LICENSE) and the content is +under [CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/). +[LICENSING.md](LICENSING.md) says which paths each one covers, and which paths +are not licensed at all: the photographs of people, the person pages, the +sponsor logos, the association's own logos, the third-party libraries, and the +archived sites. + +A pull request is a contribution under those terms. A change to a template, a +script, or a stylesheet is offered under the MIT License, and a change to the +text of a page is offered under CC BY-SA 4.0. Your own page under +`content/people/` and your photo are different: you keep every right in them, +and you give PyBCN permission to publish them on this site, which you can +withdraw. + +## Which branch + +Open the pull request against `edition`. It is the default branch and it holds +the source. `master` holds the built site: the `github-pages` workflow writes +it on every merge to `edition` and replaces its history each time, so nothing +is edited there, and a change made on `master` is lost on the next deploy. + +## Add content without writing code + +Most changes to this site are content: a person, a sponsor, an event. Each one +is a Markdown file with a front matter, and the README documents the fields. +Edit the file on GitHub, or clone the repository and follow the steps below. + +- **A person** (an organizer, a speaker, a mentor): copy a file under + `content/people/` and edit it. The front matter carries `id`, which has to + match the file name, `name`, `photo`, the role at PyBCN, and the links to + the person's own profiles. The bio goes in the body. Leave out any field the + person does not have. The photo goes under + `themes/pybcn_theme/assets/images/people/` and has to be square, see + [Person photos](README.md#person-photos). +- **A sponsor**: run `bin/hugo new sponsors/my-sponsor.md` and fill the four + fields listed in [How to add a new sponsor](README.md#how-to-add-a-new-sponsor). + The logo goes under `themes/pybcn_theme/assets/images/sponsors/`. Then list + the sponsor on the sponsors page, or on the event, that shows it. +- **An event**: copy the previous edition under `content/events/`, for example + `content/events/pyday_bcn/pyday_bcn_2025.md`, and edit it. The sections, the + agenda and the people grids are described in + [Event page](README.md#event-page). An event lists its people and sponsors by + their `id`, so add those files first. + +`bin/check-content` reads every person, sponsor and event file. The pull +request fails when a field is empty, an `id` does not match the file name, a +declared photo is missing, or an event names a person or a sponsor that does +not exist. Run it locally, step 5 below, to see what it would say. + +To report a problem with the site, open an issue and give the URL of the page. + +## Make the change + +Nothing is pushed to `edition` directly. + +1. Clone the repository. `git clone --single-branch --branch edition` is enough + and skips the built site, which lives on its own branch. +2. Run `bin/install`. It downloads the pinned Hugo binary into `bin/hugo` and + verifies its checksum. You do not need Python, Go, or npm for this step. +3. Run `bin/serve` to see the site at `http://localhost:1313` while you work. +4. Make the change. +5. Run the three checks locally, so you find what the pull request would find: + + ``` + pip install pyyaml pillow + bin/check-content + bin/check-html-safety + bin/hugo --minify -D -d public && bin/check-rendered + ``` + + Pillow is only needed for the black and white photo check. Without it the + rest still runs and the check says it was skipped, so a missing Pillow + never fails a build for the wrong reason. + +6. Open the pull request against `edition`. + +## What the pull request goes through + +The `pr-checks` workflow runs the same checks on your branch, plus a build and +a link check. **All of them have to pass**, and one approving review is needed +before the pull request can be merged. + +What each check is for: + +| Check | Fails when | +|---|---| +| Build the site | Hugo cannot build, or emits a warning | +| `bin/check-content` | Front matter does not parse, a person `id` does not match its filename, an id is duplicated, a declared photo is missing, an event references a person or sponsor that does not exist, a social URL has the wrong shape, or a field is empty. It warns, without failing, about a photo that is too small or has no colour | +| `bin/check-html-safety` | Content carries raw HTML that turns a content change into script execution, a redirect, a credential prompt, or a page overlay. See [Content safety check](README.md#content-safety-check) | +| `bin/check-rendered` | The built pages carry that same markup, which catches a template that produces it even when no content file does, or they link to anything over `http`. See [Rendered page check](README.md#rendered-page-check) | +| Check the links | Reported, never blocking, because external sites rate-limit | + +A merge to `edition` deploys the site. The `github-pages` workflow builds it and +publishes the result, so a change is live within a few minutes of the merge. + +## Review by the web team + +`.github/CODEOWNERS` lists the paths where a change reaches every page of the +site, or reaches money, or reaches the deploy: `.github/`, `bin/`, +`config.toml`, `layouts/`, `static/`, `themes/`, `CNAME`, the association +pages under `content/pybcn_association/`, `CODE_OF_CONDUCT.md`, and the +sponsor and event files under `content/sponsors/` and `content/events/`. + +A pull request that touches one of those paths requests a review from +`@pybcn/web` automatically, and the branch protection on `edition` requires +it, so the pull request waits until a member of that team approves it. A +person's file under `content/people/` is not on the list on purpose: a speaker +edits their own page and needs no organizer for that, only the one approving +review every pull request needs. diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..559e3176 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2013-2026 Associació Python Barcelona (PyBCN) and contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/LICENSING.md b/LICENSING.md new file mode 100644 index 00000000..2739d648 --- /dev/null +++ b/LICENSING.md @@ -0,0 +1,171 @@ +# Licensing + +This repository holds the source of [pybcn.org](https://pybcn.org/), the site +of the Associació Python Barcelona (PyBCN). It holds three kinds of thing, and +one licence does not fit all of them: the code of the site, the text of the +site, and material that the association publishes but does not own. This file +says which licence covers which path, and which paths no licence covers at all. + +GitHub reads the licence of a repository from the `LICENSE` file alone, and a +second licence in that file stops the detection. So `LICENSE` holds the MIT +License text and nothing else, and everything else about licensing is here. + +## Code: MIT License + +The MIT License in [`LICENSE`](LICENSE) covers the code that builds and runs +the site: + +- `layouts/`: the Hugo templates and shortcodes of the site. +- `themes/pybcn_theme/`: the theme. Its templates and partials (`layouts/`), + its stylesheets (`assets/scss/`), the scripts written for this site + (`assets/js/`, except `cookieconsent.min.js`, which is a third-party + library, see below), its `archetypes/`, `theme.toml`, and `README.md`. The + images and the vendored libraries under the theme are listed under + [Not licensed](#not-licensed). +- `bin/`: the build, check, and maintenance scripts. +- `.github/`: the workflows, the code owners, and the Dependabot configuration. +- `config.toml`, `archetypes/`, and the other configuration files at the root + (`.gitignore`, `.hugo-version`, `CNAME`). + +The copyright line in `LICENSE` names the association and the contributors, +and runs from 2013, the year of the first commit. The theme began as a copy of +the `hugo-theme-bootstrap4-blog` theme, which is also under the MIT License; +its notice stays in `themes/pybcn_theme/LICENSE.txt` and covers what remains +of it. + +## Content: CC BY-SA 4.0 + +The text of the site is licensed under the Creative Commons +Attribution-ShareAlike 4.0 International licence (CC BY-SA 4.0): + +- `content/`, except `content/people/`, see [Person pages](#person-pages). +- `README.md` and `CONTRIBUTING.md`. +- `static/humans.txt`. +- `CODE_OF_CONDUCT.md`, an adaptation of the PyLadies code of conduct, see + [Code of conduct](#code-of-conduct) below. + +Deed: + +Legal code: + +Attribute the text to "Python Barcelona (PyBCN), https://pybcn.org/". The +licence requires attribution, a link to the licence, a note of any change, and +the same licence on any adaptation. + +The full legal code is not copied here. Creative Commons keeps the canonical +text at the URL above and says a link to it is enough, and a copy in this +repository would be a second licence text at the root, which is what confuses +the detection described above. + +### Code of conduct + +`CODE_OF_CONDUCT.md` was adapted from the +[PyLadies code of conduct](https://www.pyladies.com/CodeOfConduct/), which is +licensed under +[CC BY-SA 3.0 Unported](https://creativecommons.org/licenses/by-sa/3.0/). The +adaptation is licensed under CC BY-SA 4.0, like the rest of the content, so +the whole repository is on one version of the licence family. + +The licence of the original permits that. Section 4(b) of the +[CC BY-SA 3.0 legal code](https://creativecommons.org/licenses/by-sa/3.0/legalcode) +says that an Adaptation may be distributed under "(ii) a later version of +this License with the same License Elements as this License", and section 1 +names those elements: "Attribution, ShareAlike". CC BY-SA 4.0 is a later +version of that licence, and its License Elements are Attribution and +ShareAlike. The same section 4(b) requires the notices that refer to the +licence of the original to stay intact, so the file keeps its attribution to +PyLadies and the mention of CC BY-SA 3.0, and adds the link to CC BY-SA 4.0. + +ShareAlike is also why the file cannot go under the MIT License: an +adaptation has to stay in the CC BY-SA family, at the same version or a later +one. + +## Not licensed + +The paths below are published on the site but are not covered by either +licence, because the rights in them are not the association's to give. A +licence that promised more than that would tell people they may do things they +may not. + +### Photographs of people + +`themes/pybcn_theme/assets/images/people/` holds one photograph per person on +the site, and `themes/pybcn_theme/assets/images/photos/` holds photographs +taken at PyBCN events. + +The people on this site sent their own photograph for publication on it. +Anyone who did not want one did not send one, and the site shows a silhouette +instead. The association publishes those photographs with the permission of +the people in them, given for that purpose. That permission is not a licence +for anyone else to reuse a photograph, for two reasons: + +- The copyright in a photograph belongs to whoever took it. Giving PyBCN a + copy to publish does not transfer that copyright, so PyBCN has nothing to + sub-license. +- Consent to appear on this site is narrower than a CC BY-SA grant, which + would let a stranger reuse and adapt the image, commercially, for ever. + Under Spanish law (Ley Orgánica 1/1982) the right to one's own image is a + personality right and the consent is revocable, so it cannot become a + perpetual and irrevocable licence, which is what CC BY-SA is. + +So the photographs are not licensed. They are published with the permission +of the people in them, and that permission can be withdrawn. The event +photographs are the same case: the photographer keeps the copyright, and the +people in them keep their image rights. + +### Person pages + +`content/people/` holds one page per person. Each person wrote their own text +and sent it for publication here, so the same reasoning applies: the +association publishes the text, it does not own it, and it cannot license it. + +There is a second reason, specific to these pages. A person can ask the +association to erase their page, and the GDPR (Article 17.2) then obliges the +association to take reasonable steps to tell anyone else holding a copy that +erasure was requested. A licence that invites lawful copies of a person's +entry multiplies the copies the association would have to chase, and works +against the erasure it has to be able to deliver. + +### Sponsor logos + +`themes/pybcn_theme/assets/images/sponsors/` holds the logos of the sponsors. +Each logo is the trade mark of its owner, shown here to acknowledge its +support of PyBCN, with that owner's permission. A trade mark is not the +association's to license, and CC BY-SA 4.0 does not license trade marks in any +case (section 2(b)(2) of the legal code). + +### The association's own marks + +The PyBCN logo and its variants (`themes/pybcn_theme/assets/images/logo.png`, +`themes/pybcn_theme/assets/images/favicon.png`, `static/favicon.ico`, +`static/favicon.png`, and the files under `static/images/backgrounds/`) +identify the association. They are not covered by the content licence: use +them to refer to PyBCN, not to suggest that PyBCN endorses something it has +not seen. The PyLadies name and logo in that same folder are not the +association's. + +### Third-party libraries + +Each of these keeps its own licence, and the notice inside each file stays +with it: + +- `themes/pybcn_theme/assets/vendor/`: jQuery 3.3.1 (MIT), jQuery Easing + 1.4.1 (BSD), Bootstrap 4.0.0 (MIT), and Font Awesome Free 5.8.2 (icons + CC BY 4.0, fonts SIL OFL 1.1, code MIT). +- `themes/pybcn_theme/static/vendor/font-awesome/webfonts/`: the Font Awesome + fonts (SIL OFL 1.1). +- `themes/pybcn_theme/assets/css/bootstrap.min.css`: Bootstrap 4.0.0 (MIT). +- `themes/pybcn_theme/assets/css/cookieconsent.min.css` and + `themes/pybcn_theme/assets/js/cookieconsent.min.js`: the Cookie Consent + library by Osano (MIT). +- `themes/pybcn_theme/assets/images/anon_member.png`: the Font Awesome `user` + icon as a PNG (CC BY 4.0). + +### Archived sites + +`static/archives/` holds copies of two sites as they were published, made +with `bin/archive`: `pybcn.org/`, the previous site of the association, and +`hacktoberfestbarcelona.com/`, the site of Hacktoberfest BCN 2018. They are +kept as a record and are not maintained. Each carries its own photographs, +logos, fonts, and libraries, so nothing under `static/archives/` is licensed by +this repository. diff --git a/README.md b/README.md index 24a88c78..d606f222 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,8 @@ You can bring a hugo hot reload server using the `bin/serve` script. This will b ### Publishing process -During current implementation phase, the way to publish is to push new commits to the `edition` branch. However this will change in the future, to provide some review step in the publishing process. +A change goes through a pull request against `edition`, and a merge deploys +the site. [CONTRIBUTING.md](CONTRIBUTING.md) has the steps. ### How to add a new sponsor @@ -84,43 +85,12 @@ Once the site was archived, you can create a new link in the navigational menu u ### Collaborate -A change to this site goes through a pull request. Nothing is pushed to -`edition` directly. - -1. Clone the repository. `git clone --single-branch --branch edition` is enough - and skips the built site, which lives on its own branch. -2. Run `bin/install`. It downloads the pinned Hugo binary into `bin/hugo` and - verifies its checksum. You do not need Python, Go, or npm for this step. -3. Run `bin/serve` to see the site at `http://localhost:1313` while you work. -4. Make the change. -5. Run the three checks locally, so you find what the pull request would find: - - ``` - pip install pyyaml - bin/check-content - bin/check-html-safety - bin/hugo --minify -D -d public && bin/check-rendered - ``` - -6. Open the pull request against `edition`. - -The `pr-checks` workflow then runs the same checks on your branch, plus a build -and a link check. **All of them have to pass**, and one approving review is -needed before the pull request can be merged. The paths listed in -`.github/CODEOWNERS` also request a review from the web team automatically. - -What each check is for: - -| Check | Fails when | -|---|---| -| Build the site | Hugo cannot build, or emits a warning | -| `bin/check-content` | Front matter does not parse, a person `id` does not match its filename, an id is duplicated, a declared photo is missing, or an event references a person or sponsor that does not exist | -| `bin/check-html-safety` | Content carries raw HTML that turns a content change into script execution, a redirect, a credential prompt, or a page overlay | -| `bin/check-rendered` | The built pages carry that same markup, which catches a template that produces it even when no content file does | -| Check the links | Reported, never blocking, because external sites rate-limit | - -A merge to `edition` deploys the site. The `github-pages` workflow builds it and -publishes the result, so a change is live within a few minutes of the merge. +The steps to make a change and open a pull request, what the pull request +checks, and which paths wait for a review from the web team are in +[CONTRIBUTING.md](CONTRIBUTING.md), which GitHub links from the pull request +form. It lives there and not here, so there is one description of the process +and not two that drift apart. The two sections below are the detail of the +checks it names. ### Content safety check @@ -446,4 +416,18 @@ people_sections: people: - mireia - josep -``` \ No newline at end of file +``` + +## Licence + +The code of the site (the templates, the stylesheets, the scripts, the +workflows, and the configuration) is under the [MIT License](LICENSE). The +content (the text under `content/`, the code of conduct, this README, and the +contributing guide) is under +[CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/). The +photographs of people, the person pages, the sponsor logos, the association's +own logos, the third-party libraries, and the archived sites are not licensed, +each for a reason of its own. [LICENSING.md](LICENSING.md) lists every path +and says why. The [code of conduct](CODE_OF_CONDUCT.md) was adapted from the +PyLadies one, which is under CC BY-SA 3.0; that licence allows the adaptation +to move to 4.0, and LICENSING.md says how. diff --git a/bin/check-html-safety b/bin/check-html-safety index 10dbeaab..f9468046 100755 --- a/bin/check-html-safety +++ b/bin/check-html-safety @@ -441,7 +441,11 @@ def main(): args = parser.parse_args() root = args.root or os.path.dirname(os.path.dirname(os.path.abspath(__file__))) - targets = args.paths or [os.path.join(root, "content")] + # CODE_OF_CONDUCT.md sits at the root, where GitHub reads it, and the + # repo-markdown shortcode renders it on /pybcn_association/coc/. It is + # content that Hugo renders, so it is scanned like a file under content/. + targets = args.paths or [os.path.join(root, "content"), + os.path.join(root, "CODE_OF_CONDUCT.md")] files, skipped, unknown = [], [], [] diff --git a/content/pybcn_association/coc.md b/content/pybcn_association/coc.md index 45a513c0..5c0387d2 100644 --- a/content/pybcn_association/coc.md +++ b/content/pybcn_association/coc.md @@ -12,33 +12,4 @@ layout: single heroBackground: /images/photos/people-pyday-2016.jpg --- -PyBCN is dedicated to providing a respectful, harassment-free community for -everyone. We do not tolerate harassment or bullying of any community member in -any form. This does not only extend to members to local PyBCN communities, -but to anyone who chooses to become involved in the larger PyBCN community of -users, developers and integrators through events or interactions. - -Harassment includes offensive verbal/electronic comments related to personal -characteristics or choices, sexual images or comments in public or online -spaces, deliberate intimidation, bullying, stalking, following, harassing -photography or recording, sustained disruption of talks, IRC chats, electronic -meetings, physical meetings or other events, inappropriate physical contact, or -unwelcome sexual attention. Participants asked to stop any harassing or -bullying behavior are expected to comply immediately. - -If a participant engages in harassing behavior, representatives of the -community may take reasonable action they deem appropriate, including warning -the offender, expulsion from any PyBCN event, or expulsion from mailing -lists, IRC chats, discussion boards and other electronic communications -channels to resolve the issue. This may include expulsion from PyBCN Meetup -group membership. - -If you are being harassed, notice that someone else is being harassed, or have -any other concerns, please act to intercede or ask for help from any member of -the PyBCN community, IRC chat admins, website admins, or -organizers/representatives of any physical events put on under the auspices of -PyBCN. - -This Code of Conduct has been adapted from the -[PyLadies](https://www.pyladies.com/CodeOfConduct/) one and is licensed under a -[Creative Commons Attribution-Share Alike 3.0 Unported license](https://creativecommons.org/licenses/by-sa/3.0/). +{{< repo-markdown file="CODE_OF_CONDUCT.md" >}} diff --git a/layouts/shortcodes/repo-markdown.html b/layouts/shortcodes/repo-markdown.html new file mode 100644 index 00000000..e70a259c --- /dev/null +++ b/layouts/shortcodes/repo-markdown.html @@ -0,0 +1,30 @@ +{{- /* + repo-markdown: render a Markdown file from the root of the repository. + + Usage: {{< repo-markdown file="CODE_OF_CONDUCT.md" >}} + + GitHub reads the code of conduct from CODE_OF_CONDUCT.md at the root of + the repository, never from the site, so the text has to live there. The + page at /pybcn_association/coc/ used to hold its own copy, and two copies + of one legal text drift apart. The page now renders the root file through + this shortcode, so the repository and the site always say the same thing. + + The file name is checked against the list below, so content cannot read + an arbitrary file of the repository into a page. + + A leading H1 is dropped: the file carries one so that it stands on its own + on GitHub, and the page layout already prints the title in the hero. +*/ -}} +{{- $allowed := slice "CODE_OF_CONDUCT.md" -}} +{{- $file := .Get "file" -}} +{{- if not (in $allowed $file) -}} + {{- errorf "repo-markdown: file %q is not one of %s (%s)" $file (delimit $allowed ", ") .Position -}} +{{- end -}} +{{- $text := os.ReadFile $file -}} +{{- $text = replaceRE `^# [^\n]*\n+` "" $text -}} +{{- /* + RenderString ends its output with a newline, and so does the content line + that calls this shortcode. One of them is trimmed, so the page is byte + identical to the copy it replaced. +*/ -}} +{{- .Page.RenderString (dict "display" "block") $text | strings.TrimSuffix "\n" | safeHTML -}}