Skip to content

Commit 4a9516a

Browse files
Rebase website from techdoc template (#52)
## TLDR; Website now uses eleventy-plugin-techdoc as npm dependency. Fixed canonical URLs to dartnode.org. ## What Does This Do? - Rebases website to use eleventy-plugin-techdoc npm package instead of inline config - Fixes all canonical/meta URLs from dartnode.dev to dartnode.org - Adds `npm run update:theme` script for easy theme updates - Removes redundant files now handled by plugin (feed.njk, sitemap.njk, robots.txt, llms.txt) ## Brief Details? - eleventy.config.js simplified - delegates to techdoc plugin - site.json url changed to https://dartnode.org - index.njk JSON-LD URLs updated to dartnode.org - package.json adds update:theme script and eleventy-plugin-techdoc dependency ## How Do The Tests Prove The Change Works? - `cd website && npm run build` succeeds - Built site verified against live dartnode.org - structure matches - `grep dartnode.org website/_site/index.html` confirms correct domain (26 occurrences) - Only remaining dartnode.dev is pub.dev publisher URL (correct)
1 parent 55ef35a commit 4a9516a

File tree

11 files changed

+584
-459
lines changed

11 files changed

+584
-459
lines changed

examples/too_many_cooks/readme.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ Multi-agent coordination MCP server - enables multiple AI agents to safely edit
99
- **Messaging**: Inter-agent communication with broadcast support
1010
- **Plan Visibility**: Share goals and current tasks across agents
1111
- **Real-time Status**: System overview of all agents, locks, and plans
12-
- **Written in Dart**: Made with [dart_node](https://dartnode.dev)
12+
- **Written in Dart**: Made with [dart_node](https://www.dartnode.org)
1313

1414
## Installation
1515

website/eleventy.config.js

Lines changed: 102 additions & 199 deletions
Original file line numberDiff line numberDiff line change
@@ -1,228 +1,104 @@
1-
import syntaxHighlight from "@11ty/eleventy-plugin-syntaxhighlight";
2-
import pluginRss from "@11ty/eleventy-plugin-rss";
3-
import eleventyNavigationPlugin from "@11ty/eleventy-navigation";
4-
import markdownIt from "markdown-it";
5-
import markdownItAnchor from "markdown-it-anchor";
1+
import { readFileSync } from "fs";
62
import { execSync } from "child_process";
7-
import { dirname, resolve } from "path";
3+
import { dirname, resolve, join } from "path";
84
import { fileURLToPath } from "url";
95

6+
// Import plugin submodules via file paths (workaround for #9:
7+
// virtual templates override local layouts, so we register non-layout
8+
// virtual templates separately. Package only exports "." so we use
9+
// direct file paths.)
10+
import { registerFilters } from "./node_modules/eleventy-plugin-techdoc/lib/filters/index.js";
11+
import { registerCollections } from "./node_modules/eleventy-plugin-techdoc/lib/plugins/collections.js";
12+
import { registerShortcodes } from "./node_modules/eleventy-plugin-techdoc/lib/shortcodes/index.js";
13+
import { configureMarkdown } from "./node_modules/eleventy-plugin-techdoc/lib/plugins/markdown.js";
14+
import syntaxHighlight from "@11ty/eleventy-plugin-syntaxhighlight";
15+
import rss from "@11ty/eleventy-plugin-rss";
16+
import navigation from "@11ty/eleventy-navigation";
17+
1018
const __dirname = dirname(fileURLToPath(import.meta.url));
1119
const packagesDir = resolve(__dirname, "..", "packages");
1220

13-
const supportedLanguages = ['en', 'zh'];
14-
const defaultLanguage = 'en';
21+
const techdocOptions = {
22+
site: {
23+
name: "dart_node",
24+
title: "dart_node - Full-Stack Dart for the JavaScript Ecosystem",
25+
url: "https://dartnode.org",
26+
description: "Write React, React Native, and Express apps entirely in Dart. One language for frontend, backend, and mobile.",
27+
author: "dart_node team",
28+
themeColor: "#0E7C6B",
29+
stylesheet: "/assets/css/styles.css",
30+
twitterSite: "@dart_node",
31+
twitterCreator: "@dart_node",
32+
ogImage: "/assets/images/og-image.png",
33+
ogImageWidth: "1200",
34+
ogImageHeight: "630",
35+
organization: {
36+
name: "dart_node",
37+
logo: "/assets/images/og-image.png",
38+
sameAs: [
39+
"https://github.com/melbournedeveloper/dart_node",
40+
"https://twitter.com/dart_node",
41+
"https://pub.dev/publishers/christianfindlay.com/packages"
42+
]
43+
}
44+
},
45+
features: {
46+
blog: true,
47+
docs: true,
48+
darkMode: true,
49+
i18n: true,
50+
},
51+
i18n: {
52+
defaultLanguage: "en",
53+
languages: ["en", "zh"],
54+
},
55+
};
1556

1657
export default function(eleventyConfig) {
17-
// Don't use .gitignore to ignore files (we want to process generated docs)
1858
eleventyConfig.setUseGitIgnore(false);
1959

20-
// Configure markdown-it with anchor plugin for header IDs
21-
const mdOptions = {
22-
html: true,
23-
breaks: false,
24-
linkify: true
25-
};
60+
// === techdoc plugin features (without layout virtual templates) ===
61+
// We use the plugin's filters, collections, shortcodes, markdown config,
62+
// and bundled plugins, but NOT its layout virtual templates because
63+
// dart_node's layouts are superior (see GitHub issue #9).
2664

27-
const mdAnchorOptions = {
28-
permalink: markdownItAnchor.permalink.headerLink(),
29-
slugify: (s) => s.toLowerCase().replace(/\s+/g, '-').replace(/[^\w-]+/g, ''),
30-
level: [1, 2, 3, 4]
31-
};
65+
// Global data (same as plugin sets)
66+
eleventyConfig.addGlobalData("techdocOptions", techdocOptions);
67+
eleventyConfig.addGlobalData("supportedLanguages", techdocOptions.i18n.languages);
68+
eleventyConfig.addGlobalData("defaultLanguage", techdocOptions.i18n.defaultLanguage);
3269

33-
const md = markdownIt(mdOptions).use(markdownItAnchor, mdAnchorOptions);
34-
eleventyConfig.setLibrary("md", md);
70+
// Plugin submodules
71+
configureMarkdown(eleventyConfig);
72+
registerFilters(eleventyConfig, techdocOptions);
73+
registerCollections(eleventyConfig, techdocOptions);
74+
registerShortcodes(eleventyConfig);
3575

36-
// Plugins
76+
// Bundled plugins
3777
eleventyConfig.addPlugin(syntaxHighlight);
38-
eleventyConfig.addPlugin(pluginRss);
39-
eleventyConfig.addPlugin(eleventyNavigationPlugin);
78+
eleventyConfig.addPlugin(rss);
79+
eleventyConfig.addPlugin(navigation);
80+
81+
// Plugin structural CSS (no colors - site provides visual styling)
82+
const techdocAssetsDir = join(__dirname, "node_modules", "eleventy-plugin-techdoc", "assets");
83+
eleventyConfig.addPassthroughCopy({ [techdocAssetsDir]: "techdoc" });
4084

41-
// Passthrough copy for assets
85+
// Register only NON-LAYOUT virtual templates from the plugin
86+
// (feed, sitemap, robots.txt, llms.txt, blog scaffold pages)
87+
// Layouts come from our local src/_includes/layouts/ which are superior.
88+
registerSeoVirtualTemplates(eleventyConfig);
89+
90+
// === Site-specific config ===
4291
eleventyConfig.addPassthroughCopy("src/assets");
4392
eleventyConfig.addPassthroughCopy("src/api");
44-
eleventyConfig.addPassthroughCopy("src/robots.txt");
45-
eleventyConfig.addPassthroughCopy("src/llms.txt");
46-
47-
// Watch targets
4893
eleventyConfig.addWatchTarget("src/assets/");
4994

50-
// Watch READMEs and copy when they change
5195
eleventyConfig.addWatchTarget(packagesDir);
5296
eleventyConfig.on("eleventy.beforeWatch", (changedFiles) => {
5397
if (changedFiles.some(f => f.endsWith("README.md"))) {
5498
execSync("node scripts/copy-readmes.js", { stdio: "inherit" });
5599
}
56100
});
57101

58-
// Collections
59-
// English posts only (from src/blog/)
60-
eleventyConfig.addCollection("posts", function(collectionApi) {
61-
return collectionApi.getFilteredByGlob("src/blog/*.md").sort((a, b) => {
62-
return b.date - a.date;
63-
});
64-
});
65-
66-
// Chinese posts only (from src/zh/blog/)
67-
eleventyConfig.addCollection("zhPosts", function(collectionApi) {
68-
return collectionApi.getFilteredByGlob("src/zh/blog/*.md").sort((a, b) => {
69-
return b.date - a.date;
70-
});
71-
});
72-
73-
eleventyConfig.addCollection("docs", function(collectionApi) {
74-
return collectionApi.getFilteredByGlob("src/docs/**/*.md");
75-
});
76-
77-
// Tag collection - get all unique tags from blog posts
78-
eleventyConfig.addCollection("tagList", function(collectionApi) {
79-
const tagSet = new Set();
80-
collectionApi.getFilteredByGlob("src/blog/*.md").forEach(post => {
81-
(post.data.tags || []).forEach(tag => {
82-
tag !== 'post' && tag !== 'posts' && tagSet.add(tag);
83-
});
84-
});
85-
return [...tagSet].sort();
86-
});
87-
88-
// Category collection - get all unique categories from blog posts
89-
eleventyConfig.addCollection("categoryList", function(collectionApi) {
90-
const categorySet = new Set();
91-
collectionApi.getFilteredByGlob("src/blog/*.md").forEach(post => {
92-
post.data.category && categorySet.add(post.data.category);
93-
});
94-
return [...categorySet].sort();
95-
});
96-
97-
// Posts by tag - creates a collection for each tag
98-
eleventyConfig.addCollection("postsByTag", function(collectionApi) {
99-
const postsByTag = {};
100-
collectionApi.getFilteredByGlob("src/blog/*.md").forEach(post => {
101-
(post.data.tags || []).forEach(tag => {
102-
tag !== 'post' && tag !== 'posts' && (postsByTag[tag] = postsByTag[tag] || []).push(post);
103-
});
104-
});
105-
Object.keys(postsByTag).forEach(tag => {
106-
postsByTag[tag].sort((a, b) => b.date - a.date);
107-
});
108-
return postsByTag;
109-
});
110-
111-
// Posts by category - creates a collection for each category
112-
eleventyConfig.addCollection("postsByCategory", function(collectionApi) {
113-
const postsByCategory = {};
114-
collectionApi.getFilteredByGlob("src/blog/*.md").forEach(post => {
115-
post.data.category && (postsByCategory[post.data.category] = postsByCategory[post.data.category] || []).push(post);
116-
});
117-
Object.keys(postsByCategory).forEach(cat => {
118-
postsByCategory[cat].sort((a, b) => b.date - a.date);
119-
});
120-
return postsByCategory;
121-
});
122-
123-
// Chinese tag collection
124-
eleventyConfig.addCollection("zhTagList", function(collectionApi) {
125-
const tagSet = new Set();
126-
collectionApi.getFilteredByGlob("src/zh/blog/*.md").forEach(post => {
127-
(post.data.tags || []).forEach(tag => {
128-
tag !== 'post' && tag !== 'posts' && tagSet.add(tag);
129-
});
130-
});
131-
return [...tagSet].sort();
132-
});
133-
134-
// Chinese category collection
135-
eleventyConfig.addCollection("zhCategoryList", function(collectionApi) {
136-
const categorySet = new Set();
137-
collectionApi.getFilteredByGlob("src/zh/blog/*.md").forEach(post => {
138-
post.data.category && categorySet.add(post.data.category);
139-
});
140-
return [...categorySet].sort();
141-
});
142-
143-
// Chinese posts by tag
144-
eleventyConfig.addCollection("zhPostsByTag", function(collectionApi) {
145-
const postsByTag = {};
146-
collectionApi.getFilteredByGlob("src/zh/blog/*.md").forEach(post => {
147-
(post.data.tags || []).forEach(tag => {
148-
tag !== 'post' && tag !== 'posts' && (postsByTag[tag] = postsByTag[tag] || []).push(post);
149-
});
150-
});
151-
Object.keys(postsByTag).forEach(tag => {
152-
postsByTag[tag].sort((a, b) => b.date - a.date);
153-
});
154-
return postsByTag;
155-
});
156-
157-
// Chinese posts by category
158-
eleventyConfig.addCollection("zhPostsByCategory", function(collectionApi) {
159-
const postsByCategory = {};
160-
collectionApi.getFilteredByGlob("src/zh/blog/*.md").forEach(post => {
161-
post.data.category && (postsByCategory[post.data.category] = postsByCategory[post.data.category] || []).push(post);
162-
});
163-
Object.keys(postsByCategory).forEach(cat => {
164-
postsByCategory[cat].sort((a, b) => b.date - a.date);
165-
});
166-
return postsByCategory;
167-
});
168-
169-
// Filters
170-
eleventyConfig.addFilter("dateFormat", (dateObj) => {
171-
return new Date(dateObj).toLocaleDateString('en-US', {
172-
year: 'numeric',
173-
month: 'long',
174-
day: 'numeric'
175-
});
176-
});
177-
178-
eleventyConfig.addFilter("isoDate", (dateObj) => {
179-
return new Date(dateObj).toISOString();
180-
});
181-
182-
eleventyConfig.addFilter("limit", (arr, limit) => {
183-
return arr.slice(0, limit);
184-
});
185-
186-
eleventyConfig.addFilter("capitalize", (str) => {
187-
return str ? str.charAt(0).toUpperCase() + str.slice(1) : '';
188-
});
189-
190-
eleventyConfig.addFilter("slugify", (str) => {
191-
return str ? str.toLowerCase().replace(/\s+/g, '-').replace(/[^\w-]+/g, '') : '';
192-
});
193-
194-
// i18n filter - get translation by key path
195-
eleventyConfig.addFilter("t", (key, lang = defaultLanguage) => {
196-
const i18n = eleventyConfig.globalData?.i18n;
197-
if (!i18n) return key;
198-
const langData = i18n[lang] || i18n[defaultLanguage];
199-
const keys = key.split('.');
200-
let value = langData;
201-
for (const k of keys) {
202-
value = value?.[k];
203-
}
204-
return value || key;
205-
});
206-
207-
// Get alternate language URL
208-
eleventyConfig.addFilter("altLangUrl", (url, currentLang, targetLang) => {
209-
if (currentLang === 'en' && targetLang !== 'en') {
210-
return `/${targetLang}${url}`;
211-
} else if (currentLang !== 'en' && targetLang === 'en') {
212-
return url.replace(`/${currentLang}`, '') || '/';
213-
} else if (currentLang !== 'en' && targetLang !== 'en') {
214-
return url.replace(`/${currentLang}`, `/${targetLang}`);
215-
}
216-
return url;
217-
});
218-
219-
// Add global data for languages
220-
eleventyConfig.addGlobalData("supportedLanguages", supportedLanguages);
221-
eleventyConfig.addGlobalData("defaultLanguage", defaultLanguage);
222-
223-
// Shortcodes
224-
eleventyConfig.addShortcode("year", () => `${new Date().getFullYear()}`);
225-
226102
return {
227103
dir: {
228104
input: "src",
@@ -235,3 +111,30 @@ export default function(eleventyConfig) {
235111
htmlTemplateEngine: "njk"
236112
};
237113
}
114+
115+
/**
116+
* Register only SEO virtual templates from the techdoc plugin.
117+
* Layouts and blog scaffold pages come from local files (dart_node's are superior).
118+
*/
119+
function registerSeoVirtualTemplates(eleventyConfig) {
120+
const templatesDir = join(
121+
__dirname, "node_modules", "eleventy-plugin-techdoc", "templates"
122+
);
123+
124+
eleventyConfig.addTemplate(
125+
"feed.njk",
126+
readFileSync(join(templatesDir, "pages/feed.njk"), "utf-8")
127+
);
128+
eleventyConfig.addTemplate(
129+
"sitemap.njk",
130+
readFileSync(join(templatesDir, "pages/sitemap.njk"), "utf-8")
131+
);
132+
eleventyConfig.addTemplate(
133+
"robots.txt.njk",
134+
readFileSync(join(templatesDir, "pages/robots.txt.njk"), "utf-8")
135+
);
136+
eleventyConfig.addTemplate(
137+
"llms.txt.njk",
138+
readFileSync(join(templatesDir, "pages/llms.txt.njk"), "utf-8")
139+
);
140+
}

0 commit comments

Comments
 (0)