-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Printable cheat sheet #2074
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
dscho
wants to merge
13
commits into
git:gh-pages
Choose a base branch
from
dscho:printable-cheat-sheet
base: gh-pages
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Printable cheat sheet #2074
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
1e4f2ac
assets/sass: use correct filename suffix
dscho 5ca38d1
Add a print-specific style sheet
dscho ec9f284
print: handle rounded borders correctly with multiple pages
dscho e31301c
Show a different tagline for print media
dscho a229edb
Hide the dark mode button when printing
dscho 0cee613
Cheat Sheet: define font sizes relative to container, not root
dscho 8b268e2
cheat-sheet: adjust the print style-sheet
dscho b9eb7d4
Add a script to convert HTML to PDF
dscho aec7dfa
html-to-pdf: optionally insert PDF link
dscho df355ea
html-to-pdf: deal with HUGO_RELATIVEURLS=false
dscho 9606cbf
html-to-pdf: work around minified `application.{css,js}`
dscho 033f00c
html-to-pdf: optionally start Chromium with DevTools
dscho cffeb19
deploy/ci: render the cheat sheet as PDF and offer a link to it
dscho File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
File renamed without changes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
@media print { | ||
.inner { | ||
// The `position` of the `inner` class is defined as `relative`, which | ||
// causes funny issues when printing, for example tens of empty pages in | ||
// the middle. Let's suppress that. | ||
position: inherit; | ||
|
||
width: inherit; | ||
margin-bottom: 0; | ||
} | ||
|
||
#main { | ||
margin-bottom: 0; | ||
|
||
/* Improve readability */ | ||
line-height: 1.4; | ||
text-align: justify; | ||
|
||
/* Ensure it uses full page width */ | ||
width: 100%; | ||
border: none; | ||
padding: 0; | ||
} | ||
|
||
footer { | ||
padding: 0; | ||
margin-top: 0; | ||
} | ||
|
||
aside, .sidebar-btn, #search-container, #reference-version, #dark-mode-button, .site-source { | ||
display: none; | ||
} | ||
|
||
section { | ||
break-inside: avoid-page; | ||
} | ||
|
||
div#main { | ||
box-decoration-break: clone; | ||
} | ||
|
||
.pdf-link, #dark-mode-button { | ||
display: none !important; | ||
} | ||
|
||
.cheat-sheet { | ||
font-size: 8pt; | ||
|
||
section { | ||
border: 1px solid black; | ||
display: block; | ||
gap: 0; | ||
margin-bottom: 0; | ||
|
||
.item { | ||
background: none; | ||
border: none; | ||
|
||
h3 { | ||
margin-bottom: 0; | ||
} | ||
|
||
code { | ||
background: none; | ||
line-height: inherit; | ||
padding: 1px; | ||
} | ||
} | ||
} | ||
.commit-reference { | ||
background: none; | ||
border-left: 0.5px; | ||
|
||
dl { | ||
gap: inherit; | ||
|
||
dt,dt::before,dd { | ||
line-height: inherit; | ||
font-size: 0.7em !important; | ||
} | ||
} | ||
} | ||
|
||
p { | ||
font-size: inherit; | ||
line-height: inherit; | ||
margin: 1px; | ||
} | ||
} | ||
} | ||
|
||
@media print and (min-width: 231mm) { | ||
div#content { | ||
width: 297mm; | ||
} | ||
|
||
header { | ||
padding: 0; | ||
#logo img { | ||
max-height: 23px; | ||
width: auto; | ||
height: auto; | ||
} | ||
} | ||
|
||
/* landscape orientation */ | ||
@page { | ||
size: A4 landscape; | ||
width: 297mm; | ||
height: 210mm; | ||
margin: 1cm; | ||
} | ||
|
||
.cheat-sheet { | ||
/* Multi-column layout */ | ||
column-count: 6; | ||
column-gap: 1em; | ||
|
||
h1 { | ||
column-span: all; | ||
position: absolute; | ||
top: -10px; | ||
width: 100%; | ||
font-size: 16px; | ||
text-align: center; | ||
} | ||
|
||
div.item { | ||
display: block; | ||
width: 100%; | ||
padding: 1px; | ||
margin-bottom: 1px; | ||
break-inside: avoid; | ||
|
||
label { | ||
margin-top: 2px; | ||
} | ||
} | ||
} | ||
} | ||
|
||
div#main .pdf-link img { | ||
float: right; | ||
height: 36px; | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
#!/usr/bin/env node | ||
|
||
const fs = require('fs') | ||
const path = require('path') | ||
const url = require('url') | ||
const { chromium } = require('playwright') | ||
|
||
const insertPDFLink = (htmlPath) => { | ||
const html = fs.readFileSync(htmlPath, 'utf-8') | ||
if (html.includes('class="pdf-link"')) { | ||
return | ||
} | ||
// get baseURL prefix via the `favicon.ico` link, it's in the top-level directory | ||
const match = html.match(/<link href="(.*?)favicon\.ico"/) | ||
if (!match) throw new Error('Failed to determine baseURL prefix from favicon.ico link') | ||
const img = `<img src="${match[1].replace(/\/$/, '')}/images/pdf.png" />` | ||
const updatedHtml = html.replace( | ||
/<h1/, | ||
`<a class="pdf-link" href="${path.basename(htmlPath, '.html')}.pdf">${img}</a>$&` | ||
) | ||
if (updatedHtml === html) throw new Error('Failed to insert PDF link, no <h1> found') | ||
fs.writeFileSync(htmlPath, updatedHtml, 'utf-8') | ||
} | ||
|
||
const htmlToPDF = async (htmlPath, options) => { | ||
if (!htmlPath.endsWith('.html')) { | ||
throw new Error(`Input file must have the '.html' extension: ${htmlPath}`) | ||
} | ||
if (!fs.existsSync(htmlPath)) { | ||
throw new Error(`Input file does not exist: ${htmlPath}`) | ||
} | ||
const outputPath = htmlPath.replace(/\.html$/, '.pdf') | ||
if (!options.force && fs.existsSync(outputPath)) { | ||
throw new Error(`Output file already exists: ${outputPath}`) | ||
} | ||
|
||
const browser = await chromium.launch({ channel: 'chrome', ...(options.devtools ? { devtools: true } : {}) }) | ||
const page = await browser.newPage() | ||
|
||
const htmlPathURL = url.pathToFileURL(htmlPath).toString() | ||
console.log(`Processing ${htmlPathURL}...`) | ||
|
||
// Work around HUGO_RELATIVEURLS=false by rewriting the absolute URLs | ||
const baseURLPrefix = htmlPathURL.substring(0, htmlPathURL.lastIndexOf('/public/') + 8) | ||
await page.route(/^file:\/\//, async (route, req) => { | ||
// This _will_ be a correct URL when deployed to https://whatevers/, but | ||
// this script runs before deployment, on a file:/// URL, where we need to | ||
// be a bit clever to give the browser the file it needs. | ||
const original = req.url() | ||
if (original === htmlPathURL) { | ||
// Work around rerouted `.css` and `.js` files... Symptom: "has an | ||
// integrity attribute, but the resource requires the request to be CORS | ||
// enabled to check the integrity, and it is not. The resource has been | ||
// blocked because the integrity cannot be enforced." | ||
const body = | ||
fs.readFileSync(htmlPath, "utf-8") | ||
// strip out the `integrity="sha256-..."` attributes | ||
.replace(/(\/application\.[^"/]+") integrity="sha256-[^"]+"/g, "$1") | ||
await route.fulfill({ headers: { "Content-Type": "text/html" }, body }) | ||
return | ||
} | ||
|
||
const url = | ||
original.startsWith(baseURLPrefix) | ||
? original | ||
: original.replace(/^file:\/\/\/([A-Za-z]:\/)?(git-scm\.com\/)?/, baseURLPrefix) | ||
if (url !== original) console.error(`::notice::Rewrote ${original} to ${url}`) | ||
await route.continue({ url }) | ||
}) | ||
|
||
await page.goto(htmlPathURL, { waitUntil: 'load' }) | ||
|
||
await page.pdf({ | ||
path: outputPath, | ||
format: 'A4', | ||
landscape: true, | ||
margin: { top: '0cm', bottom: '0cm', left: '0cm', right: '0cm' }, | ||
}) | ||
if (options.devtools) await new Promise((resolve) => { setTimeout(resolve, 5 * 60 * 1000) }) | ||
await browser.close() | ||
|
||
if (options.insertPDFLink) insertPDFLink(htmlPath) | ||
} | ||
|
||
const args = process.argv.slice(2) | ||
const options = {} | ||
while (args?.[0].startsWith('-')) { | ||
const arg = args.shift() | ||
if (arg === '--force' || arg === '-f') options.force = true | ||
else if (arg === '--insert-pdf-link' || arg === '-i') options.insertPDFLink = true | ||
else if (arg === '--devtools' || arg === '-d') options.devtools = true | ||
else throw new Error(`Unknown argument: ${arg}`) | ||
} | ||
|
||
if (args.length !== 1) { | ||
process.stderr.write('Usage: html-to-pdf.js [--force] [--insert-pdf-link] <input-file.html>\n') | ||
process.exit(1) | ||
} | ||
|
||
htmlToPDF(args[0], options).catch(e => { | ||
process.stderr.write(`${e.stack}\n`) | ||
process.exit(1) | ||
}) |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how do you think about putting this code in the print stylesheet vs the
cheat-sheet.scss
style sheet?I had a similar question about dark mode stuff -- for me if I'm editing the "cheat sheet" page it feels easier if all of the CSS for that page (dark mode, print, etc) are in the same place. But I think there are some advantages to having a "dark mode" or "print" stylesheet that have all of the dark mode print settings that I'm not thinking of.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it makes sense to me to move the cheat-sheet specific stuff into
cheat-sheet.scss
, and leave the generic print-related stuff inprint.scss
.