max / makenotwork
12 files changed,
+116 insertions,
-71 deletions
| @@ -2875,7 +2875,7 @@ | |||
| 2875 | 2875 | checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" | |
| 2876 | 2876 | dependencies = [ | |
| 2877 | 2877 | "libc", | |
| 2878 | - | "windows-sys 0.59.0", | |
| 2878 | + | "windows-sys 0.52.0", | |
| 2879 | 2879 | ] | |
| 2880 | 2880 | ||
| 2881 | 2881 | [[package]] | |
| @@ -4811,7 +4811,7 @@ | |||
| 4811 | 4811 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 4812 | 4812 | checksum = "82cb6a9f675da968c63b6208c641b9dca58fc0133ae53375736b1767b0cab8bd" | |
| 4813 | 4813 | dependencies = [ | |
| 4814 | - | "windows-sys 0.59.0", | |
| 4814 | + | "windows-sys 0.61.2", | |
| 4815 | 4815 | ] | |
| 4816 | 4816 | ||
| 4817 | 4817 | [[package]] | |
| @@ -5722,7 +5722,7 @@ | |||
| 5722 | 5722 | source = "registry+https://github.com/rust-lang/crates.io-index" | |
| 5723 | 5723 | checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" | |
| 5724 | 5724 | dependencies = [ | |
| 5725 | - | "windows-sys 0.59.0", | |
| 5725 | + | "windows-sys 0.61.2", | |
| 5726 | 5726 | ] | |
| 5727 | 5727 | ||
| 5728 | 5728 | [[package]] | |
| @@ -6636,7 +6636,7 @@ | |||
| 6636 | 6636 | ||
| 6637 | 6637 | [[package]] | |
| 6638 | 6638 | name = "quasi-axum" | |
| 6639 | - | version = "0.93.0" | |
| 6639 | + | version = "0.94.0" | |
| 6640 | 6640 | dependencies = [ | |
| 6641 | 6641 | "axum", | |
| 6642 | 6642 | "http 1.4.2", | |
| @@ -6647,7 +6647,7 @@ | |||
| 6647 | 6647 | ||
| 6648 | 6648 | [[package]] | |
| 6649 | 6649 | name = "quasi-basics" | |
| 6650 | - | version = "0.93.0" | |
| 6650 | + | version = "0.94.0" | |
| 6651 | 6651 | dependencies = [ | |
| 6652 | 6652 | "makeover-layout", | |
| 6653 | 6653 | "quasi-router", | |
| @@ -6655,7 +6655,7 @@ | |||
| 6655 | 6655 | ||
| 6656 | 6656 | [[package]] | |
| 6657 | 6657 | name = "quasi-http" | |
| 6658 | - | version = "0.93.0" | |
| 6658 | + | version = "0.94.0" | |
| 6659 | 6659 | dependencies = [ | |
| 6660 | 6660 | "form_urlencoded", | |
| 6661 | 6661 | "http 1.4.2", | |
| @@ -6664,7 +6664,7 @@ | |||
| 6664 | 6664 | ||
| 6665 | 6665 | [[package]] | |
| 6666 | 6666 | name = "quasi-router" | |
| 6667 | - | version = "0.93.0" | |
| 6667 | + | version = "0.94.0" | |
| 6668 | 6668 | dependencies = [ | |
| 6669 | 6669 | "makeover-layout", | |
| 6670 | 6670 | ] | |
| @@ -6687,7 +6687,7 @@ | |||
| 6687 | 6687 | ||
| 6688 | 6688 | [[package]] | |
| 6689 | 6689 | name = "quasi-webview" | |
| 6690 | - | version = "0.93.0" | |
| 6690 | + | version = "0.94.0" | |
| 6691 | 6691 | dependencies = [ | |
| 6692 | 6692 | "docengine", | |
| 6693 | 6693 | "makeover-layout", | |
| @@ -6763,7 +6763,7 @@ | |||
| 6763 | 6763 | "once_cell", | |
| 6764 | 6764 | "socket2", | |
| 6765 | 6765 | "tracing", | |
| 6766 | - | "windows-sys 0.59.0", | |
| 6766 | + | "windows-sys 0.52.0", | |
| 6767 | 6767 | ] | |
| 6768 | 6768 | ||
| 6769 | 6769 | [[package]] | |
| @@ -7326,7 +7326,7 @@ | |||
| 7326 | 7326 | "errno", | |
| 7327 | 7327 | "libc", | |
| 7328 | 7328 | "linux-raw-sys 0.4.15", | |
| 7329 | - | "windows-sys 0.59.0", | |
| 7329 | + | "windows-sys 0.52.0", | |
| 7330 | 7330 | ] | |
| 7331 | 7331 | ||
| 7332 | 7332 | [[package]] | |
| @@ -7339,7 +7339,7 @@ | |||
| 7339 | 7339 | "errno", | |
| 7340 | 7340 | "libc", | |
| 7341 | 7341 | "linux-raw-sys 0.12.1", | |
| 7342 | - | "windows-sys 0.59.0", | |
| 7342 | + | "windows-sys 0.52.0", | |
| 7343 | 7343 | ] | |
| 7344 | 7344 | ||
| 7345 | 7345 | [[package]] | |
| @@ -7419,7 +7419,7 @@ | |||
| 7419 | 7419 | "security-framework 3.7.0", | |
| 7420 | 7420 | "security-framework-sys", | |
| 7421 | 7421 | "webpki-root-certs", | |
| 7422 | - | "windows-sys 0.59.0", | |
| 7422 | + | "windows-sys 0.52.0", | |
| 7423 | 7423 | ] | |
| 7424 | 7424 | ||
| 7425 | 7425 | [[package]] | |
| @@ -7483,7 +7483,7 @@ | |||
| 7483 | 7483 | ||
| 7484 | 7484 | [[package]] | |
| 7485 | 7485 | name = "s3-storage" | |
| 7486 | - | version = "0.1.1" | |
| 7486 | + | version = "0.1.2" | |
| 7487 | 7487 | dependencies = [ | |
| 7488 | 7488 | "aws-config", | |
| 7489 | 7489 | "aws-sdk-s3", | |
| @@ -8545,7 +8545,7 @@ | |||
| 8545 | 8545 | "getrandom 0.4.3", | |
| 8546 | 8546 | "once_cell", | |
| 8547 | 8547 | "rustix 1.1.4", | |
| 8548 | - | "windows-sys 0.59.0", | |
| 8548 | + | "windows-sys 0.52.0", | |
| 8549 | 8549 | ] | |
| 8550 | 8550 | ||
| 8551 | 8551 | [[package]] | |
| @@ -10759,21 +10759,13 @@ | |||
| 10759 | 10759 | "pkg-config", | |
| 10760 | 10760 | ] | |
| 10761 | 10761 | ||
| 10762 | - | [[patch.unused]] | |
| 10763 | - | name = "synckit-client" | |
| 10764 | - | version = "0.10.0" | |
| 10765 | - | ||
| 10766 | - | [[patch.unused]] | |
| 10767 | - | name = "synckit-config" | |
| 10768 | - | version = "0.2.0" | |
| 10769 | - | ||
| 10770 | 10762 | [[patch.unused]] | |
| 10771 | 10763 | name = "quasi-immediate" | |
| 10772 | - | version = "0.93.0" | |
| 10764 | + | version = "0.94.0" | |
| 10773 | 10765 | ||
| 10774 | 10766 | [[patch.unused]] | |
| 10775 | 10767 | name = "quasi-notifs" | |
| 10776 | - | version = "0.93.0" | |
| 10768 | + | version = "0.94.0" | |
| 10777 | 10769 | ||
| 10778 | 10770 | [[patch.unused]] | |
| 10779 | 10771 | name = "quasi-store" | |
| @@ -10781,7 +10773,7 @@ | |||
| 10781 | 10773 | ||
| 10782 | 10774 | [[patch.unused]] | |
| 10783 | 10775 | name = "quasi-tauri" | |
| 10784 | - | version = "0.93.0" | |
| 10776 | + | version = "0.94.0" | |
| 10785 | 10777 | ||
| 10786 | 10778 | [[patch.unused]] | |
| 10787 | 10779 | name = "kberg" | |
| @@ -10794,3 +10786,11 @@ | |||
| 10794 | 10786 | [[patch.unused]] | |
| 10795 | 10787 | name = "painhours" | |
| 10796 | 10788 | version = "0.1.0" | |
| 10789 | + | ||
| 10790 | + | [[patch.unused]] | |
| 10791 | + | name = "synckit-client" | |
| 10792 | + | version = "0.10.0" | |
| 10793 | + | ||
| 10794 | + | [[patch.unused]] | |
| 10795 | + | name = "synckit-config" | |
| 10796 | + | version = "0.2.0" |
| @@ -163,7 +163,7 @@ | |||
| 163 | 163 | # through quasi beside its Askama equivalent, so the cost of describing a screen | |
| 164 | 164 | # here is measured rather than argued. Not load-bearing for any shipped route. | |
| 165 | 165 | # See wiki look-wave-2, tier G. | |
| 166 | - | quasi-router = { git = "https://makenot.work/git/max/quasi.git", version = "0.93" } | |
| 166 | + | quasi-router = { git = "https://makenot.work/git/max/quasi.git", version = "0.94" } | |
| 167 | 167 | # The description vocabulary quasi's screen types are built from. Pinned here | |
| 168 | 168 | # rather than reached through quasi-router's re-export because a described | |
| 169 | 169 | # screen names FieldKind and Tone directly; it has to track what quasi-router | |
| @@ -179,13 +179,13 @@ | |||
| 179 | 179 | # For the request head the per-viewer state factory reads. axum re-exports it, | |
| 180 | 180 | # but the factory's signature is quasi-axum's and names `http::request::Parts`. | |
| 181 | 181 | http = "1.3.1" | |
| 182 | - | quasi-axum = { git = "https://makenot.work/git/max/quasi.git", version = "0.93" } | |
| 183 | - | quasi-webview = { git = "https://makenot.work/git/max/quasi.git", version = "0.93" } | |
| 182 | + | quasi-axum = { git = "https://makenot.work/git/max/quasi.git", version = "0.94" } | |
| 183 | + | quasi-webview = { git = "https://makenot.work/git/max/quasi.git", version = "0.94" } | |
| 184 | 184 | # The first-party widget set. The carousel was proved here against three pages | |
| 185 | 185 | # and then moved there; what is left in `quasi/widgets/carousel.rs` is the | |
| 186 | 186 | # Askama glue. Not `makeover-basics`: a widget is an assembly of `Node`s, so it | |
| 187 | 187 | # sits above quasi-router rather than inside the description suite. | |
| 188 | - | quasi-basics = { git = "https://makenot.work/git/max/quasi.git", version = "0.93" } | |
| 188 | + | quasi-basics = { git = "https://makenot.work/git/max/quasi.git", version = "0.94" } | |
| 189 | 189 | ||
| 190 | 190 | # Git source browser | |
| 191 | 191 | gix = { version = "0.86", default-features = false, features = ["blame", "mailmap", "revision", "sha1", "max-performance-safe"] } | |
| @@ -277,7 +277,7 @@ | |||
| 277 | 277 | # Written from the crate rather than copied into the repo for the reason the | |
| 278 | 278 | # constants exist: a stale copy in a static directory is a silently dead bulk | |
| 279 | 279 | # bar, and the version has to be the one the emitter agrees with. | |
| 280 | - | quasi-webview = { git = "https://makenot.work/git/max/quasi.git", version = "0.93" } | |
| 280 | + | quasi-webview = { git = "https://makenot.work/git/max/quasi.git", version = "0.94" } | |
| 281 | 281 | ||
| 282 | 282 | [dev-dependencies] | |
| 283 | 283 | tower = { version = "0.5.3", features = ["util"] } |
| @@ -131,7 +131,7 @@ | |||
| 131 | 131 | in private alpha; we're approving applications one cohort at a time.", | |
| 132 | 132 | )) | |
| 133 | 133 | .with(Node::section("How It Works")) | |
| 134 | - | .with(Node::rich( | |
| 134 | + | .with(super::own_prose( | |
| 135 | 135 | "1. **Sign up** and verify your email\n\ | |
| 136 | 136 | 2. **Apply** from your dashboard: tell us what you make and which tier fits\n\ | |
| 137 | 137 | 3. **Get approved**: we review applications individually, usually within a few days\n\ | |
| @@ -155,7 +155,7 @@ | |||
| 155 | 155 | the payment processor's fee (~3%).", | |
| 156 | 156 | )) | |
| 157 | 157 | .with(tier_table(prices)) | |
| 158 | - | .with(Node::rich( | |
| 158 | + | .with(super::own_prose( | |
| 159 | 159 | "Every tier is the complete platform: `/u/username` profile, project and item pages, \ | |
| 160 | 160 | project forum, Discover listing, memberships, pay-what-you-want, promo codes, RSS, \ | |
| 161 | 161 | analytics, full data export, 2FA/passkeys. The tier picks the file-size envelope, \ | |
| @@ -164,13 +164,13 @@ | |||
| 164 | 164 | supported country. [Full tier details](/docs/tiers) | \ | |
| 165 | 165 | [Pricing models](/docs/pricing)", | |
| 166 | 166 | )) | |
| 167 | - | .with(Node::rich( | |
| 167 | + | .with(super::own_prose( | |
| 168 | 168 | "**Not ready to commit?** Request a **free trial** (2-6 weeks, no credit card) when \ | |
| 169 | 169 | you apply. Or [try sandbox mode](/sandbox) to explore the dashboard without signing \ | |
| 170 | 170 | up.", | |
| 171 | 171 | )) | |
| 172 | 172 | .with(Node::section("Who Runs This")) | |
| 173 | - | .with(Node::rich( | |
| 173 | + | .with(super::own_prose( | |
| 174 | 174 | "Makenotwork is built and operated by one person. No investors, no board, no outside \ | |
| 175 | 175 | pressure. Decisions are fast and aligned with creators, but there's no large team \ | |
| 176 | 176 | behind the scenes. Read the full picture in our \ |
| @@ -42,14 +42,12 @@ | |||
| 42 | 42 | //! redirect. Filed against quasicoherent rather than worked around with a | |
| 43 | 43 | //! hand-written attribute inside a described form. | |
| 44 | 44 | //! | |
| 45 | - | //! # The visitor's sentence carries `nofollow`, and should not | |
| 45 | + | //! # The visitor's sentence is the page's own, and says so | |
| 46 | 46 | //! | |
| 47 | - | //! `Node::rich` renders through `docengine::render_strict`, the preset written | |
| 48 | - | //! for user-generated content, so the two links in that sentence come out with | |
| 49 | - | //! `rel="noopener noreferrer nofollow"`. They point at `/join` and `/login`: | |
| 50 | - | //! our own pages, telling crawlers not to follow them. quasi has one `Rich` and | |
| 51 | - | //! treats every source as untrusted, which is right for a forum post and wrong | |
| 52 | - | //! for a page's own copy. quasicoherent `24a3b1df`. | |
| 47 | + | //! [`super::own_prose`] rather than `Node::rich`. The two links in it point at | |
| 48 | + | //! `/join` and `/login`, our own pages, and an untrusted source would have them | |
| 49 | + | //! carrying `nofollow` -- which is what shipped until quasi grew a trust axis | |
| 50 | + | //! separate from its richness one (quasicoherent `24a3b1df`, quasi 0.94). | |
| 53 | 51 | ||
| 54 | 52 | use makeover_layout as layout; | |
| 55 | 53 | use quasi_router::screen::{Figure, Row}; | |
| @@ -167,7 +165,7 @@ | |||
| 167 | 165 | // says which link is for whom and a pair of buttons does not, and | |
| 168 | 166 | // that distinction is the whole point of the finding above. It is | |
| 169 | 167 | // prose, so it is `Node::rich`, on `/policy`'s rule. | |
| 170 | - | pitch(page).with(Node::rich( | |
| 168 | + | pitch(page).with(super::own_prose( | |
| 171 | 169 | "[Create an account](/join) to join, or [log in](/login) if you already have one.", | |
| 172 | 170 | )) | |
| 173 | 171 | } | |
| @@ -258,18 +256,24 @@ | |||
| 258 | 256 | // site header carries `/join` and `/login` on every page, so an href | |
| 259 | 257 | // alone would pass whatever this block said. | |
| 260 | 258 | // | |
| 261 | - | // Matched loosely across the anchor's attributes because `Node::rich` | |
| 262 | - | // hardens links as untrusted content and adds | |
| 263 | - | // `rel="noopener noreferrer nofollow"` (quasicoherent `24a3b1df`). The | |
| 264 | - | // exact-markup form belongs back here when a screen can say its prose | |
| 265 | - | // is its own. | |
| 259 | + | // Each link's own copy, and neither of them nofollowed. The `nofollow` | |
| 260 | + | // half is the seal on this being `own_prose`: an untrusted source is | |
| 261 | + | // hardened by the renderer and both anchors would carry it. | |
| 262 | + | // | |
| 263 | + | // `rel="noopener noreferrer"` does survive, from ammonia's default, and | |
| 264 | + | // is left alone: it suppresses the referrer and the opener handle, not | |
| 265 | + | // the crawl, so it costs nothing an internal link needs. | |
| 266 | 266 | assert!(html.contains(r#"href="/join""#), "{html}"); | |
| 267 | - | assert!(html.contains("Create an account</a> to join"), "{html}"); | |
| 267 | + | assert!(html.contains(">Create an account</a> to join"), "{html}"); | |
| 268 | 268 | assert!(html.contains(r#"href="/login""#), "{html}"); | |
| 269 | 269 | assert!( | |
| 270 | - | html.contains("log in</a> if you already have one"), | |
| 270 | + | html.contains(">log in</a> if you already have one"), | |
| 271 | 271 | "{html}" | |
| 272 | 272 | ); | |
| 273 | + | assert!( | |
| 274 | + | !html.contains("nofollow"), | |
| 275 | + | "the page nofollowed its own links: {html}" | |
| 276 | + | ); | |
| 273 | 277 | assert!( | |
| 274 | 278 | !html.contains(SUBSCRIBE), | |
| 275 | 279 | "a visitor cannot subscribe, so the control must not be drawn: {html}" |
| @@ -172,7 +172,7 @@ | |||
| 172 | 172 | /// word in it that goes somewhere, and splitting it into text, an act and more | |
| 173 | 173 | /// text is how a sentence stops reading as a sentence in every host. | |
| 174 | 174 | fn upstream_line(base: &str) -> Node { | |
| 175 | - | Node::rich(format!( | |
| 175 | + | super::own_prose(format!( | |
| 176 | 176 | "Your memberships across [Multithreaded]({base}) forum communities." | |
| 177 | 177 | )) | |
| 178 | 178 | } |
| @@ -116,7 +116,7 @@ | |||
| 116 | 116 | // Notes are the one thing this browser does that no other forge does, | |
| 117 | 117 | // and nothing on a repository page says so to somebody who has never | |
| 118 | 118 | // seen one. The landing page is where that sentence reaches everybody. | |
| 119 | - | .with(Node::rich( | |
| 119 | + | .with(super::own_prose( | |
| 120 | 120 | "Every repository here renders [git notes](/docs/git-notes): annotation attached \ | |
| 121 | 121 | to a commit without rewriting it, stored in the repository and carried by a clone.", | |
| 122 | 122 | )); | |
| @@ -124,7 +124,7 @@ | |||
| 124 | 124 | // The only route to an annotation whose target repository is gone: nothing | |
| 125 | 125 | // else links to it once there is no commit page to link from. | |
| 126 | 126 | if loaded.signed_in { | |
| 127 | - | page = page.with(Node::rich( | |
| 127 | + | page = page.with(super::own_prose( | |
| 128 | 128 | "[Your annotations](/git/my-annotations), private to you, across every repository \ | |
| 129 | 129 | you have read here.", | |
| 130 | 130 | )); |
| @@ -145,7 +145,12 @@ | |||
| 145 | 145 | return page; | |
| 146 | 146 | } | |
| 147 | 147 | ||
| 148 | - | page.with(Node::rich(format!( | |
| 148 | + | // `own_prose` rather than `Node::rich`, and the interpolation is the reason | |
| 149 | + | // to say why: `owner` is a `Username`, which `validate_username` restricts | |
| 150 | + | // to letters, digits and underscores, so it cannot carry markup into a | |
| 151 | + | // source the renderer no longer hardens. A value that could would want | |
| 152 | + | // `Node::rich` instead, whatever else is in the string. | |
| 153 | + | page.with(super::own_prose(format!( | |
| 149 | 154 | "Push a new repository:\n\ | |
| 150 | 155 | \n\ | |
| 151 | 156 | ```\n\ |
| @@ -714,6 +714,29 @@ | |||
| 714 | 714 | quasi_axum::Adapter::new(router, state, std::sync::Arc::new(pricing::renderer())).into_router() | |
| 715 | 715 | } | |
| 716 | 716 | ||
| 717 | + | /// This server's own copy, as markdown. | |
| 718 | + | /// | |
| 719 | + | /// `Node::rich` means "markdown somebody else wrote": quasi hardens it, so its | |
| 720 | + | /// links carry `nofollow`, its raw markup is dropped and fetchable schemes are | |
| 721 | + | /// filtered. That is right for a forum post and wrong for a page's own | |
| 722 | + | /// sentence, and until quasi grew the trust axis every described page here was | |
| 723 | + | /// telling crawlers not to follow its own links (quasicoherent `24a3b1df`). | |
| 724 | + | /// | |
| 725 | + | /// The two axes stay separate in quasi -- `Richness` is what the format may | |
| 726 | + | /// express, `Trust` is who wrote it -- and this is the one combination this | |
| 727 | + | /// server reaches for often enough to name: prose, ours. A screen wanting | |
| 728 | + | /// tables says so with `Node::richness`, and a screen carrying a reader's | |
| 729 | + | /// markdown keeps `Node::rich`. | |
| 730 | + | /// | |
| 731 | + | /// **The test for using it is authorship, not tidiness.** The string has to be | |
| 732 | + | /// a literal in this repository, or interpolated from a value that cannot carry | |
| 733 | + | /// markup. A creator's description reaching a screen through the database is | |
| 734 | + | /// `Node::rich` however well-behaved it has been. | |
| 735 | + | #[must_use] | |
| 736 | + | pub fn own_prose(source: impl Into<String>) -> quasi_router::Node { | |
| 737 | + | quasi_router::Node::rich(source).trust(quasi_router::Trust::Trusted) | |
| 738 | + | } | |
| 739 | + | ||
| 717 | 740 | /// A handler, spelled once so the screens and the mount agree about it. | |
| 718 | 741 | pub type Screen = | |
| 719 | 742 | fn(&Viewer, quasi_router::Request) -> Result<quasi_router::Response, quasi_router::RouteError>; |
| @@ -16,11 +16,16 @@ | |||
| 16 | 16 | //! rows would silently flatten both, and neither loss is visible in a test that | |
| 17 | 17 | //! checks the text is present. | |
| 18 | 18 | //! | |
| 19 | - | //! So each section's body is one [`Node::rich`], which is markdown and renders | |
| 20 | - | //! through docengine -- the same path `/docs/*` takes, so the policy prose and | |
| 21 | - | //! the documents it links to are formatted by one renderer. **What stays | |
| 22 | - | //! described is the structure**: [`Node::section`] per heading, so the section | |
| 23 | - | //! hierarchy is a fact of the screen rather than an `<h2>` inside a blob. | |
| 19 | + | //! So each section's body is one [`super::own_prose`], which is markdown and | |
| 20 | + | //! renders through docengine -- the same path `/docs/*` takes, so the policy | |
| 21 | + | //! prose and the documents it links to are formatted by one renderer. **What | |
| 22 | + | //! stays described is the structure**: [`Node::section`] per heading, so the | |
| 23 | + | //! section hierarchy is a fact of the screen rather than an `<h2>` inside a | |
| 24 | + | //! blob. | |
| 25 | + | //! | |
| 26 | + | //! `own_prose` and not `Node::rich`, which is the difference between a page | |
| 27 | + | //! that points at its own documents and a page that tells crawlers not to | |
| 28 | + | //! follow them (quasi 0.94, quasicoherent `24a3b1df`). | |
| 24 | 29 | //! | |
| 25 | 30 | //! The escape hatch is not swallowing the page. It is carrying the one thing | |
| 26 | 31 | //! this page is made of, which is sentences. | |
| @@ -171,7 +176,7 @@ | |||
| 171 | 176 | for section in SECTIONS { | |
| 172 | 177 | page = page | |
| 173 | 178 | .with(Node::section(section.heading)) | |
| 174 | - | .with(Node::rich(section.body)); | |
| 179 | + | .with(super::own_prose(section.body)); | |
| 175 | 180 | } | |
| 176 | 181 | ||
| 177 | 182 | page = page | |
| @@ -182,7 +187,7 @@ | |||
| 182 | 187 | .act(Act::new("Read", Action::get(*route).navigating())) | |
| 183 | 188 | }))); | |
| 184 | 189 | ||
| 185 | - | page = page.with(Node::section("Questions")).with(Node::rich( | |
| 190 | + | page = page.with(Node::section("Questions")).with(super::own_prose( | |
| 186 | 191 | "If something's unclear or you want to check before posting, reach out at \ | |
| 187 | 192 | **policy@makenot.work**.", | |
| 188 | 193 | )); | |
| @@ -240,6 +245,13 @@ | |||
| 240 | 245 | html.contains(r#"href="/docs/guarantees""#), | |
| 241 | 246 | "the creator-guarantees link did not survive: {html}" | |
| 242 | 247 | ); | |
| 248 | + | // This page exists to point at the other policy documents, so a | |
| 249 | + | // `nofollow` on the way there would be the page working against itself. | |
| 250 | + | // The seal on `super::own_prose`: an untrusted source is hardened. | |
| 251 | + | assert!( | |
| 252 | + | !html.contains("nofollow"), | |
| 253 | + | "the policy page nofollowed its own documents: {html}" | |
| 254 | + | ); | |
| 243 | 255 | assert!( | |
| 244 | 256 | html.contains("<strong>reports@makenot.work</strong>") | |
| 245 | 257 | || html.contains("<b>reports@makenot.work</b>"), |
| @@ -212,7 +212,7 @@ | |||
| 212 | 212 | .into(), | |
| 213 | 213 | act: None, | |
| 214 | 214 | }) | |
| 215 | - | .with(Node::rich( | |
| 215 | + | .with(super::own_prose( | |
| 216 | 216 | "1. Create a project\n\ | |
| 217 | 217 | 2. Add items: audio, video, text, or software\n\ | |
| 218 | 218 | 3. Set prices (or keep them free) and publish\n\ |
| @@ -143,7 +143,7 @@ | |||
| 143 | 143 | ||
| 144 | 144 | /// How long an answer takes, and the way round the queue for a security issue. | |
| 145 | 145 | fn response_times() -> Node { | |
| 146 | - | Node::rich( | |
| 146 | + | super::own_prose( | |
| 147 | 147 | "Response times:\n\n\ | |
| 148 | 148 | - Security issues: same day\n\ | |
| 149 | 149 | - Billing and account access: 24 hours\n\ |
| @@ -19,15 +19,16 @@ | |||
| 19 | 19 | // CTA's own copy rather than on `href="/join"`, which the site header | |
| 20 | 20 | // carries on every page and would pass whatever this block said. | |
| 21 | 21 | // | |
| 22 | - | // Split across the anchor's attributes since the page became a described | |
| 23 | - | // document (`fa1a268d`): the sentence is `Node::rich`, which renders through | |
| 24 | - | // docengine's strict preset and adds `rel="noopener noreferrer nofollow"`. | |
| 25 | - | // That rel on our own pages is wrong and is quasicoherent `24a3b1df`; when | |
| 26 | - | // it is fixed this goes back to matching the whole anchor. | |
| 27 | - | assert!(resp.text.contains(r#"href="/join""#)); | |
| 28 | - | assert!(resp.text.contains("Create an account</a> to join")); | |
| 29 | - | assert!(resp.text.contains(r#"href="/login""#)); | |
| 30 | - | assert!(resp.text.contains("log in</a> if you already have one")); | |
| 22 | + | // Each link's own copy, and neither nofollowed. The `nofollow` half seals | |
| 23 | + | // that the sentence is described as this server's own prose: an untrusted | |
| 24 | + | // markdown source is hardened by the renderer and both would carry it. | |
| 25 | + | assert!(resp.text.contains(">Create an account</a> to join")); | |
| 26 | + | assert!(resp.text.contains(">log in</a> if you already have one")); | |
| 27 | + | assert!( | |
| 28 | + | !resp.text.contains("nofollow"), | |
| 29 | + | "the page nofollowed its own links: {}", | |
| 30 | + | resp.text | |
| 31 | + | ); | |
| 31 | 32 | assert!(!resp.text.contains("Join Fan+")); | |
| 32 | 33 | } | |
| 33 | 34 |