1
0
Fork 0

feat(lab): port lab to new prebuild refactor

This commit is contained in:
joostdecock 2023-07-21 14:23:42 +02:00
parent 69f38fd8a2
commit bcf47b7d50
6 changed files with 299 additions and 88 deletions

View 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>
)

View file

@ -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">
<div className="max-w-full">
return ( <div
<ModalWrapper flex="col" justify="top lg:justify-center" slideFrom="left"> className={`
<div className="max-w-full"> py-4 w-full m-auto
<div flex flex-col-reverse gap-0 flex-wrap justify-between
className={` lg:max-w-6xl lg:flex-nowrap lg:gap-8 lg:flex-row
py-4 lg:py-16 w-full m-auto `}
flex flex-col-reverse gap-0 flex-wrap justify-between >
lg:max-w-6xl lg:flex-nowrap lg:gap-8 lg:flex-row <div className="w-full">
`} <SectionsMenu />
> <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('mainSections')}</h3> find here.
<SectionsMenu /> </ChoiceLink>
</div>
<div className="w-full lg:w-1/2">
<h3>{t('currentSection')}</h3>
<ActiveSection bare />
</div>
</div> </div>
</div> </div>
</ModalWrapper> </div>
) </ModalWrapper>
} )

View file

@ -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
View 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,
},
})

View file

@ -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,23 +62,24 @@ 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
for (const slug of Object.keys(docs[lang]).sort()) { if (docs[lang]) {
const page = docs[lang][slug] for (const slug of Object.keys(docs[lang]).sort()) {
const val = { const page = docs[lang][slug]
t: page.t, const val = {
s: slug, t: page.t,
s: slug,
}
if (page.o) val.o = page.o
set(sitenav, [lang, ...slug.split('/')], val)
} }
if (page.o) val.o = page.o
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

View 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 }
}