├── .nojekyll ├── .nvmrc ├── assets ├── images │ ├── .gitkeep │ ├── article.png │ ├── favicon.png │ ├── image001.jpg │ ├── image002.jpg │ └── image003.jpg ├── .well-known │ └── example.json ├── fonts │ ├── lora-v26-latin-700.woff │ ├── lora-v26-latin-700.woff2 │ ├── lora-v26-latin-regular.woff │ └── lora-v26-latin-regular.woff2 └── css │ ├── fullbleed.css │ ├── github.repocard.css │ └── index.css ├── .npmrc ├── .eleventyignore ├── _includes ├── layouts │ ├── home.njk │ ├── post.njk │ └── base.njk ├── simplelightbox.njk ├── postslist.njk ├── pagination.njk └── github.repocard.njk ├── screenshots ├── 01.png └── 02.png ├── drafts ├── drafts.json ├── drafts.11tydata.js └── 2023-01-07-a-draft-post.md ├── .gitignore ├── renovate.json ├── posts ├── posts.json ├── 2022-01-05-set-footer-links.md ├── 2022-11-11-extra-wide-full-width-images-videos.md ├── 2022-12-20-github-repo-card.md ├── 2022-02-02-set-date-of-post.md ├── 2022-03-03-opengraph-preview-data.md ├── post-with-a-gallery.md ├── 2023-01-15-post-notice.md ├── 2022-12-19-post-with-github-gists.md ├── 2023-01-05-post-with-code.md ├── 2022-11-22-post-with-iframes-videos-third-party.md ├── posting-links.md ├── markdown-showcase.md ├── 2022-01-01-edit-the-metadata.md ├── 2021-12-24-post-with-various-headings.md ├── post-with-an-image.md └── customary-lorem-ipsum.md ├── pages ├── robots.txt.njk ├── index.njk ├── index.11tydata.js ├── sitemap.xml.njk ├── page-list.njk ├── json.njk └── feed.njk ├── _configs ├── button.shortcode.js ├── notice.shortcode.js ├── cssminification.shortcode.js ├── gallery.shortcode.js ├── lightboxref.shortcode.js ├── excerpt.shortcode.js ├── markdownlibrary.renderer.image.js ├── video.shortcode.js ├── figure.shortcode.js ├── gist.shortcode.js └── githubrepocard.shortcode.js ├── .editorconfig ├── .github ├── dependabot.yml ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md └── workflows │ ├── playwright.yml │ └── staticsite.yml ├── 404.md ├── _data ├── bottomlinks.json ├── metadata.json └── photostream.json ├── docker-compose.yml ├── tests ├── post-with-code.spec.ts ├── post-with-third-party-media.spec.ts ├── light-dark-theme.spec.ts ├── navigation.spec.ts ├── post-with-github-repo-card.spec.ts ├── post-with-notice.spec.ts ├── post-with-github-gist.spec.ts ├── draft-post.spec.ts ├── post-with-image-gallery.spec.ts ├── post-with-relative-links.spec.ts ├── post-with-images.spec.ts ├── robots-feeds.spec.ts └── home-page.spec.ts ├── LICENSE ├── package.json ├── playwright.config.ts ├── README.md └── .eleventy.js /.nojekyll: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 18 2 | -------------------------------------------------------------------------------- /assets/images/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | engine-strict=true 2 | loglevel=silent 3 | -------------------------------------------------------------------------------- /.eleventyignore: -------------------------------------------------------------------------------- 1 | README.md 2 | .github 3 | tests/ 4 | playwright-report/ 5 | -------------------------------------------------------------------------------- /assets/.well-known/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "eleventy": "satisfactory" 3 | } 4 | -------------------------------------------------------------------------------- /_includes/layouts/home.njk: -------------------------------------------------------------------------------- 1 | --- 2 | layout: layouts/base.njk 3 | --- 4 | {{ content | safe }} 5 | -------------------------------------------------------------------------------- /screenshots/01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mendhak/eleventy-satisfactory/HEAD/screenshots/01.png -------------------------------------------------------------------------------- /screenshots/02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mendhak/eleventy-satisfactory/HEAD/screenshots/02.png -------------------------------------------------------------------------------- /assets/images/article.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mendhak/eleventy-satisfactory/HEAD/assets/images/article.png -------------------------------------------------------------------------------- /assets/images/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mendhak/eleventy-satisfactory/HEAD/assets/images/favicon.png -------------------------------------------------------------------------------- /assets/images/image001.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mendhak/eleventy-satisfactory/HEAD/assets/images/image001.jpg -------------------------------------------------------------------------------- /assets/images/image002.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mendhak/eleventy-satisfactory/HEAD/assets/images/image002.jpg -------------------------------------------------------------------------------- /assets/images/image003.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mendhak/eleventy-satisfactory/HEAD/assets/images/image003.jpg -------------------------------------------------------------------------------- /drafts/drafts.json: -------------------------------------------------------------------------------- 1 | { 2 | "tags": [ 3 | "posts" 4 | ], 5 | "layout": "layouts/post.njk", 6 | "draft": true 7 | } 8 | -------------------------------------------------------------------------------- /assets/fonts/lora-v26-latin-700.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mendhak/eleventy-satisfactory/HEAD/assets/fonts/lora-v26-latin-700.woff -------------------------------------------------------------------------------- /assets/fonts/lora-v26-latin-700.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mendhak/eleventy-satisfactory/HEAD/assets/fonts/lora-v26-latin-700.woff2 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | _site/ 2 | node_modules/ 3 | .cache 4 | /test-results/ 5 | /playwright-report/ 6 | /blob-report/ 7 | /playwright/.cache/ 8 | .env 9 | -------------------------------------------------------------------------------- /assets/fonts/lora-v26-latin-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mendhak/eleventy-satisfactory/HEAD/assets/fonts/lora-v26-latin-regular.woff -------------------------------------------------------------------------------- /assets/fonts/lora-v26-latin-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mendhak/eleventy-satisfactory/HEAD/assets/fonts/lora-v26-latin-regular.woff2 -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:base" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /posts/posts.json: -------------------------------------------------------------------------------- 1 | { 2 | "permalink": "/{{ page.fileSlug }}/index.html", 3 | "tags": [ 4 | "posts" 5 | ], 6 | "includeJsonLD": true, 7 | "layout": "layouts/post.njk" 8 | } 9 | -------------------------------------------------------------------------------- /pages/robots.txt.njk: -------------------------------------------------------------------------------- 1 | --- 2 | permalink: /robots.txt 3 | eleventyExcludeFromCollections: true 4 | --- 5 | User-agent: * 6 | Allow: / 7 | 8 | Sitemap: {{ "/sitemap.xml" | htmlBaseUrl(metadata.url) }} 9 | -------------------------------------------------------------------------------- /drafts/drafts.11tydata.js: -------------------------------------------------------------------------------- 1 | export default async function() { 2 | return { 3 | eleventyComputed: { 4 | permalink: function(data){ 5 | return `/${data.page.fileSlug}/index.html`; 6 | } 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /_configs/button.shortcode.js: -------------------------------------------------------------------------------- 1 | export default function(text, url) { 2 | 3 | let buttonHtml = ``; 4 | if(url){ 5 | buttonHtml = `${buttonHtml}`; 6 | } 7 | 8 | return buttonHtml; 9 | } 10 | 11 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | charset = utf-8 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | 14 | 15 | -------------------------------------------------------------------------------- /_configs/notice.shortcode.js: -------------------------------------------------------------------------------- 1 | export default function (data, noticeType, markdownLibrary) { 2 | if (!noticeType) { 3 | noticeType = ""; 4 | } 5 | let noticeMarkup = markdownLibrary.renderInline(data); 6 | return `
${noticeMarkup}
`; 7 | } 8 | 9 | -------------------------------------------------------------------------------- /pages/index.njk: -------------------------------------------------------------------------------- 1 | --- 2 | layout: layouts/home.njk 3 | # Pagination is set in index.11tydata.js 4 | permalink: "/{% if pagination.pageNumber > 0 %}{{ pagination.pageNumber + 1 }}/{% endif %}" 5 | --- 6 | 7 | 8 | {% include "postslist.njk" %} 9 | 10 | {% include "pagination.njk" %} 11 | -------------------------------------------------------------------------------- /_includes/layouts/post.njk: -------------------------------------------------------------------------------- 1 | --- 2 | layout: layouts/base.njk 3 | --- 4 | 5 | 6 |
7 | {{ content | safe }} 8 |
9 | 10 |

11 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "npm" # See documentation for possible values 4 | directory: "/" # Location of package manifests 5 | schedule: 6 | interval: "weekly" 7 | groups: 8 | all-dependencies: 9 | patterns: 10 | - "*" -------------------------------------------------------------------------------- /_includes/simplelightbox.njk: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | -------------------------------------------------------------------------------- /_includes/postslist.njk: -------------------------------------------------------------------------------- 1 | 2 | {% for post in postslist %} 3 | 4 |

{% if post.data.title %}{{ post.data.title | markdown | safe }}{% else %}{{ post.url }}{% endif %}

5 |
{% excerpt post %}
6 | 7 | 8 | {% endfor %} 9 | -------------------------------------------------------------------------------- /404.md: -------------------------------------------------------------------------------- 1 | --- 2 | layout: layouts/home.njk 3 | permalink: 404.html 4 | eleventyExcludeFromCollections: true 5 | --- 6 | # Content not found. 7 | 8 | Go home. 9 | 10 | {# 11 | Read more: https://www.11ty.dev/docs/quicktips/not-found/ 12 | 13 | This will work for GitHub pages: 14 | 15 | * https://help.github.com/articles/creating-a-custom-404-page-for-your-github-pages-site/ 16 | #} 17 | -------------------------------------------------------------------------------- /_data/bottomlinks.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "link": "https://github.com/mendhak/eleventy-satisfactory", 4 | "text": "⌨️ Github Repo" 5 | }, 6 | { 7 | "link": "https://11ty.dev", 8 | "text": "1️⃣ Eleventy" 9 | }, 10 | { 11 | "link": "https://simplecss.org/", 12 | "text": "📜 Simple.css" 13 | }, 14 | { 15 | "link": "/feed.xml", 16 | "text": "🌐 Feed" 17 | } 18 | ] 19 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | eleventy: 4 | image: node:18-slim 5 | user: node 6 | volumes: 7 | - ${PWD}/node_modules 8 | - ${PWD}:/app 9 | working_dir: /app 10 | environment: 11 | - GITHUB_TOKEN=${GITHUB_TOKEN} 12 | - GIST_TOKEN=${GH_GIST_TOKEN} 13 | # - DEBUG=Eleventy* 14 | ports: 15 | - 8080:8080 16 | command: /bin/bash -c "npm install && npm start" 17 | -------------------------------------------------------------------------------- /pages/index.11tydata.js: -------------------------------------------------------------------------------- 1 | import { readFile } from 'fs/promises'; 2 | const metadata = JSON.parse( 3 | await readFile( 4 | new URL('../_data/metadata.json', import.meta.url) 5 | ) 6 | ); 7 | 8 | export default async function(data) { 9 | return { 10 | pagination: { 11 | data: "collections.posts", 12 | size: metadata.paginationSize, 13 | alias: "postslist", 14 | reverse: true, 15 | } 16 | }; 17 | }; 18 | -------------------------------------------------------------------------------- /_configs/cssminification.shortcode.js: -------------------------------------------------------------------------------- 1 | // For minifying the CSS 2 | import CleanCSS from "clean-css"; 3 | 4 | /** 5 | * Paired shortcode that takes a JSON array of CSS file paths 6 | * It then combines them, which includes reconciles overriden values! 7 | * @returns a long string of minified css 8 | */ 9 | export default function(data) { 10 | let filesToCombine = JSON.parse(data); 11 | let output = new CleanCSS().minify(filesToCombine).styles; 12 | return output; 13 | }; 14 | -------------------------------------------------------------------------------- /_includes/pagination.njk: -------------------------------------------------------------------------------- 1 | {# If a layout has `pagination` in its frontmatter, this partial layout can produce previous/next links. #} 2 | 8 | -------------------------------------------------------------------------------- /pages/sitemap.xml.njk: -------------------------------------------------------------------------------- 1 | --- 2 | permalink: /sitemap.xml 3 | eleventyExcludeFromCollections: true 4 | --- 5 | 6 | 7 | {%- for page in collections.all %} 8 | {% set absoluteUrl %}{{ page.url | htmlBaseUrl(metadata.url) }}{% endset %} 9 | 10 | {{ absoluteUrl }} 11 | {{ (last_modified_at or page.date) | htmlDateString }} 12 | 13 | {%- endfor %} 14 | 15 | -------------------------------------------------------------------------------- /tests/post-with-code.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, expect, Page } from '@playwright/test'; 2 | 3 | let siteURL; 4 | 5 | test.beforeEach(async ({page, baseURL}) => { 6 | await page.goto('/'); 7 | siteURL = baseURL; 8 | }); 9 | 10 | 11 | test.describe('Post with code blocks', () => { 12 | test('Code blocks are syntax highlighted', async ({ page }) => { 13 | await page.goto('post-with-code/'); 14 | let codeBlocks = await page.locator('pre.language-javascript'); 15 | await expect(codeBlocks).toBeVisible(); 16 | await expect(codeBlocks).toHaveCSS('background-color', 'rgb(43, 43, 43)'); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /assets/css/fullbleed.css: -------------------------------------------------------------------------------- 1 | /* Code blocks rendered by Prism - make it break out */ 2 | /* https://archive.hankchizljaw.com/wrote/creating-a-full-bleed-css-utility/ */ 3 | pre[class*='language-'], 4 | article > pre, 5 | article > figure, 6 | div.gallery, 7 | div.video 8 | { 9 | width: 100vw; 10 | max-width: 65rem; 11 | margin-left: 50%; 12 | transform: translateX(-50%); 13 | position: relative; 14 | box-shadow: 0 10px 30px rgba(0,0,0,.15); 15 | /* border: 1px solid #ffffff1a; */ 16 | --border: #ffffff1a; 17 | } 18 | article > figure, 19 | div.gallery, 20 | div.video 21 | { 22 | box-shadow: none; 23 | } 24 | 25 | 26 | -------------------------------------------------------------------------------- /tests/post-with-third-party-media.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, expect, Page } from '@playwright/test'; 2 | 3 | let siteURL; 4 | 5 | test.beforeEach(async ({page, baseURL}) => { 6 | await page.goto('/'); 7 | siteURL = baseURL; 8 | }); 9 | 10 | 11 | 12 | test.describe('Post with third party media', () => { 13 | 14 | test('Third party media is visible', async ({ page }) => { 15 | await page.goto('post-with-iframes-videos-third-party/'); 16 | let ytVideo = await page.locator('iframe[src="https://www.youtube.com/embed/9qOvG9KeJ6c"]'); 17 | await expect(ytVideo).toBeVisible(); 18 | 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /tests/light-dark-theme.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, expect, Page } from '@playwright/test'; 2 | 3 | let siteURL; 4 | 5 | test.beforeEach(async ({page, baseURL}) => { 6 | await page.goto('/'); 7 | siteURL = baseURL; 8 | }); 9 | 10 | 11 | test('Light and Dark Theme', async ({ page }) => { 12 | 13 | let articleContainer = await page.locator('body'); 14 | await expect(articleContainer).toHaveCSS('background-color', 'rgb(255, 255, 255)'); 15 | await page.emulateMedia({ colorScheme: 'dark' }); 16 | await expect(articleContainer).toHaveCSS('background-color', 'rgb(51, 51, 51)'); 17 | // sleep 5s 18 | await page.waitForTimeout(5000); 19 | 20 | }); 21 | -------------------------------------------------------------------------------- /pages/page-list.njk: -------------------------------------------------------------------------------- 1 | --- 2 | title: Page list 3 | pagination: 4 | data: collections.all 5 | size: 10 6 | alias: entries 7 | reverse: true 8 | layout: layouts/home.njk 9 | permalink: /page-list/{% if pagination.pageNumber > 0 %}{{ pagination.pageNumber }}/{% endif %} 10 | --- 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | {%- for entry in entries %} 20 | 21 | 22 | 23 | 24 | {%- endfor %} 25 | 26 |
URLPage Title
{{ entry.url }}{{ entry.data.title }}
27 | 28 | 29 | {% include "pagination.njk" %} 30 | -------------------------------------------------------------------------------- /tests/navigation.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, expect, Page } from '@playwright/test'; 2 | 3 | let siteURL; 4 | 5 | test.beforeEach(async ({page, baseURL}) => { 6 | await page.goto('/'); 7 | siteURL = baseURL; 8 | }); 9 | 10 | 11 | test.describe('Navigation Tests', () => { 12 | 13 | test('MORE link test', async ({ page }) => { 14 | await page.click('text=More'); 15 | expect(page).toHaveURL(siteURL + '2/'); 16 | expect(await page.locator('text=Previous')).toBeVisible(); 17 | expect(await page.locator('text=Next')).toBeVisible(); 18 | await page.click('text=Previous'); 19 | expect(page).toHaveURL(siteURL); 20 | }); 21 | 22 | }); 23 | -------------------------------------------------------------------------------- /drafts/2023-01-07-a-draft-post.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: This draft post should only appear locally 3 | description: This should only be visible locally 4 | tags: second tag 5 | 6 | --- 7 | 8 | This post has been created in the `drafts` folder. It should only appear when the eleventy server runs with `--watch` or `--serve`. 9 | 10 | In package.json, the start command has been given those flags, so drafts should appear when running via `npm run start`. 11 | 12 | In the docker-compose file from where this site runs, the command is `npm start`, so running via `docker-compose up` should show draft posts. 13 | 14 | Since `npm run build` doesn't have the flags, drafts will not appear in the production build. 15 | -------------------------------------------------------------------------------- /tests/post-with-github-repo-card.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, expect, Page } from '@playwright/test'; 2 | 3 | let siteURL; 4 | 5 | test.beforeEach(async ({page, baseURL}) => { 6 | await page.goto('github-repo-card/'); 7 | siteURL = baseURL; 8 | }); 9 | 10 | 11 | test.describe('Post with Github Repo Card', () => { 12 | 13 | test('Repo Card is rendered with description', async ({ page }) => { 14 | 15 | await expect(page.getByText('Lightweight GPS Logging Application For Android')).toBeVisible(); 16 | 17 | let sendLocator = page.getByText('mozilla/send'); 18 | const mozillaSendCount = await sendLocator.count(); 19 | await expect(mozillaSendCount).toBe(2); 20 | 21 | }); 22 | 23 | }); 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /tests/post-with-notice.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, expect, Page } from '@playwright/test'; 2 | 3 | let siteURL; 4 | 5 | test.beforeEach(async ({page, baseURL}) => { 6 | await page.goto('/'); 7 | siteURL = baseURL; 8 | }); 9 | 10 | 11 | test.describe('Post with notice block', () => { 12 | 13 | test('Notice block is visible', async ({ page }) => { 14 | await page.goto('post-notice/'); 15 | let noticeBlock = await page.locator('.notice.success'); 16 | await expect(noticeBlock).toBeVisible(); 17 | await expect(await noticeBlock.innerText()).toBe('This is a success notice. Great Success.'); 18 | await expect(noticeBlock).toHaveCSS('background-color', 'rgb(217, 237, 217)'); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /_configs/gallery.shortcode.js: -------------------------------------------------------------------------------- 1 | /** 2 | * The `gallery` paired shortcode shows a set of images and displays it in a grid. 3 | * It sets inGallery = true, which is used by the markdown image renderer 4 | */ 5 | export default function gallery(data, caption="", markdownLibrary) { 6 | // Count the number of images passed in (one per newline). 7 | // If it's an even number of items, we'll get the image to set width = 50%. 8 | // To help with the layout. 9 | let evenItems = (data.trim().split('\n').length % 2) == 0; 10 | 11 | const galleryContent = markdownLibrary.renderInline(data, { 'inGallery': true, 'evenItems': evenItems }); 12 | return `
${galleryContent}
${markdownLibrary.renderInline(caption)}
`; 13 | } 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /_data/metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Eleventy Satisfactory", 3 | "url": "https://code.mendhak.com/eleventy-satisfactory/", 4 | "pathPrefix": "/eleventy-satisfactory/", 5 | "language": "en", 6 | "paginationSize": 4, 7 | "favicon": "/assets/images/favicon.png", 8 | "description": "Blog theme for Eleventy with various new features", 9 | "tags": ["eleventy", "blog", "theme", "template", "starter"], 10 | "feed": { 11 | "path": "/feed.xml" 12 | }, 13 | "jsonfeed": { 14 | "path": "/feed.json" 15 | }, 16 | "author": { 17 | "name": "mendhak", 18 | "email": "", 19 | "url": "https://github.com/mendhak/eleventy-satisfactory" 20 | }, 21 | "opengraph": { 22 | "image": "/assets/images/article.png", 23 | "type": "article" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tests/post-with-github-gist.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, expect, Page } from '@playwright/test'; 2 | 3 | let siteURL; 4 | 5 | test.beforeEach(async ({page, baseURL}) => { 6 | await page.goto('post-with-github-gists/'); 7 | siteURL = baseURL; 8 | }); 9 | 10 | 11 | test.describe('Post with Github Gist', () => { 12 | 13 | test('Github Gist is rendered seamlessly', async ({ page }) => { 14 | 15 | await expect(page.getByText('Hello, from Github Gist!')).toBeVisible(); 16 | 17 | // await expect(page.waitForSelector('html body main article table thead tr th')) 18 | let tableSelector = page.locator('article table th').first(); 19 | await expect(await tableSelector.innerText()).toBe('Option'); 20 | await expect(tableSelector).toBeVisible(); 21 | 22 | }); 23 | 24 | }); 25 | -------------------------------------------------------------------------------- /_configs/lightboxref.shortcode.js: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import nunjucks from "nunjucks"; 3 | 4 | /** 5 | * If the given post contains a figure shortcode or a markdown image, this function adds the SimpleLightbox JS and CSS so images are displayed with a lightbox. 6 | * @param {JSON} page - the eleventy page object, from which the `.inputPath` to the page will be read. 7 | * @returns 8 | */ 9 | export default function getLightBoxIfNecessary(page){ 10 | 11 | const str = fs.readFileSync(page.inputPath, 'utf8'); 12 | 13 | if (str.includes('{% figure') || str.includes('![')) { 14 | nunjucks.configure({ trimBlocks: true, lstripBlocks: true }); 15 | let lightboxHtml = nunjucks.render('_includes/simplelightbox.njk'); 16 | return lightboxHtml; 17 | } 18 | 19 | return ``; 20 | } 21 | 22 | -------------------------------------------------------------------------------- /_configs/excerpt.shortcode.js: -------------------------------------------------------------------------------- 1 | 2 | /** 3 | * Gets the first 30 words as the excerpt or until the newline, whichever comes first. 4 | */ 5 | export default function (article) { 6 | if (!Object.prototype.hasOwnProperty.call(article, "templateContent")) { 7 | console.warn( 8 | 'Failed to extract excerpt: Document has no property "templateContent".' 9 | ); 10 | return null; 11 | } 12 | 13 | const content = article.templateContent; 14 | 15 | //Take the first paragraph until newline, remove HTML 16 | let words = content.slice(0, content.indexOf("\n")).replace(/<[^>]*>?/gm, '').split(/\s+/); 17 | let suffix = ''; 18 | 19 | if (words.length > 30) { 20 | suffix = '…'; 21 | } 22 | 23 | let excerpt = words.slice(0, 30).join(' ') + suffix; 24 | return excerpt; 25 | } 26 | 27 | 28 | -------------------------------------------------------------------------------- /tests/draft-post.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, expect, Page } from '@playwright/test'; 2 | 3 | let siteURL; 4 | 5 | test.beforeEach(async ({page, baseURL}) => { 6 | await page.goto('/'); 7 | siteURL = baseURL; 8 | }); 9 | 10 | 11 | 12 | test.describe('Draft Post Tests', () => { 13 | 14 | test('Verify Draft Post is only visible locally', async ({ page }) => { 15 | await page.getByRole('link', {name: 'This draft post should only appear locally', exact: true} ).click(); 16 | expect(page).toHaveURL(siteURL + 'a-draft-post/'); 17 | 18 | // Ensure the draft post is not visible on the live site 19 | await page.goto('https://code.mendhak.com/eleventy-satisfactory/a-draft-post/') 20 | expect(await page.locator('text=Content not found.')).toBeVisible(); 21 | }); 22 | 23 | }); 24 | -------------------------------------------------------------------------------- /_data/photostream.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "link": "https://www.flickr.com/photos/mendhak/30454355997/", 4 | "img": "https://live.staticflickr.com/1932/30454355997_287063f84b_q.jpg" 5 | }, 6 | { 7 | "link": "https://www.flickr.com/photos/mendhak/31366195988/", 8 | "img": "https://live.staticflickr.com/1917/31366195988_3b8862fd7e_q.jpg" 9 | }, 10 | { 11 | "link": "https://www.flickr.com/photos/mendhak/38225459035/", 12 | "img": "https://live.staticflickr.com/4596/38225459035_db33403af9_q.jpg" 13 | }, 14 | { 15 | "link": "https://www.flickr.com/photos/mendhak/23816072218/", 16 | "img": "https://live.staticflickr.com/4478/23816072218_c6f2a65658_q.jpg" 17 | }, 18 | { 19 | "link": "https://www.flickr.com/photos/mendhak/37937678644/", 20 | "img": "https://live.staticflickr.com/4521/37937678644_e569ddc8f6_q.jpg" 21 | } 22 | ] 23 | -------------------------------------------------------------------------------- /.github/workflows/playwright.yml: -------------------------------------------------------------------------------- 1 | name: Playwright Tests 2 | on: 3 | push: 4 | branches: 5 | - '*' 6 | pull_request: 7 | 8 | 9 | # Allows you to run this workflow manually from the Actions tab 10 | workflow_dispatch: 11 | 12 | jobs: 13 | playwright-tests: 14 | timeout-minutes: 60 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v4 18 | - uses: actions/setup-node@v4 19 | with: 20 | node-version: lts/* 21 | - name: Install dependencies 22 | run: npm ci 23 | - name: Install Playwright Browsers 24 | run: npx playwright install --with-deps 25 | - name: Run Playwright tests 26 | run: npx playwright test 27 | # - uses: actions/upload-artifact@v4 28 | # if: always() 29 | # with: 30 | # name: playwright-report 31 | # path: playwright-report/ 32 | # retention-days: 30 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /posts/2022-01-05-set-footer-links.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Set footer images and links 3 | extraWideMedia: false 4 | --- 5 | 6 | How to set the links in the footer as well as the small thumbnails that appear. 7 | 8 | ### Links 9 | 10 | The links are controlled via the `_data/bottomlinks.json` file. It's just an array of link URLs and the text to display. 11 | 12 | ```json 13 | [ 14 | { 15 | "link": "https://example.com", 16 | "text": "Example" 17 | } 18 | ... 19 | ] 20 | 21 | ``` 22 | 23 | You can also link to relative paths. 24 | 25 | To remove the links in the footer, empty the array, or empty the file, or delete the file. 26 | 27 | 28 | ### Photos 29 | 30 | The photos are controlled in the `_data/photostream.json` file. It's an array of thumbnails and the URL to link to. 31 | 32 | ```json 33 | [ 34 | { 35 | "link": "https://www.flickr.com/photos/mendhak/30454355997/", 36 | "img": "https://live.staticflickr.com/1932/30454355997_287063f84b_q.jpg" 37 | }, 38 | ... 39 | ``` 40 | 41 | To remove the images in the footer, empty the array, or empty the file, or delete the file. 42 | -------------------------------------------------------------------------------- /posts/2022-11-11-extra-wide-full-width-images-videos.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: About extra-wide images, videos and code blocks 3 | 4 | --- 5 | 6 | By default, images, videos and code blocks are displayed wider than the (text) content around it. This is to make it stand out and give it a little emphasis. An example of this 'full bleed' behavior can be seen on the [lorem ipsum page](/posts/customary-lorem-ipsum.md). 7 | 8 | 9 | {% figure "https://live.staticflickr.com/7280/27837260711_a4f38ee02e_k.jpg", "Example of extra wide image","" %} 10 | 11 | 12 | In some cases this might be a bit overwhelming or distracting. 13 | 14 | This behavior can be disabled on a per-post basis by setting the value `extraWideMedia: false` in the frontmatter of the post. An example of this can be seen on the [footer images and links page](/posts/2022-01-05-set-footer-links.md). 15 | 16 | 17 | 18 | To disable it for all posts, set the value in the `posts/posts.json` file. 19 | 20 | ```json 21 | { 22 | ... 23 | "extraWideMedia": false 24 | } 25 | 26 | ``` 27 | 28 | 29 | Note that disabling the `extraWideMedia` setting will also disable unconstrained (full width) images. 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Zach Leatherman @zachleat 4 | Copyright (c) 2023 mendhak 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /pages/json.njk: -------------------------------------------------------------------------------- 1 | --- 2 | # Metadata comes from _data/metadata.json 3 | permalink: "{{ metadata.jsonfeed.path }}" 4 | eleventyExcludeFromCollections: true 5 | --- 6 | { 7 | "version": "https://jsonfeed.org/version/1.1", 8 | "title": "{{ metadata.title }}", 9 | "language": "{{ metadata.language }}", 10 | "home_page_url": "{{ metadata.url }}", 11 | "feed_url": "{{ metadata.jsonfeed.path | htmlBaseUrl(metadata.url) }}", 12 | "description": "{{ metadata.description }}", 13 | "author": { 14 | "name": "{{ metadata.author.name }}", 15 | "url": "{{ metadata.author.url }}" 16 | }, 17 | "items": [ 18 | {%- for post in collections.posts | reverse %} 19 | {%- set absolutePostUrl %}{{ post.url | htmlBaseUrl(metadata.url) }}{% endset -%} 20 | { 21 | "id": "{{ absolutePostUrl }}", 22 | "url": "{{ absolutePostUrl }}", 23 | "title": "{{ post.data.title }}", 24 | "content_html": {% if post.templateContent %}{{ post.templateContent | htmlToAbsoluteUrls(absolutePostUrl) | dump | safe }}{% else %}""{% endif %}, 25 | "date_published": "{{ post.date | dateToRfc3339 }}" 26 | } 27 | {%- if not loop.last -%} 28 | , 29 | {%- endif -%} 30 | {%- endfor %} 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /pages/feed.njk: -------------------------------------------------------------------------------- 1 | --- 2 | # Metadata comes from _data/metadata.json 3 | permalink: "{{ metadata.feed.path }}" 4 | eleventyExcludeFromCollections: true 5 | --- 6 | 7 | 8 | {{ metadata.title }} 9 | {{ metadata.description }} 10 | {% set absoluteUrl %}{{ metadata.feed.path | htmlBaseUrl(metadata.url) }}{% endset %} 11 | 12 | 13 | {{ collections.posts | getNewestCollectionItemDate | dateToRfc3339 }} 14 | {{ metadata.url }} 15 | 16 | {{ metadata.author.name }} 17 | {{ metadata.author.email }} 18 | 19 | {%- for post in collections.posts | reverse %} 20 | {% set absolutePostUrl %}{{ post.url | htmlBaseUrl(metadata.url) }}{% endset %} 21 | 22 | {{ post.data.title }} 23 | 24 | {{ post.date | dateToRfc3339 }} 25 | {{ absolutePostUrl }} 26 | {{ post.templateContent | htmlToAbsoluteUrls(absolutePostUrl) }} 27 | 28 | {%- endfor %} 29 | 30 | -------------------------------------------------------------------------------- /tests/post-with-image-gallery.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, expect, Page } from '@playwright/test'; 2 | 3 | let siteURL; 4 | 5 | test.beforeEach(async ({page, baseURL}) => { 6 | await page.goto('/'); 7 | siteURL = baseURL; 8 | }); 9 | 10 | 11 | 12 | test.describe('Post with image gallery', () => { 13 | 14 | test('Clicking an image produces a lightbox', async ({ page }) => { 15 | await page.goto('post-with-a-gallery/'); 16 | let clickableImages = await page.locator('figure a'); 17 | clickableImages.nth(1).scrollIntoViewIfNeeded(); 18 | clickableImages.nth(1).click(); 19 | await expect(page.getByText('Second Image')).toBeVisible(); 20 | 21 | await page.keyboard.press('Escape'); 22 | 23 | await expect(page.getByText('Three Two images side by side')).toBeVisible(); 24 | 25 | // get the image just above this 26 | let nearestImage = page.locator('figure a:near(:text("Three Two images side by side"))').last(); 27 | nearestImage.scrollIntoViewIfNeeded(); 28 | await expect(nearestImage).toBeVisible(); 29 | await nearestImage.click(); 30 | await expect(page.getByText('Second image')).toBeVisible(); 31 | await page.keyboard.press('Escape'); 32 | await expect(page.getByText('Second image')).not.toBeVisible(); 33 | 34 | }); 35 | 36 | }); 37 | -------------------------------------------------------------------------------- /tests/post-with-relative-links.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, expect, Page } from '@playwright/test'; 2 | 3 | let siteURL; 4 | 5 | test.beforeEach(async ({page, baseURL}) => { 6 | await page.goto('/'); 7 | siteURL = baseURL; 8 | }); 9 | 10 | 11 | test.describe('Posts with relative links', () => { 12 | 13 | test('Test a link using a markdown path', async ({ page }) => { 14 | await page.goto('posting-links/'); 15 | let link = await page.getByRole('link', { name: 'Link using Markdown path', exact: true }); 16 | await link.click(); 17 | expect(page.url()).toBe(siteURL + 'customary-lorem-ipsum/'); 18 | 19 | await page.goBack(); 20 | 21 | link = await page.getByRole('link', { name: 'Link using Markdown path and heading anchor', exact: true }); 22 | await link.click(); 23 | expect(page.url()).toBe(siteURL + 'customary-lorem-ipsum/#tincidunt-arcu-non-sodales'); 24 | 25 | await page.goBack(); 26 | 27 | link = await page.getByRole('link', { name: 'Link using root-relative URL', exact: true }); 28 | await link.click(); 29 | expect(page.url()).toBe(siteURL + 'customary-lorem-ipsum/'); 30 | 31 | await page.goBack(); 32 | 33 | link = await page.getByRole('button', { name: 'Button with a link', exact: true }); 34 | await link.click(); 35 | expect(page.url()).toBe(siteURL ); 36 | 37 | 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /tests/post-with-images.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, expect, Page } from '@playwright/test'; 2 | 3 | let siteURL; 4 | 5 | test.beforeEach(async ({page, baseURL}) => { 6 | await page.goto('/'); 7 | siteURL = baseURL; 8 | }); 9 | 10 | 11 | test.describe('Post with images', () => { 12 | 13 | test('Clicking an image produces a lightbox', async ({ page }) => { 14 | await page.goto('post-with-an-image/'); 15 | let clickableImages = await page.locator('figure a'); 16 | clickableImages.nth(0).scrollIntoViewIfNeeded(); 17 | await clickableImages.nth(0).click(); 18 | let lightbox = await page.locator('.sl-wrapper'); 19 | await expect(lightbox).toBeVisible(); 20 | 21 | // Ensure the URL is reset when lightbox is closed 22 | expect(await page).toHaveURL('post-with-an-image/#pid=1'); 23 | await page.waitForTimeout(200); 24 | await page.keyboard.press('Escape'); 25 | await page.keyboard.press('Escape'); 26 | await page.waitForTimeout(300); 27 | expect(lightbox).not.toBeVisible(); 28 | expect(await page).toHaveURL(siteURL + 'post-with-an-image/'); 29 | 30 | clickableImages.nth(2).scrollIntoViewIfNeeded(); 31 | await clickableImages.nth(2).click(); 32 | await expect(lightbox).toBeVisible(); 33 | await page.keyboard.press('Escape'); 34 | await page.keyboard.press('Escape'); 35 | await page.waitForTimeout(200); 36 | expect(await page).toHaveURL(siteURL + 'post-with-an-image/'); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /_configs/markdownlibrary.renderer.image.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Wrap images in a figure, a, and figcaption. 3 | * This lets the simplelightbox code serve it up too! 4 | * Also adds loading lazy attribute 5 | */ 6 | 7 | export default function (tokens, idx, options, env, slf, markdownLibrary) { 8 | 9 | const token = tokens[idx]; 10 | // Set the loading=lazy attribute 11 | token.attrSet('loading', 'lazy'); 12 | 13 | let captionRendered = markdownLibrary.renderInline(token.content); 14 | 15 | 16 | if (env.inGallery) { 17 | // This is a gallery of images, so display the caption in the lightbox (by setting its title), 18 | // and only return an image, because the gallery is taking care of the
. 19 | // This is because the caption might be too long and awkward to display 20 | // in a crowded area. 21 | token.attrSet('title', captionRendered); 22 | token.attrSet('style', "width: calc(33% - 0.5em);"); 23 | if (env.evenItems) { 24 | token.attrSet('style', "width: calc(50% - 0.5em);"); 25 | } 26 | 27 | return `${slf.renderToken(tokens, idx, options)}`; 28 | } 29 | 30 | // This is a standalone image, so return figure with figcaption. 31 | // The 'a' is the image linking to itself, which then gets picked up by simplelightbox 32 | return `
33 | ${slf.renderToken(tokens, idx, options)} 34 |
${captionRendered}
35 |
`; 36 | } 37 | -------------------------------------------------------------------------------- /posts/2022-12-20-github-repo-card.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Post with Github Repo Cards 3 | description: Widget to showcase a single repo 4 | 5 | --- 6 | 7 | How to display a Github repo card. Fetches the description, stars and forks. 8 | 9 | To display it on a page use the `githubrepocard` shortcode. 10 | 11 | ``` 12 | {% raw %}{% githubrepocard 'mendhak/gpslogger' %}{% endraw %} 13 | ``` 14 | 15 | Produces: 16 | 17 | {% githubrepocard 'mendhak/gpslogger' %} 18 | 19 | This should also work for third party Github repos, not just yours. 20 | 21 | ``` 22 | {% raw %}{% githubrepocard 'mozilla/send' %}{% endraw %} 23 | ``` 24 | 25 | Produces: 26 | 27 | {% githubrepocard 'mozilla/send' %} 28 | 29 | 30 | ### Special note about Github Repo Cards and rate limits 31 | 32 | If the Repo Card area start to appear empty sometimes, this could be because the IP address you ran the build from was rate limited by Github. This can commonly happen when running from Github Actions. 33 | 34 | If running from Github Actions, no need to do anything, the [automatically generated `GITHUB_TOKEN`](https://docs.github.com/en/actions/security-guides/automatic-token-authentication) should get passed in to the action and used by the API calls. 35 | 36 | If not running on Github Actions, you'll need to [generate a token on Github](https://github.com/settings/tokens) with the `repos` permission. Then pass that as a `--githubtoken` argument when running the `npm run build` or `npm run serve` commands, for example: 37 | 38 | ```bash 39 | npm run serve --githubtoken=xxxxxxxxxxxxxxxxx 40 | ``` 41 | -------------------------------------------------------------------------------- /tests/robots-feeds.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, expect, Page } from '@playwright/test'; 2 | 3 | let siteURL; 4 | 5 | test.beforeEach(async ({page, baseURL}) => { 6 | await page.goto('/'); 7 | siteURL = baseURL; 8 | }); 9 | 10 | test.describe('Robots and Feeds and Well Known Tests', () => { 11 | test('Verify Robots.txt', async ({ page }) => { 12 | await page.goto('robots.txt'); 13 | expect(page).toHaveURL(siteURL + 'robots.txt'); 14 | expect(await page.textContent('*')).toContain('Allow: /'); 15 | expect(await page.textContent('*')).toContain('Sitemap: '); 16 | expect(await page.textContent('*')).toMatch(/Sitemap: .+sitemap.xml/); 17 | }); 18 | 19 | test('Verify Atom Feed' , async ({ page }) => { 20 | await page.goto('feed.xml'); 21 | expect(page).toHaveURL(siteURL + 'feed.xml'); 22 | expect(await page.textContent('*')).toContain('Eleventy Satisfactory'); 23 | }); 24 | 25 | test('Verify JSON feed', async ({ page }) => { 26 | await page.goto('feed.json'); 27 | expect(page).toHaveURL(siteURL + 'feed.json'); 28 | let feedJSON = JSON.parse(await page.innerText('*')); 29 | expect(feedJSON.title).toBe('Eleventy Satisfactory'); 30 | }); 31 | 32 | 33 | test('Verify .well-known URL', async ({ page }) => { 34 | await page.goto('.well-known/example.json'); 35 | expect(page).toHaveURL(siteURL + '.well-known/example.json'); 36 | let wellKnown = (await page.innerText('*')); 37 | let wellKnownJSON = JSON.parse(wellKnown); 38 | expect(wellKnownJSON.eleventy).toBe('satisfactory'); 39 | }); 40 | 41 | 42 | }); 43 | -------------------------------------------------------------------------------- /posts/2022-02-02-set-date-of-post.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: How to set the date of a post 3 | last_modified_at: 2022-08-17 4 | --- 5 | 6 | When writing a new post, you can set the date of the post in a few different ways. The date of the post determines the order it appears in the post listings. 7 | 8 | ### In the filename 9 | 10 | The date can be set in the post's filename. For example, `{{ page.inputPath }}` will be published to the URL `{{ page.url }}` and the date appears as `{{ page.date | readableDate }}`. This is the best way to write posts, since having the date at the beginning helps organize the posts in order. 11 | 12 | ### In the frontmatter 13 | 14 | The date can also be set in the page's frontmatter. 15 | 16 | ``` 17 | --- 18 | title: How to set the date of a post 19 | date: 2022-02-02 20 | --- 21 | ``` 22 | 23 | ### Both the file name _and_ the frontmatter 24 | 25 | If the date is set both in the file name as well as the frontmatter, the frontmatter date takes precedence. 26 | 27 | 28 | ### Setting a last modified date for a post 29 | 30 | Setting the `last_modified_at` in the frontmatter modifies the display and metadata date, but without changing its order in the listing. 31 | 32 | ``` 33 | --- 34 | title: How to set the date of a post 35 | last_modified_at: 2022-08-17 36 | --- 37 | ``` 38 | 39 | 40 | ### Posts without a date 41 | 42 | If there's no date in the file name, or the frontmatter, then the date defaults to the time of build, so it gets updated each time the site is built. 43 | 44 | --- 45 | 46 | Anyway, the date appears at the bottom. 47 | 48 | 👇 49 | -------------------------------------------------------------------------------- /_configs/video.shortcode.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Paired shortcode to display an iframe video. Detects YouTube or Vimeo. 3 | */ 4 | 5 | export default function(url, width) { 6 | let style = ''; 7 | let iframeUrl = ''; 8 | if(width==="unconstrained"){ 9 | style="max-width: unset;"; 10 | } 11 | 12 | let youTubeId = getVideoIdFromYouTubeURL(url); 13 | let timeStamp = getYouTubeTimeStampFromURL(url); 14 | 15 | if(youTubeId){ 16 | iframeUrl = `https://www.youtube.com/embed/${youTubeId}${timeStamp}`; 17 | } 18 | else { 19 | let vimeoId = getVideoIdFromVimeoURL(url); 20 | iframeUrl = `https://player.vimeo.com/video/${vimeoId}`; 21 | } 22 | 23 | let videoHtml = `
24 | 25 |
`; 26 | 27 | return videoHtml; 28 | 29 | 30 | }; 31 | 32 | function getYouTubeTimeStampFromURL(url) { 33 | let regExp = /[?&]t=(?:(\d+)m(\d+)s|(\d+)s)/; 34 | 35 | let match = url.match(regExp); 36 | return match ? 37 | (match[1] && match[2] ? `?start=${match[1]*60 + parseInt(match[2])}` : 38 | match[3] ? `?start=${match[3]}` : '') : ''; 39 | } 40 | 41 | function getVideoIdFromYouTubeURL(url) { 42 | let regExp = /^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|&v=)([^#&?]*).*/; 43 | let match = url.match(regExp); 44 | 45 | return match && match[2].length === 11 ? match[2] : null; 46 | } 47 | 48 | function getVideoIdFromVimeoURL(url){ 49 | 50 | let regExp = /^https:\/\/vimeo\.com\/([^#&?]*)/; 51 | let match = url.match(regExp); 52 | return match && match[1] ? match[1] : null; 53 | 54 | } 55 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eleventy-satisfactory", 3 | "version": "1.0.0", 4 | "description": "My simplified opinionated eleventy theme, the focus is on content first, and less on other things.", 5 | "type": "module", 6 | "scripts": { 7 | "build": "npx @11ty/eleventy", 8 | "bench": "DEBUG=Eleventy:Benchmark* npx @11ty/eleventy", 9 | "start": "npx @11ty/eleventy --serve --watch --incremental", 10 | "debug": "DEBUG=* npx @11ty/eleventy", 11 | "pretest": "playwright install", 12 | "test": "playwright test --headed", 13 | "stop": "kill -9 $(lsof -t -i :8080)" 14 | }, 15 | "repository": { 16 | "type": "git", 17 | "url": "https://github.com/mendhak/eleventy-satisfactory.git" 18 | }, 19 | "author": { 20 | "name": "mendhak", 21 | "email": "mendhak@users.noreply.github.com", 22 | "url": "https://code.mendhak.com/" 23 | }, 24 | "license": "MIT", 25 | "bugs": { 26 | "url": "https://github.com/mendhak/eleventy-satisfactory/issues" 27 | }, 28 | "homepage": "https://code.mendhak.com/eleventy-satisfactory/", 29 | "dependencies": { 30 | "@11ty/eleventy": "^3.1.2", 31 | "@11ty/eleventy-plugin-rss": "^2.0.4", 32 | "@11ty/eleventy-plugin-syntaxhighlight": "^5.0.2", 33 | "clean-css": "^5.3.3", 34 | "dotenv": "^17.2.1", 35 | "luxon": "^3.7.1", 36 | "markdown-it": "^14.1.0", 37 | "markdown-it-anchor": "^9.0.1", 38 | "prism-themes": "^1.9.0", 39 | "simpledotcss": "2.1.1", 40 | "simplelightbox": "^2.14.3" 41 | }, 42 | "engines": { 43 | "node": ">=18.0.0" 44 | }, 45 | "devDependencies": { 46 | "@playwright/test": "^1.54.2", 47 | "@types/node": "^24.1.0" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /posts/2022-03-03-opengraph-preview-data.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: How to set Opengraph preview image and metadata 3 | description: Instructions on setting up various Opengraph headers 4 | tags: 5 | - opengraph 6 | - image 7 | - preview 8 | opengraph: 9 | image: /assets/images/image003.jpg 10 | --- 11 | 12 | When sharing a URL, some sites will generate a preview with a title, description and image using certain [OpenGraph](https://ogp.me/) metadata. 13 | 14 | ### Set the preview image 15 | 16 | A default Opengraph preview image for all posts is set in the [metadata.json](/posts/2022-01-01-edit-the-metadata.md#opengraph), which looks like: 17 | 18 | {% figure metadata.opengraph.image, "Default opengraph image", "third" %} 19 | 20 | 21 | To override the preview image for a specific post, set the `opengraph.image` value in the frontmatter: 22 | 23 | ``` 24 | --- 25 | title: How to set Opengraph preview image and metadata 26 | opengraph: 27 | image: /assets/images/image003.jpg 28 | --- 29 | ``` 30 | 31 | ### Title, description, and tags 32 | 33 | All three of these are taken from the post's frontmatter's title, description, and tags. 34 | 35 | ``` 36 | --- 37 | title: How to set Opengraph preview image and metadata 38 | description: Instructions on setting up various Opengraph headers 39 | tags: 40 | - opengraph 41 | - image 42 | - preview 43 | opengraph: 44 | image: /assets/images/image003.jpg 45 | --- 46 | ``` 47 | 48 | If missing, the defaults are taken from the [metadata.json](/posts/2022-01-01-edit-the-metadata.md#optional-but-useful) again. 49 | 50 | 51 | ### Author and type 52 | 53 | The author and type are always taken from the metadata.json author and type. 54 | -------------------------------------------------------------------------------- /_configs/figure.shortcode.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Paired shortcode to display a figure with caption. 3 | * This is very similar to the regular Markdown image, 4 | * But I'll keep this around in case the other way ever breaks in the future 5 | * Plus, this has the 'width' flexibility, and maybe more future features. 6 | */ 7 | export default function (image, caption, widthName, useLightbox, markdownLibrary) { 8 | 9 | let width = ''; 10 | switch (widthName) { 11 | case 'half': 12 | width = 'width: calc(50% - 0.5em);'; 13 | break; 14 | case 'third': 15 | width = 'width: calc(33% - 0.5em);'; 16 | break; 17 | case 'unconstrained': 18 | width = 'max-width: unset'; 19 | break; 20 | default: 21 | break; 22 | } 23 | 24 | let captionMarkup = ""; 25 | let linkOpen = "", linkClose = ""; 26 | if (caption !== undefined && caption !== "") { 27 | captionMarkup = markdownLibrary.renderInline(caption); 28 | } 29 | 30 | if(useLightbox){ 31 | // We've configure simplelightbox to render if there's a `figure > a`. 32 | linkOpen = ``; 33 | linkClose = ``; 34 | } 35 | 36 | let rendered = `
${linkOpen}${caption}${linkClose}
${captionMarkup}
`; 37 | if(widthName==='unconstrained'){ 38 | //Since it's the image's 100% size anyway, there's no point in giving it a lightbox. Just wrap it in a figure tag, so it gets centered at least. 39 | rendered = `
${caption}
${captionMarkup}
`; 40 | } 41 | 42 | return rendered; 43 | } 44 | 45 | 46 | -------------------------------------------------------------------------------- /assets/css/github.repocard.css: -------------------------------------------------------------------------------- 1 | .github-repo-card { 2 | --gh-bg-color: #fff; 3 | --gh-color: #586069; 4 | --gh-heading-color: #0366d6; 5 | font-family: var(--sans-font); 6 | width: fit-content; 7 | max-width: 50%; 8 | background-color: var(--gh-bg-color) !important; 9 | border: 1px solid var(--gh-color) !important; 10 | border-radius: 6px !important; 11 | padding: 16px !important; 12 | color: var(--gh-color) !important; 13 | } 14 | 15 | @media screen and (max-width: 1200px) { 16 | .github-repo-card { 17 | max-width: 80%; 18 | } 19 | } 20 | 21 | @media screen and (max-width: 800px) { 22 | .github-repo-card { 23 | max-width: 100%; 24 | } 25 | } 26 | 27 | @media (prefers-color-scheme: dark) { 28 | .github-repo-card { 29 | --gh-bg-color: #212224; 30 | --gh-color: #8b949e; 31 | --gh-heading-color: #58a6ff; 32 | } 33 | } 34 | 35 | .github-repo-card svg { 36 | fill: var(--gh-color); 37 | } 38 | 39 | .github-repo-card .d-flex { 40 | display: flex !important; 41 | margin-bottom: 4px !important; 42 | align-items: flex-start !important; 43 | justify-content: space-between !important; 44 | } 45 | 46 | .github-repo-card a { 47 | color: var(--gh-heading-color) !important; 48 | } 49 | 50 | .github-repo-card .stats-icons a { 51 | display: inline-block !important; 52 | margin-right: 24px !important; 53 | color: var(--gh-color) !important; 54 | font-size: 0.95rem !important; 55 | } 56 | 57 | .github-repo-card .github-repo-text { 58 | color: var(--gh-color) !important; 59 | font-size: 1rem; 60 | display: flex !important; 61 | white-space: normal !important; 62 | margin-bottom: 8px !important; 63 | } 64 | 65 | .github-repo-card .github-repo-title { 66 | font-weight: bolder; 67 | } 68 | -------------------------------------------------------------------------------- /tests/home-page.spec.ts: -------------------------------------------------------------------------------- 1 | import { test, expect, Page } from '@playwright/test'; 2 | 3 | let siteURL; 4 | 5 | test.beforeEach(async ({page, baseURL}) => { 6 | await page.goto('/'); 7 | siteURL = baseURL; 8 | }); 9 | 10 | 11 | test.describe('Home Page Tests', () => { 12 | 13 | test('Verify Location and Title', async ({ page}) => { 14 | await page.waitForSelector('header a'); 15 | const header = await page.locator('header a').innerText(); 16 | expect(page).toHaveURL(siteURL); 17 | expect(header).toBe('Eleventy Satisfactory'); 18 | }); 19 | 20 | test('Verify Footer Links', async ({ page }) => { 21 | await page.waitForSelector('footer'); 22 | 23 | const githubLink = await page.locator('footer').first().locator("a").nth(0).getAttribute('href'); 24 | expect(githubLink).toBe('https://github.com/mendhak/eleventy-satisfactory'); 25 | 26 | const eleventyLink = await page.locator('footer').first().locator("a").nth(1).getAttribute('href'); 27 | expect(eleventyLink).toBe('https://11ty.dev'); 28 | 29 | const simpleCssLink = await page.locator('footer').first().locator("a").nth(2).getAttribute('href'); 30 | expect(simpleCssLink).toBe('https://simplecss.org/'); 31 | 32 | const rssFeedLink = await page.getByText('FEED').nth(0); 33 | await rssFeedLink.click(); 34 | expect(page).toHaveURL(siteURL + 'feed.xml'); 35 | }); 36 | 37 | test('Verify Flickr Photos', async ({ page }) => { 38 | await page.waitForSelector('ul.photostream a img'); 39 | const photos = await page.locator('ul.photostream').locator('a img'); 40 | expect(await photos.count()).toBe(5); 41 | expect(await photos.nth(0)).toBeVisible(); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /posts/post-with-a-gallery.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Post with image galleries 3 | description: This post contains an image gallery in Markdown as well as shortcodes. 4 | tags: 5 | - eleventy 6 | - markdown 7 | - shortcode 8 | - images 9 | - gallery 10 | - lightbox 11 | 12 | opengraph: 13 | image: /assets/images/image001.jpg 14 | 15 | --- 16 | 17 | To display a set of images in a gallery, just surround the images, written as Markdown, with the `gallery` shortcode. 18 | 19 | Clicking the image shows the image in lightbox, with the title as the caption (a little Markdown might in the caption might also work). 20 | 21 | ``` 22 | {% raw %}{% gallery %} 23 | ![First image](/assets/images/image001.jpg) 24 | ![**Second** image](/assets/images/image002.jpg) 25 | ![`Third` image](/assets/images/image003.jpg) 26 | {% endgallery %}{% endraw %} 27 | ``` 28 | 29 | Produces this: 30 | 31 | {% gallery %} 32 | ![First image](/assets/images/image001.jpg) 33 | ![**Second** image](/assets/images/image002.jpg) 34 | ![`Third` image](/assets/images/image003.jpg) 35 | {% endgallery %} 36 | 37 | 38 | The image's own titles appear as captions in the lightbox, but not on the page, as it could get too crowded. 39 | 40 | ### Gallery with caption 41 | 42 | To set a caption for the gallery as a whole, pass it to the shortcode. 43 | 44 | ``` 45 | {% raw %}{% gallery "~~Three~~ Two images side by side" %} 46 | ![First image](/assets/images/image001.jpg) 47 | ![**Second** image](/assets/images/image002.jpg) 48 | {% endgallery %}{% endraw %} 49 | ``` 50 | 51 | Produces: 52 | 53 | {% gallery "~~Three~~ Two images side by side" %} 54 | ![First image](/assets/images/image001.jpg) 55 | ![**Second** image](/assets/images/image002.jpg) 56 | {% endgallery %} 57 | 58 | The gallery's caption appears on the page. The image's own captions still appear in the lightbox, just not on the page. 59 | -------------------------------------------------------------------------------- /.github/workflows/staticsite.yml: -------------------------------------------------------------------------------- 1 | 2 | name: Build and Publish 3 | 4 | # Trigger this workflow only when the main branches, assets folder is modified. 5 | on: 6 | push: 7 | branches: [ "main", "master" ] 8 | paths-ignore: 9 | - '**/README.md' 10 | - '.github/workflows/playwright.yml' 11 | - 'tests/**' 12 | 13 | 14 | # Allows you to run this workflow manually from the Actions tab 15 | workflow_dispatch: 16 | 17 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 18 | permissions: 19 | contents: read 20 | pages: write 21 | id-token: write 22 | deployments: write 23 | 24 | jobs: 25 | build: 26 | runs-on: ubuntu-latest 27 | 28 | environment: 29 | name: github-pages 30 | url: ${{ steps.deployment.outputs.page_url }} 31 | 32 | steps: 33 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 34 | - uses: actions/checkout@v3 35 | 36 | - name: Use Node.js 20 37 | uses: actions/setup-node@v3 38 | with: 39 | node-version: 20 40 | 41 | - name: Build the static site 42 | # If you use gists, generate a GH_GIST_TOKEN and add to repository action secrets. 43 | # If GH_GIST_TOKEN doesn't exist, it's an unauthenticated call to Gist API... 44 | # Unauthenticated call gets made to Gist API from Github Actions will result in rate limits and gists will fail to load. 45 | # You can't use secrets.GITHUB_TOKEN either, as it's scoped to the repository and not the gists! 46 | run: /bin/bash -c 'npm install;npm run build --gisttoken=${{ secrets.GH_GIST_TOKEN }} --githubtoken=${{ secrets.GITHUB_TOKEN }}' 47 | 48 | - name: Setup Github Pages 49 | uses: actions/configure-pages@v2 50 | 51 | - name: Upload Github Pages artifact 52 | uses: actions/upload-pages-artifact@v3 53 | 54 | - name: Deploy to GitHub Pages 55 | id: deployment 56 | uses: actions/deploy-pages@v4 57 | -------------------------------------------------------------------------------- /posts/2023-01-15-post-notice.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Post with notices or alert boxes 3 | description: I want to display what notice boxes would look like. 4 | tags: 5 | - notice 6 | 7 | --- 8 | 9 | This post will show how to create different kinds of notice or alert boxes such as a regular box, as well as info, success, warning, and danger. 10 | 11 | In all cases, use the paired shortcode `notice` with the notice type (info, success, warning, danger). Leave the notice type out for the regular notice box. You can use markdown inside. 12 | 13 | {% notice %} 14 | This is the default notice. You _can_ use Markdown. 15 | {% endnotice %} 16 | 17 | 18 | 19 | {% notice "info" %} 20 | This is an info notice. Info means [information](https://en.wikipedia.org/wiki/Information) 21 | {% endnotice %} 22 | 23 | 24 | {% notice "success" %} 25 | This is a success notice. Great Success. 26 | {% endnotice %} 27 | 28 | 29 | {% notice "warning" %} 30 | This is a warning notice. Don't use `GOTO` statements! 31 | {% endnotice %} 32 | 33 | {% notice "danger" %} 34 | This is a danger notice. **Smoking is fatal**. 35 | {% endnotice %} 36 | 37 | 38 | For a regular notice box, 39 | 40 | ``` 41 | {% raw %}{% notice %} 42 | This is the default notice. You _can_ use Markdown. 43 | {% endnotice %}{% endraw %} 44 | ``` 45 | 46 | For an info notice box, 47 | 48 | ``` 49 | {% raw %}{% notice "info" %} 50 | This is an info notice. Info means [information](https://en.wikipedia.org/wiki/Information) 51 | {% endnotice %}{% endraw %} 52 | ``` 53 | 54 | For a success notice box, 55 | 56 | ``` 57 | {% raw %}{% notice "success" %} 58 | This is a success notice. Great Success. 59 | {% endnotice %}{% endraw %} 60 | ``` 61 | 62 | For a warning notice box, 63 | 64 | ``` 65 | {% raw %}{% notice "warning" %} 66 | This is a warning notice. Don't use `GOTO` statements! 67 | {% endnotice %}{% endraw %} 68 | ``` 69 | 70 | For a danger notice box, 71 | 72 | ``` 73 | {% raw %}{% notice "danger" %} 74 | This is a danger notice. **Smoking is fatal**. 75 | {% endnotice %}{% endraw %} 76 | ``` 77 | -------------------------------------------------------------------------------- /_configs/gist.shortcode.js: -------------------------------------------------------------------------------- 1 | import MarkdownIt from "markdown-it"; 2 | 3 | /** 4 | * For a given Github Gist, render the gist's files as code blocks 5 | * @param {String} gistId - the ID of the Github Gist 6 | * @param {MarkdownIt} markdownLibrary - the instance of the markdown-it object to use to render the contents 7 | * @returns 8 | */ 9 | export default async function getGist(gistId, markdownLibrary) { 10 | let url = `https://api.github.com/gists/${gistId}`; 11 | let fetchOptions = {}; 12 | 13 | let gistJson = {}; 14 | let mdCode = ''; 15 | 16 | // If we're running in a Github Action, the token can be used to make Github API calls with better rate limits. 17 | // Otherwise, without this, sometimes the API call fails due to a low rate limit. 18 | // To pass the token, npm run build --gisttoken=${{ secrets.GH_GIST_TOKEN }} 19 | if (process.env.npm_config_gisttoken || process.env.GH_GIST_TOKEN) { 20 | let token = process.env.npm_config_gisttoken || process.env.GH_GIST_TOKEN; 21 | fetchOptions.headers = { 'Authorization': `Bearer ${token}` } 22 | } 23 | 24 | /* fetch() returns a promise, but await can be used inside addNunjucksAsyncShortcode */ 25 | let response = await fetch(url, fetchOptions); 26 | 27 | if (response.ok) { 28 | gistJson = await response.json(); 29 | } 30 | else { 31 | console.log(await response.json()); 32 | return ''; 33 | } 34 | 35 | 36 | let description = gistJson.description; 37 | 38 | for (var f in gistJson.files) { 39 | let language = gistJson.files[f].language; 40 | let content = gistJson.files[f].content 41 | let fileName = gistJson.files[f].filename; 42 | 43 | if (!language) { 44 | language = "text" 45 | } 46 | if (language === "C#") { 47 | language = "csharp"; 48 | } 49 | 50 | language = language.toLowerCase(); 51 | 52 | 53 | if (language === "markdown") { 54 | //Special case for MD, just render it outright. 55 | mdCode += `\n${content}\n`; 56 | } 57 | else { 58 | //Surround by code tags so it gets rendered with prism 59 | mdCode += `\`${fileName}\`\n\`\`\`${language}\n${content}\n\`\`\`\n`; 60 | } 61 | 62 | } 63 | 64 | return `**${description}**\n${markdownLibrary.render(mdCode)}`; 65 | } 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /posts/2022-12-19-post-with-github-gists.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Post with seamless Github Gists 3 | description: Using shortcode to put Github Gists on the page 4 | tags: 5 | - eleventy 6 | - github 7 | - gist 8 | - seamless 9 | 10 | --- 11 | 12 | Although it's possible to embed a gist using the shortcode that Github provides, it looks awful on dark mode. This post makes use of the `gist` shortcode, which will render each file as a regular code block so that the Gist's contents look like any other blog content. 13 | 14 | 15 | ### Github Gists through shortcode 16 | 17 | Just use the gist shortcode and give it the Gist ID which is easily visible [in the URL](https://gist.github.com/mendhak/37b74037637b5752741160058e243094). 18 | 19 | ``` 20 | {% raw %}{% gist "37b74037637b5752741160058e243094" %}{% endraw %} 21 | ``` 22 | 23 | The Gist description appears first, followed by each file name and the contents of the file. Here is the output: 24 | 25 | 26 | {% gist "37b74037637b5752741160058e243094" %} 27 | 28 | 29 | ### Github Gists with Markdown 30 | 31 | If a Github Gist file [contains Markdown like this one](https://gist.github.com/mendhak/770907f98223b22b422be8b5e09803ab), it'll be rendered directly onto the page. 32 | 33 | --- 34 | 35 | {% gist "770907f98223b22b422be8b5e09803ab" %} 36 | 37 | 38 | 39 | ### Special note about Github Gists and rate limits 40 | 41 | If the Gists start to appear empty sometimes, this could be because the IP address you ran the build from was rate limited by Github. This can commonly happen when running from Github Actions. 42 | 43 | To get gists to more reliably appear, [generate a token on Github](https://github.com/settings/tokens) with the `gists` permission. 44 | 45 | Under `Security > Secrets and variables > Actions` create a new secret named `GH_GIST_TOKEN` and paste that value in there. 46 | 47 | This secret value should get picked up the next time the action runs. 48 | 49 | If not running on Github Actions, you'll need to [generate a token on Github](https://github.com/settings/tokens) with the `gists` permission. Then pass that as a `--gisttoken` argument when running the `npm run build` or `npm run serve` commands, for example: 50 | 51 | ```bash 52 | npm run serve --gisttoken=xxxxxxxxxxxxxxxxx 53 | ``` 54 | 55 | Or set it as an environment variable: 56 | 57 | ```bash 58 | export GH_GIST_TOKEN=xxxxxxxxxxxxxxxxx 59 | npm run serve 60 | ``` 61 | 62 | -------------------------------------------------------------------------------- /posts/2023-01-05-post-with-code.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Post with some code blocks 3 | description: This post contains sample code blocks 4 | date: 2023-01-05 5 | tags: 6 | - eleventy 7 | - markdown 8 | - code 9 | 10 | --- 11 | 12 | A post with some code samples. These are using the Prism CSS library. 13 | 14 | 15 | ### Basic code blocks using indents 16 | 17 | Any text that is indented by 4 spaces will be treated as a preformatted block. 18 | 19 | console.log("hello"); 20 | 21 | ### Basic code blocks using backticks 22 | 23 | Another way to do the same thing is surround the code with 3 backticks. 24 | 25 | 26 | ` ``` ` 27 | `console.log('hello'); ` 28 | ` ``` ` 29 | 30 | Produces: 31 | 32 | ``` 33 | console.log('hello'); 34 | ``` 35 | 36 | ### Code blocks with syntax highlighting 37 | 38 | To get colorful syntax highlighting, follow the 3 backtickes with a [language name](https://prismjs.com/#languages-list). 39 | 40 | For example ` ```javascript`, then add the code, and close the block with ` ``` ` 41 | 42 | Here is some Javascript 43 | 44 | ```javascript 45 | //If there's a JWT header, parse it and decode and put it in the response 46 | if (process.env.JWT_HEADER) { 47 | let token = req.headers[process.env.JWT_HEADER.toLowerCase()]; 48 | if (!token) { 49 | echo.jwt = token; 50 | } else { 51 | token = token.split(" ").pop(); 52 | const decoded = jwt.decode(token, {complete: true}); 53 | echo.jwt = decoded; 54 | } 55 | } 56 | 57 | ``` 58 | 59 | Similarly for C#, use ` ```csharp` 60 | 61 | ```csharp 62 | static async Task FindBucketLocationAsync(IAmazonS3 client) 63 | { 64 | string bucketLocation; 65 | var request = new GetBucketLocationRequest() 66 | { 67 | BucketName = bucketName 68 | }; 69 | GetBucketLocationResponse response = await client.GetBucketLocationAsync(request); 70 | bucketLocation = response.Location.ToString(); 71 | return bucketLocation; 72 | } 73 | ``` 74 | 75 | 76 | Have some ` ```bash` 77 | 78 | ```bash 79 | #!/usr/bin/env bash 80 | 81 | set -euo pipefail 82 | 83 | function message { 84 | echo "" 85 | echo "---------------------------------------------------------------" 86 | echo $1 87 | echo "---------------------------------------------------------------" 88 | } 89 | 90 | if ! [ -x "$(command -v jq)" ]; then 91 | message "JQ not installed. Installing..." 92 | sudo apt -y install jq 93 | fi 94 | ``` 95 | 96 | 97 | -------------------------------------------------------------------------------- /playwright.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig, devices } from '@playwright/test'; 2 | 3 | /** 4 | * Read environment variables from file. 5 | * https://github.com/motdotla/dotenv 6 | */ 7 | // require('dotenv').config(); 8 | 9 | /** 10 | * See https://playwright.dev/docs/test-configuration. 11 | */ 12 | export default defineConfig({ 13 | testDir: './tests', 14 | /* Run tests in files in parallel */ 15 | fullyParallel: true, 16 | /* Fail the build on CI if you accidentally left test.only in the source code. */ 17 | forbidOnly: !!process.env.CI, 18 | /* Retry on CI only */ 19 | retries: process.env.CI ? 2 : 0, 20 | /* Opt out of parallel tests on CI. */ 21 | workers: process.env.CI ? 1 : undefined, 22 | /* Reporter to use. See https://playwright.dev/docs/test-reporters */ 23 | reporter: process.env.CI ? 'github' : 'html', 24 | /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ 25 | use: { 26 | /* Base URL to use in actions like `await page.goto('/')`. */ 27 | // baseURL: 'http://127.0.0.1:3000', 28 | 29 | /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ 30 | trace: 'on-first-retry', 31 | screenshot: 'only-on-failure', 32 | baseURL: 'http://localhost:8080/eleventy-satisfactory/', 33 | }, 34 | 35 | /* Configure projects for major browsers */ 36 | projects: [ 37 | // { 38 | // name: 'chromium', 39 | // use: { ...devices['Desktop Chrome'] }, 40 | // }, 41 | 42 | { 43 | name: 'firefox', 44 | use: { ...devices['Desktop Firefox'] }, 45 | }, 46 | 47 | 48 | /* Test against mobile viewports. */ 49 | // { 50 | // name: 'Mobile Chrome', 51 | // use: { ...devices['Pixel 5'] }, 52 | // }, 53 | // { 54 | // name: 'Mobile Safari', 55 | // use: { ...devices['iPhone 12'] }, 56 | // }, 57 | 58 | /* Test against branded browsers. */ 59 | // { 60 | // name: 'Microsoft Edge', 61 | // use: { ...devices['Desktop Edge'], channel: 'msedge' }, 62 | // }, 63 | // { 64 | // name: 'Google Chrome', 65 | // use: { ...devices['Desktop Chrome'], channel: 'chrome' }, 66 | // }, 67 | ], 68 | 69 | /* Run your local dev server before starting the tests */ 70 | webServer: { 71 | command: 'npm run start', 72 | url: 'http://127.0.0.1:8080', 73 | reuseExistingServer: !process.env.CI, 74 | }, 75 | }); 76 | -------------------------------------------------------------------------------- /posts/2022-11-22-post-with-iframes-videos-third-party.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Post with some videos, audio, and other media 3 | description: This post contains sample videos using iframe 4 | tags: 5 | - eleventy 6 | - iframe 7 | - videos 8 | - third-party 9 | extraWideMedia: true 10 | 11 | --- 12 | 13 | It's common to place third party audio, video, gists, etc. on a page using iframes. There's a shortcode to do this for YouTube and Vimeo. 14 | 15 | 16 | ## Video shortcode 17 | 18 | The `video` shortcode can work with YouTube and Vimeo URLs. 19 | 20 | ### Youtube Video 21 | 22 | This code: 23 | 24 | ``` 25 | {% raw %}{% video "https://www.youtube.com/embed/9qOvG9KeJ6c" %}{% endraw %} 26 | ``` 27 | 28 | Produces: 29 | 30 | {% video "https://www.youtube.com/embed/9qOvG9KeJ6c" %} 31 | 32 | 33 | 34 | ### Vimeo Video 35 | 36 | The same shortcode can be used for Vimeo videos. 37 | 38 | ``` 39 | {% raw %}{% video "https://vimeo.com/35396305" %}{% endraw %} 40 | ``` 41 | 42 | Produces: 43 | 44 | {% video "https://vimeo.com/35396305" %} 45 | 46 | 47 | ### Unconstrained full width video 48 | 49 | Add the argument `unconstrained` to the shortcode to let the video go full width across the page. 50 | 51 | ``` 52 | {% raw %}{% video "https://www.youtube.com/watch?v=DppVAQqaNE4", "unconstrained" %}{% endraw %} 53 | ``` 54 | 55 | Produces: 56 | 57 | {% video "https://www.youtube.com/watch?v=DppVAQqaNE4", "unconstrained" %} 58 | 59 | 60 | ## Straight up HTML 61 | 62 | It is possible to paste the iframe code directly in Markdown. This works with YouTube, Vimeo, Twitch and any other video sources that provide HTML embed code. 63 | 64 | Remember to remove the height and width, and wrap the iframe code in a div with class="video" as shown here. 65 | 66 | ```html 67 |
68 | 69 |
70 | ``` 71 | 72 | Produces: 73 | 74 |
75 | 76 |
77 | 78 | 79 | 80 | 81 | ## Soundcloud audio 82 | 83 | For Soundcloud, there's no need to change the height and width. Just copy pasting the iframe code works. 84 | 85 |
86 | 87 |
88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /_includes/github.repocard.njk: -------------------------------------------------------------------------------- 1 | 42 | -------------------------------------------------------------------------------- /posts/posting-links.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: How to post links to another post or an external site 3 | description: Various ways to post links to markdown files, relative URLs or absolute URLs 4 | date: 2022-03-01 5 | tags: 6 | - second tag 7 | - posts with two tags 8 | 9 | --- 10 | 11 | Various ways to post links to other posts in the same blog, and external links too. 12 | 13 | ### Link to another post by its file name 14 | 15 | This is a local editor friendly way, just link straight to the Markdown file. 16 | The link must start at the project root, and end in `.md`. 17 | 18 | ```markdown 19 | [Link using Markdown path](/posts/customary-lorem-ipsum.md) 20 | ``` 21 | 22 | Produces: 23 | 24 | [Link using Markdown path](/posts/customary-lorem-ipsum.md) 25 | 26 | You can also link to an anchor in the target file. 27 | 28 | ```markdown 29 | [Link using Markdown path and heading anchor](/posts/customary-lorem-ipsum.md#tincidunt-arcu-non-sodales) 30 | ``` 31 | 32 | Produces: 33 | 34 | [Link using Markdown path and heading anchor](/posts/customary-lorem-ipsum.md#tincidunt-arcu-non-sodales) 35 | 36 | #### Links inside `notice` and `figure` 37 | 38 | ```markdown 39 | {% raw %}{% notice "warning" %}{% endraw %} 40 | You can also link inside shortcodes such as `notice` and `figure`, [pointing at the file](/posts/customary-lorem-ipsum.md). 41 | You can also link to a heading anchor in the file [like this](/posts/customary-lorem-ipsum.md#tincidunt-arcu-non-sodales). 42 | {% raw %}{% endnotice %}{% endraw %} 43 | ``` 44 | Produces: 45 | 46 | {% notice "warning" %} 47 | You can also link inside shortcodes such as `notice` and `figure`, [pointing at the file](/posts/customary-lorem-ipsum.md). 48 | You can also link to a heading anchor in the file [like this](/posts/customary-lorem-ipsum.md#tincidunt-arcu-non-sodales). 49 | {% endnotice %} 50 | 51 | ### Link to another post by URL 52 | 53 | You can also link to the output URL of the post. The blog's `pathPrefix` gets added on automatically. Remember to include the trailing slash. 54 | 55 | ```markdown 56 | [Link using root-relative URL](/customary-lorem-ipsum/) 57 | ``` 58 | 59 | Produces: 60 | 61 | [Link using root-relative URL](/customary-lorem-ipsum/) 62 | 63 | 64 | ### External links 65 | 66 | 67 | ```markdown 68 | [External link](https://example.com) 69 | ``` 70 | 71 | Produces: 72 | 73 | [External link](https://example.com) 74 | 75 | ### Link with a title 76 | 77 | ```markdown 78 | [Link with a title](https://example.com "title text!") 79 | ``` 80 | Produces: 81 | 82 | [Link with a title](https://example.com "title text!") 83 | 84 | 85 | ## Buttons 86 | 87 | It's also possible to use a button as a link. 88 | 89 | ``` 90 | {% raw %}{% button "Button with a link", "/" %}{% endraw %} 91 | ``` 92 | 93 | Produces: 94 | 95 | {% button "Button with a link", "/" %} 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /posts/markdown-showcase.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Showcase of various `Markdown` features 3 | description: Trying out various Markdown features on a post 4 | date: 2018-07-04 5 | tags: 6 | - number 2 7 | 8 | --- 9 | 10 | This page demos various markdown syntax and how it's rendered. 11 | 12 | 13 | # h1 Heading 14 | ## h2 Heading 15 | ### h3 Heading 16 | #### h4 Heading 17 | ##### h5 Heading 18 | ###### h6 Heading 19 | 20 | 21 | ## Horizontal Rules 22 | 23 | ___ 24 | 25 | --- 26 | 27 | *** 28 | 29 | 30 | ## Typographic replacements 31 | 32 | Enable typographer option to see result. 33 | 34 | (c) (C) (r) (R) (tm) (TM) (p) (P) +- 35 | 36 | test.. test... test..... test?..... test!.... 37 | 38 | !!!!!! ???? ,, -- --- 39 | 40 | "Smartypants, double quotes" and 'single quotes' 41 | 42 | 43 | ## Emphasis 44 | 45 | **This is bold text** 46 | 47 | __This is bold text__ 48 | 49 | *This is italic text* 50 | 51 | _This is italic text_ 52 | 53 | ~~Strikethrough~~ 54 | 55 | 56 | ## Blockquotes 57 | 58 | 59 | > Blockquotes can also be nested... 60 | >> ...by using additional greater-than signs right next to each other... 61 | > > > ...or with spaces between arrows. 62 | 63 | 64 | ## Lists 65 | 66 | Unordered 67 | 68 | + Create a list by starting a line with `+`, `-`, or `*` 69 | + Sub-lists are made by indenting 2 spaces: 70 | - Marker character change forces new list start: 71 | * Ac tristique libero volutpat at 72 | + Facilisis in pretium nisl aliquet 73 | - Nulla volutpat aliquam velit 74 | + Very easy! 75 | 76 | Ordered 77 | 78 | 1. Lorem ipsum dolor sit amet 79 | 2. Consectetur adipiscing elit 80 | 3. Integer molestie lorem at massa 81 | 82 | 83 | 1. You can use sequential numbers... 84 | 1. ...or keep all the numbers as `1.` 85 | 86 | Start numbering with offset: 87 | 88 | 57. foo 89 | 1. bar 90 | 91 | 92 | ## Code 93 | 94 | Inline `code` 95 | 96 | Indented code 97 | 98 | // Some comments 99 | line 1 of code 100 | line 2 of code 101 | line 3 of code 102 | 103 | 104 | Block code "fences" 105 | 106 | ``` 107 | Sample text here... 108 | ``` 109 | 110 | Syntax highlighting 111 | 112 | ``` js 113 | var foo = function (bar) { 114 | return bar++; 115 | }; 116 | 117 | console.log(foo(5)); 118 | ``` 119 | 120 | ## Tables 121 | 122 | | Option | Description | 123 | | ------ | ----------- | 124 | | data | path to data files to supply the data that will be passed into templates. | 125 | | engine | engine to be used for processing templates. Handlebars is the default. | 126 | | ext | extension to be used for dest files. | 127 | 128 | Right aligned columns 129 | 130 | | Option | Description | 131 | | ------:| -----------:| 132 | | data | path to data files to supply the data that will be passed into templates. | 133 | | engine | engine to be used for processing templates. Handlebars is the default. | 134 | | ext | extension to be used for dest files. | 135 | 136 | 137 | ## Links 138 | 139 | [link text](https://example.com) 140 | 141 | [link with title](https://example.com "title text!") 142 | 143 | [link to another post](/customary-lorem-ipsum/) 144 | 145 | 146 | ## Images 147 | 148 | ![Minion](https://octodex.github.com/images/minion.png) 149 | ![Stormtroopocat](https://octodex.github.com/images/stormtroopocat.jpg "The Stormtroopocat") 150 | 151 | Like links, Images also have a footnote style syntax 152 | 153 | ![Alt text][id] 154 | 155 | With a reference later in the document defining the URL location: 156 | 157 | [id]: https://octodex.github.com/images/dojocat.jpg "The Dojocat" 158 | 159 | 160 | -------------------------------------------------------------------------------- /_configs/githubrepocard.shortcode.js: -------------------------------------------------------------------------------- 1 | import CleanCSS from "clean-css"; 2 | import nunjucks from "nunjucks"; 3 | /** 4 | * Generates a Github Repo Card for a repository given a repo slug. 5 | * It calls the Github API to get the information out. 6 | * If this returns empty strings, you might be rate limited. See comment below. 7 | * @returns 8 | */ 9 | export default async function(repoSlug){ 10 | 11 | let css = getCss(); 12 | 13 | let repo = await getGithubApiRepoResponse(repoSlug); 14 | 15 | if(!repo){ 16 | return ''; 17 | } 18 | 19 | let html = getHtml(repo); 20 | 21 | return css + html; 22 | } 23 | 24 | class GithubApiRepoResponse{ 25 | repoSlug; 26 | repoLink; 27 | description; 28 | stargazers; 29 | stargazersLink; 30 | forks; 31 | networkLink; 32 | language; 33 | } 34 | 35 | /** 36 | * 37 | * @param {String} repoSlug - the username/reponame format slug of the Github repo 38 | * @returns {GithubApiRepoResponse} 39 | */ 40 | async function getGithubApiRepoResponse(repoSlug){ 41 | let url = `https://api.github.com/repos/${repoSlug}`; 42 | let fetchOptions = {}; 43 | 44 | // If we're running in a Github Action, the token can be used to make Github API calls with better rate limits. 45 | // Otherwise, without this, sometimes the API call fails due to a low rate limit. 46 | // To pass the token, npm run build --githubtoken=${{ secrets.GITHUB_TOKEN }} 47 | if (process.env.npm_config_githubtoken || process.env.GITHUB_TOKEN) { 48 | let token = process.env.npm_config_githubtoken || process.env.GITHUB_TOKEN; 49 | fetchOptions.headers = { 'Authorization': `Bearer ${token}` } 50 | } 51 | 52 | let response = await fetch(url, fetchOptions); 53 | let githubJson = {}; 54 | 55 | if(!response.ok){ 56 | console.log(await response.json()); 57 | return null; 58 | } 59 | 60 | githubJson = await response.json(); 61 | 62 | let repo = new GithubApiRepoResponse(); 63 | repo.repoSlug = repoSlug; 64 | repo.repoLink = `https://github.com/${repoSlug}`; 65 | repo.stargazers = githubJson.stargazers_count; 66 | repo.stargazersLink = `${repo.repoLink}/stargazers`; 67 | repo.forks = githubJson.forks_count; 68 | repo.networkLink = `${repo.repoLink}/network/members`; 69 | repo.description = githubJson.description; 70 | repo.language = githubJson.language; 71 | 72 | return repo; 73 | } 74 | 75 | /** 76 | * 77 | * @param {GithubApiRepoResponse} repo 78 | * @returns {String} - Single line HTML containing the markup for a Github repo card 79 | */ 80 | function getHtml(repo){ 81 | nunjucks.configure({ trimBlocks: true, lstripBlocks: true }); 82 | let htmlBox = nunjucks.render('_includes/github.repocard.njk', 83 | { 84 | repoSlug: repo.repoSlug, 85 | repoLink: repo.repoLink, 86 | description: repo.description, 87 | stargazers: repo.stargazers, 88 | stargazersLink: repo.stargazersLink, 89 | forks: repo.forks, 90 | networkLink: repo.networkLink, 91 | language: repo.language 92 | } 93 | ); 94 | htmlBox = htmlBox.replace(/(\r\n|\n|\r)/gm, ""); 95 | return htmlBox; 96 | } 97 | 98 | function getCss(){ 99 | let css = new CleanCSS().minify(['assets/css/github.repocard.css']).styles; 100 | css = ``; 101 | return css; 102 | } 103 | -------------------------------------------------------------------------------- /posts/2022-01-01-edit-the-metadata.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Set up the metadata.json 3 | description: Setting up and configuring this theme. 4 | --- 5 | 6 | The [metadata.json file](https://github.com/mendhak/eleventy-satisfactory/blob/main/_data/metadata.json) contains various configuration items that are needed to set up the blog. It controls things like title, URLs, tags, feeds, and some links. The `metadata.json` file is located under [`_data/metadata.json`](https://github.com/mendhak/eleventy-satisfactory/blob/main/_data/metadata.json) 7 | 8 | Starting with the important ones. 9 | 10 | ### title 11 | 12 | The site title that appears in the header. eg, "Joe's Blog" 13 | 14 | {% notice "info" %} 15 | Current value: `{{ metadata.title | safe }}` 16 | {% endnotice %} 17 | 18 | ### pathPrefix 19 | 20 | Very important. The path where the blog will sit under its domain. 21 | 22 | Examples: 23 | 24 | - `/blog/` if it's hosted on 'https:\//example.com/blogs' 25 | - `/` if it's hosted on 'https:\//example.com' 26 | 27 | {% notice "info" %} 28 | Current value: `{{ metadata.pathPrefix }}` 29 | {% endnotice %} 30 | 31 | ### url 32 | 33 | The Base URL where this site will be published. It gets used to construct the full URL to content, in the [sitemap.xml](/sitemap.xml), [feed.xml](/feed.xml), [feed.json](/feed.json), and the OpenGraph and JSON-LD headers in the HTML ``. 34 | 35 | Examples: 36 | 37 | * `https://username.github.io/` 38 | * `https://example.com/blog/` 39 | 40 | {% notice "info" %} 41 | Current value: `{{ metadata.url }}` 42 | {% endnotice %} 43 | 44 | 45 | This value gets used to construct and correct various URLs throughout all content, such as images, links, stylesheets, etc. 46 | 47 | 48 | ## Optional, but useful 49 | 50 | ### paginationSize 51 | 52 | How many post links should be shown per page. 53 | 54 | {% notice "info" %} 55 | Current value: `{{ metadata.paginationSize }}` 56 | {% endnotice %} 57 | 58 | ### description 59 | 60 | Sets the site description in the HTML head's meta and OpenGraph/JSON-LD descriptions, as well as the JSON and Atom feed. 61 | If a blog post doesn't contain a `description:` frontmatter, this value gets used as a default in the HTML head. 62 | 63 | {% notice "info" %} 64 | Current value: `{{ metadata.description }}` 65 | {% endnotice %} 66 | 67 | ### tags 68 | 69 | Sets the site's tags in the HTML head's meta keywords and OpenGraph/JSON-LD tags. 70 | If a blog post doesn't contain a `tags:` frontmatter, these values get used as a default in the HTML head. 71 | 72 | {% notice "info" %} 73 | Current value: `{{ metadata.tags | dump | safe }}` 74 | {% endnotice %} 75 | 76 | ### language 77 | 78 | Sets the HTML `lang` of the web page. It helps search engines and browsers. 79 | 80 | {% notice "info" %} 81 | Current value: `{{ metadata.language }}` 82 | {% endnotice %} 83 | 84 | ### favicon 85 | 86 | Path to the favicon. Add your own image to the `/assets/images/` folder and update the location. 87 | 88 | {% notice "info" %} 89 | Current value: `{{ metadata.favicon }}` 90 | {% endnotice %} 91 | 92 | ### feed.path 93 | 94 | The path where the Atom feed should be written to. 95 | 96 | {% notice "info" %} 97 | Current value: `{{ metadata.feed.path }}` 98 | {% endnotice %} 99 | 100 | ### jsonfeed.path 101 | 102 | The path where the JSON feed should be written to. 103 | 104 | {% notice "info" %} 105 | Current value: `{{ metadata.jsonfeed.path }}` 106 | {% endnotice %} 107 | 108 | ### author 109 | 110 | `name`: The author pseudonym or name that should appear in the HTML meta as well as the site's footer. 111 | `email`: The author email that should appear in the Atom feed. 112 | `url`: The URL that should appear in the Atom feed. 113 | 114 | {% notice "info" %} 115 | Current value: `{{ metadata.author | dump | safe }}` 116 | {% endnotice %} 117 | 118 | ### opengraph 119 | 120 | Some opengraph settings. 121 | 122 | `image`: The image to use when an OpenGraph preview is being made for a URL on this site. If a blog post doesn't have a `opengraph.image` frontmatter, then this default image gets used instead. This value is also used in the JSON-LD data. 123 | `type`: The OpenGraph content type of this content. It's a blog so, it's an article. 124 | 125 | {% notice "info" %} 126 | Current value: `{{ metadata.opengraph | dump | safe }}` 127 | {% endnotice %} 128 | 129 | 130 | -------------------------------------------------------------------------------- /posts/2021-12-24-post-with-various-headings.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Showcase of various heading sizes 3 | description: From H1 to H5 and some blockquotes 4 | tags: xmas 5 | opengraph: 6 | image: https://live.staticflickr.com/1932/30454355997_287063f84b_q.jpg 7 | --- 8 | 9 | A few different heading levels from H2 to H6. H1 is already used by the post title. H2 to H4 get permalinks, but H5 and H6 don't. 10 | 11 | ## Malesuada fames 12 | 13 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Gravida in fermentum et sollicitudin ac orci phasellus egestas. Non tellus orci ac auctor augue mauris augue. Amet consectetur adipiscing elit duis tristique. Arcu ac tortor dignissim convallis aenean. A cras semper auctor neque vitae. Donec massa sapien faucibus et molestie ac. Platea dictumst vestibulum rhoncus est pellentesque. Viverra nam libero justo laoreet sit amet cursus sit. Amet volutpat consequat mauris nunc congue nisi. Cras ornare arcu dui vivamus arcu. 14 | 15 | Malesuada fames ac turpis egestas. Porta nibh venenatis cras sed felis eget velit aliquet. Sagittis id consectetur purus ut faucibus pulvinar elementum integer enim. Arcu odio ut sem nulla pharetra. Rutrum tellus pellentesque eu tincidunt tortor aliquam nulla facilisi. Sed vulputate mi sit amet mauris commodo quis imperdiet massa. Ut etiam sit amet nisl purus in mollis nunc. Elit scelerisque mauris pellentesque pulvinar pellentesque habitant morbi tristique senectus. Egestas egestas fringilla phasellus faucibus scelerisque eleifend. Etiam tempor orci eu lobortis elementum nibh tellus molestie nunc. Integer feugiat scelerisque varius morbi. Velit ut tortor pretium viverra suspendisse. Lectus quam id leo in vitae turpis. Consequat id porta nibh venenatis cras sed felis eget. 16 | 17 | ### Scelerisque eleifend 18 | 19 | Scelerisque eleifend donec pretium vulputate sapien nec sagittis aliquam malesuada. 20 | 21 | > Viverra justo nec ultrices dui sapien eget mi proin. Tortor at risus viverra adipiscing at in tellus. Elementum curabitur vitae nunc sed velit dignissim. Accumsan lacus vel facilisis volutpat est. Pellentesque eu tincidunt tortor aliquam nulla facilisi cras fermentum odio. Viverra ipsum nunc aliquet bibendum enim facilisis gravida neque. Turpis nunc eget lorem dolor sed viverra ipsum nunc. Cursus risus at ultrices mi tempus imperdiet nulla malesuada pellentesque. Donec et odio pellentesque diam. 22 | 23 | Tempus quam pellentesque nec nam aliquam. Fringilla est ullamcorper eget nulla facilisi etiam dignissim. Ullamcorper velit sed ullamcorper morbi tincidunt ornare massa eget. Tellus id interdum velit laoreet id donec ultrices tincidunt. Platea dictumst vestibulum rhoncus est pellentesque elit ullamcorper. Faucibus et molestie ac feugiat. 24 | 25 | 26 | #### Amet purus gravida 27 | 28 | Amet purus gravida quis blandit turpis cursus in hac habitasse. Risus feugiat in ante metus dictum at tempor commodo ullamcorper. Mus mauris vitae ultricies leo integer malesuada nunc. At varius vel pharetra vel. Sed viverra ipsum nunc aliquet. In fermentum posuere urna nec tincidunt praesent semper. Sed vulputate odio ut enim. Vel fringilla est ullamcorper eget nulla facilisi etiam dignissim. Etiam non quam lacus suspendisse faucibus interdum posuere lorem. Volutpat maecenas volutpat blandit aliquam etiam erat. Diam quis enim lobortis scelerisque fermentum dui faucibus in ornare. Vitae turpis massa sed elementum tempus egestas sed. Sed risus pretium quam vulputate dignissim suspendisse in est. Odio morbi quis commodo odio. Quis viverra nibh cras pulvinar. Velit egestas dui id ornare arcu odio ut sem nulla. 29 | 30 | > Quis enim lobortis scelerisque fermentum dui faucibus in ornare. Pellentesque nec nam aliquam sem et. Urna molestie at elementum eu facilisis sed odio morbi. Metus aliquam eleifend mi in nulla posuere sollicitudin aliquam ultrices. Gravida neque convallis a cras semper. Dui sapien eget mi proin sed. Dictumst vestibulum rhoncus est pellentesque elit ullamcorper dignissim cras tincidunt. Parturient montes nascetur ridiculus mus mauris vitae ultricies leo. Imperdiet proin fermentum leo vel orci porta non pulvinar neque. Elit ullamcorper dignissim cras tincidunt. Laoreet sit amet cursus sit amet. At quis risus sed vulputate odio ut. Massa tincidunt nunc pulvinar sapien et. 31 | 32 | ##### Amet purus gravida 33 | 34 | Only H1 to H4s get permalinks. Don't want to go overboard. 35 | 36 | 37 | ###### Amet purus gravida 38 | 39 | That's as far as we'll go. 40 | -------------------------------------------------------------------------------- /_includes/layouts/base.njk: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {{ title or metadata.title }} 8 | 9 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | {% if includeJsonLD %} 53 | 70 | {% endif %} 71 | 72 | 73 | 74 | 75 |
76 | {{ metadata.title }} 77 |
78 |
79 |

{%- if title %}{{ title | markdown | safe }}{%- endif %}

80 | {{ content | safe }} 81 |
82 | 83 |
84 | 85 | 90 | 91 |

— © {% year %} {{ metadata.author.name }} —

92 | 93 |
    94 | {%- for photo in photostream -%} 95 |
  • thumbnail
  •     96 | {%- endfor -%} 97 |
98 |
99 | 100 | 101 | {# Detects if images are in the content, it will add the simplelightbox JS/CSS if needed. #} 102 | {% addLightBoxRefIfNecessary %} 103 | 104 | 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /posts/post-with-an-image.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Post with various images 3 | description: There are different ways to post images! 4 | tags: 5 | - eleventy 6 | - markdown 7 | - shortcode 8 | - images 9 | - lightbox 10 | 11 | opengraph: 12 | image: /assets/images/image001.jpg 13 | 14 | --- 15 | 16 | There are a few different ways to display an image in a post. 17 | 18 | 19 | 20 | ## Using regular Markdown syntax 21 | 22 | Using regular Markdown syntax is possible. The image gets rendered as a figure with a caption. Clicking the image displays it in a lightbox. 23 | 24 | ``` 25 | {% raw %}![A little `markdown` can work be used _here_](/assets/images/image003.jpg){% endraw %} 26 | ``` 27 | 28 | Which results in: 29 | 30 | ![A little `markdown` can work be used _here_](/assets/images/image003.jpg) 31 | 32 | 33 | ## Using the `figure` shortcode 34 | 35 | The `figure` shortcode is similar to the above, but it has some extra features. 36 | It produces a figure with an image, and a figcaption that supports Markdown. 37 | There are some width options, and the lightbox can be disabled. 38 | 39 | Here is the code: 40 | 41 | ``` 42 | {% raw %}{% figure "/assets/images/image001.jpg", "Your **caption**" %}{% endraw %} 43 | ``` 44 | 45 | Here is the output: 46 | 47 | {% figure "/assets/images/image001.jpg", "Your **caption**" %} 48 | 49 | 50 | ### Width options 51 | 52 | The image width can also be set to `default`, `half`, `third`, or `unconstrained`. It's the third argument to pass to the shortcode. 53 | 54 | 55 | ``` 56 | {% raw %}{% figure "/assets/images/image001.jpg", "Your **caption**", "third" %}{% endraw %} 57 | ``` 58 | 59 | Here is the output: 60 | 61 | {% figure "/assets/images/image001.jpg", "Your **caption**", "third" %} 62 | 63 | ### Disable lightbox 64 | 65 | The lightbox can be disabled, it is the fourth argument to pass to the shortcode. 66 | 67 | ``` 68 | {% raw %}{% figure "/assets/images/image001.jpg", "", "", false %}{% endraw %} 69 | ``` 70 | 71 | Produces: 72 | 73 | {% figure "/assets/images/image001.jpg", "", "", false %} 74 | 75 | 76 | ### Unconstrained full width image 77 | 78 | The `unconstrained` width option will let the image render to its full width, across the entire page. The lightbox is disabled if the width is set to unconstrained. 79 | 80 | ``` 81 | {% raw %}{% figure "https://live.staticflickr.com/65535/49241129673_0f0d5f2751_4k.jpg", 82 | "Photo credit [mendhak](https://www.flickr.com/photos/mendhak/49241129673/)", 83 | "unconstrained" %}{% endraw %} 84 | ``` 85 | 86 | Produces: 87 | 88 | {% figure "https://live.staticflickr.com/65535/49241129673_0f0d5f2751_4k.jpg", 89 | "Photo credit [mendhak](https://www.flickr.com/photos/mendhak/49241129673/)", 90 | "unconstrained" %} 91 | 92 | 93 | ## Straight up HTML 94 | 95 | HTML can be directly used in Markdown. In this example no lightbox is produced. 96 | 97 | ```html 98 | {% raw %}
99 | Image served using HTML 100 |
101 | An image served using HTML figure and figcaption 102 |
103 |
{% endraw %} 104 | ``` 105 | 106 | Which results in: 107 | 108 | 109 |
110 | Image served using HTML 111 |
112 | An image served using HTML figure and figcaption 113 |
114 |
115 | 116 | 117 | ### ...with a lightbox 118 | 119 | Adding a hyperlink to the image will make it appear in a lightbox. 120 | 121 | 122 | ```html 123 | {% raw %}
124 | 125 | Image served using HTML 126 | 127 |
128 | An image served using HTML figure and figcaption 129 |
130 |
{% endraw %} 131 | ``` 132 | 133 | Which results in: 134 | 135 |
136 | 137 | Image served using HTML 138 | 139 |
140 | An image served using HTML figure and figcaption 141 |
142 |
143 | 144 | 145 | ### Standalone `` 146 | 147 | Using the `` HTML tag, the image is shown as-is, no lightbox, no center alignment. 148 | 149 | ```html 150 | {% raw %}Image served using HTML{% endraw %} 151 | ``` 152 | 153 | Produces: 154 | 155 | Image served using HTML 156 | 157 | -------------------------------------------------------------------------------- /assets/css/index.css: -------------------------------------------------------------------------------- 1 | /* Overriding simple.css values */ 2 | /* Visible at file:///home/mendhak/Projects/eleventy-satisfactory/node_modules/simpledotcss/simple.css */ 3 | 4 | 5 | :root { 6 | /* Set sans-serif & mono fonts */ 7 | --sans-font: Roboto,"Nimbus Sans L","Avenir Next",Avenir,"Segoe UI",Arial,Helvetica,"Helvetica Neue",sans-serif; 8 | --mono-font: "Ubuntu Mono", Consolas, Menlo, Monaco, "Andale Mono", monospace; 9 | } 10 | 11 | /* Change some colors for dark mode */ 12 | @media (prefers-color-scheme: dark) { 13 | :root { 14 | --bg: #333333; 15 | --accent: #fedb8b; 16 | } 17 | } 18 | 19 | /* Make the middle column slightly wider, and font slightly larger */ 20 | body { 21 | grid-template-columns: 1fr min(55rem, 90%) 1fr; 22 | font-size: 1.25rem; 23 | line-height: 1.7; 24 | overflow-y: scroll; /* stops horizontal shift due to scrollbar */ 25 | } 26 | 27 | /* Left align the site's title in the header */ 28 | body > header { 29 | text-align: left; 30 | padding: 0; 31 | } 32 | /* Styling for site title */ 33 | body > header > a { 34 | max-width: 1200px; 35 | margin: 1rem auto; 36 | font-size: 1.5rem; 37 | display: block; 38 | font-weight: 700; 39 | color: var(--text) !important; 40 | font-family: 'Lora'; 41 | padding-left: 0.5rem; 42 | } 43 | 44 | /* @view-transition { 45 | navigation: auto; 46 | } */ 47 | 48 | 49 | /* No underlines for hyperlinks */ 50 | a { 51 | text-decoration: none; 52 | } 53 | 54 | /* Don't italicize blockquote */ 55 | blockquote { 56 | font-style: normal; 57 | } 58 | 59 | /* H1s should have the accent color, to make it stand out */ 60 | h1 { 61 | color: var(--accent); 62 | } 63 | 64 | /* Use the nice Lora font for headings */ 65 | h1,h2,h3,h4,h5,h6 { 66 | font-family: Lora,serif; 67 | margin-bottom: 0.25em; 68 | } 69 | 70 | 71 | 72 | /* This class is added by the markdown-it-anchor library. Removing the hyperlink color from headers */ 73 | a.header-anchor { 74 | color: unset; 75 | } 76 | 77 | /* Time text slightly grayed out */ 78 | time { 79 | color: var(--text-light); 80 | font-size: 1rem; 81 | padding-top: 1.5rem; 82 | display: inline-block; 83 | } 84 | 85 | /* Get footer to span all 3 columns */ 86 | footer { 87 | grid-column: 1/-1; 88 | background-color: var(--accent-bg); 89 | padding-top: 3rem; 90 | box-shadow: 0 50vh 0 50vh var(--accent-bg); 91 | } 92 | 93 | /* Don't show bullet points for lists in footers */ 94 | footer ul { 95 | margin: 0; 96 | padding: 0; 97 | list-style-type: none; 98 | } 99 | 100 | footer li { 101 | display: inline-block; 102 | padding-top: 5px; 103 | padding-bottom: 5px; 104 | } 105 | 106 | /* Styling for the excerpts appear below article titles on the index page */ 107 | .excerpt { 108 | font-size: 1.2rem; 109 | padding-bottom: 1.2rem; 110 | } 111 | 112 | /* Styling for the article, which wraps the post body */ 113 | article { 114 | border: none; 115 | padding: 0; 116 | padding-bottom: 2rem; 117 | } 118 | 119 | /* article a { 120 | font-weight: bolder; 121 | font-family: 'Lora'; 122 | font-size: 1.5rem; 123 | } */ 124 | 125 | /* Styling for the external links appearing in the footer */ 126 | .bottomlinks { 127 | font-variant: small-caps; 128 | font-weight: bolder; 129 | font-size: 1rem; 130 | } 131 | 132 | /* Styling for the photos appearing in the footer */ 133 | .photostream { 134 | padding-top: 2rem; 135 | padding-bottom: 2rem; 136 | } 137 | 138 | /* Simple lightbox overlay */ 139 | .sl-overlay { 140 | background: black !important; 141 | } 142 | 143 | 144 | 145 | /* Navigation for Pagination */ 146 | 147 | nav { 148 | display: inline; 149 | text-align: center; 150 | margin-top: 2rem; 151 | width: 100%; 152 | 153 | } 154 | 155 | nav :is(ol,ul) li { 156 | display: inline; 157 | list-style-type: none; 158 | padding: 0.2rem 0.5rem; 159 | text-decoration: none; 160 | margin: 0 0.5rem; 161 | float: right; 162 | 163 | } 164 | 165 | /* Alert info warning error panels */ 166 | :root { 167 | --notice-color: var(--text); 168 | --notice-bg: var(--accent-bg); 169 | --notice-info-color: var(--text); 170 | --notice-info-bg: #d8ebf1; 171 | --notice-success-color: var(--text); 172 | --notice-success-bg: #d9edd9; 173 | --notice-warning-color: var(--text); 174 | --notice-warning-bg: #f7e5cd; 175 | --notice-danger-color: var(--text); 176 | --notice-danger-bg: #fcdfde; 177 | } 178 | 179 | @media (prefers-color-scheme: dark) { 180 | :root { 181 | --notice-color: var(--text); 182 | --notice-bg: var(--accent-bg); 183 | --notice-info-color: #76a9fa; 184 | --notice-info-bg: var(--accent-bg); 185 | --notice-success-color: #31c48d; 186 | --notice-success-bg: var(--accent-bg); 187 | --notice-warning-color: #faca15; 188 | --notice-warning-bg: var(--accent-bg); 189 | --notice-danger-color: #f98080; 190 | --notice-danger-bg: var(--accent-bg); 191 | } 192 | } 193 | 194 | .notice { 195 | background: var(--accent-bg); 196 | border-radius: 5px; 197 | padding: 1rem; 198 | margin: 2rem 0; 199 | font-size: 1rem; 200 | } 201 | 202 | .notice a { 203 | color: unset; 204 | text-decoration: underline; 205 | } 206 | 207 | .notice.info { 208 | color: var(--notice-info-color); 209 | background-color: var(--notice-info-bg); 210 | } 211 | 212 | .notice.success { 213 | color: var(--notice-success-color); 214 | background-color: var(--notice-success-bg); 215 | } 216 | 217 | .notice.warning { 218 | color: var(--notice-warning-color); 219 | background-color: var(--notice-warning-bg); 220 | } 221 | 222 | .notice.danger { 223 | color: var(--notice-danger-color); 224 | background-color: var(--notice-danger-bg); 225 | } 226 | 227 | 228 | .gist td { 229 | all: initial; 230 | } 231 | 232 | /* Video */ 233 | 234 | div.video > iframe { 235 | aspect-ratio: 16 / 9; 236 | width: 100%; 237 | } 238 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Eleventy Satisfactory Blog Theme 2 | [![Build and Publish Github Pages](https://github.com/mendhak/eleventy-satisfactory/actions/workflows/staticsite.yml/badge.svg?branch=main)](https://github.com/mendhak/eleventy-satisfactory/actions/workflows/staticsite.yml) [![Playwright Tests](https://github.com/mendhak/eleventy-satisfactory/actions/workflows/playwright.yml/badge.svg)](https://github.com/mendhak/eleventy-satisfactory/actions/workflows/playwright.yml) 3 | 4 | An Eleventy blog theme focused on content, and various convenience features. [Demo](https://code.mendhak.com/eleventy-satisfactory/). 5 | 6 | 7 | |![screenshot](./screenshots/01.png)|![screenshot](./screenshots/02.png)| 8 | |:--|:--| 9 | 10 | Makes use of SimpleCSS, with inspiration from Hylia and minimal-mistakes. I developed it for [my blog](https://code.mendhak.com). 11 | 12 | **Features** 13 | 14 | * My focus is mainly on content, and less on everything else (author, social, tags, etc). 15 | * Images using regular Markdown syntax are displayed with lightbox 16 | * Image gallery with captions 17 | * Link to a post's .md file is converted to final URL 18 | * Links and images paths automatically adjusted to match blog prefix 19 | * Main CSS is inline in the page 20 | * Automatic light and dark mode 21 | * Simplified post layout with date at bottom 22 | * Simplified home page layout 23 | * Adjust number of post listings 24 | * Post excerpts below post listings 25 | * Links, year, copyright, and photos in footer 26 | * Opengraph and JSON+LD metadata for posts including preview image 27 | * Code blocks rendered using Prism syntax highlighting 28 | * Notice boxes such as info, warning, success 29 | * Seamless gist integration as code blocks 30 | * Github repo cards generator 31 | * No tags listings, no site navigation, no social, no landing page, no analytics 32 | * Draft posts appear locally 33 | * UI testing (for this repo) 34 | * Github Actions for publishing 35 | * Extra wide images, videos and code blocks to make it extra-wide and stand out from the text 36 | * Full width images and videos 37 | * Robots.txt, .well-known folder 38 | * Favicon 39 | 40 | 41 | ## How to use this blog theme 42 | 43 | Generate a repo from this public template, or fork it. 44 | Delete the `tests/` folder and `.github/workflows/playwright.yml` 45 | 46 | ### Configuration 47 | 48 | - [Set your values in the metadata.json file](https://code.mendhak.com/eleventy-satisfactory/edit-the-metadata/) 49 | - [Set the footer links and images](https://code.mendhak.com/eleventy-satisfactory/set-footer-links/) 50 | 51 | 52 | ### Write a post 53 | 54 | The posts go in the `posts` folder. Drafts can go in the `drafts` folder and will only appear when running locally (`npm run start` or `docker compose up`) but not when building (`npm run build`). 55 | 56 | - [Set the date of a post](https://code.mendhak.com/eleventy-satisfactory/set-date-of-post/) 57 | - [Add an image](https://code.mendhak.com/eleventy-satisfactory/post-with-an-image/) 58 | - [Add an image gallery](https://code.mendhak.com/eleventy-satisfactory/post-with-a-gallery/) 59 | - [Link to another post or URL](https://code.mendhak.com/eleventy-satisfactory/posting-links/) 60 | - [Add a notice panel with info, warning, success, danger](https://code.mendhak.com/eleventy-satisfactory/post-notice/) 61 | - [Add a code block with syntax highlighting](https://code.mendhak.com/eleventy-satisfactory/post-with-code/) 62 | - [Add a Github Gist](https://code.mendhak.com/eleventy-satisfactory/post-with-github-gists/) 63 | - [Add a Github Repo Card](https://code.mendhak.com/eleventy-satisfactory/github-repo-card/) 64 | - [Add videos and audio](https://code.mendhak.com/eleventy-satisfactory/post-with-iframes-videos-third-party/) 65 | - [Controlling extra-wide images, videos, code blocks](https://code.mendhak.com/eleventy-satisfactory/extra-wide-full-width-images-videos/) 66 | - [Set Opengraph preview image and metadata](https://code.mendhak.com/eleventy-satisfactory/opengraph-preview-data/) 67 | 68 | 69 | Publish it. I've included a [sample Github Action](.github/workflows/staticsite.yml), use or modify as you need. 70 | 71 | 72 | ## How to run the blog locally 73 | 74 | **Running it with Docker** 75 | 76 | This will do the npm install and npm start together. 77 | 78 | ``` 79 | docker compose up 80 | ``` 81 | 82 | Then browse to http://localhost:8080/ 83 | 84 | 85 | **Running it with Node** 86 | 87 | Requires Node 20. First get all the dependencies. 88 | 89 | ``` 90 | npm install 91 | ``` 92 | 93 | To serve the site, and watch for changes: 94 | 95 | ``` 96 | npm run start 97 | ``` 98 | 99 | Then browse to http://localhost:8080/ 100 | 101 | 102 | To just build the site once (normally used for Github Actions): 103 | 104 | ``` 105 | npm install 106 | npm run build 107 | ``` 108 | 109 | **Running Tests** 110 | 111 | Make sure Firefox is installed. `sudo apt install firefox` 112 | 113 | Run Playwright tests, this should automatically do an npm start first. 114 | 115 | ``` 116 | npm test 117 | ``` 118 | 119 | ## Updating 120 | 121 | Once you've forked this repo, copying new features and updates from this repo to yours shouldn't be done wholesale. Instead, a diff tool like meld or Beyond Compare is recommended. 122 | 123 | For commandline, here's a useful rsync command which copies most files and excludes some unnecessary ones. At least it's a starting point: 124 | 125 | ``` 126 | cd your-blog-repo 127 | rsync -av --progress ../eleventy-satisfactory/ ./ \ 128 | --exclude '.git' \ 129 | --exclude 'tests' \ 130 | --exclude '_site' \ 131 | --exclude 'node_modules' \ 132 | --exclude 'posts' --include 'posts/posts.json' \ 133 | --exclude 'drafts/*.md' --include 'drafts.11tydata.js' --include 'drafts.json' \ 134 | --exclude 'playwright*' \ 135 | --exclude 'test*' \ 136 | --exclude '_data' \ 137 | --exclude 'screenshots' \ 138 | --exclude 'README.md' 139 | ``` 140 | 141 | After the rsync, it's still worth doing a git diff to see what's changed, or if there are any conflicts. 142 | 143 | 144 | ## Notes 145 | 146 | **TODO/Investigate** 147 | 148 | - Can the cssmin shortcode adjust the inline url() paths? That could allow putting the font lines into its own CSS file. 149 | - Can the Github CSS be included just once, instead of once per card? This could be done using WebC, have to wrap my head around it. 150 | - Should the gallery behave the same for gallery + markdown and gallery + shortcode? 151 | - There isn't a _clean_ way to give users the ability to [toggle light and dark mode](https://github.com/mendhak/eleventy-satisfactory/issues/2) 152 | 153 | 154 | **References** 155 | 156 | - [Eleventy](https://www.11ty.dev/docs/) 157 | - [Simple.css](https://github.com/kevquirk/simple.css/wiki) 158 | - [Favicon created by Uniconlabs](https://www.flaticon.com/free-icons/website) 159 | - [Article icon by Freepik](https://www.flaticon.com/free-icons/blog) 160 | - [Paired shortcode](https://www.markllobrera.com/posts/eleventy-paired-shortcodes-and-markdown-rendering/) 161 | - [Shortcode with image gallery](https://www.markllobrera.com/posts/eleventy-building-image-gallery-photoswipe/) 162 | - [Customizing markdown-it](https://publishing-project.rivendellweb.net/customizing-markdown-it/) 163 | 164 | 165 | -------------------------------------------------------------------------------- /posts/customary-lorem-ipsum.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Customary Lorem Ipsum post 3 | description: This is a post on My Blog with a wall of text. 4 | date: 2018-05-01 5 | tags: 6 | - another tag 7 | 8 | --- 9 | 10 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Tempor orci eu lobortis elementum nibh tellus molestie nunc non. Amet facilisis magna etiam tempor orci eu lobortis elementum nibh. Pharetra sit amet aliquam id. Volutpat odio facilisis mauris sit amet massa vitae tortor condimentum. Semper viverra nam libero justo laoreet. Mattis rhoncus urna neque viverra justo nec ultrices dui. Mauris augue neque gravida in fermentum et sollicitudin. Nunc mattis enim ut tellus elementum sagittis vitae. Nibh sed pulvinar proin gravida hendrerit lectus. Diam quam nulla porttitor massa. Ac felis donec et odio pellentesque diam volutpat. Cursus eget nunc scelerisque viverra mauris in. Non curabitur gravida arcu ac tortor dignissim convallis. Adipiscing at in tellus integer feugiat. 11 | 12 | ## Consectetur libero id 13 | 14 | Faucibus nisl tincidunt eget nullam non nisi. Tristique nulla aliquet enim tortor at auctor urna. Nisi scelerisque eu ultrices vitae auctor eu augue ut. Cras ornare arcu dui vivamus arcu felis bibendum ut. Eget nulla facilisi etiam dignissim diam quis enim. Consectetur purus ut faucibus pulvinar elementum. Ultricies tristique nulla aliquet enim tortor at auctor urna nunc. Ullamcorper velit sed ullamcorper morbi tincidunt ornare. Id semper risus in hendrerit gravida rutrum. Dignissim convallis aenean et tortor at. 15 | 16 | ![lorem photum](https://live.staticflickr.com/397/31445325431_981b759c23_h.jpg) 17 | 18 | Laoreet non curabitur gravida arcu. Neque sodales ut etiam sit amet. Vitae purus faucibus ornare suspendisse sed nisi lacus sed viverra. Ornare arcu dui vivamus arcu. Et leo duis ut diam quam nulla porttitor massa id. Diam sit amet nisl suscipit adipiscing bibendum est. Porttitor massa id neque aliquam vestibulum morbi blandit. Tellus integer feugiat scelerisque varius morbi enim nunc faucibus. Dui ut ornare lectus sit amet est placerat in egestas. Vel elit scelerisque mauris pellentesque pulvinar. 19 | 20 | 21 | 22 | Sit amet justo donec enim diam vulputate ut pharetra sit. Mi proin sed libero enim sed faucibus. Tortor at auctor urna nunc id cursus metus. In pellentesque massa placerat duis ultricies. Semper feugiat nibh sed pulvinar. 23 | 24 | Amet purus gravida quis blandit turpis cursus in hac. Natoque penatibus et magnis dis parturient montes nascetur. Tortor dignissim convallis aenean et tortor at. Eget est lorem ipsum dolor. Lectus nulla at volutpat diam ut venenatis tellus. Ultrices neque ornare aenean euismod elementum nisi quis eleifend. At tellus at urna condimentum mattis pellentesque id. Egestas egestas fringilla phasellus faucibus scelerisque eleifend. Etiam tempor orci eu lobortis elementum nibh tellus. Integer enim neque volutpat ac. 25 | 26 | ### Non blandit massa enim nec 27 | 28 | Leo urna molestie at elementum eu facilisis. Vestibulum sed arcu non odio. Tortor aliquam nulla facilisi cras fermentum odio. Ante in nibh mauris cursus mattis molestie. Lacus sed viverra tellus in. Tortor pretium viverra suspendisse potenti nullam ac tortor vitae purus. Non consectetur a erat nam at lectus urna duis convallis. Tortor dignissim convallis aenean et tortor at risus viverra adipiscing. Tincidunt id aliquet risus feugiat in ante. Aliquam vestibulum morbi blandit cursus risus at ultrices mi tempus. Nec ultrices dui sapien eget. Ullamcorper a lacus vestibulum sed arcu. Et odio pellentesque diam volutpat commodo sed egestas. Orci phasellus egestas tellus rutrum tellus pellentesque eu tincidunt. Ipsum a arcu cursus vitae. Adipiscing elit duis tristique sollicitudin nibh sit. Quis ipsum suspendisse ultrices gravida dictum fusce ut placerat orci. Felis donec et odio pellentesque diam volutpat commodo. Eget nulla facilisi etiam dignissim diam quis. 29 | 30 | > Mauris a diam maecenas sed enim ut sem viverra. Suspendisse faucibus interdum posuere lorem ipsum dolor sit amet. Cursus eget nunc scelerisque viverra. Id velit ut tortor pretium viverra suspendisse. Ultrices eros in cursus turpis. Quis hendrerit dolor magna eget est lorem ipsum dolor. Diam in arcu cursus euismod quis. Vitae suscipit tellus mauris a diam maecenas sed. Purus non enim praesent elementum facilisis leo vel. Porttitor massa id neque aliquam. Lectus urna duis convallis convallis tellus. Nunc consequat interdum varius sit amet mattis vulputate enim. 31 | 32 | Orci nulla pellentesque dignissim enim sit. Tincidunt vitae semper quis lectus nulla at volutpat. Molestie at elementum eu facilisis. In aliquam sem fringilla ut morbi tincidunt augue interdum. Dui nunc mattis enim ut tellus elementum. Ultricies lacus sed turpis tincidunt id aliquet risus feugiat in. Enim diam vulputate ut pharetra sit amet aliquam id. Volutpat est velit egestas dui id ornare. Eu mi bibendum neque egestas congue. Cras sed felis eget velit. Integer quis auctor elit sed vulputate mi sit amet mauris. Gravida rutrum quisque non tellus orci. Rutrum quisque non tellus orci ac . 33 | 34 | ### Tincidunt arcu non sodales 35 | 36 | Neque sodales ut etiam sit. Nibh venenatis cras sed felis eget velit aliquet sagittis id. Aliquet enim tortor at auctor urna nunc id. Turpis massa tincidunt dui ut ornare lectus sit amet. Id faucibus nisl tincidunt eget nullam non. Vitae ultricies leo integer malesuada nunc. Volutpat est velit egestas dui id ornare arcu odio ut. Cursus turpis massa tincidunt dui ut ornare. Neque aliquam vestibulum morbi blandit cursus risus at. Aenean vel elit scelerisque mauris. Quisque sagittis purus sit amet volutpat consequat mauris nunc congue. Nibh tortor id aliquet lectus proin. Sagittis nisl rhoncus mattis rhoncus urna neque. 37 | 38 | ```java 39 | private void loremFunction(boolean ipsum) { 40 | LOG.debug("Lorem ipsum begins"); 41 | Intent sendIntent = new Intent(); 42 | sendIntent.putExtra("lorem ipsum dolor", "sit amet"); 43 | } 44 | ``` 45 | 46 | Metus vulputate eu scelerisque felis imperdiet proin fermentum leo. Vulputate enim nulla aliquet porttitor lacus luctus accumsan tortor posuere. Vel pharetra vel turpis nunc eget lorem dolor. Ut tellus elementum sagittis vitae et. Donec ultrices tincidunt arcu non sodales neque sodales. Dis parturient montes nascetur ridiculus mus mauris vitae. Pulvinar etiam non quam lacus suspendisse. Senectus et netus et malesuada. Sed viverra tellus in hac habitasse. Est sit amet facilisis magna etiam tempor orci eu lobortis. 47 | 48 |
Ac turpis egestas integer eget. Euismod elementum nisi quis eleifend. Risus quis varius quam quisque id diam. Adipiscing elit ut aliquam purus sit amet. Tristique senectus et netus et malesuada fames ac. Fames ac turpis egestas sed tempus urna et pharetra. Vel pharetra vel turpis nunc eget lorem dolor. Augue eget arcu dictum varius duis at. Nibh tortor id aliquet lectus proin nibh.
49 | 50 | ## Bibendum ut tristique 51 | 52 | Et egestas quis ipsum suspendisse ultrices gravida. Est placerat in egestas erat imperdiet. Imperdiet dui accumsan sit amet nulla facilisi. Quis ipsum suspendisse ultrices gravida dictum fusce ut placerat orci. Aliquet risus feugiat in ante metus dictum at tempor. Vel pharetra vel turpis nunc eget lorem. Suscipit tellus mauris a diam maecenas. Volutpat commodo sed egestas egestas fringilla phasellus faucibus. Justo eget magna fermentum iaculis. Sed euismod nisi porta lorem mollis aliquam ut porttitor leo. Gravida rutrum quisque non tellus orci. Cursus metus aliquam eleifend mi in. Vulputate mi sit amet mauris commodo. 53 | 54 | Arcu odio ut sem nulla pharetra diam sit amet. Ipsum faucibus vitae aliquet nec ullamcorper sit. At imperdiet dui accumsan sit amet nulla facilisi. In nibh mauris cursus mattis molestie a iaculis at erat. Suspendisse in est ante in nibh. Fringilla phasellus faucibus scelerisque eleifend donec pretium vulputate sapien nec. Elit duis tristique sollicitudin nibh. Sagittis nisl rhoncus mattis rhoncus urna neque. Sit amet volutpat consequat mauris nunc. Eget nullam non nisi est sit amet. Et leo duis ut diam quam. Tincidunt arcu non sodales neque sodales ut. Auctor elit sed vulputate mi sit amet. Viverra suspendisse potenti nullam ac tortor vitae purus faucibus. Cras adipiscing enim eu turpis. Placerat in egestas erat imperdiet. Egestas tellus rutrum tellus pellentesque eu tincidunt tortor. 55 | 56 |
57 | 58 |
59 | -------------------------------------------------------------------------------- /.eleventy.js: -------------------------------------------------------------------------------- 1 | import "dotenv/config"; 2 | import markdownIt from "markdown-it"; 3 | import markdownItAnchor from "markdown-it-anchor"; 4 | 5 | import pluginRss from "@11ty/eleventy-plugin-rss"; 6 | import pluginSyntaxHighlight from "@11ty/eleventy-plugin-syntaxhighlight"; 7 | 8 | import { readFile } from 'fs/promises'; 9 | const metadata = JSON.parse( 10 | await readFile( 11 | new URL('./_data/metadata.json', import.meta.url) 12 | ) 13 | ); 14 | 15 | // Change this to match the actual path prefix. 16 | const pathPrefix = process.env.PATH_PREFIX || metadata.pathPrefix; 17 | 18 | 19 | import { InputPathToUrlTransformPlugin, EleventyHtmlBasePlugin } from "@11ty/eleventy"; 20 | import { DateTime } from "luxon"; 21 | 22 | // Various custom shortcodes and filters 23 | import imageRenderer from "./_configs/markdownlibrary.renderer.image.js"; 24 | import cssminification from './_configs/cssminification.shortcode.js'; 25 | import notice from './_configs/notice.shortcode.js'; 26 | import button from './_configs/button.shortcode.js'; 27 | import figure from './_configs/figure.shortcode.js'; 28 | import lightbox from './_configs/lightboxref.shortcode.js'; 29 | import gallery from './_configs/gallery.shortcode.js'; 30 | import video from './_configs/video.shortcode.js'; 31 | import excerpt from './_configs/excerpt.shortcode.js'; 32 | import ghRepoCard from './_configs/githubrepocard.shortcode.js'; 33 | import gist from './_configs/gist.shortcode.js'; 34 | 35 | 36 | /** @param {import("@11ty/eleventy").UserConfig} eleventyConfig */ 37 | export default async function (eleventyConfig) { 38 | 39 | // Copy the `assets` (includes images, fonts) folders to the output 40 | eleventyConfig.addPassthroughCopy("assets/fonts"); 41 | eleventyConfig.addPassthroughCopy("assets/images"); 42 | eleventyConfig.addPassthroughCopy({"assets/.well-known": ".well-known"}); 43 | eleventyConfig.addPassthroughCopy({ "node_modules/simplelightbox/dist/simple-lightbox.min.css": "simplelightbox/simple-lightbox.min.css" }); 44 | eleventyConfig.addPassthroughCopy({ "node_modules/simplelightbox/dist/simple-lightbox.min.js": "simplelightbox/simple-lightbox.min.js" }); 45 | 46 | //Since moving the CSS inline eleventy no longer watches it (because it's not being copied to output), so I had to include it as a watch target. 47 | //Adding it to addPassthroughCopy also means it's not watched. 48 | eleventyConfig.addWatchTarget("assets/css/"); 49 | eleventyConfig.addWatchTarget("assets/js/"); 50 | 51 | // Customize Markdown library and settings: 52 | let markdownLibrary = markdownIt({ 53 | html: true, 54 | linkify: false, 55 | typographer: true 56 | }).use(markdownItAnchor, { 57 | permalink: markdownItAnchor.permalink.headerLink(), 58 | level: [1, 2, 3, 4], 59 | slugify: eleventyConfig.getFilter("slugify") 60 | }); 61 | 62 | // Wrap images in a figure, a, and figcaption. 63 | // This lets the simplelightbox code serve it up too! 64 | // Also adds loading lazy attribute 65 | markdownLibrary.renderer.rules.image = (tokens, idx, options, env, slf) => imageRenderer(tokens, idx, options, env, slf, markdownLibrary); 66 | 67 | 68 | eleventyConfig.setLibrary("md", markdownLibrary); 69 | // Re-enable the indented code block feature 70 | eleventyConfig.amendLibrary("md", mdLib => mdLib.enable("code")) 71 | 72 | // RSS 73 | eleventyConfig.addPlugin(pluginRss); 74 | // Code syntax with Prism JS 75 | eleventyConfig.addPlugin(pluginSyntaxHighlight); 76 | 77 | //Converts most URLs to URLs with pathPrefix 78 | eleventyConfig.addPlugin(EleventyHtmlBasePlugin); 79 | // Automatically convert links pointing at .md to corresponding URL. 80 | eleventyConfig.addPlugin(InputPathToUrlTransformPlugin); 81 | 82 | // Date used below posts 83 | eleventyConfig.addFilter("readableDate", dateObj => { 84 | return DateTime.fromJSDate(dateObj, { zone: 'utc' }).toFormat("dd LLL yyyy"); 85 | }); 86 | 87 | // Date used in sitemap and data attribute 88 | // https://html.spec.whatwg.org/multipage/common-microsyntaxes.html#valid-date-string 89 | eleventyConfig.addFilter('htmlDateString', (dateObj) => { 90 | return DateTime.fromJSDate(dateObj, { zone: 'utc' }).toFormat('yyyy-LL-dd'); 91 | }); 92 | 93 | // Get the first `n` elements of a collection. Used on the home page to limit number of items to display. 94 | eleventyConfig.addFilter("head", (array, n) => { 95 | if (!Array.isArray(array) || array.length === 0) { 96 | return []; 97 | } 98 | if (n < 0) { 99 | return array.slice(n); 100 | } 101 | 102 | return array.slice(0, n); 103 | }); 104 | 105 | // Ensures that draft postts only show up when doing a --watch or --serve 106 | // https://www.11ty.dev/docs/config-preprocessors/#example-drafts 107 | eleventyConfig.addPreprocessor("drafts", "*", (data, content) => { 108 | if (data.draft && process.env.ELEVENTY_RUN_MODE === "build") { 109 | return false; 110 | } 111 | }); 112 | 113 | // Filters out irrelevant tags that aren't really related to content, only used for organising things 114 | eleventyConfig.addFilter("filterTagList", function (tags) { 115 | return (tags || []).filter(tag => ["all", "nav", "post", "posts"].indexOf(tag) === -1); 116 | }); 117 | 118 | // filter to convert content to Markdown. 119 | // Useful for allowing `code` in the h1 120 | eleventyConfig.addFilter("markdown", (content) => { 121 | return markdownLibrary.renderInline(content); 122 | }); 123 | 124 | // Paired shortcode that takes a JSON array of CSS file paths 125 | // It then combines them, which includes reconciles overriden values! 126 | // And returns the output. 127 | eleventyConfig.addPairedShortcode("cssminification", cssminification); 128 | 129 | //Paired shortcode to display a notice panel like standard, error, warning, etc. 130 | eleventyConfig.addPairedShortcode("notice", (data, noticeType) => notice(data, noticeType, markdownLibrary)); 131 | 132 | //Shortcode to render a button, optionally with a link 133 | eleventyConfig.addShortcode("button", button); 134 | 135 | // Paired shortcode to display a figure with caption. 136 | // This is very similar to the regular Markdown image, 137 | // But I'll keep this around in case the other way ever breaks in the future 138 | // Plus, this has the 'width' flexibility, and maybe more future features. 139 | eleventyConfig.addShortcode("figure", (image, caption, widthName, useLightbox=true) => figure(image, caption, widthName, useLightbox, markdownLibrary)); 140 | 141 | // If the post contains images, then add the Lightbox JS/CSS and render lightboxes for it. 142 | // Since it needs access to the `page` object, we can't use arrow notation here. 143 | eleventyConfig.addShortcode("addLightBoxRefIfNecessary", function () { return lightbox(this.page); }); 144 | 145 | // The `gallery` paired shortcode shows a set of images and displays it in a row together. 146 | eleventyConfig.addPairedShortcode("gallery", (data, caption) => gallery(data, caption, markdownLibrary)); 147 | 148 | 149 | // The `video` shortcode gets a YouTube video and displays it 150 | eleventyConfig.addShortcode("video", video); 151 | 152 | // Generate excerpt from first paragraph 153 | eleventyConfig.addShortcode("excerpt", excerpt); 154 | 155 | // Show the current year using a shortcode 156 | eleventyConfig.addShortcode("year", () => `${new Date().getFullYear()}`); 157 | 158 | // Shortcode for Github Repo Card 159 | eleventyConfig.addShortcode("githubrepocard", ghRepoCard); 160 | 161 | // The `gist` shortcode renders the gist's files as code blocks 162 | // For some reason calling the method directly isn't possible, I have to wrap it. 163 | eleventyConfig.addShortcode("gist", async (gistId) => gist(gistId, markdownLibrary)); 164 | 165 | 166 | return { 167 | // Control which files Eleventy will process 168 | // e.g.: *.md, *.njk, *.html, *.liquid 169 | templateFormats: [ 170 | "md", 171 | "njk", 172 | "html", 173 | "liquid" 174 | ], 175 | 176 | // Pre-process *.md files with: (default: `liquid`) 177 | markdownTemplateEngine: "njk", 178 | 179 | // Pre-process *.html files with: (default: `liquid`) 180 | htmlTemplateEngine: "njk", 181 | 182 | // ----------------------------------------------------------------- 183 | // If your site deploys to a subdirectory, change `pathPrefix` in metadata.json. 184 | // Don’t worry about leading and trailing slashes, we normalize these. 185 | 186 | // If you don’t have a subdirectory, use "" or "/" (they do the same thing) 187 | // This is only used for link URLs (it does not affect your file structure) 188 | // Best paired with the `url` filter: https://www.11ty.dev/docs/filters/url/ 189 | 190 | // You can also pass this in on the command line using `--pathprefix` 191 | 192 | // Optional (default is "/") 193 | pathPrefix: pathPrefix, 194 | // ----------------------------------------------------------------- 195 | 196 | // These are all optional (defaults are shown): 197 | dir: { 198 | input: ".", 199 | includes: "_includes", 200 | data: "_data", 201 | output: "_site" 202 | } 203 | }; 204 | }; 205 | --------------------------------------------------------------------------------