eleventy.config.mjs 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  1. import { appFilters } from "../shared/e11ty/filters.mjs"
  2. import { appData } from "../shared/e11ty/data.mjs";
  3. import { readFileSync, existsSync } from 'node:fs';
  4. import { fileURLToPath } from 'node:url'
  5. import { join, dirname } from 'node:path';
  6. import beautify from 'js-beautify';
  7. const shiki = await import('shiki');
  8. import { createCssVariablesTheme } from 'shiki/core'
  9. const __dirname = dirname(fileURLToPath(import.meta.url))
  10. export default function (eleventyConfig) {
  11. const environment = process.env.NODE_ENV || "production";
  12. appFilters(eleventyConfig);
  13. appData(eleventyConfig);
  14. eleventyConfig.addPassthroughCopy({
  15. "node_modules/@tabler/core/dist": "dist",
  16. "public": "/",
  17. "static": "static",
  18. });
  19. eleventyConfig.addCollection('docs', collection => {
  20. return [...collection.getFilteredByGlob('./content/**/*.md')].sort((a, b) => {
  21. return a.data.title - b.data.title;
  22. });
  23. });
  24. eleventyConfig.setInputDirectory("content");
  25. eleventyConfig.setOutputDirectory("dist");
  26. eleventyConfig.setLayoutsDirectory("../../shared/layouts");
  27. eleventyConfig.setIncludesDirectory("../../shared/includes");
  28. eleventyConfig.setDataDirectory("../../shared/data");
  29. eleventyConfig.amendLibrary('md', () => { });
  30. eleventyConfig.addShortcode('scss-docs', function (name, filename) {
  31. const file = join(__dirname, `../core/scss/${filename}`)
  32. if (existsSync(file)) {
  33. const content = readFileSync(file, 'utf8');
  34. const regex = new RegExp(`\/\/\\sscss-docs-start\\s${name}\\n(.+?)\/\/\\sscss-docs-end`, 'gs')
  35. const m = content.matchAll(regex)
  36. if (m) {
  37. const matches = [...m]
  38. if (matches[0] && matches[0][1]) {
  39. const lines = matches[0][1].split('\n');
  40. // Find minimum number of leading spaces in non-empty lines
  41. const minIndent = lines
  42. .filter(line => line.trim().length > 0)
  43. .reduce((min, line) => {
  44. const match = line.match(/^(\s*)/);
  45. const leadingSpaces = match ? match[1].length : 0;
  46. return Math.min(min, leadingSpaces);
  47. }, Infinity);
  48. // Remove that many spaces from the start of each line
  49. const result = lines.map(line => line.startsWith(' '.repeat(minIndent))
  50. ? line.slice(minIndent)
  51. : line).join('\n');
  52. return "\n```scss\n" + result.trimRight() + "\n```\n"
  53. }
  54. }
  55. }
  56. return ''
  57. })
  58. // Shiki
  59. eleventyConfig.on('eleventy.before', async () => {
  60. const myTheme = createCssVariablesTheme({
  61. name: 'css-variables',
  62. variablePrefix: '--shiki-',
  63. variableDefaults: {},
  64. fontStyle: true
  65. })
  66. const highlighter = await shiki.createHighlighter({
  67. themes: ['github-dark', myTheme],
  68. langs: [
  69. 'html',
  70. 'blade',
  71. 'php',
  72. 'yaml',
  73. 'js',
  74. 'jsx',
  75. 'ts',
  76. 'shell',
  77. 'diff',
  78. 'vue',
  79. 'scss',
  80. 'css'
  81. ],
  82. });
  83. eleventyConfig.amendLibrary('md', function (mdLib) {
  84. return mdLib.set({
  85. highlight: function (code, lang) {
  86. // prettify code
  87. if(lang === 'html') {
  88. code = beautify.html(code, {
  89. indent_size: 2,
  90. wrap_line_length: 80,
  91. });
  92. }
  93. let highlightedCode = highlighter.codeToHtml(code, {
  94. lang: lang,
  95. theme: 'github-dark'
  96. });
  97. return highlightedCode;
  98. },
  99. });
  100. }
  101. );
  102. });
  103. /**
  104. * Filters
  105. */
  106. function buildCollectionTree(flatData) {
  107. const tree = [];
  108. const lookup = {};
  109. flatData
  110. .filter(item => item.url !== '/')
  111. .forEach(item => {
  112. lookup[item.url] = { ...item, children: [] };
  113. });
  114. flatData.forEach(item => {
  115. const parts = item.url.split('/').filter(Boolean);
  116. if (parts.length === 1) {
  117. tree.push(lookup[item.url]);
  118. } else {
  119. const parentUrl = '/' + parts.slice(0, -1).join('/') + '/';
  120. if (lookup[parentUrl]) {
  121. lookup[parentUrl].children.push(lookup[item.url]);
  122. } else {
  123. tree.push(lookup[item.url]);
  124. }
  125. }
  126. });
  127. return tree;
  128. }
  129. eleventyConfig.addFilter("collection-tree", function (collection) {
  130. const a = collection.map(item => {
  131. return {
  132. data: item.data,
  133. page: item.page,
  134. url: item.url,
  135. children: []
  136. }
  137. }).sort((a, b) => {
  138. const orderA = a.data.order ?? 999;
  139. const orderB = b.data.order ?? 999;
  140. if (orderA !== orderB) {
  141. return orderA - orderB;
  142. }
  143. const titleA = a.data.title ?? '';
  144. const titleB = b.data.title ?? '';
  145. return titleA.localeCompare(titleB);
  146. });
  147. return buildCollectionTree(a);
  148. });
  149. eleventyConfig.addFilter("collection-children", function (collection, page) {
  150. const url = page.url.split('/').filter(Boolean).join('/');
  151. const filteredCollection = collection.filter(item => {
  152. const parts = item.url.split('/').filter(Boolean);
  153. return parts.length > 1 && parts.slice(0, -1).join('/') === url;
  154. });
  155. return filteredCollection.sort((a, b) => {
  156. return (a.data?.order || 999) - (b.data?.order || 999);
  157. });
  158. });
  159. eleventyConfig.addFilter("next-prev", function (collection, page) {
  160. const items = collection
  161. .filter(item => {
  162. const parts = item.url.split('/').filter(Boolean);
  163. return parts.length > 1 && parts.slice(0, -1).join('/') === page.url.split('/').filter(Boolean).slice(0, -1).join('/');
  164. })
  165. .sort((a, b) => {
  166. return a.data.title.localeCompare(b.data.title);
  167. })
  168. .sort((a, b) => {
  169. return (a.data?.order || 999) - (b.data?.order || 999);
  170. });
  171. const index = items.findIndex(item => item.url === page.url);
  172. const prevPost = index > 0 ? items[index - 1] : null;
  173. const nextPost = index < items.length - 1 ? items[index + 1] : null;
  174. return {
  175. prev: prevPost ? prevPost : null,
  176. next: nextPost ? nextPost : null,
  177. };
  178. });
  179. const generateUniqueId = (text) => {
  180. return text
  181. .replace(/<[^>]+>/g, "")
  182. .replace(/\s/g, "-")
  183. .replace(/[^\w-]+/g, "")
  184. .replace(/--+/g, "-")
  185. .replace(/^-+|-+$/g, "")
  186. .toLowerCase();
  187. }
  188. eleventyConfig.addFilter("headings-id", function (content) {
  189. return content.replace(/<h([1-6])>([^<]+)<\/h\1>/g, (match, level, text) => {
  190. const headingId = generateUniqueId(text);
  191. return `<h${level} id="${headingId}">${text}</h${level}>`;
  192. });
  193. })
  194. eleventyConfig.addFilter("toc", function (name) {
  195. const toc = [];
  196. const contentWithoutExamples = name.replace(/<!--EXAMPLE-->[\s\S]*?<!--\/EXAMPLE-->/g, '');
  197. const headings = contentWithoutExamples.match(/<h([23])>([^<]+)<\/h\1>/g);
  198. if (headings) {
  199. headings.forEach(heading => {
  200. const level = parseInt(heading.match(/<h([1-6])>/)[1]);
  201. const text = heading.replace(/<[^>]+>/g, "");
  202. const id = generateUniqueId(text);
  203. toc.push({ level, text, id });
  204. });
  205. }
  206. return toc;
  207. })
  208. eleventyConfig.addFilter("remove-href", function (content) {
  209. return content.replace(/href="#"/g, 'href="javascript:void(0)"');
  210. })
  211. /**
  212. * Data
  213. */
  214. const pkg = JSON.parse(readFileSync(join("..", "core", "package.json"), "utf-8"))
  215. eleventyConfig.addGlobalData("environment", environment);
  216. eleventyConfig.addGlobalData("package", pkg);
  217. eleventyConfig.addGlobalData("cdnUrl", `https://cdn.jsdelivr.net/npm/@tabler/core@${pkg.version}`);
  218. const data = {
  219. iconsCount: () => 123,
  220. emailsCount: () => 123,
  221. illustrationsCount: () => 123
  222. };
  223. for (const [key, value] of Object.entries(data)) {
  224. eleventyConfig.addGlobalData(key, value);
  225. }
  226. eleventyConfig.addGlobalData("docs-links", [
  227. { title: 'Website', url: 'https://tabler.io', icon: 'world' },
  228. { title: 'Preview', url: 'https://preview.tabler.io', icon: 'layout-dashboard' },
  229. { title: 'Support', url: 'https://tabler.io/support', icon: 'headset' },
  230. ]);
  231. /**
  232. * Tags
  233. */
  234. eleventyConfig.addPairedShortcode("cards", function (content) {
  235. return `<div class="mt-6"><div class="row g-3">${content}</div></div>`;
  236. });
  237. eleventyConfig.addPairedShortcode("card", function (content, title, href) {
  238. return `<div class="col-6">
  239. <${href ? "a" : "div"} href="${href}" class="card ${href ? "" : " bg-surface-tertiary"}">
  240. <div class="card-body">
  241. <div class="position-relative">${href ? "" : `<span class="badge position-absolute top-0 end-0">Coming soon</span>`}
  242. <div class="row align-items-center">
  243. <div class="col">
  244. <h3 class="card-title mb-2">${title}</h3>
  245. <div class="text-secondary small">${content}</div>
  246. </div>
  247. <div class="col-auto">
  248. <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-chevron-right"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M9 6l6 6l-6 6" /></svg>
  249. </div>
  250. </div>
  251. </div>
  252. </div>
  253. </${href ? "a" : "div"}>
  254. </div>`;
  255. });
  256. };