From 9da9683f07e6ec41fa9fc0a950f9c1a5605b4d16 Mon Sep 17 00:00:00 2001 From: monochromer Date: Wed, 18 Oct 2023 14:55:29 +0500 Subject: [PATCH 1/3] =?UTF-8?q?=D0=A0=D0=B5=D1=84=D0=B0=D0=BA=D1=82=D0=BE?= =?UTF-8?q?=D1=80=D0=B8=D1=82=20=D1=81=D1=82=D1=80=D1=83=D0=BA=D1=82=D1=83?= =?UTF-8?q?=D1=80=D1=83=20=D0=BF=D1=80=D0=BE=D0=B5=D0=BA=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- eleventy.config.js | 339 +----------------- jsconfig.json | 8 + package.json | 3 + src/eleventy-config/collections.js | 44 +++ src/eleventy-config/filters.js | 56 +++ src/eleventy-config/markdown-library.js | 25 ++ src/eleventy-config/shortcodes.js | 23 ++ src/eleventy-config/static-files.js | 11 + src/eleventy-config/transforms.js | 185 ++++++++++ src/{helpers => libs}/markdown-it-anchor.js | 0 src/{helpers => libs}/pagination.js | 0 src/{helpers => libs}/podcasts-service.js | 5 +- src/pages/{ => articles}/articles.11tydata.js | 2 +- src/pages/{ => articles}/articles.njk | 0 src/pages/{ => index}/index.11tydata.js | 0 src/pages/{ => index}/index.njk | 0 src/pages/{ => podcast}/podcast.11tydata.js | 2 +- src/pages/{ => podcast}/podcast.njk | 0 18 files changed, 369 insertions(+), 334 deletions(-) create mode 100644 jsconfig.json create mode 100644 src/eleventy-config/collections.js create mode 100644 src/eleventy-config/filters.js create mode 100644 src/eleventy-config/markdown-library.js create mode 100644 src/eleventy-config/shortcodes.js create mode 100644 src/eleventy-config/static-files.js create mode 100644 src/eleventy-config/transforms.js rename src/{helpers => libs}/markdown-it-anchor.js (100%) rename src/{helpers => libs}/pagination.js (100%) rename src/{helpers => libs}/podcasts-service.js (98%) rename src/pages/{ => articles}/articles.11tydata.js (95%) rename src/pages/{ => articles}/articles.njk (100%) rename src/pages/{ => index}/index.11tydata.js (100%) rename src/pages/{ => index}/index.njk (100%) rename src/pages/{ => podcast}/podcast.11tydata.js (91%) rename src/pages/{ => podcast}/podcast.njk (100%) diff --git a/eleventy.config.js b/eleventy.config.js index cc6328223..524894673 100644 --- a/eleventy.config.js +++ b/eleventy.config.js @@ -1,340 +1,21 @@ -const path = require('node:path'); -const { parseHTML } = require('linkedom'); -const Image = require('@11ty/eleventy-img'); +module.exports = function(eleventyConfig) { + // Настройка Markdown + require('#eleventy-config/markdown-library.js')(eleventyConfig); -Image.concurrency = require('os').cpus().length; + // Коллекции + require('#eleventy-config/collections.js')(eleventyConfig); -const isProdMode = process.env.NODE_ENV === 'production'; - -module.exports = function(config) { - // Markdown Options - - const markdownItAnchor = require('./src/helpers/markdown-it-anchor.js'); - - const md = require('markdown-it')({ - html: true, - highlight: function(str, lang) { - return `
${md.utils.escapeHtml(str)}
`; - }, - }).use(markdownItAnchor, { - permalink: true, - permalinkClass: 'tooltip__button', - permalinkSymbol: '', - permalinkSpace: false, - slugify: () => 'section', - }).use(require('markdown-it-multimd-table')); - - md.renderer.rules = { ...md.renderer.rules, - table_close: () => '', - table_open: () => '
', - }; - - config.setLibrary('md', md); - - config.addCollection('tagList', (collection) => { - const set = new Set(); - for (const item of collection.getAllSorted()) { - if ('tags' in item.data) { - let tags = item.data.tags; - if (typeof tags === 'string') { - tags = [tags]; - } - for (const tag of tags) { - set.add(tag); - } - } - } - return [...set].sort(); - }); - - /* - Коллекция для выпусков подкаста. - Формат данных одного выпуска: - - episode - - title - - date - - chapters - - time - - title - - content - - hosts - - audio - */ - config.addCollection('episodes', () => { - const { getEpisodesData } = require('./src/helpers/podcasts-service'); - return getEpisodesData(); - }); - - config.addCollection('people', (collectionAPI) => { - return collectionAPI.getFilteredByGlob('src/people/*/*.md'); - }); - - config.addCollection('articles', (collectionAPI) => { - return collectionAPI.getFilteredByGlob('src/articles/*/*.md'); - }); - - config.addFilter('limit', (array, limit) => { - return array.slice(0, limit); - }); - - config.addFilter('addHyphens', (content, maxLength = 0) => { - if (!content || content.length <= maxLength) { - return content; - } - let hyphenLibRu = require('hyphen/ru'); - let contentWithHyps = hyphenLibRu.hyphenateSync(content); - return contentWithHyps; - }); - - config.addFilter('fixLinks', (content) => { - const reg = /(src="[^(https://)])|(src="\/)|(href="[^(https://)])|(href="\/)/g; - const prefix = `https://web-standards.ru` + content.url; - return content.templateContent.replace(reg, (match) => { - if (match === `src="/` || match === `href="/`) { - match = match.slice(0, -1); - return match + prefix; - } else { - return match.slice(0, -1) + prefix + match.slice(-1); - } - }); - }); - - // Даты - - config.addFilter('ruDate', (value) => { - return value.toLocaleString('ru', { - year: 'numeric', - month: 'long', - day: 'numeric', - }).replace(' г.', ''); - }); - - config.addFilter('shortDate', (value) => { - return value.toLocaleString('ru', { - month: 'short', - day: 'numeric', - }).replace('.', ''); - }); - - config.addFilter('isoDate', (value) => { - return value.toISOString(); - }); - - config.addFilter('markdown', (value) => { - let markdown = require('markdown-it')({ - html: true, - }); - return markdown.render(value); - }); + // Фильтры + require('#eleventy-config/filters.js')(eleventyConfig); // Трансформации - - config.addTransform('optimizeContentImages', async function(content) { - if (!this.page.inputPath.includes('/articles/')) { - return content; - } - - if (!this.page.outputPath.endsWith('.html')) { - return content; - } - - const { document } = parseHTML(content); - const images = Array.from(document.querySelectorAll('.article__content img')) - .filter((image) => !image.src.match(/^https?:/)); - - if (images.length === 0) { - return content; - } - - const articleSourceFolder = path.dirname(this.page.inputPath); - const outputArticleImagesFolder = path.join(path.dirname(this.page.outputPath), 'images'); - - await Promise.all(images.map(async(image) => { - const fullImagePath = path.join(articleSourceFolder, image.src); - const imageStats = await Image(fullImagePath, { - widths: ['auto', 600, 1200, 2400], - formats: isProdMode - ? ['svg', 'avif', 'webp', 'auto'] - : ['svg', 'webp', 'auto'], - outputDir: outputArticleImagesFolder, - urlPath: 'images/', - svgShortCircuit: true, - filenameFormat: (hash, src, width, format) => { - const extension = path.extname(src); - const name = path.basename(src, extension); - return `${hash}-${name}-${width}.${format}`; - }, - }); - - const imageAttributes = Object.assign( - { - loading: 'lazy', - decoding: 'async', - sizes: [ - '(min-width: 1920px) calc((1920px - 2 * 64px) * 5 / 8 - 2 * 16px)', - '(min-width: 1240px) calc((100vw - 2 * 64px) * 5 / 8 - 2 * 16px)', - '(min-width: 700px) calc(700px - 2 * 16px)', - 'calc(100vw - 2 * 16px)', - ].join(','), - }, - Object.fromEntries( - [...image.attributes].map((attr) => [attr.name, attr.value]) - ) - ); - - const newImageHTML = Image.generateHTML(imageStats, imageAttributes); - image.outerHTML = newImageHTML; - })); - - return document.toString(); - }); - - { - const avatarImageFormats = isProdMode - ? ['avif', 'webp', 'jpeg'] - : ['webp', 'jpeg']; - - const formatsOrder = ['avif', 'webp', 'jpeg']; - - config.addTransform('optimizeAvatarImages', async function(content) { - if (!this.page.outputPath.endsWith?.('.html')) { - return content; - } - - const { document } = parseHTML(content); - const images = Array.from(document.querySelectorAll('.blob__photo')) - .filter((image) => !image.src.match(/^https?:/)); - - if (images.length === 0) { - return content; - } - - await Promise.all(images.map(async(image) => { - const fullImagePath = path.join(config.dir.input, image.src); - const avatarsOutputFolder = path.dirname(path.join(config.dir.output, image.src)); - - const imageStats = await Image(fullImagePath, { - widths: image.sizes - .split(',') - .flatMap((entry) => { - entry = entry.split(/\s+/).at(-1); - entry = parseFloat(entry); - return [entry, entry * 2]; - }), - formats: avatarImageFormats, - outputDir: avatarsOutputFolder, - urlPath: image.src.split('/').slice(0, -1).join('/'), - svgShortCircuit: true, - filenameFormat: (hash, src, width, format) => { - const extension = path.extname(src); - const name = path.basename(src, extension); - return `${hash}-${name}-${width}.${format}`; - }, - }); - - image.outerHTML = ` - - ${ - formatsOrder - .map(((format) => imageStats[format])) - .filter(Boolean) - .map((stats) => { - const type = stats[0].sourceType; - const srcset = stats.map((statsItem) => statsItem.srcset).join(','); - return ``; - }) - .join('') - } - ${image.outerHTML} - - `; - })); - - return document.toString(); - }); - } - - config.addTransform('htmlmin', (content, outputPath) => { - if (outputPath && outputPath.endsWith('.html')) { - let htmlmin = require('html-minifier'); - let result = htmlmin.minify( - content, { - removeComments: true, - collapseWhitespace: true, - collapseBooleanAttributes: true, - } - ); - return result; - } - return content; - }); - - config.addTransform('xmlmin', function(content, outputPath) { - if (outputPath && outputPath.endsWith('.xml')) { - let prettydata = require('pretty-data'); - let result = prettydata.pd.xmlmin(content); - return result; - } - return content; - }); - - config.addTransform('lazyYouTube', (content, outputPath) => { - let articles = /articles\/([a-zA-Z0-9_-]+)\/index\.html/i; - let iframes = /