diff --git a/packages/freesewing.org/CHANGELOG.md b/packages/freesewing.org/CHANGELOG.md deleted file mode 100644 index ad0c3844f55..00000000000 --- a/packages/freesewing.org/CHANGELOG.md +++ /dev/null @@ -1,9 +0,0 @@ -# Change log for: freesewing.org - - - -This is the **initial release**, and the start of this change log. - -> Prior to version 2, FreeSewing was not a JavaScript project. -> As such, that history is out of scope for this change log. - diff --git a/packages/freesewing.org/components/footer.js b/packages/freesewing.org/components/footer.js new file mode 100644 index 00000000000..59b0ca7fd3c --- /dev/null +++ b/packages/freesewing.org/components/footer.js @@ -0,0 +1,166 @@ +import NextLink from 'next/link' +import Logo from 'shared/components/logos/freesewing.js' +import contributors from 'site/prebuild/allcontributors.js' +import patrons from 'site/prebuild/patrons.js' +import OsiLogo from 'shared/components/logos/osi.js' +import CreativeCommonsLogo from 'shared/components/logos/cc.js' +import CcByLogo from 'shared/components/logos/cc-by.js' + +const Link = ({ href, txt }) => ( + + {txt} + +) +const link = "text-secondary font-bold hover:pointer hover:underline px-1" + +const social = { + Discord: 'https://discord.freesewing.org/', + Instagram: 'https://instagram.com/freesewing_org', + Facebook: 'https://www.facebook.com/groups/627769821272714/', + Github: 'https://github.com/freesewing', + Reddit: 'https://www.reddit.com/r/freesewing/', + Twitter: 'https://twitter.com/freesewing_org', +} + +const Footer = ({ app }) => ( + +) + +export default Footer + diff --git a/packages/freesewing.org/components/header.js b/packages/freesewing.org/components/header.js new file mode 100644 index 00000000000..3d799e5fe76 --- /dev/null +++ b/packages/freesewing.org/components/header.js @@ -0,0 +1,109 @@ +import { useState, useEffect } from 'react' +import Logo from 'shared/components/logos/freesewing.js' +import Link from 'next/link' +import ThemePicker from 'shared/components/theme-picker.js' +import CloseIcon from 'shared/components/icons/close.js' +import MenuIcon from 'shared/components/icons/menu.js' +import SearchIcon from 'shared/components/icons/search.js' + +const Right = props => ( + + + +) +const Left = props => ( + + + +) + +const Header = ({ app, setSearch }) => { + + const [prevScrollPos, setPrevScrollPos] = useState(0) + const [show, setShow] = useState(true) + + const handleScroll = () => { + const curScrollPos = (typeof window !== 'undefined') ? window.pageYOffset : 0 + if (curScrollPos >= prevScrollPos) { + if (show && curScrollPos > 20) setShow(false) + } + else setShow(true) + setPrevScrollPos(curScrollPos) + } + + useEffect(() => { + if (typeof window !== 'undefined') { + window.addEventListener('scroll', handleScroll) + return () => window.removeEventListener('scroll', handleScroll) + } + }, [prevScrollPos, show, handleScroll]) + + + return ( +
+
+
+ +
+ +
+ +
+ + + + + + + + freesewing.dev + + +
+
+ +
+
+
+
+
+ ) +} + +export default Header diff --git a/packages/freesewing.org/components/help-us.js b/packages/freesewing.org/components/help-us.js new file mode 100644 index 00000000000..772a9c3c829 --- /dev/null +++ b/packages/freesewing.org/components/help-us.js @@ -0,0 +1,49 @@ +import Popout from 'shared/components/popout.js' + +const HelpUs = ({ mdx=false, slug='/' }) => ( +
+ Click here to learn how you can help us improve this page + {mdx && ( + +
Found a mistake?
+ You can edit this page on Github and help us improve our documentation. +
+ )} + +
Does this look ok?
+ +

+ If it looks ok, great! But if not, please let me know about it. + Either by + reaching out on Discord + or feel free to create + an issue on Github. +

+
Why do you ask?
+

+ I recently added a backend endpoint to auto-generate pretty (I hope) Open Graph images. + They are those little preview images you see when you paste a link in Discord (for example). +

+

+ This idea is that it will auto-generate an image, but I am certain there are some edge cases + where that will not work. + There are hundreds of pages on this website and checking them all one by one is not something + I see myself doing. But since you are here on this page, perhaps you could see if the image + above looks ok. +

+

Thank you, I really appreciate your help with this.

+
+
+) + +export default HelpUs + diff --git a/packages/freesewing.org/components/search.js b/packages/freesewing.org/components/search.js new file mode 100644 index 00000000000..37c3138db3f --- /dev/null +++ b/packages/freesewing.org/components/search.js @@ -0,0 +1,205 @@ +import { useState, useRef, useEffect } from 'react' +import Link from 'next/link' +import { useRouter } from 'next/router' + +import algoliasearch from 'algoliasearch/lite'; +import { useHotkeys } from 'react-hotkeys-hook' +import { InstantSearch, connectHits, connectHighlight, connectSearchBox } from 'react-instantsearch-dom' +import config from 'site/freesewing.config.js' + +const searchClient = algoliasearch(config.algolia.app, config.algolia.key) + +const Hits = props => { + + // When we hit enter in the text field, we want to navigate to the result + // which means we must make the result links available in the input somehow + // so let's stuff them in a data attribute + const links = props.hits.map(hit => hit.page) + props.input.current.setAttribute('data-links', JSON.stringify(links)) + + return props.hits.map((hit, index) => ( + + )) +} + +const CustomHits = connectHits(Hits); + +const Highlight = ({ highlight, attribute, hit, snippet=false }) => { + const parsedHit = highlight({ + highlightProperty: snippet ? '_snippetResult' : '_highlightResult', + attribute, + hit, + }); + + return parsedHit.map((part, index) => part.isHighlighted + ? {part.value} + : {part.value} + ) +} + +const CustomHighlight = connectHighlight(Highlight); + +const Hit = props => ( +
+ + + + {props.hit?._highlightResult?.title + ? + : props.hit.title + } + + {props.hit.page.split('/')[1]} + + + {props.hit?._snippetResult?.body && ( + + + + + + )} + {props.hit?._highlightResult?.page && ( + + + + + + )} +
+) + +// We use this for trapping ctrl-c +let prev +const handleInputKeydown = (evt, setSearch, setActive, active, router) => { + if (evt.key === 'Escape') setSearch(false) + if (evt.key === 'ArrowDown') setActive(act => act + 1) + if (evt.key === 'ArrowUp') setActive(act => act - 1) + if (evt.key === 'Enter') { + // Trigger navigation + if (evt?.target?.dataset?.links) { + router.push(JSON.parse(evt.target.dataset.links)[active]) + setSearch(false) + } + } +} + +const SearchBox = props => { + + const input = useRef(null) + const router = useRouter() + useHotkeys('ctrl+x', () => { + input.current.value = '' + }) + if (input.current && props.active < 0) input.current.focus() + + const { currentRefinement, isSearchStalled, refine, setSearch, setActive } = props + + return ( +
+
evt.preventDefault()}> +
+
+ refine(event.currentTarget.value)} + onKeyDown={(evt) => handleInputKeydown(evt, setSearch, setActive, props.active, router)} + className="input lg:input-lg input-bordered input-neutral w-full pr-16" + placeholder='Type to search' + /> + +
+ +
+
+ { + input.current + && input.current.value.length > 0 + && + } +
+
+
+
+ +
+
+
+ ) +} + +const CustomSearchBox = connectSearchBox(SearchBox); + +const Search = props => { + + const [active, setActive] = useState(0) + useHotkeys('esc', () => props.setSearch(false)) + useHotkeys('up', () => { + if (active) setActive(act => act - 1) + }) + useHotkeys('down', () => { + setActive(act => act + 1) + }) + useHotkeys('down', () => { + console.log('enter', active) + }) + + const stateProps = { + setSearch: props.setSearch, + setMenu: props.setMenu, + active, setActive + } + + return ( + + + + ) +} + +export default Search diff --git a/packages/freesewing.org/freesewing.config.js b/packages/freesewing.org/freesewing.config.js new file mode 100644 index 00000000000..e5e3a098edd --- /dev/null +++ b/packages/freesewing.org/freesewing.config.js @@ -0,0 +1,12 @@ +const config = { + algolia: { + app: 'MA0Y5A2PF0', // Application ID + index: 'canary_freesewing.dev', + key: '589c7a7e4d9c95a4f12868581259bf3a', // Search-only API key + }, + strapi: 'https://posts.freesewing.org', + monorepo: 'https://github.com/freesewing/freesewing' +} + +export default config + diff --git a/packages/freesewing.org/hooks/useApp.js b/packages/freesewing.org/hooks/useApp.js new file mode 100644 index 00000000000..c36aa30fcec --- /dev/null +++ b/packages/freesewing.org/hooks/useApp.js @@ -0,0 +1,99 @@ +import { useState } from 'react' +import set from 'lodash.set' +// Stores state in local storage +import useLocalStorage from 'shared/hooks/useLocalStorage.js' +// Translation +import { en } from '@freesewing/i18n' +// Prebuild navigation +import prebuildNavigation from 'site/prebuild/navigation.js' + +function useApp(full = true) { + + // User color scheme preference + const prefersDarkMode = (typeof window !== 'undefined' && typeof window.matchMedia === 'function') + ? window.matchMedia(`(prefers-color-scheme: dark`).matches + : null + + // Persistent state + const [account, setAccount] = useLocalStorage('account', { username: false }) + const [theme, setTheme] = useLocalStorage('theme', prefersDarkMode ? 'dark' : 'light') + const [language, setLanguage] = useLocalStorage('language', 'en') + + // React State + const [primaryMenu, setPrimaryMenu] = useState(false) + const [navigation, setNavigation] = useState(prebuildNavigation[language]) + const [slug, setSlug] = useState('/') + const [loading, setLoading] = useState(false) + + // State methods + const togglePrimaryMenu = () => setPrimaryMenu(!primaryMenu) + const openPrimaryMenu = () => setPrimaryMenu(true) + const closePrimaryMenu = () => setPrimaryMenu(false) + + /* + * Hot-update navigation method + */ + const updateNavigation = (path, content) => { + const newNavigation = {...navigation} + if (typeof path === 'string') { + path = (path.slice(0,1) === '/') + ? path.slice(1).split('/') + : path.split('/') + } + setNavigation(set(navigation, path, content)) + } + + /* + * Translation method + * + * Note that freesewing.dev is only available in English + * however we use certain shared code/components between + * freesewing.dev and freesewing.org, so we still need + * a translation method + */ + const t = (key=false, vals=false) => { + if (!key) return '' + if (!en.strings[key]) return key + let val = en.strings[key] + if (vals) { + for (const [search, replace] of Object.entries(vals)) { + val = val.replace(/search/g, replace) + } + } + + return val + } + + + return { + // Static vars + site: 'dev', + + // State + language, + loading, + navigation, + primaryMenu, + slug, + theme, + + // State setters + setLanguage, + setLoading, + setNavigation, + setPrimaryMenu, + setSlug, + setTheme, + startLoading: () => { setLoading(true); setPrimaryMenu(false) }, // Always close menu when navigating + stopLoading: () => setLoading(false), + updateNavigation, + + // State handlers + togglePrimaryMenu, + + } + +} + +export default useApp + diff --git a/packages/freesewing.org/hooks/useMdx.js b/packages/freesewing.org/hooks/useMdx.js new file mode 100644 index 00000000000..a10f3d8970b --- /dev/null +++ b/packages/freesewing.org/hooks/useMdx.js @@ -0,0 +1,10 @@ +import path from 'path' + +const useMdx = (slug=false) => { + if (!slug) null + const file = ['markdown', 'dev', ...slug.split('/'), 'en.md'].join('/') + const mdx = require(file) + return

{file}

+} + +export default useMdx diff --git a/packages/freesewing.org/next.config.mjs b/packages/freesewing.org/next.config.mjs new file mode 100644 index 00000000000..05d14efe112 --- /dev/null +++ b/packages/freesewing.org/next.config.mjs @@ -0,0 +1,3 @@ +import configBuilder from '../freesewing.shared/config/next.mjs' + +export default configBuilder('dev') diff --git a/packages/freesewing.org/package.json b/packages/freesewing.org/package.json new file mode 100644 index 00000000000..f354465e268 --- /dev/null +++ b/packages/freesewing.org/package.json @@ -0,0 +1,53 @@ +{ + "name": "freesewing.dev", + "version": "2.19.6", + "private": true, + "scripts": { + "dev": "next dev -p 3002", + "develop": "next dev -p 3002", + "prebuild": "SITE=dev node ../freesewing.shared/prebuild/index.mjs", + "build": "next build", + "export": "yarn prebuild && next build && next export", + "start": "next start -p 3002", + "lint": "next lint", + "testdeploy": "next build && next export && netlify-cli deploy", + "deploy": "next build && next export && netlify-cli deploy --prod", + "serve": "pm2 start npm --name 'freesewing.dev' -- run start" + }, + "dependencies": { + "@heroicons/react": "^1.0.5", + "@mdx-js/loader": "^2.0.0-rc.2", + "@mdx-js/mdx": "^2.0.0-rc.2", + "@mdx-js/react": "^2.0.0-rc.2", + "@mdx-js/runtime": "next", + "@tailwindcss/typography": "^0.5.0", + "algoliasearch": "^4.11.0", + "daisyui": "^1.16.2", + "lodash.get": "^4.4.2", + "lodash.orderby": "^4.6.0", + "lodash.set": "^4.3.2", + "netlify-cli": "^8.4.2", + "next": "latest", + "react-hotkeys-hook": "^3.4.4", + "react-instantsearch-dom": "^6.18.0", + "react-markdown": "^7.1.1", + "react-swipeable": "^6.2.0", + "react-timeago": "^6.2.1", + "rehype-highlight": "^5.0.1", + "rehype-sanitize": "^5.0.1", + "rehype-stringify": "^9.0.2", + "remark-copy-linked-files": "https://github.com/joostdecock/remark-copy-linked-files", + "remark-gfm": "^3.0.1", + "remark-jargon": "^2.19.6" + }, + "devDependencies": { + "autoprefixer": "^10.4.0", + "eslint-config-next": "12.0.7", + "js-yaml": "^4.1.0", + "postcss": "^8.4.4", + "tailwindcss": "^3.0.1" + }, + "engines": { + "node": ">=14.18.1" + } +} diff --git a/packages/freesewing.org/pages/[...mdxslug].js b/packages/freesewing.org/pages/[...mdxslug].js new file mode 100644 index 00000000000..afd0c45c24a --- /dev/null +++ b/packages/freesewing.org/pages/[...mdxslug].js @@ -0,0 +1,96 @@ +import Page from 'shared/components/wrappers/page.js' +import useApp from 'site/hooks/useApp.js' +import mdxMeta from 'site/prebuild/mdx.en.js' +import mdxLoader from 'shared/mdx/loader' +import MdxWrapper from 'shared/components/wrappers/mdx' +import Head from 'next/head' +import HelpUs from 'site/components/help-us.js' + +const MdxPage = props => { + + // This hook is used for shared code and global state + const app = useApp() + + /* + * Each page should be wrapped in the Page wrapper component + * You MUST pass it the result of useApp() as the `app` prop + * and for MDX pages you can spread the props.page props to it + * to automatically set the title and navigation + * + * Like breadcrumbs and updating the primary navigation with + * active state + */ + return ( + + + + + + + + + + + + + + + + + + ) +} + +/* + * Default export is the NextJS page object + */ +export default MdxPage + + +/* + * getStaticProps() is used to fetch data at build-time. + * + * On this page, it is loading the mdx (markdown) content + * from the markdown file on disk. + * + * This, in combination with getStaticPaths() below means this + * page will be used to render/generate all markdown/mdx content. + * + * To learn more, see: https://nextjs.org/docs/basic-features/data-fetching + */ +export async function getStaticProps({ params }) { + + const { mdx, intro } = await mdxLoader('en', 'dev', params.mdxslug.join('/')) + + return { + props: { + mdx, + intro: intro.join(' '), + page: { + slug: params.mdxslug.join('/'), + path: '/' + params.mdxslug.join('/'), + slugArray: params.mdxslug, + ...mdxMeta[params.mdxslug.join('/')], + }, + params + } + } +} + +/* + * getStaticPaths() is used to specify for which routes (think URLs) + * this page should be used to generate the result. + * + * On this page, it is returning a list of routes (think URLs) for all + * the mdx (markdown) content. + * That list comes from mdxMeta, which is build in the prebuild step + * and contains paths, titles, and intro for all markdown. + * + * To learn more, see: https://nextjs.org/docs/basic-features/data-fetching + */ +export async function getStaticPaths() { + return { + paths: Object.keys(mdxMeta).map(slug => '/'+slug), + fallback: false + } +} diff --git a/packages/freesewing.org/pages/_app.js b/packages/freesewing.org/pages/_app.js new file mode 100644 index 00000000000..e125a54674f --- /dev/null +++ b/packages/freesewing.org/pages/_app.js @@ -0,0 +1,5 @@ +import 'shared/styles/globals.css' + +const FreeSewingDev = ({ Component, pageProps }) => + +export default FreeSewingDev diff --git a/packages/freesewing.org/pages/blog/[slug].js b/packages/freesewing.org/pages/blog/[slug].js new file mode 100644 index 00000000000..8b9dab90a2c --- /dev/null +++ b/packages/freesewing.org/pages/blog/[slug].js @@ -0,0 +1,133 @@ +import Page from 'shared/components/wrappers/page.js' +import useApp from 'site/hooks/useApp.js' +import strapiLoader from 'shared/strapi/loader' +import { posts } from 'site/prebuild/strapi.blog.en.js' +import TimeAgo from 'react-timeago' +import MdxWrapper from 'shared/components/wrappers/mdx' +import Markdown from 'react-markdown' +import Head from 'next/head' +import HelpUs from 'site/components/help-us.js' + +const strapi = "https://posts.freesewing.org" + +const Author = ({ author }) => ( +
+
+ +
+ +
+ {author?.displayname} +
+
+

+ {author?.displayname} + Wrote this +

+
+ {author?.about} +
+
+
+) + +const PostPage = ({ post, author }) => { + const app = useApp() + + return ( + + + + + + + + + + + + + + + + + ) + + return ( + +
+
+ +
+
+ +
+ ) +} + +export const getStaticProps = async (props) => { + const { post, author } = await strapiLoader('en', 'dev', 'blog', props.params.slug) + + return { props: { post, author, slug: `blog/${props.params.slug}` } } +} + +export const getStaticPaths = async () => { + const paths = [] + for (const post of posts) paths.push({ + params: {slug: post.slug} + }) + + return { + paths, + fallback: false, + } +} + +export default PostPage diff --git a/packages/freesewing.org/pages/blog/index.js b/packages/freesewing.org/pages/blog/index.js new file mode 100644 index 00000000000..36ff2ff78d0 --- /dev/null +++ b/packages/freesewing.org/pages/blog/index.js @@ -0,0 +1,78 @@ +import Page from 'shared/components/wrappers/page.js' +import useApp from 'site/hooks/useApp.js' +import Link from 'next/link' +import { posts } from 'site/prebuild/strapi.blog.en.js' +import orderBy from 'lodash.orderby' +import TimeAgo from 'react-timeago' +import Head from 'next/head' +import HelpUs from 'site/components/help-us.js' + +const strapi = "https://posts.freesewing.org" + +const Preview = ({ app, post }) => ( +
+ + +
+
+
+
+
+ {post.title} +
+

+ by {post.author} +

+
+
+
+
+ +
+) + +const BlogIndexPage = (props) => { + const app = useApp() + + return ( + + + + + + + + + + + + + + +
+ {Object.values(orderBy(posts, ['date'], ['desc'])) + .map(post => ) + } +
+ +
+ ) +} + +export default BlogIndexPage diff --git a/packages/freesewing.org/pages/index.js b/packages/freesewing.org/pages/index.js new file mode 100644 index 00000000000..9d8a307d199 --- /dev/null +++ b/packages/freesewing.org/pages/index.js @@ -0,0 +1,123 @@ +import Page from 'shared/components/wrappers/page.js' +import useApp from 'site/hooks/useApp.js' +import Logo from 'shared/components/logos/freesewing.js' +import Head from 'next/head' +import HelpUs from 'site/components/help-us.js' +import Link from 'next/link' + +const HomePage = (props) => { + const app = useApp() + return ( + + + + + + + + + + + + + + +
+

+ FreeSewing.dev hosts documentation for contributors and developers alike. +
+ For our maker site, and to try our platform, go + to freesewing.org. +

+

What is FreeSewing?

+
+

+ FreeSewing is an open source platform for made-to-measure sewing patterns. +

+

+ @freeSewing/core is a Javascript library for 2D parametric design +

+

+ It has a primary focus is on sewing patterns, + but can be utilized for a variety of similar 2D design tasks. +

+ +

How can I try it out?

+
+

+ You can try it + in the browser + , + in NodeJS + , + or on any Javascript runtime. +

+

+ The includes Deno, AWS Lamba, Cloudflare workers, Vercel Edge functions, Netlify functions, and so on. +

+

+ Or save yourself the trouble, and check freesewing.org for a showcase of our software. +

+ +

+ You son of a bitch, I'm in + + * + +

+
+

+ We are an all-contributors project + and welcome all contributions. +

+

+ Come say hi on Discord, + or check out ways to contribute + to get inspired. +

+

+ Last but certainly not least, you can also support FreeSewing financially: +

+ +
+

Support FreeSewing

+

+ FreeSewing is fuelled by a voluntary subscription model +

+

+ If you think what we do is worthwhile, + and if you can spare a few coins each month without hardship, + please support our work +

+ Become a Patron +
+ +
+
+ ) +} + +export default HomePage diff --git a/packages/freesewing.org/pages/typography.js b/packages/freesewing.org/pages/typography.js new file mode 100644 index 00000000000..49140ad1373 --- /dev/null +++ b/packages/freesewing.org/pages/typography.js @@ -0,0 +1,99 @@ +import { useEffect } from 'react' +import Page from 'shared/components/wrappers/page.js' +import useApp from 'site/hooks/useApp.js' +import Popout from 'shared/components/popout.js' + +const TypographyPage = (props) => { + const app = useApp() + const { updateNavigation } = app + + useEffect(() => { + updateNavigation( + ['typography'], + { + __title: 'Typography', + __linktitle: 'Typography', + __slug: 'typography', + __order: 'typography' + }) + }, [updateNavigation]) + + const p = ( +

+ This paragraph is here to show the vertical spacing between headings and paragraphs. + In addition, let's make it a bit longer so we can see the line height as the text wraps. +

+ ) + + return ( + +
+

This typography page shows an overview of different elements and how they are styled.

+

It's a good starting point for theme development.

+

Headings (this is h2)

+ {p} +

This is h3

{p} +

This is h4

{p} +
This is h5
{p} +
This is h6
{p} +

Links and buttons

+

A regular link looks like this, whereas buttons look like this:

+

Main button styles

+
+ + + + +
+

State button styles

+
+ + + + +
+

Other button styles

+
+ + +
+

Outlined button styles

+
+ + + + +
+

Button sizes

+
+ + + + + + + + +
+

Popouts

+

The Popout component is what powers various custom MDX components under the hood:

+ {['note', 'tip', 'warning', 'fixme', 'link', 'related', 'none'].map(type => { + const props = {} + props[type] = true + return ( +
+

{type}

+ +
I am the {type} title
+ {p} +
+
+ ) + })} +
+
+ ) +} + +export default TypographyPage + diff --git a/packages/freesewing.org/postcss.config.js b/packages/freesewing.org/postcss.config.js new file mode 100644 index 00000000000..85bf46214ea --- /dev/null +++ b/packages/freesewing.org/postcss.config.js @@ -0,0 +1,5 @@ +// Can't seem to make this work as ESM +const config = require('../freesewing.shared/config/postcss.config.js') + +module.exports = config + diff --git a/packages/freesewing.org/public/brands/algolia.svg b/packages/freesewing.org/public/brands/algolia.svg new file mode 100644 index 00000000000..ab6ae5ab524 --- /dev/null +++ b/packages/freesewing.org/public/brands/algolia.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/freesewing.org/public/brands/bugsnag.svg b/packages/freesewing.org/public/brands/bugsnag.svg new file mode 100644 index 00000000000..8abd9ff4ebf --- /dev/null +++ b/packages/freesewing.org/public/brands/bugsnag.svg @@ -0,0 +1,15 @@ + + + + + + + diff --git a/packages/freesewing.org/public/brands/crowdin.svg b/packages/freesewing.org/public/brands/crowdin.svg new file mode 100644 index 00000000000..bbfe90540be --- /dev/null +++ b/packages/freesewing.org/public/brands/crowdin.svg @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + diff --git a/packages/freesewing.org/public/brands/netlify.svg b/packages/freesewing.org/public/brands/netlify.svg new file mode 100644 index 00000000000..8d306be6fb6 --- /dev/null +++ b/packages/freesewing.org/public/brands/netlify.svg @@ -0,0 +1,85 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/packages/freesewing.org/public/favicon.ico b/packages/freesewing.org/public/favicon.ico new file mode 100644 index 00000000000..718d6fea483 Binary files /dev/null and b/packages/freesewing.org/public/favicon.ico differ diff --git a/packages/freesewing.org/public/support.jpg b/packages/freesewing.org/public/support.jpg new file mode 100644 index 00000000000..3c476403b0b Binary files /dev/null and b/packages/freesewing.org/public/support.jpg differ diff --git a/packages/freesewing.org/scripts/algolia.mjs b/packages/freesewing.org/scripts/algolia.mjs new file mode 100644 index 00000000000..ac8255068de --- /dev/null +++ b/packages/freesewing.org/scripts/algolia.mjs @@ -0,0 +1,150 @@ +/* + * This will update (replace really) the Algolia index with the + * current website contents. Or at least the markdown and Strapi + * content + * + * It expects the following environment vars to be set in a + * .env file in the 'packages/freesewing.dev' folder: + * + * ALGOLIA_APP_ID -> probably MA0Y5A2PF0 + * ALGOLIA_API_KEY -> Needs permission to index/create/delete + * ALGOLIA_INDEX -> Name of the index to index to + * + */ +import dotenv from 'dotenv' +import fs from 'fs' +import path from 'path' +import algoliasearch from 'algoliasearch' +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 remarkRehype from 'remark-rehype' +import rehypeSanitize from 'rehype-sanitize' +import rehypeStringify from 'rehype-stringify' +import yaml from 'yaml' +import { getPosts } from '../../freesewing.shared/prebuild/strapi.mjs' +import { getMdxFileList } from '../../freesewing.shared/prebuild/mdx.mjs' +dotenv.config() + +/* + * Initialize Algolia client + */ +const client = algoliasearch(process.env.ALGOLIA_APP_ID, process.env.ALGOLIA_API_KEY) +const index = client.initIndex(process.env.ALGOLIA_INDEX) + +/* + * Turn a Strapi blog post into an object ready for indexing + */ +const transformBlogpost = post => ({ + objectID: `/blog/${post.slug}`, + page: `/blog/${post.slug}`, + title: post.title, + date: post.date, + slug: post.slug, + body: post.body, + author: post.author, + caption: post.caption, + type: 'blog', +}) + +/* + * Turn a Strapi author into an object ready for indexing + */ +const transformAuthor = author => ({ + objectID: `/blog/authors/${author.name}`, + page: `/blog/authors/${author.name}`, + name: author.name, + displayname: author.displayname, + about: author.about, +}) + +/* + * Get and index blog posts and author info from Strapi + */ +const indexStrapiContent = async () => { + + // Say hi + console.log() + console.log(`Indexing Strapi content to Algolia`) + + const authors = {} + const rawPosts = await getPosts('blog', 'dev', 'en') + // Extract list of authors + for (const [slug, post] of Object.entries(rawPosts)) { + authors[post.author.slug] = transformAuthor(post.author) + rawPosts[slug].author = post.author.slug + } + // Index posts to Algolia + index + .saveObjects(Object.values(rawPosts).map(post => transformBlogpost(post))) + .then(({ objectIDs }) => console.log(objectIDs)) + .catch(err => console.log(err)) + // Index authors to Algolia + index + .saveObjects(Object.values(authors)) + .then(({ objectIDs }) => console.log(objectIDs)) + .catch(err => console.log(err)) +} + +/* + * Loads markdown from disk and compiles it into HTML for indexing + */ +const markdownLoader = async file => { + + const md = await fs.promises.readFile(file, 'utf-8') + + const page = await unified() + .use(remarkParser) + .use(remarkCompiler) + .use(remarkFrontmatter) + .use(remarkFrontmatterExtractor, { yaml: yaml.parse }) + .use(remarkRehype) + .use(rehypeSanitize) + .use(rehypeStringify) + .process(md) + const id = file.split('freesewing/markdown/dev').pop().slice(0, -6) + + return { + objectID: id, + page: id, + title: page.data.title, + body: page.value, + type: 'docs', + } +} + + + +/* + * Get and index markdown content + */ +const indexMarkdownContent = async () => { + + // Say hi + console.log() + console.log(`Indexing Markdown content to Algolia`) + + // Setup MDX root path + const mdxRoot = path.resolve('..', '..', 'markdown', 'dev') + + // Get list of filenames + const list = await getMdxFileList(mdxRoot, 'en') + const pages = [] + for (const file of list) pages.push(await markdownLoader(file)) + // Index markdown to Algolia + index + .saveObjects(pages) + .then(({ objectIDs }) => console.log(objectIDs)) + .catch(err => console.log(err)) +} + +const run = async () => { + await indexMarkdownContent() + await indexStrapiContent() + console.log() +} + +run() + diff --git a/packages/freesewing.org/tailwind.config.js b/packages/freesewing.org/tailwind.config.js new file mode 100644 index 00000000000..3f664c97b59 --- /dev/null +++ b/packages/freesewing.org/tailwind.config.js @@ -0,0 +1,4 @@ +// Can't seem to make this work as ESM +const config = require('../freesewing.shared/config/tailwind.config.js') + +module.exports = config