diff --git a/README.md b/README.md index 2b198b3..3596999 100644 --- a/README.md +++ b/README.md @@ -22,33 +22,35 @@ ## Table of Contents -- Overview -- Prerequisites - - Must Knows - - Good-to-knows -- Quick Start Guide -- Explanation of File Structure - - Root Files - - .eleventy.js - - netlify.toml - - package.json and package-lock.json - - node_modules/ - - public/ - - src/ - - \_data/ - - \_includes - - Navigations \- Rendering Automatically - - Navigations \- Rendering Manually - - admin/ - - assets/ - - config/ - - content/ - - Root src/ Files - - \_redirects - - index.html - - robots.html - - sitemap.html -- Deployment +- Overview +- Prerequisites + - Must Knows + - Good-to-knows +- Quick Start Guide +- Customisation Scripts + +- Explanation of File Structure + - Root Files + - .eleventy.js + - netlify.toml + - package.json and package-lock.json + - node_modules/ + - public/ + - src/ + - \_data/ + - \_includes + - Navigations + - admin/ + - assets/ + - Image Optimization + - config/ + - content/ + - Root src/ Files + - \_redirects + - index.html + - robots.html + - sitemap.html +- Deployment @@ -68,10 +70,10 @@ An example website is also provided, with easy substitution of website sections _Knowledge requirements before using the kit_ -- HTML/CSS -- Beginner-level JS -- Familiarity with working in a NodeJS-powered project (handling dependencies with npm, source vs built files, etc) -- Familiarity with templating languages (this kit uses Nunjucks) +- HTML/CSS +- Beginner-level JS +- Familiarity with working in a NodeJS-powered project (handling dependencies with npm, source vs built files, etc) +- Familiarity with templating languages (this kit uses Nunjucks) @@ -79,14 +81,14 @@ _Knowledge requirements before using the kit_ _Not required for light-medium kit usage, but helpful if you want to customise the kit beyond base functionality_ -- Nunjucks ([Docs found here](https://mozilla.github.io/nunjucks/)) - - If you've never used Nunjucks before, [this excellent article by Hyunbin](https://hyunbinseo.medium.com/nunjucks-settings-for-vs-code-a0da0dc66b95) explains how to set up VSCode to best support Nunjucks, including formatting, syntax highlighting and Emmet. -- Eleventy ([Docs found here](https://www.11ty.dev/docs/)). Key topics include: - - [The Data Cascade](https://www.11ty.dev/docs/data-cascade/) - - [Layouts](https://www.11ty.dev/docs/layouts/) - - [Permalinks](https://www.11ty.dev/docs/permalinks/) - - [Passthroughs](https://www.11ty.dev/docs/copy/) -- Decap CMS ([Docs found here](https://decapcms.org/docs/intro/)) +- Nunjucks ([Docs found here](https://mozilla.github.io/nunjucks/)) + - If you've never used Nunjucks before, [this excellent article by Hyunbin](https://hyunbinseo.medium.com/nunjucks-settings-for-vs-code-a0da0dc66b95) explains how to set up VSCode to best support Nunjucks, including formatting, syntax highlighting and Emmet. +- Eleventy ([Docs found here](https://www.11ty.dev/docs/)). Key topics include: + - [The Data Cascade](https://www.11ty.dev/docs/data-cascade/) + - [Layouts](https://www.11ty.dev/docs/layouts/) + - [Permalinks](https://www.11ty.dev/docs/permalinks/) + - [Passthroughs](https://www.11ty.dev/docs/copy/) +- Decap CMS ([Docs found here](https://decapcms.org/docs/intro/)) @@ -98,45 +100,73 @@ _Not required for light-medium kit usage, but helpful if you want to customise t 4. Run `npm install` to install all dependencies. 5. After the installation is complete, run `npm start` to start the development server. 6. Fill out the `./src/_data/client.js` file with the appropriate information for your client. -7. Adjust the `:root` variables in `./src/assets/[sass or less]/root.[scss or less]` +7. Adjust the `:root` variables in `./src/assets/less/root.less` 8. Modify the website files (use `./src`, NOT `./public`) as needed. Use the template file in `content/pages/_template.txt` to get started, or modify the existing pages. 9. Deploy using your preferred hosting provider. + + +## Customization Scripts + +Scripts to quickly strip features you don't need. + +### Remove Dark Mode + +Run `npm run remove-dark-mode` to remove all dark mode code. + +### Remove Decap CMS + +Run `npm run remove-decap` to remove Decap CMS and optionally all blog content. + +### Remove Demo Content + +Run `npm run remove-demo` to strip the template to its bare minimum. + -## Explaination of File Structure +## Explanation of File Structure This documentation will explain all the files and directories in the starter kit, from root inwards, top to bottom. By the end, you should have a full understanding of all files and directories, and be fully equipped to adapt the kit to your needs. ``` . -├── public/ +├── scripts/ +│ ├── utils/ +│ ├── remove-dark-mode.js +│ ├── remove-decap.js +│ └── remove-demo.js ├── src/ -│ ├── _data/ -│ │ └── client.js -│ ├── _includes/ -│ │ ├── components/ -│ │ └── layouts/ -│ ├── admin/ -│ │ └── config.yml -│ ├── assets/ -│ │ ├── css/ -│ │ ├── favicons/ -│ │ ├── fonts/ -│ │ ├── images/ -│ │ ├── js/ -│ │ ├── sass or less/ -│ │ └── svgs/ -| ├── config/ -│ ├── content/ -│ │ ├── blog/ -│ │ └── pages/ -│ ├── _redirects -│ ├── index.html -│ ├── robots.txt +│ ├── _data/ +│ │ └── client.js +│ ├── _includes/ +│ │ ├── components/ +│ │ ├── layouts/ +│ │ └── sections/ +│ ├── admin/ +│ │ ├── config.yml +│ │ ├── decap-preview-styles.css +│ │ └── index.html +│ ├── assets/ +│ │ ├── favicons/ +│ │ ├── fonts/ +│ │ ├── images/ +│ │ ├── js/ +│ │ ├── less/ +│ │ └── svgs/ +│ ├── config/ +│ │ ├── filters/ +│ │ ├── plugins/ +│ │ └── processors/ +│ ├── content/ +│ │ ├── blog/ +│ │ └── pages/ +│ ├── _redirects +│ ├── index.html +│ ├── robots.html +│ └── sitemap.html ├── .eleventy.js -└── netlify.toml -└── package-lock.json +├── netlify.toml +├── package-lock.json └── package.json ``` @@ -152,28 +182,27 @@ The heart of the kit, the `.eleventy.js` file configures the Eleventy static sit The `.eleventy.js` file is well-documented, with all necessary extra documentation provided for extra reading if desired. A full list of functionalities added via `.eleventy.js` is given below: -- Sets up CSS and JS as template languages, allowing modification at build time by Eleventy. JS is bundled and minified by esbuild. -- Adds the following plugins: - - [Eleventy Navigation](https://github.com/11ty/eleventy-navigation) - Allows the option to define navigation data within the template front matter. - - [Eleventy Sitemap](https://www.npmjs.com/package/@quasibit/eleventy-plugin-sitemap) - Automatically generates a sitemap from all files in `./src/content`. - - [Eleventy Minification](https://github.com/benjaminrancourt/eleventy-plugin-files-minifier) - Minifies HTML and CSS (only run in production - when `npm run build` is executed). -- Passes through all assets (in `./src/assets`) without modification by Eleventy. -- Adds date formatting filters and a year shortcode. -- Sets some basic server options. +- Uses Eleventy build events (`eleventy.after`) to process JS and LESS files externally. JS is bundled and minified by esbuild. LESS is compiled to CSS and run through a PostCSS pipeline (autoprefixer + cssnano in production). +- Adds the following plugins: + - [Eleventy Sitemap](https://github.com/quasibit/eleventy-plugin-sitemap) - Automatically generates a sitemap from all files in `./src/content`. + - [Eleventy Minification](https://github.com/CodeStitchOfficial/eleventy-plugin-minify) - Minifies HTML, CSS, JSON, XML, XSL, and webmanifest files (only run in production - when `npm run build` is executed). + - [Sharp Images](https://github.com/CodeStitchOfficial/eleventy-plugin-sharp-images) - Resizes and optimizes images at build time for better performance. See the [Image Optimization](#image-optimization) section below. +- Passes through all assets (in `./src/assets`), admin files, and redirect rules without modification by Eleventy. +- Adds date formatting filters and a year shortcode. #### netlify.toml -The kit is made to support deployment to Netlify out-of-the-box, enabled through this `netlify.toml` file. Here, some basic configuration is used to define the `public/` directory as serving the built website files, as well as adding a Google Lighthouse plugin to show Lighthouse scores in Netlify. +The kit is made to support deployment to Netlify out-of-the-box, enabled through this `netlify.toml` file. Here, some basic configuration is used to define the `public/` directory as serving the built website files, as well as adding a cache plugin to cache processed images and remote assets between builds for faster deployments. #### package.json and package-lock.json -Standard NodeJS package files, containing the dependencies needed for the project to work. The only things worth noting are the `watch:eleventy` and `build:eleventy` scripts in `package.json`. When `npm start` is used, the `watch:eleventy` script is run, which contains an environment variable (`ELEVENTY_ENV=DEV`). When `npm run build` is used, the `ELEVENTY_ENV` variable is set to `PROD`. +Standard NodeJS package files, containing the dependencies needed for the project to work. The only things worth noting are the `watch:*` and `build:*` scripts in `package.json`. When `npm start` is used, `watch:eleventy` and `watch:cms` (a local Decap CMS proxy server) are run in parallel, with the environment variable `ELEVENTY_ENV` set to `DEV`. When `npm run build` is used, the `ELEVENTY_ENV` variable is set to `PROD`. -You may notice around the project (e.g., `./src/config/server.js` and `.eleventy.js`) that there is reference to an `isProduction` variable. This is used to control some functionality that is only run while the website is "in production". For example, when `npm run build` is used, we can assume the website is deployed to a live domain, so we can do things like minify the code. This allows comments to be shown in the dev tools while you're actively working on the site but have them removed, and the code minified, for the smallest file sizes and most efficiency when you deploy it. +You may notice around the project (e.g., `./src/config/processors/javascript.js`, `./src/config/processors/less.js`, and `.eleventy.js`) that there is reference to an `isProduction` variable. This is used to control some functionality that is only run while the website is "in production". For example, when `npm run build` is used, we can assume the website is deployed to a live domain, so we can do things like minify the code. This allows comments to be shown in the dev tools while you're actively working on the site but have them removed, and the code minified, for the smallest file sizes and most efficiency when you deploy it. You shouldn't have to worry about this, however, as all the initial setup has been done for you. It's still good to know if you wish to expand the kit to add production-only functionality. @@ -203,7 +232,7 @@ This directory contains data files that are accessible within any template throu Consider adding the client's contact details, address, and social media information to this file. Examples have been provided in the kit. This way, you can access the client's information from a single source of truth. If a client changes their email address, you can update it in the `client.js` file and have it reflect across the website without needing to search through multiple files or use Find and Replace. -As an example, we have defined the client's email address under the `email` key. In the footer (`./src/_includes/components/footer.html`), we can use `{{ client.email }}` to access this value and output "help@codestitch.app". The format for outputting the data is `{{ [FILENAME].[KEY] }}`. If we wanted to add another file for pricing information, we could create a file (`_data/pricing.json`), then use `{{ pricing.price }}` to render the price. +As an example, we have defined the client's email address under the `email` key. In the footer (`./src/_includes/sections/footer.html`), we can use `{{ client.email }}` to access this value and output "help@codestitch.app". The format for outputting the data is `{{ [FILENAME].[KEY] }}`. If we wanted to add another file for pricing information, we could create a file (`_data/pricing.json`), then use `{{ pricing.price }}` to render the price. In Eleventy, this is known as "Global Data". You can read more about Global Data [here](https://www.11ty.dev/docs/data-global/), with more information about how this works in the context of the Data Cascade [here](https://www.11ty.dev/docs/data-cascade/). @@ -213,29 +242,17 @@ In Eleventy, this is known as "Global Data". You can read more about Global Data The `_includes` directory contains pieces of HTML code that you want to share between multiple pages. This code could be small components (a button or a loading spinner), larger sections (header, footer, or a stitch from [CodeStitch](https://codestitch.app/)), or a layout containing a reusable `` element with all necessary meta tags. -By default, the kit has two sub-directories in `_includes` - one for components and one for layouts. +By default, the kit has three sub-directories in `_includes` - `components/` for reusable pieces like featured posts and schema markup, `layouts/` for page layouts, and `sections/` for shared page sections like the header, footer, and CTA. -Components can be used on one, none, or many pages. For example, we've set up a header and a footer that we load on all pages (through `_includes/layouts/base.html`). If you want to make a change to the header or footer, you can do so within `_includes/components`, and this change will be reflected across all pages. This is done by using `{% include "components/header.html" %}`. If you want to adjust some of the data within the component (e.g., a button that has the same structure/core styles but different CTA text), you should look into using a [Nunjucks Macro](https://mozilla.github.io/nunjucks/templating#macro), which you can [import](https://mozilla.github.io/nunjucks/templating#import) where needed. +Sections are loaded on all pages through `_includes/layouts/base.html`. For example, the header and footer are included via `{% include "sections/header.html" %}` and `{% include "sections/footer.html" %}`. If you want to make a change to the header or footer, you can do so within `_includes/sections/`, and this change will be reflected across all pages. If you want to adjust some of the data within a component (e.g., a button that has the same structure/core styles but different CTA text), you should look into using a [Nunjucks Macro](https://mozilla.github.io/nunjucks/templating#macro), which you can [import](https://mozilla.github.io/nunjucks/templating#import) where needed. Layouts define the wider page structure. The main one used in this kit is `base.html`, which contains the document type declaration, `` tag with associated `` tags (using data in `_data/client.js` and the page front matter), a `` with a `
` tag and skip-to-content link, and calls to the header and footer. All pages use `base.html`. This has been configured to work automatically, so you shouldn't need much additional work. For the blog, we have also created a `post.html` layout (which also uses the `base.html` file, through Nunjucks' `{% extends %}`) that we use to render the blog article pages. - - -##### Navigations - Rendering Automatically - -One thing you may notice in the `\_includes/header.html` file is the vast amount of Nunjucks code in the `cs-ul-wrapper` element. This is code that makes use of the `eleventyNavigation` object and keys in the frontmatter of all pages that are, by default, added to the kit. This is part of the Eleventy Navigation plugin, which allows us to create scalable navigation menus without having to constantly add new list items and dropdowns to the header whenever a new page is made. If you make a new page using the `\_template.txt` file in `content/pages`, you will be guided to add this information into the front matter, where the navigation will be remade with the new page data automatically. + -If you wish to use this kit, and benefit from this way of doing navigations but want to swap out the navigation for another one in the CodeStitch template library, you can copy the `cs-ul-wrapper` `
` element that's found in the kit and replace the `cs-ul-wrapper` in the new stitch. As the class system is the same with all Stitches, the auto-rendering functionality, including the application of active-style classes and dropdowns (if a "dropdown" Stitch is used) will remain the same. +##### Navigations - - -##### Navigations - Rendering Manually - -Some developers may wish to continue with the "old school" way of rendering navigations and add the HTML for new navigation links to the header individually. This is fine to do too. - -One issue that you may run into, however, is the addition of the `cs-active` class to the page that the user is currently on. As the same navigation element is being rendered on all pages, manually adding the `cs-active` class to one of the navigation items will cause that item to be "activated" between all pages. - -To get around this, you will need to manually add some Nunjucks code to each of the navigation items to check the page the user is on and add `cs-active` if that particular page is being viewed. That code would look like this: +The navigation in `_includes/sections/header.html` is built with manually written nav items. Each navigation link uses a Nunjucks check against `page.url` to conditionally apply the `cs-active` class, highlighting the current page. This looks like: ``` @@ -267,9 +284,9 @@ Styling the Decap preview pane This template includes custom styles for the Deca The preview styles are defined in /admin/decap-preview-styles.css. The CMS preview script in /admin/index.html: -- pulls the props from the collection -- creates the DOM elements -- registers these elements and styles for the preview panel to use +- pulls the props from the collection +- creates the DOM elements +- registers these elements and styles for the preview panel to use 2. How to update or customize: @@ -277,8 +294,8 @@ Edit /admin/decap-preview-styles.css and the preview pane script in /admin/index 3.Notes -- The style sheet must be a CSS file -- The style sheet does not support nested CSS. +- The style sheet must be a CSS file +- The style sheet does not support nested CSS. @@ -286,13 +303,22 @@ Edit /admin/decap-preview-styles.css and the preview pane script in /admin/index All other non-content files are stored in `assets/`, which is set up in `.eleventy.js` to be passed through to `public/`. A brief overview of each of the folders within `assets/`, and any relevant notes, is provided below: -- `css/` - SASS/LESS files from the `less/` or `sass/` directories are built into `css/`. From here, the CSS is processed as an Eleventy template, where we minify the code (production only), and pass it through to `public/`. **Do not** make CSS changes here - instead, use the SASS or LESS asset directory (depending on which kit you're using). -- `favicons/` - Any favicon files can be stored here. We recommend using [this tool](https://realfavicongenerator.net/) to generate favicons for all devices. -- `fonts/` - If you have any non-standard fonts you wish to locally host, you can put the files here. You can use [this tool](https://gwfh.mranftl.com/fonts) to download font files to be stored in `fonts/`, as well as generate the code to be put in your `root.scss` or `root.less` file. -- `images/` - Any images can go here. No processing will occur. -- `js/` - Put any JavaScript in this directory. It will be processed, bundled, and minified by esbuild. -- `sass/` or `less/` - Depending on whether you're using the SASS or LESS version of the kit, you'll find your preprocessor files in one of these directories. Make your changes to styling here, not in `assets/css` -- `svgs/` - A separate directory for SVGs. This makes it easier to bulk-compress SVGs separate from `images/` if you're using a tool like [compressor.io](https://compressor.io/). +- `favicons/` - Any favicon files can be stored here. We recommend using [this tool](https://realfavicongenerator.net/) to generate favicons for all devices. +- `fonts/` - If you have any non-standard fonts you wish to locally host, you can put the files here. You can use [this tool](https://gwfh.mranftl.com/fonts) to download font files to be stored in `fonts/`, as well as generate the code to be put in your `root.less` file. +- `images/` - Any images can go here. If the Sharp Images plugin is enabled, images referenced via `{% getUrl %}` will be resized and optimized at build time. +- `js/` - Put any JavaScript in this directory. It will be processed, bundled, and minified by esbuild. +- `less/` - Your LESS preprocessor files. LESS is compiled to CSS and output directly to `public/assets/css/` by the build event processor, which also handles autoprefixing and minification (production only) via PostCSS. Make your style changes here. +- `svgs/` - A separate directory for SVGs. This makes it easier to bulk-compress SVGs separate from `images/` if you're using a tool like [compressor.io](https://compressor.io/). + + + +#### Image Optimization + +This kit uses [`@codestitchofficial/eleventy-plugin-sharp-images`](https://github.com/CodeStitchOfficial/eleventy-plugin-sharp-images) to resize and convert images at build time, producing optimized AVIF, WebP, and JPEG variants at multiple breakpoints. The plugin provides a `{% getUrl %}` shortcode used throughout the page templates to generate `` elements with multiple `` tags. + +This is **entirely optional** — it is included to demonstrate how the plugin can be used for image optimization, but it is not required. + +For a walkthrough of how the plugin works, see [this video tutorial](https://www.youtube.com/watch?v=scYFC1LRfPg). @@ -308,22 +334,16 @@ Unless you are confident in your Eleventy abilities, we recommend not making any Any files that are built into HTML pages are held in `content/`. This includes standalone informational pages (held in `pages/`) and the blog markdown posts, which use the `post.html` layout (that in turn uses `base.html` through [Eleventy layout chaining](https://www.11ty.dev/docs/layout-chaining/) and [Nunjucks template inheritance](https://mozilla.github.io/nunjucks/templating#block)) to form the blog posts controlled by the CMS. -You're welcome to modify any of these pages or create your own. If you wish to create your own pages, a template can be found in `pages/content/_template.txt`. Copy this file, paste it, rename it to an HTML file, and follow the structure shown in the template. This template contains some code at the top, wrapped between "---"s, which is called the front matter. This contains data specific to this page, which is used within the layout to set the `` tag, `<meta>` description tag, among other things. This data will overwrite any data contained elsewhere in the [Eleventy Data Cascade](https://www.11ty.dev/docs/data-cascade/). +You're welcome to modify any of these pages or create your own. If you wish to create your own pages, a template can be found in `content/pages/_template.txt`. Copy this file, paste it, rename it to an HTML file, and follow the structure shown in the template. This template contains some code at the top, wrapped between "---"s, which is called the front matter. This contains data specific to this page, which is used within the layout to set the `<title>` tag, `<meta>` description tag, among other things. This data will overwrite any data contained elsewhere in the [Eleventy Data Cascade](https://www.11ty.dev/docs/data-cascade/). A copy of the default front matter can be found below: ``` --- -title: 'Page title for <title> and OG tags' -description: 'Description for <meta> description and OG tags' -preloadImg: '/assets/images/imagename.format' -permalink: 'page-path/' -eleventyNavigation: - key: Name to appear in navigation - order: 1000 - parent: Optional - Put another page's "key" here to create a dropdown - hideOnMobile: Optional - set to "true" to hide on devices from, and below, 1023px - hideOnDesktop: Optional - set to "true" to hide on devices above, and including, 1024px +title: "Page title for <title> and OG tags" +description: "Description for <meta> description and OG tags" +permalink: "/page-path/" +image: "OPTIONAL - path to an OG image for this page" --- ``` @@ -411,9 +431,8 @@ With slight modifications for usage with 11ty, this setup guide for DecapBridge Fill in the 3 input fields: -- Github repository: it has to be in a `user-or-org/repository-name` format. e.g. `BuckyBuck135/testing-decapbridge` -- Github access token. To create a personal access token in GitHub, follow these steps: - +- Github repository: it has to be in a `user-or-org/repository-name` format. e.g. `BuckyBuck135/testing-decapbridge` +- Github access token. To create a personal access token in GitHub, follow these steps: 1. Log into your Github account. 2. Click on your profile picture (top right) (not the repository profile), and click the “Settings” link. 3. Scroll down and click the “Developer Settings” link. @@ -430,7 +449,7 @@ Fill in the 3 input fields: 12. Double check your permissions before generating the token. It must have read and write access to Contents and Pull Requests. -- Decap CMS URL: provide the (deployed) URL of the Decap CMS dashboard. e.g [`https://testing-decapbridge.netlify.app/admin/#/`](https://testing-decapbridge.netlify.app/admin/#/) +- Decap CMS URL: provide the (deployed) URL of the Decap CMS dashboard. e.g [`https://testing-decapbridge.netlify.app/admin/#/`](https://testing-decapbridge.netlify.app/admin/#/) ### On your CS Decap kit: diff --git a/package.json b/package.json index e597bfd..184e635 100644 --- a/package.json +++ b/package.json @@ -1,42 +1,44 @@ { - "name": "intermediate-website-kit-less", - "version": "2.0.1", - "description": "The official CodeStitch Intermediate kit, featuring 11ty, Decap CMS and LESS - all set up for you! Perfect for websites of all sizes.", - "main": "index.js", - "scripts": { - "watch:cms": "npx decap-server", - "watch:eleventy": "cross-env ELEVENTY_ENV=DEV eleventy --serve", - "build:eleventy": "cross-env ELEVENTY_ENV=PROD eleventy", - "start": "run-p watch:*", - "build": "run-s build:*", - "preview": "cross-env ELEVENTY_ENV=PROD eleventy --serve" - }, - "repository": { - "type": "git", - "url": "git+https://github.com/CodeStitchOfficial/Intermediate-Website-Kit-LESS.git" - }, - "keywords": [], - "author": "", - "license": "ISC", - "bugs": { - "url": "https://github.com/CodeStitchOfficial/Intermediate-Website-Kit-LESS/issues" - }, - "homepage": "https://github.com/CodeStitchOfficial/Intermediate-Website-Kit-LESS#readme", - "dependencies": { - "@11ty/eleventy": "^3.1.2", - "@codestitchofficial/eleventy-plugin-minify": "^1.1.3", - "@codestitchofficial/eleventy-plugin-sharp-images": "^2.2.0", - "@quasibit/eleventy-plugin-sitemap": "^2.2.0", - "autoprefixer": "^10.4.23", - "codestitch-sharp-image-automation": "^0.4.0", - "cross-env": "^10.1.0", - "cssnano": "^7.1.2", - "decap-server": "^3.5.0", - "esbuild": "^0.27.2", - "glob": "^11.1.0", - "less": "^4.5.1", - "netlify-plugin-cache": "^1.0.3", - "npm-run-all2": "^5.0.2", - "postcss": "^8.5.6" - } -} + "name": "intermediate-website-kit-less", + "version": "2.0.1", + "description": "The official CodeStitch Intermediate kit, featuring 11ty, Decap CMS and LESS - all set up for you! Perfect for websites of all sizes.", + "main": "index.js", + "scripts": { + "watch:cms": "npx decap-server", + "watch:eleventy": "cross-env ELEVENTY_ENV=DEV eleventy --serve", + "build:eleventy": "cross-env ELEVENTY_ENV=PROD eleventy", + "start": "run-p watch:*", + "build": "run-s build:*", + "preview": "cross-env ELEVENTY_ENV=PROD eleventy --serve", + "remove-dark-mode": "node scripts/remove-dark-mode.js", + "remove-decap": "node scripts/remove-decap.js", + "remove-demo": "node scripts/remove-demo.js" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/CodeStitchOfficial/Intermediate-Website-Kit-LESS.git" + }, + "keywords": [], + "author": "", + "license": "ISC", + "bugs": { + "url": "https://github.com/CodeStitchOfficial/Intermediate-Website-Kit-LESS/issues" + }, + "homepage": "https://github.com/CodeStitchOfficial/Intermediate-Website-Kit-LESS#readme", + "dependencies": { + "@11ty/eleventy": "^3.1.2", + "@codestitchofficial/eleventy-plugin-minify": "^1.1.3", + "@codestitchofficial/eleventy-plugin-sharp-images": "^2.2.0", + "@quasibit/eleventy-plugin-sitemap": "^2.2.0", + "autoprefixer": "^10.4.23", + "codestitch-sharp-image-automation": "^0.4.0", + "cross-env": "^10.1.0", + "cssnano": "^7.1.2", + "esbuild": "^0.27.2", + "glob": "^11.1.0", + "less": "^4.5.1", + "netlify-plugin-cache": "^1.0.3", + "npm-run-all2": "^5.0.2", + "postcss": "^8.5.6" + } +} \ No newline at end of file diff --git a/scripts/remove-dark-mode.js b/scripts/remove-dark-mode.js new file mode 100644 index 0000000..dfb0238 --- /dev/null +++ b/scripts/remove-dark-mode.js @@ -0,0 +1,210 @@ +const fs = require("fs"); +const path = require("path"); + +const paths = { + baseHtml: "src/_includes/layouts/base.html", + headerHtml: "src/_includes/sections/header.html", + rootLess: "src/assets/less/root.less", + darkJs: "src/assets/js/dark.js", + navJs: "src/assets/js/nav.js", +}; + +function resolvePath(p) { + return path.join(process.cwd(), p); +} + +function removeFile(filePath) { + const fullPath = resolvePath(filePath); + if (fs.existsSync(fullPath)) { + fs.unlinkSync(fullPath); + console.log(`Deleted ${filePath}`); + } else { + console.log(`File not found: ${filePath}`); + } +} + +function updateFile(filePath, replacements) { + const fullPath = resolvePath(filePath); + if (!fs.existsSync(fullPath)) { + console.log(`File not found: ${filePath}`); + return; + } + + let content = fs.readFileSync(fullPath, "utf8"); + let originalContent = content; + + replacements.forEach(({ pattern, replacement, name }) => { + content = content.replace(pattern, replacement); + }); + + if (content !== originalContent) { + fs.writeFileSync(fullPath, content, "utf8"); + console.log(`Updated ${filePath}`); + } else { + console.log(`No changes needed for ${filePath}`); + } +} + +// Helper to remove a CSS block by selector, handling balanced braces +function removeCssBlock(content, selector) { + let result = content; + + // Escaping selector for use in new RegExp + const escapedSelector = selector.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + // Match selector followed by anything until { + const selectorRegex = new RegExp( + `\\s*${escapedSelector}(?:\\s+[^{,]+)?\\s*\\{`, + "g", + ); + + let match; + while ((match = selectorRegex.exec(result)) !== null) { + const startIndex = match.index; + const openBraceIndex = result.indexOf("{", startIndex); + + let braceCount = 1; + let i = openBraceIndex + 1; + while (braceCount > 0 && i < result.length) { + if (result[i] === "{") braceCount++; + else if (result[i] === "}") braceCount--; + i++; + } + + if (braceCount === 0) { + // Remove from match start to end brace + result = result.substring(0, startIndex) + result.substring(i); + // Reset regex to search from beginning since string changed + selectorRegex.lastIndex = 0; + } else { + // Unbalanced, skip + selectorRegex.lastIndex = startIndex + 1; + } + } + return result; +} + +// Custom LESS cleaner +function cleanLess(filePath) { + const fullPath = resolvePath(filePath); + if (!fs.existsSync(fullPath)) { + console.log(`File not found: ${filePath}`); + return; + } + + let content = fs.readFileSync(fullPath, "utf8"); + const originalContent = content; + + // 1. Remove Dark Mode variables in :root + content = content.replace(/--dark:\s*#[0-9a-fA-F]+;\s*/g, ""); + content = content.replace(/--medium:\s*#[0-9a-fA-F]+;\s*/g, ""); + content = content.replace(/--accent:\s*#[0-9a-fA-F]+;\s*/g, ""); + + // 2. Remove "Dark Mode" comments (single-line and multi-line CodeStitch section headers) + // Uses [\s\S] instead of a character range so newlines inside multi-line comments are matched + content = content.replace( + /\/\*(?:(?!\*\/)[\s\S])*?[Dd]ark [Mm]ode(?:(?!\*\/)[\s\S])*?\*\/\s*/g, + "", + ); + + // 3. Remove dark mode specific blocks + content = removeCssBlock(content, "body.dark-mode"); + content = removeCssBlock(content, "#dark-mode-toggle"); + + // 4. Remove .cs-dark/.dark utility classes (only useful with dark mode) + content = removeCssBlock(content, ".cs-dark"); + + // 5. Remove .cs-dark, .dark comma-separated block (removeCssBlock can't handle comma-separated selectors) + content = content.replace( + /\n?\s*\.cs-dark,\s*\n?\s*\.dark\s*\{[^}]*\}/g, + "", + ); + + // 6. Remove nested &.dark blocks (e.g. inside .cs-logo-img) + content = content.replace(/\n?\s*&\.dark\s*\{[^}]*\}/g, ""); + + // 7. Remove the dark mode background-color transition on html/body + content = content.replace( + /\s*transition:\s*background-color\s+0\.3s;\s*/g, + "\n", + ); + + // 8. Remove Empty Media Queries (generic) + // We run this multiple times to catch media queries that become empty after their content is removed + const emptyMediaRegex = /@media[^{]+\{\s*\}/g; + let loopCount = 0; + while (emptyMediaRegex.test(content) && loopCount < 5) { + content = content.replace(emptyMediaRegex, ""); + loopCount++; + } + + // 9. Clean up multiple empty lines + content = content.replace(/\n{3,}/g, "\n\n"); + + if (content !== originalContent) { + fs.writeFileSync(fullPath, content, "utf8"); + console.log(`Deep cleaned ${filePath}`); + } else { + console.log(`No deeper changes needed for ${filePath}`); + } +} + +// --- EXECUTION --- + +// 1. Remove dark.js +removeFile(paths.darkJs); + +// 2. Update base.html +updateFile(paths.baseHtml, [ + { + name: "Remove dark.js script tag", + pattern: /\s*<script defer src="\/assets\/js\/dark.js"><\/script>/g, + replacement: "", + }, +]); + +// 3. Update nav.js - remove dark mode config and element lookup +updateFile(paths.navJs, [ + { + name: "Remove darkModeToggle config entry", + pattern: /\s*darkModeToggle:\s*"#dark-mode-toggle",?/g, + replacement: "", + }, + { + name: "Remove darkModeToggle element lookup", + pattern: + /\s*darkModeToggle:\s*document\.querySelector\(CONFIG\.SELECTORS\.darkModeToggle\),?/g, + replacement: "", + }, +]); + +// 4. Update header.html - remove toggle comment and button together +updateFile(paths.headerHtml, [ + { + name: "Dark mode toggle comment and button", + pattern: + /\s*<!--Dark Mode toggle[\s\S]*?-->\s*<button[^>]*id="dark-mode-toggle"[\s\S]*?<\/button>/gi, + replacement: "", + }, +]); + +// 5. Update root.less file description to remove dark mode mention +updateFile(paths.rootLess, [ + { + name: "Remove dark mode from file description", + pattern: /dark mode styles, /gi, + replacement: "", + }, +]); + +// 6. Update all LESS files using the smart cleaner +const lessDir = resolvePath("src/assets/less"); +if (fs.existsSync(lessDir)) { + const lessFiles = fs + .readdirSync(lessDir) + .filter((file) => file.endsWith(".less")); + lessFiles.forEach((file) => { + cleanLess(path.join("src/assets/less", file)); + }); +} + +console.log("Dark mode removal script completed."); diff --git a/scripts/remove-decap.js b/scripts/remove-decap.js new file mode 100644 index 0000000..f263b1b --- /dev/null +++ b/scripts/remove-decap.js @@ -0,0 +1,289 @@ +const fs = require("fs"); +const path = require("path"); +const readline = require("readline"); +const { collectFiles } = require("./utils/collect-files.js"); + +const destinationDir = path.join(process.cwd(), "scripts", "deleted"); + +// ─── Helpers ───────────────────────────────────────────────────────────────── + +function resolvePath(p) { + return path.join(process.cwd(), p); +} + +function ensureDir(dir) { + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); + } +} + +function moveItem(src, dest) { + const fullSrc = resolvePath(src); + if (!fs.existsSync(fullSrc)) { + console.log(`File not found: ${src}`); + return false; + } + ensureDir(path.dirname(dest)); + // Remove destination if it already exists + if (fs.existsSync(dest)) { + fs.rmSync(dest, { recursive: true, force: true }); + } + fs.renameSync(fullSrc, dest); + console.log(`Moved ${src} → ${path.relative(process.cwd(), dest)}`); + return true; +} + +function updateFile(filePath, replacements) { + const fullPath = resolvePath(filePath); + if (!fs.existsSync(fullPath)) { + console.log(`File not found: ${filePath}`); + return; + } + + let content = fs.readFileSync(fullPath, "utf8"); + const originalContent = content; + + for (const { pattern, replacement } of replacements) { + content = content.replace(pattern, replacement); + } + + if (content !== originalContent) { + fs.writeFileSync(fullPath, content, "utf8"); + console.log(`Updated ${filePath}`); + } else { + console.log(`No changes needed for ${filePath}`); + } +} + +function ask(question) { + return new Promise((resolve) => { + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + }); + rl.question(question, (answer) => { + rl.close(); + resolve(answer.toLowerCase() === "y"); + }); + }); +} + +// ─── Core Decap Removal ───────────────────────────────────────────────────── + +function removeDecapCore() { + console.log("\n--- Removing Decap CMS core files ---\n"); + + // 1. Move src/admin/ → scripts/deleted/admin/ + moveItem("src/admin", path.join(destinationDir, "admin")); + + // 2. Update .eleventy.js – remove admin passthrough + updateFile(".eleventy.js", [ + { + pattern: /\s*eleventyConfig\.addPassthroughCopy\("\.\/src\/admin"\);\s*\/\/.*\n?/, + replacement: "\n", + }, + ]); + + // 3. Update package.json – remove watch:cms, decap-server, simplify start + const pkgPath = resolvePath("package.json"); + if (fs.existsSync(pkgPath)) { + let pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8")); + let changed = false; + + // Remove watch:cms script + if (pkg.scripts && pkg.scripts["watch:cms"]) { + delete pkg.scripts["watch:cms"]; + changed = true; + } + + // Simplify start script: run-p watch:* → just run watch:eleventy + if (pkg.scripts && pkg.scripts.start && pkg.scripts.start.includes("run-p watch:*")) { + pkg.scripts.start = "npm run watch:eleventy"; + changed = true; + } + + // Remove decap-server dependency + if (pkg.dependencies && pkg.dependencies["decap-server"]) { + delete pkg.dependencies["decap-server"]; + changed = true; + } + if (pkg.devDependencies && pkg.devDependencies["decap-server"]) { + delete pkg.devDependencies["decap-server"]; + changed = true; + } + + if (changed) { + fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, "\t") + "\n", "utf8"); + console.log("Updated package.json"); + } else { + console.log("No changes needed for package.json"); + } + } + + // 4. Update robots.html – remove Disallow: /admin/ line + updateFile("src/robots.html", [ + { + pattern: /Disallow: \/admin\/\n?/, + replacement: "", + }, + ]); + + // 5. Update src/_redirects – remove /admin redirect lines + const redirectsPath = resolvePath("src/_redirects"); + if (fs.existsSync(redirectsPath)) { + let content = fs.readFileSync(redirectsPath, "utf8"); + const original = content; + content = content.replace(/.*\/admin.*\n?/g, ""); + if (content !== original) { + fs.writeFileSync(redirectsPath, content, "utf8"); + console.log("Updated src/_redirects"); + } else { + console.log("No changes needed for src/_redirects"); + } + } +} + +// ─── Blog Removal ──────────────────────────────────────────────────────────── + +function removeBlog() { + console.log("\n--- Removing blog content ---\n"); + + // Move blog content directory + moveItem("src/content/blog", path.join(destinationDir, "blog")); + + // Move blog page + moveItem("src/content/pages/blog.html", path.join(destinationDir, "blog-page.html")); + + // Move post layout + moveItem("src/_includes/layouts/post.html", path.join(destinationDir, "post-layout.html")); + + // Move blog components + moveItem("src/_includes/components/featured-posts.html", path.join(destinationDir, "featured-posts.html")); + moveItem("src/_includes/components/post-schema.html", path.join(destinationDir, "post-schema.html")); + + // Move blog LESS + moveItem("src/assets/less/blog.less", path.join(destinationDir, "blog.less")); + + // Move blog images + moveItem("src/assets/images/blog", path.join(destinationDir, "images-blog")); + + // Move cabinets.jpg (used as blog banner in post.html and blog.html) + moveItem("src/assets/images/cabinets.jpg", path.join(destinationDir, "cabinets.jpg")); + + // Update header – remove blog <li> nav link + updateFile("src/_includes/sections/header.html", [ + { + pattern: /\s*<li class="cs-li">\s*<a href="\/blog\/"[^>]*>[\s\S]*?Blog[\s\S]*?<\/a>\s*<\/li>/, + replacement: "", + }, + ]); + + // Update footer – remove blog <li> nav link + updateFile("src/_includes/sections/footer.html", [ + { + pattern: /\s*<li class="cs-nav-li">\s*<a class="cs-nav-link" href="\/blog\/">Blog<\/a>\s*<\/li>/, + replacement: "", + }, + ]); +} + +// ─── Scan & Report ─────────────────────────────────────────────────────────── + +async function scanForReferences(removedBlog) { + console.log("\nScanning for remaining references..."); + + const files = []; + const srcDir = path.join(process.cwd(), "src"); + + try { + await collectFiles(files, srcDir); + } catch (error) { + console.error(`Error collecting files: ${error}`); + return { decapRefs: [], blogRefs: [] }; + } + + const decapRefs = []; + const blogRefs = []; + + for (const file of files) { + try { + const content = fs.readFileSync(file, "utf-8"); + + if (/decap|netlify-cms/i.test(content)) { + decapRefs.push(file); + } + + if (removedBlog && /\/blog\/|blog\.html|blog\.less|featured-posts|post-schema/i.test(content)) { + blogRefs.push(file); + } + } catch { + continue; + } + } + + if (decapRefs.length > 0) { + console.log(`\nFound ${decapRefs.length} file(s) with remaining Decap CMS references:`); + decapRefs.forEach((f) => console.log(` - ${path.relative(process.cwd(), f)}`)); + } + + if (blogRefs.length > 0) { + console.log(`\nFound ${blogRefs.length} file(s) with remaining blog references:`); + blogRefs.forEach((f) => console.log(` - ${path.relative(process.cwd(), f)}`)); + } + + return { decapRefs, blogRefs }; +} + +// ─── Main ──────────────────────────────────────────────────────────────────── + +async function main() { + const confirmed = await ask("Are you sure you want to remove Decap CMS from this project? (y/n): "); + if (!confirmed) { + console.log("Operation cancelled."); + process.exit(0); + } + + const removeBlogContent = await ask("Do you also want to remove all blog-related content? (y/n): "); + + // Create destination directory + ensureDir(destinationDir); + + // Always remove Decap core + removeDecapCore(); + + // Conditionally remove blog + if (removeBlogContent) { + removeBlog(); + } + + // Scan for leftovers + const { decapRefs, blogRefs } = await scanForReferences(removeBlogContent); + + console.log("\n...done!\n"); + console.log("================================================="); + console.log(" Successfully removed Decap CMS from the project"); + console.log("=================================================\n"); + + if (decapRefs.length > 0 || (blogRefs.length > 0 && removeBlogContent)) { + console.log("Manual cleanup needed:"); + if (decapRefs.length > 0) { + console.log(" - Review files with Decap CMS references listed above"); + } + if (blogRefs.length > 0 && removeBlogContent) { + console.log(" - Review files with remaining blog references listed above"); + } + console.log(); + } + + console.log("Next steps:"); + if (removeBlogContent) { + console.log("1. Run your build to ensure everything works"); + console.log("2. All removed files are in scripts/deleted/ if you need to restore them\n"); + } else { + console.log("1. Blog content remains intact - you can manage posts locally via markdown"); + console.log("2. Run your build to ensure everything works"); + console.log("3. All removed files are in scripts/deleted/ if you need to restore them\n"); + } +} + +main(); diff --git a/scripts/remove-demo.js b/scripts/remove-demo.js new file mode 100644 index 0000000..d178d65 --- /dev/null +++ b/scripts/remove-demo.js @@ -0,0 +1,356 @@ +const fs = require("fs"); +const path = require("path"); +const readline = require("readline"); + +const destinationDir = path.join(process.cwd(), "scripts", "deleted"); + +// ─── Helpers ───────────────────────────────────────────────────────────────── + +function resolvePath(p) { + return path.join(process.cwd(), p); +} + +function ensureDir(dir) { + if (!fs.existsSync(dir)) { + fs.mkdirSync(dir, { recursive: true }); + } +} + +function moveItem(src, dest) { + const fullSrc = resolvePath(src); + if (!fs.existsSync(fullSrc)) { + console.log(`File not found: ${src}`); + return false; + } + ensureDir(path.dirname(dest)); + if (fs.existsSync(dest)) { + fs.rmSync(dest, { recursive: true, force: true }); + } + fs.renameSync(fullSrc, dest); + console.log(`Moved ${src} → ${path.relative(process.cwd(), dest)}`); + return true; +} + +function updateFile(filePath, replacements) { + const fullPath = resolvePath(filePath); + if (!fs.existsSync(fullPath)) { + console.log(`File not found: ${filePath}`); + return; + } + + let content = fs.readFileSync(fullPath, "utf8"); + const originalContent = content; + + for (const { pattern, replacement } of replacements) { + content = content.replace(pattern, replacement); + } + + if (content !== originalContent) { + fs.writeFileSync(fullPath, content, "utf8"); + console.log(`Updated ${filePath}`); + } else { + console.log(`No changes needed for ${filePath}`); + } +} + +function ask(question) { + return new Promise((resolve) => { + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + }); + rl.question(question, (answer) => { + rl.close(); + resolve(answer.toLowerCase() === "y"); + }); + }); +} + +// ─── Move Demo Pages ───────────────────────────────────────────────────────── + +function moveDemoPages() { + console.log("\n--- Moving demo pages ---\n"); + const dest = path.join(destinationDir, "pages-demo"); + + const pages = [ + "about.html", + "contact.html", + "reviews.html", + "project-one.html", + "project-two.html", + ]; + + let count = 0; + for (const page of pages) { + if (moveItem(`src/content/pages/${page}`, path.join(dest, page))) + count++; + } + console.log(`Moved ${count} demo page(s)`); +} + +// ─── Move Demo LESS Files ──────────────────────────────────────────────────── + +function moveDemoLess() { + console.log("\n--- Moving demo LESS files ---\n"); + const dest = path.join(destinationDir, "less-demo"); + + const lessFiles = [ + "about.less", + "contact.less", + "reviews.less", + "projects.less", + ]; + + let count = 0; + for (const file of lessFiles) { + if (moveItem(`src/assets/less/${file}`, path.join(dest, file))) count++; + } + console.log(`Moved ${count} demo LESS file(s)`); +} + +// ─── Move Demo Images ──────────────────────────────────────────────────────── + +function moveDemoImages() { + console.log("\n--- Moving demo images ---\n"); + const dest = path.join(destinationDir, "images-demo"); + + // Note: cabinets.jpg is kept because blog templates (post.html, blog.html) use it as a banner image. + // It will be moved when remove-decap is run with blog removal. + const images = ["landing.jpg", "construction.jpg"]; + let count = 0; + + for (const img of images) { + if (moveItem(`src/assets/images/${img}`, path.join(dest, img))) count++; + } + + // Move portfolio/ folder + if (moveItem("src/assets/images/portfolio", path.join(dest, "portfolio"))) + count++; + + // Move gallery/ folder if it exists + if (moveItem("src/assets/images/gallery", path.join(dest, "gallery"))) + count++; + + console.log(`Moved ${count} demo image(s)/folder(s)`); +} + +// ─── Move Demo SVGs ────────────────────────────────────────────────────────── + +function moveDemoSvgs() { + console.log("\n--- Moving demo SVGs ---\n"); + const dest = path.join(destinationDir, "svgs-demo"); + + const svgs = [ + "quote.svg", + "s1.svg", + "s2.svg", + "s3.svg", + "s4.svg", + "stars.svg", + ]; + + let count = 0; + for (const svg of svgs) { + if (moveItem(`src/assets/svgs/${svg}`, path.join(dest, svg))) count++; + } + console.log(`Moved ${count} demo SVG(s)`); +} + +// ─── Move Demo Sections ────────────────────────────────────────────────────── + +function moveDemoSections() { + console.log("\n--- Moving demo sections ---\n"); + const dest = path.join(destinationDir, "sections-demo"); + + moveItem("src/_includes/sections/cta.html", path.join(dest, "cta.html")); +} + +// ─── Simplify Index ────────────────────────────────────────────────────────── + +function simplifyIndex() { + console.log("\n--- Simplifying index.html ---\n"); + const indexPath = resolvePath("src/index.html"); + + const content = `--- +title: "Welcome" +description: "Your new website" +permalink: "/" +tags: "sitemap" +--- + +{% extends "layouts/base.html" %} + +{% block head %} + <style> + #welcome { + padding: 100px 16px; + } + #welcome .cs-container { + max-width: 1280px; + margin: 0 auto; + } + #welcome .cs-content { + max-width: 800px; + margin: 0 auto; + text-align: center; + } + #welcome h1 { + margin-bottom: 24px; + font-size: clamp(2rem, 5vw, 3rem); + } + #welcome p { + margin-bottom: 16px; + font-size: 1.125rem; + line-height: 1.6; + } + #welcome code { + padding: 2px 8px; + background: #f4f4f4; + border-radius: 4px; + font-family: monospace; + } + #welcome a { + color: var(--primary); + text-decoration: underline; + } + #welcome a:hover { + opacity: 0.8; + } + </style> +{% endblock %} + +{% block body %} + <section id="welcome"> + <div class="cs-container"> + <div class="cs-content"> + <h1>Welcome to Your New Website</h1> + <p> + This template has been stripped to its bare minimum. All demo content + has been moved to <code>scripts/deleted/</code> and can be safely deleted + once you no longer need it. + </p> + <p> + Get started by reading the + Quick Start Guide + section in the README. + </p> + </div> + </div> + </section> +{% endblock %} +`; + + fs.writeFileSync(indexPath, content, "utf8"); + console.log("Simplified src/index.html"); +} + +// ─── Update Header ─────────────────────────────────────────────────────────── + +function updateHeader() { + console.log("\n--- Updating header ---\n"); + + updateFile("src/_includes/sections/header.html", [ + // Remove About nav item + { + pattern: + /\s*<li class="cs-li">\s*<a href="\/about\/"[^>]*>[\s\S]*?About[\s\S]*?<\/a>\s*<\/li>/, + replacement: "", + }, + // Remove Projects dropdown (entire <li class="cs-li cs-dropdown">...</li>) + { + pattern: /\s*<li class="cs-li cs-dropdown">[\s\S]*?<\/ul>\s*<\/li>/, + replacement: "", + }, + // Remove Reviews nav item + { + pattern: + /\s*<li class="cs-li">\s*<a href="\/reviews\/"[^>]*>[\s\S]*?Reviews[\s\S]*?<\/a>\s*<\/li>/, + replacement: "", + }, + // Remove Contact mobile nav item (cs-hide-on-desktop) + { + pattern: + /\s*<li class="cs-li cs-hide-on-desktop">\s*<a href="\/contact\/"[^>]*>[\s\S]*?Contact[\s\S]*?<\/a>\s*<\/li>/, + replacement: "", + }, + // Remove Contact Us CTA button + { + pattern: + /\s*<a href="\/contact\/" class="cs-button-solid cs-nav-button">Contact Us<\/a>/, + replacement: "", + }, + ]); +} + +// ─── Update Footer ─────────────────────────────────────────────────────────── + +function updateFooter() { + console.log("\n--- Updating footer ---\n"); + + updateFile("src/_includes/sections/footer.html", [ + // Remove About link + { + pattern: + /\s*<li class="cs-nav-li">\s*<a class="cs-nav-link" href="\/about\/">About<\/a>\s*<\/li>/, + replacement: "", + }, + // Remove Reviews link + { + pattern: + /\s*<li class="cs-nav-li">\s*<a class="cs-nav-link" href="\/reviews\/">Reviews<\/a>\s*<\/li>/, + replacement: "", + }, + // Remove Contact link + { + pattern: + /\s*<li class="cs-nav-li">\s*<a class="cs-nav-link" href="\/contact\/">Contact<\/a>\s*<\/li>/, + replacement: "", + }, + // Remove entire Projects nav section + { + pattern: + /\s*<ul class="cs-nav">\s*<li class="cs-nav-li">\s*<span class="cs-header">Projects<\/span>\s*<\/li>[\s\S]*?<\/ul>/, + replacement: "", + }, + ]); +} + +// ─── Main ──────────────────────────────────────────────────────────────────── + +async function main() { + const confirmed = await ask( + "This will remove all demo content and strip the template to bare minimum. Continue? (y/n): ", + ); + + if (!confirmed) { + console.log("Operation cancelled."); + process.exit(0); + } + + // Create destination directory + ensureDir(destinationDir); + + // Move all demo content + moveDemoPages(); + moveDemoLess(); + moveDemoImages(); + moveDemoSvgs(); + moveDemoSections(); + + // Update files + simplifyIndex(); + updateHeader(); + updateFooter(); + + console.log("\n...done!\n"); + console.log("================================================="); + console.log(" Successfully removed demo content"); + console.log("=================================================\n"); + + console.log("Next steps:"); + console.log("- Review removed files in scripts/deleted/"); + console.log("- Run 'npm start' to start building your site"); + console.log("- To remove Decap CMS/blog: 'npm run remove-decap'\n"); +} + +main(); diff --git a/scripts/utils/collect-files.js b/scripts/utils/collect-files.js new file mode 100644 index 0000000..5b84e36 --- /dev/null +++ b/scripts/utils/collect-files.js @@ -0,0 +1,19 @@ +const { readdir } = require("fs/promises"); +const { resolve, extname } = require("path"); + +const ALLOWED_EXTENSIONS = [".html", ".njk", ".less", ".md", ".ts", ".js", ".mjs", ".cjs"]; + +// Collect files with specific extensions +async function collectFiles(files, dir) { + const dirents = await readdir(dir, { withFileTypes: true }); + for (const dirent of dirents) { + const res = resolve(dir, dirent.name); + if (dirent.isDirectory() && dirent.name !== "js") { + await collectFiles(files, res); + } else if (dirent.isFile() && ALLOWED_EXTENSIONS.includes(extname(res))) { + files.push(res); + } + } +} + +module.exports = { collectFiles }; diff --git a/scripts/utils/replace-in-files.js b/scripts/utils/replace-in-files.js new file mode 100644 index 0000000..0e70fcb --- /dev/null +++ b/scripts/utils/replace-in-files.js @@ -0,0 +1,41 @@ +const fs = require("fs"); +const { join } = require("path"); + +/** + * Search through files in passed folder (including subdirectories) and replace text + * @param {string} path - folder path to search through + * @param {string} regex - regex string to match + * @param {string} replacement - replacement text to replace each regex match with + * @param {boolean} logging - whether to log updated files + */ +function replaceInFiles(path, regex, replacement, logging = false) { + const files = fs.readdirSync(path); + for (const file of files) { + const filePath = join(path, file); + const stats = fs.statSync(filePath); + if (stats.isDirectory()) { + // Recursively process subdirectories + replaceInFiles(filePath, regex, replacement); + } else { + try { + // Read file content + const content = fs.readFileSync(filePath, "utf-8"); + // Create regex object from string + const searchRegex = new RegExp(regex, "g"); + // Replace matches + const updatedContent = content.replace(searchRegex, replacement); + // Only write if content changed + if (content !== updatedContent) { + fs.writeFileSync(filePath, updatedContent, "utf-8"); + if (logging) { + console.log(`Updated ${filePath}`); + } + } + } catch (error) { + console.error(`Error processing file ${filePath}:`, error); + } + } + } +} + +module.exports = { replaceInFiles };