1
0
Fork 0

wip(fs.dev): Work on navigation menu

This commit is contained in:
Joost De Cock 2021-12-17 17:51:20 +01:00
parent bd2d5a49f2
commit 092c81f535
11 changed files with 2241 additions and 1957 deletions

View file

@ -16,16 +16,17 @@
"@mdx-js/react": "^2.0.0-rc.2", "@mdx-js/react": "^2.0.0-rc.2",
"@mdx-js/runtime": "next", "@mdx-js/runtime": "next",
"daisyui": "^1.16.2", "daisyui": "^1.16.2",
"lodash.orderby": "^4.6.0",
"next": "latest", "next": "latest",
"react-swipeable": "^6.2.0", "react-swipeable": "^6.2.0",
"lodash.orderby": "^4.6.0",
"lodash.get": "^4.4.2",
"lodash.set": "^4.3.2",
"remark-gfm": "^3.0.1", "remark-gfm": "^3.0.1",
"remark-jargon": "^2.19.5" "remark-jargon": "^2.19.5"
}, },
"devDependencies": { "devDependencies": {
"autoprefixer": "^10.4.0", "autoprefixer": "^10.4.0",
"lodash.get": "^4.4.2", "js-yaml": "^4.1.0",
"lodash.set": "^4.3.2",
"postcss": "^8.4.4", "postcss": "^8.4.4",
"tailwindcss": "^3.0.1" "tailwindcss": "^3.0.1"
} }

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,5 @@
export const posts = { export const posts = {
"blog/welcome-to-our-dev-blog": { "welcome-to-our-dev-blog": {
"dev": true, "dev": true,
"localizations": [], "localizations": [],
"_id": "60eaceb44f0f4957dbc9d64b", "_id": "60eaceb44f0f4957dbc9d64b",
@ -86,7 +86,7 @@ export const posts = {
"id": "60eaceb44f0f4957dbc9d64b", "id": "60eaceb44f0f4957dbc9d64b",
"intro": "The internet is plastered with abandoned or dormant blogs, so starting another one is perhaps madness, but hear me out:" "intro": "The internet is plastered with abandoned or dormant blogs, so starting another one is perhaps madness, but hear me out:"
}, },
"blog/project-2022": { "project-2022": {
"dev": true, "dev": true,
"localizations": [], "localizations": [],
"_id": "60eb151cdb32b45d5c4d6d96", "_id": "60eb151cdb32b45d5c4d6d96",
@ -173,7 +173,7 @@ export const posts = {
"id": "60eb151cdb32b45d5c4d6d96", "id": "60eb151cdb32b45d5c4d6d96",
"intro": "During my summer break I wanted to look into migrating the FreeSewing websites (freesewing.org and freesewing.dev) from GatbsyJS to NextJS ." "intro": "During my summer break I wanted to look into migrating the FreeSewing websites (freesewing.org and freesewing.dev) from GatbsyJS to NextJS ."
}, },
"blog/tailwind-css-project-2022": { "tailwind-css-project-2022": {
"dev": true, "dev": true,
"localizations": [], "localizations": [],
"_id": "60eb1836db32b45d5c4d6d99", "_id": "60eb1836db32b45d5c4d6d99",
@ -260,7 +260,7 @@ export const posts = {
"id": "60eb1836db32b45d5c4d6d99", "id": "60eb1836db32b45d5c4d6d99",
"intro": "Building a website that is supposed to do something essentially boils down to two things:" "intro": "Building a website that is supposed to do something essentially boils down to two things:"
}, },
"blog/daisyui-components-themes": { "daisyui-components-themes": {
"dev": true, "dev": true,
"localizations": [], "localizations": [],
"_id": "60eb1d41db32b45d5c4d6d9c", "_id": "60eb1d41db32b45d5c4d6d9c",
@ -347,7 +347,7 @@ export const posts = {
"id": "60eb1d41db32b45d5c4d6d9c", "id": "60eb1d41db32b45d5c4d6d9c",
"intro": "Since we're changing pretty much everything in project 2022 anyway, we might as well try to reduce our bundle size by removing our dependency on Material UI , the React component framework that implements Google's material design." "intro": "Since we're changing pretty much everything in project 2022 anyway, we might as well try to reduce our bundle size by removing our dependency on Material UI , the React component framework that implements Google's material design."
}, },
"blog/shared-frontend-code-monorepo": { "shared-frontend-code-monorepo": {
"dev": true, "dev": true,
"localizations": [], "localizations": [],
"_id": "60eb237cdb32b45d5c4d6d9f", "_id": "60eb237cdb32b45d5c4d6d9f",
@ -434,7 +434,7 @@ export const posts = {
"id": "60eb237cdb32b45d5c4d6d9f", "id": "60eb237cdb32b45d5c4d6d9f",
"intro": "Up until now, our websites each live in their own repository. This makes sharing code and content somewhat more involved, and situation that we currently handle as follows:" "intro": "Up until now, our websites each live in their own repository. This makes sharing code and content somewhat more involved, and situation that we currently handle as follows:"
}, },
"blog/improved-search-keyboard": { "improved-search-keyboard": {
"dev": true, "dev": true,
"localizations": [], "localizations": [],
"_id": "60eb2759db32b45d5c4d6da2", "_id": "60eb2759db32b45d5c4d6da2",
@ -521,7 +521,7 @@ export const posts = {
"id": "60eb2759db32b45d5c4d6da2", "id": "60eb2759db32b45d5c4d6da2",
"intro": "Since we've got a ton of documentation, good search is a crucial aspect of our websites." "intro": "Since we've got a ton of documentation, good search is a crucial aspect of our websites."
}, },
"blog/strapi-headless-cms": { "strapi-headless-cms": {
"dev": true, "dev": true,
"localizations": [], "localizations": [],
"_id": "60eb2e11db32b45d5c4d6da5", "_id": "60eb2e11db32b45d5c4d6da5",
@ -608,7 +608,7 @@ export const posts = {
"id": "60eb2e11db32b45d5c4d6da5", "id": "60eb2e11db32b45d5c4d6da5",
"intro": "As things are now, all of FreeSewing's content is contained in our markdown repository ." "intro": "As things are now, all of FreeSewing's content is contained in our markdown repository ."
}, },
"blog/layout-blocks-overview": { "layout-blocks-overview": {
"dev": true, "dev": true,
"localizations": [], "localizations": [],
"_id": "60f0616ddb32b45d5c4d6dac", "_id": "60f0616ddb32b45d5c4d6dac",
@ -695,7 +695,7 @@ export const posts = {
"id": "60f0616ddb32b45d5c4d6dac", "id": "60f0616ddb32b45d5c4d6dac",
"intro": "I wanted to do a quick write-up of the main layout blocks of our websites. So let's have a look:" "intro": "I wanted to do a quick write-up of the main layout blocks of our websites. So let's have a look:"
}, },
"blog/freesewing-v3-seems-inevitable": { "freesewing-v3-seems-inevitable": {
"dev": true, "dev": true,
"localizations": [], "localizations": [],
"_id": "60f2e2ef2f81c03239366844", "_id": "60f2e2ef2f81c03239366844",
@ -793,7 +793,7 @@ export const posts = {
"id": "60f2e2ef2f81c03239366844", "id": "60f2e2ef2f81c03239366844",
"intro": "I just made a bunch of breaking changes in @freesewing/i18n and it made me realize that if/when we get around to merging my work back into our monorepo, there's going to be too many changes not to bump the major version." "intro": "I just made a bunch of breaking changes in @freesewing/i18n and it made me realize that if/when we get around to merging my work back into our monorepo, there's going to be too many changes not to bump the major version."
}, },
"blog/migration-to-strapi": { "migration-to-strapi": {
"dev": true, "dev": true,
"localizations": [], "localizations": [],
"_id": "612785b5fca7f36c6a727a6b", "_id": "612785b5fca7f36c6a727a6b",
@ -890,7 +890,7 @@ export const posts = {
"id": "612785b5fca7f36c6a727a6b", "id": "612785b5fca7f36c6a727a6b",
"intro": "Effective immediately, all blog and showcase posts on freesewing.org are backed by our Strapi instance , rather than our markdown repository . That markdown repository has been archived, and the markdown content that are not blog or showcase posts (predominantly documentation) has been moved into our monorepo ." "intro": "Effective immediately, all blog and showcase posts on freesewing.org are backed by our Strapi instance , rather than our markdown repository . That markdown repository has been archived, and the markdown content that are not blog or showcase posts (predominantly documentation) has been moved into our monorepo ."
}, },
"blog/pattern-docs-option-sampling": { "pattern-docs-option-sampling": {
"dev": true, "dev": true,
"localizations": [], "localizations": [],
"_id": "6128ccacfca7f36c6a727a70", "_id": "6128ccacfca7f36c6a727a70",

View file

@ -4,12 +4,84 @@ import nav from 'site/prebuild/navigation.js'
import Link from 'next/link' import Link from 'next/link'
import orderBy from 'lodash.orderby' import orderBy from 'lodash.orderby'
// TODO: Clean this up after restructuring markdown content
const hide = ['contributors', 'developers', 'editors', 'translators']
// Don't show children for blog and showcase posts
const keepClosed = ['blog', 'showcase', ] const keepClosed = ['blog', 'showcase', ]
const linkClasses = {className: 'hover:text-underline color-primary'} // TODO: For now we force tailwind to pickup these styles
// At some point this should 'just work' though, but let's not worry about it now
const force = <p className="w-6 mr-3"/>
// Component for the collapse toggle
const Chevron = ({w=8, m=1}) => <svg
className={`fill-current opacity-75 w-${w} h-${w} mr-${m} details-toggle`}
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20">
<path d="M12.95 10.707l.707-.707L8 4.343 6.586 5.757 10.828 10l-4.242 4.243L8 15.657l4.95-4.95z"/>
</svg>
const TopLevel = ({ icon, title, nav, current }) => ( // Helper method to filter out the real children
const currentChildren = current => Object.values(current)
.filter(entry => (typeof entry === 'object'))
// Component that renders a sublevel of navigation
const SubLevel = ({ nodes={} }) => (
<ul className='list-inside'>
{currentChildren(nodes).map(child => (Object.keys(child).length > 4)
? (
<li>
<details key={child.__slug}>
<summary className={`
flex flex-row gap-4 font-bold
hover:cursor-row-resize
hover:bg-base-200
p-1
px-2
text-primary
`}>
<Link href={child.__slug}>
<a title={child.__title} className={`
grow
text-primary
color-primary
hover:cursor-pointer
hover:underline
hover:decoration-secondary
hover:decoration-2
`}>
{ child?.__linktitle || child.__title }
</a>
</Link>
<Chevron w={6} m={3}/>
</summary>
<SubLevel nodes={child} />
</details>
</li>
) : (
<li className='text-primary pl-2 font-bold'>
<div className={`
p-1
text-primary
color-primary
hover:cursor-pointer
hover:underline
hover:decoration-secondary
hover:decoration-2
`}>
<Link href={child.__slug} title={child.__title}>
{child.__linktitle}
</Link>
</div>
</li>
)
)}
</ul>
)
// Component that renders a toplevel of navigation
const TopLevel = ({ icon, title, nav, current, slug, showChildren=false }) => (
<details className='p-2' open={((keepClosed.indexOf(current._slug) === -1) ? 1 : 0)}> <details className='p-2' open={((keepClosed.indexOf(current._slug) === -1) ? 1 : 0)}>
<summary className={` <summary className={`
flex flex-row uppercase gap-4 font-bold text-lg flex flex-row uppercase gap-4 font-bold text-lg
@ -19,45 +91,40 @@ const TopLevel = ({ icon, title, nav, current }) => (
text-primary text-primary
`}> `}>
{icon} {icon}
<Link {/* Wrapping this in a div because tailwind doesn't pick up
href={`/${current._slug}/`} classes on the next js Link component */}
className='hover:cursor-pointer' <div className={`
> grow
{title} hover:cursor-pointer hover:text-underline
</Link> hover:underline
hover:decoration-secondary
hover:decoration-4
`}>
<Link href={`/${current._slug}/`}>
{title}
</Link>
</div>
{showChildren && <Chevron />}
</summary> </summary>
<div className='pl-4'> {showChildren && <SubLevel nodes={current} />}
<ul>
{orderBy(Object.values(current._children), ['order', 'title'], ['asc', 'asc']).map(item => {
console.log(item)
const target = item._slug ? get(nav, item._slug.split('/')) : '/'
return (
<li key={item._slug}>
{ item?._linktitle || item._title }
</li>
)
})}
</ul>
</div>
</details> </details>
) )
// TODO: Get rid of this when markdown has been restructured // TODO: Get rid of this when markdown has been restructured
const remove = ['contributors', 'developers', 'editors', 'translators'] const remove = ['contributors', 'developers', 'editors', 'translators']
const Navigation = ({ nav, app }) => { const Navigation = ({ nav, app }) => {
console.log(nav.en)
const output = [] const output = []
for (const key of Object.keys(nav[app.language]).sort()) { for (const key of Object.keys(nav[app.language]).sort()) {
if ( if (hide.indexOf(key) === -1) output.push(<TopLevel
key.slice(0,1) !== '_' && icon={<Icon icon={key}/>}
remove.indexOf(key) === -1 title={key}
) output.push(<TopLevel slug={key}
icon={<Icon icon={key}/>} key={key}
title={key} showChildren={keepClosed.indexOf(key) === -1}
key={key} nav={nav[app.language]}
nav={nav[app.language]} current={orderBy(nav[app.language][key], ['order', 'title'], ['asc', 'asc'])}
current={nav[app.language][key]} />)
/>
)
} }
return output return output

View file

@ -7,9 +7,10 @@ import remarkParser from 'remark-parse'
import remarkCompiler from 'remark-stringify' import remarkCompiler from 'remark-stringify'
import remarkFrontmatter from 'remark-frontmatter' import remarkFrontmatter from 'remark-frontmatter'
import remarkFrontmatterExtractor from 'remark-extract-frontmatter' import remarkFrontmatterExtractor from 'remark-extract-frontmatter'
import remarkMdx from 'remark-mdx'
import vfileReporter from 'vfile-reporter' import vfileReporter from 'vfile-reporter'
import { readSync } from 'to-vfile' import { readSync } from 'to-vfile'
import yaml from 'yaml' import yaml from 'js-yaml'
import { remarkIntroPlugin } from './remark-intro-plugin.mjs' import { remarkIntroPlugin } from './remark-intro-plugin.mjs'
@ -59,13 +60,24 @@ const fileToSlug = (file, site, lang) => (file.slice(-6) === `/${lang}.md`)
* *
* - file: the full path to the file * - file: the full path to the file
*/ */
const mdxMetaInfo = async file => await unified() const mdxMetaInfo = async file => {
.use(remarkIntroPlugin) let result
.use(remarkParser) try {
.use(remarkCompiler) result = await unified()
.use(remarkFrontmatter) .use(remarkMdx)
.use(remarkFrontmatterExtractor, { yaml: yaml.parse }) .use(remarkIntroPlugin)
.process(readSync(file)) .use(remarkParser)
.use(remarkCompiler)
.use(remarkFrontmatter)
.use(remarkFrontmatterExtractor, { yaml: yaml.load })
.process(readSync(file))
}
catch (err) {
console.log(err)
}
return result
}
/* /*
* Main method that does what needs doing * Main method that does what needs doing
@ -94,14 +106,21 @@ export const prebuildMdx = async(site) => {
const slug = fileToSlug(file, site, lang) const slug = fileToSlug(file, site, lang)
if (slug) { if (slug) {
const meta = await mdxMetaInfo(file) const meta = await mdxMetaInfo(file)
if (meta) { if (meta?.data?.title && meta?.data?.title) {
pages[lang][slug] = { pages[lang][slug] = {
...meta.data, title: meta.data.title,
intro: meta.data.intro,
order: meta.data.order
? meta.data.order+meta.data.title
: meta.data.title,
slug, slug,
order: meta?.data?.order order: meta?.data?.order
? `${meta.data.order}${meta.data.title}` ? `${meta.data.order}${meta.data.title}`
: meta.data.title : meta.data.title
} }
} else {
console.log('Failed to extract title & intro from:', slug)
console.log(meta.messages, null ,2)
} }
} }
} }

View file

@ -19,38 +19,38 @@ export const prebuildNavigation = (mdxPages, strapiPosts, site) => {
for (const [slug, page] of Object.entries(mdxPages[lang])) { for (const [slug, page] of Object.entries(mdxPages[lang])) {
const chunks = slug.split('/') const chunks = slug.split('/')
set(nav, [lang, ...chunks], { set(nav, [lang, ...chunks], {
_title: page.title, __title: page.title,
_linktitle: page.linktitle || page.title, __linktitle: page.linktitle || page.title,
_slug: slug, __slug: slug,
_order: page.order, __order: page.order,
_children: {} //__children: {}
}) })
const children = get(nav, [lang, ...chunks.slice(0, -1), '_children'], {}) //const children = get(nav, [lang, ...chunks.slice(0, -1), '_children'], {})
children[page.order || slug] = slug //children[page.order || slug] = slug
set(nav, [lang, ...chunks.slice(0, -1), '_children'], children) //set(nav, [lang, ...chunks.slice(0, -1), '_children'], children)
} }
// Handle strapi content // Handle strapi content
for (const type in strapiPosts[lang]) { for (const type in strapiPosts[lang]) {
set(nav, [lang, type], { set(nav, [lang, type], {
_title: type, __title: type,
_linktitle: type, __linktitle: type,
_slug: type, __slug: type,
_order: type, __order: type,
_children: {} //__children: {}
}) })
for (const [slug, page] of Object.entries(strapiPosts[lang][type])) { for (const [slug, page] of Object.entries(strapiPosts[lang][type])) {
const chunks = slug.split('/') const chunks = slug.split('/')
set(nav, [lang, type, ...chunks], { set(nav, [lang, type, ...chunks], {
_title: page.title, __title: page.title,
_linktitle: page.linktitle, __linktitle: page.linktitle,
_slug: slug, __slug: type + '/' + slug,
_order: (future - new Date(page.date).getTime()) / 100000, __order: (future - new Date(page.date).getTime()) / 100000,
_children: {} //__children: {}
}) })
const children = get(nav, [lang, type, '_children'], {}) //const children = get(nav, [lang, type, '_children'], {})
children[page.date+slug] = slug //children[page.date+slug] = slug
set(nav, [lang, type, '_children'], children) //set(nav, [lang, type, '_children'], children)
} }
} }
} }

View file

@ -54,7 +54,8 @@ export const remarkIntroPlugin = () => {
const visitor = (node) => { const visitor = (node) => {
const intro = extractFirstParagraph(node) const intro = extractFirstParagraph(node)
if (hasFrontmatter(node)) { if (hasFrontmatter(node)) {
node.children[0].value += `\nintro: "${intro}"` node.children[0].value += `\nintro: "${intro}"\n`
//console.log(node.children[0])
} else { } else {
node.children.unshift({ node.children.unshift({
type: 'yaml', type: 'yaml',

View file

@ -46,8 +46,8 @@ const getPosts = async (type, site, lang) => {
} }
const posts = {} const posts = {}
for (const post of res.data) { for (const post of res.data) {
const intro = await postIntro(post.body) const intro = await postIntro(`---\n---\n\n${post.body}`)
posts[`${type}/${post.slug}`] = { ...post, intro: intro.data.intro } posts[post.slug] = { ...post, intro: intro.data.intro }
} }
return posts return posts

View file

@ -2,3 +2,22 @@
@tailwind components; @tailwind components;
@tailwind utilities; @tailwind utilities;
details { user-select: none; }
details > summary > svg.details-toggle {
transform: rotate(90deg);
opacity: 0.4;
}
details[open] > summary > svg.details-toggle {
transform: rotate(-90deg);
opacity: 0.8;
}
details[open] summary ~ * { animation: ease-opacity-t-b .5s ease}
summary { cursor: pointer; }
svg.details-toggle { transition: all 0.2s ease-out; }
/* TO JE TO - TO JE TAJ */
summary::-webkit-details-marker {
display: none;
}

View file

@ -12460,7 +12460,7 @@ js-yaml@3.14.1, js-yaml@^3.13.1, js-yaml@^3.14.0:
argparse "^1.0.7" argparse "^1.0.7"
esprima "^4.0.0" esprima "^4.0.0"
js-yaml@4.1.0, js-yaml@^4.0.0: js-yaml@4.1.0, js-yaml@^4.0.0, js-yaml@^4.1.0:
version "4.1.0" version "4.1.0"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602"
integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==