diff --git a/packages/freesewing.dev/pages/index.js b/packages/freesewing.dev/pages/index.js index fb6b56a96e0..452e0ebe2aa 100644 --- a/packages/freesewing.dev/pages/index.js +++ b/packages/freesewing.dev/pages/index.js @@ -2,6 +2,8 @@ import Page from 'shared/components/wrappers/page.js' import useApp from 'site/hooks/useApp.js' import ThemePicker from 'shared/components/theme-picker.js' +import { blog, mdx } from 'site/prebuild/index.js' + export default (props) => { const app = useApp() return ( @@ -13,6 +15,8 @@ export default (props) => { >Toggle
{JSON.stringify(blog, null ,2)}+
{JSON.stringify(mdx, null ,2)}) } diff --git a/packages/freesewing.dev/prebuild/index.js b/packages/freesewing.dev/prebuild/index.js new file mode 100644 index 00000000000..6cacc09c4cb --- /dev/null +++ b/packages/freesewing.dev/prebuild/index.js @@ -0,0 +1,5 @@ +import mdxImport from './mdx.js' +import blogImport from './strapi.blog.js' + +export const mdx = mdxImport +export const blog = blogImport diff --git a/packages/freesewing.dev/prebuild/mdx.en.js b/packages/freesewing.dev/prebuild/mdx.en.js new file mode 100644 index 00000000000..36d75a53b61 --- /dev/null +++ b/packages/freesewing.dev/prebuild/mdx.en.js @@ -0,0 +1,2541 @@ +export default { + "contributors/code-of-conduct": { + "title": "Code of Conduct", + "for": "contributors", + "icons": [ + "face", + "heart" + ], + "about": "FreeSewing's code of conduct is based on the [contributor covenant](https://www.contributor-covenant.org/)\n", + "intro": "Upholding our Code of Conduct is a requirement for all FreeSewing contributors", + "slug": "contributors/code-of-conduct", + "order": "Code of Conduct" + }, + "contributors/code-of-conduct/enforcement-guidelines/correction": { + "slug": "contributors/code-of-conduct/enforcement-guidelines/correction" + }, + "contributors/code-of-conduct/enforcement-guidelines": { + "slug": "contributors/code-of-conduct/enforcement-guidelines" + }, + "contributors/code-of-conduct/enforcement-guidelines/permanent-ban": { + "slug": "contributors/code-of-conduct/enforcement-guidelines/permanent-ban" + }, + "contributors/code-of-conduct/enforcement-guidelines/temporary-ban": { + "slug": "contributors/code-of-conduct/enforcement-guidelines/temporary-ban" + }, + "contributors/code-of-conduct/enforcement-guidelines/warning": { + "title": "Warning", + "order": "20Warning", + "intro": "A violation through a single incident or series of actions.", + "slug": "contributors/code-of-conduct/enforcement-guidelines/warning" + }, + "contributors/code-of-conduct/enforcement-responsibilities": { + "slug": "contributors/code-of-conduct/enforcement-responsibilities" + }, + "contributors/code-of-conduct/enforcement": { + "slug": "contributors/code-of-conduct/enforcement" + }, + "contributors/code-of-conduct/our-pledge": { + "slug": "contributors/code-of-conduct/our-pledge" + }, + "contributors/code-of-conduct/our-standards": { + "title": "Our standards", + "order": "20Our standards", + "intro": "Examples of behavior that contributes to a positive environment for our community include:", + "slug": "contributors/code-of-conduct/our-standards" + }, + "contributors/code-of-conduct/scope": { + "slug": "contributors/code-of-conduct/scope" + }, + "contributors": { + "title": "For contributors", + "order": "1110For contributors", + "intro": "Thank you for being part of our community, and for wanting to contribute! ❤️", + "slug": "contributors" + }, + "contributors/help": { + "slug": "contributors/help" + }, + "contributors/terms/backend": { + "title": "Backend", + "intro": "The FreeSewing backend handles all user data for freesewing.org.", + "slug": "contributors/terms/backend", + "order": "Backend" + }, + "contributors/terms/commit": { + "title": "Commit", + "intro": "A commit is made every time somebody published an update to our source code.", + "slug": "contributors/terms/commit", + "order": "Commit" + }, + "contributors/terms/contributor-call": { + "slug": "contributors/terms/contributor-call" + }, + "contributors/terms/contributor": { + "slug": "contributors/terms/contributor" + }, + "contributors/terms/design-pattern": { + "slug": "contributors/terms/design-pattern" + }, + "contributors/terms/developer": { + "title": "Developer", + "intro": "Anybody who works with software code or wants to learn to do so.", + "slug": "contributors/terms/developer", + "order": "Developer" + }, + "contributors/terms/discord": { + "title": "Discord", + "intro": "The name of our chat provider that powers our chat at https://discord.freesewing.org/", + "slug": "contributors/terms/discord", + "order": "Discord" + }, + "contributors/terms/editor": { + "slug": "contributors/terms/editor" + }, + "contributors/terms": { + "slug": "contributors/terms" + }, + "contributors/terms/express": { + "title": "Express", + "intro": "Express is a web framework for NodeJS.", + "slug": "contributors/terms/express", + "order": "Express" + }, + "contributors/terms/freesewing.dev": { + "title": "freesewing.dev", + "intro": "Our website for developers.", + "slug": "contributors/terms/freesewing.dev", + "order": "freesewing.dev" + }, + "contributors/terms/freesewing.org": { + "title": "freesewing.org", + "intro": "Our website for makers.", + "slug": "contributors/terms/freesewing.org", + "order": "freesewing.org" + }, + "contributors/terms/freesewing": { + "title": "FreeSewing", + "intro": "FreeSewing refers to the community-driven open source project.", + "slug": "contributors/terms/freesewing", + "order": "FreeSewing" + }, + "contributors/terms/frontend": { + "title": "Frontend", + "intro": "A user-facing interface. Can refer to one of our websites, or our development environment.", + "slug": "contributors/terms/frontend", + "order": "Frontend" + }, + "contributors/terms/gatsby": { + "title": "Gatsby", + "intro": "Gatsby is a static-site generator for React.", + "slug": "contributors/terms/gatsby", + "order": "Gatsby" + }, + "contributors/terms/i18n": { + "slug": "contributors/terms/i18n" + }, + "contributors/terms/issue": { + "slug": "contributors/terms/issue" + }, + "contributors/terms/javascript": { + "slug": "contributors/terms/javascript" + }, + "contributors/terms/made-to-measure": { + "title": "Made-to-measure", + "intro": "Made to someone's (body) measurements, rather than based on specific sizes.", + "slug": "contributors/terms/made-to-measure", + "order": "Made-to-measure" + }, + "contributors/terms/markdown": { + "slug": "contributors/terms/markdown" + }, + "contributors/terms/monorepo": { + "slug": "contributors/terms/monorepo" + }, + "contributors/terms/node": { + "title": "Node", + "intro": "Node (or NodeJS) is a Javascript runtime that allows to use the language outside the browser.", + "slug": "contributors/terms/node", + "order": "Node" + }, + "contributors/terms/parametric-pattern-design": { + "slug": "contributors/terms/parametric-pattern-design" + }, + "contributors/terms/patron": { + "title": "Patron", + "intro": "A person who supports FreeSewing financially.", + "slug": "contributors/terms/patron", + "order": "Patron" + }, + "contributors/terms/plugin": { + "title": "Plugin", + "intro": "An extension to FreeSewing core that provides an extra feature of functionality.", + "slug": "contributors/terms/plugin", + "order": "Plugin" + }, + "contributors/terms/pull-request": { + "title": "Pull request", + "intro": "A pull request is a proposal to commit changes to a repository.", + "slug": "contributors/terms/pull-request", + "order": "Pull request" + }, + "contributors/terms/react": { + "title": "React", + "intro": "React is a Javascript framework to build user interfaces on the web.", + "slug": "contributors/terms/react", + "order": "React" + }, + "contributors/terms/repo": { + "title": "Repo", + "intro": "Short for repository .", + "slug": "contributors/terms/repo", + "order": "Repo" + }, + "contributors/terms/repository": { + "title": "Repository", + "intro": "We develop our software collaboratively, and all code is available in Github in so-called repositories.", + "slug": "contributors/terms/repository", + "order": "Repository" + }, + "contributors/terms/tiler": { + "slug": "contributors/terms/tiler" + }, + "contributors/terms/translator": { + "title": "Translator", + "intro": "Somebody who helps with our i18n efforts by translating from English to other languages.", + "slug": "contributors/terms/translator", + "order": "Translator" + }, + "contributors/ways-to-contribute/body-ambassador": { + "slug": "contributors/ways-to-contribute/body-ambassador" + }, + "contributors/ways-to-contribute/community-building": { + "slug": "contributors/ways-to-contribute/community-building" + }, + "contributors/ways-to-contribute/design-patterns": { + "title": "Design sewing patterns", + "intro": "Everybody wants us to add more patterns. But somebody has to design them.", + "slug": "contributors/ways-to-contribute/design-patterns", + "order": "Design sewing patterns" + }, + "contributors/ways-to-contribute/develop-patterns": { + "slug": "contributors/ways-to-contribute/develop-patterns" + }, + "contributors/ways-to-contribute/devops": { + "slug": "contributors/ways-to-contribute/devops" + }, + "contributors/ways-to-contribute": { + "slug": "contributors/ways-to-contribute" + }, + "contributors/ways-to-contribute/illustrations": { + "slug": "contributors/ways-to-contribute/illustrations" + }, + "contributors/ways-to-contribute/language-ambassador": { + "slug": "contributors/ways-to-contribute/language-ambassador" + }, + "contributors/ways-to-contribute/pattern-ambassador": { + "title": "Pattern ambassador", + "intro": "You could take charge of a specific FreeSewing design/pattern.", + "slug": "contributors/ways-to-contribute/pattern-ambassador", + "order": "Pattern ambassador" + }, + "contributors/ways-to-contribute/pattern-testing": { + "title": "Pattern testing", + "intro": "You could make (a muslin for) our patterns prior to release to make sure everything is ok.", + "slug": "contributors/ways-to-contribute/pattern-testing", + "order": "Pattern testing" + }, + "contributors/ways-to-contribute/project-management": { + "title": "Project management", + "intro": "There's a lot going on within the FreeSewing project and it's easy to forget about something.", + "slug": "contributors/ways-to-contribute/project-management", + "order": "Project management" + }, + "contributors/ways-to-contribute/proofreading": { + "slug": "contributors/ways-to-contribute/proofreading" + }, + "contributors/ways-to-contribute/report-bugs": { + "slug": "contributors/ways-to-contribute/report-bugs" + }, + "contributors/ways-to-contribute/showcase-our-patterns": { + "title": "Showcase our patterns", + "intro": "Anytime somebody has made one of our patterns, we like to showcase it on freesewing.org .", + "slug": "contributors/ways-to-contribute/showcase-our-patterns", + "order": "Showcase our patterns" + }, + "contributors/ways-to-contribute/technical-writing/code": { + "title": "Writing for freesewing.dev", + "intro": "You could write documentation for freesewing.dev, our developers website.", + "slug": "contributors/ways-to-contribute/technical-writing/code", + "order": "Writing for freesewing.dev" + }, + "contributors/ways-to-contribute/technical-writing": { + "title": "Technical writing", + "intro": "FIXME_no_intro", + "slug": "contributors/ways-to-contribute/technical-writing", + "order": "Technical writing" + }, + "contributors/ways-to-contribute/technical-writing/patterns": { + "title": "Writing for freesewing.org", + "intro": "You could write documentation for freesewing.org, our makers website.", + "slug": "contributors/ways-to-contribute/technical-writing/patterns", + "order": "Writing for freesewing.org" + }, + "contributors/ways-to-contribute/translation": { + "slug": "contributors/ways-to-contribute/translation" + }, + "contributors/ways-to-contribute/triage-issues": { + "title": "Triage issues", + "intro": "Triaging issues is a great way to get involved in FreeSewing. You can do tasks such as:", + "slug": "contributors/ways-to-contribute/triage-issues", + "order": "Triage issues" + }, + "contributors/ways-to-contribute/webdesign": { + "title": "UI / UX Webdesign", + "intro": "You could help us make our website and tools pretty and improve the user experience.", + "slug": "contributors/ways-to-contribute/webdesign", + "order": "UI / UX Webdesign" + }, + "developers/backend": { + "title": "The freesewing backend", + "intro": "Under construction", + "slug": "developers/backend", + "order": "The freesewing backend" + }, + "developers/dev": { + "title": "The freesewing.dev website", + "intro": "Under construction", + "slug": "developers/dev", + "order": "The freesewing.dev website" + }, + "developers": { + "title": "For developers", + "order": "1120For developers", + "intro": "Welcome to the FreeSewing documentation for developers.", + "slug": "developers" + }, + "developers/org": { + "title": "The freesewing.org website", + "intro": "Under construction", + "slug": "developers/org", + "order": "The freesewing.org website" + }, + "developers/svg2pdf": { + "title": "The freesewing on-demand tiler", + "intro": "Under construction", + "slug": "developers/svg2pdf", + "order": "The freesewing on-demand tiler" + }, + "developers/tiler": { + "title": "Our tile command line tool", + "intro": "Under construction", + "slug": "developers/tiler", + "order": "Our tile command line tool" + }, + "editors/content": { + "title": "Content sources", + "intro": "As an editor, you need to know where you can find/edit what type of content.", + "slug": "editors/content", + "order": "Content sources" + }, + "editors": { + "title": "For editors", + "order": "1130For editors", + "intro": "Content is king, and as an content is your baby.", + "slug": "editors" + }, + "editors/howtos/blogpost": { + "title": "Adding a blog post", + "for": "editors", + "intro": "Blog posts have been migrated to Strapi , a headless CMS system.", + "slug": "editors/howtos/blogpost", + "order": "Adding a blog post" + }, + "editors/howtos": { + "title": "Common tasks for editors", + "intro": "Below is a list of commons tasks for editors", + "slug": "editors/howtos", + "order": "Common tasks for editors" + }, + "editors/howtos/frontmatter/about": { + "title": "About frontmatter", + "order": "10About frontmatter", + "intro": "Frontmatter is a way to add metadata to markdown documents.", + "slug": "editors/howtos/frontmatter/about" + }, + "editors/howtos/frontmatter": { + "slug": "editors/howtos/frontmatter" + }, + "editors/howtos/frontmatter/lists": { + "slug": "editors/howtos/frontmatter/lists" + }, + "editors/howtos/frontmatter/multi-line": { + "slug": "editors/howtos/frontmatter/multi-line" + }, + "editors/howtos/frontmatter/structure": { + "slug": "editors/howtos/frontmatter/structure" + }, + "editors/howtos/showcase": { + "title": "Adding a showcase on freesewing.org", + "for": "editors", + "intro": "Showcase posts have been migrated to Strapi , a headless CMS system.", + "slug": "editors/howtos/showcase", + "order": "Adding a showcase on freesewing.org" + }, + "editors/markdown/code-blocks": { + "slug": "editors/markdown/code-blocks" + }, + "editors/markdown/custom-components": { + "title": "Custom components", + "order": "90Custom components", + "intro": "The way we render markdown on our websites is through the use of MDX . This allows us to extend Markdown with our own so-called .", + "slug": "editors/markdown/custom-components" + }, + "editors/markdown/custom-components/example": { + "slug": "editors/markdown/custom-components/example" + }, + "editors/markdown/custom-components/fixme": { + "title": "Fixme", + "order": "40Fixme", + "intro": "I indicate that something needs work or improvement", + "slug": "editors/markdown/custom-components/fixme" + }, + "editors/markdown/custom-components/hashtag": { + "title": "Hashtag", + "order": "60Hashtag", + "intro": "Typically used on our community pages:", + "slug": "editors/markdown/custom-components/hashtag" + }, + "editors/markdown/custom-components/note": { + "title": "Note", + "order": "10Note", + "intro": "I point out things", + "slug": "editors/markdown/custom-components/note" + }, + "editors/markdown/custom-components/readmore": { + "title": "ReadMore", + "order": "70ReadMore", + "intro": "This component will list child pages of the current page.", + "slug": "editors/markdown/custom-components/readmore" + }, + "editors/markdown/custom-components/readmore/example-1": { + "title": "Example page 1", + "intro": "This page is here to illustrate the use of the .", + "slug": "editors/markdown/custom-components/readmore/example-1", + "order": "Example page 1" + }, + "editors/markdown/custom-components/readmore/example-2": { + "title": "Example page 2", + "intro": "This page is here to illustrate the use of the .", + "slug": "editors/markdown/custom-components/readmore/example-2", + "order": "Example page 2" + }, + "editors/markdown/custom-components/readmore/example-2/sub-example-2-1": { + "title": "Sub-example page 1", + "intro": "This page is here to illustrate the use of the .", + "slug": "editors/markdown/custom-components/readmore/example-2/sub-example-2-1", + "order": "Sub-example page 1" + }, + "editors/markdown/custom-components/readmore/example-2/sub-example-2-2": { + "title": "Sub-example page 2", + "intro": "This page is here to illustrate the use of the .", + "slug": "editors/markdown/custom-components/readmore/example-2/sub-example-2-2", + "order": "Sub-example page 2" + }, + "editors/markdown/custom-components/tip": { + "title": "Tip", + "order": "20Tip", + "intro": "I give great advice", + "slug": "editors/markdown/custom-components/tip" + }, + "editors/markdown/custom-components/warning": { + "title": "Warning", + "order": "30Warning", + "intro": "Ignore me at your own peril", + "slug": "editors/markdown/custom-components/warning" + }, + "editors/markdown/custom-components/youtube": { + "title": "YouTube", + "order": "50YouTube", + "intro": "This components will embed videos and playlists responsively.", + "slug": "editors/markdown/custom-components/youtube" + }, + "editors/markdown": { + "slug": "editors/markdown" + }, + "editors/markdown/headings": { + "title": "Headings", + "order": "40Headings", + "intro": "Prefix your line with a number of # characters to determine the header level.", + "slug": "editors/markdown/headings" + }, + "editors/markdown/images": { + "slug": "editors/markdown/images" + }, + "editors/markdown/italic-and-bold": { + "title": "Italics and bold", + "order": "30Italics and bold", + "intro": "You can make text or by wrapping it in 1 or 2 asterisk respectively:", + "slug": "editors/markdown/italic-and-bold" + }, + "editors/markdown/line-breaks": { + "slug": "editors/markdown/line-breaks" + }, + "editors/markdown/links": { + "title": "Links", + "order": "60Links", + "intro": "Links combine square brackets for the link text with round brackets for the destination.", + "slug": "editors/markdown/links" + }, + "editors/markdown/lists": { + "title": "Lists", + "order": "50Lists", + "intro": "To make a list, just do as you would in plain text:", + "slug": "editors/markdown/lists" + }, + "editors/markdown/tables": { + "title": "Tables", + "order": "70Tables", + "intro": "If you need them, you can create tables too, using a structure as shown below:", + "slug": "editors/markdown/tables" + }, + "editors/markdown/text-and-paragraphs": { + "title": "Text and paragraphs", + "order": "10Text and paragraphs", + "intro": "For the most part, you can just write as you would in any other format.", + "slug": "editors/markdown/text-and-paragraphs" + }, + "editors/style/colors": { + "slug": "editors/style/colors" + }, + "editors/style": { + "title": "Style guide", + "order": "910Style guide", + "intro": "FIXME_no_intro", + "slug": "editors/style" + }, + "editors/style/typography": { + "title": "Typography", + "intro": "FIXME_no_intro", + "slug": "editors/style/typography", + "order": "Typography" + }, + "editors/style/typography/fonts": { + "title": "Fonts", + "order": "10Fonts", + "intro": "We use three types of fonts:", + "slug": "editors/style/typography/fonts" + }, + "editors/style/typography/headings": { + "title": "Headings", + "order": "30Headings", + "intro": "Headings serve two different purposes:", + "slug": "editors/style/typography/headings" + }, + "editors/style/typography/text": { + "title": "Text and paragraphs", + "order": "20Text and paragraphs", + "intro": "Text is one of the most important things on the website, so we try to make it look good.", + "slug": "editors/style/typography/text" + }, + "guides/best-practices": { + "title": "Design guide", + "order": "300Design guide", + "for": "developers", + "icons": [ + "thumbup", + "pattern" + ], + "about": "There's often many different ways to do things. Learn about our conventions and best practices", + "goals": [ + "Re-using measurement and options names", + "Re-using CSS classes", + "Respecting draft settings", + "Using percentage options where possible", + "Using translation keys for text", + "Constructing paths counter-clockwise" + ], + "intro": "Here is a list of best practices when designing patterns:", + "slug": "guides/best-practices" + }, + "guides/best-practices/go-counter-clockwise": { + "slug": "guides/best-practices/go-counter-clockwise" + }, + "guides/best-practices/respect-draft-settings": { + "slug": "guides/best-practices/respect-draft-settings" + }, + "guides/best-practices/reuse-css-classes": { + "slug": "guides/best-practices/reuse-css-classes" + }, + "guides/best-practices/reuse-measurements": { + "slug": "guides/best-practices/reuse-measurements" + }, + "guides/best-practices/reuse-options": { + "title": "Re-use options", + "order": "20Re-use options", + "intro": "The same arguments for re-using measurements are also (somewhat) true for options.", + "slug": "guides/best-practices/reuse-options" + }, + "guides/best-practices/use-percentages": { + "title": "Use percentage options where possible", + "order": "50Use percentage options where possible", + "intro": "When designing patterns, you should refrain from using absolute values.", + "slug": "guides/best-practices/use-percentages" + }, + "guides/best-practices/use-translation-keys": { + "title": "Use translation keys, not text", + "order": "60Use translation keys, not text", + "intro": "Don't insert literal text in your patterns. Instead, insert a key that can then be translated.", + "slug": "guides/best-practices/use-translation-keys" + }, + "guides": { + "title": "Guides", + "order": "1020Guides", + "intro": "FIXME_no_intro", + "slug": "guides" + }, + "guides/patterns/config": { + "slug": "guides/patterns/config" + }, + "guides/patterns": { + "slug": "guides/patterns" + }, + "guides/patterns/parts": { + "slug": "guides/patterns/parts" + }, + "guides/patterns/paths": { + "slug": "guides/patterns/paths" + }, + "guides/patterns/pattern": { + "slug": "guides/patterns/pattern" + }, + "guides/patterns/points": { + "slug": "guides/patterns/points" + }, + "guides/patterns/snippets": { + "slug": "guides/patterns/snippets" + }, + "guides/patterns/store": { + "slug": "guides/patterns/store" + }, + "guides/plugins/conditionally-loading-build-time-plugins": { + "title": "Conditionally loading build-time plugins", + "order": "30Conditionally loading build-time plugins", + "intro": "You can choose to load your build-time plugin conditionally based on run-time data.", + "slug": "guides/plugins/conditionally-loading-build-time-plugins" + }, + "guides/plugins": { + "title": "Plugin guide", + "order": "400Plugin guide", + "icons": [ + "logo", + "plugin" + ], + "for": "developers", + "about": "This guide shows you everything you need to know to understand plugins in FreeSewing, and create your own.\n", + "goals": [ + "Know about build-time plugins vs run-time plugins", + "Understanding plugin structure", + "Hooks and how to use them", + "Using hooks without a plugin", + "Using macros" + ], + "intro": "Plugins allow you to extend FreeSewing.", + "slug": "guides/plugins" + }, + "guides/plugins/hooks": { + "title": "Hooks", + "order": "60Hooks", + "intro": "A is a lifecycle event. The available hooks are:", + "slug": "guides/plugins/hooks" + }, + "guides/plugins/loading-build-time-plugins": { + "slug": "guides/plugins/loading-build-time-plugins" + }, + "guides/plugins/loading-run-time-plugins": { + "slug": "guides/plugins/loading-run-time-plugins" + }, + "guides/plugins/macros": { + "title": "Macros", + "order": "90Macros", + "intro": "Plugin structure for macros is similar, with a few changes:", + "slug": "guides/plugins/macros" + }, + "guides/plugins/plugin-structure": { + "title": "Plugin structure", + "order": "50Plugin structure", + "intro": "Plugins can do two things:", + "slug": "guides/plugins/plugin-structure" + }, + "guides/plugins/types-of-plugins": { + "title": "Types of plugins", + "order": "10Types of plugins", + "intro": "Plugins come in two flavours:", + "slug": "guides/plugins/types-of-plugins" + }, + "guides/plugins/using-hooks-more-than-once": { + "slug": "guides/plugins/using-hooks-more-than-once" + }, + "guides/plugins/using-hooks-without-plugin": { + "slug": "guides/plugins/using-hooks-without-plugin" + }, + "guides/plugins/using-hooks": { + "slug": "guides/plugins/using-hooks" + }, + "guides/prerequisites/bezier-curves": { + "slug": "guides/prerequisites/bezier-curves" + }, + "guides/prerequisites/coordinate-system": { + "slug": "guides/prerequisites/coordinate-system" + }, + "guides/prerequisites": { + "slug": "guides/prerequisites" + }, + "guides/prerequisites/parametric-pattern-design": { + "slug": "guides/prerequisites/parametric-pattern-design" + }, + "guides/prerequisites/svg": { + "slug": "guides/prerequisites/svg" + }, + "guides/prerequisites/units": { + "slug": "guides/prerequisites/units" + }, + "howtos/code/accessing-measurements": { + "title": "Accessing measurements", + "for": "developers", + "about": "Shows you how to access user measurements from inside your pattern", + "intro": "Measurements are stored in pattern.settings.measurements .", + "slug": "howtos/code/accessing-measurements", + "order": "Accessing measurements" + }, + "howtos/code/accessing-options": { + "title": "Accessing user options", + "for": "developers", + "about": "Shows you how to access user options from inside your pattern", + "intro": "Options are stored in pattern.settings.options .", + "slug": "howtos/code/accessing-options", + "order": "Accessing user options" + }, + "howtos/code/adding-instructions": { + "slug": "howtos/code/adding-instructions" + }, + "howtos/code/adding-parts": { + "slug": "howtos/code/adding-parts" + }, + "howtos/code/adding-paths": { + "slug": "howtos/code/adding-paths" + }, + "howtos/code/adding-points": { + "slug": "howtos/code/adding-points" + }, + "howtos/code/adding-snippets": { + "slug": "howtos/code/adding-snippets" + }, + "howtos/code/adding-text": { + "title": "Adding text", + "for": "developers", + "about": "Shows you how to add text to your pattern", + "intro": "SVG is pretty great, but its text handling leaves much to be desired.", + "slug": "howtos/code/adding-text", + "order": "Adding text" + }, + "howtos/code/attributes": { + "slug": "howtos/code/attributes" + }, + "howtos/code/create-new-design": { + "slug": "howtos/code/create-new-design" + }, + "howtos/code/dependencies": { + "slug": "howtos/code/dependencies" + }, + "howtos/code/drawing-circles": { + "slug": "howtos/code/drawing-circles" + }, + "howtos/code": { + "title": "Common code challenges", + "intro": "Below is a list of examples of how to implement common tasks in code:", + "slug": "howtos/code", + "order": "Common code challenges" + }, + "howtos/code/extend-pattern": { + "title": "Create a new design based on an existing design", + "for": "developers", + "about": "Shows how to create a variation of a pre-existing design", + "intro": "To be able to extend existing patterns, you will have to access them on your local machine. There are two ways to do this:", + "slug": "howtos/code/extend-pattern", + "order": "Create a new design based on an existing design" + }, + "howtos/code/hide-paths": { + "title": "Hide paths from an inherited part", + "for": "developers", + "about": "When you inherit a part, it comes with a bunch of paths. Here'show to hide them", + "intro": "The example below is from Aaron which inherits from Brian.", + "slug": "howtos/code/hide-paths", + "order": "Hide paths from an inherited part" + }, + "howtos/code/inheritance": { + "slug": "howtos/code/inheritance" + }, + "howtos/code/inject": { + "slug": "howtos/code/inject" + }, + "howtos/code/macros": { + "slug": "howtos/code/macros" + }, + "howtos/code/remove-paths": { + "title": "Remove paths from an inherited part", + "for": "developers", + "about": "When you inherit a part, it comes with a bunch of paths. Here'show to remove them", + "intro": "FIXME_no_intro", + "slug": "howtos/code/remove-paths", + "order": "Remove paths from an inherited part" + }, + "howtos/code/shared-dimensions": { + "slug": "howtos/code/shared-dimensions" + }, + "howtos/code/shorthand": { + "title": "Using shorthand", + "for": "developers", + "about": "Shows you how to use our shorthand method and notation", + "intro": "The Part.shorthand() method will become your best friend.", + "slug": "howtos/code/shorthand", + "order": "Using shorthand" + }, + "howtos/code/store": { + "slug": "howtos/code/store" + }, + "howtos/code/storing-path-length": { + "slug": "howtos/code/storing-path-length" + }, + "howtos/design": { + "title": "Common design challenges", + "intro": "Below is a list of examples of how to implement common design challenges in code:", + "slug": "howtos/design", + "order": "Common design challenges" + }, + "howtos/design/fit-sleeve": { + "slug": "howtos/design/fit-sleeve" + }, + "howtos/design/seam-allowance": { + "slug": "howtos/design/seam-allowance" + }, + "howtos/design/slash-spread": { + "slug": "howtos/design/slash-spread" + }, + "howtos/design/sprinkle-snippets": { + "title": "Add several of the same snippets with the sprinkle macro", + "for": "developers", + "about": "Adding multiple snippets doesn't need to be a chore with this handy macro", + "intro": "Adding multiple snippets at the same time results in a lot of repetitive code.", + "slug": "howtos/design/sprinkle-snippets", + "order": "Add several of the same snippets with the sprinkle macro" + }, + "howtos/dev": { + "title": "Setting up your development environment", + "intro": "FIXME_no_intro", + "slug": "howtos/dev", + "order": "Setting up your development environment" + }, + "howtos/dev/freesewing-dev": { + "title": "Working on freesewing.dev", + "for": "developers", + "about": "Shows you how to setup your development environment to work on freesewing.dev, our website for developers", + "intro": "To work on freesewing.dev, checkout the repository:", + "slug": "howtos/dev/freesewing-dev", + "order": "Working on freesewing.dev" + }, + "howtos/dev/freesewing-org": { + "title": "Working on freesewing.org", + "for": "developers", + "about": "Shows you how to setup your development environment to work on freesewing.org, our website for makers", + "intro": "To work on freesewing.org, checkout the repository:", + "slug": "howtos/dev/freesewing-org", + "order": "Working on freesewing.org" + }, + "howtos": { + "title": "Howtos", + "order": "1020Howtos", + "intro": "FIXME_no_intro", + "slug": "howtos" + }, + "reference/api/attributes/add": { + "title": "add()", + "intro": "Adds value to the attribute identified by key .", + "slug": "reference/api/attributes/add", + "order": "add()" + }, + "reference/api/attributes/clone": { + "title": "clone()", + "intro": "Returns a new Attributes object that is a deep copy of this one.", + "slug": "reference/api/attributes/clone", + "order": "clone()" + }, + "reference/api/attributes": { + "title": "Attributes", + "order": "40Attributes", + "intro": "Attributes is an object that holds attributes for a variety of other objects.", + "slug": "reference/api/attributes" + }, + "reference/api/attributes/get": { + "title": "get()", + "intro": "Will return the value of attribute stored under key , or false if it's not set.", + "slug": "reference/api/attributes/get", + "order": "get()" + }, + "reference/api/attributes/getasarray": { + "title": "getAsArray()", + "intro": "Will return an array with the value of attribute stored under key , or false if it's not set.", + "slug": "reference/api/attributes/getasarray", + "order": "getAsArray()" + }, + "reference/api/attributes/remove": { + "title": "remove()", + "intro": "Removes the attribute values under key and returns the Attributes object.", + "slug": "reference/api/attributes/remove", + "order": "remove()" + }, + "reference/api/attributes/set": { + "title": "set()", + "intro": "Sets the attribute identified by key to value value .", + "slug": "reference/api/attributes/set", + "order": "set()" + }, + "reference/api/attributes/setifunset": { + "title": "setIfUnset()", + "intro": "Sets the attribute identified by key to value value but only if it's currently unset ( undefined ).", + "slug": "reference/api/attributes/setifunset", + "order": "setIfUnset()" + }, + "reference/api/design": { + "slug": "reference/api/design" + }, + "reference/api": { + "slug": "reference/api" + }, + "reference/api/part": { + "slug": "reference/api/part" + }, + "reference/api/part/getid": { + "slug": "reference/api/part/getid" + }, + "reference/api/part/raise/debug": { + "slug": "reference/api/part/raise/debug" + }, + "reference/api/part/raise": { + "slug": "reference/api/part/raise" + }, + "reference/api/part/raise/error": { + "slug": "reference/api/part/raise/error" + }, + "reference/api/part/raise/info": { + "slug": "reference/api/part/raise/info" + }, + "reference/api/part/raise/warning": { + "slug": "reference/api/part/raise/warning" + }, + "reference/api/part/shorthand": { + "slug": "reference/api/part/shorthand" + }, + "reference/api/part/units": { + "slug": "reference/api/part/units" + }, + "reference/api/path/_curve": { + "slug": "reference/api/path/_curve" + }, + "reference/api/path/attr": { + "title": "attr()", + "intro": "This Path.attr() method calls this.attributes.add() under the hood, but returns the Path object.", + "slug": "reference/api/path/attr", + "order": "attr()" + }, + "reference/api/path/clone": { + "title": "clone()", + "intro": "Returns a new Path that is a deep copy of this path.", + "slug": "reference/api/path/clone", + "order": "clone()" + }, + "reference/api/path/close": { + "title": "close()", + "intro": "Closes a path by drawing a straight line from the current position to the path's start.", + "slug": "reference/api/path/close", + "order": "close()" + }, + "reference/api/path/curve": { + "title": "curve()", + "intro": "Draws a cubic Bezier curve from the current position via two control points to a given endpoint.", + "slug": "reference/api/path/curve", + "order": "curve()" + }, + "reference/api/path/curve_": { + "slug": "reference/api/path/curve_" + }, + "reference/api/path/divide": { + "slug": "reference/api/path/divide" + }, + "reference/api/path/edge": { + "title": "edge()", + "intro": "Returns the Point object at the edge of the path you specify. Edge must be one of:", + "slug": "reference/api/path/edge", + "order": "edge()" + }, + "reference/api/path": { + "title": "Path", + "order": "30Path", + "intro": "A path represents an SVG path; The lines and curves on our pattern.", + "slug": "reference/api/path" + }, + "reference/api/path/end": { + "title": "end()", + "intro": "Returns the Point object at the end of the path.", + "slug": "reference/api/path/end", + "order": "end()" + }, + "reference/api/path/insop": { + "title": "insop()", + "intro": "Injects a Path into the noop with id id .", + "slug": "reference/api/path/insop", + "order": "insop()" + }, + "reference/api/path/intersects": { + "title": "intersects()", + "intro": "Returns the Point object(s) where the path intersects with a path you pass it.", + "slug": "reference/api/path/intersects", + "order": "intersects()" + }, + "reference/api/path/intersectsx": { + "title": "intersectsX()", + "intro": "Returns the Point object(s) where the path intersects with a given X-value.", + "slug": "reference/api/path/intersectsx", + "order": "intersectsX()" + }, + "reference/api/path/intersectsy": { + "title": "intersectsY()", + "intro": "Returns the Point object(s) where the path intersects with a given Y-value.", + "slug": "reference/api/path/intersectsy", + "order": "intersectsY()" + }, + "reference/api/path/join": { + "title": "join()", + "intro": "Joins this path with another path.", + "slug": "reference/api/path/join", + "order": "join()" + }, + "reference/api/path/length": { + "title": "length()", + "intro": "Returns the length of the path.", + "slug": "reference/api/path/length", + "order": "length()" + }, + "reference/api/path/line": { + "title": "line()", + "intro": "Draws a straight line from the current position to a given point.", + "slug": "reference/api/path/line", + "order": "line()" + }, + "reference/api/path/move": { + "title": "move()", + "intro": "Moves to a given point without drawing a line.", + "slug": "reference/api/path/move", + "order": "move()" + }, + "reference/api/path/noop": { + "title": "noop()", + "intro": "Adds a placeholder path opertion. A noop operation does nothing, but is intended to be replaced later with Path.insop() .", + "slug": "reference/api/path/noop", + "order": "noop()" + }, + "reference/api/path/offset": { + "title": "offset()", + "intro": "Returns a new Path that is offset by distance from the original path.", + "slug": "reference/api/path/offset", + "order": "offset()" + }, + "reference/api/path/reverse": { + "title": "reverse()", + "intro": "Returns a path that is the reversed version of this path. As in, start becomes end, and end becomes start.", + "slug": "reference/api/path/reverse", + "order": "reverse()" + }, + "reference/api/path/setrender": { + "slug": "reference/api/path/setrender" + }, + "reference/api/path/shiftalong": { + "title": "shiftAlong()", + "intro": "Returns a point that lies at distance travelled along the path.", + "slug": "reference/api/path/shiftalong", + "order": "shiftAlong()" + }, + "reference/api/path/shiftfractionalong": { + "title": "shiftFractionAlong()", + "intro": "Returns a point that lies at fraction of the length of the path travelled along the path.", + "slug": "reference/api/path/shiftfractionalong", + "order": "shiftFractionAlong()" + }, + "reference/api/path/split": { + "title": "split", + "intro": "Splits a path in two halves, on a point along that path that you pass it.", + "slug": "reference/api/path/split", + "order": "split" + }, + "reference/api/path/start": { + "title": "start()", + "intro": "Returns the Point object at the start of the path.", + "slug": "reference/api/path/start", + "order": "start()" + }, + "reference/api/path/translate": { + "title": "translate()", + "intro": "Returns a path with a translate transform applied.", + "slug": "reference/api/path/translate", + "order": "translate()" + }, + "reference/api/path/trim": { + "title": "trim()", + "intro": "Returns a new Path that is this path with overlapping parts removed.", + "slug": "reference/api/path/trim", + "order": "trim()" + }, + "reference/api/pattern/draft": { + "slug": "reference/api/pattern/draft" + }, + "reference/api/pattern": { + "slug": "reference/api/pattern" + }, + "reference/api/pattern/getrenderprops": { + "slug": "reference/api/pattern/getrenderprops" + }, + "reference/api/pattern/on": { + "slug": "reference/api/pattern/on" + }, + "reference/api/pattern/render": { + "slug": "reference/api/pattern/render" + }, + "reference/api/pattern/sample": { + "slug": "reference/api/pattern/sample" + }, + "reference/api/pattern/samplemeasurement": { + "slug": "reference/api/pattern/samplemeasurement" + }, + "reference/api/pattern/samplemodels": { + "slug": "reference/api/pattern/samplemodels" + }, + "reference/api/pattern/sampleoption": { + "slug": "reference/api/pattern/sampleoption" + }, + "reference/api/pattern/use": { + "slug": "reference/api/pattern/use" + }, + "reference/api/point/angle": { + "slug": "reference/api/point/angle" + }, + "reference/api/point/attr": { + "slug": "reference/api/point/attr" + }, + "reference/api/point/clone": { + "title": "Point.clone()", + "intro": "Returns a new Point with the same coordinates and attributes as the original point.", + "slug": "reference/api/point/clone", + "order": "Point.clone()" + }, + "reference/api/point/copy": { + "slug": "reference/api/point/copy" + }, + "reference/api/point/dist": { + "title": "Point.dist()", + "intro": "A point's dist() method returns the distance (in mm) between this point and the point you pass it.", + "slug": "reference/api/point/dist", + "order": "Point.dist()" + }, + "reference/api/point/dx": { + "title": "Point.dx()", + "intro": "A point's dx() method returns the delta (in mm) along the X-axis between this point and the point you pass it.", + "slug": "reference/api/point/dx", + "order": "Point.dx()" + }, + "reference/api/point/dy": { + "title": "Point.dy()", + "intro": "A point's dy() method returns the delta (in mm) along the Y-axis between this point and the point you pass it.", + "slug": "reference/api/point/dy", + "order": "Point.dy()" + }, + "reference/api/point": { + "title": "Point", + "order": "25Point", + "intro": "A Point object represents a point on a 2D plane with an X and Y axis.", + "slug": "reference/api/point" + }, + "reference/api/point/flipx": { + "slug": "reference/api/point/flipx" + }, + "reference/api/point/flipy": { + "slug": "reference/api/point/flipy" + }, + "reference/api/point/rotate": { + "slug": "reference/api/point/rotate" + }, + "reference/api/point/shift": { + "slug": "reference/api/point/shift" + }, + "reference/api/point/shiftfractiontowards": { + "slug": "reference/api/point/shiftfractiontowards" + }, + "reference/api/point/shiftoutwards": { + "title": "Point.shiftOutwards()", + "intro": "Returns a new Point that is shifted distance (mm) beyond the target in the direction of the target point.", + "slug": "reference/api/point/shiftoutwards", + "order": "Point.shiftOutwards()" + }, + "reference/api/point/shifttowards": { + "title": "Point.shiftTowards()", + "intro": "Returns a new Point that is shifted distance (mm) in the direction of the target .", + "slug": "reference/api/point/shifttowards", + "order": "Point.shiftTowards()" + }, + "reference/api/point/sitson": { + "title": "Point.sitsOn()", + "intro": "Returns true if this point has the same coordinates as the point you pass to it.", + "slug": "reference/api/point/sitson", + "order": "Point.sitsOn()" + }, + "reference/api/point/sitsroughlyon": { + "title": "Point.sitsRoughlyOn()", + "intro": "Returns true if this point has roughly (rounded to the nearest millimeter) the same coordinates as the one you pass to it.", + "slug": "reference/api/point/sitsroughlyon", + "order": "Point.sitsRoughlyOn()" + }, + "reference/api/point/translate": { + "title": "Point.translate()", + "intro": "Returns a new Point with a translate transform applied.", + "slug": "reference/api/point/translate", + "order": "Point.translate()" + }, + "reference/api/snippet/attr": { + "slug": "reference/api/snippet/attr" + }, + "reference/api/snippet/clone": { + "title": "clone()", + "intro": "Returns a new Snippets object that is a deep copy of this one.", + "slug": "reference/api/snippet/clone", + "order": "clone()" + }, + "reference/api/snippet": { + "slug": "reference/api/snippet" + }, + "reference/api/store": { + "slug": "reference/api/store" + }, + "reference/api/store/get": { + "title": "get()", + "intro": "Returnes the value stored under key .", + "slug": "reference/api/store/get", + "order": "get()" + }, + "reference/api/store/set": { + "title": "set()", + "intro": "Stores the value of value in the store under key key .", + "slug": "reference/api/store/set", + "order": "set()" + }, + "reference/api/store/setifunset": { + "slug": "reference/api/store/setifunset" + }, + "reference/api/svg/attributes": { + "title": "attributes", + "intro": "An Attributes instance that controls the attributes of the SVG tag.", + "slug": "reference/api/svg/attributes", + "order": "attributes" + }, + "reference/api/svg/defs": { + "slug": "reference/api/svg/defs" + }, + "reference/api/svg": { + "slug": "reference/api/svg" + }, + "reference/api/svg/head": { + "slug": "reference/api/svg/head" + }, + "reference/api/svg/layout": { + "slug": "reference/api/svg/layout" + }, + "reference/api/svg/pattern": { + "title": "pattern", + "intro": "A reference to the Pattern object .", + "slug": "reference/api/svg/pattern", + "order": "pattern" + }, + "reference/api/svg/prefix": { + "title": "prefix", + "intro": "A string that will be rendered before the opening SVG tag.", + "slug": "reference/api/svg/prefix", + "order": "prefix" + }, + "reference/api/svg/script": { + "title": "script", + "intro": "A string that will be rendered as the script section of the SVG document.", + "slug": "reference/api/svg/script", + "order": "script" + }, + "reference/api/svg/style": { + "title": "style", + "intro": "A string that will be rendered as the style section of the SVG document.", + "slug": "reference/api/svg/style", + "order": "style" + }, + "reference/api/svg/tail": { + "title": "tail", + "intro": "A string that closes both the group opened by Pattern.svg.head and the SVG tag.", + "slug": "reference/api/svg/tail", + "order": "tail" + }, + "reference/api/utils/beamintersectscircle": { + "title": "beamIntersectsCircle()", + "intro": "Finds the intersection between an endless line through points point1 and point2 and a circle with its center at point center and a radius of radius mm.", + "slug": "reference/api/utils/beamintersectscircle", + "order": "beamIntersectsCircle()" + }, + "reference/api/utils/beamintersectsx": { + "slug": "reference/api/utils/beamintersectsx" + }, + "reference/api/utils/beamintersectsy": { + "slug": "reference/api/utils/beamintersectsy" + }, + "reference/api/utils/beamsintersect": { + "slug": "reference/api/utils/beamsintersect" + }, + "reference/api/utils/circlesintersect": { + "title": "circlesIntersect()", + "intro": "Finds the intersections between two circles described by their center point and radius.", + "slug": "reference/api/utils/circlesintersect", + "order": "circlesIntersect()" + }, + "reference/api/utils/curveintersectsx": { + "title": "curveIntersectsX()", + "intro": "Finds the point(s) where a curve intersects a given X-value.", + "slug": "reference/api/utils/curveintersectsx", + "order": "curveIntersectsX()" + }, + "reference/api/utils/curveintersectsy": { + "title": "curveIntersectsY()", + "intro": "Finds the point(s) where a curve intersects a given Y-value.", + "slug": "reference/api/utils/curveintersectsy", + "order": "curveIntersectsY()" + }, + "reference/api/utils/curvesintersect": { + "title": "curvesIntersect()", + "intro": "Finds the intersections between two curves described by 4 points each.", + "slug": "reference/api/utils/curvesintersect", + "order": "curvesIntersect()" + }, + "reference/api/utils/deg2rad": { + "title": "deg2rad()", + "intro": "Returns the degrees you pass to it as radians.", + "slug": "reference/api/utils/deg2rad", + "order": "deg2rad()" + }, + "reference/api/utils": { + "title": "Utils", + "order": "95Utils", + "intro": "The Utils object provides the following utility methods to facilitate your work:", + "slug": "reference/api/utils" + }, + "reference/api/utils/lineintersectscircle": { + "title": "lineIntersectsCircle()", + "intro": "Finds the intersection between a line segment from point from to point to and a circle with its center at point center and a radius of radius mm.", + "slug": "reference/api/utils/lineintersectscircle", + "order": "lineIntersectsCircle()" + }, + "reference/api/utils/lineintersectscurve": { + "title": "lineIntersectsCurve()", + "intro": "Finds the intersection between a line segment from point from to point to and a curve described by points start , cp1 , cp2, and end`.", + "slug": "reference/api/utils/lineintersectscurve", + "order": "lineIntersectsCurve()" + }, + "reference/api/utils/linesintersect": { + "slug": "reference/api/utils/linesintersect" + }, + "reference/api/utils/pointonbeam": { + "slug": "reference/api/utils/pointonbeam" + }, + "reference/api/utils/pointoncurve": { + "title": "pointOnCurve()", + "intro": "Returns true if the point check lies on a curve described by points start , cp1 , cp2 , and end .", + "slug": "reference/api/utils/pointoncurve", + "order": "pointOnCurve()" + }, + "reference/api/utils/pointonline": { + "title": "pointOnLine()", + "intro": "Returns true if the point check lies on a line segment from point from to point to .", + "slug": "reference/api/utils/pointonline", + "order": "pointOnLine()" + }, + "reference/api/utils/rad2deg": { + "title": "rad2deg()", + "intro": "Returns the radians you pass to it as degrees.", + "slug": "reference/api/utils/rad2deg", + "order": "rad2deg()" + }, + "reference/api/utils/round": { + "title": "round()", + "intro": "Rounds a value to two decimals. For example:", + "slug": "reference/api/utils/round", + "order": "round()" + }, + "reference/api/utils/stretchtoscale": { + "title": "stretchToScale()", + "intro": "The way people measure stretch intuitively is different from the way we handle stretch in code.", + "slug": "reference/api/utils/stretchtoscale", + "order": "stretchToScale()" + }, + "reference/api/utils/units": { + "title": "units()", + "intro": "Converts the units value you pass it into a formatted string for the format you pass it.", + "slug": "reference/api/utils/units", + "order": "units()" + }, + "reference/backend/account": { + "slug": "reference/backend/account" + }, + "reference/backend": { + "title": "Backend API", + "for": "developers", + "about": "Documentation for our backend REST API", + "intro": "This documentation is outdated", + "slug": "reference/backend", + "order": "Backend API" + }, + "reference/backend/login": { + "slug": "reference/backend/login" + }, + "reference/backend/models": { + "title": "Models", + "intro": "On success:", + "slug": "reference/backend/models", + "order": "Models" + }, + "reference/backend/oauth": { + "title": "Oauth", + "intro": "On success:", + "slug": "reference/backend/oauth", + "order": "Oauth" + }, + "reference/backend/recipes": { + "title": "Recipes", + "intro": "On success:", + "slug": "reference/backend/recipes", + "order": "Recipes" + }, + "reference/backend/signup": { + "title": "Sign up", + "intro": "On success:", + "slug": "reference/backend/signup", + "order": "Sign up" + }, + "reference/backend/users": { + "title": "Users", + "intro": "On success:", + "slug": "reference/backend/users", + "order": "Users" + }, + "reference/config/dependencies": { + "title": "dependencies", + "intro": "An object of key - value pairs that controls the order in which pattern parts will get drafted.", + "slug": "reference/config/dependencies", + "order": "dependencies" + }, + "reference/config": { + "title": "Pattern configuration file", + "for": "developers", + "about": "Reference documentation for the pattern configuration file", + "intro": "A pattern configuration file exports a default object with the following properties:", + "slug": "reference/config", + "order": "Pattern configuration file" + }, + "reference/config/hide": { + "title": "hide", + "intro": "An array that lists pattern parts that should be hidden by default.", + "slug": "reference/config/hide", + "order": "hide" + }, + "reference/config/inject": { + "title": "inject", + "intro": "An object of key/value pairs of parts. The value part will be injected in the key part.", + "slug": "reference/config/inject", + "order": "inject" + }, + "reference/config/measurements": { + "title": "measurements", + "intro": "An array with the names of the measurements required to draft this pattern.", + "slug": "reference/config/measurements", + "order": "measurements" + }, + "reference/config/name": { + "title": "name", + "intro": "A string with the name of your pattern.", + "slug": "reference/config/name", + "order": "name" + }, + "reference/config/optionalmeasurements": { + "title": "optionalMeasurements", + "intro": "An array with the names of optional measurements that can be used to draft this pattern.", + "slug": "reference/config/optionalmeasurements", + "order": "optionalMeasurements" + }, + "reference/config/options/boolean": { + "title": "boolean", + "intro": "If your option is either true or false , or or or or , you can use a boolean:", + "slug": "reference/config/options/boolean", + "order": "boolean" + }, + "reference/config/options/constant": { + "title": "constant", + "intro": "If your option is a scalar value (like a string or a number), it will be treated as a constant:", + "slug": "reference/config/options/constant", + "order": "constant" + }, + "reference/config/options/counter": { + "slug": "reference/config/options/counter" + }, + "reference/config/options/degrees": { + "title": "degrees", + "intro": "For angles, use degrees.", + "slug": "reference/config/options/degrees", + "order": "degrees" + }, + "reference/config/options": { + "title": "options", + "intro": "Options come in 6 varities:", + "slug": "reference/config/options", + "order": "options" + }, + "reference/config/options/list": { + "title": "list", + "intro": "Use a list option when you want to offer an array of choices.", + "slug": "reference/config/options/list", + "order": "list" + }, + "reference/config/options/millimeter": { + "title": "millimeter", + "intro": "While we recommend using percentages where possible, sometimes that doesn't make sense.", + "slug": "reference/config/options/millimeter", + "order": "millimeter" + }, + "reference/config/options/percentage": { + "title": "percentage", + "intro": "Percentage options are the bread and butter of freesewing.", + "slug": "reference/config/options/percentage", + "order": "percentage" + }, + "reference/config/parts": { + "title": "parts", + "intro": "An array that lists your (additional) pattern parts. The name must be the key the pattern.parts object.", + "slug": "reference/config/parts", + "order": "parts" + }, + "reference/config/version": { + "title": "version", + "intro": "The version of your pattern.", + "slug": "reference/config/version", + "order": "version" + }, + "reference/designs": { + "title": "Designs", + "for": "contributors", + "about": "Complete list of FreeSewing designs that we've put out", + "intro": "We have a growing number of designs (sewing patterns).", + "slug": "reference/designs", + "order": "Designs" + }, + "reference": { + "title": "Reference", + "order": "1040Reference", + "intro": "FIXME_no_intro", + "slug": "reference" + }, + "reference/hooks": { + "title": "Hooks API", + "for": "developers", + "about": "Documents the available lifecycle hooks in Core and how to use them", + "intro": "A is a lifecycle event.", + "slug": "reference/hooks", + "order": "Hooks API" + }, + "reference/hooks/inserttext": { + "title": "insertText", + "intro": "The insertText hook is called when text is about to be inserted during rendering.", + "slug": "reference/hooks/inserttext", + "order": "insertText" + }, + "reference/hooks/postdraft": { + "title": "postDraft", + "intro": "The postDraft hook runs just after your pattern is drafted.", + "slug": "reference/hooks/postdraft", + "order": "postDraft" + }, + "reference/hooks/postrender": { + "title": "postRender", + "intro": "The postRender hook is triggered after the SVG is rendered.", + "slug": "reference/hooks/postrender", + "order": "postRender" + }, + "reference/hooks/postsample": { + "slug": "reference/hooks/postsample" + }, + "reference/hooks/predraft": { + "title": "preDraft", + "intro": "The preDraft hook runs just before your pattern is drafted.", + "slug": "reference/hooks/predraft", + "order": "preDraft" + }, + "reference/hooks/prerender": { + "title": "preRender", + "intro": "The preRender hook is triggered just before your pattern is rendered to SVG.", + "slug": "reference/hooks/prerender", + "order": "preRender" + }, + "reference/hooks/presample": { + "title": "preSample", + "intro": "The preSample hook runs just before your pattern is sampled.", + "slug": "reference/hooks/presample", + "order": "preSample" + }, + "reference/macros/bartack": { + "slug": "reference/macros/bartack" + }, + "reference/macros/bartackalong": { + "slug": "reference/macros/bartackalong" + }, + "reference/macros/bartackfractionalong": { + "slug": "reference/macros/bartackfractionalong" + }, + "reference/macros/cutonfold": { + "title": "cutonfold", + "intro": "The cutonfold macro adds a indicator to your pattern. It is provided by the cutonfold plugin .", + "slug": "reference/macros/cutonfold", + "order": "cutonfold" + }, + "reference/macros": { + "slug": "reference/macros" + }, + "reference/macros/flip": { + "title": "flip", + "intro": "The flip macro flips (mirrors) an entire part vertically around the Y-axis. It takes no arguments, and is provided by the flip plugin .", + "slug": "reference/macros/flip", + "order": "flip" + }, + "reference/macros/grainline": { + "title": "grainline", + "intro": "The grainline macro adds a indicator to your pattern. It is provided by the grainline plugin .", + "slug": "reference/macros/grainline", + "order": "grainline" + }, + "reference/macros/hd": { + "title": "hd", + "intro": "The hd macro adds a to your pattern. It is provided by the dimension plugin .", + "slug": "reference/macros/hd", + "order": "hd" + }, + "reference/macros/ld": { + "title": "ld", + "intro": "The ld macro adds a to your pattern. It is provided by the dimension plugin .", + "slug": "reference/macros/ld", + "order": "ld" + }, + "reference/macros/miniscale": { + "slug": "reference/macros/miniscale" + }, + "reference/macros/mirror": { + "slug": "reference/macros/mirror" + }, + "reference/macros/pd": { + "title": "pd", + "intro": "The pd macro adds a to your pattern, indicating the length of a path. It is provided by the dimension plugin .", + "slug": "reference/macros/pd", + "order": "pd" + }, + "reference/macros/rmad": { + "slug": "reference/macros/rmad" + }, + "reference/macros/rmd": { + "slug": "reference/macros/rmd" + }, + "reference/macros/round": { + "title": "round", + "intro": "The round macro rounds a corner. It is provided by the round plugin .", + "slug": "reference/macros/round", + "order": "round" + }, + "reference/macros/scalebox": { + "slug": "reference/macros/scalebox" + }, + "reference/macros/sprinkle": { + "title": "sprinkle", + "intro": "The sprinkle macro facilitates adding snippets to your pattern in bulk. It is by the sprinkle plugin .", + "slug": "reference/macros/sprinkle", + "order": "sprinkle" + }, + "reference/macros/title": { + "title": "title", + "intro": "The title macro adds a title to a pattern part. It is provided by the title plugin .", + "slug": "reference/macros/title", + "order": "title" + }, + "reference/macros/vd": { + "title": "vd", + "intro": "The vd macro adds a to your pattern. It is provided by the dimension plugin .", + "slug": "reference/macros/vd", + "order": "vd" + }, + "reference/packages/aaron": { + "title": "@freesewing/aaron", + "intro": "FIXME_no_intro", + "slug": "reference/packages/aaron", + "order": "@freesewing/aaron" + }, + "reference/packages/benjamin": { + "title": "@freesewing/benjamin", + "intro": "FIXME_no_intro", + "slug": "reference/packages/benjamin", + "order": "@freesewing/benjamin" + }, + "reference/packages/bent": { + "title": "@freesewing/bent", + "intro": "FIXME_no_intro", + "slug": "reference/packages/bent", + "order": "@freesewing/bent" + }, + "reference/packages/breanna": { + "title": "@freesewing/breanna", + "intro": "FIXME_no_intro", + "slug": "reference/packages/breanna", + "order": "@freesewing/breanna" + }, + "reference/packages/brian": { + "title": "@freesewing/brian", + "intro": "FIXME_no_intro", + "slug": "reference/packages/brian", + "order": "@freesewing/brian" + }, + "reference/packages/bruce": { + "title": "@freesewing/bruce", + "intro": "FIXME_no_intro", + "slug": "reference/packages/bruce", + "order": "@freesewing/bruce" + }, + "reference/packages/carlita": { + "title": "@freesewing/carlita", + "intro": "FIXME_no_intro", + "slug": "reference/packages/carlita", + "order": "@freesewing/carlita" + }, + "reference/packages/carlton": { + "title": "@freesewing/carlton", + "intro": "FIXME_no_intro", + "slug": "reference/packages/carlton", + "order": "@freesewing/carlton" + }, + "reference/packages/cathrin": { + "title": "@freesewing/cathrin", + "intro": "FIXME_no_intro", + "slug": "reference/packages/cathrin", + "order": "@freesewing/cathrin" + }, + "reference/packages/components/blockquote": { + "slug": "reference/packages/components/blockquote" + }, + "reference/packages/components/draft": { + "title": "Draft", + "intro": "The Draft component renders a FreeSewing pattern in the browser as SVG.", + "slug": "reference/packages/components/draft", + "order": "Draft" + }, + "reference/packages/components/draftconfgurator": { + "slug": "reference/packages/components/draftconfgurator" + }, + "reference/packages/components/emblem": { + "title": "Emblem", + "intro": "The Emblem component renders the freesewing word in the top-left of our navigation bar.", + "slug": "reference/packages/components/emblem", + "order": "Emblem" + }, + "reference/packages/components": { + "title": "@freesewing/components", + "intro": "This is a collection for React components for FreeSewing's websites:", + "slug": "reference/packages/components", + "order": "@freesewing/components" + }, + "reference/packages/components/example": { + "slug": "reference/packages/components/example" + }, + "reference/packages/components/footer": { + "title": "Footer", + "intro": "The Footer component is the shared footer we use on our websites.", + "slug": "reference/packages/components/footer", + "order": "Footer" + }, + "reference/packages/components/icon": { + "slug": "reference/packages/components/icon" + }, + "reference/packages/components/linedrawing": { + "title": "Linedrawing", + "intro": "The Linedrawing component renders linedrawings for the FreeSewing patterns.", + "slug": "reference/packages/components/linedrawing", + "order": "Linedrawing" + }, + "reference/packages/components/logo": { + "title": "Logo", + "intro": "The Logo component renders the FreeSewing logo.", + "slug": "reference/packages/components/logo", + "order": "Logo" + }, + "reference/packages/components/navbar": { + "title": "Navbar", + "intro": "The Navbar component is the shared navigation bar we use at the top of our websites.", + "slug": "reference/packages/components/navbar", + "order": "Navbar" + }, + "reference/packages/components/render": { + "slug": "reference/packages/components/render" + }, + "reference/packages/components/robot": { + "slug": "reference/packages/components/robot" + }, + "reference/packages/components/sampleconfigurator": { + "slug": "reference/packages/components/sampleconfigurator" + }, + "reference/packages/components/spinner": { + "title": "Spinner", + "intro": "The Spinner component renders a loading indicator with the FreeSewing logo.", + "slug": "reference/packages/components/spinner", + "order": "Spinner" + }, + "reference/packages/components/withgist": { + "slug": "reference/packages/components/withgist" + }, + "reference/packages/components/withlanguage": { + "slug": "reference/packages/components/withlanguage" + }, + "reference/packages/components/withstorage": { + "slug": "reference/packages/components/withstorage" + }, + "reference/packages/components/workbench": { + "slug": "reference/packages/components/workbench" + }, + "reference/packages/core": { + "title": "@freesewing/core", + "intro": "This is the main FreeSewing library.", + "slug": "reference/packages/core", + "order": "@freesewing/core" + }, + "reference/packages/create-freesewing-pattern": { + "title": "create-freesewing-pattern", + "intro": "This is an npm initializer package to jump-start your FreeSewing pattern designs with:", + "slug": "reference/packages/create-freesewing-pattern", + "order": "create-freesewing-pattern" + }, + "reference/packages/css-theme": { + "title": "@freesewing/css-theme", + "intro": "This is the CSS that's used on FreeSewing's websites.", + "slug": "reference/packages/css-theme", + "order": "@freesewing/css-theme" + }, + "reference/packages": { + "title": "Packages", + "for": "developers", + "about": "Complete list of all the packages we publish on NPM", + "intro": "FreeSewing publishes the following JavaScript packages on NPM :", + "slug": "reference/packages", + "order": "Packages" + }, + "reference/packages/examples": { + "title": "@freesewing/examples", + "intro": "This pattern holds examples used throughout the documentation. Examples like this one:", + "slug": "reference/packages/examples", + "order": "@freesewing/examples" + }, + "reference/packages/florence": { + "title": "@freesewing/florence", + "intro": "FIXME_no_intro", + "slug": "reference/packages/florence", + "order": "@freesewing/florence" + }, + "reference/packages/florent": { + "title": "@freesewing/florent", + "intro": "FIXME_no_intro", + "slug": "reference/packages/florent", + "order": "@freesewing/florent" + }, + "reference/packages/gatsby-remark-jargon": { + "title": "gatsby-remark-jargon", + "intro": "This is a Gatsby plugin that wraps our remark-jargon plugin for Remark .", + "slug": "reference/packages/gatsby-remark-jargon", + "order": "gatsby-remark-jargon" + }, + "reference/packages/holmes": { + "title": "@freesewing/holmes", + "intro": "FIXME_no_intro", + "slug": "reference/packages/holmes", + "order": "@freesewing/holmes" + }, + "reference/packages/huey": { + "title": "@freesewing/huey", + "intro": "FIXME_no_intro", + "slug": "reference/packages/huey", + "order": "@freesewing/huey" + }, + "reference/packages/hugo": { + "title": "@freesewing/hugo", + "intro": "FIXME_no_intro", + "slug": "reference/packages/hugo", + "order": "@freesewing/hugo" + }, + "reference/packages/i18n": { + "title": "@freesewing/i18n", + "intro": "These are the string translations for FreeSewing's websites and patterns.", + "slug": "reference/packages/i18n", + "order": "@freesewing/i18n" + }, + "reference/packages/jaeger": { + "title": "@freesewing/jaeger", + "intro": "FIXME_no_intro", + "slug": "reference/packages/jaeger", + "order": "@freesewing/jaeger" + }, + "reference/packages/models": { + "title": "@freesewing/models", + "intro": "This package provides a set of standard-sized measurements to test your patterns with.", + "slug": "reference/packages/models", + "order": "@freesewing/models" + }, + "reference/packages/mui-theme": { + "title": "@freesewing/mui-theme", + "intro": "This is the Material-ui theme that's used on FreeSewing's websites.", + "slug": "reference/packages/mui-theme", + "order": "@freesewing/mui-theme" + }, + "reference/packages/pattern-info": { + "title": "@freesewing/pattern-info", + "intro": "This package provides information about FreeSewing patterns.", + "slug": "reference/packages/pattern-info", + "order": "@freesewing/pattern-info" + }, + "reference/packages/penelope": { + "title": "@freesewing/penelope", + "intro": "FIXME_no_intro", + "slug": "reference/packages/penelope", + "order": "@freesewing/penelope" + }, + "reference/packages/plugin-bundle": { + "title": "@freesewing/plugin-bundle", + "intro": "FIXME_no_intro", + "slug": "reference/packages/plugin-bundle", + "order": "@freesewing/plugin-bundle" + }, + "reference/packages/plugin-bust": { + "title": "@freesewing/plugin-bust", + "intro": "FIXME_no_intro", + "slug": "reference/packages/plugin-bust", + "order": "@freesewing/plugin-bust" + }, + "reference/packages/plugin-buttons": { + "title": "@freesewing/plugin-buttons", + "intro": "FIXME_no_intro", + "slug": "reference/packages/plugin-buttons", + "order": "@freesewing/plugin-buttons" + }, + "reference/packages/plugin-cutonfold": { + "title": "@freesewing/plugin-cutonfold", + "intro": "FIXME_no_intro", + "slug": "reference/packages/plugin-cutonfold", + "order": "@freesewing/plugin-cutonfold" + }, + "reference/packages/plugin-dimension": { + "title": "@freesewing/plugin-dimension", + "intro": "FIXME_no_intro", + "slug": "reference/packages/plugin-dimension", + "order": "@freesewing/plugin-dimension" + }, + "reference/packages/plugin-flip": { + "title": "@freesewing/plugin-flip", + "intro": "FIXME_no_intro", + "slug": "reference/packages/plugin-flip", + "order": "@freesewing/plugin-flip" + }, + "reference/packages/plugin-gore": { + "title": "@freesewing/plugin-gore", + "intro": "FIXME_no_intro", + "slug": "reference/packages/plugin-gore", + "order": "@freesewing/plugin-gore" + }, + "reference/packages/plugin-grainline": { + "title": "@freesewing/plugin-grainline", + "intro": "FIXME_no_intro", + "slug": "reference/packages/plugin-grainline", + "order": "@freesewing/plugin-grainline" + }, + "reference/packages/plugin-i18n": { + "title": "@freesewing/plugin-i18n", + "intro": "FIXME_no_intro", + "slug": "reference/packages/plugin-i18n", + "order": "@freesewing/plugin-i18n" + }, + "reference/packages/plugin-logo": { + "title": "@freesewing/plugin-logo", + "intro": "FIXME_no_intro", + "slug": "reference/packages/plugin-logo", + "order": "@freesewing/plugin-logo" + }, + "reference/packages/plugin-round": { + "title": "@freesewing/plugin-round", + "intro": "FIXME_no_intro", + "slug": "reference/packages/plugin-round", + "order": "@freesewing/plugin-round" + }, + "reference/packages/plugin-scalebox": { + "title": "@freesewing/plugin-scalebox", + "intro": "FIXME_no_intro", + "slug": "reference/packages/plugin-scalebox", + "order": "@freesewing/plugin-scalebox" + }, + "reference/packages/plugin-sprinkle": { + "title": "@freesewing/plugin-sprinkle", + "intro": "FIXME_no_intro", + "slug": "reference/packages/plugin-sprinkle", + "order": "@freesewing/plugin-sprinkle" + }, + "reference/packages/plugin-svgattr": { + "title": "@freesewing/plugin-svgattr", + "intro": "FIXME_no_intro", + "slug": "reference/packages/plugin-svgattr", + "order": "@freesewing/plugin-svgattr" + }, + "reference/packages/plugin-theme": { + "title": "@freesewing/plugin-theme", + "intro": "FIXME_no_intro", + "slug": "reference/packages/plugin-theme", + "order": "@freesewing/plugin-theme" + }, + "reference/packages/plugin-title": { + "title": "@freesewing/plugin-title", + "intro": "FIXME_no_intro", + "slug": "reference/packages/plugin-title", + "order": "@freesewing/plugin-title" + }, + "reference/packages/prettier-config": { + "title": "@freesewing/prettier-config", + "intro": "This is FreeSewing's Prettier configuration, to be shared across projects.", + "slug": "reference/packages/prettier-config", + "order": "@freesewing/prettier-config" + }, + "reference/packages/remark-jargon": { + "title": "remark-jargon", + "intro": "This is a Remark plugin for jargon terms.", + "slug": "reference/packages/remark-jargon", + "order": "remark-jargon" + }, + "reference/packages/rendertest": { + "slug": "reference/packages/rendertest" + }, + "reference/packages/sandy": { + "title": "@freesewing/sandy", + "intro": "FIXME_no_intro", + "slug": "reference/packages/sandy", + "order": "@freesewing/sandy" + }, + "reference/packages/shin": { + "title": "@freesewing/shin", + "intro": "FIXME_no_intro", + "slug": "reference/packages/shin", + "order": "@freesewing/shin" + }, + "reference/packages/simon": { + "title": "@freesewing/simon", + "intro": "FIXME_no_intro", + "slug": "reference/packages/simon", + "order": "@freesewing/simon" + }, + "reference/packages/simone": { + "title": "@freesewing/simone", + "intro": "FIXME_no_intro", + "slug": "reference/packages/simone", + "order": "@freesewing/simone" + }, + "reference/packages/sven": { + "title": "@freesewing/sven", + "intro": "FIXME_no_intro", + "slug": "reference/packages/sven", + "order": "@freesewing/sven" + }, + "reference/packages/tamiko": { + "title": "@freesewing/tamiko", + "intro": "FIXME_no_intro", + "slug": "reference/packages/tamiko", + "order": "@freesewing/tamiko" + }, + "reference/packages/theo": { + "title": "@freesewing/theo", + "intro": "FIXME_no_intro", + "slug": "reference/packages/theo", + "order": "@freesewing/theo" + }, + "reference/packages/trayvon": { + "title": "@freesewing/trayvon", + "intro": "FIXME_no_intro", + "slug": "reference/packages/trayvon", + "order": "@freesewing/trayvon" + }, + "reference/packages/tutorial": { + "title": "@freesewing/tutorial", + "intro": "FIXME_no_intro", + "slug": "reference/packages/tutorial", + "order": "@freesewing/tutorial" + }, + "reference/packages/utils": { + "title": "@freesewing/utils", + "intro": "A collection of utilities shared between different FreeSewing projects.", + "slug": "reference/packages/utils", + "order": "@freesewing/utils" + }, + "reference/packages/wahid": { + "title": "@freesewing/wahid", + "intro": "FIXME_no_intro", + "slug": "reference/packages/wahid", + "order": "@freesewing/wahid" + }, + "reference/packages/waralee": { + "title": "@freesewing/waralee", + "intro": "FIXME_no_intro", + "slug": "reference/packages/waralee", + "order": "@freesewing/waralee" + }, + "reference/plugins/bartack": { + "title": "bartack", + "intro": " ", + "slug": "reference/plugins/bartack", + "order": "bartack" + }, + "reference/plugins/bundle": { + "title": "bundle", + "intro": " ", + "slug": "reference/plugins/bundle", + "order": "bundle" + }, + "reference/plugins/bust": { + "title": "bust", + "intro": " ", + "slug": "reference/plugins/bust", + "order": "bust" + }, + "reference/plugins/buttons": { + "title": "buttons", + "intro": " ", + "slug": "reference/plugins/buttons", + "order": "buttons" + }, + "reference/plugins/cutonfold": { + "title": "cutonfold", + "intro": " ", + "slug": "reference/plugins/cutonfold", + "order": "cutonfold" + }, + "reference/plugins/dimension": { + "title": "dimension", + "intro": " ", + "slug": "reference/plugins/dimension", + "order": "dimension" + }, + "reference/plugins": { + "title": "Plugins", + "for": "developers", + "about": "Complete list of all the plugins available for FreeSewing core", + "intro": "The following FreeSewing plugins are available:", + "slug": "reference/plugins", + "order": "Plugins" + }, + "reference/plugins/flip": { + "title": "flip", + "intro": " ", + "slug": "reference/plugins/flip", + "order": "flip" + }, + "reference/plugins/grainline": { + "title": "grainline", + "intro": " ", + "slug": "reference/plugins/grainline", + "order": "grainline" + }, + "reference/plugins/i18n": { + "title": "i18n", + "intro": " ", + "slug": "reference/plugins/i18n", + "order": "i18n" + }, + "reference/plugins/logo": { + "title": "logo", + "intro": " ", + "slug": "reference/plugins/logo", + "order": "logo" + }, + "reference/plugins/mirror": { + "title": "mirror", + "intro": " ", + "slug": "reference/plugins/mirror", + "order": "mirror" + }, + "reference/plugins/round": { + "title": "round", + "intro": " ", + "slug": "reference/plugins/round", + "order": "round" + }, + "reference/plugins/scalebox": { + "title": "scalebox", + "intro": " ", + "slug": "reference/plugins/scalebox", + "order": "scalebox" + }, + "reference/plugins/sprinkle": { + "title": "sprinkle", + "intro": " ", + "slug": "reference/plugins/sprinkle", + "order": "sprinkle" + }, + "reference/plugins/svgattr": { + "title": "svgattr", + "intro": " ", + "slug": "reference/plugins/svgattr", + "order": "svgattr" + }, + "reference/plugins/theme": { + "title": "theme", + "intro": " ", + "slug": "reference/plugins/theme", + "order": "theme" + }, + "reference/plugins/title": { + "title": "title", + "intro": " ", + "slug": "reference/plugins/title", + "order": "title" + }, + "reference/repos/backend": { + "title": "backend", + "intro": "See also: The backend API reference", + "slug": "reference/repos/backend", + "order": "backend" + }, + "reference/repos": { + "title": "Repositories", + "for": "contributors", + "about": "Complete list of all FreeSewing software repositories", + "intro": "FreeSewing uses the following repositories:", + "slug": "reference/repos", + "order": "Repositories" + }, + "reference/repos/freesewing.dev": { + "slug": "reference/repos/freesewing.dev" + }, + "reference/repos/freesewing.org": { + "title": "freesewing.org", + "intro": "This is the source code for FreeSewing.org , our main website.", + "slug": "reference/repos/freesewing.org", + "order": "freesewing.org" + }, + "reference/repos/freesewing": { + "slug": "reference/repos/freesewing" + }, + "reference/repos/svgtopdf": { + "slug": "reference/repos/svgtopdf" + }, + "reference/repos/tile": { + "title": "tile", + "intro": "This is a PostScript tiler, a command line utility written in C.", + "slug": "reference/repos/tile", + "order": "tile" + }, + "reference/settings/complete": { + "slug": "reference/settings/complete" + }, + "reference/settings/embed": { + "title": "embed", + "intro": "Set to true to make SVG output suitable for embedding in a web page.", + "slug": "reference/settings/embed", + "order": "embed" + }, + "reference/settings": { + "title": "Settings", + "for": "developers", + "about": "Documents all the settings your pattern can receive, including the pattern options, measurmeents, and design options", + "intro": "Settings are what the user passes to your pattern at run-time.", + "slug": "reference/settings", + "order": "Settings" + }, + "reference/settings/idprefix": { + "title": "idPrefix", + "intro": "Prefixes all IDs in the SVG with the string you pass it. (defaults to fs- ).", + "slug": "reference/settings/idprefix", + "order": "idPrefix" + }, + "reference/settings/layout": { + "slug": "reference/settings/layout" + }, + "reference/settings/locale": { + "title": "locale", + "intro": "A 2-letter language code that indicates what language the user wants.", + "slug": "reference/settings/locale", + "order": "locale" + }, + "reference/settings/margin": { + "title": "margin", + "intro": "Allows you to specify a part margin (in mm). The default is 2mm. Each part will have this margin applied. This means that:", + "slug": "reference/settings/margin", + "order": "margin" + }, + "reference/settings/measurements": { + "title": "measurements", + "intro": "The measurements to draft for. The pattern configuration lists all required measurements.", + "slug": "reference/settings/measurements", + "order": "measurements" + }, + "reference/settings/only": { + "title": "only", + "intro": "Allows you to specify one or more parts to draft/render, rather than the entire pattern.", + "slug": "reference/settings/only", + "order": "only" + }, + "reference/settings/options": { + "title": "options", + "intro": "The pattern options as specified in the pattern configuration.", + "slug": "reference/settings/options", + "order": "options" + }, + "reference/settings/paperless": { + "title": "paperless", + "intro": "Set this to true to draft a paperless pattern. The default is false .", + "slug": "reference/settings/paperless", + "order": "paperless" + }, + "reference/settings/sa": { + "title": "sa", + "intro": "The seam allowance in mm.", + "slug": "reference/settings/sa", + "order": "sa" + }, + "reference/settings/units": { + "title": "units", + "intro": "Either metric (the default) or imperial .", + "slug": "reference/settings/units", + "order": "units" + }, + "reference/snippets/bnotch": { + "slug": "reference/snippets/bnotch" + }, + "reference/snippets/button": { + "slug": "reference/snippets/button" + }, + "reference/snippets/buttonhole-end": { + "slug": "reference/snippets/buttonhole-end" + }, + "reference/snippets/buttonhole-start": { + "slug": "reference/snippets/buttonhole-start" + }, + "reference/snippets/buttonhole": { + "slug": "reference/snippets/buttonhole" + }, + "reference/snippets": { + "title": "Snippets", + "for": "developers", + "about": "Complete list of all the available snippets", + "intro": "Snippets are provided by plugins. Follow the links below for more info on and an example of a specific snippet:", + "slug": "reference/snippets", + "order": "Snippets" + }, + "reference/snippets/logo": { + "slug": "reference/snippets/logo" + }, + "reference/snippets/notch": { + "slug": "reference/snippets/notch" + }, + "reference/snippets/snap-socket": { + "title": "snap-socket", + "intro": "The snap-socket snippet is used to mark the socket part of a snap button.", + "slug": "reference/snippets/snap-socket", + "order": "snap-socket" + }, + "reference/snippets/snap-stud": { + "title": "snap-stud", + "intro": "The snap-stud snippet is used to mark the stud part of a snap button.", + "slug": "reference/snippets/snap-stud", + "order": "snap-stud" + }, + "translators/empty": { + "title": " ", + "intro": "This page was intentionally left blank.", + "slug": "translators/empty", + "order": " " + }, + "translators": { + "title": "For translators", + "order": "1140For translators", + "intro": "Freesewing is proudly multilingual. We currently support 5 languges:", + "slug": "translators" + }, + "tutorials": { + "title": "Tutorials", + "order": "1010Tutorials", + "intro": "FIXME_no_intro", + "slug": "tutorials" + }, + "tutorials/getting-started-linux/create-freesewing-pattern": { + "title": "Setting up the FreeSewing development environment", + "order": "40Setting up the FreeSewing development environment", + "intro": "FreeSewing provides a development environment to help you design and develop patterns.", + "slug": "tutorials/getting-started-linux/create-freesewing-pattern" + }, + "tutorials/getting-started-linux": { + "slug": "tutorials/getting-started-linux" + }, + "tutorials/getting-started-linux/installing-node": { + "title": "Installing Node", + "order": "20Installing Node", + "intro": "Now we will use nvm to install Node JS. Run the following command:", + "slug": "tutorials/getting-started-linux/installing-node" + }, + "tutorials/getting-started-linux/installing-nvm": { + "title": "Installing nvm", + "order": "10Installing nvm", + "intro": "FreeSewing is built with Node.js , a JavaScript runtime.", + "slug": "tutorials/getting-started-linux/installing-nvm" + }, + "tutorials/getting-started-linux/node-versions": { + "slug": "tutorials/getting-started-linux/node-versions" + }, + "tutorials/getting-started-linux/start-development-environment": { + "slug": "tutorials/getting-started-linux/start-development-environment" + }, + "tutorials/getting-started-mac/create-freesewing-pattern": { + "title": "Setting up the FreeSewing development environment", + "order": "50Setting up the FreeSewing development environment", + "intro": "FreeSewing provides a development environment to help you design and develop patterns.", + "slug": "tutorials/getting-started-mac/create-freesewing-pattern" + }, + "tutorials/getting-started-mac": { + "slug": "tutorials/getting-started-mac" + }, + "tutorials/getting-started-mac/installing-node": { + "title": "Installing Node", + "order": "30Installing Node", + "intro": "Now we will use nvm to install Node JS. Run the following command:", + "slug": "tutorials/getting-started-mac/installing-node" + }, + "tutorials/getting-started-mac/installing-nvm": { + "title": "Installing nvm", + "order": "20Installing nvm", + "intro": "FreeSewing is built with Node.js , a JavaScript runtime.", + "slug": "tutorials/getting-started-mac/installing-nvm" + }, + "tutorials/getting-started-mac/installing-xcode": { + "slug": "tutorials/getting-started-mac/installing-xcode" + }, + "tutorials/getting-started-mac/node-versions": { + "slug": "tutorials/getting-started-mac/node-versions" + }, + "tutorials/getting-started-mac/start-development-environment": { + "slug": "tutorials/getting-started-mac/start-development-environment" + }, + "tutorials/getting-started-windows": { + "title": "Getting started on Windows", + "order": "30Getting started on Windows", + "for": "developers", + "icons": [ + "start", + "windows" + ], + "about": "You'll learn how to setup Node JS on a Windows system, \nas well as initialize the FreeSewing development environment.\n", + "goals": [ + "Install Node JS using nvm", + "Chosing a Node version to use", + "Initializing the FreeSewing development environment", + "Starting the FreeSewing development environment" + ], + "intro": "If you already have a working WSL environment and VSCode Remote configured you can follow the getting started on Linux guide or skip ahead to Setting up the FreeSewing development environment (WSL) . If not, the following process is very similar but has some differences to avoid quirks specific to this environment.", + "slug": "tutorials/getting-started-windows" + }, + "tutorials/pattern-design/adding-measurements": { + "slug": "tutorials/pattern-design/adding-measurements" + }, + "tutorials/pattern-design/adding-options": { + "slug": "tutorials/pattern-design/adding-options" + }, + "tutorials/pattern-design/avoiding-overlap": { + "slug": "tutorials/pattern-design/avoiding-overlap" + }, + "tutorials/pattern-design/completing-the-neck-opening": { + "slug": "tutorials/pattern-design/completing-the-neck-opening" + }, + "tutorials/pattern-design/completing-your-pattern": { + "title": "Completing your pattern", + "order": "260Completing your pattern", + "intro": "When we started out, we said a good part boilerplate looks like this:", + "slug": "tutorials/pattern-design/completing-your-pattern" + }, + "tutorials/pattern-design/conclusion": { + "slug": "tutorials/pattern-design/conclusion" + }, + "tutorials/pattern-design/constructing-the-neck-opening": { + "slug": "tutorials/pattern-design/constructing-the-neck-opening" + }, + "tutorials/pattern-design/create-freesewing-pattern": { + "title": "Setting up the development environment", + "order": "100Setting up the development environment", + "intro": "If you already set up the FreeSewing development environment and created a pattern, you can use that pattern and skip these steps. You can move on to Your first part .", + "slug": "tutorials/pattern-design/create-freesewing-pattern" + }, + "tutorials/pattern-design/creating-the-closure": { + "slug": "tutorials/pattern-design/creating-the-closure" + }, + "tutorials/pattern-design/drawing-the-bib-outline": { + "title": "Drawing the bib outline", + "order": "190Drawing the bib outline", + "intro": "With our neck opening in place, let's draw the basic outline of our bib:", + "slug": "tutorials/pattern-design/drawing-the-bib-outline" + }, + "tutorials/pattern-design/drawing-the-straps": { + "slug": "tutorials/pattern-design/drawing-the-straps" + }, + "tutorials/pattern-design": { + "slug": "tutorials/pattern-design" + }, + "tutorials/pattern-design/fitting-the-neck-opening": { + "title": "Fitting the neck opening", + "order": "170Fitting the neck opening", + "intro": "Here's how we'll make sure the neck opening is :", + "slug": "tutorials/pattern-design/fitting-the-neck-opening" + }, + "tutorials/pattern-design/paperless-bib": { + "title": "Making your pattern paperless", + "order": "270Making your pattern paperless", + "intro": "Users can request paperless patterns by setting the paperless setting to true .", + "slug": "tutorials/pattern-design/paperless-bib" + }, + "tutorials/pattern-design/part-structure": { + "slug": "tutorials/pattern-design/part-structure" + }, + "tutorials/pattern-design/rounding-the-corners": { + "title": "Rounding the corners", + "order": "240Rounding the corners", + "intro": "We already know how to round corners, let the round macro do it:", + "slug": "tutorials/pattern-design/rounding-the-corners" + }, + "tutorials/pattern-design/shaping-the-straps": { + "slug": "tutorials/pattern-design/shaping-the-straps" + }, + "tutorials/pattern-design/testing-your-pattern": { + "slug": "tutorials/pattern-design/testing-your-pattern" + }, + "tutorials/pattern-design/your-first-part": { + "title": "Your first part", + "order": "120Your first part", + "intro": "Much like garments themselves, patterns are made up of .", + "slug": "tutorials/pattern-design/your-first-part" + } +} \ No newline at end of file diff --git a/packages/freesewing.dev/prebuild/mdx.js b/packages/freesewing.dev/prebuild/mdx.js new file mode 100644 index 00000000000..cf3d722774e --- /dev/null +++ b/packages/freesewing.dev/prebuild/mdx.js @@ -0,0 +1,3 @@ +import en from './mdx.en.js' + +export default { en } diff --git a/packages/freesewing.dev/prebuild/strapi.blog.en.js b/packages/freesewing.dev/prebuild/strapi.blog.en.js new file mode 100644 index 00000000000..2f23fd564cb --- /dev/null +++ b/packages/freesewing.dev/prebuild/strapi.blog.en.js @@ -0,0 +1,1991 @@ +export const posts = { + "blog/welcome-to-our-dev-blog": { + "dev": true, + "localizations": [], + "_id": "60eaceb44f0f4957dbc9d64b", + "title": "Welcome to FreeSewing's development blog", + "slug": "welcome-to-our-dev-blog", + "date": "2021-07-04", + "linktitle": "Welcome to our dev blog", + "caption": "Brought to you by a lot of coffee - Photo by Kaboompics.com via Pexels", + "body": "The internet is plastered with abandoned or dormant blogs, so starting another one is perhaps madness, but hear me out:\n\n- I'm working on some new stuff, and I'd like to talk about it so people can weigh in with their opinions and feedback\n- I am often reluctant to write on the *main* FreeSewing blog about things that are way too technical for the average visitor\n- I have some wild plans for how to handle our blog content, and I need a place to kick the tires\n\nSo, without much fanfare, I am launching this blog. Here we go.", + "published_at": "2021-07-11T12:29:54.663Z", + "createdAt": "2021-07-11T10:57:56.433Z", + "updatedAt": "2021-07-23T15:05:17.024Z", + "__v": 0, + "author": { + "localizations": [], + "_id": "60eae49cdb32b45d5c4d6d93", + "about": "Joost is FreeSewing's maintainer. As an introvert, he enjoys making clothes and shoes since you don't have to leave the house to do so 🙈\n\nYou can follow him as [j__st on Twitter](https://twitter.com/j__st) or [joostdecock on Github](https://github.com/joostdecock).", + "name": "joost", + "displayname": "Joost De Cock", + "createdAt": "2021-07-11T12:31:24.779Z", + "updatedAt": "2021-09-19T14:23:43.748Z", + "__v": 0, + "picture": { + "_id": "6134ca45cd9dc073cc08db6a", + "name": "tumblr_ok2vhiYCDh1qbhhcgo1_1280.jpg", + "alternativeText": "Joost De Cock", + "caption": "Joost De Cock", + "hash": "tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e", + "ext": ".jpg", + "mime": "image/jpeg", + "size": 76.85, + "width": 1080, + "height": 810, + "url": "/uploads/tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e.jpg", + "formats": { + "thumbnail": { + "name": "thumbnail_tumblr_ok2vhiYCDh1qbhhcgo1_1280.jpg", + "hash": "thumbnail_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 208, + "height": 156, + "size": 6.22, + "path": null, + "url": "/uploads/thumbnail_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e.jpg" + }, + "large": { + "name": "large_tumblr_ok2vhiYCDh1qbhhcgo1_1280.jpg", + "hash": "large_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 1000, + "height": 750, + "size": 85.96, + "path": null, + "url": "/uploads/large_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e.jpg" + }, + "medium": { + "name": "medium_tumblr_ok2vhiYCDh1qbhhcgo1_1280.jpg", + "hash": "medium_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 600, + "height": 450, + "size": 36, + "path": null, + "url": "/uploads/medium_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e.jpg" + }, + "small": { + "name": "small_tumblr_ok2vhiYCDh1qbhhcgo1_1280.jpg", + "hash": "small_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 400, + "height": 300, + "size": 17.49, + "path": null, + "url": "/uploads/small_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e.jpg" + }, + "xsmall": { + "name": "xsmall_tumblr_ok2vhiYCDh1qbhhcgo1_1280.jpg", + "hash": "xsmall_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 200, + "height": 150, + "size": 5.99, + "path": null, + "url": "/uploads/xsmall_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e.jpg" + } + }, + "provider": "local", + "related": [ + "60eae49cdb32b45d5c4d6d93", + "60fade591972003530e03b39", + "60fadf5e19815b356b32781e" + ], + "createdAt": "2021-09-05T13:46:45.753Z", + "updatedAt": "2021-09-19T14:24:00.480Z", + "__v": 0, + "id": "6134ca45cd9dc073cc08db6a" + }, + "locale": "en", + "slug": "joostdecock", + "id": "60eae49cdb32b45d5c4d6d93" + }, + "image": { + "_id": "60eace684f0f4957dbc9d64a", + "name": "pexels-kaboompics-com-6347.jpg", + "alternativeText": "", + "caption": "", + "hash": "pexels_kaboompics_com_6347_2e9a6ffd54", + "ext": ".jpg", + "mime": "image/jpeg", + "size": 210.66, + "width": 1920, + "height": 1280, + "url": "/uploads/pexels_kaboompics_com_6347_2e9a6ffd54.jpg", + "formats": { + "thumbnail": { + "name": "thumbnail_pexels-kaboompics-com-6347.jpg", + "hash": "thumbnail_pexels_kaboompics_com_6347_2e9a6ffd54", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 234, + "height": 156, + "size": 4.95, + "path": null, + "url": "/uploads/thumbnail_pexels_kaboompics_com_6347_2e9a6ffd54.jpg" + }, + "large": { + "name": "large_pexels-kaboompics-com-6347.jpg", + "hash": "large_pexels_kaboompics_com_6347_2e9a6ffd54", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 1000, + "height": 667, + "size": 50.35, + "path": null, + "url": "/uploads/large_pexels_kaboompics_com_6347_2e9a6ffd54.jpg" + }, + "medium": { + "name": "medium_pexels-kaboompics-com-6347.jpg", + "hash": "medium_pexels_kaboompics_com_6347_2e9a6ffd54", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 750, + "height": 500, + "size": 28.35, + "path": null, + "url": "/uploads/medium_pexels_kaboompics_com_6347_2e9a6ffd54.jpg" + }, + "small": { + "name": "small_pexels-kaboompics-com-6347.jpg", + "hash": "small_pexels_kaboompics_com_6347_2e9a6ffd54", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 500, + "height": 333, + "size": 14.48, + "path": null, + "url": "/uploads/small_pexels_kaboompics_com_6347_2e9a6ffd54.jpg" + } + }, + "provider": "local", + "related": [ + "60eaceb44f0f4957dbc9d64b" + ], + "createdAt": "2021-07-11T10:56:40.376Z", + "updatedAt": "2021-07-11T10:57:56.439Z", + "__v": 0, + "id": "60eace684f0f4957dbc9d64a" + }, + "site": "60ead51670e8d35a6d089fa0", + "locale": "en", + "id": "60eaceb44f0f4957dbc9d64b" + }, + "blog/project-2022": { + "dev": true, + "localizations": [], + "_id": "60eb151cdb32b45d5c4d6d96", + "body": "During [my summer break](https://github.com/freesewing/freesewing/discussions/1139) I wanted to look into migrating the FreeSewing websites (freesewing.org and freesewing.dev) from [GatbsyJS](https://gatsbyjs.org) to [NextJS](https://nextjs.org).\n\nI've been [frustrated with Gatsby](https://twitter.com/j__st/status/1398987583644966912) for a while now, and I feel its complexity is a large hurdle for people to get more hands-on with our websites.\n\nMy main gripe is with Gatsby's build process. You essentially have to maintain two different projects: The website itself as it runs. And the infamous `gatbsy-node.js` that outlines everything required to build the site.\n\nIn comparison, NextJS handles this much better (IMHO) with their `getStaticProps` and `getStaticPaths` methods. Because they are defined at the page level, we can keep break up the complexity for an entire site build per page.\n\nI am going to start with the freesewing.dev website, and if that goes well, I'll have a look at (the much more complex) freesewing.org site.\n\nTo manage expectations, I'm calling this the **2022 project** because frankly, if this gets done by the end of the year, I'd be happy.\n\nIf you want to follow along, check out [my fork of the FreeSewing monorepo](https://github.com/joostdecock/freesewing) or [come hang out with us on Discord](https://discord.freesewing.org/) in the `#project-2022` temporary channel.", + "caption": "A person working in construction - Picture by by Anamul Rezwan via Pexels", + "slug": "project-2022", + "date": "2021-07-05", + "title": "Project 2022: Giving FreeSewing a new face", + "linktitle": "Announcing projet 2022", + "published_at": "2021-07-11T15:59:35.454Z", + "createdAt": "2021-07-11T15:58:20.218Z", + "updatedAt": "2021-07-23T15:05:11.967Z", + "__v": 0, + "author": { + "localizations": [], + "_id": "60eae49cdb32b45d5c4d6d93", + "about": "Joost is FreeSewing's maintainer. As an introvert, he enjoys making clothes and shoes since you don't have to leave the house to do so 🙈\n\nYou can follow him as [j__st on Twitter](https://twitter.com/j__st) or [joostdecock on Github](https://github.com/joostdecock).", + "name": "joost", + "displayname": "Joost De Cock", + "createdAt": "2021-07-11T12:31:24.779Z", + "updatedAt": "2021-09-19T14:23:43.748Z", + "__v": 0, + "picture": { + "_id": "6134ca45cd9dc073cc08db6a", + "name": "tumblr_ok2vhiYCDh1qbhhcgo1_1280.jpg", + "alternativeText": "Joost De Cock", + "caption": "Joost De Cock", + "hash": "tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e", + "ext": ".jpg", + "mime": "image/jpeg", + "size": 76.85, + "width": 1080, + "height": 810, + "url": "/uploads/tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e.jpg", + "formats": { + "thumbnail": { + "name": "thumbnail_tumblr_ok2vhiYCDh1qbhhcgo1_1280.jpg", + "hash": "thumbnail_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 208, + "height": 156, + "size": 6.22, + "path": null, + "url": "/uploads/thumbnail_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e.jpg" + }, + "large": { + "name": "large_tumblr_ok2vhiYCDh1qbhhcgo1_1280.jpg", + "hash": "large_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 1000, + "height": 750, + "size": 85.96, + "path": null, + "url": "/uploads/large_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e.jpg" + }, + "medium": { + "name": "medium_tumblr_ok2vhiYCDh1qbhhcgo1_1280.jpg", + "hash": "medium_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 600, + "height": 450, + "size": 36, + "path": null, + "url": "/uploads/medium_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e.jpg" + }, + "small": { + "name": "small_tumblr_ok2vhiYCDh1qbhhcgo1_1280.jpg", + "hash": "small_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 400, + "height": 300, + "size": 17.49, + "path": null, + "url": "/uploads/small_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e.jpg" + }, + "xsmall": { + "name": "xsmall_tumblr_ok2vhiYCDh1qbhhcgo1_1280.jpg", + "hash": "xsmall_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 200, + "height": 150, + "size": 5.99, + "path": null, + "url": "/uploads/xsmall_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e.jpg" + } + }, + "provider": "local", + "related": [ + "60eae49cdb32b45d5c4d6d93", + "60fade591972003530e03b39", + "60fadf5e19815b356b32781e" + ], + "createdAt": "2021-09-05T13:46:45.753Z", + "updatedAt": "2021-09-19T14:24:00.480Z", + "__v": 0, + "id": "6134ca45cd9dc073cc08db6a" + }, + "locale": "en", + "slug": "joostdecock", + "id": "60eae49cdb32b45d5c4d6d93" + }, + "image": { + "_id": "60eb14eedb32b45d5c4d6d95", + "name": "pexels-anamul-rezwan-1216544.jpg", + "alternativeText": "", + "caption": "", + "hash": "pexels_anamul_rezwan_1216544_05424140f6", + "ext": ".jpg", + "mime": "image/jpeg", + "size": 196.29, + "width": 1920, + "height": 1280, + "url": "/uploads/pexels_anamul_rezwan_1216544_05424140f6.jpg", + "formats": { + "thumbnail": { + "name": "thumbnail_pexels-anamul-rezwan-1216544.jpg", + "hash": "thumbnail_pexels_anamul_rezwan_1216544_05424140f6", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 234, + "height": 156, + "size": 7.19, + "path": null, + "url": "/uploads/thumbnail_pexels_anamul_rezwan_1216544_05424140f6.jpg" + }, + "large": { + "name": "large_pexels-anamul-rezwan-1216544.jpg", + "hash": "large_pexels_anamul_rezwan_1216544_05424140f6", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 1000, + "height": 667, + "size": 67.52, + "path": null, + "url": "/uploads/large_pexels_anamul_rezwan_1216544_05424140f6.jpg" + }, + "medium": { + "name": "medium_pexels-anamul-rezwan-1216544.jpg", + "hash": "medium_pexels_anamul_rezwan_1216544_05424140f6", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 750, + "height": 500, + "size": 43.01, + "path": null, + "url": "/uploads/medium_pexels_anamul_rezwan_1216544_05424140f6.jpg" + }, + "small": { + "name": "small_pexels-anamul-rezwan-1216544.jpg", + "hash": "small_pexels_anamul_rezwan_1216544_05424140f6", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 500, + "height": 333, + "size": 23.31, + "path": null, + "url": "/uploads/small_pexels_anamul_rezwan_1216544_05424140f6.jpg" + } + }, + "provider": "local", + "related": [ + "60eb151cdb32b45d5c4d6d96" + ], + "createdAt": "2021-07-11T15:57:34.488Z", + "updatedAt": "2021-07-11T15:58:20.222Z", + "__v": 0, + "id": "60eb14eedb32b45d5c4d6d95" + }, + "site": "60ead51670e8d35a6d089fa0", + "locale": "en", + "id": "60eb151cdb32b45d5c4d6d96" + }, + "blog/tailwind-css-project-2022": { + "dev": true, + "localizations": [], + "_id": "60eb1836db32b45d5c4d6d99", + "body": "Building a website that is supposed to do something essentially boils down to two things:\n\n - Make it work\n - Make it pretty\n\nMaking it work is a challenge of its own, but it's making things pretty that is the biggest time-sink.\n\nIt can be fun to take a deep-dive into CSS and tweak every little aspect of how things look. But you need an ocean of discipline (or perhaps a good design system) to make sure things are consistent.\n\nFor a while now, there's a new kid on the block that promises to help with taming this CSS mess: [Tailwind CSS](https://tailwindcss.com/).\n\nThere's a lot of people who love it, there's some haters too. I kicked the tires a bit and once you get used to the declarative approach, I find it to be rather pleasant to work with.\n\nSo, I am going to use Tailwind to handle the style for [project 2022](/blog/project-2022). I am hopeful that this will also lower the barrier for contributors to go ahead and make things pretty. Because things can always be prettier.", + "caption": "Pretty colors - Picture by Suleyman Seykan via Pexels", + "slug": "tailwind-css-project-2022", + "date": "2021-07-06", + "title": "Taming CSS complexity with TailwindCSS", + "linktitle": "Tailwind CSS in project 2022", + "published_at": "2021-07-11T16:11:54.384Z", + "createdAt": "2021-07-11T16:11:34.563Z", + "updatedAt": "2021-07-23T15:04:59.003Z", + "__v": 0, + "author": { + "localizations": [], + "_id": "60eae49cdb32b45d5c4d6d93", + "about": "Joost is FreeSewing's maintainer. As an introvert, he enjoys making clothes and shoes since you don't have to leave the house to do so 🙈\n\nYou can follow him as [j__st on Twitter](https://twitter.com/j__st) or [joostdecock on Github](https://github.com/joostdecock).", + "name": "joost", + "displayname": "Joost De Cock", + "createdAt": "2021-07-11T12:31:24.779Z", + "updatedAt": "2021-09-19T14:23:43.748Z", + "__v": 0, + "picture": { + "_id": "6134ca45cd9dc073cc08db6a", + "name": "tumblr_ok2vhiYCDh1qbhhcgo1_1280.jpg", + "alternativeText": "Joost De Cock", + "caption": "Joost De Cock", + "hash": "tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e", + "ext": ".jpg", + "mime": "image/jpeg", + "size": 76.85, + "width": 1080, + "height": 810, + "url": "/uploads/tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e.jpg", + "formats": { + "thumbnail": { + "name": "thumbnail_tumblr_ok2vhiYCDh1qbhhcgo1_1280.jpg", + "hash": "thumbnail_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 208, + "height": 156, + "size": 6.22, + "path": null, + "url": "/uploads/thumbnail_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e.jpg" + }, + "large": { + "name": "large_tumblr_ok2vhiYCDh1qbhhcgo1_1280.jpg", + "hash": "large_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 1000, + "height": 750, + "size": 85.96, + "path": null, + "url": "/uploads/large_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e.jpg" + }, + "medium": { + "name": "medium_tumblr_ok2vhiYCDh1qbhhcgo1_1280.jpg", + "hash": "medium_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 600, + "height": 450, + "size": 36, + "path": null, + "url": "/uploads/medium_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e.jpg" + }, + "small": { + "name": "small_tumblr_ok2vhiYCDh1qbhhcgo1_1280.jpg", + "hash": "small_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 400, + "height": 300, + "size": 17.49, + "path": null, + "url": "/uploads/small_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e.jpg" + }, + "xsmall": { + "name": "xsmall_tumblr_ok2vhiYCDh1qbhhcgo1_1280.jpg", + "hash": "xsmall_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 200, + "height": 150, + "size": 5.99, + "path": null, + "url": "/uploads/xsmall_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e.jpg" + } + }, + "provider": "local", + "related": [ + "60eae49cdb32b45d5c4d6d93", + "60fade591972003530e03b39", + "60fadf5e19815b356b32781e" + ], + "createdAt": "2021-09-05T13:46:45.753Z", + "updatedAt": "2021-09-19T14:24:00.480Z", + "__v": 0, + "id": "6134ca45cd9dc073cc08db6a" + }, + "locale": "en", + "slug": "joostdecock", + "id": "60eae49cdb32b45d5c4d6d93" + }, + "image": { + "_id": "60eb1815db32b45d5c4d6d98", + "name": "pexels-suleyman-seykan-6068893.jpg", + "alternativeText": "", + "caption": "", + "hash": "pexels_suleyman_seykan_6068893_2130d0ee2c", + "ext": ".jpg", + "mime": "image/jpeg", + "size": 492.08, + "width": 1920, + "height": 1280, + "url": "/uploads/pexels_suleyman_seykan_6068893_2130d0ee2c.jpg", + "formats": { + "thumbnail": { + "name": "thumbnail_pexels-suleyman-seykan-6068893.jpg", + "hash": "thumbnail_pexels_suleyman_seykan_6068893_2130d0ee2c", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 234, + "height": 156, + "size": 14.25, + "path": null, + "url": "/uploads/thumbnail_pexels_suleyman_seykan_6068893_2130d0ee2c.jpg" + }, + "large": { + "name": "large_pexels-suleyman-seykan-6068893.jpg", + "hash": "large_pexels_suleyman_seykan_6068893_2130d0ee2c", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 1000, + "height": 667, + "size": 167.88, + "path": null, + "url": "/uploads/large_pexels_suleyman_seykan_6068893_2130d0ee2c.jpg" + }, + "medium": { + "name": "medium_pexels-suleyman-seykan-6068893.jpg", + "hash": "medium_pexels_suleyman_seykan_6068893_2130d0ee2c", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 750, + "height": 500, + "size": 103.55, + "path": null, + "url": "/uploads/medium_pexels_suleyman_seykan_6068893_2130d0ee2c.jpg" + }, + "small": { + "name": "small_pexels-suleyman-seykan-6068893.jpg", + "hash": "small_pexels_suleyman_seykan_6068893_2130d0ee2c", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 500, + "height": 333, + "size": 53.76, + "path": null, + "url": "/uploads/small_pexels_suleyman_seykan_6068893_2130d0ee2c.jpg" + } + }, + "provider": "local", + "related": [ + "60eb1836db32b45d5c4d6d99" + ], + "createdAt": "2021-07-11T16:11:01.674Z", + "updatedAt": "2021-07-11T16:11:34.567Z", + "__v": 0, + "id": "60eb1815db32b45d5c4d6d98" + }, + "site": "60ead51670e8d35a6d089fa0", + "locale": "en", + "id": "60eb1836db32b45d5c4d6d99" + }, + "blog/daisyui-components-themes": { + "dev": true, + "localizations": [], + "_id": "60eb1d41db32b45d5c4d6d9c", + "body": "Since we're changing pretty much everything in [project 2022](/blog/project-2022) anyway, we might as well try to reduce our bundle size by removing our dependency on [Material UI](https://material-ui.com/), the React component framework that implements Google's material design.\n\nMaterialUI is a large project, and it makes up a significant portion of the so-called *bundle size*, the amount of Javascript code we ship to the browser.\n\nObviously, for that size, MaterialUI brings a lot to the table, and it remains to be seen whether we can build everything without it.\n\nStill, I want to try, and I've got a secret little helper in the form of [DaisyUI](https://daisyui.com/).\n\nBefore you think that we're swapping out one component library for another, DaisyUI works with TailwindCSS and the components it provides are CSS only. So no extra Javascript to ship 🎉\n\n## Can I haz themes?\n\nAnother thing that DaisyUI brings to the table is support for themeing. While TailwindCSS recently added support for dark mode, theming in DaisyUI is more flexible, allowing us to not only have dark and light versions of the site, but also a bunch of other themes.\n\nFor someone who gets bored easily like myself, that sounds great 😃\n", + "caption": "A daisy - Picture by Bess Hamiti via Pexels", + "slug": "daisyui-components-themes", + "date": "2021-07-07", + "title": "Once more without MaterialUI: DaisyUI for CSS components and themes", + "linktitle": "Components and themes with DaisyUI", + "published_at": "2021-07-11T16:33:09.567Z", + "createdAt": "2021-07-11T16:33:05.698Z", + "updatedAt": "2021-07-23T15:04:53.670Z", + "__v": 0, + "author": { + "localizations": [], + "_id": "60eae49cdb32b45d5c4d6d93", + "about": "Joost is FreeSewing's maintainer. As an introvert, he enjoys making clothes and shoes since you don't have to leave the house to do so 🙈\n\nYou can follow him as [j__st on Twitter](https://twitter.com/j__st) or [joostdecock on Github](https://github.com/joostdecock).", + "name": "joost", + "displayname": "Joost De Cock", + "createdAt": "2021-07-11T12:31:24.779Z", + "updatedAt": "2021-09-19T14:23:43.748Z", + "__v": 0, + "picture": { + "_id": "6134ca45cd9dc073cc08db6a", + "name": "tumblr_ok2vhiYCDh1qbhhcgo1_1280.jpg", + "alternativeText": "Joost De Cock", + "caption": "Joost De Cock", + "hash": "tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e", + "ext": ".jpg", + "mime": "image/jpeg", + "size": 76.85, + "width": 1080, + "height": 810, + "url": "/uploads/tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e.jpg", + "formats": { + "thumbnail": { + "name": "thumbnail_tumblr_ok2vhiYCDh1qbhhcgo1_1280.jpg", + "hash": "thumbnail_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 208, + "height": 156, + "size": 6.22, + "path": null, + "url": "/uploads/thumbnail_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e.jpg" + }, + "large": { + "name": "large_tumblr_ok2vhiYCDh1qbhhcgo1_1280.jpg", + "hash": "large_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 1000, + "height": 750, + "size": 85.96, + "path": null, + "url": "/uploads/large_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e.jpg" + }, + "medium": { + "name": "medium_tumblr_ok2vhiYCDh1qbhhcgo1_1280.jpg", + "hash": "medium_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 600, + "height": 450, + "size": 36, + "path": null, + "url": "/uploads/medium_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e.jpg" + }, + "small": { + "name": "small_tumblr_ok2vhiYCDh1qbhhcgo1_1280.jpg", + "hash": "small_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 400, + "height": 300, + "size": 17.49, + "path": null, + "url": "/uploads/small_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e.jpg" + }, + "xsmall": { + "name": "xsmall_tumblr_ok2vhiYCDh1qbhhcgo1_1280.jpg", + "hash": "xsmall_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 200, + "height": 150, + "size": 5.99, + "path": null, + "url": "/uploads/xsmall_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e.jpg" + } + }, + "provider": "local", + "related": [ + "60eae49cdb32b45d5c4d6d93", + "60fade591972003530e03b39", + "60fadf5e19815b356b32781e" + ], + "createdAt": "2021-09-05T13:46:45.753Z", + "updatedAt": "2021-09-19T14:24:00.480Z", + "__v": 0, + "id": "6134ca45cd9dc073cc08db6a" + }, + "locale": "en", + "slug": "joostdecock", + "id": "60eae49cdb32b45d5c4d6d93" + }, + "image": { + "_id": "60eb1d29db32b45d5c4d6d9b", + "name": "pexels-bess-hamiti-36764.jpg", + "alternativeText": "", + "caption": "", + "hash": "pexels_bess_hamiti_36764_b2c736ca98", + "ext": ".jpg", + "mime": "image/jpeg", + "size": 144.55, + "width": 1980, + "height": 1289, + "url": "/uploads/pexels_bess_hamiti_36764_b2c736ca98.jpg", + "formats": { + "thumbnail": { + "name": "thumbnail_pexels-bess-hamiti-36764.jpg", + "hash": "thumbnail_pexels_bess_hamiti_36764_b2c736ca98", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 240, + "height": 156, + "size": 4.91, + "path": null, + "url": "/uploads/thumbnail_pexels_bess_hamiti_36764_b2c736ca98.jpg" + }, + "large": { + "name": "large_pexels-bess-hamiti-36764.jpg", + "hash": "large_pexels_bess_hamiti_36764_b2c736ca98", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 1000, + "height": 651, + "size": 42.87, + "path": null, + "url": "/uploads/large_pexels_bess_hamiti_36764_b2c736ca98.jpg" + }, + "medium": { + "name": "medium_pexels-bess-hamiti-36764.jpg", + "hash": "medium_pexels_bess_hamiti_36764_b2c736ca98", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 750, + "height": 488, + "size": 26.51, + "path": null, + "url": "/uploads/medium_pexels_bess_hamiti_36764_b2c736ca98.jpg" + }, + "small": { + "name": "small_pexels-bess-hamiti-36764.jpg", + "hash": "small_pexels_bess_hamiti_36764_b2c736ca98", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 500, + "height": 326, + "size": 14.45, + "path": null, + "url": "/uploads/small_pexels_bess_hamiti_36764_b2c736ca98.jpg" + } + }, + "provider": "local", + "related": [ + "60eb1d41db32b45d5c4d6d9c" + ], + "createdAt": "2021-07-11T16:32:41.273Z", + "updatedAt": "2021-07-11T16:33:05.741Z", + "__v": 0, + "id": "60eb1d29db32b45d5c4d6d9b" + }, + "site": "60ead51670e8d35a6d089fa0", + "locale": "en", + "id": "60eb1d41db32b45d5c4d6d9c" + }, + "blog/shared-frontend-code-monorepo": { + "dev": true, + "localizations": [], + "_id": "60eb237cdb32b45d5c4d6d9f", + "body": "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:\n\n- Content is shared by making [our markdown repository](https://github.com/freesewing/markdown) available as a git submodule\n- Code is shared by publishing packages on NPM, specifically [@freesewing/components](https://www.npmjs.com/package/@freesewing/components) for shared React components, [@freesewing/css-theme](https://www.npmjs.com/package/@freesewing/css-theme) for shared CSS,\nand [@freesewing/mui-theme](https://www.npmjs.com/package/@freesewing/mui-theme) for our shared MaterialUI theme.\n\nWhile that works fine, it has a few downsides:\n\n - Changes to the theming require publishing a new version of our software\n - The mix of library components and forntend components in [@freesewing/components](https://www.npmjs.com/package/@freesewing/components) make semantic versioning hard to apply\n\nThe first point is pretty straight-forward. The latter might require some more explanation:\n\nOur components package contains a number of components that we provide to users our our software, rather than users of our websites. Things like our `Draft` components, and `Workbench` component can be re-used by people who want to build their own projects/sites on top of FreeSewing.\n\nOn the other hand, there's a bunch of components that are clearly more geared towards internal use, such as the `Icon`, `Logo` or `Robots` components to name but a few.\n\nStrictly speaking, respecting [semantic versioning](https://semver.org/) means that a breaking changes in these components should trigger a new major version. But clearly it doesn't make sense to bump FreeSewing to a new major version just because we made a breaking change in our `Logo` component.\n\nIf we can bring our websites into our monorepo and share code directly in the repository, we can limit ourselves to publishing only those components that are not tightly coupled with our own website(s).\n\nSo how do we do that?\n\n# freesewing.dev in the monorepo\n\nFirst thing I did was bring freesewing.dev into our monorep. In `packages/freesewing.dev` to be precise. Then I added a new folder for shared code as `packages/shared`. Finally, I used webpack aliases to link all of this together:\n\n```js\nconfig.resolve.alias.shared = path.resolve(__dirname, '../shared')\nconfig.resolve.alias.site = path.resolve(__dirname)\n```\n\nThe first alias `shared` allows us to import a shared component as such:\n\n```js\nimport AppWrapper from 'shared/components/wrappers/app'\n```\n\nBut what's more, a site-specific component can be imported as such:\n\n```js\nimport NavigationButtons from 'site/components/navigation-buttons'\n```\n\nThis also works inside shared components. Which gives us a flexible way to add site-specific code to a component that is shared between freesewing.dev and freesewing.org.\n\nIt also avoids this kind of maintenance nightmare:\n\n```js\nimport Icon from '../../../../../../../components/Icon'\n```\n\nSo far, all this seems to work well with no unexpected side-effects 🤞", + "caption": "Picture by Ihsan Aditya via Pexels", + "slug": "shared-frontend-code-monorepo", + "date": "2021-07-08", + "title": "Better code sharing by bringing frontend code into our monorepo", + "linktitle": "Sharing frontend code in our monorepo", + "published_at": "2021-07-11T16:59:43.035Z", + "createdAt": "2021-07-11T16:59:40.699Z", + "updatedAt": "2021-07-23T15:04:46.863Z", + "__v": 0, + "author": { + "localizations": [], + "_id": "60eae49cdb32b45d5c4d6d93", + "about": "Joost is FreeSewing's maintainer. As an introvert, he enjoys making clothes and shoes since you don't have to leave the house to do so 🙈\n\nYou can follow him as [j__st on Twitter](https://twitter.com/j__st) or [joostdecock on Github](https://github.com/joostdecock).", + "name": "joost", + "displayname": "Joost De Cock", + "createdAt": "2021-07-11T12:31:24.779Z", + "updatedAt": "2021-09-19T14:23:43.748Z", + "__v": 0, + "picture": { + "_id": "6134ca45cd9dc073cc08db6a", + "name": "tumblr_ok2vhiYCDh1qbhhcgo1_1280.jpg", + "alternativeText": "Joost De Cock", + "caption": "Joost De Cock", + "hash": "tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e", + "ext": ".jpg", + "mime": "image/jpeg", + "size": 76.85, + "width": 1080, + "height": 810, + "url": "/uploads/tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e.jpg", + "formats": { + "thumbnail": { + "name": "thumbnail_tumblr_ok2vhiYCDh1qbhhcgo1_1280.jpg", + "hash": "thumbnail_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 208, + "height": 156, + "size": 6.22, + "path": null, + "url": "/uploads/thumbnail_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e.jpg" + }, + "large": { + "name": "large_tumblr_ok2vhiYCDh1qbhhcgo1_1280.jpg", + "hash": "large_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 1000, + "height": 750, + "size": 85.96, + "path": null, + "url": "/uploads/large_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e.jpg" + }, + "medium": { + "name": "medium_tumblr_ok2vhiYCDh1qbhhcgo1_1280.jpg", + "hash": "medium_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 600, + "height": 450, + "size": 36, + "path": null, + "url": "/uploads/medium_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e.jpg" + }, + "small": { + "name": "small_tumblr_ok2vhiYCDh1qbhhcgo1_1280.jpg", + "hash": "small_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 400, + "height": 300, + "size": 17.49, + "path": null, + "url": "/uploads/small_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e.jpg" + }, + "xsmall": { + "name": "xsmall_tumblr_ok2vhiYCDh1qbhhcgo1_1280.jpg", + "hash": "xsmall_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 200, + "height": 150, + "size": 5.99, + "path": null, + "url": "/uploads/xsmall_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e.jpg" + } + }, + "provider": "local", + "related": [ + "60eae49cdb32b45d5c4d6d93", + "60fade591972003530e03b39", + "60fadf5e19815b356b32781e" + ], + "createdAt": "2021-09-05T13:46:45.753Z", + "updatedAt": "2021-09-19T14:24:00.480Z", + "__v": 0, + "id": "6134ca45cd9dc073cc08db6a" + }, + "locale": "en", + "slug": "joostdecock", + "id": "60eae49cdb32b45d5c4d6d93" + }, + "image": { + "_id": "60eb236bdb32b45d5c4d6d9e", + "name": "pexels-ihsan-aditya-1056251.jpg", + "alternativeText": "", + "caption": "", + "hash": "pexels_ihsan_aditya_1056251_41549d8d66", + "ext": ".jpg", + "mime": "image/jpeg", + "size": 329.3, + "width": 1920, + "height": 1280, + "url": "/uploads/pexels_ihsan_aditya_1056251_41549d8d66.jpg", + "formats": { + "thumbnail": { + "name": "thumbnail_pexels-ihsan-aditya-1056251.jpg", + "hash": "thumbnail_pexels_ihsan_aditya_1056251_41549d8d66", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 234, + "height": 156, + "size": 8.22, + "path": null, + "url": "/uploads/thumbnail_pexels_ihsan_aditya_1056251_41549d8d66.jpg" + }, + "large": { + "name": "large_pexels-ihsan-aditya-1056251.jpg", + "hash": "large_pexels_ihsan_aditya_1056251_41549d8d66", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 1000, + "height": 667, + "size": 94.56, + "path": null, + "url": "/uploads/large_pexels_ihsan_aditya_1056251_41549d8d66.jpg" + }, + "medium": { + "name": "medium_pexels-ihsan-aditya-1056251.jpg", + "hash": "medium_pexels_ihsan_aditya_1056251_41549d8d66", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 750, + "height": 500, + "size": 56.07, + "path": null, + "url": "/uploads/medium_pexels_ihsan_aditya_1056251_41549d8d66.jpg" + }, + "small": { + "name": "small_pexels-ihsan-aditya-1056251.jpg", + "hash": "small_pexels_ihsan_aditya_1056251_41549d8d66", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 500, + "height": 333, + "size": 28.71, + "path": null, + "url": "/uploads/small_pexels_ihsan_aditya_1056251_41549d8d66.jpg" + } + }, + "provider": "local", + "related": [ + "60eb237cdb32b45d5c4d6d9f" + ], + "createdAt": "2021-07-11T16:59:23.684Z", + "updatedAt": "2021-07-11T16:59:40.702Z", + "__v": 0, + "id": "60eb236bdb32b45d5c4d6d9e" + }, + "site": "60ead51670e8d35a6d089fa0", + "locale": "en", + "id": "60eb237cdb32b45d5c4d6d9f" + }, + "blog/improved-search-keyboard": { + "dev": true, + "localizations": [], + "_id": "60eb2759db32b45d5c4d6da2", + "body": "\nSince we've got a ton of documentation, good search is a crucial aspect of our websites.\n\nIn [project 2022](/blog/project-2022) I wanted to improve the search experience for our users. Here are some of the pain points I have identified:\n\n - Searching takes too many clicks\n - All content is indexed for search, when people are typically looking for documentation\n\nI've implemented a new search solution that is never more than one click away (right there in the navigation bar). And, even better, can be triggered at any time by pressing `Ctrl-k` on your keyboard.\n\nThis will launch the search and auto-focus the input field so you can just start typing. The first result will be selected, but you can navigation through the results using the up and down arrows, giving you full control over the search experience from the keyboard.\n\nIf you want to cancel the search, clicking `escape` will do just that. To clear the search input, `ctrl-c` is your friend.\n\nAll of this keyboard-drive search adds little value on mobile. But it's particularly handy when you're working on a pattern or documentation and you quickly want to look something up.\n\nWhile we still have to handle the search indexing itself (we're using the index of the current freesewing.dev site since we did not change any URLs), I feel the search experience itself is a nice improvement.\n\nGive it a go and let me know what you think [in our #project-2022 Discord channel](https://discord.freesewing.org/).", + "caption": "Picture by Skitterphoto via Pexels", + "slug": "improved-search-keyboard", + "date": "2021-07-09", + "title": "Improved search with keyboard bindings", + "linktitle": "Improved search with keyboard bindings", + "published_at": "2021-07-11T17:16:16.082Z", + "createdAt": "2021-07-11T17:16:09.119Z", + "updatedAt": "2021-07-23T15:04:40.641Z", + "__v": 0, + "author": { + "localizations": [], + "_id": "60eae49cdb32b45d5c4d6d93", + "about": "Joost is FreeSewing's maintainer. As an introvert, he enjoys making clothes and shoes since you don't have to leave the house to do so 🙈\n\nYou can follow him as [j__st on Twitter](https://twitter.com/j__st) or [joostdecock on Github](https://github.com/joostdecock).", + "name": "joost", + "displayname": "Joost De Cock", + "createdAt": "2021-07-11T12:31:24.779Z", + "updatedAt": "2021-09-19T14:23:43.748Z", + "__v": 0, + "picture": { + "_id": "6134ca45cd9dc073cc08db6a", + "name": "tumblr_ok2vhiYCDh1qbhhcgo1_1280.jpg", + "alternativeText": "Joost De Cock", + "caption": "Joost De Cock", + "hash": "tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e", + "ext": ".jpg", + "mime": "image/jpeg", + "size": 76.85, + "width": 1080, + "height": 810, + "url": "/uploads/tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e.jpg", + "formats": { + "thumbnail": { + "name": "thumbnail_tumblr_ok2vhiYCDh1qbhhcgo1_1280.jpg", + "hash": "thumbnail_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 208, + "height": 156, + "size": 6.22, + "path": null, + "url": "/uploads/thumbnail_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e.jpg" + }, + "large": { + "name": "large_tumblr_ok2vhiYCDh1qbhhcgo1_1280.jpg", + "hash": "large_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 1000, + "height": 750, + "size": 85.96, + "path": null, + "url": "/uploads/large_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e.jpg" + }, + "medium": { + "name": "medium_tumblr_ok2vhiYCDh1qbhhcgo1_1280.jpg", + "hash": "medium_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 600, + "height": 450, + "size": 36, + "path": null, + "url": "/uploads/medium_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e.jpg" + }, + "small": { + "name": "small_tumblr_ok2vhiYCDh1qbhhcgo1_1280.jpg", + "hash": "small_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 400, + "height": 300, + "size": 17.49, + "path": null, + "url": "/uploads/small_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e.jpg" + }, + "xsmall": { + "name": "xsmall_tumblr_ok2vhiYCDh1qbhhcgo1_1280.jpg", + "hash": "xsmall_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 200, + "height": 150, + "size": 5.99, + "path": null, + "url": "/uploads/xsmall_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e.jpg" + } + }, + "provider": "local", + "related": [ + "60eae49cdb32b45d5c4d6d93", + "60fade591972003530e03b39", + "60fadf5e19815b356b32781e" + ], + "createdAt": "2021-09-05T13:46:45.753Z", + "updatedAt": "2021-09-19T14:24:00.480Z", + "__v": 0, + "id": "6134ca45cd9dc073cc08db6a" + }, + "locale": "en", + "slug": "joostdecock", + "id": "60eae49cdb32b45d5c4d6d93" + }, + "image": { + "_id": "60eb2742db32b45d5c4d6da1", + "name": "pexels-skitterphoto-63901.jpg", + "alternativeText": "", + "caption": "", + "hash": "pexels_skitterphoto_63901_6dad2c294b", + "ext": ".jpg", + "mime": "image/jpeg", + "size": 150.68, + "width": 1920, + "height": 1302, + "url": "/uploads/pexels_skitterphoto_63901_6dad2c294b.jpg", + "formats": { + "thumbnail": { + "name": "thumbnail_pexels-skitterphoto-63901.jpg", + "hash": "thumbnail_pexels_skitterphoto_63901_6dad2c294b", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 230, + "height": 156, + "size": 5.91, + "path": null, + "url": "/uploads/thumbnail_pexels_skitterphoto_63901_6dad2c294b.jpg" + }, + "large": { + "name": "large_pexels-skitterphoto-63901.jpg", + "hash": "large_pexels_skitterphoto_63901_6dad2c294b", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 1000, + "height": 678, + "size": 48.18, + "path": null, + "url": "/uploads/large_pexels_skitterphoto_63901_6dad2c294b.jpg" + }, + "medium": { + "name": "medium_pexels-skitterphoto-63901.jpg", + "hash": "medium_pexels_skitterphoto_63901_6dad2c294b", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 750, + "height": 509, + "size": 30.98, + "path": null, + "url": "/uploads/medium_pexels_skitterphoto_63901_6dad2c294b.jpg" + }, + "small": { + "name": "small_pexels-skitterphoto-63901.jpg", + "hash": "small_pexels_skitterphoto_63901_6dad2c294b", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 500, + "height": 339, + "size": 17.53, + "path": null, + "url": "/uploads/small_pexels_skitterphoto_63901_6dad2c294b.jpg" + } + }, + "provider": "local", + "related": [ + "60eb2759db32b45d5c4d6da2" + ], + "createdAt": "2021-07-11T17:15:46.611Z", + "updatedAt": "2021-07-11T17:16:09.123Z", + "__v": 0, + "id": "60eb2742db32b45d5c4d6da1" + }, + "site": "60ead51670e8d35a6d089fa0", + "locale": "en", + "id": "60eb2759db32b45d5c4d6da2" + }, + "blog/strapi-headless-cms": { + "dev": true, + "localizations": [], + "_id": "60eb2e11db32b45d5c4d6da5", + "title": "Strapi as headless CMS", + "linktitle": "Strapi as headless CMS", + "body": "As things are now, all of FreeSewing's content is contained [in our markdown repository](https://github.com/freesewing/markdown/).\n\nHistorically, that made a lot of sense:\n\n - I am perfectly happy to write markdown and put it in git\n - We need that content in a repo to we can translate it with [Crowding](https://crowdin.com)\n\nHowever, as the project grows, this approach is starting to show some real downsides:\n\n - It is scary/intimidating for new users to get familiar with git(hub) just to write a blog post or submit a showcase\n - The fact that the markdown repo is included as a submodule in our freesewing.org and freesewing.dev repositories means there's extra hoops to jump through before changes show up on the website\n - Translators have a lot of translation work in their queue with arguably little added value (like a blog post from 2 years ago).\n\nI've been thinking about this within the context of our [project 2022](/blog/project-2022) and I think we should make a distinction between two types of content:\n\n- **documentation**: This should be in git because we want a history, we want to include that little pencil icon that lets people fix errors directly on the github website, and should be translated as close as possible and as complete as possible to provide consistent info in different languages.\n- **posts**: Specifically blog posts, and to a lesser extend showcase posts. This content does not really have to live in a git repository. It is much more a write-once, never look back sort of thing.\n\nI also feel it makes little sense to translate blog posts word for word. Sure, blog posts announcing new patterns and so on would preferable be available in all languages. But I'd like to lower the bar for people to contribute to our blog, and that includes letting people write original content in languages other than English.\n\nSo, as a sort of test, I have setup [Strapi](https://strapi.io/) and it is the source for all blog posts on this developer blog.\n\nStrapi is a so-called *headless CMS* (where CMS stands for content management system). You might be familiar with Wordpress, which is a more classic CMS. A headless CMS provides similar functionality for creating and editing content. But rather than generating a website, it comes with an API you can talk to to get your content. Such an approach works great with the kind of [JAMstack](https://jamstack.org/) websites we buid.\n\nWhile it's early to draw conclusions, there's a bunch of things that I feel make this an avenue worth pursuing:\n\n- Writing and editing content is significantly simpler\n- Images can simply be uploaded, which was a pain point in the current setup\n- Keeping (large) images out of a git repository makes a lot of sense. Case in point: Our markdown repo is currently about 400MB and growing steadily. in comparison, our monorepo is less than 10% of that size.\n- All the image handling and resizing makes build times exceedingly slow. It's also somewhat frustrating that we have to do this on every build, when in reality, the images don't change\n\nThe Strapi instance I've setup lives at https://posts.freesewing.org/ \n\nI would like to configure authentication in such a way that you can either re-use your FreeSewing accounts, or your Discord account.\n\nStrapi uses the same Mongo database server as the main website, just a different database).\n\nI think this is a promising path to go down on. Anecdotal evidence: this is my 7th blog post since setting it up 😄 It's just nice to be able to dump your thoughts into a post without that having to be a big deal.\n\nUntil I've setup proper authentication, I can create an account for you if you'd like to kick the tires. As usual, the [#project-2022 channel on our Discord server](https://discord.freesewing.org/) is the place to be.\n", + "caption": "Headless. Get it? - Picture by Leah Kelley via Pexels", + "published_at": "2021-07-11T17:45:31.369Z", + "createdAt": "2021-07-11T17:44:49.116Z", + "updatedAt": "2021-07-23T15:04:36.017Z", + "__v": 0, + "author": { + "localizations": [], + "_id": "60eae49cdb32b45d5c4d6d93", + "about": "Joost is FreeSewing's maintainer. As an introvert, he enjoys making clothes and shoes since you don't have to leave the house to do so 🙈\n\nYou can follow him as [j__st on Twitter](https://twitter.com/j__st) or [joostdecock on Github](https://github.com/joostdecock).", + "name": "joost", + "displayname": "Joost De Cock", + "createdAt": "2021-07-11T12:31:24.779Z", + "updatedAt": "2021-09-19T14:23:43.748Z", + "__v": 0, + "picture": { + "_id": "6134ca45cd9dc073cc08db6a", + "name": "tumblr_ok2vhiYCDh1qbhhcgo1_1280.jpg", + "alternativeText": "Joost De Cock", + "caption": "Joost De Cock", + "hash": "tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e", + "ext": ".jpg", + "mime": "image/jpeg", + "size": 76.85, + "width": 1080, + "height": 810, + "url": "/uploads/tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e.jpg", + "formats": { + "thumbnail": { + "name": "thumbnail_tumblr_ok2vhiYCDh1qbhhcgo1_1280.jpg", + "hash": "thumbnail_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 208, + "height": 156, + "size": 6.22, + "path": null, + "url": "/uploads/thumbnail_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e.jpg" + }, + "large": { + "name": "large_tumblr_ok2vhiYCDh1qbhhcgo1_1280.jpg", + "hash": "large_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 1000, + "height": 750, + "size": 85.96, + "path": null, + "url": "/uploads/large_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e.jpg" + }, + "medium": { + "name": "medium_tumblr_ok2vhiYCDh1qbhhcgo1_1280.jpg", + "hash": "medium_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 600, + "height": 450, + "size": 36, + "path": null, + "url": "/uploads/medium_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e.jpg" + }, + "small": { + "name": "small_tumblr_ok2vhiYCDh1qbhhcgo1_1280.jpg", + "hash": "small_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 400, + "height": 300, + "size": 17.49, + "path": null, + "url": "/uploads/small_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e.jpg" + }, + "xsmall": { + "name": "xsmall_tumblr_ok2vhiYCDh1qbhhcgo1_1280.jpg", + "hash": "xsmall_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 200, + "height": 150, + "size": 5.99, + "path": null, + "url": "/uploads/xsmall_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e.jpg" + } + }, + "provider": "local", + "related": [ + "60eae49cdb32b45d5c4d6d93", + "60fade591972003530e03b39", + "60fadf5e19815b356b32781e" + ], + "createdAt": "2021-09-05T13:46:45.753Z", + "updatedAt": "2021-09-19T14:24:00.480Z", + "__v": 0, + "id": "6134ca45cd9dc073cc08db6a" + }, + "locale": "en", + "slug": "joostdecock", + "id": "60eae49cdb32b45d5c4d6d93" + }, + "image": { + "_id": "60eb2d74db32b45d5c4d6da4", + "name": "pexels-leah-kelley-5852942.jpg", + "alternativeText": "", + "caption": "", + "hash": "pexels_leah_kelley_5852942_3a27bb6b4e", + "ext": ".jpg", + "mime": "image/jpeg", + "size": 113.12, + "width": 1920, + "height": 1279, + "url": "/uploads/pexels_leah_kelley_5852942_3a27bb6b4e.jpg", + "formats": { + "thumbnail": { + "name": "thumbnail_pexels-leah-kelley-5852942.jpg", + "hash": "thumbnail_pexels_leah_kelley_5852942_3a27bb6b4e", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 234, + "height": 156, + "size": 4.33, + "path": null, + "url": "/uploads/thumbnail_pexels_leah_kelley_5852942_3a27bb6b4e.jpg" + }, + "large": { + "name": "large_pexels-leah-kelley-5852942.jpg", + "hash": "large_pexels_leah_kelley_5852942_3a27bb6b4e", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 1000, + "height": 666, + "size": 36.1, + "path": null, + "url": "/uploads/large_pexels_leah_kelley_5852942_3a27bb6b4e.jpg" + }, + "medium": { + "name": "medium_pexels-leah-kelley-5852942.jpg", + "hash": "medium_pexels_leah_kelley_5852942_3a27bb6b4e", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 750, + "height": 500, + "size": 23.26, + "path": null, + "url": "/uploads/medium_pexels_leah_kelley_5852942_3a27bb6b4e.jpg" + }, + "small": { + "name": "small_pexels-leah-kelley-5852942.jpg", + "hash": "small_pexels_leah_kelley_5852942_3a27bb6b4e", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 500, + "height": 333, + "size": 13.02, + "path": null, + "url": "/uploads/small_pexels_leah_kelley_5852942_3a27bb6b4e.jpg" + } + }, + "provider": "local", + "related": [ + "60eb2e11db32b45d5c4d6da5" + ], + "createdAt": "2021-07-11T17:42:12.875Z", + "updatedAt": "2021-07-11T17:44:49.121Z", + "__v": 0, + "id": "60eb2d74db32b45d5c4d6da4" + }, + "site": "60ead51670e8d35a6d089fa0", + "date": "2021-07-10", + "slug": "strapi-headless-cms", + "locale": "en", + "id": "60eb2e11db32b45d5c4d6da5" + }, + "blog/layout-blocks-overview": { + "dev": true, + "localizations": [], + "_id": "60f0616ddb32b45d5c4d6dac", + "body": "I wanted to do a quick write-up of the main layout blocks of our websites. So let's have a look:\n\n## Screen sizes and break points: 1024px wide or not?\n\nOur websites should work on a variety of devices:\n\n\nFor this, we practice so-called **responsive design**. A design that adapts to the screen size. \nOr rather, the screen width because thanks to scrolling, we always have plenty of height.\n\nIn practice, we use so-called **break points**. A list of one or more screen widths at which we apply a different layout for our website.\nTo keep things simple, we utilize a single breakpoint: **1024px**. \n\n - large screen: 1024 pixels or more wide\n - small screen: less than 1024 pixels wide\n\n## Page structure: Three elements, one dimension\n\nThe structure of each page on the website is a vertical column with three elements:\n\n - The header which holds the so-called `navbar`, short for *navigation bar*\n - The main part of the page, which in this case shows this blog post\n - The footer\n\nIt looks like this:\n\n\n\nThat's on large screens. We'll have a closer look at small screens later.\n\n## Footer\n\nThe footer is the simplest element. It's always there, and it sits at the bottom of the page.\n\nThe footer spans the entire width of the screen, but the content within is constrained by a maximum width. That width is **1536px** (indicated by the red dashed lines in the image above).\nThis is to avoid that people on a 4K monitor get neck cramps like they're attending some tennis match, just to read what's on the screen.\n\n## Navbar\n\nThe navbar is similar to the footer in the sense that it spans the full width, yet the content within is constrained to maximum 1536 pixels.\n\nOn large screens, the navbar sits at the top of the page and does not have a background color to make it stand out.\nThat's because it's rather intuitive to have the navigation at the top of the page.\n\nOn mobile, the navbar collapses gets fixed at the bottom of the screen, and gets a background color to draw attention to the fact that this is where the navigation controls are:\n\n\n\nThe reason we place the navigation at the bottom of the screen is that it's easier to reach when using a mobile phone with one hand.\n\nRather than try to cram everything in the (now very small) navbar. We simply use a button that makes the whole screen the navigation menu on small screens.\n\n## Main layout\n\nThe *main* part of a page can be pretty much anything we want. But on most pages, it looks like this:\n\n\n\nHere's too we are constraining the content to the same maximum width.\nApart from that, we have the main content of the page, and the `aside` which typically holds navigation.\n\nThis aside is not available on mobile, and instead can be reached by bringing up the menu.\n\n\n## Summary\n\nAll our websites look the same\n\n - The page is a vertical column made of 3 parts: header -> main -> footer\n - The main part can be any layout, but the most common has content + aside\n - On mobile, the navbar sits fixed at the bottom, and aside moves off-canvas\n\n", + "caption": " Photo by Kelly Lacy via Pexels", + "slug": "layout-blocks-overview", + "date": "2021-07-15", + "title": "A quick tour of the main layout blocks", + "linktitle": "Main layout blocks", + "published_at": "2021-07-15T16:25:20.922Z", + "createdAt": "2021-07-15T16:25:17.007Z", + "updatedAt": "2021-07-23T15:04:30.866Z", + "__v": 0, + "author": { + "localizations": [], + "_id": "60eae49cdb32b45d5c4d6d93", + "about": "Joost is FreeSewing's maintainer. As an introvert, he enjoys making clothes and shoes since you don't have to leave the house to do so 🙈\n\nYou can follow him as [j__st on Twitter](https://twitter.com/j__st) or [joostdecock on Github](https://github.com/joostdecock).", + "name": "joost", + "displayname": "Joost De Cock", + "createdAt": "2021-07-11T12:31:24.779Z", + "updatedAt": "2021-09-19T14:23:43.748Z", + "__v": 0, + "picture": { + "_id": "6134ca45cd9dc073cc08db6a", + "name": "tumblr_ok2vhiYCDh1qbhhcgo1_1280.jpg", + "alternativeText": "Joost De Cock", + "caption": "Joost De Cock", + "hash": "tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e", + "ext": ".jpg", + "mime": "image/jpeg", + "size": 76.85, + "width": 1080, + "height": 810, + "url": "/uploads/tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e.jpg", + "formats": { + "thumbnail": { + "name": "thumbnail_tumblr_ok2vhiYCDh1qbhhcgo1_1280.jpg", + "hash": "thumbnail_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 208, + "height": 156, + "size": 6.22, + "path": null, + "url": "/uploads/thumbnail_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e.jpg" + }, + "large": { + "name": "large_tumblr_ok2vhiYCDh1qbhhcgo1_1280.jpg", + "hash": "large_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 1000, + "height": 750, + "size": 85.96, + "path": null, + "url": "/uploads/large_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e.jpg" + }, + "medium": { + "name": "medium_tumblr_ok2vhiYCDh1qbhhcgo1_1280.jpg", + "hash": "medium_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 600, + "height": 450, + "size": 36, + "path": null, + "url": "/uploads/medium_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e.jpg" + }, + "small": { + "name": "small_tumblr_ok2vhiYCDh1qbhhcgo1_1280.jpg", + "hash": "small_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 400, + "height": 300, + "size": 17.49, + "path": null, + "url": "/uploads/small_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e.jpg" + }, + "xsmall": { + "name": "xsmall_tumblr_ok2vhiYCDh1qbhhcgo1_1280.jpg", + "hash": "xsmall_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 200, + "height": 150, + "size": 5.99, + "path": null, + "url": "/uploads/xsmall_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e.jpg" + } + }, + "provider": "local", + "related": [ + "60eae49cdb32b45d5c4d6d93", + "60fade591972003530e03b39", + "60fadf5e19815b356b32781e" + ], + "createdAt": "2021-09-05T13:46:45.753Z", + "updatedAt": "2021-09-19T14:24:00.480Z", + "__v": 0, + "id": "6134ca45cd9dc073cc08db6a" + }, + "locale": "en", + "slug": "joostdecock", + "id": "60eae49cdb32b45d5c4d6d93" + }, + "image": { + "_id": "60f05ac3db32b45d5c4d6da7", + "name": "pexels-kelly-lacy-2402233.jpg", + "alternativeText": "", + "caption": "", + "hash": "pexels_kelly_lacy_2402233_45b5683839", + "ext": ".jpg", + "mime": "image/jpeg", + "size": 630.2, + "width": 1920, + "height": 1078, + "url": "/uploads/pexels_kelly_lacy_2402233_45b5683839.jpg", + "formats": { + "thumbnail": { + "name": "thumbnail_pexels-kelly-lacy-2402233.jpg", + "hash": "thumbnail_pexels_kelly_lacy_2402233_45b5683839", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 245, + "height": 138, + "size": 16.81, + "path": null, + "url": "/uploads/thumbnail_pexels_kelly_lacy_2402233_45b5683839.jpg" + }, + "large": { + "name": "large_pexels-kelly-lacy-2402233.jpg", + "hash": "large_pexels_kelly_lacy_2402233_45b5683839", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 1000, + "height": 561, + "size": 207.61, + "path": null, + "url": "/uploads/large_pexels_kelly_lacy_2402233_45b5683839.jpg" + }, + "medium": { + "name": "medium_pexels-kelly-lacy-2402233.jpg", + "hash": "medium_pexels_kelly_lacy_2402233_45b5683839", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 750, + "height": 421, + "size": 122.96, + "path": null, + "url": "/uploads/medium_pexels_kelly_lacy_2402233_45b5683839.jpg" + }, + "small": { + "name": "small_pexels-kelly-lacy-2402233.jpg", + "hash": "small_pexels_kelly_lacy_2402233_45b5683839", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 500, + "height": 281, + "size": 61.63, + "path": null, + "url": "/uploads/small_pexels_kelly_lacy_2402233_45b5683839.jpg" + } + }, + "provider": "local", + "related": [ + "60f0616ddb32b45d5c4d6dac" + ], + "createdAt": "2021-07-15T15:56:51.570Z", + "updatedAt": "2021-07-15T16:25:17.017Z", + "__v": 0, + "id": "60f05ac3db32b45d5c4d6da7" + }, + "site": "60ead51670e8d35a6d089fa0", + "locale": "en", + "id": "60f0616ddb32b45d5c4d6dac" + }, + "blog/freesewing-v3-seems-inevitable": { + "dev": true, + "localizations": [], + "_id": "60f2e2ef2f81c03239366844", + "body": "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.\n\nAnd I guess that with that I've made the realization that I'm working on FreeSewing v3.\n\nThe breaking changes are not in our core library or how patterns work, but more in how we organize our code and tie it all together. Ultimately, I want to make it easier for people to graduate from interested bystander to contributor, and bringing everything into our monorepo with reasonable dependencies is part of that.\n\n## Changes in i18n\n\nTake our internationalization package, [@freesewing/i18n](https://www.npmjs.com/package/@freesewing/i18n) which holds all our translations.\n\nBecause of this, it's a relatively large package, and weighs in just over 282.9kB when minified. That is 4 times the size of our core library.\n\nIt's not a problem as such. There's no shortcut for all this translated strings, we need to ship them somehow. But the problem is that the package is currently not *tree-shakeable*.\n\nTree-shaking is a term that is used to describe a process of removing unused code from the Javascript bundle, and is implemented by bundlers such as Rollup or Webpack.\nThe idea is that if you use only a part of a library, the rest of it will be kept out of the bundle, so you have to ship less code.\n\nWith the current setup, `@freesewing/i18n` is not tree-shakeable, which means that if you build a website that is exclusively English, you still will find all other translations in the bundle. Not cool.\n\nCurrently, `i18n` has a default export which is an object with these keys: \n\n```js\nexport { strings, languages, plugin, jargon }\n```\n\nIn v3, we will switch to named exports, and export the following:\n\n - `locales`: List of languages codes we provide\n - `languages`: Translations for the languages codes (for use in a language switcher)\n - `en`: English translations\n - `es`: Spanish translations\n - `de`: German translations\n - `fr`: French translations\n - `nl`: Dutch translations\n - `jargon_en`: English jargon file (for use in our [remark plugin for jargon](https://www.npmjs.com/package/remark-jargon))\n - `jargon_es`: Spanish jargon file (for use in our [remark plugin for jargon](https://www.npmjs.com/package/remark-jargon))\n - `jargon_de`: German jargon file (for use in our [remark plugin for jargon](https://www.npmjs.com/package/remark-jargon))\n - `jargon_fr`: French jargon file (for use in our [remark plugin for jargon](https://www.npmjs.com/package/remark-jargon))\n - `jargon_nl`: Dutch jargon file (for use in our [remark plugin for jargon](https://www.npmjs.com/package/remark-jargon))\n\n\nNow you can import only what you need, and let your bundler tree-shake the rest.\n\n\n\n\n", + "caption": "Photo by Miguel Á. Padriñán via Pexels", + "slug": "freesewing-v3-seems-inevitable", + "date": "2021-07-16", + "title": "Better tree-shaking means breaking changes; FreeSewing v3 now seems inevitable", + "linktitle": "FreeSewing v3 seems inevitable", + "published_at": "2021-07-17T15:15:08.840Z", + "createdAt": "2021-07-17T14:02:23.828Z", + "updatedAt": "2021-07-23T15:04:23.665Z", + "__v": 0, + "author": { + "localizations": [], + "_id": "60eae49cdb32b45d5c4d6d93", + "about": "Joost is FreeSewing's maintainer. As an introvert, he enjoys making clothes and shoes since you don't have to leave the house to do so 🙈\n\nYou can follow him as [j__st on Twitter](https://twitter.com/j__st) or [joostdecock on Github](https://github.com/joostdecock).", + "name": "joost", + "displayname": "Joost De Cock", + "createdAt": "2021-07-11T12:31:24.779Z", + "updatedAt": "2021-09-19T14:23:43.748Z", + "__v": 0, + "picture": { + "_id": "6134ca45cd9dc073cc08db6a", + "name": "tumblr_ok2vhiYCDh1qbhhcgo1_1280.jpg", + "alternativeText": "Joost De Cock", + "caption": "Joost De Cock", + "hash": "tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e", + "ext": ".jpg", + "mime": "image/jpeg", + "size": 76.85, + "width": 1080, + "height": 810, + "url": "/uploads/tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e.jpg", + "formats": { + "thumbnail": { + "name": "thumbnail_tumblr_ok2vhiYCDh1qbhhcgo1_1280.jpg", + "hash": "thumbnail_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 208, + "height": 156, + "size": 6.22, + "path": null, + "url": "/uploads/thumbnail_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e.jpg" + }, + "large": { + "name": "large_tumblr_ok2vhiYCDh1qbhhcgo1_1280.jpg", + "hash": "large_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 1000, + "height": 750, + "size": 85.96, + "path": null, + "url": "/uploads/large_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e.jpg" + }, + "medium": { + "name": "medium_tumblr_ok2vhiYCDh1qbhhcgo1_1280.jpg", + "hash": "medium_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 600, + "height": 450, + "size": 36, + "path": null, + "url": "/uploads/medium_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e.jpg" + }, + "small": { + "name": "small_tumblr_ok2vhiYCDh1qbhhcgo1_1280.jpg", + "hash": "small_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 400, + "height": 300, + "size": 17.49, + "path": null, + "url": "/uploads/small_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e.jpg" + }, + "xsmall": { + "name": "xsmall_tumblr_ok2vhiYCDh1qbhhcgo1_1280.jpg", + "hash": "xsmall_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 200, + "height": 150, + "size": 5.99, + "path": null, + "url": "/uploads/xsmall_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e.jpg" + } + }, + "provider": "local", + "related": [ + "60eae49cdb32b45d5c4d6d93", + "60fade591972003530e03b39", + "60fadf5e19815b356b32781e" + ], + "createdAt": "2021-09-05T13:46:45.753Z", + "updatedAt": "2021-09-19T14:24:00.480Z", + "__v": 0, + "id": "6134ca45cd9dc073cc08db6a" + }, + "locale": "en", + "slug": "joostdecock", + "id": "60eae49cdb32b45d5c4d6d93" + }, + "image": { + "_id": "60f2de802f81c03239366843", + "name": "pexels-miguel-á-padriñán-1061140.jpg", + "alternativeText": "", + "caption": "", + "hash": "pexels_miguel_a_padrinan_1061140_7a14d61e89", + "ext": ".jpg", + "mime": "image/jpeg", + "size": 161.74, + "width": 1920, + "height": 1276, + "url": "/uploads/pexels_miguel_a_padrinan_1061140_7a14d61e89.jpg", + "formats": { + "thumbnail": { + "name": "thumbnail_pexels-miguel-á-padriñán-1061140.jpg", + "hash": "thumbnail_pexels_miguel_a_padrinan_1061140_7a14d61e89", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 235, + "height": 156, + "size": 2.6, + "path": null, + "url": "/uploads/thumbnail_pexels_miguel_a_padrinan_1061140_7a14d61e89.jpg" + }, + "large": { + "name": "large_pexels-miguel-á-padriñán-1061140.jpg", + "hash": "large_pexels_miguel_a_padrinan_1061140_7a14d61e89", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 1000, + "height": 665, + "size": 37.15, + "path": null, + "url": "/uploads/large_pexels_miguel_a_padrinan_1061140_7a14d61e89.jpg" + }, + "medium": { + "name": "medium_pexels-miguel-á-padriñán-1061140.jpg", + "hash": "medium_pexels_miguel_a_padrinan_1061140_7a14d61e89", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 600, + "height": 399, + "size": 11.93, + "path": null, + "url": "/uploads/medium_pexels_miguel_a_padrinan_1061140_7a14d61e89.jpg" + }, + "small": { + "name": "small_pexels-miguel-á-padriñán-1061140.jpg", + "hash": "small_pexels_miguel_a_padrinan_1061140_7a14d61e89", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 400, + "height": 266, + "size": 5.54, + "path": null, + "url": "/uploads/small_pexels_miguel_a_padrinan_1061140_7a14d61e89.jpg" + }, + "xsmall": { + "name": "xsmall_pexels-miguel-á-padriñán-1061140.jpg", + "hash": "xsmall_pexels_miguel_a_padrinan_1061140_7a14d61e89", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 200, + "height": 133, + "size": 2.16, + "path": null, + "url": "/uploads/xsmall_pexels_miguel_a_padrinan_1061140_7a14d61e89.jpg" + } + }, + "provider": "local", + "related": [ + "60f2e2ef2f81c03239366844" + ], + "createdAt": "2021-07-17T13:43:28.919Z", + "updatedAt": "2021-07-17T14:02:23.831Z", + "__v": 0, + "id": "60f2de802f81c03239366843" + }, + "site": "60ead51670e8d35a6d089fa0", + "locale": "en", + "id": "60f2e2ef2f81c03239366844" + }, + "blog/migration-to-strapi": { + "dev": true, + "localizations": [], + "_id": "612785b5fca7f36c6a727a6b", + "body": "Effective immediately, all blog and showcase posts on freesewing.org are backed by [our Strapi instance](https://posts.freesewing.org/), rather than [our markdown repository](https://github.com/freesewing/markdown). 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](https://github.com/freesewing/freesewing).\n\n## These changes have already been deployed\n\nBoth freesewing.org and freesewing.dev have been updated to reflect this new reality. That includes pulling blog and showcase posts from strapi, but we also replaced the markdown submodule with our monorepo to access the markdown content.\n\n## Impact on translation\n\nThis change also has an impact on translations, as we used to have two crowdin projects, one for the markdown content (linked to the markdown repository) and another for the strings (linked to our monorepo). \n\nNow that all this content in hosted in our monorepo, I have merged these projects, so that we now have a single Crowdin project that's available via [translate.freesewing.org](https://translate.freesewing.org/).\n\nWhile that consolidates translation on Crowdin, we now have a different split since content hosted on Strapi must be translated there. However, translation of blog posts and showcase posts is a lot less crucial than translation of the documentation and strings.\n\nIn addition, this also opens the door for having original non-English content. For example, a French blog post could be written that does not need to be available in English. This is a departure of the tight coupling of English (as the origin language) and the translations for this kind of language, and opens the door for the various linguistic communities to make our blogs more *their own* by providing original content.\n\n## Impact on content creation\n\nPeople looking to create blog posts or showcases should now use Strapi to do so.\nThe [documentation for editors](https://freesewing.dev/editors/howtos/) has already been updated.\n\nIf you want a Strapi account, please let us know [on our Discord server](https://discord.freesewing.org)\n\n## Thanks, I hate it\n\nThese are the first changes to come out of [project 2022](/blog/project-2022) and they impact the way contributors & translators work, which is potentially frustrating to people.\n\nApart from doing my work on project 2022 in the open, I've also tried to create some buy-in by adding a discussion on these changes to the [agenda of the contributor call of 3 weeks ago](https://github.com/freesewing/freesewing/issues/1241). \n\nAs that agenda item was not picked up, it is possible that people feel blind-sighted by these changes. If that is the case, feel free to reach out to me to discuss where I'm going with this.", + "caption": "This migration marks the first important changes to come out of project 2022", + "slug": "migration-to-strapi", + "date": "2021-08-26", + "title": "We migrated to strapi and moved our markdown content into our monorepo", + "linktitle": "Migration to Strapi", + "locale": "en", + "published_at": "2021-08-26T12:41:37.022Z", + "createdAt": "2021-08-26T12:14:45.394Z", + "updatedAt": "2021-08-26T12:41:37.039Z", + "__v": 0, + "author": { + "localizations": [], + "_id": "60eae49cdb32b45d5c4d6d93", + "about": "Joost is FreeSewing's maintainer. As an introvert, he enjoys making clothes and shoes since you don't have to leave the house to do so 🙈\n\nYou can follow him as [j__st on Twitter](https://twitter.com/j__st) or [joostdecock on Github](https://github.com/joostdecock).", + "name": "joost", + "displayname": "Joost De Cock", + "createdAt": "2021-07-11T12:31:24.779Z", + "updatedAt": "2021-09-19T14:23:43.748Z", + "__v": 0, + "picture": { + "_id": "6134ca45cd9dc073cc08db6a", + "name": "tumblr_ok2vhiYCDh1qbhhcgo1_1280.jpg", + "alternativeText": "Joost De Cock", + "caption": "Joost De Cock", + "hash": "tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e", + "ext": ".jpg", + "mime": "image/jpeg", + "size": 76.85, + "width": 1080, + "height": 810, + "url": "/uploads/tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e.jpg", + "formats": { + "thumbnail": { + "name": "thumbnail_tumblr_ok2vhiYCDh1qbhhcgo1_1280.jpg", + "hash": "thumbnail_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 208, + "height": 156, + "size": 6.22, + "path": null, + "url": "/uploads/thumbnail_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e.jpg" + }, + "large": { + "name": "large_tumblr_ok2vhiYCDh1qbhhcgo1_1280.jpg", + "hash": "large_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 1000, + "height": 750, + "size": 85.96, + "path": null, + "url": "/uploads/large_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e.jpg" + }, + "medium": { + "name": "medium_tumblr_ok2vhiYCDh1qbhhcgo1_1280.jpg", + "hash": "medium_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 600, + "height": 450, + "size": 36, + "path": null, + "url": "/uploads/medium_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e.jpg" + }, + "small": { + "name": "small_tumblr_ok2vhiYCDh1qbhhcgo1_1280.jpg", + "hash": "small_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 400, + "height": 300, + "size": 17.49, + "path": null, + "url": "/uploads/small_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e.jpg" + }, + "xsmall": { + "name": "xsmall_tumblr_ok2vhiYCDh1qbhhcgo1_1280.jpg", + "hash": "xsmall_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 200, + "height": 150, + "size": 5.99, + "path": null, + "url": "/uploads/xsmall_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e.jpg" + } + }, + "provider": "local", + "related": [ + "60eae49cdb32b45d5c4d6d93", + "60fade591972003530e03b39", + "60fadf5e19815b356b32781e" + ], + "createdAt": "2021-09-05T13:46:45.753Z", + "updatedAt": "2021-09-19T14:24:00.480Z", + "__v": 0, + "id": "6134ca45cd9dc073cc08db6a" + }, + "locale": "en", + "slug": "joostdecock", + "id": "60eae49cdb32b45d5c4d6d93" + }, + "image": { + "_id": "61276adbfca7f36c6a727a6a", + "name": "pexels-pixabay-315939.jpg", + "alternativeText": "", + "caption": "", + "hash": "pexels_pixabay_315939_c82497916e", + "ext": ".jpg", + "mime": "image/jpeg", + "size": 279.85, + "width": 1920, + "height": 1280, + "url": "/uploads/pexels_pixabay_315939_c82497916e.jpg", + "formats": { + "thumbnail": { + "name": "thumbnail_pexels-pixabay-315939.jpg", + "hash": "thumbnail_pexels_pixabay_315939_c82497916e", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 234, + "height": 156, + "size": 9.17, + "path": null, + "url": "/uploads/thumbnail_pexels_pixabay_315939_c82497916e.jpg" + }, + "large": { + "name": "large_pexels-pixabay-315939.jpg", + "hash": "large_pexels_pixabay_315939_c82497916e", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 1000, + "height": 667, + "size": 97.01, + "path": null, + "url": "/uploads/large_pexels_pixabay_315939_c82497916e.jpg" + }, + "medium": { + "name": "medium_pexels-pixabay-315939.jpg", + "hash": "medium_pexels_pixabay_315939_c82497916e", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 600, + "height": 400, + "size": 43.19, + "path": null, + "url": "/uploads/medium_pexels_pixabay_315939_c82497916e.jpg" + }, + "small": { + "name": "small_pexels-pixabay-315939.jpg", + "hash": "small_pexels_pixabay_315939_c82497916e", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 400, + "height": 267, + "size": 22.21, + "path": null, + "url": "/uploads/small_pexels_pixabay_315939_c82497916e.jpg" + }, + "xsmall": { + "name": "xsmall_pexels-pixabay-315939.jpg", + "hash": "xsmall_pexels_pixabay_315939_c82497916e", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 200, + "height": 133, + "size": 7.14, + "path": null, + "url": "/uploads/xsmall_pexels_pixabay_315939_c82497916e.jpg" + } + }, + "provider": "local", + "related": [ + "612785b5fca7f36c6a727a6b" + ], + "createdAt": "2021-08-26T10:20:11.355Z", + "updatedAt": "2021-08-26T12:14:45.398Z", + "__v": 0, + "id": "61276adbfca7f36c6a727a6a" + }, + "id": "612785b5fca7f36c6a727a6b" + }, + "blog/pattern-docs-option-sampling": { + "dev": true, + "localizations": [], + "_id": "6128ccacfca7f36c6a727a70", + "title": "Auto-generated images showing option impact to improve documentation for pattern options", + "date": "2021-08-27", + "linktitle": "Sampling pattern options for bettter docs", + "slug": "pattern-docs-option-sampling", + "caption": "Much like these steps, intervals in sampled options form a rainbow in the auto-generated images", + "body": "Yesterday, I merged our markdown content into our monorepo, and archived our markdown repository. Our documentation now lives side-by-side with our code, and that opens up some options that previously would have been cumbersome to implement. \n\nToday, I went ahead and added auto-generated images to the documentation page for all pattern options. The idea is simple: For each option, we [sample the option](https://freesewing.dev/reference/api/pattern/sampleoption/) which generates an SVG image like the one below:\n\n\n\nWe currently have 761 pattern options, so after generating the image, I went ahead and added it at the bottom of each of the documentation pages.\n\n## No more shared option pages\n\nTo make this possible, I first had to de-dupe a bunch of documentation pages that were shared between various patterns. Patterns that extend another pattern often share a bunch of options, and initially I added logic to the frontend code to avoid having to create all this documenation.\n\nHowever, this added logic makes it harder for people to wrap their head around our frontend, and understand how things work (where is this documentation coming from?). It also meant that sometimes the description of the option was poorly matched to the pattern. For example, the documentation would talk about a *block* because the option documentation is re-used from Brian, when the user is browsing documentation for a different design that happens to extend Brian and use this option.\n\nI had to add a bunch of new markdown files and folders for this, but it will make organisation of the documentation more straight-forward, and in general simpler to *get* how the website works, which was a goal I set for myself in [project 2022](/blog/project-2022).\n\nAnd while in principle this would add a bunch of translation work, these are all strings that have been translated before, so I triggered an automatic translation based on the so-called *translation memory* in Crowdin. (This translation memory is a list of strings and their translations that have been translated and approved inside the project).\n\nIf you see some pattern options where this doesn't work, or looks weird, [let me know](https://discord.freesewing.org/).\n", + "locale": "en", + "published_at": "2021-08-27T11:31:14.661Z", + "createdAt": "2021-08-27T11:29:48.548Z", + "updatedAt": "2021-08-27T12:05:59.065Z", + "__v": 0, + "image": { + "_id": "6128cce1fca7f36c6a727a72", + "name": "pexels-george-becker-122480.jpg", + "alternativeText": "", + "caption": "", + "hash": "pexels_george_becker_122480_5e1723471f", + "ext": ".jpg", + "mime": "image/jpeg", + "size": 444.06, + "width": 1920, + "height": 1278, + "url": "/uploads/pexels_george_becker_122480_5e1723471f.jpg", + "formats": { + "thumbnail": { + "name": "thumbnail_pexels-george-becker-122480.jpg", + "hash": "thumbnail_pexels_george_becker_122480_5e1723471f", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 234, + "height": 156, + "size": 7.73, + "path": null, + "url": "/uploads/thumbnail_pexels_george_becker_122480_5e1723471f.jpg" + }, + "large": { + "name": "large_pexels-george-becker-122480.jpg", + "hash": "large_pexels_george_becker_122480_5e1723471f", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 1000, + "height": 666, + "size": 109.82, + "path": null, + "url": "/uploads/large_pexels_george_becker_122480_5e1723471f.jpg" + }, + "medium": { + "name": "medium_pexels-george-becker-122480.jpg", + "hash": "medium_pexels_george_becker_122480_5e1723471f", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 600, + "height": 399, + "size": 38.4, + "path": null, + "url": "/uploads/medium_pexels_george_becker_122480_5e1723471f.jpg" + }, + "small": { + "name": "small_pexels-george-becker-122480.jpg", + "hash": "small_pexels_george_becker_122480_5e1723471f", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 400, + "height": 266, + "size": 18.42, + "path": null, + "url": "/uploads/small_pexels_george_becker_122480_5e1723471f.jpg" + }, + "xsmall": { + "name": "xsmall_pexels-george-becker-122480.jpg", + "hash": "xsmall_pexels_george_becker_122480_5e1723471f", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 200, + "height": 133, + "size": 6.08, + "path": null, + "url": "/uploads/xsmall_pexels_george_becker_122480_5e1723471f.jpg" + } + }, + "provider": "local", + "related": [ + "6128ccacfca7f36c6a727a70" + ], + "createdAt": "2021-08-27T11:30:41.808Z", + "updatedAt": "2021-08-27T11:31:12.950Z", + "__v": 0, + "id": "6128cce1fca7f36c6a727a72" + }, + "author": { + "localizations": [], + "_id": "60eae49cdb32b45d5c4d6d93", + "about": "Joost is FreeSewing's maintainer. As an introvert, he enjoys making clothes and shoes since you don't have to leave the house to do so 🙈\n\nYou can follow him as [j__st on Twitter](https://twitter.com/j__st) or [joostdecock on Github](https://github.com/joostdecock).", + "name": "joost", + "displayname": "Joost De Cock", + "createdAt": "2021-07-11T12:31:24.779Z", + "updatedAt": "2021-09-19T14:23:43.748Z", + "__v": 0, + "picture": { + "_id": "6134ca45cd9dc073cc08db6a", + "name": "tumblr_ok2vhiYCDh1qbhhcgo1_1280.jpg", + "alternativeText": "Joost De Cock", + "caption": "Joost De Cock", + "hash": "tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e", + "ext": ".jpg", + "mime": "image/jpeg", + "size": 76.85, + "width": 1080, + "height": 810, + "url": "/uploads/tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e.jpg", + "formats": { + "thumbnail": { + "name": "thumbnail_tumblr_ok2vhiYCDh1qbhhcgo1_1280.jpg", + "hash": "thumbnail_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 208, + "height": 156, + "size": 6.22, + "path": null, + "url": "/uploads/thumbnail_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e.jpg" + }, + "large": { + "name": "large_tumblr_ok2vhiYCDh1qbhhcgo1_1280.jpg", + "hash": "large_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 1000, + "height": 750, + "size": 85.96, + "path": null, + "url": "/uploads/large_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e.jpg" + }, + "medium": { + "name": "medium_tumblr_ok2vhiYCDh1qbhhcgo1_1280.jpg", + "hash": "medium_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 600, + "height": 450, + "size": 36, + "path": null, + "url": "/uploads/medium_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e.jpg" + }, + "small": { + "name": "small_tumblr_ok2vhiYCDh1qbhhcgo1_1280.jpg", + "hash": "small_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 400, + "height": 300, + "size": 17.49, + "path": null, + "url": "/uploads/small_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e.jpg" + }, + "xsmall": { + "name": "xsmall_tumblr_ok2vhiYCDh1qbhhcgo1_1280.jpg", + "hash": "xsmall_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e", + "ext": ".jpg", + "mime": "image/jpeg", + "width": 200, + "height": 150, + "size": 5.99, + "path": null, + "url": "/uploads/xsmall_tumblr_ok2vhi_YC_Dh1qbhhcgo1_1280_41d8d5799e.jpg" + } + }, + "provider": "local", + "related": [ + "60eae49cdb32b45d5c4d6d93", + "60fade591972003530e03b39", + "60fadf5e19815b356b32781e" + ], + "createdAt": "2021-09-05T13:46:45.753Z", + "updatedAt": "2021-09-19T14:24:00.480Z", + "__v": 0, + "id": "6134ca45cd9dc073cc08db6a" + }, + "locale": "en", + "slug": "joostdecock", + "id": "60eae49cdb32b45d5c4d6d93" + }, + "id": "6128ccacfca7f36c6a727a70" + } +} \ No newline at end of file diff --git a/packages/freesewing.dev/prebuild/strapi.blog.js b/packages/freesewing.dev/prebuild/strapi.blog.js new file mode 100644 index 00000000000..58289f6727a --- /dev/null +++ b/packages/freesewing.dev/prebuild/strapi.blog.js @@ -0,0 +1,4 @@ +import { posts as en } from './strapi.blog.en.js' + +export default { en } + diff --git a/packages/freesewing.shared/config/freesewing.mjs b/packages/freesewing.shared/config/freesewing.mjs new file mode 100644 index 00000000000..844f30bc96e --- /dev/null +++ b/packages/freesewing.shared/config/freesewing.mjs @@ -0,0 +1,7 @@ +export const languages = ['en','de','es','fr','nl'] + +export const strapiHost = 'https://posts.freesewing.org' + +export const monorepo = 'https://github.com/freesewing/freesewing' + + diff --git a/packages/freesewing.shared/prebuild/index.mjs b/packages/freesewing.shared/prebuild/index.mjs index b3d5306edf6..68a19dc4b1c 100644 --- a/packages/freesewing.shared/prebuild/index.mjs +++ b/packages/freesewing.shared/prebuild/index.mjs @@ -1,10 +1,12 @@ import { prebuildMdx } from './mdx.mjs' +import { prebuildStrapi } from './strapi.mjs' const SITE = process.env.SITE const LANG = process.env.LANG const run = async () => { - await prebuildMdx(SITE, LANG) + //await prebuildMdx(SITE, LANG) + await prebuildStrapi(SITE, LANG) } run() diff --git a/packages/freesewing.shared/prebuild/mdx.mjs b/packages/freesewing.shared/prebuild/mdx.mjs index 5ccc61b71cb..ca7bb4e01a2 100644 --- a/packages/freesewing.shared/prebuild/mdx.mjs +++ b/packages/freesewing.shared/prebuild/mdx.mjs @@ -1,5 +1,6 @@ import path from 'path' import fs from 'fs' +import { languages, strapiHost } from '../config/freesewing.mjs' import rdir from 'recursive-readdir' import { unified } from 'unified' import remarkParser from 'remark-parse' @@ -21,7 +22,7 @@ import { remarkIntroPlugin } from './remark-intro-plugin.mjs' * - folder: the root folder to look in * - lang: the language files to looks for */ -const getMdxFileList = async (folder, lang='en') => { +const getMdxFileList = async (folder, lang) => { let allFiles try { allFiles = await rdir(folder) @@ -69,42 +70,46 @@ const mdxMetaInfo = async file => await unified() /* * Main method that does what needs doing */ -export const prebuildMdx = async(site, lang) => { +export const prebuildMdx = async(site) => { // Say hi console.log() - console.log('Prebuilding MDX for:') - console.log(` - Website: freesewing.${site}`) - console.log(` - Language: ${lang}`) + console.log(`Prebuilding MDX for freesewing.${site}`) console.log() // Setup MDX root path const mdxRoot = path.resolve('..', '..', 'markdown', site) - // Get list of filenames - const list = await getMdxFileList(mdxRoot, lang) + // Loop over languages + for (const lang of (site === 'dev' ? ['en'] : languages)) { - // Parse them for title and intro - const pages = {} - for (const file of list) { - const slug = fileToSlug(file, site, lang) - if (slug) { - const meta = await mdxMetaInfo(file) - if (meta) { - pages[slug] = { - ...meta.data, - slug, - order: meta?.data?.order - ? `${meta.data.order}${meta.data.title}` - : meta.data.title + console.log(` - Language: ${lang}`) + + // Get list of filenames + const list = await getMdxFileList(mdxRoot, lang) + + // Parse them for title and intro + const pages = {} + for (const file of list) { + const slug = fileToSlug(file, site, lang) + if (slug) { + const meta = await mdxMetaInfo(file) + if (meta) { + pages[slug] = { + ...meta.data, + slug, + order: meta?.data?.order + ? `${meta.data.order}${meta.data.title}` + : meta.data.title + } } } } - } - fs.writeFileSync( - path.resolve('..', `freesewing.${site}`, 'prebuild', 'mdx-pages.json'), - JSON.stringify(pages, null ,2) - ) + fs.writeFileSync( + path.resolve('..', `freesewing.${site}`, 'prebuild', `mdx.${lang}.js`), + `export default ${JSON.stringify(pages, null ,2)}` + ) + } } diff --git a/packages/freesewing.shared/prebuild/strapi.mjs b/packages/freesewing.shared/prebuild/strapi.mjs new file mode 100644 index 00000000000..0e4b57652da --- /dev/null +++ b/packages/freesewing.shared/prebuild/strapi.mjs @@ -0,0 +1,70 @@ +import path from 'path' +import fs from 'fs' +import axios from 'axios' +import { languages, strapiHost } from '../config/freesewing.mjs' +//import rdir from 'recursive-readdir' +//import { unified } from 'unified' +//import remarkParser from 'remark-parse' +//import remarkCompiler from 'remark-stringify' +//import remarkFrontmatter from 'remark-frontmatter' +//import remarkFrontmatterExtractor from 'remark-extract-frontmatter' +//import vfileReporter from 'vfile-reporter' +//import { readSync } from 'to-vfile' +//import yaml from 'yaml' +//import { remarkIntroPlugin } from './remark-intro-plugin.mjs' + + +/* + * Helper method to create the API url for retrieval of Strapi posts + */ +const buildUrl = (type, site, lang) => (type === 'blog') + ? `${strapiHost}/blogposts?_locale=${lang}&_sort=date:ASC&dev_${site === 'dev' ? 'eq' : 'ne'}=true` + : `${strapiHost}/showcaseposts?_locale=${lang}&_sort=date:ASC` + +/* + * Helper method to load posts from Strapi + */ +const getPosts = async (type, site, lang) => { + let res + try { + res = await axios.get(buildUrl(type, site, lang)) + } + catch (err) { + console.log(err) + } + const posts = {} + for (const post of res.data) posts[`${type}/${post.slug}`] = post + + return posts +} + + +/* + * Main method that does what needs doing + */ +export const prebuildStrapi = async(site, lang) => { + + // Say hi + console.log() + console.log('Prebuilding Strapi content for:') + console.log(` - Website: freesewing.${site}`) + console.log(` - Language: ${lang}`) + console.log() + + // What types of content to load + const types = ['blog'] + if (site === 'org') types.push('showcase') + + const all = {} + for (const type of types) { + // Loop over languages + for (const lang of (site === 'dev' ? ['en'] : languages)) { + all[type] = await getPosts(type, process.env.SITE, lang) + fs.writeFileSync( + path.resolve('..', `freesewing.${site}`, 'prebuild', `strapi.${type}.${lang}.js`), + `export const posts = ${JSON.stringify(all[type], null, 2)}` + ) + } + } +} +