1
0
Fork 0

wip(new-design): Work on v3 for this package

This commit is contained in:
joostdecock 2023-09-29 09:47:54 +02:00
parent 3604cb918d
commit 3553c43633
40 changed files with 364 additions and 1753 deletions

View file

@ -11,6 +11,6 @@ export const cli = async () => {
// Get user input
const choices = await getChoices()
// Create environment from template
// Create environment
createEnvironment(choices)
}

View file

@ -4,7 +4,7 @@ export const config = {
// Whether we're publishing next or latest tags
tag: 'next',
// Minimum node version
node: 16,
node: 18,
// Site to download from
fileUri: 'https://raw.githubusercontent.com',
// Repository to download from
@ -19,7 +19,7 @@ export const config = {
'workbench',
'errors',
'i18n',
'lab',
'sde',
'measurements',
'optiongroups',
'o_bella',
@ -54,8 +54,6 @@ npm-debug.log*
yarn-debug.log*
yarn-error.log*
# e2e test results
playwright-report
`,
fetch: {
config: [
@ -65,176 +63,161 @@ playwright-report
},
],
sites: [
'shared/utils.mjs',
'shared/designs/index.js',
'shared/config/freesewing.mjs',
{
from: 'sde/env.local',
to: 'sde/.env.local',
},
'sde/i18n.config.mjs',
'sde/next-i18next.config.js',
'sde/next.config.mjs',
'sde/package.json',
'sde/postcss.config.js',
'sde/site.config.mjs',
'sde/tailwind.config.mjs',
'sde/hooks/use-design.mjs',
'sde/components/dynamic-org-docs.mjs',
'sde/components/feeds.mjs',
'sde/components/search.mjs',
'sde/components/header/design-picker.mjs',
'sde/components/header/index.mjs',
'sde/components/navigation/modal-menu.mjs',
'sde/components/layouts/bare.mjs',
'sde/components/layouts/default.mjs',
'sde/components/layouts/workbench.mjs',
'sde/components/wrappers/page.mjs',
'sde/design/from-bent/src/back.mjs',
'sde/design/from-bent/src/front.mjs',
'sde/design/from-bent/src/index.mjs',
'sde/design/from-bent/src/top-sleeve.mjs',
'sde/design/from-bent/src/under-sleeve.mjs',
'sde/design/from-bent/i18n/de.json',
'sde/design/from-bent/i18n/en.json',
'sde/design/from-bent/i18n/es.json',
'sde/design/from-bent/i18n/fr.json',
'sde/design/from-bent/i18n/index.mjs',
'sde/design/from-bent/i18n/nl.json',
'sde/design/from-bent/i18n/uk.json',
'sde/design/from-bella/en.json',
'sde/design/from-bella/src/back.mjs',
'sde/design/from-bella/src/front.mjs',
'sde/design/from-bella/src/index.mjs',
'sde/design/from-bella/i18n/de.json',
'sde/design/from-bella/i18n/en.json',
'sde/design/from-bella/i18n/es.json',
'sde/design/from-bella/i18n/fr.json',
'sde/design/from-bella/i18n/index.mjs',
'sde/design/from-bella/i18n/nl.json',
'sde/design/from-bella/i18n/uk.json',
'sde/design/from-breanna/src/back.mjs',
'sde/design/from-breanna/src/front.mjs',
'sde/design/from-breanna/src/index.mjs',
'sde/design/from-breanna/src/sleeve.mjs',
'sde/design/from-breanna/i18n/de.json',
'sde/design/from-breanna/i18n/en.json',
'sde/design/from-breanna/i18n/es.json',
'sde/design/from-breanna/i18n/fr.json',
'sde/design/from-breanna/i18n/index.mjs',
'sde/design/from-breanna/i18n/nl.json',
'sde/design/from-breanna/i18n/uk.json',
'sde/design/from-brian/i18n/de.json',
'sde/design/from-brian/i18n/en.json',
'sde/design/from-brian/i18n/es.json',
'sde/design/from-brian/i18n/fr.json',
'sde/design/from-brian/i18n/index.mjs',
'sde/design/from-brian/i18n/nl.json',
'sde/design/from-brian/i18n/uk.json',
'sde/design/from-brian/src/back.mjs',
'sde/design/from-brian/src/front.mjs',
'sde/design/from-brian/src/index.mjs',
'sde/design/from-brian/src/sleeve.mjs',
'sde/design/from-titan/i18n/de.json',
'sde/design/from-titan/i18n/en.json',
'sde/design/from-titan/i18n/es.json',
'sde/design/from-titan/i18n/fr.json',
'sde/design/from-titan/i18n/index.mjs',
'sde/design/from-titan/i18n/nl.json',
'sde/design/from-titan/i18n/uk.json',
'sde/design/from-titan/src/back.mjs',
'sde/design/from-titan/src/front.mjs',
'sde/design/from-titan/src/index.mjs',
'sde/design/from-scratch/i18n/de.json',
'sde/design/from-scratch/i18n/en.json',
'sde/design/from-scratch/i18n/es.json',
'sde/design/from-scratch/i18n/fr.json',
'sde/design/from-scratch/i18n/index.mjs',
'sde/design/from-scratch/i18n/nl.json',
'sde/design/from-scratch/i18n/uk.json',
'sde/design/from-scratch/src/index.mjs',
'sde/design/from-scratch/src/scratch.mjs',
'sde/design/tutorial/i18n/de.json',
'sde/design/tutorial/i18n/en.json',
'sde/design/tutorial/i18n/es.json',
'sde/design/tutorial/i18n/fr.json',
'sde/design/tutorial/i18n/index.mjs',
'sde/design/tutorial/i18n/nl.json',
'sde/design/tutorial/i18n/uk.json',
'sde/design/tutorial/src/bib.mjs',
'sde/design/tutorial/src/index.mjs',
'sde/public/brands/algolia.svg',
'sde/public/brands/bugsnag.svg',
'sde/public/brands/crowdin.svg',
'sde/public/brands/netlify.svg',
'sde/public/brands/vercel.svg',
'sde/pages/_app.mjs',
'sde/pages/design.mjs',
'sde/pages/index.mjs',
'sde/pages/support.mjs',
'sde/pages/code/index.mjs',
'sde/pages/design/[design].mjs',
'sde/pages/docs/index.mjs',
'sde/pages/sde/en.yaml',
'sde/pages/sde/index.mjs',
'sde/pages/sets/[id].mjs',
'sde/pages/signup/index.mjs',
'sde/pages/patterns/index.mjs',
'sde/pages/patterns/[id]/edit.mjs',
'sde/pages/patterns/[id]/index.mjs',
'sde/pages/signin/index.mjs',
'sde/pages/signin/callback/[provider].mjs',
'sde/pages/account/[platform].mjs',
'sde/pages/account/bio.mjs',
'sde/pages/account/compare.mjs',
'sde/pages/account/consent.mjs',
'sde/pages/account/control.mjs',
'sde/pages/account/email.mjs',
'sde/pages/account/export.mjs',
'sde/pages/account/github.mjs',
'sde/pages/account/img.mjs',
'sde/pages/account/index.mjs',
'sde/pages/account/language.mjs',
'sde/pages/account/mfa.mjs',
'sde/pages/account/newsletter.mjs',
'sde/pages/account/password.mjs',
'sde/pages/account/privacy.mjs',
'sde/pages/account/reload.mjs',
'sde/pages/account/remove.mjs',
'sde/pages/account/restrict.mjs',
'sde/pages/account/units.mjs',
'sde/pages/account/username.mjs',
'sde/pages/account/apikeys/[id].mjs',
'sde/pages/account/apikeys/index.mjs',
'sde/pages/account/bookmarks/[id].mjs',
'sde/pages/account/bookmarks/index.mjs',
'sde/pages/account/sets/[id].mjs',
'sde/pages/account/sets/index.mjs',
'sde/pages/account/patterns/index.mjs',
'sde/pages/account/patterns/[id]/edit.mjs',
'sde/pages/account/patterns/[id]/index.mjs',
'shared/config/cloudflare.mjs',
'shared/config/designs.mjs',
'shared/config/freesewing.config.mjs',
'shared/config/i18n.config.mjs',
'shared/config/next.mjs',
'shared/config/paypal.mjs',
'shared/config/playwright.mjs',
'shared/config/postcss.config.js',
'shared/config/tailwind-force.html',
'shared/config/tailwind.config.js',
'shared/hooks/useGist.mjs',
'shared/hooks/useLocalStorage.mjs',
'shared/hooks/useTheme.mjs',
'shared/mdx/compiler.mjs',
'shared/mdx/loader.mjs',
'shared/mdx/mdx-plugin-toc.mjs',
'shared/mdx/remark-intro-plugin.mjs',
'shared/strapi/loader.js',
'shared/strapi/qa.mjs',
'shared/themes/dark.js',
'shared/themes/hax0r.js',
'shared/themes/index.js',
'shared/themes/lgbtq.js',
'shared/themes/light.js',
'shared/themes/runtime.mjs',
'shared/styles/code.css',
'shared/styles/globals.css',
'shared/styles/svg-freesewing-draft.css',
'shared/prebuild/contributors.mjs',
'shared/prebuild/feed.mjs',
'shared/prebuild/i18n-only.mjs',
'shared/prebuild/i18n.mjs',
'shared/prebuild/index.mjs',
'shared/prebuild/lab.mjs',
'shared/prebuild/md-intro.mjs',
'shared/prebuild/mdx.mjs',
'shared/prebuild/navigation.mjs',
'shared/prebuild/patrons.mjs',
'shared/prebuild/strapi.mjs',
'shared/prebuild/og/index.mjs',
'shared/components/breadcrumbs.mjs',
'shared/components/code.mjs',
'shared/components/copy-to-clipboard.mjs',
'shared/components/docs-link.mjs',
'shared/components/icons.mjs',
'shared/components/json.mjs',
'shared/components/lightbox.mjs',
'shared/components/loader.mjs',
'shared/components/modal.mjs',
'shared/components/link.mjs',
'shared/components/picker.mjs',
'shared/components/popout.mjs',
'shared/components/raw-span.mjs',
'shared/components/ribbon.mjs',
'shared/components/spinner.mjs',
'shared/components/web-link.mjs',
'shared/components/wordmark.mjs',
'shared/components/worm.mjs',
'shared/components/yaml.mjs',
'shared/components/error/error-boundary.mjs',
'shared/components/error/reset-buttons.mjs',
'shared/components/error/view.mjs',
'shared/components/icons/flip.js',
'shared/components/icons/rotate.js',
'shared/components/locale-picker/index.mjs',
'shared/components/locale-picker/locales.de.yaml',
'shared/components/locale-picker/locales.en.yaml',
'shared/components/locale-picker/locales.es.yaml',
'shared/components/locale-picker/locales.fr.yaml',
'shared/components/locale-picker/locales.nl.yaml',
'shared/components/logos/cc-by.mjs',
'shared/components/logos/cc.mjs',
'shared/components/logos/freesewing.mjs',
'shared/components/logos/osi.mjs',
'shared/components/mdx/figure.mjs',
'shared/components/mdx/highlight.mjs',
'shared/components/mdx/http.mjs',
'shared/components/mdx/index.mjs',
'shared/components/mdx/mermaid.mjs',
'shared/components/mdx/prev-next.mjs',
'shared/components/mdx/read-more.mjs',
'shared/components/mdx/tabbed-example.mjs',
'shared/components/mdx/tabs.mjs',
'shared/components/mdx/youtube.mjs',
'shared/components/navigation/aside.mjs',
'shared/components/navigation/primary.mjs',
'shared/components/robot/index.mjs',
'shared/components/robot/poses.mjs',
'shared/components/theme-picker/index.mjs',
'shared/components/theme-picker/themes.de.yaml',
'shared/components/theme-picker/themes.en.yaml',
'shared/components/theme-picker/themes.es.yaml',
'shared/components/theme-picker/themes.fr.yaml',
'shared/components/theme-picker/themes.nl.yaml',
'shared/components/wrappers/img.mjs',
'shared/components/wrappers/mdx.mjs',
'shared/components/wrappers/page.mjs',
'shared/components/wrappers/toc.mjs',
'shared/components/wrappers/workbench.mjs',
'shared/components/workbench/gist.mjs',
'shared/components/workbench/logs.mjs',
'shared/components/workbench/preloaders.mjs',
'shared/components/workbench/sample.mjs',
'shared/components/workbench/edit/index.mjs',
'shared/components/workbench/edit/gist-validator.mjs',
'shared/components/workbench/exporting/export-handler.mjs',
'shared/components/workbench/exporting/export-worker.js',
'shared/components/workbench/exporting/index.mjs',
'shared/components/workbench/exporting/pdf-maker.mjs',
'shared/components/workbench/inputs/design-option-count.mjs',
'shared/components/workbench/inputs/design-option-list.mjs',
'shared/components/workbench/inputs/design-option-pct-deg.mjs',
'shared/components/workbench/inputs/measurement.mjs',
'shared/components/workbench/measurements/index.mjs',
'shared/components/workbench/draft/circle.mjs',
'shared/components/workbench/draft/defs.mjs',
'shared/components/workbench/draft/error.mjs',
'shared/components/workbench/draft/index.mjs',
'shared/components/workbench/draft/part.mjs',
'shared/components/workbench/draft/path.mjs',
'shared/components/workbench/draft/point.mjs',
'shared/components/workbench/draft/snippet.mjs',
'shared/components/workbench/draft/stack.mjs',
'shared/components/workbench/draft/svg.mjs',
'shared/components/workbench/draft/text.mjs',
'shared/components/workbench/draft/utils.mjs',
'shared/components/workbench/layout/default.mjs',
'shared/components/workbench/layout/cut/index.mjs',
'shared/components/workbench/layout/cut/plugin-cut-layout.mjs',
'shared/components/workbench/layout/cut/settings.mjs',
'shared/components/workbench/layout/print/index.mjs',
'shared/components/workbench/layout/print/orientation-picker.mjs',
'shared/components/workbench/layout/print/pagesize-picker.mjs',
'shared/components/workbench/layout/plugin-layout-part.mjs',
'shared/components/workbench/layout/print/settings.mjs',
'shared/components/workbench/layout/draft/buttons.mjs',
'shared/components/workbench/layout/draft/index.mjs',
'shared/components/workbench/layout/draft/stack.mjs',
'shared/components/workbench/menu/index.mjs',
'shared/components/workbench/menu/view.mjs',
'shared/components/workbench/menu/design-options/index.mjs',
'shared/components/workbench/menu/design-options/option-group.mjs',
'shared/components/workbench/menu/design-options/option-input.mjs',
'shared/components/workbench/menu/design-options/option-value.mjs',
'shared/components/workbench/menu/design-options/option.mjs',
'shared/components/workbench/menu/test-design-options/index.mjs',
'shared/components/workbench/menu/test-design-options/option.mjs',
'shared/components/workbench/menu/core-settings/core-setting-bool.mjs',
'shared/components/workbench/menu/core-settings/core-setting-list.mjs',
'shared/components/workbench/menu/core-settings/core-setting-mm.mjs',
'shared/components/workbench/menu/core-settings/core-setting-nr.mjs',
'shared/components/workbench/menu/core-settings/core-setting-only.mjs',
'shared/components/workbench/menu/core-settings/core-setting-sa-bool.mjs',
'shared/components/workbench/menu/core-settings/core-setting-sa-mm.mjs',
'shared/components/workbench/menu/core-settings/index.mjs',
'shared/components/workbench/menu/core-settings/setting.mjs',
'shared/components/workbench/menu/xray/attributes.mjs',
'shared/components/workbench/menu/xray/disable.mjs',
'shared/components/workbench/menu/xray/index.mjs',
'shared/components/workbench/menu/xray/list.mjs',
'shared/components/workbench/menu/xray/log.mjs',
'shared/components/workbench/menu/xray/path-ops.mjs',
'shared/components/workbench/menu/xray/path.mjs',
'shared/components/workbench/menu/xray/point.mjs',
'shared/components/workbench/menu/xray/reset.mjs',
'lab/components/about.mjs',
'lab/components/design-picker.mjs',
'lab/components/header.mjs',
'lab/components/layouts/bare.mjs',
'lab/components/layouts/lab.mjs',
'lab/components/wrappers/layout.mjs',
'lab/components/wrappers/page.mjs',
'shared/config/tailwind.config.mjs',
],
},
}

View file

@ -24,7 +24,7 @@ const nl = '\n'
const tab = ' '
const nlt = nl + tab
// Checks for node 16 or higher
// Checks for node 18 or higher
export const checkNodeVersion = () => {
const node_version = process.version.slice(1).split('.')[0]
if (parseInt(node_version) < config.node) {
@ -47,68 +47,22 @@ export const checkNodeVersion = () => {
}
}
// Helper method to validate the design name
const validateDesignName = (name) => {
if (/^([a-z][a-z0-9_]*)$/.test(name)) return true
else
return ' 🙈 Please use only lowercase letters, digits, or underscores. Names must start with a lowercase letter. 🤷'
}
// Gets user input to figure out what to do
export const getChoices = async () => {
const { template } = await prompts({
type: 'select',
name: 'template',
message: 'What template would you like to use? 📑',
choices: [
{ title: 'Tutorial', value: 'tutorial', description: 'Setup the pattern design tutorial' },
{ title: 'From Scratch', value: 'scratch', description: 'Create a design from scratch' },
{
title: 'Extend Brian',
value: 'brian',
description: 'Extend the Brian design (basic torso block for menswear)',
},
{
title: 'Extend Bent',
value: 'bent',
description: 'Extend the Bent design (like brian with added two-part sleeve)',
},
{
title: 'Extend Bella',
value: 'bella',
description: 'Extend the Bella design (womenswear torso block)',
},
{
title: 'Extend Breanna',
value: 'breanna',
description: 'Extend the Breanna design (womenswear torso block - YMMV)',
},
{
title: 'Extend Titan',
value: 'titan',
description: 'Extend the Titan design (gender-neutral trouser block)',
},
],
initial: 0,
})
let finalName = false // we're going to use this to track whether we stay in the naming loop
let overwrite = true // should we overwrite existing files?
const cwd = process.cwd()
let name // name will go here
let sideStep // allows to do something custom
const cwd = process.cwd()
// while we're not finalized on a name
while (finalName === false) {
// request a name
name =
template === 'tutorial' && name === undefined
? 'tutorial'
: (
name = (
await prompts({
type: 'text',
name: 'name',
message: 'What name would you like the design to have? 🏷️ ([a-z0-9_] only)',
validate: validateDesignName,
message: 'Give a folder name in which we can setup the development environment? 🏷️ ',
})
).name
@ -127,19 +81,28 @@ export const getChoices = async () => {
const { nextStep } = await prompts({
type: 'select',
name: 'nextStep',
message:
'It looks like you already have a design by that name in progress. What should we do?',
message: 'It looks like that folder already exists. What should we do?',
choices: [
{ title: 'Rename', value: 'rename', description: 'Choose a new name for this design' },
{ title: 'Overwrite', value: 'overwrite', description: 'Overwrite the existing design' },
{ title: 'Go back', value: 'rename', description: 'Choose a different folder name' },
{
title: 'Overwrite',
value: 'overwrite',
description: 'Overwrite the contents in the existing folder',
},
{
title: 'Re-initialize',
value: 'reinit',
description:
"Bring in a fresh workbench, but don't overwrite existing design files (useful for updating to the latest dev environment)",
'Re-install depenencies, and update the development environment in this folder',
},
{
title: 'Re-download',
value: 'redownload',
description: 'Update the development environment in this folder',
},
],
})
sideStep = nextStep
// if they said rename, we loop again. otherwise
if (nextStep !== 'rename') {
@ -160,7 +123,7 @@ export const getChoices = async () => {
initial: 0,
})
return { template, name, manager, overwrite }
return { name, manager, overwrite, sideStep }
}
const capitalize = (string) => string.charAt(0).toUpperCase() + string.slice(1)
@ -209,111 +172,6 @@ const copyFileOrTemplate = async (
}
}
// Template the package.json
const copyPackageJson = async (config, choices) => {
const packageJsonTemplate = await readFile(
config.relativeFiles.templates['package.json'],
'utf-8'
)
await copyFileOrTemplate(packageJsonTemplate, config.dest, 'package.json', {
name: choices.name,
tag: config.tag,
dependencies: config.templateData.dependencies,
includeTests: choices.includeTests,
})
}
// Template the design index file
const copyIndexFile = async (config, choices) => {
// Template the index file
const indexTemplate = await readFile(config.relativeFiles.templates['index'], 'utf-8')
// get the part names based on how they are given in the configuration
const partNames = config.complexParts
? config.templateData.parts.map((p) => p.part)
: config.templateData.parts
// write the file
await copyFileOrTemplate(
indexTemplate,
config.dest,
`${designSrcDir}/index.mjs`,
{
name: choices.name,
Name: capitalize(choices.name),
parts: partNames,
},
choices.overwrite
)
}
// Template the part files
const copyPartFiles = async (config, choices) => {
// Template the parts
const partTemplate = await readFile(config.relativeFiles.templates.part, 'utf-8')
// does this design inherit from another?
const doesInherit = !config.templateData.noInheritance
// all part templates need these arguments
const baseConfig = {
name: choices.name, // the name of the design
doesInherit, // whether it's an inherited design
draftUses: {}, // what parameters need to be uncommented in the draft method (default none because part is always uncommented)
}
// if it inherits, we also need the name of the design it inherits from
if (doesInherit) {
baseConfig.baseName = choices.template
baseConfig.BaseName = capitalize(choices.template)
}
// for each part
return config.templateData.parts.map((p) => {
// set up the arguments based on what's in the part's config
const templateArgs = config.complexParts
? {
...baseConfig,
...p,
}
: {
...baseConfig,
part: p,
}
// add an uppercase version of the partName
templateArgs.Part = capitalize(templateArgs.part)
// write the part file
return copyFileOrTemplate(
partTemplate,
config.dest,
`${designSrcDir}/${templateArgs.part}.mjs`,
templateArgs,
choices.overwrite
)
})
}
// Helper method to copy template files
const copyAll = async (config, choices) => {
let promises = []
// Copy shared files
promises = promises.concat(
config.relativeFiles.shared.map((from) => {
if (choices.includeTests || !from.match(/e2e|playwright/))
copyFileOrTemplate(config.source.shared, config.dest, from)
})
)
// template design files
promises.push(copyPackageJson(config, choices))
promises.push(copyIndexFile(config, choices))
promises = promises.concat(copyPartFiles(config, choices))
await Promise.all(promises)
}
// Helper method to run [yarn|npm] install
const installDependencies = async (config, choices) =>
await execa(`${choices.manager} install`, {
@ -322,22 +180,28 @@ const installDependencies = async (config, choices) =>
})
// Helper method to download web environment
const downloadLabFiles = async (config) => {
const downloadFiles = async (config) => {
const promises = []
for (const dir in config.fetch) {
promises.push(
...config.fetch[dir].map(async (file) => {
const to = typeof file === 'string' ? join(config.dest, file) : join(config.dest, file.to)
const to =
typeof file === 'string'
? join(config.dest, file.slice(0, 4) === 'sde/' ? file.slice(4) : file)
: join(config.dest, file.to)
await ensureDir(to)
try {
const res = await axios.get(
`${config.fileUri}/${config.repo}/${config.branch}/${dir}/${
const url = `${config.fileUri}/${config.repo}/${config.branch}/${dir}/${
typeof file === 'string' ? file : file.from
}`
try {
const res = await axios.get(url)
await writeFile(
to,
typeof res.data === 'object' ? JSON.stringify(res.data, null, 2) : res.data
)
await writeFile(to, res.data)
} catch (err) {
console.log(err)
if (err.response?.status === 404) console.log(`404: ${url}`)
else console.log(err)
}
})
)
@ -351,7 +215,7 @@ const initGitRepo = async (config, choices) => {
await copyFileOrTemplate(config.gitignore, config.dest, '.gitignore', {}, choices.overwrite)
return execa(
`git init -b main && git add . && git commit -m ":tada: Initialized ${choices.name} repository"`,
`git init -b main && git add . && git commit -m ":tada: Initialized FreeSewing stand-alone development environment"`,
{
cwd: config.dest,
shell: true,
@ -360,13 +224,13 @@ const initGitRepo = async (config, choices) => {
}
// Tips
const showTips = (config, choices) => {
const showTips = (config, choices) =>
console.log(`
All done 🤓 Your new design ${chalk.yellow.bold(
choices.name
)} was initialized in: ${chalk.green.bold(config.dest)}
All done 🤓 Your FreeSewing development environment was initialized in: ${chalk.green.bold(
config.dest
)}
The code for your design is in the ${chalk.yellow.bold('design')} folder.
The templates for various designs are in the ${chalk.yellow.bold('design')} folder.
The other files and folders are the development environment. You can safely ignore those.
To start your development environment, follow these three steps:
@ -377,41 +241,12 @@ const showTips = (config, choices) => {
)}
3) Now open your browser and navigate to ${chalk.green('http://localhost:8000/')}
${chalk.bold.yellow('🤔 More info & help')}
${chalk.gray('≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡')}`)
if (choices.template === 'tutorial')
console.log(`
Our pattern design tutorial is available at: ${chalk.green(
'https://freesewing.dev/tutorials/pattern-design'
)}
It will walk your through the process step by step.
If you get stuck, reach out to our community on Discord: ${chalk.green(
'https://discord.freesewing.dev/'
)}
The ${chalk.bold('development-help')} channel is a good place to ask questions
Don't be shy to reach out. If something is not clear, that's on us, not on you.
So your feedback really helps us improve our tutorial/documentation.
Thanks for giving FreeSewing a shot. We hope you'll 💜 it.
Thanks for giving FreeSewing a shot. I hope you'll 💜 it.
Have fun 🤓
`)
else
console.log(`
FreeSewing's documentation for developers is available at: ${chalk.green(
'https://freesewing.dev/'
)}
Our community is on Discord: ${chalk.green('https://discord.freesewing.dev/')}
The ${chalk.bold('development-help')} channel is a good place to ask for help if you get stuck
Happy hacking 🤓
`)
}
joost
`)
// Creates the environment based on the user's choices
export const createEnvironment = async (choices) => {
@ -423,88 +258,61 @@ export const createEnvironment = async (choices) => {
shared: join(newDesignDir, `shared`),
}
// Create target directory
await mkdir(config.dest, { recursive: true })
// get the template files in a dictionary
const templates = {}
const templateFiles = await rdir(config.source.templates)
templateFiles.forEach((file) => {
const relativeName = relative(config.source.templates, file).replace(/(\.mjs)*\.mustache/, '')
templates[relativeName] = file
})
config.relativeFiles = {
templates,
shared: (await rdir(config.source.shared)).map((file) => relative(config.source.shared, file)),
}
config.templateData = await import(pathToFileURL(config.source.templateData))
// does this base have parts with a lot of attending config?
config.complexParts = typeof config.templateData.parts[0] === 'object'
// Output a linebreak
console.log()
// Copy/Template files
// Download files from GitHub
try {
await oraPromise(copyAll(config, choices), {
await oraPromise(downloadFiles(config), {
text:
chalk.white.bold('🟨⬜⬜⬜ Copying template files') +
chalk.white.dim(' | Just a moment'),
successText: chalk.white.bold('🟩⬜⬜⬜ Copied template files'),
chalk.white.bold('🟧⬜⬜ Downloading components from GitHub') +
chalk.white.dim(' | Almost there'),
successText: chalk.white.bold('🟩⬜⬜ Downloaded components from GitHub'),
failText: chalk.white.bold(
'🟥⬜⬜⬜ Failed to copy template files | Development environment will not function'
'🟥⬜⬜ Failed to download components from GitHub | The development environment will not function'
),
})
} catch (err) {
console.log(err)
/* no feedback here */
}
if (!choices.sideStep) {
// Create target directory
await mkdir(config.dest, { recursive: true })
// Install dependencies
try {
await oraPromise(installDependencies(config, choices), {
text:
chalk.white.bold('🟩🟨⬜⬜ Installing dependencies') +
chalk.white.bold('🟩🟧⬜ Installing dependencies') +
chalk.white.dim(' | Please wait, this will take a while'),
successText: chalk.white.bold('🟩🟩⬜ Installed dependencies'),
successText: chalk.white.bold('🟩🟩⬜ Installed dependencies'),
failText: chalk.white.bold(
'🟩🟥⬜⬜ Failed to install dependencies | Development environment will not function'
'🟩🟥⬜ Failed to install dependencies | The development environment will not function'
),
})
} catch (err) {
/* no feedback here */
}
// Fetch web components
try {
await oraPromise(downloadLabFiles(config), {
text:
chalk.white.bold('🟩🟩🟨⬜ Downloading web components') +
chalk.white.dim(' | Almost there'),
successText: chalk.white.bold('🟩🟩🟩⬜ Downloaded web components'),
failText: chalk.white.bold(
'🟩🟩🟥⬜ Failed to download web components | Development environment will not function'
),
})
} catch (err) {
/* no feedback here */
}
if (!choices.sideStep) {
// Initialize git repository
try {
await oraPromise(initGitRepo(config, choices), {
text:
chalk.white.bold('🟩🟩🟩⬜ Initializing git repository') +
chalk.white.bold('🟩🟩🟧 Initializing git repository') +
chalk.white.dim(' | You have git, right?'),
successText: chalk.white.bold('🟩🟩🟩🟩 Initialized git repository'),
successText: chalk.white.bold('🟩🟩🟩 Initialized git repository'),
failText:
chalk.white.bold('🟩🟩🟩🟥 Failed to initialize git repository') +
chalk.white.dim(' | This does not stop you from developing your design'),
chalk.white.bold('🟩🟩🟥 Failed to initialize git repository') +
chalk.white.dim(' | This does not stop the development environment from functioning'),
})
} catch (err) {
console.log(err)
}
}
// All done. Show tips
showTips(config, choices)

View file

@ -2,16 +2,25 @@ import rdir from 'recursive-readdir'
import path from 'path'
const ignore = [
'package.json',
'node_modules',
'.eslint',
'.gitignore',
'.md',
'lab/components/header.js',
'lab/components/help-us.js',
'lab/components/search.js',
'lab/components/footer.js',
'.next',
'prebuild.mjs',
'prebuild',
'public/locales',
'shared/config/measurements.js',
'sde/public/android-chrome-192x192.png',
'sde/public/android-chrome-384x384.png',
'sde/public/apple-touch-icon.png',
'sde/public/browserconfig.xml',
'sde/public/favicon-16x16.png',
'sde/public/favicon-32x32.png',
'sde/public/favicon.ico',
'sde/public/mstile-150x150.png',
'sde/public/safari-pinned-tab.svg',
'sde/public/site.webmanifest',
]
const getFiles = async (dir) => {
@ -28,9 +37,9 @@ const getFiles = async (dir) => {
const doIt = async () => {
let files = []
const shared = await getFiles('../../sites/shared')
const lab = await getFiles('../../sites/lab/components')
console.log(JSON.stringify([...shared, ...lab], null, 2))
const sde = await getFiles('../../sites/sde')
const shared = await getFiles('../../sites/shared/config')
console.log(JSON.stringify([...shared, ...sde], null, 2))
}
doIt()

View file

@ -1,19 +0,0 @@
import { test, expect } from '@playwright/test'
test('Compiles and drafts', async ({ page }) => {
await page.goto('http://localhost:8000/design')
await page.waitForSelector('main')
if (await page.getByRole('heading', { name: 'Preload a set of measurements' }).isVisible()) {
await page
.getByRole('list')
.filter({ hasText: 'Size 28Size 30Size 32Size 34Size 36Size 38Size 40Size 42Size 44Size 46' })
.getByRole('button', { name: 'Size 36' })
.click()
await page.getByTitle('draftDesign').click()
}
await expect(page.getByText('Something went wrong')).toHaveCount(0)
await expect(page.getByText('Unhandled Runtime Error')).toHaveCount(0)
await expect(page.getByTitle('Measurements')).toBeVisible()
})

View file

@ -1,145 +0,0 @@
// Hooks
import { useTranslation } from 'next-i18next'
// Components
import Link from 'next/link'
import { FreeSewingLogo } from 'shared/components/logos/freesewing.mjs'
import { OsiLogo } from 'shared/components/logos/osi.mjs'
import { CCLogo } from 'shared/components/logos/cc.mjs'
import { CCByLogo } from 'shared/components/logos/cc-by.mjs'
import { Ribbon } from 'shared/components/ribbon.mjs'
import { WordMark } from 'shared/components/wordmark.mjs'
import {
DiscordIcon,
FacebookIcon,
GithubIcon,
InstagramIcon,
RedditIcon,
TwitterIcon,
} from 'shared/components/icons.mjs'
// Classes
const link = 'text-secondary font-bold hover:pointer hover:underline px-1'
const accent = 'text-accent font-bold text-lg px-1 block sm:inline'
const freesewing = 'px-1 text-lg font-bold block sm:inline'
// Keep these translations in the component because they're only used here
const translations = {
cc: (
<span>
Content on FreeSewing.org is available under{' '}
<a className={link} href="https://creativecommons.org/licenses/by/4.0/">
a Creative Commons license
</a>
</span>
),
mit: (
<span>
The FreeSewing source code is{' '}
<a href="https://github.com/freesewing/freesewing" className={link}>
available on Github
</a>{' '}
under{' '}
<a href="https://opensource.org/licenses/MIT" className={link}>
the MIT license
</a>
</span>
),
sponsors: (
<>
<span className={freesewing}>FreeSewing</span> is sponsored by these{' '}
<span className={accent}>awesome companies</span>
</>
),
}
const icon = { className: 'w-8 lg:w-12 h-8 lg:h-12' }
const social = {
Discord: {
icon: <DiscordIcon {...icon} />,
href: 'https://discord.freesewing.org/',
},
Instagram: {
icon: <InstagramIcon {...icon} />,
href: 'https://instagram.com/freesewing_org',
},
Facebook: {
icon: <FacebookIcon {...icon} />,
href: 'https://www.facebook.com/groups/627769821272714/',
},
Github: {
icon: <GithubIcon {...icon} />,
href: 'https://github.com/freesewing',
},
Reddit: {
icon: <RedditIcon {...icon} />,
href: 'https://www.reddit.com/r/freesewing/',
},
Twitter: {
icon: <TwitterIcon {...icon} />,
href: 'https://twitter.com/freesewing_org',
},
}
export const Footer = ({ app }) => {
const { t } = useTranslation(['common', 'patrons'])
return (
<footer className="bg-neutral">
<Ribbon loading={app.loading} theme={app.theme} />
<div className="grid grid-cols-1 lg:grid-cols-4 py-12 2xl:py-20 text-neutral-content px-4">
{/* First col - CC & MIT */}
<div className="mb-20 order-1 mt-20 2xl:mt-0 2xl:mb-0">
<div className="max-w-md m-auto">
<div>
<CCLogo className="w-64 m-auto" />
</div>
<div className="flex flex-row gap-2 justify-center items-center mt-8">
<CCByLogo className="w-8 lg:w-12" />
<p className="text-neutral-content text-right basis-4/5 lg:basis-3/4 leading-5">
{translations.cc}
</p>
</div>
<div className="flex flex-row gap-2 justify-center items-center mt-4">
<OsiLogo className="w-8 lg:w-12" />
<p className="text-neutral-content text-right basis-4/5 lg:basis-3/4 leading-5">
{translations.mit}
</p>
</div>
</div>
</div>
{/* Second col - Social & Sponsors */}
<div className="lg:col-span-2 -order-2 2xl:order-2 px-4 lg:px-0">
{/* Social icons */}
<div className="w-full sm:w-auto flex flex-row flex-wrap gap-4 lg:gap-8 items-center justify-center">
{Object.keys(social).map((item) => (
<Link
key={item}
href={social[item].href}
className="hover:text-secondary hover:-mt-2 transition-all"
title={item}
>
{social[item].icon}
</Link>
))}
</div>
</div>
{/* Col 3 - Logo & Slogan */}
<div className="w-full 4xl:w-auto xl:max-w-md mb-8 text-center order-3 mt-0 lg:mt-20 2xl:mt-0 2xl:mb-0">
<div className="max-w-md m-auto">
<FreeSewingLogo stroke="none" size={164} className="w-40 lg:w-64 m-auto m-auto" />
<h5 className="lg:text-3xl mt-4">
<WordMark />
</h5>
<p className="bold text-neutral-content text-normal lg:text-xl leading-5">
{t('sloganCome')}
<br />
{t('sloganStay')}
</p>
</div>
</div>
</div>
</footer>
)
}

View file

@ -1,82 +0,0 @@
import { useState, useEffect } from 'react'
import Link from 'next/link'
import { ModalThemePicker } from 'shared/components/modal/theme-picker.mjs'
import { ModalLocalePicker } from 'shared/components/modal/locale-picker.mjs'
import { CloseIcon, MenuIcon, HelpIcon, DocsIcon } from 'shared/components/icons/close.js'
import { Ribbon } from 'shared/components/ribbon.js'
import { WordMark } from 'shared/components/wordmark.js'
import { useTranslation } from 'next-i18next'
const btnClasses =
'btn btn-ghost text-base font-medium btn-sm text-neutral-content ' +
' capitalize hover:bg-transparent hover:text-secondary-focus'
export const Header = ({ app }) => {
const { t } = useTranslation(['common'])
const [prevScrollPos, setPrevScrollPos] = useState(0)
const [show, setShow] = useState(true)
useEffect(() => {
if (typeof window !== 'undefined') {
const handleScroll = () => {
const curScrollPos = typeof window !== 'undefined' ? window.pageYOffset : 0
if (curScrollPos >= prevScrollPos) {
if (show && curScrollPos > 20) setShow(false)
} else setShow(true)
setPrevScrollPos(curScrollPos)
}
window.addEventListener('scroll', handleScroll)
return () => window.removeEventListener('scroll', handleScroll)
}
}, [prevScrollPos, show])
return (
<header
className={`
fixed top-0 left-0
bg-neutral
w-full
z-30
transition-transform
${show ? '' : 'fixed top-0 left-0 -translate-y-20'}
`}
>
<div>
<div className="p-2 flex flex-row justify-between text-neutral-content">
<div className="flex flex-row items-center">
<button
className={`
btn btn-sm btn-ghost
text-neutral-content bg-transparent
hover:text-secondary-focus
lg:hidden
`}
onClick={app.togglePrimaryMenu}
>
{app.primaryMenu ? <CloseIcon /> : <MenuIcon />}
</button>
<WordMark />
<div className="hidden md:flex flex-row items-center">
<a role="button" className={btnClasses} href="https://freesewing.dev/">
<DocsIcon />
<span className="ml-2">{t('docs')}</span>
</a>
<Link href="/support" role="button" className={btnClasses}>
<>
<HelpIcon />
<span className="ml-2">{t('support')}</span>
</>
</Link>
</div>
</div>
<div className="hidden md:flex flex-row items-center gap-2">
<ModalThemePicker app={app} />
<ModalLocalePicker app={app} />
</div>
</div>
</div>
<Ribbon loading={app.loading} theme={app.theme} />
</header>
)
}

View file

@ -1,15 +0,0 @@
import { useRouter } from 'next/router'
import { AsideNavigation } from 'shared/components/navigation/aside.mjs'
import { BeforeNav } from './lab'
export const DefaultLayout = ({ app, children = [] }) => {
const router = useRouter()
const slug = router.asPath.slice(1)
return (
<>
<AsideNavigation app={app} slug={slug} before={<BeforeNav app={app} />} mobileOnly />
{children}
</>
)
}

View file

@ -1,10 +0,0 @@
import React from 'react'
import { AsideNavigation } from 'shared/components/navigation/aside.mjs'
export const HomeLayout = ({ app, title = false, children = [] }) => (
<div className="max-w-7xl m-auto mt-32">
{title && <h1 className="capitalize">{title}</h1>}
<AsideNavigation app={app} mobileOnly />
{children}
</div>
)

View file

@ -1,44 +0,0 @@
import { ModalThemePicker } from 'shared/components/modal/theme-picker.mjs'
import { ModalLocalePicker } from 'shared/components/modal/locale-picker.mjs'
export const BeforeNav = ({ app }) => (
<>
<div className="md:hidden flex flex-row flex-wrap sm:flex-nowrap gap-2 mb-2">
<ModalThemePicker app={app} />
<ModalLocalePicker app={app} />
</div>
<div className="md:hidden flex flex-row flex-wrap sm:flex-nowrap gap-2 mb-2"></div>
</>
)
export const LabLayout = ({ app, AltMenu, children = [] }) => (
<div className="py-24 lg:py-36 flex flex-row">
<div className="w-full px-8">{children}</div>
<aside
className={`
fixed top-0 right-0
pt-20 pb-8 px-8
md:pt-0
md:relative md:transform-none
h-screen w-screen
bg-base-100
md:bg-base-50
md:flex
md:sticky
overflow-y-scroll
z-20
bg-base-100 text-base-content
transition-all
xl:w-1/4
${app.primaryMenu ? '' : 'translate-x-[-100%]'} transition-transform
md:flex-row
md:w-80
lg:w-96
shrink-0
`}
>
<BeforeNav app={app} />
{AltMenu}
</aside>
</div>
)

View file

@ -1,4 +0,0 @@
// Noop placeholder
const Noop = () => null
export default Noop

View file

@ -1,56 +0,0 @@
import { useState } from 'react'
// Locale and translation
import { useRouter } from 'next/router'
import { useTheme } from 'shared/hooks/useTheme.mjs'
export const useApp = () => {
// Load translation method
const locale = useRouter().locale
// Persistent state
const [theme, setTheme] = useTheme()
// React State
const [primaryMenu, setPrimaryMenu] = useState(false)
const [navigation, setNavigation] = useState({})
const [slug, setSlug] = useState('/')
const [loading, setLoading] = useState(false)
// State methods
const togglePrimaryMenu = () => setPrimaryMenu(!primaryMenu)
return {
// Static vars
site: 'lab',
// i18n
locale,
// State
loading,
navigation,
primaryMenu,
slug,
theme,
// State setters
setLoading,
setNavigation,
setPrimaryMenu,
setSlug,
setTheme,
startLoading: () => {
setLoading(true)
setPrimaryMenu(false)
}, // Always close menu when navigating
stopLoading: () => setLoading(false),
// State handlers
togglePrimaryMenu,
// Standalone is for the development environment
standalone: true,
}
}
export default useApp

View file

@ -1,13 +0,0 @@
// See: https://github.com/isaachinman/next-i18next
module.exports = {
i18n: {
defaultLocale: 'en',
locales: ['en', 'de', 'es', 'fr', 'nl'],
defaultNS: 'common',
},
interpolation: {
prefix: '{',
suffix: '}',
},
localeStructure: '{lng}/{ns}',
}

View file

@ -1,16 +0,0 @@
import path from 'path'
import i18nConfig from './next-i18next.config.js'
const config = {
i18n: i18nConfig.i18n,
pageExtensions: ['mjs'],
webpack: (config, options) => {
// Aliases
config.resolve.alias.shared = path.resolve('./shared/')
config.resolve.alias.site = path.resolve('./lab/')
config.resolve.alias.design = path.resolve('./design/')
return config
},
}
export default config

View file

@ -1,6 +0,0 @@
import 'shared/styles/globals.css'
import { appWithTranslation } from 'next-i18next'
const FreeSewingLab = ({ Component, pageProps }) => <Component {...pageProps} />
export default appWithTranslation(FreeSewingLab)

View file

@ -1,26 +0,0 @@
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
import { Pattern } from 'design/src/index.mjs'
import { useApp } from 'site/hooks/useApp.mjs'
import { PageWrapper } from 'site/components/wrappers/page.mjs'
import { LabLayout } from 'site/components/layouts/lab.mjs'
import { WorkbenchWrapper } from 'shared/components/wrappers/workbench.mjs'
const WorkbenchPage = () => {
const app = useApp()
return (
<PageWrapper app={app}>
<WorkbenchWrapper {...{ app, design: Pattern, layout: LabLayout }} />
</PageWrapper>
)
}
export default WorkbenchPage
export async function getStaticProps({ locale }) {
return {
props: {
...(await serverSideTranslations(locale)),
},
}
}

View file

@ -1,116 +0,0 @@
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
import { useTranslation } from 'next-i18next'
import { useRouter } from 'next/router'
import Link from 'next/link'
import { PageWrapper } from 'site/components/wrappers/page.mjs'
import { useApp } from 'site/hooks/useApp.mjs'
import { HomeLayout } from 'site/components/layouts/home.mjs'
import { FreeSewingIcon } from 'shared/components/icons.mjs'
import { Popout } from 'shared/components/popout.mjs'
import themes from 'shared/themes/index.js'
const translations = {
sade: {
en: `Stand-alone development environment`,
nl: `Vrijstaande ontwikkeling omgeving`,
},
load: {
en: `To your design`,
nl: `Naar jouw ontwerp`,
},
tips: {
en: (
<Popout tip compact>
Edit the files in the <strong>design</strong> folder, and we&apos;ll auto-update your design
</Popout>
),
nl: (
<Popout tip compact>
Bewerk de bestanden in de <strong>design</strong> map, en we passen je ontwerp automatisch
aan
</Popout>
),
},
}
const HomePage = () => {
const app = useApp()
const router = useRouter()
const { t } = useTranslation(['common', 'patrons', 'locales', 'themes'])
return (
<PageWrapper app={app} title={false} layout={HomeLayout}>
<div className="text-center w-full pt-20 pb-10 max-w-4xl m-auto">
<FreeSewingIcon className="w-96 m-auto" />
<h1>FreeSewing</h1>
<h4>{translations.sade[app.locale]}</h4>
<Link href="/design" className="btn btn-primary btn-lg h-20 my-8 mb-12">
<>
<span role="image" className="text-4xl px-6">
👉
</span>
<span className="text-xl px-2">{translations.load[app.locale]}</span>
<span role="image" className="text-4xl px-6">
👈
</span>
</>
</Link>
{translations.tips[app.locale]}
</div>
<div className="flex flex-row flex-wrap gap-4 w-full max-w-4xl m-auto justify-center">
{router.locales.map((locale) => (
<Link
href={router.asPath}
locale={locale}
key={locale}
className="btn btn-ghost text-base-content hover:bg-base-200"
>
<span className="text-base-content">{t(`locales:${locale}`)}</span>
</Link>
))}
</div>
<div className="flex flex-row flex-wrap gap-4 w-full max-w-4xl m-auto justify-center mt-4">
{Object.keys(themes).map((theme) => (
<button
key={theme}
onClick={() => app.setTheme(theme)}
className="btn btn-ghost hover:bg-base-200"
>
<span className="text-base-content">{t(`themes:${theme}Theme`)}</span>
</button>
))}
</div>
<div className="py-20">
<h2>{t('patrons:supportFreesewing')}</h2>
<div className="flex flex-row flex-wrap gap-2">
<div>
<p className="max-w-3xl">{t('patrons:patronLead')}</p>
<p className="max-w-3xl">{t('patrons:patronPitch')}</p>
</div>
<a className="btn btn-accent btn-lg ">
<span role="image" className="text-4xl px-4">
🥰
</span>
<span className="px-2">{t('patrons:becomeAPatron')}</span>
<span role="image" className="text-4xl px-4">
🙏🏻
</span>
</a>
</div>
</div>
{/* here to force Tailwind inclusion of the w-8 h-8 classes */}
<span className="w-8 h-8" />
</PageWrapper>
)
}
export default HomePage
export async function getStaticProps({ locale }) {
return {
props: {
...(await serverSideTranslations(locale)),
},
}
}

View file

@ -1,127 +0,0 @@
import { useTranslation } from 'next-i18next'
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
import { PageWrapper } from 'site/components/wrappers/page.mjs'
import { useApp } from 'site/hooks/useApp.mjs'
import { DefaultLayout } from 'site/components/layouts/default.mjs'
import { DiscordIcon, GithubIcon, CcIcon, HeartIcon, DocsIcon } from 'shared/components/icons/docs'
const gh = `<a class="text-secondary hover:text-secondary-focus"
href="https://github.com/freesewing/freesewing">freesewing/freesewing</a>`
const fsd = `<a class="text-secondary hover:text-secondary-focus"
href="https://freesewing.dev">freesewing.dev</a>`
const translations = {
discord: {
en: `Our Discord server on is the best place to ask questions, or hang
out with other members of the FreeSewing community.`,
nl: `Onze Discord server is de best plek om vragen te stellen, hulp te ontvangen,
of gewoon een leuke tijd te spenderen met andere leden van de FreeSewing gemeenschap.`,
},
github: {
en: `For bug reports, please create an issue in the ${gh} repository on Github.
This is also where you'll find all our source code.`,
nl: `Een bug gevonden? Maak dan een issue aan in de ${gh} repository op Github.
Dit is ook waar je al de FreeSewing broncode kan vinden.`,
},
cc: {
en: `Every two weeks, there's the FreeSewing contributor call, which is when we discuss
ongoing issues, future plans, and news big and small about FreeSewing and its community.`,
nl: `Elke twee weken is er de FreeSewing contributor call (Engelstalig), waar de FreeSewing
vrijwilligers de lopende zaken bespreken. Ook de plannen voor de toekomst en groot en klein
nieuws over FreeSewing en de gemeenschap komen aan bod.`,
},
docs: {
en: `Our documentation for developers hosted on ${fsd}. You can find guides and how-to's
there, as well as reference documantation for FreeSewing's core API.
<br /> <br />
We stive to provide excellent documentation. So if something is not clear please, let us know.`,
nl: `Onze documentatie voor ontwikkelaars is beschikbaar op ${fsd}. Je vindt er guides en how-to's,
alsook de referentie documentatie voor FreeSewing's core API.
<br /> <br />
We streven ernaar om uitstekende documentatie te voorzien. Dus als er iets niet duidelijk is, laat
het ons dan zeker weten.`,
},
}
const SupportPage = () => {
const app = useApp()
const { t } = useTranslation(['common', 'patrons'])
return (
<PageWrapper app={app} title={t('support')} layout={DefaultLayout}>
<h2 className="border-0">Discord</h2>
<div className="flex flex-row flex-wrap gap-2">
<p className="max-w-3xl">{translations.discord[app.locale]}</p>
<a className="btn btn-primary btn-lg w-96" href="https://discord.freesewing.org/">
<DiscordIcon />
<span className="ml-4">discord.freesewing.org</span>
</a>
</div>
<h2 className="border-0">Github</h2>
<div className="flex flex-row flex-wrap gap-2">
<p
className="max-w-3xl"
dangerouslySetInnerHTML={{ __html: translations.github[app.locale] }}
/>
<a
className="btn btn-primary btn-outline btn-lg w-96"
href="https://github.com/freesewing/freesewing"
>
<GithubIcon />
<span className="ml-4">github.com/freesewing</span>
</a>
</div>
<h2 className="border-0">{t('docs')}</h2>
<div className="flex flex-row flex-wrap gap-2">
<p
className="max-w-3xl"
dangerouslySetInnerHTML={{ __html: translations.docs[app.locale] }}
/>
<a className="btn btn-primary btn-outline btn-lg w-96" href="https://freesewing.dev/">
<DocsIcon />
<span className="ml-4">www.FreeSewing.dev</span>
</a>
</div>
<h2 className="border-0">Contributor Calls</h2>
<div className="flex flex-row flex-wrap gap-2">
<p
className="max-w-3xl"
dangerouslySetInnerHTML={{ __html: translations.cc[app.locale] }}
/>
<a
className="btn btn-primary btn-outline btn-lg w-96"
href="https://github.com/freesewing/freesewing/discussions?discussions_q=label%3A%22%3Atv%3A+fscc%22"
>
<CcIcon />
<span className="ml-4">Contributor Calls</span>
</a>
</div>
<div className="py-20">
<h2 className="border-0">{t('patrons:supportFreesewing')}</h2>
<div className="flex flex-row flex-wrap gap-2">
<div>
<p className="max-w-3xl">{t('patrons:patronLead')}</p>
<p className="max-w-3xl">{t('patrons:patronPitch')}</p>
</div>
<a className="btn btn-accent btn-lg w-96">
<HeartIcon className="fill-accent-content stroke-accent-content w-6 h-6" />
<span className="ml-4">{t('patrons:becomeAPatron')}</span>
</a>
</div>
</div>
</PageWrapper>
)
}
export default SupportPage
export async function getStaticProps({ locale }) {
return {
props: {
...(await serverSideTranslations(locale)),
},
}
}

View file

@ -1,70 +0,0 @@
const { defineConfig, devices } = require('@playwright/test')
/**
* @see https://playwright.dev/docs/test-configuration
*/
module.exports = defineConfig({
testDir: './e2e',
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'html',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
// baseURL: 'http://127.0.0.1:3000',
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
},
webServer: {
command: 'yarn dev',
url: 'http://127.0.0.1:8000',
reuseExistingServer: !process.env.CI,
},
/* Configure projects for major browsers */
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
// {
// name: 'firefox',
// use: { ...devices['Desktop Firefox'] },
// },
// {
// name: 'webkit',
// use: { ...devices['Desktop Safari'] },
// },
/* Test against mobile viewports. */
// {
// name: 'Mobile Chrome',
// use: { ...devices['Pixel 5'] },
// },
// {
// name: 'Mobile Safari',
// use: { ...devices['iPhone 12'] },
// },
/* Test against branded browsers. */
// {
// name: 'Microsoft Edge',
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
// },
// {
// name: 'Google Chrome',
// use: { ..devices['Desktop Chrome'], channel: 'chrome' },
// },
],
})

