En bref
Les deux mêmes paragraphes sur la typographie, avec les mêmes sources, imprimés quatre fois. Chaque page cite les sources à sa façon, et les citations comme la liste finale changent avec elle, alors que le texte n’a été écrit qu’une fois.
Ce que vous allez composer
Un essai de deux paragraphes sur la raison pour laquelle la bonne typographie passe inaperçue, avec six sources : une citation narrative, une avec une page, une qui omet l’auteur parce que la phrase le nomme, et deux ouvrages cités ensemble. Le texte est composé quatre fois sur une page de 155 × 235 mm, et la seule différence entre les quatre compositions est citations.style : APA 7, Chicago auteur-date, IEEE et ISO 690. Chaque page porte le nom du style dans un bandeau de sa propre couleur ; posées côte à côte, elles se lisent comme un tableau comparatif. Les citations changent de forme, d’ordre et de ponctuation, la liste change de tri et de forme des noms, et l’essai lui-même reste le même fichier.
Cette recette répond à
- Comment faire passer un livre d’un style de citation à un autre sans toucher au texte ?
- Comment citer des ouvrages et construire la bibliographie en APA, en IEEE ou dans un autre style de citation ?
La réponse courte
// Citations are written once, Pandoc's way: [@key], [@a; @b], [-@key] without the author,
// @key [p. 17] inside the sentence. citeproc-js writes them, and the list, in the style named.
registerCitationEngine(createCiteprocEngine({ styles: STYLES, locales: LOCALES }));
const SHOWN = [ // the same text, built once per entry
{ style: 'apa', band: 'apa', name: 'APA 7', system: t({ en: 'author-date', es: 'autor-año' }) },
{ style: 'chicago-author-date', band: 'chicago', name: 'Chicago',
system: t({ en: 'author-date', es: 'autor-año' }) },
{ style: 'ieee', band: 'ieee', name: 'IEEE', system: t({ en: 'numbered', es: 'numérico' }) },
{ style: t({ en: 'iso690-author-date-en', es: 'iso690-author-date-es' }), band: 'iso',
name: 'ISO 690', system: t({ en: 'author-date', es: 'autor-año' }) },
];
const citations = (style) => ({
style, // 'apa' → '(Warde, 1955)'; 'ieee' → '[1]'; the list is sorted and set to match
link: true,
// The authors' names stay whole in the citations, the text and the list: the APA build
// set '(Lup-' | 'ton, 2010)'.
hyphenateNames: false,
bibliography: { fontSize: em(0.85), lineHeight: pt(11.2), hangingIndent: mm(6),
entrySpacing: pt(2), labelWidth: mm(6), doi: 'hide' },
});
Ingrédients
- Fonctionnalités
- Citations dans un style bibliographiqueBibliographie tirée des référencesOuvertures composéesPalette de couleurs sémantique
- Typographie
- Source Serif 4, Source Sans 3 (SIL OFL 1.1)
- Ressources
- Aucune : chaque image est dessinée en code
Préparation
#1 · Un seul réglage
Le code est la réponse courte ci-dessus. Les citations sont écrites une fois dans le contenu, dans la syntaxe de Pandoc, et les références se trouvent dans un bloc BibTeX à la fin. citations.style désigne un style CSL, et citeproc-js en tire chaque citation et chaque entrée : APA met une virgule avant l’année et trie deux ouvrages par auteur, Chicago garde l’ordre dans lequel ils ont été écrits, IEEE les transforme en numéros dans l’ordre de première citation, ISO 690 met les noms de famille de la liste en capitales. Pour un livre en cours, c’est tout le changement : une valeur dans la configuration, et le texte n’est pas touché.
#2 · Quatre compositions, deux doubles pages
// The style is set for the whole document, so each style is a build of its own.
const docs = [];
for (at = 0; at < SHOWN.length; at++) {
// pageIndexOffset makes each page the next one of a book, so the desk pairs them:
// APA beside Chicago, IEEE beside ISO 690.
const content = { markdown, continuation: { pageIndexOffset: at + 1 } };
docs.push(await buildDocumentWithFonts(content, config(), kitFonts(FONTS)));
}
Le style vaut pour tout le document : les quatre versions sont donc quatre compositions du même Markdown. Chacune reçoit un pageIndexOffset supérieur d’une unité au précédent, ce qui fait de sa page la page suivante d’un livre imaginaire ; la table de travail les réunit ensuite en deux doubles pages, en plaçant côte à côte les deux styles auteur-date qui diffèrent le moins.
#3 · Le nom du style dans un bandeau
const opener = (s, i) => ({
enabled: true,
minHeight: mm(41), // from the top margin to under the title, 63 mm from the trim
slot: { elements: [
{ kind: 'box', id: 'band', style: { backgroundColor: col(s.band) },
placement: { anchor: { to: 'page', edge: 'top-left' },
size: { width: 'fill', height: mm(44) } } },
{ kind: 'text', id: 'kicker', content: `${t({ en: 'Citation style', es: 'Estilo de cita' })}`
+ ` · ${i + 1} / ${SHOWN.length} · ${s.system}`, fontFamily: SANS, fontSize: pt(8),
fontWeight: 600, letterSpacing: pt(1.4), textTransform: 'uppercase', color: col('paper'),
align: 'left',
placement: { anchor: { to: 'page', edge: 'top-left' }, offset: { x: mm(18), y: mm(12) } } },
{ kind: 'text', id: 'name', content: s.name, fontFamily: SERIF, fontSize: pt(54),
fontWeight: 600, lineHeight: 1, color: col('paper'), align: 'left', overflow: 'clip',
placement: { anchor: { to: '#kicker', edge: 'below' }, offset: { x: mm(0), y: mm(5) },
size: { width: mm(119), height: 'auto' } } },
{ kind: 'text', id: 'title', content: '{titleText}', fontFamily: SERIF, fontSize: pt(20),
fontWeight: 600, lineHeight: 1.15, color: col('ink'), align: 'left', overflow: 'wrap',
placement: { anchor: { to: 'page', edge: 'top-left' }, offset: { x: mm(18), y: mm(52) },
size: { width: mm(119), height: 'auto' } } },
] },
});
La maquette du titre dessine le bandeau depuis le bord du format, le surtitre avec le numéro et le système du style, et le nom en 54 pt. Le nom et le système viennent du script, pas du Markdown, puisque le texte est le même dans les quatre documents.
const palette = {
ink: '#1d1b19', muted: '#66605a', rule: '#cfc8bf', paper: '#ffffff',
apa: '#2f5d8a', chicago: '#8c2f2b', ieee: '#24476b', iso: '#3c6b4f', // the four bands
};
const col = (id) => ({ hex: palette[id], model: 'hex', paletteId: id });
// Each document points main-color at its own band, so any default left follows it.
const colorPalette = (band) => Object.entries({ ...palette, 'main-color': palette[band] })
.map(([id, hex]) => ({ id, name: id, value: { hex, model: 'hex' } }));
Chaque document fait pointer main-color vers son propre bandeau : les quatre couleurs sont des entrées de palette liées, et toute valeur par défaut que la configuration ne précise pas suit le bandeau de sa page.
La recette complète
// ═══ Postext Cookbook · Nº 093 · One text in four citation styles ════════════════ // https://postext.dev/en/cookbook/one-text-four-citation-styles // Code: MIT · Text: original (CC BY 4.0) · Pictures: none // Fonts: Source Serif 4, Source Sans 3 (SIL OFL 1.1) · Needs postext ≥ 1.28.0 import { buildDocumentWithFonts, renderPageToCanvas, registerCitationEngine, } from 'https://esm.sh/postext'; import { createCiteprocEngine, STYLES, LOCALES } from 'https://esm.sh/postext-citeproc'; const LANG = 'en'; // @lang: the language of the sample document ('en' | 'es') const RECIPE = 'one-text-four-citation-styles'; // ─── 1 · Design ───────────────────────────────────────────────────────────── // #region palette: one ink, and a colour for each style's band const palette = { ink: '#1d1b19', muted: '#66605a', rule: '#cfc8bf', paper: '#ffffff', apa: '#2f5d8a', chicago: '#8c2f2b', ieee: '#24476b', iso: '#3c6b4f', // the four bands }; const col = (id) => ({ hex: palette[id], model: 'hex', paletteId: id }); // Each document points main-color at its own band, so any default left follows it. const colorPalette = (band) => Object.entries({ ...palette, 'main-color': palette[band] }) .map(([id, hex]) => ({ id, name: id, value: { hex, model: 'hex' } })); // #endregion const [SERIF, SANS] = ['Source Serif 4', 'Source Sans 3']; const LEAD = 14; // pt // #region answer: the style is one setting; everything else stays the same // Citations are written once, Pandoc's way: [@key], [@a; @b], [-@key] without the author, // @key [p. 17] inside the sentence. citeproc-js writes them, and the list, in the style named. registerCitationEngine(createCiteprocEngine({ styles: STYLES, locales: LOCALES })); const SHOWN = [ // the same text, built once per entry { style: 'apa', band: 'apa', name: 'APA 7', system: t({ en: 'author-date', es: 'autor-año' }) }, { style: 'chicago-author-date', band: 'chicago', name: 'Chicago', system: t({ en: 'author-date', es: 'autor-año' }) }, { style: 'ieee', band: 'ieee', name: 'IEEE', system: t({ en: 'numbered', es: 'numérico' }) }, { style: t({ en: 'iso690-author-date-en', es: 'iso690-author-date-es' }), band: 'iso', name: 'ISO 690', system: t({ en: 'author-date', es: 'autor-año' }) }, ]; const citations = (style) => ({ style, // 'apa' → '(Warde, 1955)'; 'ieee' → '[1]'; the list is sorted and set to match link: true, // The authors' names stay whole in the citations, the text and the list: the APA build // set '(Lup-' | 'ton, 2010)'. hyphenateNames: false, bibliography: { fontSize: em(0.85), lineHeight: pt(11.2), hangingIndent: mm(6), entrySpacing: pt(2), labelWidth: mm(6), doi: 'hide' }, }); // #endregion // #region band: the style's name, large, in a band of its colour const opener = (s, i) => ({ enabled: true, minHeight: mm(41), // from the top margin to under the title, 63 mm from the trim slot: { elements: [ { kind: 'box', id: 'band', style: { backgroundColor: col(s.band) }, placement: { anchor: { to: 'page', edge: 'top-left' }, size: { width: 'fill', height: mm(44) } } }, { kind: 'text', id: 'kicker', content: `${t({ en: 'Citation style', es: 'Estilo de cita' })}` + ` · ${i + 1} / ${SHOWN.length} · ${s.system}`, fontFamily: SANS, fontSize: pt(8), fontWeight: 600, letterSpacing: pt(1.4), textTransform: 'uppercase', color: col('paper'), align: 'left', placement: { anchor: { to: 'page', edge: 'top-left' }, offset: { x: mm(18), y: mm(12) } } }, { kind: 'text', id: 'name', content: s.name, fontFamily: SERIF, fontSize: pt(54), fontWeight: 600, lineHeight: 1, color: col('paper'), align: 'left', overflow: 'clip', placement: { anchor: { to: '#kicker', edge: 'below' }, offset: { x: mm(0), y: mm(5) }, size: { width: mm(119), height: 'auto' } } }, { kind: 'text', id: 'title', content: '{titleText}', fontFamily: SERIF, fontSize: pt(20), fontWeight: 600, lineHeight: 1.15, color: col('ink'), align: 'left', overflow: 'wrap', placement: { anchor: { to: 'page', edge: 'top-left' }, offset: { x: mm(18), y: mm(52) }, size: { width: mm(119), height: 'auto' } } }, ] }, }); // #endregion let at = 0; // the entry of SHOWN being built const config = () => ({ locale: t({ en: 'en-us', es: 'es' }), colorPalette: colorPalette(SHOWN[at].band), citations: citations(SHOWN[at].style), page: { sizePreset: 'custom', width: mm(155), height: mm(235), dpi: 150, margins: { top: mm(22), bottom: mm(20), left: mm(18), right: mm(18) } }, layout: { layoutType: 'single' }, bodyText: { fontFamily: SERIF, fontSize: pt(10), lineHeight: pt(LEAD), color: col('ink'), boldColor: col('ink'), italicColor: col('ink'), referenceColor: col('ink'), textAlign: 'justify', firstLineIndent: mm(4.5), indentAfterHeading: false, hyphenation: { enabled: true }, optimalLineBreaking: true, avoidWidows: true, avoidOrphans: true, avoidRunts: true, }, headings: { fontFamily: SERIF, color: col('ink'), levels: [ { level: 1, breakBefore: { enabled: true, parity: 'any' }, // the essay opens a page advancedDesign: opener(SHOWN[at], at) }, ] }, header: { elements: [] }, footer: { elements: [ { kind: 'text', id: 'colophon', content: t({ en: 'Set in Source Serif 4 and Source Sans 3 (SIL OFL) · Text: original, CC BY 4.0', es: 'Compuesto en Source Serif 4 y Source Sans 3 (SIL OFL) · Texto: original, CC BY 4.0' }), fontFamily: SANS, fontSize: pt(7), color: col('muted'), placement: { anchor: { to: 'page', edge: 'bottom-left' }, offset: { x: mm(18), y: mm(-11) } } }, ] }, }); // ─── 2 · Content ──────────────────────────────────────────────────────────── const markdown = String.raw`---Exemple Markdown · 33 lignes · content.en.md
title: "The invisible page" author: "Postext Cookbook" --- # The page that is not seen Good typography is meant to go unnoticed. @warde1955 put the idea in an image that printers still quote: a page should be a crystal goblet, a clear glass that shows the wine, rather than a cup of gold that hides it. Tschichold argued much the same about the book, which in his essays serves its reader before it serves its designer [-@tschichold1991]. The argument is moral as much as practical, and @bringhurst2004 [p. 17] makes it the first principle of his manual: typography exists to honour the content. The practical half has evidence behind it. Tests of legibility measured how the size of the type, the length of the line and the leading change the speed of reading, and the study of eye movements later explained why an even texture matters: the eye takes in a few letters at each fixation and plans its next jump from the shape of the line ahead [@tinker1963; @rayner1998]. A page that draws attention to itself costs the reader something, however little. None of this makes design a matter of taste alone. A reader who never notices a well-made page notices a badly made one at once, and the grammar of a good one can be taught [@lupton2010]. :::references{format=bibtex} @book{warde1955, author = {Warde, Beatrice}, title = {The Crystal Goblet: Sixteen Essays on Typography}, editor = {Jacob, Henry}, publisher = {Sylvan Press}, address = {London}, year = 1955} @book{tschichold1991, author = {Tschichold, Jan}, title = {The Form of the Book: Essays on the Morality of Good Design}, translator = {Hadeler, Hajo}, publisher = {Hartley \& Marks}, address = {Point Roberts, WA}, year = 1991} @book{bringhurst2004, author = {Bringhurst, Robert}, title = {The Elements of Typographic Style}, edition = {3}, publisher = {Hartley \& Marks}, address = {Point Roberts, WA}, year = 2004} @book{tinker1963, author = {Tinker, Miles A.}, title = {Legibility of Print}, publisher = {Iowa State University Press}, address = {Ames, IA}, year = 1963} @article{rayner1998, author = {Rayner, Keith}, title = {Eye movements in reading and information processing: 20 years of research}, journal = {Psychological Bulletin}, volume = 124, number = 3, pages = {372--422}, year = 1998, doi = {10.1037/0033-2909.124.3.372}} @book{lupton2010, author = {Lupton, Ellen}, title = {Thinking with Type}, edition = {2}, publisher = {Princeton Architectural Press}, address = {New York}, year = 2010} :::`; // ─── 3 · Fonts ────────────────────────────────────────────────────────────── const FONTS = { 'Source Serif 4': ['400', '400i', '600', '600i', '700', '700i'], // 700: the list's title 'Source Sans 3': ['400', '600'], }; // ─── 4 · Build & show ─────────────────────────────────────────────────────── // #region build: one text, four documents, shown as two spreads // The style is set for the whole document, so each style is a build of its own. const docs = []; for (at = 0; at < SHOWN.length; at++) { // pageIndexOffset makes each page the next one of a book, so the desk pairs them: // APA beside Chicago, IEEE beside ISO 690. const content = { markdown, continuation: { pageIndexOffset: at + 1 } }; docs.push(await buildDocumentWithFonts(content, config(), kitFonts(FONTS))); } // #endregion showPages(docs, { title: t({ en: 'One text, four styles', es: 'Un texto, cuatro estilos' }) });Kit · core, fonts, viewer : identique dans toutes les recettes · 224 lignes
// ─── Kit ── helpers shared by every Cookbook recipe · postext.dev/cookbook ───── // ─── Kit · core v1 ── the same in every recipe · postext.dev/cookbook function mm(value) { return { value, unit: 'mm' }; } function pt(value) { return { value, unit: 'pt' }; } function em(value) { return { value, unit: 'em' }; } /** The sample language's string: t({ en: 'Figure', es: 'Figura' }). */ function t(strings) { return strings[LANG] ?? Object.values(strings)[0]; } /** A file in this recipe's assets folder, served from the Postext repo by jsDelivr. */ function asset(file) { return `https://cdn.jsdelivr.net/gh/drnachio/postext@main/cookbook/${RECIPE}/assets/${file}`; } // ─── Kit · fonts v3 ── the same in every recipe · postext.dev/cookbook // Postext loads the faces a config asks for before layout (prepareFonts, // buildDocumentWithFonts) and asks the kit for the files: Fontsource's, the // files the PDF embeds too. /** The options of buildDocumentWithFonts / prepareFonts / withLoadedFonts: * the Fontsource resolver, and the faces of FONTS loaded with the ones the * config names. FONTS = { 'Family Name': ['400', '400i', '700'] }. */ function kitFonts(FONTS = {}) { const faces = Object.entries(FONTS).flatMap(([family, specs]) => specs.map((spec) => ({ family, weight: parseInt(spec, 10), style: spec.endsWith('i') ? 'italic' : 'normal' }))); return { resolve: fontsourceResolver, faces }; } /** A face's Fontsource files, in a fixed order: latin, then latin-ext and greek * when `text` needs them and the family ships them (kitSubsetsFor), each with * its unicode-range. Null for a weight or slant Fontsource does not ship. */ async function fontsourceResolver(family, weight, style, { text = '' } = {}) { const id = fontsourceId(family); const meta = await fontsourceMeta(family); if (meta && !(meta.weights.includes(weight) && meta.styles.includes(style))) return null; kitStatus('Loading fonts…'); const at = await fontsourceAt(id); return ['latin', ...kitSubsetsFor(text, meta)].map((subset) => ({ source: `${at}files/${id}-${subset}-${weight}-${style}.woff2`, unicodeRange: kitRanges()[subset], })); } /** The unicode-range of each Fontsource file the kit loads (a function: the * kit is inlined after the pen's code, which runs before a const would be set). */ function kitRanges() { return { latin: 'U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,' + 'U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD', 'latin-ext': 'U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,' + 'U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF', greek: 'U+0370-03FF', }; } /** For the cjk, arabic and comics blocks, which add their own files: the * faces of `faces` = { 'Family Name': ['400', '400i', '700'] }. `text` is the sample: * č ł † α χ also load latin-ext and greek files (kitSubsetsFor). With * `optional`, a face Fontsource does not ship is skipped instead of failing. * The files are added once all have loaded, in the order asked (latin, then * latin-ext, then greek), whatever order they arrive in: the last one added * wins a character two files hold. Resolves to the number of faces added. */ async function loadFonts(faces, text = '', { optional = false } = {}) { kitStatus('Loading fonts…'); const ranges = kitRanges(); const jobs = []; for (const [family, specs] of Object.entries(faces)) { const id = fontsourceId(family); const todo = [...new Set(specs)].map((spec) => [parseInt(spec, 10), spec.endsWith('i') ? 'italic' : 'normal']) .filter(([weight, style]) => !hasFace(family, weight, style)); // before any await const at = fontsourceAt(id); const meta = optional || /[^\0-ÿ]/u.test(text) ? await fontsourceMeta(family) : null; const subsets = ['latin', ...kitSubsetsFor(text, meta)]; for (const [weight, style] of todo) { if (optional && !(meta?.weights.includes(weight) && meta.styles.includes(style))) continue; for (const subset of subsets) { const url = `${await at}files/${id}-${subset}-${weight}-${style}.woff2`; const face = new FontFace(family, `url(${url}) format('woff2')`, { weight: String(weight), style, unicodeRange: ranges[subset] }); jobs.push(face.load().catch(() => { if (subset === 'latin' && !optional) throw new Error(`Fontsource has no ${family} ${weight} ${style}`); })); } } } const ready = await Promise.all(jobs).catch((error) => { kitFail(error); throw error; }); return ready.filter((face) => face && document.fonts.add(face)).length; } /** A loaded FontFace covers this family, weight and style (fonts.check() would * also say yes for families nobody declared). */ function hasFace(family, weight, style) { for (const face of document.fonts) { if (face.status !== 'loaded' || face.style !== style) continue; if (face.family.replace(/^["']|["']$/g, '') !== family) continue; const [low, high = low] = face.weight.split(' ').map(Number); if (weight >= low && weight <= high) return true; } return false; } /** The files beyond latin `text` needs that `meta`'s family ships. */ function kitSubsetsFor(text, meta) { return [[/[Ā-˿ᴀ-ᶿḀ-ỿ†ℓⱠ-Ɀ꜠-ꟿ]/u, 'latin-ext'], [/[Ͱ-Ͽ]/u, 'greek']] .filter(([re, x]) => re.test(text) && meta?.subsets?.includes(x)).map(([, x]) => x); } /** Fontsource's id for a family: 'Source Serif 4' → 'source-serif-4'. */ function fontsourceId(family) { return family.toLowerCase().replace(/\s+/g, '-'); } /** A package's folder at the one version @5 names now (#766). */ function fontsourceAt(id, u = `https://cdn.jsdelivr.net/npm/@fontsource/${id}@`) { return (fontsourceAt.c ??= {})[id] ??= fetch(u + '5/package.json').then((r) => r.json()) .then((p) => p.version || 5, () => 5).then((v) => `${u}${v}/`); } /** The family's Fontsource metadata (weights, styles, subsets), or null. */ function fontsourceMeta(family) { fontsourceMeta.cache ??= new Map(); const id = fontsourceId(family); if (!fontsourceMeta.cache.has(id)) { fontsourceMeta.cache.set(id, fetch(`https://api.fontsource.org/v1/fonts/${id}`) .then((res) => (res.ok ? res.json() : null), () => null)); } return fontsourceMeta.cache.get(id); } // ─── Kit · viewer v2 ── the same in every recipe · postext.dev/cookbook /** The pages as spreads on a dark desk, page 1 alone, then verso | recto, * each painted when it scrolls near. Other options go to renderPageToCanvas. */ function showPages(docs, { title, width = 460, ...render } = {}) { const root = viewer(title); const pages = [docs].flat().flatMap((doc) => doc.pages.map((page) => ({ doc, page, n: (doc.pageIndexOffset ?? 0) + page.index }))); const spreads = []; let verso = null; for (const p of pages) { if (p.n % 2 === 1) { if (verso) spreads.push([verso, null]); verso = p; } else { spreads.push([verso, p]); verso = null; } } if (verso) spreads.push([verso, null]); const density = Math.min(window.devicePixelRatio || 1, 2); showPages.painter?.disconnect(); const painter = new IntersectionObserver((entries) => { for (const { isIntersecting, target } of entries) { if (!isIntersecting) continue; painter.unobserve(target); const { doc, page } = target.postext; renderPageToCanvas(page, doc, target, { ...render, scale: (width * density) / page.width }); } }, { rootMargin: '800px' }); showPages.painter = painter; root.replaceChildren(...spreads.map((pair) => { const spread = document.createElement('div'); spread.className = 'pt-spread'; for (const p of pair) { const figure = document.createElement('figure'); if (p) { const label = p.page.pageLabel || String(p.n + 1); const canvas = document.createElement('canvas'); canvas.postext = p; canvas.style.aspectRatio = `${p.page.width} / ${p.page.height}`; canvas.setAttribute('role', 'img'); canvas.setAttribute('aria-label', `Page ${label}`); const folio = document.createElement('figcaption'); folio.textContent = label; figure.append(canvas, folio); painter.observe(canvas); } else figure.className = 'pt-blank'; spread.append(figure); } return spread; })); kitStatus(`${pages.length} ${pages.length === 1 ? 'page' : 'pages'}`); document.documentElement.dataset.postext = 'ready'; return pages.length; } /** The desk, the bar and the error reporting, created once. */ function viewer(title) { if (!document.getElementById('pt-kit')) { document.head.insertAdjacentHTML('beforeend', `<style id="pt-kit"> :root { color-scheme: dark; } body { margin: 0; background: #0e1014; color: #b9bcc4; font: 13px/1.45 system-ui, sans-serif; } #pt-bar { position: sticky; top: 0; z-index: 1; display: flex; flex-wrap: wrap; align-items: center; gap: 6px 16px; padding: 10px 16px; background: rgb(14 16 20 / .92); backdrop-filter: blur(6px); border-bottom: 1px solid #23262d; } #pt-bar strong { color: #f4f1ea; font-weight: 600; } #pt-actions { display: flex; gap: 12px; margin-left: auto; } #pt-actions a, #pt-actions button { color: #d8a21a; font: inherit; background: none; border: 0; padding: 0; cursor: pointer; } #pages { display: grid; justify-items: center; gap: 48px; padding: 32px 16px 72px; } .pt-spread { display: flex; } .pt-spread figure { margin: 0; width: min(460px, 44vw); } .pt-spread canvas { display: block; width: 100%; background: #fff; box-shadow: 0 1px 2px rgb(0 0 0 / .5), 0 22px 44px -16px rgb(0 0 0 / .8); } .pt-spread figure:first-child canvas { box-shadow: inset -14px 0 14px -14px rgb(0 0 0 / .18), 0 1px 2px rgb(0 0 0 / .5), 0 22px 44px -16px rgb(0 0 0 / .8); } .pt-spread figcaption { margin-top: 10px; text-align: center; font: 600 10px/1 system-ui, sans-serif; letter-spacing: .18em; text-transform: uppercase; color: #6c7079; } .pt-blank { visibility: hidden; } @media (max-width: 760px) { .pt-spread { flex-direction: column; gap: 32px; } .pt-spread figure { width: min(460px, 92vw); } .pt-blank { display: none; } } </style>`); document.body.insertAdjacentHTML('afterbegin', '<header id="pt-bar"><strong id="pt-title"></strong><span id="pt-status" role="status"></span><span id="pt-actions"></span></header>'); document.getElementById('pt-title').textContent = document.title || 'Postext'; addEventListener('error', (event) => kitFail(event.error ?? event.message)); addEventListener('unhandledrejection', (event) => kitFail(event.reason)); } if (title) document.getElementById('pt-title').textContent = title; return document.getElementById('pages') ?? document.body.appendChild(Object.assign(document.createElement('main'), { id: 'pages' })); } function kitStatus(text) { viewer(); document.getElementById('pt-status').textContent = text; } function kitFail(error) { document.documentElement.dataset.postext = 'error'; kitStatus(`Error: ${error?.message ?? error}`); } // ─── /Kit ───────────────────────────────────────────────────────────────────────
Le script.js composé s’exécute tel quel : collez-le dans le script de module de n’importe quelle page, ou ouvrez la recette sur CodePen. Dossier de la recette sur GitHub ↗ (s’ouvre dans un nouvel onglet)
Variantes
#Essayer un style à notes
Avec Chicago notes et bibliographie, chaque citation devient une note de bas de page et la liste une bibliographie ; le texte ne change toujours pas.
- { style: 'apa', band: 'apa', name: 'APA 7', system: t({ en: 'author-date', es: 'autor-año' }) },
+ { style: 'chicago-notes-bibliography', band: 'apa', name: 'Chicago', system: 'notes' },#Écrire les numéros en exposant
Un style numérique peut garder sa liste et imprimer les citations en chiffres en exposant, comme Nature et Vancouver.
- link: true,
+ link: true, marker: 'superscript',Erreurs fréquentes
- Un localisateur s’écrit une fois, et chaque style imprime son propre mot pour le désigner : « p. 17 » dans les quatre ici. Le style ISO 690 espagnol fourni imprime « p. » pour tout type de localisateur, section ou chapitre compris : ce texte cite donc une page.
- Les numéros d’édition s’écrivent en nombres dans BibTeX (
edition = {3}) : chaque style écrit alors « 3rd ed. », « 3.ª ed. » ou « 3. » à sa manière. Écrit « 3rd », le mot s’imprime tel quel et la forme propre au style est perdue.
Crédits
- Recette
- Ignacio Ferro
- Texte
- Texte original, CC BY 4.0
- Polices
- Source Serif 4 (SIL OFL 1.1) · Source Sans 3 (SIL OFL 1.1)
Modifier ce texte ↗ (s’ouvre dans un nouvel onglet)Dossier de la recette sur GitHub ↗ (s’ouvre dans un nouvel onglet)


