| 37 |
37 |
|
//! [`Node::list`] of rows with acts, not markdown. The test is what it would
|
| 38 |
38 |
|
//! cost to add an eighth: a row, versus a line of prose somebody has to match
|
| 39 |
39 |
|
//! against six others by hand.
|
|
40 |
+ |
//!
|
|
41 |
+ |
//! # The words are in `content/policy.toml`
|
|
42 |
+ |
//!
|
|
43 |
+ |
//! Both lists were `const` arrays here and are now copy, read at build time by
|
|
44 |
+ |
//! `copy "content/policy.toml" as sections` and `as others`. What is left in
|
|
45 |
+ |
//! this file is the structure: which regions there are, that a section's body
|
|
46 |
+ |
//! is prose and an other-policy is a row with an act. Somebody changing what
|
|
47 |
+ |
//! the policy says edits TOML and reads no Rust, which is the whole point
|
|
48 |
+ |
//! (quasicoherent `98fbee62`).
|
|
49 |
+ |
//!
|
|
50 |
+ |
//! It is not only a place to put words. A `const` cannot be evaluated by a
|
|
51 |
+ |
//! proc macro, so `for part in SECTIONS` survived into whatever the macro
|
|
52 |
+ |
//! emitted, loop and all. Copy the macro can read is copy the macro can write
|
|
53 |
+ |
//! out, which is what leaves the page foldable to a literal later.
|
|
54 |
+ |
//!
|
|
55 |
+ |
//! What did not move is [`QUESTIONS`], which is one sentence and not a table.
|
| 40 |
56 |
|
|
| 41 |
57 |
|
use makeover_layout as layout;
|
| 42 |
58 |
|
use quasi_declare::declare;
|
| 51 |
67 |
|
|
| 52 |
68 |
|
const MEASURE: layout::Measure = layout::Measure::Wide;
|
| 53 |
69 |
|
|
| 54 |
|
- |
/// One headed section of the policy.
|
| 55 |
|
- |
struct Section {
|
| 56 |
|
- |
heading: &'static str,
|
| 57 |
|
- |
/// The body, as markdown. See the module header for why this is prose
|
| 58 |
|
- |
/// rather than nodes.
|
| 59 |
|
- |
body: &'static str,
|
| 60 |
|
- |
}
|
| 61 |
|
- |
|
| 62 |
|
- |
/// The five prose sections, in the template's order. "Other Policies" is not
|
| 63 |
|
- |
/// here; see [`OTHER`].
|
| 64 |
|
- |
const SECTIONS: &[Section] = &[
|
| 65 |
|
- |
Section {
|
| 66 |
|
- |
heading: "What's Welcome",
|
| 67 |
|
- |
body: "Creative work across all supported types:\n\
|
| 68 |
|
- |
\n\
|
| 69 |
|
- |
- Software, plugins, presets, and templates\n\
|
| 70 |
|
- |
- Audio: music, podcasts, samples, sound design\n\
|
| 71 |
|
- |
- Writing: articles, guides, courses, fiction\n\
|
| 72 |
|
- |
- Visual work: images, photography, design assets\n\
|
| 73 |
|
- |
- Video: tutorials, performances, documentaries\n\
|
| 74 |
|
- |
\n\
|
| 75 |
|
- |
If you made it and it's legal to distribute, it belongs here.",
|
| 76 |
|
- |
},
|
| 77 |
|
- |
Section {
|
| 78 |
|
- |
heading: "What's Not Allowed",
|
| 79 |
|
- |
body: "- Content that violates applicable law\n\
|
| 80 |
|
- |
- Harassment, threats, or doxxing\n\
|
| 81 |
|
- |
- Spam, deceptive listings, or bait-and-switch pricing\n\
|
| 82 |
|
- |
- Malware, exploits, or tools designed to cause harm\n\
|
| 83 |
|
- |
- Impersonation of other creators or organizations\n\
|
| 84 |
|
- |
- Content you don't have the rights to distribute",
|
| 85 |
|
- |
},
|
| 86 |
|
- |
Section {
|
| 87 |
|
- |
heading: "How We Handle Issues",
|
| 88 |
|
- |
body: "During private alpha, every creator has a direct relationship with the admin. \
|
| 89 |
|
- |
If something comes up, we talk about it. No automated takedowns, no faceless \
|
| 90 |
|
- |
tickets.\n\
|
| 91 |
|
- |
\n\
|
| 92 |
|
- |
Post-alpha, we'll introduce a formal process with written notice of any policy \
|
| 93 |
|
- |
violation, an opportunity to appeal, and continued access to data export \
|
| 94 |
|
- |
throughout.",
|
| 95 |
|
- |
},
|
| 96 |
|
- |
Section {
|
| 97 |
|
- |
heading: "Your Rights",
|
| 98 |
|
- |
body: "- Full data export is always available: your content, metadata, and transaction \
|
| 99 |
|
- |
history\n\
|
| 100 |
|
- |
- If we ever moderate content or suspend an account, you'll get a clear \
|
| 101 |
|
- |
explanation of what policy was violated\n\
|
| 102 |
|
- |
- You'll have the opportunity to appeal\n\
|
| 103 |
|
- |
- You can export your data even while suspended (excluding content we can't \
|
| 104 |
|
- |
legally host)\n\
|
| 105 |
|
- |
\n\
|
| 106 |
|
- |
These commitments are part of our [creator guarantees](/docs/guarantees).",
|
| 107 |
|
- |
},
|
| 108 |
|
- |
Section {
|
| 109 |
|
- |
heading: "Software Downloads",
|
| 110 |
|
- |
body: "Makenotwork hosts downloadable software uploaded by creators. While we take \
|
| 111 |
|
- |
steps to make sure downloads are safe:\n\
|
| 112 |
|
- |
\n\
|
| 113 |
|
- |
- Creators are responsible for the safety and integrity of their uploads\n\
|
| 114 |
|
- |
- Users should verify downloads with antivirus software before running them\n\
|
| 115 |
|
- |
- We do not guarantee that any download is free of malware or other harmful \
|
| 116 |
|
- |
content\n\
|
| 117 |
|
- |
- Report suspicious downloads to **reports@makenot.work**",
|
| 118 |
|
- |
},
|
| 119 |
|
- |
];
|
| 120 |
|
- |
|
| 121 |
|
- |
/// One of the other policy documents.
|
| 122 |
|
- |
///
|
| 123 |
|
- |
/// Named members rather than a tuple, for the reason [`Section`] has them: a
|
| 124 |
|
- |
/// description names what it draws, and `.1` is not a name.
|
| 125 |
|
- |
struct Other {
|
| 126 |
|
- |
title: &'static str,
|
| 127 |
|
- |
covers: &'static str,
|
| 128 |
|
- |
route: &'static str,
|
| 129 |
|
- |
}
|
| 130 |
|
- |
|
| 131 |
|
- |
/// The other policy documents: a title, what it covers, and where it lives.
|
| 132 |
|
- |
const OTHER: &[Other] = &[
|
| 133 |
|
- |
Other {
|
| 134 |
|
- |
title: "Terms of Service",
|
| 135 |
|
- |
covers: "What you agree to by using Makenotwork",
|
| 136 |
|
- |
route: "/docs/terms-of-service",
|
| 137 |
|
- |
},
|
| 138 |
|
- |
Other {
|
| 139 |
|
- |
title: "Privacy Policy",
|
| 140 |
|
- |
covers: "What we collect, why, and how to exercise your rights",
|
| 141 |
|
- |
route: "/docs/privacy-policy",
|
| 142 |
|
- |
},
|
| 143 |
|
- |
Other {
|
| 144 |
|
- |
title: "Payments & Refunds",
|
| 145 |
|
- |
covers: "Merchant-of-record model, refunds, chargebacks",
|
| 146 |
|
- |
route: "/docs/payments",
|
| 147 |
|
- |
},
|
| 148 |
|
- |
Other {
|
| 149 |
|
- |
title: "Acceptable Use",
|
| 150 |
|
- |
covers: "Specific behaviour that gets accounts suspended",
|
| 151 |
|
- |
route: "/docs/acceptable-use",
|
| 152 |
|
- |
},
|
| 153 |
|
- |
Other {
|
| 154 |
|
- |
title: "Copyright & DMCA",
|
| 155 |
|
- |
covers: "How takedowns and counter-notifications work",
|
| 156 |
|
- |
route: "/docs/copyright",
|
| 157 |
|
- |
},
|
| 158 |
|
- |
Other {
|
| 159 |
|
- |
title: "Appeals",
|
| 160 |
|
- |
covers: "How to challenge a moderation decision",
|
| 161 |
|
- |
route: "/docs/appeals",
|
| 162 |
|
- |
},
|
| 163 |
|
- |
Other {
|
| 164 |
|
- |
title: "Mailing List Data Processing",
|
| 165 |
|
- |
covers: "Who answers a subscriber's request about a project mailing list",
|
| 166 |
|
- |
route: "/docs/mailing-list-data-processing",
|
| 167 |
|
- |
},
|
| 168 |
|
- |
];
|
| 169 |
|
- |
|
| 170 |
70 |
|
/// The page. Reads nothing.
|
| 171 |
71 |
|
pub fn screen(_viewer: &super::Viewer, _request: Request) -> Result<Response, RouteError> {
|
| 172 |
72 |
|
Ok(page_screen().into())
|
| 186 |
86 |
|
text "Makenotwork exists so creators can sell their work on fair terms. This policy \
|
| 187 |
87 |
|
describes what belongs here, what doesn't, and how we handle problems.";
|
| 188 |
88 |
|
|
| 189 |
|
- |
for part in SECTIONS {
|
|
89 |
+ |
for part in copy "content/policy.toml" as sections {
|
| 190 |
90 |
|
section part.heading;
|
| 191 |
91 |
|
include super::own_prose(part.body);
|
| 192 |
92 |
|
}
|
| 193 |
93 |
|
|
| 194 |
94 |
|
section "Other Policies";
|
| 195 |
95 |
|
list {
|
| 196 |
|
- |
for other in OTHER {
|
|
96 |
+ |
for other in copy "content/policy.toml" as others {
|
| 197 |
97 |
|
row other.title {
|
| 198 |
98 |
|
secondary other.covers;
|
| 199 |
99 |
|
act "Read" to get other.route navigating;
|
| 275 |
175 |
|
);
|
| 276 |
176 |
|
}
|
| 277 |
177 |
|
|
| 278 |
|
- |
/// Every policy document the page pointed at is still pointed at.
|
|
178 |
+ |
/// The copy the page is built from, read the way the macro reads it.
|
|
179 |
+ |
///
|
|
180 |
+ |
/// The test asks the content file what the page should say rather than
|
|
181 |
+ |
/// holding a second copy of it. A row that stops being rendered fails here;
|
|
182 |
+ |
/// a row somebody deletes from the file is a deletion and not a failure,
|
|
183 |
+ |
/// which is the right answer for copy.
|
|
184 |
+ |
fn copy() -> toml::Table {
|
|
185 |
+ |
include_str!("../../content/policy.toml")
|
|
186 |
+ |
.parse()
|
|
187 |
+ |
.expect("the policy copy is TOML")
|
|
188 |
+ |
}
|
|
189 |
+ |
|
|
190 |
+ |
/// Every policy document the file names is on the page.
|
| 279 |
191 |
|
///
|
| 280 |
192 |
|
/// The titles are compared escaped, because that is what lands in the
|
| 281 |
193 |
|
/// markup: two of the seven carry an ampersand, and the description layer
|
| 284 |
196 |
|
fn every_other_policy_keeps_its_row_and_its_route() {
|
| 285 |
197 |
|
let html = html();
|
| 286 |
198 |
|
|
| 287 |
|
- |
for other in OTHER {
|
| 288 |
|
- |
let escaped = crate::helpers::escape_html(other.title);
|
| 289 |
|
- |
assert!(html.contains(&escaped), "{} missing", other.title);
|
| 290 |
|
- |
assert!(html.contains(other.route), "{} missing", other.route);
|
|
199 |
+ |
let copy = copy();
|
|
200 |
+ |
let others = copy["others"].as_array().expect("a list of documents");
|
|
201 |
+ |
assert_eq!(others.len(), 7, "the page pointed at seven documents");
|
|
202 |
+ |
for other in others {
|
|
203 |
+ |
let title = other["title"].as_str().expect("a title");
|
|
204 |
+ |
let route = other["route"].as_str().expect("a route");
|
|
205 |
+ |
let escaped = crate::helpers::escape_html(title);
|
|
206 |
+ |
assert!(html.contains(&escaped), "{title} missing");
|
|
207 |
+ |
assert!(html.contains(route), "{route} missing");
|
|
208 |
+ |
}
|
|
209 |
+ |
}
|
|
210 |
+ |
|
|
211 |
+ |
/// Every prose section the file names is a heading on the page.
|
|
212 |
+ |
///
|
|
213 |
+ |
/// The counterpart for the other list, and the reason both are here: the
|
|
214 |
+ |
/// copy moved out of Rust in `98fbee62`, so what proves it is still on the
|
|
215 |
+ |
/// page has to read the file it moved into.
|
|
216 |
+ |
#[test]
|
|
217 |
+ |
fn every_section_keeps_its_heading() {
|
|
218 |
+ |
let html = html();
|
|
219 |
+ |
|
|
220 |
+ |
let copy = copy();
|
|
221 |
+ |
let sections = copy["sections"].as_array().expect("a list of sections");
|
|
222 |
+ |
assert_eq!(sections.len(), 5, "the page had five prose sections");
|
|
223 |
+ |
for section in sections {
|
|
224 |
+ |
let heading = section["heading"].as_str().expect("a heading");
|
|
225 |
+ |
let escaped = crate::helpers::escape_html(heading);
|
|
226 |
+ |
assert!(html.contains(&escaped), "{heading} missing");
|
| 291 |
227 |
|
}
|
| 292 |
228 |
|
}
|
| 293 |
229 |
|
|