View file

@ -1,5 +0,0 @@
// Can't seem to make this work as ESM
const config = require('./shared/config/postcss.config.js')
module.exports = config

View file

@ -1,40 +0,0 @@
import CopyToClipboard from 'shared/components/copy-to-clipboard'
const names = {
json: 'JSON',
yaml: 'YAML',
}
const Highlight = (props) => {
let language = 'txt'
if (props.language) language = props.language
if (props.children?.props?.className) {
language = props.children.props.className.split('-').pop()
}
const preProps = {
className: `language-${language} hljs text-base lg:text-lg whitespace-pre overflow-scroll pr-4`
}
if (props.raw) preProps.dangerouslySetInnerHTML = { __html: props.raw }
return (
<div className="hljs my-4">
<div className={`
flex flex-row justify-between
text-xs uppercase font-bold text-neutral-content
mt-1 border-b border-neutral-content border-opacity-25
py-1 mb-2 lg:text-sm
`}>
<span>&nbsp;</span>
<span>{names[language] ? names[language] : language}</span>
<CopyToClipboard content={props.children} />
</div>
<pre {...preProps}>
{props.children}
</pre>
</div>
)
}
export default Highlight

View file

@ -1,32 +0,0 @@
// Handle themes
const themes = require('./shared/themes/index.js')
module.exports = {
content: [
'./pages/*.mjs',
'./pages/**/*.mjs',
'./shared/**/*.mjs',
'./lab/**/*.mjs',
// Legacy stuff, to be removed once everything is .mjs
'./pages/*.js',
'./pages/**/*.js',
'./shared/**/*.js',
'./lab/**/*.js',
],
plugins: [require('daisyui'), require('tailwindcss/nesting')],
daisyui: {
styled: true,
themes: [themes],
base: true,
utils: true,
logs: true,
rtl: false,
},
theme: {
extend: {
aspectRatio: {
'9/16': '9 / 16',
},
},
},
}

