feat(lab): port lab to new prebuild refactor
This commit is contained in:
parent
69f38fd8a2
commit
bcf47b7d50
6 changed files with 299 additions and 88 deletions
24
sites/lab/components/layouts/default.mjs
Normal file
24
sites/lab/components/layouts/default.mjs
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
// Components
|
||||||
|
import { BaseLayout, BaseLayoutLeft, BaseLayoutWide } from 'shared/components/base-layout.mjs'
|
||||||
|
import { NavLinks, Breadcrumbs, MainSections } from 'shared/components/navigation/sitenav.mjs'
|
||||||
|
|
||||||
|
export const ns = []
|
||||||
|
|
||||||
|
export const DefaultLayout = ({ children = [], pageTitle = false }) => (
|
||||||
|
<BaseLayout>
|
||||||
|
<BaseLayoutLeft>
|
||||||
|
<MainSections />
|
||||||
|
<NavLinks sections={['new']} />
|
||||||
|
</BaseLayoutLeft>
|
||||||
|
|
||||||
|
<BaseLayoutWide>
|
||||||
|
{pageTitle && (
|
||||||
|
<div className="xl:pl-4">
|
||||||
|
<Breadcrumbs />
|
||||||
|
<h1 className="break-words">{pageTitle}</h1>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="xl:pl-4">{children}</div>
|
||||||
|
</BaseLayoutWide>
|
||||||
|
</BaseLayout>
|
||||||
|
)
|
|
@ -1,33 +1,28 @@
|
||||||
import { SectionsMenu } from 'site/components/navigation/sections-menu.mjs'
|
// Components
|
||||||
import { useTranslation } from 'next-i18next'
|
import { SectionsMenu, ns as sectionsNs } from 'shared/components/navigation/sections-menu.mjs'
|
||||||
import { ActiveSection, ns as primaryNs } from 'shared/components/navigation/primary.mjs'
|
|
||||||
import { ModalWrapper } from 'shared/components/wrappers/modal.mjs'
|
import { ModalWrapper } from 'shared/components/wrappers/modal.mjs'
|
||||||
|
import { ChoiceLink } from 'shared/components/choice-link.mjs'
|
||||||
|
|
||||||
export const ns = primaryNs
|
export const ns = sectionsNs
|
||||||
|
|
||||||
export const ModalMenu = () => {
|
export const ModalMenu = () => (
|
||||||
const { t } = useTranslation(ns)
|
<ModalWrapper flex="col" justify="top" slideFrom="left">
|
||||||
|
|
||||||
return (
|
|
||||||
<ModalWrapper flex="col" justify="top lg:justify-center" slideFrom="left">
|
|
||||||
<div className="max-w-full">
|
<div className="max-w-full">
|
||||||
<div
|
<div
|
||||||
className={`
|
className={`
|
||||||
py-4 lg:py-16 w-full m-auto
|
py-4 w-full m-auto
|
||||||
flex flex-col-reverse gap-0 flex-wrap justify-between
|
flex flex-col-reverse gap-0 flex-wrap justify-between
|
||||||
lg:max-w-6xl lg:flex-nowrap lg:gap-8 lg:flex-row
|
lg:max-w-6xl lg:flex-nowrap lg:gap-8 lg:flex-row
|
||||||
`}
|
`}
|
||||||
>
|
>
|
||||||
<div className="w-full lg:w-1/2">
|
<div className="w-full">
|
||||||
<h3>{t('mainSections')}</h3>
|
|
||||||
<SectionsMenu />
|
<SectionsMenu />
|
||||||
</div>
|
<ChoiceLink href="/sitemap" title="Sitemap">
|
||||||
<div className="w-full lg:w-1/2">
|
The sitemap lists all pages on this website. It can give you a good idea of what you can
|
||||||
<h3>{t('currentSection')}</h3>
|
find here.
|
||||||
<ActiveSection bare />
|
</ChoiceLink>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ModalWrapper>
|
</ModalWrapper>
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
|
@ -1,46 +0,0 @@
|
||||||
import { useContext } from 'react'
|
|
||||||
import Link from 'next/link'
|
|
||||||
import { icons, ns as sectionsNs } from 'shared/components/navigation/primary.mjs'
|
|
||||||
import { useTranslation } from 'next-i18next'
|
|
||||||
import orderBy from 'lodash.orderby'
|
|
||||||
import { colors } from 'shared/components/header.mjs'
|
|
||||||
import { NavigationContext } from 'shared/context/navigation-context.mjs'
|
|
||||||
import { HelpIcon } from 'shared/components/icons.mjs'
|
|
||||||
|
|
||||||
export const ns = sectionsNs
|
|
||||||
|
|
||||||
export const SectionsMenu = () => {
|
|
||||||
const { t } = useTranslation(ns)
|
|
||||||
const { sections = false } = useContext(NavigationContext)
|
|
||||||
if (!sections) return null
|
|
||||||
|
|
||||||
// Ensure each page as an `o` key so we can put them in order
|
|
||||||
const sortableSections = sections.map((s) => ({ ...s, o: s.o ? s.o : s.t }))
|
|
||||||
const output = []
|
|
||||||
let i = 1
|
|
||||||
for (const page of orderBy(sortableSections, ['o', 't'])) {
|
|
||||||
const item = (
|
|
||||||
<Link
|
|
||||||
key={page.s}
|
|
||||||
className={`bg-${colors[i]}-400 p-0 rounded shadow hover:shadow-lg w-full bg-opacity-70 hover:bg-opacity-100 text-neutral-900
|
|
||||||
`}
|
|
||||||
href={`/${page.s}`}
|
|
||||||
title={page.t}
|
|
||||||
>
|
|
||||||
<div className="flex flex-col rounded">
|
|
||||||
<div className={`flex flex-row items-center justify-between pt-2 px-4`}>
|
|
||||||
<h4 className="text-neutral-900">{page.t}</h4>
|
|
||||||
{icons[page.s] ? icons[page.s]('w-10 h-10') : <HelpIcon />}
|
|
||||||
</div>
|
|
||||||
<div className={`font-medium text-base leading-5 text-left rounded-b py-4 px-4 `}>
|
|
||||||
{t(page.s + 'About')}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Link>
|
|
||||||
)
|
|
||||||
i++
|
|
||||||
output.push(item)
|
|
||||||
}
|
|
||||||
|
|
||||||
return <div className="flex flex-col gap-2">{output}</div>
|
|
||||||
}
|
|
27
sites/lab/prebuild.mjs
Normal file
27
sites/lab/prebuild.mjs
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
import { prebuildRunner } from '../shared/prebuild/runner.mjs'
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This handles the prebuild step for FreeSewing.dev
|
||||||
|
* It runs via an NPM run script, so in a pure NodeJS context
|
||||||
|
*
|
||||||
|
* See the org or dev site for an example with inline-comments
|
||||||
|
*/
|
||||||
|
prebuildRunner({
|
||||||
|
site: 'lab',
|
||||||
|
prebuild: {
|
||||||
|
// Always prebuild
|
||||||
|
designs: true,
|
||||||
|
i18n: true,
|
||||||
|
navigation: true,
|
||||||
|
// Prebuild in production
|
||||||
|
favicon: 'productionOnly',
|
||||||
|
ogImages: 'productionOnly',
|
||||||
|
// Never prebuild
|
||||||
|
docs: false,
|
||||||
|
contributors: false,
|
||||||
|
crowdin: false,
|
||||||
|
git: false,
|
||||||
|
patrons: false,
|
||||||
|
posts: false,
|
||||||
|
},
|
||||||
|
})
|
|
@ -1,13 +1,15 @@
|
||||||
|
import allLanguages from '../../../config/languages.json' assert { type: 'json' }
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import fs from 'fs'
|
import fs from 'fs'
|
||||||
import set from 'lodash.set'
|
import set from 'lodash.set'
|
||||||
import orderBy from 'lodash.orderby'
|
import orderBy from 'lodash.orderby'
|
||||||
import { extendSiteNav as dev } from './sitenav-dev.mjs'
|
import { extendSiteNav as dev } from './sitenav-dev.mjs'
|
||||||
import { extendSiteNav as org } from './sitenav-org.mjs'
|
import { extendSiteNav as org } from './sitenav-org.mjs'
|
||||||
|
import { extendSiteNav as lab } from './sitenav-lab.mjs'
|
||||||
import { pageHasChildren } from '../utils.mjs'
|
import { pageHasChildren } from '../utils.mjs'
|
||||||
import { header } from './shared.mjs'
|
import { header } from './shared.mjs'
|
||||||
|
|
||||||
const extendNav = { dev, org }
|
const extendNav = { dev, org, lab }
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* A method to recursively add the ordered slugs to the LUT
|
* A method to recursively add the ordered slugs to the LUT
|
||||||
|
@ -47,7 +49,7 @@ export const orderedSlugLut = (nav) => {
|
||||||
* Main method that does what needs doing
|
* Main method that does what needs doing
|
||||||
*/
|
*/
|
||||||
export const prebuildNavigation = async (store) => {
|
export const prebuildNavigation = async (store) => {
|
||||||
const { docs, site, posts = false } = store
|
const { site, docs = false, posts = false } = store
|
||||||
/*
|
/*
|
||||||
* Since this is written to disk and loaded as JSON, we minimize
|
* Since this is written to disk and loaded as JSON, we minimize
|
||||||
* the data to load by using the following 1-character keys:
|
* the data to load by using the following 1-character keys:
|
||||||
|
@ -60,12 +62,12 @@ export const prebuildNavigation = async (store) => {
|
||||||
const all = {
|
const all = {
|
||||||
sitenav: '',
|
sitenav: '',
|
||||||
}
|
}
|
||||||
const locales = []
|
const locales = docs ? Object.keys(docs) : allLanguages
|
||||||
for (const lang in docs) {
|
for (const lang of locales) {
|
||||||
locales.push(lang)
|
|
||||||
sitenav[lang] = {}
|
sitenav[lang] = {}
|
||||||
|
|
||||||
// Handle docs
|
// Handle docs if there are any
|
||||||
|
if (docs[lang]) {
|
||||||
for (const slug of Object.keys(docs[lang]).sort()) {
|
for (const slug of Object.keys(docs[lang]).sort()) {
|
||||||
const page = docs[lang][slug]
|
const page = docs[lang][slug]
|
||||||
const val = {
|
const val = {
|
||||||
|
@ -75,8 +77,9 @@ export const prebuildNavigation = async (store) => {
|
||||||
if (page.o) val.o = page.o
|
if (page.o) val.o = page.o
|
||||||
set(sitenav, [lang, ...slug.split('/')], val)
|
set(sitenav, [lang, ...slug.split('/')], val)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Handle posts
|
// Handle posts if there are any
|
||||||
if (posts) {
|
if (posts) {
|
||||||
for (const type in posts) {
|
for (const type in posts) {
|
||||||
for (const [slug, post] of Object.entries(posts[type].posts[lang])) {
|
for (const [slug, post] of Object.entries(posts[type].posts[lang])) {
|
||||||
|
@ -118,6 +121,9 @@ export const prebuildNavigation = async (store) => {
|
||||||
`${header}${all.sitenav}export const siteNav = { ${locales.join(',')} }`
|
`${header}${all.sitenav}export const siteNav = { ${locales.join(',')} }`
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// In the lab, there will be no navigation set in the store
|
||||||
|
if (!store.navigation) store.navigation = {}
|
||||||
|
|
||||||
// Update the store
|
// Update the store
|
||||||
store.navigation.sitenav = sitenav
|
store.navigation.sitenav = sitenav
|
||||||
|
|
||||||
|
|
205
sites/shared/prebuild/sitenav-lab.mjs
Normal file
205
sites/shared/prebuild/sitenav-lab.mjs
Normal file
|
@ -0,0 +1,205 @@
|
||||||
|
import { freeSewingConfig as conf } from '../config/freesewing.config.mjs'
|
||||||
|
import { designs, tags } from '../config/designs.mjs'
|
||||||
|
// Translation via i18next directly
|
||||||
|
import i18next from 'i18next'
|
||||||
|
// Actual translations for various languages
|
||||||
|
// EN
|
||||||
|
import accountEn from '../../lab/public/locales/en/sections.json' assert { type: 'json' }
|
||||||
|
import designsEn from '../../lab/public/locales/en/sections.json' assert { type: 'json' }
|
||||||
|
import sectionsEn from '../../lab/public/locales/en/sections.json' assert { type: 'json' }
|
||||||
|
import tagsEn from '../../lab/public/locales/en/sections.json' assert { type: 'json' }
|
||||||
|
// DE
|
||||||
|
import accountDe from '../../lab/public/locales/de/sections.json' assert { type: 'json' }
|
||||||
|
import designsDe from '../../lab/public/locales/de/sections.json' assert { type: 'json' }
|
||||||
|
import sectionsDe from '../../lab/public/locales/de/sections.json' assert { type: 'json' }
|
||||||
|
import tagsDe from '../../lab/public/locales/de/sections.json' assert { type: 'json' }
|
||||||
|
// ES
|
||||||
|
import accountEs from '../../lab/public/locales/es/sections.json' assert { type: 'json' }
|
||||||
|
import designsEs from '../../lab/public/locales/es/sections.json' assert { type: 'json' }
|
||||||
|
import sectionsEs from '../../lab/public/locales/es/sections.json' assert { type: 'json' }
|
||||||
|
import tagsEs from '../../lab/public/locales/es/sections.json' assert { type: 'json' }
|
||||||
|
// FR
|
||||||
|
import accountFr from '../../lab/public/locales/fr/sections.json' assert { type: 'json' }
|
||||||
|
import designsFr from '../../lab/public/locales/fr/sections.json' assert { type: 'json' }
|
||||||
|
import sectionsFr from '../../lab/public/locales/fr/sections.json' assert { type: 'json' }
|
||||||
|
import tagsFr from '../../lab/public/locales/fr/sections.json' assert { type: 'json' }
|
||||||
|
// NL
|
||||||
|
import accountNl from '../../lab/public/locales/nl/sections.json' assert { type: 'json' }
|
||||||
|
import designsNl from '../../lab/public/locales/nl/sections.json' assert { type: 'json' }
|
||||||
|
import sectionsNl from '../../lab/public/locales/nl/sections.json' assert { type: 'json' }
|
||||||
|
import tagsNl from '../../lab/public/locales/nl/sections.json' assert { type: 'json' }
|
||||||
|
// UK
|
||||||
|
import accountUk from '../../lab/public/locales/uk/sections.json' assert { type: 'json' }
|
||||||
|
import designsUk from '../../lab/public/locales/uk/sections.json' assert { type: 'json' }
|
||||||
|
import sectionsUk from '../../lab/public/locales/uk/sections.json' assert { type: 'json' }
|
||||||
|
import tagsUk from '../../lab/public/locales/uk/sections.json' assert { type: 'json' }
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Construct an object we can load the translations from
|
||||||
|
*/
|
||||||
|
const translations = {
|
||||||
|
en: {
|
||||||
|
account: accountEn,
|
||||||
|
design: designsEn,
|
||||||
|
sections: sectionsEn,
|
||||||
|
tags: tagsEn,
|
||||||
|
},
|
||||||
|
de: {
|
||||||
|
account: accountDe,
|
||||||
|
design: designsDe,
|
||||||
|
sections: sectionsDe,
|
||||||
|
tags: tagsDe,
|
||||||
|
},
|
||||||
|
es: {
|
||||||
|
account: accountEs,
|
||||||
|
design: designsEs,
|
||||||
|
sections: sectionsEs,
|
||||||
|
tags: tagsEs,
|
||||||
|
},
|
||||||
|
fr: {
|
||||||
|
account: accountFr,
|
||||||
|
design: designsFr,
|
||||||
|
sections: sectionsFr,
|
||||||
|
tags: tagsFr,
|
||||||
|
},
|
||||||
|
nl: {
|
||||||
|
account: accountNl,
|
||||||
|
design: designsNl,
|
||||||
|
sections: sectionsNl,
|
||||||
|
tags: tagsNl,
|
||||||
|
},
|
||||||
|
uk: {
|
||||||
|
account: accountUk,
|
||||||
|
design: designsUk,
|
||||||
|
sections: sectionsUk,
|
||||||
|
tags: tagsUk,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Remember Mc_Shifton:
|
||||||
|
* Note: Set 'm' to truthy to show this as a main section in the side-navigation (optional)
|
||||||
|
* Note: Set 'c' to set the control level to hide things from users (optional)
|
||||||
|
* Note: Set 's' to the slug (optional insofar as it's not a real page (a spacer for the header))
|
||||||
|
* Note: Set '_' to never show the page in the site navigation (like the tags pages)
|
||||||
|
* Note: Set 'h' to indicate this is a top-level page that should be hidden from the side-nav (like search)
|
||||||
|
* Note: Set 'i' when something should be included as top-level in the collapse side-navigation (optional)
|
||||||
|
* Note: Set 'f' to add the page to the footer
|
||||||
|
* Note: Set 't' to the title
|
||||||
|
* Note: Set 'o' to set the order (optional)
|
||||||
|
* Note: Set 'n' to mark this as a noisy entry that should always be closed unless active (like blog)
|
||||||
|
*/
|
||||||
|
export const extendSiteNav = (pages, lang) => {
|
||||||
|
const resources = {}
|
||||||
|
resources[lang] = translations[lang]
|
||||||
|
i18next.init({
|
||||||
|
lng: lang,
|
||||||
|
resources,
|
||||||
|
})
|
||||||
|
const { t } = i18next
|
||||||
|
|
||||||
|
const addThese = {
|
||||||
|
designs: {
|
||||||
|
m: 1,
|
||||||
|
s: 'designs',
|
||||||
|
t: t('sections:designs'),
|
||||||
|
n: 1,
|
||||||
|
tags: {
|
||||||
|
_: 1,
|
||||||
|
s: 'designs/tags',
|
||||||
|
t: t('design:tags'),
|
||||||
|
o: 'aaa',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
patterns: {
|
||||||
|
m: 1,
|
||||||
|
s: 'patterns',
|
||||||
|
t: t('sections:patterns'),
|
||||||
|
},
|
||||||
|
sets: {
|
||||||
|
m: 1,
|
||||||
|
s: 'sets',
|
||||||
|
t: t('sections:sets'),
|
||||||
|
},
|
||||||
|
docs: {
|
||||||
|
m: 1,
|
||||||
|
s: 'docs',
|
||||||
|
t: t('sections:docs'),
|
||||||
|
},
|
||||||
|
code: {
|
||||||
|
m: 1,
|
||||||
|
s: 'code',
|
||||||
|
t: t('sections:code'),
|
||||||
|
},
|
||||||
|
account: {
|
||||||
|
m: 1,
|
||||||
|
s: 'account',
|
||||||
|
t: t('sections:account'),
|
||||||
|
n: 1,
|
||||||
|
reload: {
|
||||||
|
s: `account/reload`,
|
||||||
|
t: t(`account:reload`),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// Top-level pages that are not in the sections menu
|
||||||
|
new: {
|
||||||
|
m: 1,
|
||||||
|
s: 'new',
|
||||||
|
h: 1,
|
||||||
|
t: t('sections:new'),
|
||||||
|
pattern: {
|
||||||
|
t: t('patternNew'),
|
||||||
|
s: 'new/pattern',
|
||||||
|
o: 10,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
profile: {
|
||||||
|
s: 'profile',
|
||||||
|
h: 1,
|
||||||
|
t: t('yourProfile'),
|
||||||
|
},
|
||||||
|
sitemap: {
|
||||||
|
s: 'sitemap',
|
||||||
|
h: 1,
|
||||||
|
t: t('sitemap'),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const section in conf.account.fields) {
|
||||||
|
for (const [field, controlScore] of Object.entries(conf.account.fields[section])) {
|
||||||
|
addThese.account[field] = {
|
||||||
|
s: `account/${field}`,
|
||||||
|
t: t(`account:${field}`),
|
||||||
|
c: controlScore,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const design in designs) {
|
||||||
|
// addThese.designs[design] = {
|
||||||
|
// t: t(`designs:${design}.t`),
|
||||||
|
// s: `designs/${design}`,
|
||||||
|
// }
|
||||||
|
addThese.new.pattern[design] = {
|
||||||
|
s: `new/${design}`,
|
||||||
|
t: t(`account:generateANewThing`, { thing: t(`designs:${design}.t`) }),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const tag of tags) {
|
||||||
|
addThese.designs.tags[tag] = {
|
||||||
|
s: `designs/tags/${tag}`,
|
||||||
|
t: t(`tags:${tag}`),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set order on main sections
|
||||||
|
addThese.designs.o = 10
|
||||||
|
addThese.patterns.o = 20
|
||||||
|
addThese.sets.o = 30
|
||||||
|
addThese.docs.o = 40
|
||||||
|
addThese.code.o = 50
|
||||||
|
addThese.account.o = 80
|
||||||
|
addThese.new.o = 90
|
||||||
|
|
||||||
|
return { ...pages, ...addThese }
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue