Skip to content

Commit 9baaa30

Browse files
authored
Merge pull request #19 from croque-scp/scp-head
[HEAD] New article
2 parents 16a172c + 4c0a49f commit 9baaa30

File tree

74 files changed

+1336
-3
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

74 files changed

+1336
-3
lines changed

Diff for: .github/workflows/preview.yml

+13-3
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,27 @@ jobs:
1616
runs-on: ubuntu-latest
1717
steps:
1818
- name: Checkout
19-
uses: actions/checkout@v2
20-
- name: Install & build projects
19+
uses: actions/checkout@v4
20+
with:
21+
fetch-depth: 0
22+
23+
- name: Determine changed projects
24+
id: changes
25+
run: |
26+
CHANGED_PROJECTS=$(git diff --name-only origin/${{ github.event.pull_request.base.ref }}...HEAD -- 'source/*' | cut -d/ -f2 | sort -u)
27+
echo "CHANGED_PROJECTS=$CHANGED_PROJECTS" >> $GITHUB_ENV
28+
29+
- name: Install & build changed projects
2130
run: |
2231
mkdir dist
23-
for project in $(cd source && ls -d */); do
32+
for project in $CHANGED_PROJECTS; do
2433
pushd source/$project
2534
npm install
2635
npm run build
2736
popd
2837
cp -r source/$project/dist dist/$project
2938
done
39+
3040
- name: Deploy preview
3141
uses: rossjrw/pr-preview-action@v1
3242
with:

Diff for: source/scp-head/.prettierrc.json

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{}

Diff for: source/scp-head/assets/README.md

+16

Diff for: source/scp-head/assets/head-bertie.jpg

29.7 KB

Diff for: source/scp-head/assets/head-brian-angle.jpg

710 KB

Diff for: source/scp-head/assets/head-brian-straight.jpg

654 KB

Diff for: source/scp-head/assets/head-christie-angle.jpg

1.99 MB

Diff for: source/scp-head/assets/head-christie-straight.jpg

2.03 MB

Diff for: source/scp-head/assets/head-claire-angle.jpg

896 KB

Diff for: source/scp-head/assets/head-claire-straight.jpg

892 KB

Diff for: source/scp-head/assets/head-gary-angle.jpg

955 KB

Diff for: source/scp-head/assets/head-gary-straight.jpg

760 KB

Diff for: source/scp-head/assets/head-gregory-angle.jpg

572 KB

Diff for: source/scp-head/assets/head-gregory-straight.jpg

592 KB

Diff for: source/scp-head/assets/head-literally-you.jpg

27 KB

Diff for: source/scp-head/assets/head-nicholas-angle.jpg

1.09 MB

Diff for: source/scp-head/assets/head-nicholas-straight.jpg

1.07 MB

Diff for: source/scp-head/assets/head-original-angle.jpg

801 KB

Diff for: source/scp-head/assets/head-original-straight.jpg

859 KB

Diff for: source/scp-head/assets/head-roger.jpg

37.3 KB

Diff for: source/scp-head/assets/heads-pile.jpg

1.68 MB

Diff for: source/scp-head/assets/heads-row-1.jpg

29.5 KB

Diff for: source/scp-head/assets/heads-row-2.jpg

42.3 KB

Diff for: source/scp-head/assets/masonry-exterior-bradford.jpg

2.38 MB

Diff for: source/scp-head/assets/masonry-exterior-salford.jpg

2.38 MB

Diff for: source/scp-head/assets/masonry-interior.jpg

737 KB

Diff for: source/scp-head/assets/optimise-images.sh

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#!/usr/bin/env sh
2+
3+
set -e
4+
5+
rm -rf resized
6+
mkdir -p resized
7+
8+
for image in *.jpg; do
9+
# Convert to PNG to enable transparency
10+
convert "$image" resized/temp.png
11+
12+
convert -resize 1000x\> "$image" resized/temp.png
13+
# When a WEBP without transparency is loaded as an overlay to another image, it starts blanked out as the overlay starts to load - add 1% transparency to a pixel to prevent that
14+
mogrify -alpha on -channel A -fx 'j==h-1 && i==w-1 ? A*0.99 : A' resized/temp.png
15+
cwebp -q 80 resized/temp.png -o "resized/${image%%.jpg}-$(identify -format '%wx%h' resized/temp.png).webp"
16+
17+
# Make small-size WEBP at 300px
18+
convert -resize 300x\> "$image" resized/temp.png
19+
cwebp -q 80 resized/temp.png -o "resized/${image%%.jpg}-$(identify -format '%wx%h' resized/temp.png).webp"
20+
21+
# Clean up
22+
rm resized/temp.png
23+
done
7.93 KB
Binary file not shown.
67.3 KB
Binary file not shown.
6.98 KB
Binary file not shown.
Binary file not shown.
6.22 KB
Binary file not shown.
Binary file not shown.
6.55 KB
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
91.3 KB
Binary file not shown.
11.4 KB
Binary file not shown.
Binary file not shown.
8.14 KB
Binary file not shown.
Binary file not shown.
7.16 KB
Binary file not shown.
Binary file not shown.
Binary file not shown.
10.3 KB
Binary file not shown.
Binary file not shown.
12.9 KB
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
9.83 KB
Binary file not shown.
Binary file not shown.
Binary file not shown.
11.9 KB
Binary file not shown.
13.5 KB
Binary file not shown.
6.6 KB
Binary file not shown.
9.19 KB
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
79.2 KB
Binary file not shown.
13.2 KB
Binary file not shown.

