astro-accelerator 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +6 -0
  3. package/package.json +52 -0
  4. package/public/js/main.js +45 -0
  5. package/public/js/modules/animation.js +69 -0
  6. package/public/js/modules/click-blocks.js +41 -0
  7. package/public/js/modules/code-blocks.js +59 -0
  8. package/public/js/modules/events.js +19 -0
  9. package/public/js/modules/external-links.js +20 -0
  10. package/public/js/modules/figures.js +37 -0
  11. package/public/js/modules/focus.js +76 -0
  12. package/public/js/modules/nav-mobile.js +122 -0
  13. package/public/js/modules/nav-sticky.js +54 -0
  14. package/public/js/modules/query.js +41 -0
  15. package/public/js/modules/resizing.js +43 -0
  16. package/public/js/modules/string.js +66 -0
  17. package/public/js/modules/youtube.js +39 -0
  18. package/public/js/search.js +249 -0
  19. package/src/data/footer.ts +43 -0
  20. package/src/data/image-size.mjs +5 -0
  21. package/src/data/images.mjs +4 -0
  22. package/src/data/navigation.ts +64 -0
  23. package/src/layouts/Author.astro +15 -0
  24. package/src/layouts/Default.astro +15 -0
  25. package/src/layouts/Redirect.astro +15 -0
  26. package/src/layouts/Search.astro +15 -0
  27. package/src/pages/articles/feed.xml.ts +52 -0
  28. package/src/pages/report/missing-banner.astro +42 -0
  29. package/src/pages/report/missing-meta.astro +42 -0
  30. package/src/pages/report/oldest-content.astro +41 -0
  31. package/src/pages/report/taxonomy.astro +47 -0
  32. package/src/pages/search.json.ts +43 -0
  33. package/src/pages/sitemap.xml.ts +36 -0
  34. package/src/themes/accelerator/components/ArticleList.astro +67 -0
  35. package/src/themes/accelerator/components/Authors.astro +57 -0
  36. package/src/themes/accelerator/components/AuthorsMini.astro +37 -0
  37. package/src/themes/accelerator/components/Breadcrumbs.astro +33 -0
  38. package/src/themes/accelerator/components/Footer.astro +37 -0
  39. package/src/themes/accelerator/components/FooterItem.astro +29 -0
  40. package/src/themes/accelerator/components/Header.astro +46 -0
  41. package/src/themes/accelerator/components/HtmlHead.astro +49 -0
  42. package/src/themes/accelerator/components/Navigation.astro +31 -0
  43. package/src/themes/accelerator/components/NavigationBar.astro +31 -0
  44. package/src/themes/accelerator/components/NavigationItem.astro +37 -0
  45. package/src/themes/accelerator/components/Paging.astro +33 -0
  46. package/src/themes/accelerator/components/Related.astro +88 -0
  47. package/src/themes/accelerator/components/SkipLinks.astro +25 -0
  48. package/src/themes/accelerator/components/TableOfContents.astro +32 -0
  49. package/src/themes/accelerator/components/Taxonomy.astro +49 -0
  50. package/src/themes/accelerator/layouts/Author.astro +30 -0
  51. package/src/themes/accelerator/layouts/Default.astro +62 -0
  52. package/src/themes/accelerator/layouts/Redirect.astro +19 -0
  53. package/src/themes/accelerator/layouts/Search.astro +43 -0
  54. package/src/themes/accelerator/utilities/Authors.astro +48 -0
  55. package/src/themes/accelerator/utilities/Breadcrumbs.astro +27 -0
  56. package/src/themes/accelerator/utilities/Cache.astro +42 -0
  57. package/src/themes/accelerator/utilities/DateFormat.astro +25 -0
  58. package/src/themes/accelerator/utilities/Footer.astro +176 -0
  59. package/src/themes/accelerator/utilities/Languages.astro +14 -0
  60. package/src/themes/accelerator/utilities/Markdown.astro +27 -0
  61. package/src/themes/accelerator/utilities/NavPage.astro +65 -0
  62. package/src/themes/accelerator/utilities/Navigation.astro +81 -0
  63. package/src/themes/accelerator/utilities/NavigationTypes.astro +20 -0
  64. package/src/themes/accelerator/utilities/PageLinks.astro +72 -0
  65. package/src/themes/accelerator/utilities/PageQueries.astro +71 -0
  66. package/src/themes/accelerator/utilities/PageTypeFilters.astro +69 -0
  67. package/src/themes/accelerator/utilities/Taxonomy.astro +112 -0
  68. package/src/themes/accelerator/utilities/Url.astro +19 -0
  69. package/src/themes/accelerator/utilities/custom-markdown.mjs +104 -0
  70. package/src/themes/accelerator/utilities/img.mjs +152 -0
  71. package/src/themes/accelerator/utilities/language.json +111 -0
