Skip to content

Commit 27dfdcb

Browse files
committed
feat(attachment): add attachments with index pages & syntax highlighting
- also add attachments list to ticket pages
1 parent 7be705b commit 27dfdcb

4,064 files changed

Lines changed: 313304 additions & 66 deletions

File tree

Some content is hidden

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

_data/attachments.json

Lines changed: 23362 additions & 0 deletions
Large diffs are not rendered by default.

_includes/layouts/post.njk

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,38 @@
22
layout: layouts/base.njk
33
---
44
{# Only include the syntax highlighter CSS on blog posts #}
5-
{%- css %}{% include "node_modules/prismjs/themes/prism-okaidia.css" %}{% endcss %}
5+
{%- css %}{% include "node_modules/prismjs/themes/prism.min.css" %}{% endcss %}
66
{%- css %}{% include "public/css/prism-diff.css" %}{%- endcss %}
77
<h1>{{ title }}</h1>
88

99
<ul class="post-metadata">
10-
<li><time datetime="{{ page.date | htmlDateString }}">{{ page.date | readableDate }}</time></li>
11-
{%- for tag in tags | filterTagList %}
12-
{%- set tagUrl %}/tags/{{ tag | slugify }}/{% endset %}
13-
<li><a href="{{ tagUrl }}" class="post-tag">{{ tag }}</a>{%- if not loop.last %}, {% endif %}</li>
14-
{%- endfor %}
10+
<li>
11+
<time datetime="{{ page.date | htmlDateString }}">{{ page.date | readableDate }}</time>
12+
</li>
13+
{%- for tag in tags | filterTagList %}
14+
{%- set tagUrl %}/tags/{{ tag | slugify }}/{% endset %}
15+
<li>
16+
<a href="{{ tagUrl }}" class="post-tag">{{ tag }}</a>
17+
{%- if not loop.last %}, {% endif %}
18+
</li>
19+
{%- endfor %}
1520
</ul>
1621

1722
{{ content | safe }}
1823

1924
{%- if collections.posts %}
20-
{%- set previousPost = collections.posts | getPreviousCollectionItem %}
21-
{%- set nextPost = collections.posts | getNextCollectionItem %}
22-
{%- if nextPost or previousPost %}
23-
<ul class="links-nextprev">
24-
{%- if previousPost %}<li>Previous: <a href="{{ previousPost.url }}">{{ previousPost.data.title }}</a></li>{% endif %}
25-
{%- if nextPost %}<li>Next: <a href="{{ nextPost.url }}">{{ nextPost.data.title }}</a></li>{% endif %}
26-
</ul>
27-
{%- endif %}
25+
{%- set previousPost = collections.posts | getPreviousCollectionItem %}
26+
{%- set nextPost = collections.posts | getNextCollectionItem %}
27+
{%- if nextPost or previousPost %}
28+
<ul class="links-nextprev">
29+
{%- if previousPost %}
30+
<li>Previous: <a href="{{ previousPost.url }}">{{ previousPost.data.title }}</a>
31+
</li>
32+
{% endif %}
33+
{%- if nextPost %}
34+
<li>Next: <a href="{{ nextPost.url }}">{{ nextPost.data.title }}</a>
35+
</li>
36+
{% endif %}
37+
</ul>
38+
{%- endif %}
2839
{%- endif %}

content/attachment.njk

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
---js
2+
{
3+
pagination: {
4+
data: 'attachments',
5+
size: 1,
6+
alias: 'attachment',
7+
// Sliced in development or the server gets bogged down
8+
before: (paginationData) => {
9+
if (process.env.NODE_ENV === 'development') {
10+
return paginationData.filter((attachment) => attachment.id === process.env.ATTACHMENT_TICKET)
11+
}
12+
return paginationData
13+
}
14+
},
15+
layout: 'layouts/base.njk',
16+
eleventyComputed: {
17+
title: "{{ attachment.filename }} on Ticket #{{ attachment.id }} – Attachment – jQuery - Bug Tracker"
18+
},
19+
permalink: '/attachment/ticket/{{ attachment.id }}/{{ attachment.filename }}/index.html',
20+
tags: ['attachment']
21+
}
22+
---
23+
24+
{# Include syntax highligher CSS #}
25+
{%- css %}{% include "node_modules/prismjs/themes/prism.min.css" %}{% endcss %}
26+
{%- css %}{% include "public/css/prism-diff.css" %}{%- endcss %}
27+
<link rel="stylesheet" href="/css/attachment.css" />
28+
29+
<div class="flex-column attachment">
30+
<nav class="content-nav">
31+
<ul class="flex-row">
32+
<li>
33+
<a href="/ticket/{{ attachment.id }}">Back to Ticket #{{ attachment.id }}</a>
34+
</li>
35+
</ul>
36+
</nav>
37+
38+
<h1>
39+
<a href="/ticket/{{ attachment.id }}">Ticket #{{ attachment.id }}</a>: {{ attachment.filename }}
40+
</h3>
41+
<hr>
42+
43+
<div class="attachment-info">
44+
<h6>File {{ attachment.filename }}, {{ attachment.size | bytesToKilos }} (added by {{ attachment.author }}, {{ attachment.time | yearsAgo }})</h6>
45+
{% if attachment.description %}
46+
<p>{{ attachment.description }}</p>
47+
{% endif %}
48+
</div>
49+
50+
{% if attachment.filename | isImage %}
51+
{% set imageUrl = '../raw-attachment/ticket/' + attachment.id + '/' + attachment.filename %}
52+
{%- image imageUrl, "Attachment Image" %}
53+
{% elif attachment.filename | isPreviewable %}
54+
<div class="attachment-preview">
55+
{% set ext = attachment.filename | extension %}
56+
{% highlight ext %}
57+
{%- attachment attachment.id, attachment.filename %}
58+
{% endhighlight %}
59+
</div>
60+
{% else %}
61+
<p>
62+
<strong>This file cannot be previewed.</strong> Try <a download href="/raw-attachment/ticket/{{ attachment.id }}/{{ attachment.filename }}">downloading</a> the file instead.</p>
63+
{% endif %}
64+
65+
<div class="attachment-download flex-column flex-center">
66+
<h3>Download in other formats:</h3>
67+
<a class="attachment-download-link" download href="/raw-attachment/ticket/{{ attachment.id }}/{{ attachment.filename }}">Original Format</a>
68+
</div>
69+
</div>

content/blog/firstpost.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ Bring to the table win-win survival strategies to ensure proactive domination. A
1414

1515
Capitalize on low hanging fruit to identify a ballpark value added activity to beta test. Override the digital divide with additional clickthroughs from DevOps. Nanotechnology immersion along the information highway will close the loop on focusing solely on the bottom line.
1616

17-
```diff-js
17+
```diff
1818
// this is a command
1919
function myCommand() {
2020
+ let counter = 0;

content/ticket.njk

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,21 @@
77
// Sliced in development or the server gets bogged down
88
before: (paginationData) => {
99
if (process.env.NODE_ENV === 'development') {
10-
return paginationData.slice(0, 5)
10+
const ticketIndex = paginationData.findIndex((ticket) => ticket.id + '' === process.env.ATTACHMENT_TICKET)
11+
const tickets = paginationData.slice(0, 10)
12+
if (ticketIndex > 10) {
13+
tickets.push(paginationData[ticketIndex])
14+
}
15+
return tickets
1116
}
1217
return paginationData
1318
}
1419
},
1520
layout: 'layouts/ticket.njk',
1621
eleventyComputed: {
22+
ticketAttachments: (data) => {
23+
return data.attachments.filter((attachment) => attachment.id === data.ticket.id + '')
24+
},
1725
title: "#{{ ticket.id }} ({{ ticket.summary }}) - jQuery - Bug Tracker"
1826
},
1927
permalink: '/ticket/{{ ticket.id }}/',
@@ -134,6 +142,22 @@
134142
</div>
135143
</div>
136144

145+
{% if ticketAttachments | length %}
146+
<details open>
147+
<summary class="ticket-details-summary">Attachments ({{ ticketAttachments | length }})</summary>
148+
149+
<ul class="ticket-attachments">
150+
{% for attachment in ticketAttachments %}
151+
<li>
152+
<a href="/attachment/ticket/{{ attachment.id }}/{{ attachment.filename }}">{{ attachment.filename }}</a> ({{ attachment.size | bytesToKilos }}) - added by {{ attachment.author }}
153+
<strong>{{ attachment.time | yearsAgo }}</strong>.<br />
154+
{{ attachment.description | tracToHTML | safe }}
155+
</li>
156+
{% endfor %}
157+
</ul>
158+
</details>
159+
{% endif %}
160+
137161
<details open>
138162
<summary class="ticket-details-summary">Change History ({{ ticket.changes | length }})</summary>
139163
{% for change in ticket.changes %}
@@ -174,4 +198,4 @@
174198
</div>
175199
</div>
176200
{% endfor %}
177-
</details>
201+
</details>

eleventy.config.js

Lines changed: 61 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
const { DateTime } = require('luxon')
2-
const markdownItAnchor = require('markdown-it-anchor')
32

43
const pluginRss = require('@11ty/eleventy-plugin-rss')
54
const pluginSyntaxHighlight = require('@11ty/eleventy-plugin-syntaxhighlight')
@@ -8,18 +7,44 @@ const pluginNavigation = require('@11ty/eleventy-navigation')
87
const { EleventyHtmlBasePlugin } = require('@11ty/eleventy')
98
const pluginFavicon = require('eleventy-favicon')
109
const CleanCSS = require('clean-css')
10+
const fs = require('node:fs')
11+
const path = require('node:path')
1112

1213
const pluginDrafts = require('./eleventy.config.drafts.js')
1314
const pluginImages = require('./eleventy.config.images.js')
1415

16+
function escapeHTML(string) {
17+
return string.replace(/</g, '&lt;').replace(/>/g, '&gt;')
18+
}
19+
1520
module.exports = function (eleventyConfig) {
1621
// Copy the contents of the `public` folder to the output folder
1722
// For example, `./public/css/` ends up in `_site/css/`
1823
eleventyConfig.addPassthroughCopy({
1924
'./public/': '/',
20-
'./node_modules/prismjs/themes/prism-okaidia.css': '/css/prism-okaidia.css'
25+
'./node_modules/prismjs/themes/prism.min.css': '/css/prism.min.css'
2126
})
2227

28+
const attachTicket =
29+
process.env.ATTACHMENT_TICKET || (process.env.ATTACHMENT_TICKET = '25')
30+
31+
// Limit the number of copies during development builds
32+
if (process.env.NODE_ENV === 'development') {
33+
console.log(
34+
'[Development] Added attachments for http://localhost:8080/ticket/' +
35+
attachTicket
36+
)
37+
eleventyConfig.addPassthroughCopy({
38+
[`./raw-attachment/ticket/${attachTicket}`]: `/raw-attachment/ticket/${attachTicket}`,
39+
[`./zip-attachment/ticket/${attachTicket}`]: `/zip-attachment/ticket/${attachTicket}`
40+
})
41+
} else {
42+
eleventyConfig.addPassthroughCopy({
43+
'./raw-attachment/': '/raw-attachment/',
44+
'./zip-attachment/': '/zip-attachment/'
45+
})
46+
}
47+
2348
// Run Eleventy when these files change:
2449
// https://www.11ty.dev/docs/watch-serve/#add-your-own-watch-targets
2550

@@ -70,6 +95,23 @@ module.exports = function (eleventyConfig) {
7095
return DateTime.fromJSDate(date, { zone: 'utc' }).toFormat('dd LLLL yyyy')
7196
})
7297

98+
eleventyConfig.addFilter('isImage', (filename) => {
99+
return /\.(jpg|jpeg|png|webp|gif|tiff|avif|svg)$/i.test(filename)
100+
})
101+
102+
eleventyConfig.addFilter('isPreviewable', (filename) => {
103+
return /\.(js|html?|diff|patch|css|txt|php)$/i.test(filename)
104+
})
105+
106+
eleventyConfig.addFilter('extension', (filename) => {
107+
return path.extname(filename).replace(/^\./, '')
108+
})
109+
110+
eleventyConfig.addFilter('bytesToKilos', (bytes) => {
111+
const kilos = bytes / 1024
112+
return `${kilos.toFixed(1)} KB`
113+
})
114+
73115
eleventyConfig.addFilter('readableDate', (dateObj, format, zone) => {
74116
// Formatting tokens for Luxon: https://moment.github.io/luxon/#/formatting?id=table-of-tokens
75117
return DateTime.fromJSDate(dateObj, { zone: zone || 'utc' }).toFormat(
@@ -135,12 +177,9 @@ module.exports = function (eleventyConfig) {
135177
const codes = []
136178
const pres = []
137179
return (
138-
text
180+
escapeHTML(text)
139181
// Newlines have extra escapes in the strings
140182
.replace(/\\\n/g, '\n')
141-
// Escape HTML
142-
.replace(/</g, '&lt;')
143-
.replace(/>/g, '&gt;')
144183
// Replace `` with <code> tags
145184
.replace(/`([^\r\n`]+?)`/g, (_match, code) => {
146185
codes.push(code) // Save the code for later
@@ -173,6 +212,13 @@ module.exports = function (eleventyConfig) {
173212
}</a>`
174213
}
175214
)
215+
// Linkify CamelCase links in brackets
216+
.replace(
217+
/\[([A-Z][a-z]+[A-Z][\w#-]+)(?:\s+([^\]]+))?\]/g,
218+
function (_match, page, text) {
219+
return `<a href="/wiki/${page}">${text || page}</a>`
220+
}
221+
)
176222
// Linkify trac links
177223
.replace(
178224
/(?:\[trac:([^ ]+) "([^"]+)"\])|(?:\[trac:([^\s\]]+)(?: ([^\]]+))?\])/g,
@@ -185,10 +231,10 @@ module.exports = function (eleventyConfig) {
185231
}
186232
)
187233
// Linkify ticket references (avoid trac ticket links)
188-
.replace(/#(\d+)(?<=y)</g, `<a href="/ticket/$1">$&</a>`)
234+
.replace(/#(\d+)(?!<=>)/g, `<a href="/ticket/$1">$&</a>`)
189235
// Linkify CamelCase to wiki
190236
.replace(
191-
/(^|\s)(!)?\[?([A-Z][a-z]+[A-Z]\w+)\]?(?!\w)/g,
237+
/(^|\s)(!)?([A-Z][a-z]+[A-Z]\w+(?:#\w+)?)(?!\w)/g,
192238
function (_match, space, excl, page) {
193239
if (excl) {
194240
return `${space}${page}`
@@ -257,6 +303,13 @@ module.exports = function (eleventyConfig) {
257303
return DateTime.local().toFormat('yyyy')
258304
})
259305

306+
eleventyConfig.addAsyncShortcode('attachment', async (ticketId, filename) => {
307+
const content = await fs.promises.readFile(
308+
path.join(__dirname, 'raw-attachment/ticket/', ticketId, filename)
309+
)
310+
return content.toString()
311+
})
312+
260313
// Features to make your build faster (when you need them)
261314

262315
// If your passthrough copy gets heavy and cumbersome, add this line

package-lock.json

Lines changed: 0 additions & 36 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@
4545
"cross-env": "^7.0.3",
4646
"eleventy-favicon": "^1.1.3",
4747
"luxon": "^3.3.0",
48-
"markdown-it-anchor": "^8.6.7",
4948
"prettier": "^2.8.8"
5049
},
5150
"overrides": {

0 commit comments

Comments
 (0)