Diff for: source/scp-head/convert-shorthand.cjs

+154
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
#!/usr/bin/env node
2+
3+
const patterns = {
4+
inlineStatement: /(?:\{.*?\})+/g,
5+
blockStatement: /(?:\{\{\{[\s\S]*?\}\}\}\n?)+/g,
6+
contradiction: /\{=[A-Z0-9?^,]+\}/g,
7+
};
8+
const defaultChannel = "B";
9+
10+
if (require.main === module) main();
11+
12+
async function main() {
13+
let source = "";
14+
for await (const chunk of process.stdin) source += chunk;
15+
16+
if (!source || !process.env.SCOUTFILE) {
17+
if (!source) console.error("No input source");
18+
if (!process.env.SCOUTFILE) console.error("No scout file specified");
19+
console.error(
20+
"Usage: (export SCOUTFILE=/scout.html; cat sourcefile | ./convert-shorthand.cjs)"
21+
);
22+
process.exit(1);
23+
}
24+
25+
source = replaceShorthand(source, process.env.SCOUTFILE);
26+
27+
process.stdout.write(source);
28+
}
29+
30+
module.exports = { replaceShorthand };
31+
32+
/**
33+
* Replaces shorthand syntax with wikitext.
34+
*
35+
* @param {string} source
36+
* @param {string} scoutFile - URL of the scout iframe
37+
* @returns {string}
38+
*/
39+
function replaceShorthand(source, scoutFile) {
40+
return source
41+
.replace(
42+
/(SCP-[0-9]{4}-[0-9])\[([a-z]+)\]/g,
43+
"{A1 croqstyle-img-footnote img-head-$2-angle: $1}{A6 croqstyle-img-footnote img-head-$2-straight: $1}{A7 croqstyle-img-footnote img-head-$2-angle: $1}"
44+
)
45+
.replace(patterns.contradiction, (match) =>
46+
replaceContradiction(match, scoutFile)
47+
)
48+
.replace(patterns.inlineStatement, replaceInlineStatement)
49+
.replace(/\[\[tt\]\]/g, "{{")
50+
.replace(/\[\[\/tt\]\]/g, "}}")
51+
.trim();
52+
}
53+
54+
/**
55+
* Replaces inline statement shorthand with markup.
56+
*
57+
* @param {string} match - Matched inline statement
58+
* @returns {string} - Converted inline statement
59+
*/
60+
function replaceInlineStatement(match) {
61+
console.error("Replacing inline statement", match);
62+
const assertions = parseStatement(match.split(/{|}/));
63+
return [
64+
`[[span class="ch_${assertions[0].channel}"]]`,
65+
...assertions
66+
.sort((a, b) => b.stage - a.stage)
67+
.map((assertion) => {
68+
if (assertion.text.trim().length === 0) {
69+
// Zero-content spans are invalid wikitext - add a space and hide it later with a class
70+
return `[[span class="a_${assertion.stage} empty"]]_[[/span]]`;
71+
}
72+
73+
return `[[span class="a_${assertion.stage}${
74+
assertion.classes ? " " + assertion.classes.trim() : ""
75+
}"]]${assertion.text}[[/span]]`;
76+
}),
77+
"[[/span]]",
78+
].join("");
79+
}
80+
81+
/**
82+
* Replaces contradiction shorthand with corresponding scout.
83+
*
84+
* @param {string} match
85+
* @param {string} scoutFile - URL of the scout iframe
86+
* @returns {string}
87+
*/
88+
function replaceContradiction(match, scoutFile) {
89+
console.error("Replacing contradiction", match);
90+
const conditionPattern = /[A-Z]?[0-9]+/;
91+
const singleContradictionPattern = new RegExp(
92+
conditionPattern.source + "(?:\\?" + conditionPattern.source + ")?\\^?"
93+
);
94+
const multiConditionPattern = new RegExp(
95+
singleContradictionPattern.source +
96+
"(?:," +
97+
singleContradictionPattern.source +
98+
")*"
99+
);
100+
const contradictionShorthandPattern = new RegExp(
101+
"^\\{=" + multiConditionPattern.source + "\\}$"
102+
);
103+
if (!contradictionShorthandPattern.test(match)) {
104+
console.error({ match });
105+
throw new Error("Invalid contradiction");
106+
}
107+
const contradictions = match.slice(2, -1);
108+
return [
109+
"[[embed]]",
110+
`<iframe src="${scoutFile}?contradiction=${contradictions}" loading="lazy" class="scout"></iframe>`,
111+
"[[/embed]]",
112+
].join("\n");
113+
}
114+
115+
/**
116+
* Parses a statement to an assertions list.
117+
*
118+
* @param {Array.<string>} phrases - Text source of each phrase of the statement
119+
* @returns {Array.<{ stage: number, text: string }>} - List of assertions
120+
*/
121+
function parseStatement(phrases) {
122+
let statementChannel = null;
123+
const assertions = phrases
124+
.filter((s) => /\S/.test(s))
125+
.map((assertion) => {
126+
console.error("Parsing assertion", JSON.stringify(assertion));
127+
const match = assertion.match(
128+
/(?:\s*([A-Z]?)([0-9]*)(?:( [ a-zA-Z0-9_-]+))?:\n?\s*)?([\s\S]*?)$/
129+
);
130+
const channel = String(match[1] || defaultChannel);
131+
const stage = Number(match[2]) || 10;
132+
const classes = String(match[3] || "");
133+
const text = String(match[4] || "");
134+
135+
if (statementChannel === null) statementChannel = channel;
136+
else if (statementChannel !== channel) {
137+
console.error({ match, statementChannel, channel, stage, text });
138+
throw new Error("Inconsistent channel in statement");
139+
}
140+
return { stage, classes, text, channel: statementChannel };
141+
});
142+
143+
// Verify that all assertions have unique stages
144+
const stages = new Set();
145+
assertions.forEach((assertion) => {
146+
if (stages.has(assertion.stage)) {
147+
console.error({ assertion, assertions, phrases });
148+
throw new Error("Duplicate stage in statement");
149+
}
150+
stages.add(assertion.stage);
151+
});
152+
153+
return assertions;
154+
}

