@beforesemicolon/builder 1.8.20 → 1.8.22
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.
- package/README.md +8 -0
- package/dist/cjs/docs/run.js +3 -3
- package/dist/cjs/docs/templates/fading-citrus/README.md +35 -10
- package/dist/cjs/docs/templates/fading-citrus/layouts/_head-meta.js +54 -35
- package/dist/cjs/docs/templates/fading-citrus/stylesheets/documentation.css +7 -0
- package/dist/cjs/docs/templates/fading-citrus/template.config.js +7 -0
- package/dist/cjs/docs/types.js +1 -1
- package/dist/esm/docs/run.js +4 -4
- package/dist/esm/docs/templates/fading-citrus/README.md +35 -10
- package/dist/esm/docs/templates/fading-citrus/layouts/_head-meta.js +54 -35
- package/dist/esm/docs/templates/fading-citrus/stylesheets/documentation.css +7 -0
- package/dist/esm/docs/templates/fading-citrus/template.config.js +7 -0
- package/dist/types/docs/types.d.ts +10 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -303,6 +303,7 @@ A template config exports an object:
|
|
|
303
303
|
|
|
304
304
|
```js
|
|
305
305
|
export default {
|
|
306
|
+
meta: {},
|
|
306
307
|
markedOptions: {},
|
|
307
308
|
markdownLayouts: {},
|
|
308
309
|
scripts: {},
|
|
@@ -319,6 +320,7 @@ Merge behavior:
|
|
|
319
320
|
|
|
320
321
|
- `markdownLayouts` are shallow-merged by layout name.
|
|
321
322
|
- `scripts` are shallow-merged by script name.
|
|
323
|
+
- `meta` is shallow-merged by metadata field.
|
|
322
324
|
- `theme.light` and `theme.dark` are shallow-merged by CSS variable name.
|
|
323
325
|
- Other top-level config values use the docs source config value when provided.
|
|
324
326
|
|
|
@@ -328,6 +330,12 @@ Example docs source extension:
|
|
|
328
330
|
import pricingCards from './layouts/pricing-cards.js'
|
|
329
331
|
|
|
330
332
|
export default {
|
|
333
|
+
meta: {
|
|
334
|
+
siteName: 'Example',
|
|
335
|
+
title: 'Example Docs',
|
|
336
|
+
description: 'Documentation for Example.',
|
|
337
|
+
image: '/assets/site-image.jpg',
|
|
338
|
+
},
|
|
331
339
|
markdownLayouts: {
|
|
332
340
|
'pricing-cards': pricingCards,
|
|
333
341
|
},
|
package/dist/cjs/docs/run.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"use strict";var he=Object.create;var
|
|
1
|
+
"use strict";var he=Object.create;var C=Object.defineProperty;var ge=Object.getOwnPropertyDescriptor;var ye=Object.getOwnPropertyNames;var we=Object.getPrototypeOf,ve=Object.prototype.hasOwnProperty;var m=(e,t)=>C(e,"name",{value:t,configurable:!0});var Se=(e,t)=>{for(var r in t)C(e,r,{get:t[r],enumerable:!0})},Q=(e,t,r,a)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of ye(t))!ve.call(e,n)&&n!==r&&C(e,n,{get:()=>t[n],enumerable:!(a=ge(t,n))||a.enumerable});return e};var v=(e,t,r)=>(r=e!=null?he(we(e)):{},Q(t||!e||!e.__esModule?C(r,"default",{value:e,enumerable:!0}):r,e)),De=e=>Q(C({},"__esModule",{value:!0}),e);var Ue={};Se(Ue,{buildDocs:()=>Ne});module.exports=De(Ue);const ze={};var se=require("marked"),c=require("fs/promises"),o=v(require("path"),1),re=v(require("front-matter"),1),oe=v(require("isomorphic-dompurify"),1),ae=require("marked-highlight"),_=v(require("highlight.js"),1),ne=v(require("./templates/default.template.js"),1),ie=v(require("./renderer/index.js"),1),le=require("./markdown-layout/index.js"),ce=v(require("clean-css"),1),me=v(require("html-minifier"),1),X=v(require("@putout/minify"),1),P=require("url"),p=v(require("fs"),1);let V,A="";const Y=new ce.default({inline:["none"]}),R=new Map;R.set("default",ne.default);let W;const Z=m((e,t)=>t===!1?!1:t===void 0?e:e===!1||e===void 0?t:{...e,...t},"mergeThemeMode"),be=m((e,t)=>{if(!(!e&&!t))return{light:Z(e?.light,t?.light),dark:Z(e?.dark,t?.dark)}},"mergeTheme"),xe=m((e,t)=>({...e,...t,markdownLayouts:{...e.markdownLayouts,...t.markdownLayouts},scripts:{...e.scripts,...t.scripts},theme:be(e.theme,t.theme)}),"mergeTemplateConfig"),$e=m((e,t)=>{if(typeof e=="function")return!0;const{match:r}=e;return r?typeof r=="string"?t.includes(r):Array.isArray(r)?r.some(a=>t.includes(a)):r instanceof RegExp?r.test(t):r(t):!0},"scriptMatches"),Pe=m((e,t)=>e?Object.values(e).filter(r=>r!==!1).filter(r=>$e(r,t)).map(r=>typeof r=="function"?r():r.render()):[],"renderPageScripts"),G=m((e,t)=>{const r=Object.entries(t).filter(([a])=>a.startsWith("--")).map(([a,n])=>` ${a}: ${n};`).join(`
|
|
2
2
|
`);return r?`${e} {
|
|
3
3
|
${r}
|
|
4
4
|
}`:""},"renderThemeBlock"),ke=m(e=>{if(!e)return"";const t=e.light===!1,r=e.dark===!1,a=e.light===!1?void 0:e.light,n=r?void 0:e.dark,u=a||n;return u?[G(":root",u),n&&!t?`@media (prefers-color-scheme: dark) {
|
|
@@ -7,7 +7,7 @@ ${G(" :root",n)}
|
|
|
7
7
|
.site-logo-dark { display: block; }`:"",r&&a?`.site-logo-light { display: block; }
|
|
8
8
|
.site-logo-dark { display: none; }`:""].filter(Boolean).join(`
|
|
9
9
|
|
|
10
|
-
`):""},"renderThemeStylesheet"),Te=m((e="")=>String(e).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'"),"escapeXML"),Me=m((e="")=>e.replace(/```[\s\S]*?```/g," ").replace(/`([^`]+)`/g,"$1").replace(/!\[[^\]]*]\([^)]*\)/g," ").replace(/\[([^\]]+)]\([^)]*\)/g,"$1").replace(/^#{1,6}\s+/gm,"").replace(/^---+$/gm," ").replace(/[>*_~]/g,"").replace(/\s+/g," ").trim(),"stripMarkdown"),
|
|
10
|
+
`):""},"renderThemeStylesheet"),Te=m((e="")=>String(e).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'"),"escapeXML"),Me=m((e="")=>e.replace(/```[\s\S]*?```/g," ").replace(/`([^`]+)`/g,"$1").replace(/!\[[^\]]*]\([^)]*\)/g," ").replace(/\[([^\]]+)]\([^)]*\)/g,"$1").replace(/^#{1,6}\s+/gm,"").replace(/^---+$/gm," ").replace(/[>*_~]/g,"").replace(/\s+/g," ").trim(),"stripMarkdown"),je=m(e=>e==="/index.html"?"/":e.replace(/\/index\.html$/,"/").replace(/\.html$/,"").replace(/\/+/g,"/"),"pagePathToUrlPath"),ee=m((e,t)=>e?`${e.replace(/\/$/,"")}${t==="/"?"/":t}`:t,"canonicalUrl"),Ce=m(async(e,t)=>p.default.existsSync(e)?(await(0,c.mkdir)(o.default.dirname(t),{recursive:!0}),await(0,c.cp)(e,t),!0):!1,"copyIfExists"),Fe=m(async e=>{const t=o.default.resolve(e,"logo.svg");p.default.existsSync(t)&&await Promise.all(["logo.light.svg","logo.dark.svg"].map(async r=>{const a=o.default.resolve(e,r);p.default.existsSync(a)||await(0,c.cp)(t,a)}))},"ensureLogoModeAssets"),Ge=m(e=>{const t={sitemap:!0,robots:!0,llms:!0,llmsFull:!0,netlify:!1};return e===!1?{sitemap:!1,robots:!1,llms:!1,llmsFull:!1,netlify:!1}:e===!0||e===void 0?t:{...t,...e}},"normalizeGeneratedFilesConfig"),Le=m((e,t)=>{if(!t)return"";const r=new Date().toISOString().slice(0,10);return`<?xml version="1.0" encoding="UTF-8"?>
|
|
11
11
|
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
12
12
|
${e.map(n=>{const u=n.urlPath==="/"?"1.0":"0.8";return` <url>
|
|
13
13
|
<loc>${Te(ee(t,n.urlPath))}</loc>
|
|
@@ -75,4 +75,4 @@ for = "/robots.txt"
|
|
|
75
75
|
[headers.values]
|
|
76
76
|
Content-Type = "text/plain; charset=utf-8"
|
|
77
77
|
Cache-Control = "public, max-age=0, must-revalidate"
|
|
78
|
-
`,"generateNetlifyToml")
|
|
78
|
+
`,"generateNetlifyToml"),$=m(async(e,t,r,a)=>{if(await Ce(e,t))return;const n=r();if(!n){a&&console.warn(a);return}await(0,c.mkdir)(o.default.dirname(t),{recursive:!0}),await(0,c.writeFile)(t,n)},"writeGeneratedFile"),_e=m(async({docsDir:e,docsSiteDir:t,pages:r,siteUrl:a,publicDir:n,generatedFiles:u})=>{u.sitemap&&await $(o.default.resolve(e,"sitemap.xml"),o.default.resolve(t,"sitemap.xml"),()=>Le(r,a),"Skipping generated sitemap.xml because buildDocs({ siteUrl }) is not set."),u.robots&&await $(o.default.resolve(e,"robots.txt"),o.default.resolve(t,"robots.txt"),()=>Ee(a)),u.llms&&await $(o.default.resolve(e,"llms.txt"),o.default.resolve(t,"llms.txt"),()=>Oe(r)),u.llmsFull&&await $(o.default.resolve(e,"llms-full.txt"),o.default.resolve(t,"llms-full.txt"),()=>Ae(r,a)),u.netlify&&(await $(o.default.resolve(e,"_redirects"),o.default.resolve(t,"_redirects"),()=>Re(r,u)),await $(o.default.resolve(e,"netlify.toml"),o.default.resolve(t,"netlify.toml"),()=>We(n&&o.default.basename(n))))},"writeGeneratedDocsFiles"),te=m((e={},t,r)=>{const a=new se.Marked((0,ae.markedHighlight)({langPrefix:"hljs language-",highlight(n,u){const h=_.default.getLanguage(u)?u:"plaintext";return _.default.highlight(n,{language:h}).value}}));return a.use({renderer:ie.default}),a.use((0,le.createMarkdownLayoutExtension)({marked:a,handlers:e.markdownLayouts,renderMarkdown:r})),e.markedOptions&&a.use(e.markedOptions),t&&a.use(t),a},"createBaseMarked"),Be=m((e={},t,r)=>{const a=te(e,t),n=m(h=>a.parse(h),"renderMarkdown"),u=te(e,t,n);return u.use({hooks:{postprocess(h){const{layout:w="default",...k}=W.attributes,T=Pe(e.scripts,h);h=oe.default.sanitize(h);const M=[...h.matchAll(/<h([0-6])\sid="[^"]+".*?>.*?<a\s+href="([^"]+)".*?>([\s\S]*?)<\/a><\/h\1>/gm)].map(D=>({path:D[2],label:D[3].replace(/<[^>]+>/g,"").replace(/\s+/g," ").trim(),level:D[1]}));return R.get(w)?.({...k,content:h,siteMap:W.siteMap,tableOfContent:M,projectMeta:V,renderMarkdown:n,scripts:T,themeStylesheet:e.theme?`/stylesheets/theme.css?v=${A}`:void 0,cacheKey:A,siteUrl:r,templateConfig:e})||h}}}),u},"createMarked"),S=m(async e=>{const t=await(0,c.readdir)(e),r=[];for(const a of t){const n=o.default.extname(a);/^[._]/.test(a)||(n?r.push(o.default.join(e,a)):/(stylesheets|assets|scripts)/.test(a)||r.push(...await S(o.default.join(e,a))))}return r},"traverseDirectory"),Ne=m(async({srcDir:e,publicDir:t,markedOptions:r,template:a,siteUrl:n,generatedFiles:u}={})=>{A=Date.now().toString(36);try{const s=o.default.resolve(process.cwd(),"package.json");p.default.existsSync(s)&&(V=JSON.parse(await(0,c.readFile)(s,"utf-8")))}catch(s){console.warn("Failed to load project package.json metadata",s)}const h=m(()=>{try{return __dirname}catch{return o.default.dirname((0,P.fileURLToPath)(ze.url))}},"getDirname"),w=a?o.default.resolve(h(),"templates",a):null,k=w?o.default.resolve(w,"assets"):null,T=w?o.default.resolve(w,"stylesheets"):null,M=w?o.default.resolve(w,"layouts"):null;let D={};if(w)try{let s=o.default.resolve(w,"template.config.js");if(p.default.existsSync(s)||(s=s.replace(/\.js$/,".ts")),p.default.existsSync(s)){const i=await import((0,P.pathToFileURL)(s).href);D=i.default||i}}catch(s){console.error(`Failed to load template config for ${a}`,s)}const d=e??o.default.resolve(process.cwd(),"docs"),f=t??o.default.resolve(process.cwd(),"website"),ue=Ge(u),b=o.default.resolve(d,"_template"),B=o.default.resolve(b,"assets"),N=o.default.resolve(b,"stylesheets"),U=o.default.resolve(b,"scripts"),z=o.default.resolve(b,"layouts"),q=o.default.resolve(d,"_layouts"),F=o.default.resolve(d,"assets"),H=o.default.resolve(d,"stylesheets"),L=o.default.resolve(d,"scripts");try{let s=o.default.resolve(b,"template.config.js");if(p.default.existsSync(s)||(s=s.replace(/\.js$/,".ts")),p.default.existsSync(s)){const i=await import((0,P.pathToFileURL)(s).href),l=i.default||i;D=xe(D,l)}}catch(s){console.error("Failed to load docs _template config",s)}const pe=Be(D,r,n);if(await(0,c.mkdir)(f,{recursive:!0}),k&&p.default.existsSync(k))try{await(0,c.cp)(k,F.replace(d,f),{recursive:!0})}catch(s){console.warn(s)}if(p.default.existsSync(F))try{await(0,c.cp)(F,F.replace(d,f),{recursive:!0})}catch(s){console.warn(s)}if(p.default.existsSync(B))try{await(0,c.cp)(B,o.default.resolve(f,"assets"),{recursive:!0})}catch(s){console.warn(s)}await Fe(o.default.resolve(f,"assets"));try{const s=new Set;if(T&&p.default.existsSync(T)&&(await S(T)).forEach(l=>s.add(l)),p.default.existsSync(H)&&(await S(H)).forEach(l=>s.add(l)),p.default.existsSync(N)&&(await S(N)).forEach(l=>s.add(l)),s.size){await(0,c.mkdir)(o.default.join(f,"stylesheets"),{recursive:!0});for(const l of s){const g=await(0,c.readFile)(l,"utf-8"),y=Y.minify(g);y.errors.length&&y.errors.forEach(console.error);const j=l.startsWith(b)?l.replace(b,f):l.startsWith(d)?l.replace(d,f):o.default.join(f,"stylesheets",o.default.basename(l));await(0,c.writeFile)(j,y.styles??g)}}const i=ke(D.theme);if(i){const l=Y.minify(i);l.errors.length&&l.errors.forEach(console.error),await(0,c.mkdir)(o.default.join(f,"stylesheets"),{recursive:!0}),await(0,c.writeFile)(o.default.join(f,"stylesheets","theme.css"),l.styles??i)}}catch(s){console.error(s)}if(p.default.existsSync(L))try{const s=await S(L);s.length&&await(0,c.mkdir)(L.replace(d,f),{recursive:!0});for(const i of s)try{const l=await(0,c.readFile)(i,"utf-8");await(0,c.writeFile)(i.replace(d,f),X.minify(l))}catch(l){console.error(l)}}catch(s){console.warn(s)}if(p.default.existsSync(U))try{const s=await S(U);s.length&&await(0,c.mkdir)(o.default.join(f,"scripts"),{recursive:!0});for(const i of s)try{const l=await(0,c.readFile)(i,"utf-8");await(0,c.writeFile)(i.replace(b,f),X.minify(l))}catch(l){console.error(l)}}catch(s){console.warn(s)}try{const s=new Map;M&&p.default.existsSync(M)&&(await S(M)).forEach(i=>{/\.(j|t)s$/.test(i)&&s.set(o.default.basename(i).replace(/\.(j|t)s$/,""),i)}),p.default.existsSync(q)&&(await S(q)).forEach(i=>{/\.(j|t)s$/.test(i)&&s.set(o.default.basename(i).replace(/\.(j|t)s$/,""),i)}),p.default.existsSync(z)&&(await S(z)).forEach(i=>{/\.(j|t)s$/.test(i)&&s.set(o.default.basename(i).replace(/\.(j|t)s$/,""),i)});for(const[i,l]of s.entries()){const g=await import((0,P.pathToFileURL)(l).href);R.set(i,g.default)}}catch(s){console.error("Template import failed",s)}const fe=await S(d),I=new Map,J=(await Promise.all(fe.filter(s=>s.endsWith(".md")).map(async s=>{const i=await(0,c.readFile)(s,"utf-8");return{filePath:s,...(0,re.default)(i)}}))).sort((s,i)=>(s.attributes.order??0)-(i.attributes.order??0)).map(({attributes:s,body:i,filePath:l})=>{const g=l.replace(d,"").replace(/\.md/,".html"),y=l.replace(d,f).replace(".md",".html"),j=y.replace(o.default.basename(y),""),E=o.default.basename(g)||"/",de=g.replace(E,"").replace(/\/$/,"");let x=I;de.split("/").filter(Boolean).forEach(O=>{x.has(O)||x.set(O,new Map),x=x.get(O)});const K={...s,path:g};return x.has(E)||x.set(E,K),{attributes:K,fileWebsitePath:y,fileDirWebsitePath:j,body:i,sourcePath:o.default.relative(d,l),siteMap:I}});for(const{attributes:s,body:i,fileDirWebsitePath:l,fileWebsitePath:g,siteMap:y}of J){W={attributes:s,siteMap:y};const j=await pe.parse(i);await(0,c.mkdir)(l,{recursive:!0}),await(0,c.writeFile)(g,me.default.minify(j,{includeAutoGeneratedTags:!0,removeAttributeQuotes:!0,removeComments:!0,removeRedundantAttributes:!0,removeScriptTypeAttributes:!0,removeStyleLinkTypeAttributes:!0,sortClassName:!0,useShortDoctype:!0,collapseWhitespace:!0,minifyJS:!0,minifyCSS:!0}))}await _e({docsDir:d,docsSiteDir:f,pages:J.map(({attributes:s,body:i,sourcePath:l})=>{const g=je(String(s.path)),y=Me(i);return{sourcePath:l,urlPath:g,title:String(s.title||s.name||"").trim()||"Documentation",name:String(s.name||s.title||"").trim()||"Documentation",description:String(s.description||"").trim()||y.slice(0,156)||"Documentation page.",order:Number(s.order??999),summary:y.slice(0,320)}}),siteUrl:n,publicDir:f,generatedFiles:ue})},"buildDocs");0&&(module.exports={buildDocs});
|
|
@@ -144,9 +144,28 @@ It emits:
|
|
|
144
144
|
|
|
145
145
|
Important current behavior:
|
|
146
146
|
|
|
147
|
-
- The
|
|
148
|
-
-
|
|
149
|
-
-
|
|
147
|
+
- The helper reads defaults from `template.config.js` `meta`.
|
|
148
|
+
- `meta.siteUrl` overrides the `buildDocs({ siteUrl })` value when needed.
|
|
149
|
+
- `meta.siteName`, `meta.title`, and `meta.description` customize the fallback site identity.
|
|
150
|
+
- `meta.image` customizes the default Open Graph and Twitter card image.
|
|
151
|
+
- `meta.image` can be absolute, such as `https://example.com/card.jpg`, or site-relative, such as `/assets/site-image.jpg`.
|
|
152
|
+
- If no image is configured, the fallback is `/assets/site-image.jpg`.
|
|
153
|
+
|
|
154
|
+
For project-specific social images, add the file under `docs/_template/assets` and point `meta.image` at it:
|
|
155
|
+
|
|
156
|
+
```js
|
|
157
|
+
// docs/_template/template.config.js
|
|
158
|
+
export default {
|
|
159
|
+
meta: {
|
|
160
|
+
siteName: 'Example',
|
|
161
|
+
title: 'Example by Before Semicolon',
|
|
162
|
+
description: 'Documentation for Example.',
|
|
163
|
+
image: '/assets/site-image.jpg',
|
|
164
|
+
},
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Override `_head-meta.js` in `docs/_template/layouts` only when the generated metadata shape itself needs to change.
|
|
150
169
|
|
|
151
170
|
## Markdown Layout Handlers
|
|
152
171
|
|
|
@@ -590,7 +609,7 @@ docs/_template/assets/logo.svg
|
|
|
590
609
|
docs/_template/assets/logo.light.svg
|
|
591
610
|
docs/_template/assets/logo.dark.svg
|
|
592
611
|
docs/_template/assets/favicon/favicon.ico
|
|
593
|
-
docs/_template/assets/
|
|
612
|
+
docs/_template/assets/site-image.jpg
|
|
594
613
|
```
|
|
595
614
|
|
|
596
615
|
Because `docs/_template/assets` is copied into `publicDir/assets` after template assets, source assets can replace template assets. If only `logo.svg` is present, builder copies it to missing `logo.light.svg` and `logo.dark.svg` files in the generated site so the header and footer do not request missing assets. Projects that need correct contrast should provide explicit light and dark variants.
|
|
@@ -668,11 +687,12 @@ export default {
|
|
|
668
687
|
Use the smallest override that solves the problem:
|
|
669
688
|
|
|
670
689
|
1. Override assets for logos, favicons, images, and icons.
|
|
671
|
-
2. Override
|
|
672
|
-
3.
|
|
673
|
-
4.
|
|
674
|
-
5. Override
|
|
675
|
-
6. Override `
|
|
690
|
+
2. Override `meta` fields for site identity and social card defaults.
|
|
691
|
+
3. Override theme variables for color and surface tuning.
|
|
692
|
+
4. Add Markdown layout handlers for new page sections.
|
|
693
|
+
5. Override existing Markdown layout handlers only when the section HTML needs to change.
|
|
694
|
+
6. Override `_header.js`, `_footer.js`, or `_head-meta.js` when navigation, footer copy, metadata shape, or brand assumptions need to change.
|
|
695
|
+
7. Override `landing.js` or `document.js` only when the full page shell changes.
|
|
676
696
|
|
|
677
697
|
Example docs extension:
|
|
678
698
|
|
|
@@ -692,6 +712,12 @@ docs/
|
|
|
692
712
|
import pricingCards from './layouts/pricing-cards.js'
|
|
693
713
|
|
|
694
714
|
export default {
|
|
715
|
+
meta: {
|
|
716
|
+
siteName: 'Example',
|
|
717
|
+
title: 'Example by Before Semicolon',
|
|
718
|
+
description: 'Documentation for Example.',
|
|
719
|
+
image: '/assets/site-image.jpg',
|
|
720
|
+
},
|
|
695
721
|
markdownLayouts: {
|
|
696
722
|
'pricing-cards': pricingCards,
|
|
697
723
|
},
|
|
@@ -710,7 +736,6 @@ export default {
|
|
|
710
736
|
|
|
711
737
|
Some template internals are still Markup or Before Semicolon oriented:
|
|
712
738
|
|
|
713
|
-
- `_head-meta.js` has Markup-specific defaults and URLs.
|
|
714
739
|
- `_header.js` has Markup-oriented GitHub and documentation links.
|
|
715
740
|
- `_footer.js` has Before Semicolon social links, footer copy, and learning links.
|
|
716
741
|
- `document.js` builds edit links against the Markup GitHub docs path.
|
|
@@ -1,22 +1,20 @@
|
|
|
1
1
|
/* global process */
|
|
2
2
|
const isDev = process.env.NODE_ENV === 'development'
|
|
3
3
|
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
'
|
|
9
|
-
const
|
|
4
|
+
const fallbackSiteUrl = 'https://beforesemicolon.com'
|
|
5
|
+
const fallbackSiteName = 'Before Semicolon'
|
|
6
|
+
const fallbackTitle = 'Before Semicolon Documentation'
|
|
7
|
+
const fallbackDescription =
|
|
8
|
+
'Documentation for Before Semicolon projects, libraries, and developer tools.'
|
|
9
|
+
const fallbackImage = '/assets/site-image.jpg'
|
|
10
10
|
const baseKeywords = [
|
|
11
|
-
'
|
|
12
|
-
'
|
|
13
|
-
'
|
|
14
|
-
'
|
|
15
|
-
'
|
|
16
|
-
'
|
|
17
|
-
'
|
|
18
|
-
'no build step',
|
|
19
|
-
'vanilla JavaScript',
|
|
11
|
+
'Before Semicolon',
|
|
12
|
+
'documentation',
|
|
13
|
+
'JavaScript',
|
|
14
|
+
'developer tools',
|
|
15
|
+
'web development',
|
|
16
|
+
'frontend development',
|
|
17
|
+
'open source',
|
|
20
18
|
]
|
|
21
19
|
|
|
22
20
|
const escapeHTML = (value = '') =>
|
|
@@ -38,15 +36,20 @@ const canonicalPath = (path = '/') => {
|
|
|
38
36
|
return cleanPath.replace(/\.html$/, '')
|
|
39
37
|
}
|
|
40
38
|
|
|
41
|
-
const absoluteUrl = (path = '/') => `${siteUrl}${canonicalPath(path)}`
|
|
39
|
+
const absoluteUrl = (siteUrl, path = '/') => `${siteUrl}${canonicalPath(path)}`
|
|
42
40
|
|
|
43
|
-
const
|
|
41
|
+
const absoluteAssetUrl = (siteUrl, imagePath) => {
|
|
42
|
+
if (/^https?:\/\//i.test(imagePath)) return imagePath
|
|
43
|
+
return `${siteUrl}${imagePath.startsWith('/') ? imagePath : `/${imagePath}`}`
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const compactDescription = (description, defaultDescription) =>
|
|
44
47
|
(description || defaultDescription).replace(/\s+/g, ' ').trim()
|
|
45
48
|
|
|
46
49
|
const pageKeywords = ({ title, name, keywords } = {}) => {
|
|
47
50
|
const values = [
|
|
48
51
|
...baseKeywords,
|
|
49
|
-
title?.replace(/ -
|
|
52
|
+
title?.replace(/ - Before Semicolon Documentation$/, ''),
|
|
50
53
|
name,
|
|
51
54
|
...(Array.isArray(keywords)
|
|
52
55
|
? keywords
|
|
@@ -64,7 +67,7 @@ const jsonScript = (data) =>
|
|
|
64
67
|
.replace(/>/g, '\\u003e')
|
|
65
68
|
.replace(/&/g, '\\u0026')}</script>`
|
|
66
69
|
|
|
67
|
-
const breadcrumbJson = ({ title, path }) => {
|
|
70
|
+
const breadcrumbJson = ({ siteUrl, title, path }) => {
|
|
68
71
|
const cleanPath = canonicalPath(path)
|
|
69
72
|
const parts = cleanPath.split('/').filter(Boolean)
|
|
70
73
|
const items = [
|
|
@@ -84,7 +87,7 @@ const breadcrumbJson = ({ title, path }) => {
|
|
|
84
87
|
position: index + 2,
|
|
85
88
|
name:
|
|
86
89
|
index === parts.length - 1
|
|
87
|
-
? title.replace(/ -
|
|
90
|
+
? title.replace(/ - Before Semicolon Documentation$/, '')
|
|
88
91
|
: part
|
|
89
92
|
.split('-')
|
|
90
93
|
.map((word) => word[0]?.toUpperCase() + word.slice(1))
|
|
@@ -100,9 +103,17 @@ const breadcrumbJson = ({ title, path }) => {
|
|
|
100
103
|
}
|
|
101
104
|
}
|
|
102
105
|
|
|
103
|
-
const pageJson = ({
|
|
106
|
+
const pageJson = ({
|
|
107
|
+
siteUrl,
|
|
108
|
+
siteName,
|
|
109
|
+
defaultImage,
|
|
110
|
+
title,
|
|
111
|
+
description,
|
|
112
|
+
path,
|
|
113
|
+
name,
|
|
114
|
+
}) => {
|
|
104
115
|
const isDocsPage = canonicalPath(path).startsWith('/documentation')
|
|
105
|
-
const url = absoluteUrl(path)
|
|
116
|
+
const url = absoluteUrl(siteUrl, path)
|
|
106
117
|
|
|
107
118
|
if (isDocsPage) {
|
|
108
119
|
return {
|
|
@@ -116,14 +127,14 @@ const pageJson = ({ title, description, path, name }) => {
|
|
|
116
127
|
inLanguage: 'en-US',
|
|
117
128
|
isPartOf: {
|
|
118
129
|
'@type': 'TechArticle',
|
|
119
|
-
name:
|
|
130
|
+
name: `${siteName} Documentation`,
|
|
120
131
|
url: `${siteUrl}/documentation`,
|
|
121
132
|
},
|
|
122
133
|
about: [
|
|
123
|
-
|
|
124
|
-
'JavaScript
|
|
125
|
-
'
|
|
126
|
-
'
|
|
134
|
+
`${siteName} documentation`,
|
|
135
|
+
'JavaScript',
|
|
136
|
+
'Web development',
|
|
137
|
+
'Developer tools',
|
|
127
138
|
],
|
|
128
139
|
publisher: {
|
|
129
140
|
'@type': 'Organization',
|
|
@@ -141,15 +152,12 @@ const pageJson = ({ title, description, path, name }) => {
|
|
|
141
152
|
return {
|
|
142
153
|
'@context': 'https://schema.org',
|
|
143
154
|
'@type': 'SoftwareSourceCode',
|
|
144
|
-
name:
|
|
145
|
-
alternateName: '@beforesemicolon/markup',
|
|
155
|
+
name: siteName,
|
|
146
156
|
description,
|
|
147
157
|
url,
|
|
148
158
|
image: defaultImage,
|
|
149
|
-
codeRepository: 'https://github.com/beforesemicolon/markup',
|
|
150
159
|
programmingLanguage: 'JavaScript',
|
|
151
160
|
runtimePlatform: 'Browser and Node.js',
|
|
152
|
-
license: 'https://github.com/beforesemicolon/markup/blob/main/LICENSE',
|
|
153
161
|
publisher: {
|
|
154
162
|
'@type': 'Organization',
|
|
155
163
|
name: 'Before Semicolon',
|
|
@@ -159,9 +167,20 @@ const pageJson = ({ title, description, path, name }) => {
|
|
|
159
167
|
}
|
|
160
168
|
|
|
161
169
|
export default (props = {}) => {
|
|
170
|
+
const meta = props.templateConfig?.meta || {}
|
|
171
|
+
const siteUrl = String(
|
|
172
|
+
meta.siteUrl || props.siteUrl || fallbackSiteUrl
|
|
173
|
+
).replace(/\/$/, '')
|
|
174
|
+
const siteName = meta.siteName || fallbackSiteName
|
|
175
|
+
const defaultTitle = meta.title || fallbackTitle
|
|
176
|
+
const defaultDescription = meta.description || fallbackDescription
|
|
177
|
+
const defaultImage = absoluteAssetUrl(siteUrl, meta.image || fallbackImage)
|
|
162
178
|
const title = props.title || defaultTitle
|
|
163
|
-
const description = compactDescription(
|
|
164
|
-
|
|
179
|
+
const description = compactDescription(
|
|
180
|
+
props.description,
|
|
181
|
+
defaultDescription
|
|
182
|
+
)
|
|
183
|
+
const url = absoluteUrl(siteUrl, props.path)
|
|
165
184
|
const canonical = escapeHTML(url)
|
|
166
185
|
const image = props.image || defaultImage
|
|
167
186
|
const cacheKey = props.cacheKey ? `?v=${escapeHTML(props.cacheKey)}` : ''
|
|
@@ -213,8 +232,8 @@ export default (props = {}) => {
|
|
|
213
232
|
<link rel="manifest" href="/assets/favicon/site.webmanifest" />
|
|
214
233
|
<link rel="icon" type="image/x-icon" href="/assets/favicon/favicon.ico" />
|
|
215
234
|
<link rel="stylesheet" href="/stylesheets/github-dark.hightlighter.css${cacheKey}">
|
|
216
|
-
${jsonScript(pageJson({ ...props, title, description }))}
|
|
217
|
-
${jsonScript(breadcrumbJson({ title, path: props.path }))}
|
|
235
|
+
${jsonScript(pageJson({ ...props, siteUrl, siteName, defaultImage, title, description }))}
|
|
236
|
+
${jsonScript(breadcrumbJson({ siteUrl, title, path: props.path }))}
|
|
218
237
|
${
|
|
219
238
|
isDev
|
|
220
239
|
? ''
|
|
@@ -201,6 +201,13 @@ body {
|
|
|
201
201
|
border-bottom-color: var(--primary-glow);
|
|
202
202
|
}
|
|
203
203
|
|
|
204
|
+
#documentation article blockquote {
|
|
205
|
+
background: var(--surface-muted);
|
|
206
|
+
margin: 0;
|
|
207
|
+
padding: 5px 20px;
|
|
208
|
+
border-radius: 3px;
|
|
209
|
+
}
|
|
210
|
+
|
|
204
211
|
#documentation article .heading {
|
|
205
212
|
scroll-margin-top: 6rem;
|
|
206
213
|
}
|
|
@@ -14,6 +14,13 @@ import landingInstall, {
|
|
|
14
14
|
import landingCta from './layouts/landing-cta.js'
|
|
15
15
|
|
|
16
16
|
export default {
|
|
17
|
+
meta: {
|
|
18
|
+
siteName: 'Before Semicolon',
|
|
19
|
+
title: 'Before Semicolon Documentation',
|
|
20
|
+
description:
|
|
21
|
+
'Documentation for Before Semicolon projects, libraries, and developer tools.',
|
|
22
|
+
image: '/assets/site-image.jpg',
|
|
23
|
+
},
|
|
17
24
|
markdownLayouts: {
|
|
18
25
|
'landing-hero': landingHero,
|
|
19
26
|
'landing-ecosystem': landingEcosystem,
|
package/dist/cjs/docs/types.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var i=Object.defineProperty;var
|
|
1
|
+
"use strict";var i=Object.defineProperty;var o=Object.getOwnPropertyDescriptor;var a=Object.getOwnPropertyNames;var g=Object.prototype.hasOwnProperty;var p=(e,t,s,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let r of a(t))!g.call(e,r)&&r!==s&&i(e,r,{get:()=>t[r],enumerable:!(n=o(t,r))||n.enumerable});return e};var c=e=>p(i({},"__esModule",{value:!0}),e);var m={};module.exports=c(m);
|
package/dist/esm/docs/run.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var ne=Object.defineProperty;var c=(e,t)=>ne(e,"name",{value:t,configurable:!0});import{Marked as ie}from"marked";import{cp as
|
|
1
|
+
var ne=Object.defineProperty;var c=(e,t)=>ne(e,"name",{value:t,configurable:!0});import{Marked as ie}from"marked";import{cp as j,mkdir as D,readdir as le,readFile as C,writeFile as x}from"fs/promises";import o from"path";import ce from"front-matter";import me from"isomorphic-dompurify";import{markedHighlight as ue}from"marked-highlight";import Q from"highlight.js";import pe from"./templates/default.template.js";import fe from"./renderer/index.js";import{createMarkdownLayoutExtension as de}from"./markdown-layout/index.js";import he from"clean-css";import ge from"html-minifier";import*as X from"@putout/minify";import{fileURLToPath as ye,pathToFileURL as A}from"url";import u from"fs";let V,R="";const Y=new he({inline:["none"]}),W=new Map;W.set("default",pe);let _;const Z=c((e,t)=>t===!1?!1:t===void 0?e:e===!1||e===void 0?t:{...e,...t},"mergeThemeMode"),we=c((e,t)=>{if(!(!e&&!t))return{light:Z(e?.light,t?.light),dark:Z(e?.dark,t?.dark)}},"mergeTheme"),ve=c((e,t)=>({...e,...t,markdownLayouts:{...e.markdownLayouts,...t.markdownLayouts},scripts:{...e.scripts,...t.scripts},theme:we(e.theme,t.theme)}),"mergeTemplateConfig"),Se=c((e,t)=>{if(typeof e=="function")return!0;const{match:r}=e;return r?typeof r=="string"?t.includes(r):Array.isArray(r)?r.some(a=>t.includes(a)):r instanceof RegExp?r.test(t):r(t):!0},"scriptMatches"),De=c((e,t)=>e?Object.values(e).filter(r=>r!==!1).filter(r=>Se(r,t)).map(r=>typeof r=="function"?r():r.render()):[],"renderPageScripts"),G=c((e,t)=>{const r=Object.entries(t).filter(([a])=>a.startsWith("--")).map(([a,i])=>` ${a}: ${i};`).join(`
|
|
2
2
|
`);return r?`${e} {
|
|
3
3
|
${r}
|
|
4
4
|
}`:""},"renderThemeBlock"),be=c(e=>{if(!e)return"";const t=e.light===!1,r=e.dark===!1,a=e.light===!1?void 0:e.light,i=r?void 0:e.dark,m=a||i;return m?[G(":root",m),i&&!t?`@media (prefers-color-scheme: dark) {
|
|
@@ -7,7 +7,7 @@ ${G(" :root",i)}
|
|
|
7
7
|
.site-logo-dark { display: block; }`:"",r&&a?`.site-logo-light { display: block; }
|
|
8
8
|
.site-logo-dark { display: none; }`:""].filter(Boolean).join(`
|
|
9
9
|
|
|
10
|
-
`):""},"renderThemeStylesheet"),xe=c((e="")=>String(e).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'"),"escapeXML"),$e=c((e="")=>e.replace(/```[\s\S]*?```/g," ").replace(/`([^`]+)`/g,"$1").replace(/!\[[^\]]*]\([^)]*\)/g," ").replace(/\[([^\]]+)]\([^)]*\)/g,"$1").replace(/^#{1,6}\s+/gm,"").replace(/^---+$/gm," ").replace(/[>*_~]/g,"").replace(/\s+/g," ").trim(),"stripMarkdown"),Pe=c(e=>e==="/index.html"?"/":e.replace(/\/index\.html$/,"/").replace(/\.html$/,"").replace(/\/+/g,"/"),"pagePathToUrlPath"),ee=c((e,t)=>e?`${e.replace(/\/$/,"")}${t==="/"?"/":t}`:t,"canonicalUrl"),ke=c(async(e,t)=>u.existsSync(e)?(await
|
|
10
|
+
`):""},"renderThemeStylesheet"),xe=c((e="")=>String(e).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'"),"escapeXML"),$e=c((e="")=>e.replace(/```[\s\S]*?```/g," ").replace(/`([^`]+)`/g,"$1").replace(/!\[[^\]]*]\([^)]*\)/g," ").replace(/\[([^\]]+)]\([^)]*\)/g,"$1").replace(/^#{1,6}\s+/gm,"").replace(/^---+$/gm," ").replace(/[>*_~]/g,"").replace(/\s+/g," ").trim(),"stripMarkdown"),Pe=c(e=>e==="/index.html"?"/":e.replace(/\/index\.html$/,"/").replace(/\.html$/,"").replace(/\/+/g,"/"),"pagePathToUrlPath"),ee=c((e,t)=>e?`${e.replace(/\/$/,"")}${t==="/"?"/":t}`:t,"canonicalUrl"),ke=c(async(e,t)=>u.existsSync(e)?(await D(o.dirname(t),{recursive:!0}),await j(e,t),!0):!1,"copyIfExists"),Te=c(async e=>{const t=o.resolve(e,"logo.svg");u.existsSync(t)&&await Promise.all(["logo.light.svg","logo.dark.svg"].map(async r=>{const a=o.resolve(e,r);u.existsSync(a)||await j(t,a)}))},"ensureLogoModeAssets"),Me=c(e=>{const t={sitemap:!0,robots:!0,llms:!0,llmsFull:!0,netlify:!1};return e===!1?{sitemap:!1,robots:!1,llms:!1,llmsFull:!1,netlify:!1}:e===!0||e===void 0?t:{...t,...e}},"normalizeGeneratedFilesConfig"),je=c((e,t)=>{if(!t)return"";const r=new Date().toISOString().slice(0,10);return`<?xml version="1.0" encoding="UTF-8"?>
|
|
11
11
|
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
12
12
|
${e.map(i=>{const m=i.urlPath==="/"?"1.0":"0.8";return` <url>
|
|
13
13
|
<loc>${xe(ee(t,i.urlPath))}</loc>
|
|
@@ -17,7 +17,7 @@ ${e.map(i=>{const m=i.urlPath==="/"?"1.0":"0.8";return` <url>
|
|
|
17
17
|
</url>`}).join(`
|
|
18
18
|
`)}
|
|
19
19
|
</urlset>
|
|
20
|
-
`},"generateSitemap"),
|
|
20
|
+
`},"generateSitemap"),Ce=c(e=>`User-agent: *
|
|
21
21
|
Allow: /
|
|
22
22
|
${e?`
|
|
23
23
|
Sitemap: ${e.replace(/\/$/,"")}/sitemap.xml
|
|
@@ -75,4 +75,4 @@ for = "/robots.txt"
|
|
|
75
75
|
[headers.values]
|
|
76
76
|
Content-Type = "text/plain; charset=utf-8"
|
|
77
77
|
Cache-Control = "public, max-age=0, must-revalidate"
|
|
78
|
-
`,"generateNetlifyToml")
|
|
78
|
+
`,"generateNetlifyToml"),$=c(async(e,t,r,a)=>{if(await ke(e,t))return;const i=r();if(!i){a&&console.warn(a);return}await D(o.dirname(t),{recursive:!0}),await x(t,i)},"writeGeneratedFile"),Oe=c(async({docsDir:e,docsSiteDir:t,pages:r,siteUrl:a,publicDir:i,generatedFiles:m})=>{m.sitemap&&await $(o.resolve(e,"sitemap.xml"),o.resolve(t,"sitemap.xml"),()=>je(r,a),"Skipping generated sitemap.xml because buildDocs({ siteUrl }) is not set."),m.robots&&await $(o.resolve(e,"robots.txt"),o.resolve(t,"robots.txt"),()=>Ce(a)),m.llms&&await $(o.resolve(e,"llms.txt"),o.resolve(t,"llms.txt"),()=>Fe(r)),m.llmsFull&&await $(o.resolve(e,"llms-full.txt"),o.resolve(t,"llms-full.txt"),()=>Ge(r,a)),m.netlify&&(await $(o.resolve(e,"_redirects"),o.resolve(t,"_redirects"),()=>Le(r,m)),await $(o.resolve(e,"netlify.toml"),o.resolve(t,"netlify.toml"),()=>Ee(i&&o.basename(i))))},"writeGeneratedDocsFiles"),te=c((e={},t,r)=>{const a=new ie(ue({langPrefix:"hljs language-",highlight(i,m){const d=Q.getLanguage(m)?m:"plaintext";return Q.highlight(i,{language:d}).value}}));return a.use({renderer:fe}),a.use(de({marked:a,handlers:e.markdownLayouts,renderMarkdown:r})),e.markedOptions&&a.use(e.markedOptions),t&&a.use(t),a},"createBaseMarked"),Ae=c((e={},t,r)=>{const a=te(e,t),i=c(d=>a.parse(d),"renderMarkdown"),m=te(e,t,i);return m.use({hooks:{postprocess(d){const{layout:y="default",...P}=_.attributes,k=De(e.scripts,d);d=me.sanitize(d);const T=[...d.matchAll(/<h([0-6])\sid="[^"]+".*?>.*?<a\s+href="([^"]+)".*?>([\s\S]*?)<\/a><\/h\1>/gm)].map(v=>({path:v[2],label:v[3].replace(/<[^>]+>/g,"").replace(/\s+/g," ").trim(),level:v[1]}));return W.get(y)?.({...P,content:d,siteMap:_.siteMap,tableOfContent:T,projectMeta:V,renderMarkdown:i,scripts:k,themeStylesheet:e.theme?`/stylesheets/theme.css?v=${R}`:void 0,cacheKey:R,siteUrl:r,templateConfig:e})||d}}}),m},"createMarked"),w=c(async e=>{const t=await le(e),r=[];for(const a of t){const i=o.extname(a);/^[._]/.test(a)||(i?r.push(o.join(e,a)):/(stylesheets|assets|scripts)/.test(a)||r.push(...await w(o.join(e,a))))}return r},"traverseDirectory"),lt=c(async({srcDir:e,publicDir:t,markedOptions:r,template:a,siteUrl:i,generatedFiles:m}={})=>{R=Date.now().toString(36);try{const s=o.resolve(process.cwd(),"package.json");u.existsSync(s)&&(V=JSON.parse(await C(s,"utf-8")))}catch(s){console.warn("Failed to load project package.json metadata",s)}const d=c(()=>{try{return __dirname}catch{return o.dirname(ye(import.meta.url))}},"getDirname"),y=a?o.resolve(d(),"templates",a):null,P=y?o.resolve(y,"assets"):null,k=y?o.resolve(y,"stylesheets"):null,T=y?o.resolve(y,"layouts"):null;let v={};if(y)try{let s=o.resolve(y,"template.config.js");if(u.existsSync(s)||(s=s.replace(/\.js$/,".ts")),u.existsSync(s)){const n=await import(A(s).href);v=n.default||n}}catch(s){console.error(`Failed to load template config for ${a}`,s)}const f=e??o.resolve(process.cwd(),"docs"),p=t??o.resolve(process.cwd(),"website"),se=Me(m),S=o.resolve(f,"_template"),B=o.resolve(S,"assets"),N=o.resolve(S,"stylesheets"),U=o.resolve(S,"scripts"),z=o.resolve(S,"layouts"),q=o.resolve(f,"_layouts"),F=o.resolve(f,"assets"),H=o.resolve(f,"stylesheets"),L=o.resolve(f,"scripts");try{let s=o.resolve(S,"template.config.js");if(u.existsSync(s)||(s=s.replace(/\.js$/,".ts")),u.existsSync(s)){const n=await import(A(s).href),l=n.default||n;v=ve(v,l)}}catch(s){console.error("Failed to load docs _template config",s)}const re=Ae(v,r,i);if(await D(p,{recursive:!0}),P&&u.existsSync(P))try{await j(P,F.replace(f,p),{recursive:!0})}catch(s){console.warn(s)}if(u.existsSync(F))try{await j(F,F.replace(f,p),{recursive:!0})}catch(s){console.warn(s)}if(u.existsSync(B))try{await j(B,o.resolve(p,"assets"),{recursive:!0})}catch(s){console.warn(s)}await Te(o.resolve(p,"assets"));try{const s=new Set;if(k&&u.existsSync(k)&&(await w(k)).forEach(l=>s.add(l)),u.existsSync(H)&&(await w(H)).forEach(l=>s.add(l)),u.existsSync(N)&&(await w(N)).forEach(l=>s.add(l)),s.size){await D(o.join(p,"stylesheets"),{recursive:!0});for(const l of s){const h=await C(l,"utf-8"),g=Y.minify(h);g.errors.length&&g.errors.forEach(console.error);const M=l.startsWith(S)?l.replace(S,p):l.startsWith(f)?l.replace(f,p):o.join(p,"stylesheets",o.basename(l));await x(M,g.styles??h)}}const n=be(v.theme);if(n){const l=Y.minify(n);l.errors.length&&l.errors.forEach(console.error),await D(o.join(p,"stylesheets"),{recursive:!0}),await x(o.join(p,"stylesheets","theme.css"),l.styles??n)}}catch(s){console.error(s)}if(u.existsSync(L))try{const s=await w(L);s.length&&await D(L.replace(f,p),{recursive:!0});for(const n of s)try{const l=await C(n,"utf-8");await x(n.replace(f,p),X.minify(l))}catch(l){console.error(l)}}catch(s){console.warn(s)}if(u.existsSync(U))try{const s=await w(U);s.length&&await D(o.join(p,"scripts"),{recursive:!0});for(const n of s)try{const l=await C(n,"utf-8");await x(n.replace(S,p),X.minify(l))}catch(l){console.error(l)}}catch(s){console.warn(s)}try{const s=new Map;T&&u.existsSync(T)&&(await w(T)).forEach(n=>{/\.(j|t)s$/.test(n)&&s.set(o.basename(n).replace(/\.(j|t)s$/,""),n)}),u.existsSync(q)&&(await w(q)).forEach(n=>{/\.(j|t)s$/.test(n)&&s.set(o.basename(n).replace(/\.(j|t)s$/,""),n)}),u.existsSync(z)&&(await w(z)).forEach(n=>{/\.(j|t)s$/.test(n)&&s.set(o.basename(n).replace(/\.(j|t)s$/,""),n)});for(const[n,l]of s.entries()){const h=await import(A(l).href);W.set(n,h.default)}}catch(s){console.error("Template import failed",s)}const oe=await w(f),I=new Map,J=(await Promise.all(oe.filter(s=>s.endsWith(".md")).map(async s=>{const n=await C(s,"utf-8");return{filePath:s,...ce(n)}}))).sort((s,n)=>(s.attributes.order??0)-(n.attributes.order??0)).map(({attributes:s,body:n,filePath:l})=>{const h=l.replace(f,"").replace(/\.md/,".html"),g=l.replace(f,p).replace(".md",".html"),M=g.replace(o.basename(g),""),E=o.basename(h)||"/",ae=h.replace(E,"").replace(/\/$/,"");let b=I;ae.split("/").filter(Boolean).forEach(O=>{b.has(O)||b.set(O,new Map),b=b.get(O)});const K={...s,path:h};return b.has(E)||b.set(E,K),{attributes:K,fileWebsitePath:g,fileDirWebsitePath:M,body:n,sourcePath:o.relative(f,l),siteMap:I}});for(const{attributes:s,body:n,fileDirWebsitePath:l,fileWebsitePath:h,siteMap:g}of J){_={attributes:s,siteMap:g};const M=await re.parse(n);await D(l,{recursive:!0}),await x(h,ge.minify(M,{includeAutoGeneratedTags:!0,removeAttributeQuotes:!0,removeComments:!0,removeRedundantAttributes:!0,removeScriptTypeAttributes:!0,removeStyleLinkTypeAttributes:!0,sortClassName:!0,useShortDoctype:!0,collapseWhitespace:!0,minifyJS:!0,minifyCSS:!0}))}await Oe({docsDir:f,docsSiteDir:p,pages:J.map(({attributes:s,body:n,sourcePath:l})=>{const h=Pe(String(s.path)),g=$e(n);return{sourcePath:l,urlPath:h,title:String(s.title||s.name||"").trim()||"Documentation",name:String(s.name||s.title||"").trim()||"Documentation",description:String(s.description||"").trim()||g.slice(0,156)||"Documentation page.",order:Number(s.order??999),summary:g.slice(0,320)}}),siteUrl:i,publicDir:p,generatedFiles:se})},"buildDocs");export{lt as buildDocs};
|
|
@@ -144,9 +144,28 @@ It emits:
|
|
|
144
144
|
|
|
145
145
|
Important current behavior:
|
|
146
146
|
|
|
147
|
-
- The
|
|
148
|
-
-
|
|
149
|
-
-
|
|
147
|
+
- The helper reads defaults from `template.config.js` `meta`.
|
|
148
|
+
- `meta.siteUrl` overrides the `buildDocs({ siteUrl })` value when needed.
|
|
149
|
+
- `meta.siteName`, `meta.title`, and `meta.description` customize the fallback site identity.
|
|
150
|
+
- `meta.image` customizes the default Open Graph and Twitter card image.
|
|
151
|
+
- `meta.image` can be absolute, such as `https://example.com/card.jpg`, or site-relative, such as `/assets/site-image.jpg`.
|
|
152
|
+
- If no image is configured, the fallback is `/assets/site-image.jpg`.
|
|
153
|
+
|
|
154
|
+
For project-specific social images, add the file under `docs/_template/assets` and point `meta.image` at it:
|
|
155
|
+
|
|
156
|
+
```js
|
|
157
|
+
// docs/_template/template.config.js
|
|
158
|
+
export default {
|
|
159
|
+
meta: {
|
|
160
|
+
siteName: 'Example',
|
|
161
|
+
title: 'Example by Before Semicolon',
|
|
162
|
+
description: 'Documentation for Example.',
|
|
163
|
+
image: '/assets/site-image.jpg',
|
|
164
|
+
},
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Override `_head-meta.js` in `docs/_template/layouts` only when the generated metadata shape itself needs to change.
|
|
150
169
|
|
|
151
170
|
## Markdown Layout Handlers
|
|
152
171
|
|
|
@@ -590,7 +609,7 @@ docs/_template/assets/logo.svg
|
|
|
590
609
|
docs/_template/assets/logo.light.svg
|
|
591
610
|
docs/_template/assets/logo.dark.svg
|
|
592
611
|
docs/_template/assets/favicon/favicon.ico
|
|
593
|
-
docs/_template/assets/
|
|
612
|
+
docs/_template/assets/site-image.jpg
|
|
594
613
|
```
|
|
595
614
|
|
|
596
615
|
Because `docs/_template/assets` is copied into `publicDir/assets` after template assets, source assets can replace template assets. If only `logo.svg` is present, builder copies it to missing `logo.light.svg` and `logo.dark.svg` files in the generated site so the header and footer do not request missing assets. Projects that need correct contrast should provide explicit light and dark variants.
|
|
@@ -668,11 +687,12 @@ export default {
|
|
|
668
687
|
Use the smallest override that solves the problem:
|
|
669
688
|
|
|
670
689
|
1. Override assets for logos, favicons, images, and icons.
|
|
671
|
-
2. Override
|
|
672
|
-
3.
|
|
673
|
-
4.
|
|
674
|
-
5. Override
|
|
675
|
-
6. Override `
|
|
690
|
+
2. Override `meta` fields for site identity and social card defaults.
|
|
691
|
+
3. Override theme variables for color and surface tuning.
|
|
692
|
+
4. Add Markdown layout handlers for new page sections.
|
|
693
|
+
5. Override existing Markdown layout handlers only when the section HTML needs to change.
|
|
694
|
+
6. Override `_header.js`, `_footer.js`, or `_head-meta.js` when navigation, footer copy, metadata shape, or brand assumptions need to change.
|
|
695
|
+
7. Override `landing.js` or `document.js` only when the full page shell changes.
|
|
676
696
|
|
|
677
697
|
Example docs extension:
|
|
678
698
|
|
|
@@ -692,6 +712,12 @@ docs/
|
|
|
692
712
|
import pricingCards from './layouts/pricing-cards.js'
|
|
693
713
|
|
|
694
714
|
export default {
|
|
715
|
+
meta: {
|
|
716
|
+
siteName: 'Example',
|
|
717
|
+
title: 'Example by Before Semicolon',
|
|
718
|
+
description: 'Documentation for Example.',
|
|
719
|
+
image: '/assets/site-image.jpg',
|
|
720
|
+
},
|
|
695
721
|
markdownLayouts: {
|
|
696
722
|
'pricing-cards': pricingCards,
|
|
697
723
|
},
|
|
@@ -710,7 +736,6 @@ export default {
|
|
|
710
736
|
|
|
711
737
|
Some template internals are still Markup or Before Semicolon oriented:
|
|
712
738
|
|
|
713
|
-
- `_head-meta.js` has Markup-specific defaults and URLs.
|
|
714
739
|
- `_header.js` has Markup-oriented GitHub and documentation links.
|
|
715
740
|
- `_footer.js` has Before Semicolon social links, footer copy, and learning links.
|
|
716
741
|
- `document.js` builds edit links against the Markup GitHub docs path.
|
|
@@ -1,22 +1,20 @@
|
|
|
1
1
|
/* global process */
|
|
2
2
|
const isDev = process.env.NODE_ENV === 'development'
|
|
3
3
|
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
'
|
|
9
|
-
const
|
|
4
|
+
const fallbackSiteUrl = 'https://beforesemicolon.com'
|
|
5
|
+
const fallbackSiteName = 'Before Semicolon'
|
|
6
|
+
const fallbackTitle = 'Before Semicolon Documentation'
|
|
7
|
+
const fallbackDescription =
|
|
8
|
+
'Documentation for Before Semicolon projects, libraries, and developer tools.'
|
|
9
|
+
const fallbackImage = '/assets/site-image.jpg'
|
|
10
10
|
const baseKeywords = [
|
|
11
|
-
'
|
|
12
|
-
'
|
|
13
|
-
'
|
|
14
|
-
'
|
|
15
|
-
'
|
|
16
|
-
'
|
|
17
|
-
'
|
|
18
|
-
'no build step',
|
|
19
|
-
'vanilla JavaScript',
|
|
11
|
+
'Before Semicolon',
|
|
12
|
+
'documentation',
|
|
13
|
+
'JavaScript',
|
|
14
|
+
'developer tools',
|
|
15
|
+
'web development',
|
|
16
|
+
'frontend development',
|
|
17
|
+
'open source',
|
|
20
18
|
]
|
|
21
19
|
|
|
22
20
|
const escapeHTML = (value = '') =>
|
|
@@ -38,15 +36,20 @@ const canonicalPath = (path = '/') => {
|
|
|
38
36
|
return cleanPath.replace(/\.html$/, '')
|
|
39
37
|
}
|
|
40
38
|
|
|
41
|
-
const absoluteUrl = (path = '/') => `${siteUrl}${canonicalPath(path)}`
|
|
39
|
+
const absoluteUrl = (siteUrl, path = '/') => `${siteUrl}${canonicalPath(path)}`
|
|
42
40
|
|
|
43
|
-
const
|
|
41
|
+
const absoluteAssetUrl = (siteUrl, imagePath) => {
|
|
42
|
+
if (/^https?:\/\//i.test(imagePath)) return imagePath
|
|
43
|
+
return `${siteUrl}${imagePath.startsWith('/') ? imagePath : `/${imagePath}`}`
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const compactDescription = (description, defaultDescription) =>
|
|
44
47
|
(description || defaultDescription).replace(/\s+/g, ' ').trim()
|
|
45
48
|
|
|
46
49
|
const pageKeywords = ({ title, name, keywords } = {}) => {
|
|
47
50
|
const values = [
|
|
48
51
|
...baseKeywords,
|
|
49
|
-
title?.replace(/ -
|
|
52
|
+
title?.replace(/ - Before Semicolon Documentation$/, ''),
|
|
50
53
|
name,
|
|
51
54
|
...(Array.isArray(keywords)
|
|
52
55
|
? keywords
|
|
@@ -64,7 +67,7 @@ const jsonScript = (data) =>
|
|
|
64
67
|
.replace(/>/g, '\\u003e')
|
|
65
68
|
.replace(/&/g, '\\u0026')}</script>`
|
|
66
69
|
|
|
67
|
-
const breadcrumbJson = ({ title, path }) => {
|
|
70
|
+
const breadcrumbJson = ({ siteUrl, title, path }) => {
|
|
68
71
|
const cleanPath = canonicalPath(path)
|
|
69
72
|
const parts = cleanPath.split('/').filter(Boolean)
|
|
70
73
|
const items = [
|
|
@@ -84,7 +87,7 @@ const breadcrumbJson = ({ title, path }) => {
|
|
|
84
87
|
position: index + 2,
|
|
85
88
|
name:
|
|
86
89
|
index === parts.length - 1
|
|
87
|
-
? title.replace(/ -
|
|
90
|
+
? title.replace(/ - Before Semicolon Documentation$/, '')
|
|
88
91
|
: part
|
|
89
92
|
.split('-')
|
|
90
93
|
.map((word) => word[0]?.toUpperCase() + word.slice(1))
|
|
@@ -100,9 +103,17 @@ const breadcrumbJson = ({ title, path }) => {
|
|
|
100
103
|
}
|
|
101
104
|
}
|
|
102
105
|
|
|
103
|
-
const pageJson = ({
|
|
106
|
+
const pageJson = ({
|
|
107
|
+
siteUrl,
|
|
108
|
+
siteName,
|
|
109
|
+
defaultImage,
|
|
110
|
+
title,
|
|
111
|
+
description,
|
|
112
|
+
path,
|
|
113
|
+
name,
|
|
114
|
+
}) => {
|
|
104
115
|
const isDocsPage = canonicalPath(path).startsWith('/documentation')
|
|
105
|
-
const url = absoluteUrl(path)
|
|
116
|
+
const url = absoluteUrl(siteUrl, path)
|
|
106
117
|
|
|
107
118
|
if (isDocsPage) {
|
|
108
119
|
return {
|
|
@@ -116,14 +127,14 @@ const pageJson = ({ title, description, path, name }) => {
|
|
|
116
127
|
inLanguage: 'en-US',
|
|
117
128
|
isPartOf: {
|
|
118
129
|
'@type': 'TechArticle',
|
|
119
|
-
name:
|
|
130
|
+
name: `${siteName} Documentation`,
|
|
120
131
|
url: `${siteUrl}/documentation`,
|
|
121
132
|
},
|
|
122
133
|
about: [
|
|
123
|
-
|
|
124
|
-
'JavaScript
|
|
125
|
-
'
|
|
126
|
-
'
|
|
134
|
+
`${siteName} documentation`,
|
|
135
|
+
'JavaScript',
|
|
136
|
+
'Web development',
|
|
137
|
+
'Developer tools',
|
|
127
138
|
],
|
|
128
139
|
publisher: {
|
|
129
140
|
'@type': 'Organization',
|
|
@@ -141,15 +152,12 @@ const pageJson = ({ title, description, path, name }) => {
|
|
|
141
152
|
return {
|
|
142
153
|
'@context': 'https://schema.org',
|
|
143
154
|
'@type': 'SoftwareSourceCode',
|
|
144
|
-
name:
|
|
145
|
-
alternateName: '@beforesemicolon/markup',
|
|
155
|
+
name: siteName,
|
|
146
156
|
description,
|
|
147
157
|
url,
|
|
148
158
|
image: defaultImage,
|
|
149
|
-
codeRepository: 'https://github.com/beforesemicolon/markup',
|
|
150
159
|
programmingLanguage: 'JavaScript',
|
|
151
160
|
runtimePlatform: 'Browser and Node.js',
|
|
152
|
-
license: 'https://github.com/beforesemicolon/markup/blob/main/LICENSE',
|
|
153
161
|
publisher: {
|
|
154
162
|
'@type': 'Organization',
|
|
155
163
|
name: 'Before Semicolon',
|
|
@@ -159,9 +167,20 @@ const pageJson = ({ title, description, path, name }) => {
|
|
|
159
167
|
}
|
|
160
168
|
|
|
161
169
|
export default (props = {}) => {
|
|
170
|
+
const meta = props.templateConfig?.meta || {}
|
|
171
|
+
const siteUrl = String(
|
|
172
|
+
meta.siteUrl || props.siteUrl || fallbackSiteUrl
|
|
173
|
+
).replace(/\/$/, '')
|
|
174
|
+
const siteName = meta.siteName || fallbackSiteName
|
|
175
|
+
const defaultTitle = meta.title || fallbackTitle
|
|
176
|
+
const defaultDescription = meta.description || fallbackDescription
|
|
177
|
+
const defaultImage = absoluteAssetUrl(siteUrl, meta.image || fallbackImage)
|
|
162
178
|
const title = props.title || defaultTitle
|
|
163
|
-
const description = compactDescription(
|
|
164
|
-
|
|
179
|
+
const description = compactDescription(
|
|
180
|
+
props.description,
|
|
181
|
+
defaultDescription
|
|
182
|
+
)
|
|
183
|
+
const url = absoluteUrl(siteUrl, props.path)
|
|
165
184
|
const canonical = escapeHTML(url)
|
|
166
185
|
const image = props.image || defaultImage
|
|
167
186
|
const cacheKey = props.cacheKey ? `?v=${escapeHTML(props.cacheKey)}` : ''
|
|
@@ -213,8 +232,8 @@ export default (props = {}) => {
|
|
|
213
232
|
<link rel="manifest" href="/assets/favicon/site.webmanifest" />
|
|
214
233
|
<link rel="icon" type="image/x-icon" href="/assets/favicon/favicon.ico" />
|
|
215
234
|
<link rel="stylesheet" href="/stylesheets/github-dark.hightlighter.css${cacheKey}">
|
|
216
|
-
${jsonScript(pageJson({ ...props, title, description }))}
|
|
217
|
-
${jsonScript(breadcrumbJson({ title, path: props.path }))}
|
|
235
|
+
${jsonScript(pageJson({ ...props, siteUrl, siteName, defaultImage, title, description }))}
|
|
236
|
+
${jsonScript(breadcrumbJson({ siteUrl, title, path: props.path }))}
|
|
218
237
|
${
|
|
219
238
|
isDev
|
|
220
239
|
? ''
|
|
@@ -201,6 +201,13 @@ body {
|
|
|
201
201
|
border-bottom-color: var(--primary-glow);
|
|
202
202
|
}
|
|
203
203
|
|
|
204
|
+
#documentation article blockquote {
|
|
205
|
+
background: var(--surface-muted);
|
|
206
|
+
margin: 0;
|
|
207
|
+
padding: 5px 20px;
|
|
208
|
+
border-radius: 3px;
|
|
209
|
+
}
|
|
210
|
+
|
|
204
211
|
#documentation article .heading {
|
|
205
212
|
scroll-margin-top: 6rem;
|
|
206
213
|
}
|
|
@@ -14,6 +14,13 @@ import landingInstall, {
|
|
|
14
14
|
import landingCta from './layouts/landing-cta.js'
|
|
15
15
|
|
|
16
16
|
export default {
|
|
17
|
+
meta: {
|
|
18
|
+
siteName: 'Before Semicolon',
|
|
19
|
+
title: 'Before Semicolon Documentation',
|
|
20
|
+
description:
|
|
21
|
+
'Documentation for Before Semicolon projects, libraries, and developer tools.',
|
|
22
|
+
image: '/assets/site-image.jpg',
|
|
23
|
+
},
|
|
17
24
|
markdownLayouts: {
|
|
18
25
|
'landing-hero': landingHero,
|
|
19
26
|
'landing-ecosystem': landingEcosystem,
|
|
@@ -6,6 +6,13 @@ export interface DocsTheme {
|
|
|
6
6
|
light?: DocsThemeModeConfig;
|
|
7
7
|
dark?: DocsThemeModeConfig;
|
|
8
8
|
}
|
|
9
|
+
export interface DocsMetaConfig {
|
|
10
|
+
siteUrl?: string;
|
|
11
|
+
siteName?: string;
|
|
12
|
+
title?: string;
|
|
13
|
+
description?: string;
|
|
14
|
+
image?: string;
|
|
15
|
+
}
|
|
9
16
|
export type DocsScriptMatcher = string | string[] | RegExp | ((html: string) => boolean);
|
|
10
17
|
export interface DocsScriptDefinition {
|
|
11
18
|
match?: DocsScriptMatcher;
|
|
@@ -34,6 +41,8 @@ export interface PageProps {
|
|
|
34
41
|
scripts?: string[];
|
|
35
42
|
themeStylesheet?: string;
|
|
36
43
|
cacheKey?: string;
|
|
44
|
+
siteUrl?: string;
|
|
45
|
+
templateConfig?: DocsTemplateConfig;
|
|
37
46
|
}
|
|
38
47
|
export interface CustomOptions {
|
|
39
48
|
name?: string;
|
|
@@ -60,4 +69,5 @@ export interface DocsTemplateConfig {
|
|
|
60
69
|
markdownLayouts?: Record<string, MarkdownLayoutHandler>;
|
|
61
70
|
scripts?: DocsScriptRegistry;
|
|
62
71
|
theme?: DocsTheme;
|
|
72
|
+
meta?: DocsMetaConfig;
|
|
63
73
|
}
|