View file

@ -1,2 +0,0 @@
export const parts = ['back', 'frontSideDart']
export const dependencies = ['@freesewing/bella']

View file

@ -1,2 +0,0 @@
export const parts = ['back', 'front', 'topSleeve', 'underSleeve']
export const dependencies = ['@freesewing/bent', '@freesewing/brian', '@freesewing/plugin-bust']

View file

@ -1,2 +0,0 @@
export const parts = ['back', 'front', 'sleeve']
export const dependencies = ['@freesewing/breanna']

View file

@ -1,2 +0,0 @@
export const parts = ['back', 'front', 'sleeve']
export const dependencies = ['@freesewing/brian', '@freesewing/plugin-bust']

View file

@ -1,72 +0,0 @@
export const noInheritance = true
export const parts = [
{
part: 'box',
// this part uses the pluginBundle
pluginBundle: true,
// options needs to be a string of the internals of the options object
options: `size: { pct: 50, min: 10, max: 100 }`,
// the draft method needs all of these parameters uncommented
draftUses: {
options: true,
points: true,
paths: true,
Point: true,
Path: true,
complete: true,
paperless: true,
sa: true,
snippets: true,
Snippet: true,
macro: true,
},
// the draft method
draft: `// Add points to make a box
const w = 500 * options.size
points.topLeft = new Point(0, 0)
points.topRight = new Point(w, 0)
points.bottomLeft = new Point(0, w / 2)
points.bottomRight = new Point(w, w / 2)
// Create a path for the box outline
paths.seam = new Path()
.move(points.topLeft)
.line(points.bottomLeft)
.line(points.bottomRight)
.line(points.topRight)
.line(points.topLeft)
.close()
.setClass('fabric')
// Complete?
if (complete) {
// Add a logo
points.logo = points.topLeft.shiftFractionTowards(points.bottomRight, 0.5)
snippets.logo = new Snippet('logo', points.logo)
// Add some text
points.text = points.logo
.shift(-90, w / 8)
.addText('FreeSewing', 'center')
if (sa) {
// Add seam allowance
paths.sa = paths.seam.offset(sa).setClass('fabric sa')
}
}
// Add dimensions for paperless mode
if (paperless) {
macro('hd', {
from: points.bottomLeft,
to: points.bottomRight,
y: points.bottomLeft.y + sa + 15,
})
macro('vd', {
from: points.bottomRight,
to: points.topRight,
x: points.topRight.x + sa + 15,
})
}`,
},
]