Diff for: source/scp-head/convert-shorthand.spec.cjs

+131
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
#!/usr/bin/env node
2+
3+
const { replaceShorthand } = require("./convert-shorthand.cjs");
4+
5+
function verify(it) {
6+
for (let [description, [source, expected]] of Object.entries(it)) {
7+
console.log(`\t - it ${description} `);
8+
source = source.replace(/\n +/g, "\n").trim();
9+
let result;
10+
try {
11+
result = replaceShorthand(source, "/scout.html");
12+
} catch (err) {
13+
if (expected === Error) {
14+
continue;
15+
}
16+
console.log(`Input: ${source}`);
17+
throw err;
18+
}
19+
expected = expected.replace(/\n +/g, "\n").trim();
20+
if (result === expected) {
21+
continue;
22+
}
23+
console.log(`Input: ${source}`);
24+
console.log(`Expected: ${expected}`);
25+
console.log(`Got: ${result}`);
26+
throw new Error("Test failed");
27+
}
28+
}
29+
30+
const it = {
31+
"doesn't convert stuff that's not shorthand": [`Normal text`, `Normal text`],
32+
"converts inline statements": [
33+
`{A10: A}{A11: B}{A12: C}`,
34+
`[[span class="ch_A"]][[span class="a_12"]]C[[/span]][[span class="a_11"]]B[[/span]][[span class="a_10"]]A[[/span]][[/span]]`,
35+
],
36+
"assumes default channel if none is specified": [
37+
`{10: A}{11: B}`,
38+
`[[span class="ch_B"]][[span class="a_11"]]B[[/span]][[span class="a_10"]]A[[/span]][[/span]]`,
39+
],
40+
"fails if assertions do not share a channel": [`{A10: A}{B11: B}`, Error],
41+
"fails if assertions share a stage": [`{10: A}{10: B}`, Error],
42+
"converts multiple separate inline statements": [
43+
`test {A}{11: B} test {C}{12: D} test`,
44+
`test [[span class="ch_B"]][[span class="a_11"]]B[[/span]][[span class="a_10"]]A[[/span]][[/span]] test [[span class="ch_B"]][[span class="a_12"]]D[[/span]][[span class="a_10"]]C[[/span]][[/span]] test`,
45+
],
46+
"can create an empty assertion": [
47+
`{test7}{28:}`,
48+
`[[span class="ch_B"]][[span class="a_28 empty"]]_[[/span]][[span class="a_10"]]test7[[/span]][[/span]]`,
49+
],
50+
"doesn't fuck up syntax inside/outside block statements": [
51+
`[[span]]test[[/span]]
52+
53+
[[div class="ch_B"]]
54+
[[div class="a_11"]]
55+
B
56+
57+
[[div class="test"]]
58+
[[/div]]
59+
[[/div]]
60+
[[div class="a_10"]]
61+
A
62+
[[/div]]
63+
[[/div]]
64+
65+
[[span]]test[[/span]]`,
66+
`[[span]]test[[/span]]
67+
68+
[[div class="ch_B"]]
69+
[[div class="a_11"]]
70+
B
71+
72+
[[div class="test"]]
73+
[[/div]]
74+
[[/div]]
75+
[[div class="a_10"]]
76+
A
77+
[[/div]]
78+
[[/div]]
79+
80+
[[span]]test[[/span]]`,
81+
],
82+
"supports inline statements inside block statements": [
83+
`[[div class="ch_B"]]
84+
[[div class="a_25"]]
85+
test4 {test5}{11: test6}
86+
[[/div]]
87+
[[div class="a_20"]]
88+
test3
89+
[[/div]]
90+
[[div class="a_10"]]
91+
test2
92+
[[/div]]
93+
[[/div]]`,
94+
`[[div class="ch_B"]]
95+
[[div class="a_25"]]
96+
test4 [[span class="ch_B"]][[span class="a_11"]]test6[[/span]][[span class="a_10"]]test5[[/span]][[/span]]
97+
[[/div]]
98+
[[div class="a_20"]]
99+
test3
100+
[[/div]]
101+
[[div class="a_10"]]
102+
test2
103+
[[/div]]
104+
[[/div]]`,
105+
],
106+
"lets you put separate inline statements near each other": [
107+
`{10:}{11:}{12:} {A10:}{A11:}`,
108+
`[[span class="ch_B"]][[span class="a_12 empty"]]_[[/span]][[span class="a_11 empty"]]_[[/span]][[span class="a_10 empty"]]_[[/span]][[/span]] [[span class="ch_A"]][[span class="a_11 empty"]]_[[/span]][[span class="a_10 empty"]]_[[/span]][[/span]]`,
109+
],
110+
"converts contradictions right": [
111+
`{=10}
112+
{=11}
113+
{=12^}
114+
{=13
115+
{=14?15^,16}`,
116+
`[[embed]]
117+
<iframe src="/scout.html?contradiction=10" loading="lazy"></iframe>
118+
[[/embed]]
119+
[[embed]]
120+
<iframe src="/scout.html?contradiction=11" loading="lazy"></iframe>
121+
[[/embed]]
122+
[[embed]]
123+
<iframe src="/scout.html?contradiction=12^" loading="lazy"></iframe>
124+
[[/embed]]
125+
{=13
126+
[[embed]]
127+
<iframe src="/scout.html?contradiction=14?15^,16" loading="lazy"></iframe>
128+
[[/embed]]`,
129+
],
130+
};
131+
verify(it);

Diff for: source/scp-head/package.json

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"version": "0.0.0",
3+
"scripts": {
4+
"build:cleanup": "rm -rf dist && mkdir dist",
5+
"build:sass": "sass src/assertions.scss dist/assertions-v$(echo $npm_package_version | cut -d. -f1).css",
6+
"build:minify": "csso dist/assertions-v$(echo $npm_package_version | cut -d. -f1).css -o dist/assertions-v$(echo $npm_package_version | cut -d. -f1).css",
7+
"build:html": "for file in src/*.html; do cp $file dist/$(basename ${file%.html})-v$(echo $npm_package_version | cut -d. -f1).html; done",
8+
"build:assets": "cp assets/resized/*.webp dist",
9+
"build": "npm run build:cleanup && npm run build:sass && npm run build:minify && npm run build:html && npm run build:assets",
10+
"dev": "npm run build:cleanup && npm run build:sass && npm run build:html && npm run build:assets"
11+
},
12+
"devDependencies": {
13+
"csso-cli": "^4.0.2",
14+
"sass": "^1.77.2"
15+
}
16+
}

0 commit comments

Comments
 (0)