@@ -0,0 +1,112 @@
1
+ ---
2
+ import type { Entry } from '@util/Languages.astro';
3
+
4
+ import t from '@util/language.json';
5
+
6
+ import { SITE } from '@config';
7
+ import { getPages } from '@util/PageQueries.astro';
8
+ import { getItem, setItem } from '@util/Cache.astro';
9
+ import { addSlashToAddress } from '@util/Url.astro';
10
+
11
+ type TaxonomyEntry = {
12
+ title: string;
13
+ count: number;
14
+ };
15
+
16
+ type Taxonomy = {
17
+ tags: TaxonomyEntry[];
18
+ topTags: TaxonomyEntry[];
19
+ categories: TaxonomyEntry[];
20
+ };
21
+
22
+ export function sortByTitle (a: TaxonomyEntry, b: TaxonomyEntry) {
23
+ if ( a.title < b.title ){
24
+ return -1;
25
+ }
26
+
27
+ if ( a.title > b.title ){
28
+ return 1;
29
+ }
30
+
31
+ return 0;
32
+ }
33
+
34
+ export function taxonomyLinks(lang: (entry: Entry) => string) {
35
+ const category = lang(t.articles.category) ?? 'category';
36
+ const categoryLink = `${SITE.subfolder}/${category}/`;
37
+
38
+ const tag = lang(t.articles.tag) ?? 'category';
39
+ const tagLink = `${SITE.subfolder}/${tag}/`;
40
+
41
+ return {
42
+ tag: tag,
43
+ category: category,
44
+ getCategoryLink: (category: string) => {
45
+ return addSlashToAddress(categoryLink + category.toLowerCase().replace(/ /g, '-') + '/1/');
46
+ },
47
+ getTagLink: (tag: string) => {
48
+ return addSlashToAddress(tagLink + tag.toLowerCase().replace(/ /g, '-') + '/1/');
49
+ }
50
+ };
51
+
52
+ }
53
+
54
+ export async function getTaxonomy (): Promise<Taxonomy> {
55
+ const cacheKey = 'Global__taxonomy';
56
+
57
+ let taxonomy: Taxonomy = await getItem(cacheKey);
58
+
59
+ if (taxonomy == null) {
60
+ const allPages = await getPages();
61
+ const tags: { [key: string]: number } = {};
62
+ const cats: { [key: string]: number } = {};
63
+
64
+ // Get taxonomy and counts
65
+ allPages.forEach((p) => {
66
+ p.frontmatter.tags && p.frontmatter.tags.forEach(t => {
67
+ tags[t] = (tags[t] ?? 0) + 1;
68
+ });
69
+
70
+ p.frontmatter.categories && p.frontmatter.categories.forEach(c => {
71
+ cats[c] = (cats[c] ?? 0) + 1;
72
+ });
73
+ });
74
+
75
+ // Map into the taxonomy
76
+ taxonomy = {
77
+ tags: Object.keys(tags).sort().map(x => {
78
+ return {
79
+ title: x,
80
+ count: tags[x]
81
+ };
82
+ }),
83
+ topTags: [],
84
+ categories: Object.keys(cats).sort().map(x => {
85
+ return {
86
+ title: x,
87
+ count: cats[x]
88
+ };
89
+ })
90
+ };
91
+
92
+ // Get a list of "top tags" by usage count
93
+ const length = Math.min(taxonomy.categories.length, taxonomy.tags.length);
94
+ taxonomy.topTags = taxonomy.tags
95
+ .sort((a, b) => b.count - a.count)
96
+ .slice(0, length)
97
+ .sort((a, b) => {
98
+ if ( a.title < b.title ){
99
+ return -1;
100
+ }
101
+ if ( a.title > b.title ){
102
+ return 1;
103
+ }
104
+ return 0;
105
+ });
106
+
107
+ await setItem(cacheKey, taxonomy);
108
+ }
109
+
110
+ return taxonomy;
111
+ }
112
+ ---
@@ -0,0 +1,19 @@
1
+ ---
2
+ export function addSlashToUrl (url: URL) {
3
+ url.pathname += url.pathname.endsWith('/') ? '' : '/';
4
+ return url;
5
+ }
6
+
7
+ export function addSlashToAddress (address: string | undefined) {
8
+ if (!address) {
9
+ address = '/';
10
+ }
11
+
12
+ if (address.indexOf('://') > -1) {
13
+ return address;
14
+ }
15
+
16
+ const url = addSlashToUrl(new URL(address, Astro.site));
17
+ return url.pathname + url.search;
18
+ }
19
+ ---
@@ -0,0 +1,104 @@
1
+ import { SITE } from '/src/config';
2
+ import { visit } from 'unist-util-visit';
3
+ import { h } from 'hastscript';
4
+ import { size } from '/src/data/image-size.mjs';
5
+ import { fromSelector } from 'hast-util-from-selector'
6
+
7
+
8
+ /* Based on https://github.com/remarkjs/remark-directive
9
+ * Examples:
10
+
11
+ ## Inline
12
+
13
+ This is an inline :abbr[I18n]{ title="Internationalization" } element
14
+
15
+ ## Images
16
+
17
+ :img{ src="/img/frankenstein.png" alt="Book cover" loading="lazy" }
18
+
19
+ ## Block
20
+
21
+ :::div{.note}
22
+ This is a custom div element with the class `note`
23
+ :::
24
+
25
+ ## Combinations
26
+ :::figure
27
+ :img{ src="/img/frankenstein.png" alt="Book cover" loading="lazy" }
28
+ ::figcaption[The modern hardback edition of Frankenstein]
29
+ :::
30
+
31
+ */
32
+
33
+ export function getDestination(uri, s) {
34
+ const fromRegEx = new RegExp('^' + SITE.subfolder + '/img/');
35
+ const replacement = SITE.subfolder + '/i/' + s.toString() + '/';
36
+ return uri.replace(fromRegEx, replacement);
37
+ }
38
+
39
+ export function getImageInfo(src, className, sizes) {
40
+ const info = {};
41
+
42
+ let uri = src;
43
+ uri = uri.replace(/.jpg|.jpeg|.png/, '.webp');
44
+
45
+ const imgFallback = getDestination(src, 'x');
46
+
47
+ const imgSmall = getDestination(uri, size.small);
48
+ const imgMedium = getDestination(uri, size.medium);
49
+ const imgLarge = getDestination(uri, size.large);
50
+
51
+ info.src = imgFallback;
52
+ info.srcset = `${imgSmall} ${size.small}w, ${imgMedium} ${size.medium}w, ${imgLarge}`;
53
+ info.sizes = sizes;
54
+ info.class = (className ?? '' + ' resp-img').trim();
55
+
56
+ return info;
57
+ }
58
+
59
+ /** @type {import('unified').Plugin<[], import('mdast').Root>} */
60
+ export function attributeMarkdown() {
61
+ return (tree) => {
62
+
63
+ visit(tree, (node) => {
64
+ if (['textDirective', 'leafDirective', 'containerDirective'].includes(node.type)) {
65
+ const data = node.data || (node.data = {});
66
+ const hast = h(node.name, node.attributes);
67
+
68
+ if (hast.properties.src) {
69
+ // Process the image
70
+ const info = getImageInfo(hast.properties.src, hast.properties.class, SITE.images.contentSize);
71
+
72
+ hast.properties.src = info.src;
73
+ hast.properties.srcset = info.srcset;
74
+ hast.properties.sizes = info.sizes;
75
+ hast.properties.class = info.class;
76
+ }
77
+
78
+ data.hName = hast.tagName;
79
+ data.hProperties = hast.properties;
80
+ }
81
+ });
82
+ }
83
+ }
84
+
85
+ /** @type {import('unified').Plugin<[], import('mdast').Root>} */
86
+ export function wrapTables() {
87
+ return (tree) => {
88
+ visit(tree, (node, i, parent) => {
89
+ if (node.type == 'table') {
90
+ // Create the wrapping element
91
+ const wrap = fromSelector('div');
92
+ const data = wrap.data || (wrap.data = {})
93
+ const props = data.hProperties || (data.hProperties = {})
94
+ props.className = 'table-wrap';
95
+
96
+ // Add the table to the wrapper
97
+ wrap.children = [node];
98
+
99
+ // Replace the table with the wrapper
100
+ parent.children[i] = wrap;
101
+ }
102
+ });
103
+ }
104
+ }
@@ -0,0 +1,152 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { ImagePool } from '@squoosh/lib';
4
+
5
+ const workingDirectory = process.cwd();
6
+
7
+ const imageSize = await import('file://' + path.join(workingDirectory, 'src/data/image-size.mjs'));
8
+ const imageModule = await import('file://' + path.join(workingDirectory, 'src/data/images.mjs'));
9
+ const size = imageSize.size;
10
+ const imagePaths = imageModule.imagePaths;
11
+
12
+ const imagePath = path.join('public', imagePaths.src);
13
+ const outputPath = path.join('public', imagePaths.dest);
14
+ const imageDirectory = path.join(workingDirectory, imagePath);
15
+
16
+ console.log(imageDirectory);
17
+
18
+ const filesToProcess = [];
19
+
20
+ function getDestinationFilePathless(source, s) {
21
+ let destination = path.join(workingDirectory, outputPath, s.toString(), source);
22
+ destination = destination.replace(path.parse(destination).ext, '');
23
+ return destination;
24
+ }
25
+
26
+ async function createDestinationFolder(destinationFile) {
27
+ const file = path.parse(destinationFile + '.txt');
28
+ console.log(file.dir);
29
+ await fs.promises.mkdir(file.dir, { recursive: true });
30
+ }
31
+
32
+ async function recurseFiles(directory) {
33
+ const f = await fs.promises.readdir(path.join(imageDirectory, directory), { withFileTypes: true });
34
+
35
+ for (const file of f) {
36
+ if (file.isDirectory()) {
37
+ const nextDirectory = path.join(directory, file.name);
38
+ await recurseFiles(nextDirectory);
39
+ } else {
40
+ const ext = path.parse(file.name).ext;
41
+
42
+ switch (ext) {
43
+ case '.jpg':
44
+ case '.jpeg':
45
+ case '.png':
46
+ case '.webp':
47
+ const sourcePath = path.join(directory, file.name);
48
+
49
+ const webP = sourcePath.replace(/.jpg$|.jpeg$|.png$/, '.webp');
50
+ const info = {
51
+ path: sourcePath,
52
+ webP: webP
53
+ };
54
+
55
+ const fullDestination = path.join(workingDirectory, outputPath, 'x', info.path);
56
+
57
+ if(!fs.existsSync(fullDestination)) {
58
+ filesToProcess.push(info);
59
+ }
60
+
61
+ // The code below uses modified dates (and will update more images than the above)
62
+ // const fullPath = path.join(imageDirectory, info.path);
63
+ // const modified = fs.statSync(fullPath).mtime;
64
+
65
+ // const destinationModified = fs.existsSync(fullDestination)
66
+ // ? fs.statSync(fullDestination).mtime
67
+ // : new Date(0);
68
+
69
+ // if (destinationModified < modified) {
70
+ // filesToProcess.push(info);
71
+ // }
72
+ break;
73
+ }
74
+ }
75
+ }
76
+ }
77
+
78
+ await recurseFiles('');
79
+
80
+ console.log(`Found ${filesToProcess.length} files to process`);
81
+
82
+ async function processImage(imagePool, src, options) {
83
+ const file = await fs.promises.readFile(src);
84
+ const image = imagePool.ingestImage(file);
85
+ await image.encode(options);
86
+ return image;
87
+ }
88
+
89
+ for (const file of filesToProcess) {
90
+ console.log(file.path);
91
+ const source = path.join(imageDirectory, file.path);
92
+ const destination = getDestinationFilePathless(file.path, 'x');
93
+ await createDestinationFolder(destination);
94
+
95
+ const ext = path.parse(source).ext;
96
+
97
+ let image;
98
+ let rawEncodedImage;
99
+
100
+ // Create optimised fallback image
101
+ const imagePool = new ImagePool(1);
102
+ switch (ext) {
103
+ case '.png':
104
+ image = await processImage(imagePool, source, { oxipng: {} });
105
+ rawEncodedImage = (await image.encodedWith.oxipng).binary;
106
+ await fs.promises.writeFile(destination + '.png', rawEncodedImage);
107
+ break;
108
+ case '.jpg':
109
+ case '.jpeg':
110
+ image = await processImage(imagePool, source, { mozjpeg: {} });
111
+ rawEncodedImage = (await image.encodedWith.mozjpeg).binary;
112
+ await fs.promises.writeFile(destination + '.jpg', rawEncodedImage);
113
+ break;
114
+ case '.webp':
115
+ image = await processImage(imagePool, source, { webp: { quality: 85 } });
116
+ rawEncodedImage = (await image.encodedWith.webp).binary;
117
+ await fs.promises.writeFile(destination + '.webp', rawEncodedImage);
118
+ break;
119
+ }
120
+ await imagePool.close();
121
+
122
+ // Create resized images
123
+ for (const key in size) {
124
+ const imagePool = new ImagePool(1);
125
+ const resizeDestination = getDestinationFilePathless(file.path, size[key]);
126
+ await createDestinationFolder(resizeDestination);
127
+
128
+ const imgFile = await fs.promises.readFile(source);
129
+ const image = imagePool.ingestImage(imgFile);
130
+
131
+ const info = await image.decoded;
132
+ if (info.width > size[key]) {
133
+ // Only resize if the image is larger than the target size
134
+ const preprocessOptions = {
135
+ resize: {
136
+ width: size[key]
137
+ }
138
+ };
139
+
140
+ await image.preprocess(preprocessOptions);
141
+ }
142
+
143
+ await image.encode({ webp: {} });
144
+
145
+ rawEncodedImage = (await image.encodedWith.webp).binary;
146
+ await fs.promises.writeFile(resizeDestination + '.webp', rawEncodedImage);
147
+
148
+ await imagePool.close();
149
+ }
150
+ }
151
+
152
+ console.log(`Finished`);
@@ -0,0 +1,111 @@
1
+ {
2
+ "aria": {
3
+ "breadcrumbs": {
4
+ "en": "Breadcrumb"
5
+ },
6
+ "site_navigation": {
7
+ "en": "Site Navigation"
8
+ },
9
+ "toc": {
10
+ "en": "Table of contents"
11
+ },
12
+ "paging": {
13
+ "en": "Paging"
14
+ },
15
+ "skiplinks": {
16
+ "en": "Skip Links"
17
+ },
18
+ "back_to_top": {
19
+ "en": "Skip Back"
20
+ }
21
+ },
22
+ "skiplinks": {
23
+ "skip_to_top": {
24
+ "en": "Back to top"
25
+ },
26
+ "skip_to_navigation": {
27
+ "en": "Skip to navigation"
28
+ },
29
+ "skip_to_content": {
30
+ "en": "Skip to main content"
31
+ },
32
+ "skip_to_footer": {
33
+ "en": "Skip to footer"
34
+ }
35
+ },
36
+ "header": {
37
+ "open_menu": {
38
+ "en": "Open menu"
39
+ },
40
+ "open_search": {
41
+ "en": "Open site search"
42
+ }
43
+ },
44
+ "navigation": {
45
+ "title": {
46
+ "en": "Navigation"
47
+ }
48
+ },
49
+ "toc": {
50
+ "title":{
51
+ "en": "Table of contents"
52
+ }
53
+ },
54
+ "footer": {
55
+ "copyright": {
56
+ "en": "Copyright"
57
+ }
58
+ },
59
+ "post": {
60
+ "written_by": {
61
+ "en": "Written by"
62
+ },
63
+ "last_modified": {
64
+ "en": "Revised"
65
+ }
66
+ },
67
+ "author": {
68
+ "recent_articles": {
69
+ "en": "Recent Articles"
70
+ },
71
+ "twitter": {
72
+ "en": "Twitter"
73
+ }
74
+ },
75
+ "search": {
76
+ "search_for": {
77
+ "en": "Search for"
78
+ },
79
+ "submit": {
80
+ "en": "Go"
81
+ },
82
+ "results_title": {
83
+ "en": "Results"
84
+ },
85
+ "no_results_title": {
86
+ "en": "No Results"
87
+ }
88
+ },
89
+ "articles": {
90
+ "previous": {
91
+ "en": "Prev"
92
+ },
93
+ "next": {
94
+ "en": "Next"
95
+ },
96
+ "category_title": {
97
+ "en": "Categories"
98
+ },
99
+ "tag_title": {
100
+ "en": "Tags"
101
+ },
102
+ "category": {
103
+ "_note": "This must match your URL path to category pages, as in /category/example/1/",
104
+ "en": "category"
105
+ },
106
+ "tag": {
107
+ "_note": "This must match your URL path to tag pages, as in /tag/example/1/",
108
+ "en": "tag"
109
+ }
110
+ }
111
+ }