View file

@ -1,2 +0,0 @@
export const parts = ['back', 'front']
export const dependencies = ['@freesewing/titan', '@freesewing/snapseries']

View file

@ -1,11 +0,0 @@
export const noInheritance = true
export const parts = [
{
part: 'bib',
// no draft method
draft: '',
},
]
// no additional dependencies
export const dependencies = []

View file

@ -1,38 +0,0 @@
{{!
// Change the Mustache delimiter from double curly braces to double dollar signs.
// Dollar signs are allowed in EcmaScript identifier names,
// which is helpful when running unrendered Mustache templates through eslint.
//}}{{=$$ $$=}}
// Import Design constructor
import { Design } from '@freesewing/core'
// Import parts
$$#parts$$
import { $$.$$ } from './$$.$$.mjs'
$$/parts$$
// Create the new design
const $$Name$$ = new Design({
data: {
/*
* If you like, you can add any data you want to your design.
* We'll add the name here as an example.
*
* If you don't use this,
* you can remove this data key enterely.
*/
name: "$$ Name $$",
},
// A list of parts is all that is required.
parts: [ $$parts$$ ],
})
const Pattern = $$Name$$
/*
* Named exports
*
* We export the design itself as well as each part individually.
* This allows us to re-use these parts in other designs.
*/
export {$$#parts$$ $$.$$,$$/parts$$ $$Name$$, Pattern }

View file

@ -1,120 +0,0 @@
{{!
// Change the Mustache delimiter from double curly braces to double dollar signs.
// Dollar signs are allowed in EcmaScript identifier names,
// which is helpful when running unrendered Mustache templates through eslint.
//}}{{=$$ $$=}}
{
"name": "@freesewing/$$ name $$",
"version": "0.0.1",
"description": "A new FreeSewing design",
"author": "Joost De Cock <joost@joost.at> (https://github.com/joostdecock)",
"homepage": "https://freesewing.org/",
"repository": "github:freesewing/freesewing",
"license": "MIT",
"bugs": {
"url": "https://github.com/freesewing/freesewing/issues"
},
"funding": {
"type": "individual",
"url": "https://freesewing.org/patrons/join"
},
"keywords": [
"freesewing",
"design",
"diy",
"fashion",
"parametric design",
"sewing",
"sewing pattern"
],
"main": "dist/index.js",
"module": "dist/index.mjs",
"scripts": {
"dev": "next dev -p 8000",
"build": "node build.js",
"clean": "rimraf dist",
"mbuild": "NO_MINIFY=1 node build.js",
"test": "BABEL_ENV=production npx mocha tests/*.test.mjs --require @babel/register",
"vbuild": "VERBOSE=1 node build.js"
},
"dependencies": {
"@freesewing/core": "$$ tag $$",
$$ #dependencies $$
"$$& . $$": "$$ tag $$",
$$ /dependencies $$
"@freesewing/plugin-bundle": "$$ tag $$"
},
"devDependencies": {
"@freesewing/plugin-annotations": "$$ tag $$",
"@freesewing/plugin-flip": "$$ tag $$",
"@freesewing/plugin-svgattr": "$$ tag $$",
"@freesewing/plugin-theme": "$$ tag $$",
"@freesewing/plugin-i18n": "$$ tag $$",
"@freesewing/plugin-mirror": "$$ tag $$",
"@freesewing/models": "$$ tag $$",
"@headlessui/react": "^1.6.5",
$$ #includeTests $$
"@playwright/test": "^1.32.3",
$$ /includeTests $$
"js-yaml": "^4.1.0",
"file-saver": "^2.0.5",
"axios": "^0.27.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-sizeme": "^3.0.2",
"react-zoom-pan-pinch": "^2.1.3",
"react-markdown": "^8.0.3",
"roughjs": "^4.5.2",
"@tailwindcss/typography": "^0.5.2",
"d3-dispatch": "^3.0.1",
"d3-drag": "^3.0.0",
"d3-selection": "^3.0.0",
"daisyui": "^2.0.6",
"lodash.get": "^4.4.2",
"lodash.orderby": "^4.6.0",
"lodash.set": "^4.3.2",
"lodash.unset": "^4.5.2",
"lodash.clonedeep": "^4.5.0",
"next": "^13",
"mermaid": "10.1.0",
"next-i18next": "^11.0.0",
"pdfkit": "^0.13.0",
$$ #includeTests $$
"playwright": "^1.32.3",
$$ /includeTests $$
"react-copy-to-clipboard": "^5.0.4",
"react-hotkeys-hook": "^3.4.4",
"react-swipeable": "^6.2.0",
"react-timeago": "^7.1.0",
"mocha": "^9.1.1",
"chai": "^4.2.0",
"autoprefixer": "^10.4.0",
"eslint-config-next": "12.1.6",
"highlight.js": "^11.5.1",
"postcss": "^8.4.14",
"postcss-for": "^2.1.1",
"svg-to-pdfkit": "^0.1.8",
"tailwindcss": "^3.1.3",
"tailwindcss-open-variant": "^1.0.0",
"web-worker": "^1.2.0"
},
"files": [
"dist/*",
"README.md",
"package.json"
],
"publishConfig": {
"access": "public",
"tag": "next"
},
"engines": {
"node": ">=16.0.0",
"npm": ">=8.3.0"
},
"overrides": {
"react-zoom-pan-pinch": {
"react": "$react",
"react-dom": "$react-dom"
}
}
}

View file

@ -1,199 +0,0 @@
{{!
// Change the Mustache delimiter from double curly braces to double dollar signs.
// Dollar signs are allowed in EcmaScript identifier names,
// which is helpful when running unrendered Mustache templates through eslint.
//}}{{=$$ $$=}}
$$ #doesInherit $$
import { $$ part $$ as $$ baseName $$$$ Part $$ } from '@freesewing/$$ baseName $$'
$$ /doesInherit $$
$$ #pluginBundle $$
import { pluginBundle } from '@freesewing/plugin-bundle'
$$ /pluginBundle $$
function draft$$ Part $$ ({
// Uncomment below to destructure what you need
/*
* Content constructors
*/
$$ ^draftUses.Path $$//$$ /draftUses.Path $$Path, // A Path constructor to create new paths
$$ ^draftUses.Point $$//$$ /draftUses.Point $$Point, // A Point constructor to create new points
$$ ^draftUses.Snippet $$//$$ /draftUses.Snippet $$Snippet, // A Snippet constructor to create new snippets
/*
* Content constainers
*/
$$ ^draftUses.paths $$//$$ /draftUses.paths $$paths, // Add a Path to your part by adding it to this object
$$ ^draftUses.points $$//$$ /draftUses.points $$points, // Add a Points to your part by adding it to this object
$$ ^draftUses.snippets $$//$$ /draftUses.snippets $$snippets, // Add a Snippet to your part by adding it to this object
/*
* Access to settings
*/
$$ ^draftUses.absoluteOptions $$//$$ /draftUses.absoluteOptions $$absoluteOptions, // Access to settings.absoluteOptions
$$ ^draftUses.complete $$//$$ /draftUses.complete $$complete, // Access to settings.complete
$$ ^draftUses.measurements $$//$$ /draftUses.measurements $$measurements, // Access to settings.measurements
$$ ^draftUses.options $$//$$ /draftUses.options $$options, // Access to settings.options
$$ ^draftUses.paperless $$//$$ /draftUses.paperless $$paperless, // Access to settings.paperless
$$ ^draftUses.sa $$//$$ /draftUses.sa $$sa, // Access to settings.sa
$$ ^draftUses.scale $$//$$ /draftUses.scale $$scale, // Access to settings.scale
/*
* Access to utilities
*/
$$ ^draftUses.getId $$//$$ /draftUses.getId $$getId, //See the getId documentation
$$ ^draftUses.hide $$//$$ /draftUses.hide $$hide, //See the hide documentation
$$ ^draftUses.log $$//$$ /draftUses.log $$log, //See the logging documentation
$$ ^draftUses.macro $$//$$ /draftUses.macro $$macro, //See the macros documentation
$$ ^draftUses.setHidden $$//$$ /draftUses.setHidden $$setHidden, //See the setHidden documentation
$$ ^draftUses.store $$//$$ /draftUses.store $$store, //See the store documentation
$$ ^draftUses.unhide $$//$$ /draftUses.unhide $$unhide, //See the unhide documentation
$$ ^draftUses.units $$//$$ /draftUses.units $$units, //See the units documentation
/$$ ^draftUses.utils $$//$$ /draftUses.utils $$utils, //See the utils documentation
/*
* Return value
*/
part, // Your draft method must return this
}) {
$$& draft $$
$$ ^draft $$// Work your magic here $$ /draft $$
return part
}
export const $$ part $$ = {
/*
* name: Holds the name of this part.
*
* We STRONGLY recommend naming your parts in the format of
* design.part to avoid naming conflicts when people re-use
* parts across designs.
*/
name: '$$ name $$.$$ part $$',
/*
* draft: Holds the draft method for this part
*
* This should be a function that drafts and returns the part
*
* Documentation: https://freesewing.dev/reference/api/part/draft
*/
draft: draft$$ Part $$,
after: [
/*
* after: Holds a list of parts that should be drafted prior to this part.
*
* You'll need to import these parts, just as with the from key above.
*
* If you don't have any parts to draft prior to this part,
* you can remove this options key entirely.
*
* Documentation: https://freesewing.dev/reference/api/part/config/dependencies
*/
],
/*
* from: Holds the part you want to extend.
*
$$ #doesInherit $$
* Since you opted to extend $$ BaseName $$, and this is the $$ part $$ part,
* we're extending $$ BaseName $$'s $$ part $$ part here.
* It was imported at the top of this file from @freesewing/$$ BaseName $$
*
$$ /doesInherit $$
* Documentation: https://freesewing.dev/reference/api/part/config/dependencies
*/
$$ #doesInherit $$
from: $$ baseName $$$$ Part $$,
$$ /doesInherit $$
$$ ^doesInherit $$
from: false,
$$ /doesInherit $$
/* hide: */
hide: {
/*
* self: Set this to true to hide the part.
*
* We've set this to false here to clarify its use.
* If you don't want to hide this part,
* you can remove the hide key entirely.
*/
self: false,
/*
* from: Set this to true to hide a part's from dependency.
*
$$ #doesInherit $$
* We've set this to true here since you're extending $$ BaseName $$'s $$ part $$ part.
$$ /doesInherit $$
* If you don't want to hide this part's from dependency,
* you can remove the from key entirely.
*/
from: $$ doesInherit $$,
/*
* after: Set this to true to hide any parts specified in the after setting
*
* We've set this to false here to clarify its use.
* If you don't want to hide this part,
* you can remove the hide key entirely.
*/
after: false
},
options: {
/*
* options: Holds (the configuration of) options for this part
*
* Declare options used in this part here.
$$ #doesInherit $$
* You only need to add additional options.
* All options coming from $$ BaseName $$'s $$ part $$ part are already loaded.
$$ /doesInherit $$
*
* If you don't have any options to add,
* you can remove this options key entirely.
*
* Documentation: https://freesewing.dev/reference/api/part/config/options
*/
$$& options $$
},
measurements: [
/*
* measurements: Holds a list of measurements required by this part.
*
* Declare measurements required by this part here.
$$ #doesInherit $$
* You only need to add additional measurements.
* All measurements coming from $$ BaseName $$'s $$ part $$ part are already loaded.
$$ /doesInherit $$
*
* If you don't have any required measurements to add,
* you can remove this measurements key entirely.
*
* Documentation: https://freesewing.dev/reference/api/part/config/measurements
*/
],
optionalMeasurements: [
/*
* optionalMeasurements: Holds a list of measurements optional in this part.
*
* Declare measurements that are optional for this part here.
*
* If you don't have any optional measurements to add,
* you can remove this optionalMeasurements key entirely.
*
* Documentation: https://freesewing.dev/reference/api/part/config/measurements
*/
],
plugins: [
/*
* plugins: Holds a list of plugins this part relies on.
*
* Add all the plugins here that you need in this part.
$$ #doesInherit $$
* You only need to add additional plugins.
* All plugins coming from $$ BaseName $$'s $$ part $$ part are already loaded.
$$ /doesInherit $$
*
* If you don't have any plugins to add,
* you can remove this plugins key entirely.
*
* Documentation: https://freesewing.dev/reference/api/part/config/plugins
*/
$$ #pluginBundle $$
pluginBundle,
$$ /pluginBundle $$
]
}

View file

@ -1,11 +1,57 @@
import configBuilder from '../shared/config/next.mjs'
import i18nConfig from './next-i18next.config.js'
import { banner } from '../../scripts/banner.mjs'
import path from 'path'
// Remark plugins
import remarkFrontmatter from 'remark-frontmatter'
import remarkMdxFrontmatter from 'remark-mdx-frontmatter'
import remarkGfm from 'remark-gfm'
import smartypants from 'remark-smartypants'
let config = configBuilder({ site: 'sde' })
config.i18n = i18nConfig.i18n
/*
* This is the NextJS configuration
*/
const config = {
experimental: {
externalDir: true,
},
pageExtensions: ['mjs'],
webpack: (config, options) => {
// Fixes npm packages that depend on node modules
if (!options.isServer) {
config.resolve.fallback.fs = false
config.resolve.fallback.path = false
config.resolve.fallback.child_process = false
}
// Say hi
console.log(banner + '\n')
// MDX support
config.module.rules.push({
test: /\.mdx?$/,
use: [
options.defaultLoaders.babel,
{
loader: '@mdx-js/loader',
options: {
providerImportSource: '@mdx-js/react',
format: 'mdx',
remarkPlugins: [remarkFrontmatter, remarkMdxFrontmatter, remarkGfm, smartypants],
},
},
],
})
// Fix for nextjs bug #17806
config.module.rules.push({
test: /index.mjs$/,
type: 'javascript/auto',
resolve: {
fullySpecified: false,
},
})
// Aliases
config.resolve.alias.shared = path.resolve('./shared/')
config.resolve.alias.site = path.resolve(`./`)
return config
},
}
export default config

View file

@ -15,19 +15,61 @@
},
"scripts": {
"build": "next build",
"cibuild": "yarn build",
"dev": "next dev -p 8000",
"develop": "next dev -p 8000",
"i18n": "SITE=sde node --conditions=internal ../shared/prebuild/i18n-only.mjs",
"lint": "next lint",
"prebuild": "node --conditions=internal --experimental-json-modules ./prebuild.mjs",
"start": "yarn prebuild && yarn dev",
"wbuild": "next build",
"prewbuild": "node --conditions=internal --experimental-json-modules ./prebuild.mjs"
"lint": "next lint"
},
"peerDependencies": {},
"dependencies": {},
"devDependencies": {},
"dependencies": {
"@freesewing/core": "next",
"@freesewing/core-plugins": "next",
"@freesewing/brian": "next",
"@freesewing/titan": "next",
"@freesewing/bella": "next",
"@freesewing/breanna": "next",
"@headlessui/react": "1.7.17",
"@mdx-js/loader": "2.3.0",
"@mdx-js/mdx": "2.3.0",
"@mdx-js/react": "2.3.0",
"@mdx-js/runtime": "2.0.0-next.9",
"@tailwindcss/typography": "0.5.9",
"d3-dispatch": "3.0.1",
"d3-drag": "3.0.0",
"d3-selection": "3.0.0",
"daisyui": "3.6.4",
"i18next": "23.4.6",
"lodash.get": "4.4.2",
"lodash.orderby": "4.6.0",
"lodash.set": "4.3.2",
"next": "13.4.19",
"next-i18next": "14.0.0",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-copy-to-clipboard": "5.1.0",
"react-hotkeys-hook": "4.4.1",
"react-i18next": "13.2.2",
"react-markdown": "8.0.7",
"react-swipeable": "7.0.1",
"react-timeago": "7.1.0",
"rehype-autolink-headings": "6.1.1",
"rehype-highlight": "6.0.0",
"rehype-sanitize": "5.0.1",
"rehype-slug": "5.1.0",
"rehype-stringify": "10.0.0",
"remark-smartypants": "2.0.0",
"remark-copy-linked-files": "git+https://git@github.com/joostdecock/remark-copy-linked-files",
"remark-gfm": "3.0.1",
"remark-mdx-frontmatter": "3.0.0"
},
"devDependencies": {
"autoprefixer": "10.4.15",
"eslint-config-next": "13.4.19",
"js-yaml": "4.1.0",
"postcss": "8.4.29",
"remark-extract-frontmatter": "3.2.0",
"remark-mdx-frontmatter": "3.0.0",
"tailwindcss": "3.3.3"
},
"engines": {
"node": "18",
"npm": "9"

View file

@ -1 +0,0 @@
["plugin-bundle", "plugin-bust"]