diff --git a/.gitignore b/.gitignore index c42c3f7..48339b6 100644 --- a/.gitignore +++ b/.gitignore @@ -14,8 +14,13 @@ # production /build -/dist /.build +/dist +/.dist + +# development +/dev +/.dev # misc .DS_Store diff --git a/next.config.js b/next.config.js index 86533bc..ada4d18 100644 --- a/next.config.js +++ b/next.config.js @@ -9,15 +9,16 @@ // Dependencies const path = require('path'); const StylelintPlugin = require('stylelint-webpack-plugin'); -const package = require('./package.json'); +const { Constants } = require('./.dev/src/constants/Constants'); +const { Routes } = require('./.dev/src/routes/Routes'); -// Contstants: begin -const appName = 'Notiflix'; -const appVersion = (JSON.stringify((package || {}).version) || '').replace(/"/gm, '') || 'v1.0.0'; +// Constants: begin const isDev = process.env.NODE_ENV === 'development'; const isProd = process.env.NODE_ENV === 'production'; -const publicUrl = isProd ? (JSON.stringify((package || {}).homepage) || '').replace(/"/gm, '') : ''; -// Contstants: end +const appUrl = isProd ? Constants.appUrl : ''; +const appName = Constants.appName; +const appVersion = Constants.appVersion; +// Constants: end // Next Config: begin const nextConfig = { @@ -40,13 +41,13 @@ const nextConfig = { env: { isDev, isProd, - publicUrl, + appUrl, appName, appVersion, }, // assets prefix - assetPrefix: publicUrl, + assetPrefix: appUrl, // extensions pageExtensions: ['jsx', 'js', 'ts', 'tsx'], @@ -75,15 +76,23 @@ const nextConfig = { ] }, - // TODO: exportPathMap: async ( defaultPathMap, { dev, dir, outDir, distDir, buildId } ) => { - return { + + let defaultPaths = { '/': { page: '/home' }, - '/about': { page: '/about' }, - } + }; + + Routes?.filter(route => route.isActive && route.addToNextJSConfig)?.map(route => { + const routePath = { + [route.pathAs]: { page: route.pathPage }, + }; + defaultPaths = { ...defaultPaths, ...routePath }; + }); + + return defaultPaths; }, // Build ID diff --git a/package.json b/package.json index 52362c4..66ff0f1 100644 --- a/package.json +++ b/package.json @@ -1,14 +1,16 @@ { "name": "notiflix-documentation", "homepage": "https://notiflix.github.io", - "version": "3.0.1-beta.01", + "version": "1.0.0-beta.01", "private": true, "scripts": { + "sitemap:dev": "eslint src/constants/Constants.ts src/routes/Routes.ts src/helpers/Sitemap.ts", + "sitemap": "yarn sitemap:dev && tsc --resolveJsonModule --outDir .dev src/helpers/Sitemap.ts && node .dev/src/helpers/Sitemap.js", "lint": "next lint", - "dev": "next lint && next dev", - "build": "next build", - "start": "next start", - "deploy": "next build && next export -o dist" + "dev": "yarn sitemap && next lint && next dev", + "build": "yarn sitemap && next build", + "start": "yarn sitemap && next start", + "deploy": "yarn build && next export -o dist" }, "dependencies": { "next": "11.0.1", @@ -27,6 +29,7 @@ "stylelint-config-standard": "^22.0.0", "stylelint-scss": "^3.19.0", "stylelint-webpack-plugin": "^2.2.2", + "parse-md": "^2.0.4", "typescript": "4.3.5" }, "browserslist": { diff --git a/public/sitemap.xml b/public/sitemap.xml new file mode 100644 index 0000000..b094dea --- /dev/null +++ b/public/sitemap.xml @@ -0,0 +1,27 @@ + + + + + + https://notiflix.github.io + 2021-07-22 + daily + 1.0 + + https://notiflix.github.io/webapp/notiflix-og.jpg + Notiflix + + + + + https://notiflix.github.io/about + 2021-07-22 + daily + 1.0 + + https://notiflix.github.io/webapp/notiflix-og.jpg + Notiflix + + + + \ No newline at end of file diff --git a/public/sitemap.xsl b/public/sitemap.xsl new file mode 100644 index 0000000..b648853 --- /dev/null +++ b/public/sitemap.xsl @@ -0,0 +1,212 @@ + + + + + + + Sitemap + SitemapIndex + + + + + + <xsl:choose> + <xsl:when test="$fileType='Sitemap'">Sitemap - Notiflix</xsl:when> + <xsl:otherwise>Sitemap Index - Notiflix</xsl:otherwise> + </xsl:choose> + + + + + + +
+

XML Sitemap

+

Generated by Notiflix

+

You can find more information about XML sitemaps at sitemaps.org

+

+ + This sitemap contains URLs. + This Index contains sitemaps. + +

+
+ + + + + + + + + + +
+ + + + + + + + + + + + + + + second + + + + + + +
URLLastChange
+ + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + second + + + + + + + + + +
URLImagesPriorityChange FrequencyLast Change
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
diff --git a/src/_database/database.i.ts b/src/_database/database.i.ts index 9d3d1c5..e575757 100644 --- a/src/_database/database.i.ts +++ b/src/_database/database.i.ts @@ -21,6 +21,7 @@ interface IDatabaseSocialMedia { } interface IDatabaseMeta { + lastModifiedDate: string; title: string; description: string; robots?: string | null; diff --git a/src/_database/pages/about.md b/src/_database/pages/about.md index 8e67af5..e95e854 100644 --- a/src/_database/pages/about.md +++ b/src/_database/pages/about.md @@ -1,5 +1,6 @@ --- _dbMeta: + lastModifiedDate: "2021-07-22" # YYYY-MM-DD title: About Us | Notiflix description: About Us Description robots: noindex, nofollow, noodp, noydir diff --git a/src/_database/pages/home.md b/src/_database/pages/home.md index 52aab23..6b6d440 100644 --- a/src/_database/pages/home.md +++ b/src/_database/pages/home.md @@ -1,5 +1,6 @@ --- _dbMeta: + lastModifiedDate: "2021-07-22" # YYYY-MM-DD title: Notiflix | a JavaScript library for client-side non-blocking notifications. description: Notiflix is a pure JavaScript library for client-side non-blocking notifications, popup boxes, loading indicators, and more to that makes your web projects much better. robots: noindex, nofollow, noodp, noydir diff --git a/src/components/meta/MetaTags.tsx b/src/components/meta/MetaTags.tsx index 1fe27c0..96b94ab 100644 --- a/src/components/meta/MetaTags.tsx +++ b/src/components/meta/MetaTags.tsx @@ -13,8 +13,8 @@ function MetaTags({ meta }: IMetaTags): JSX.Element { const router = useRouter(); const appName = process.env.appName; - const publicUrl = process.env.publicUrl; - const canonicalUrl = `${publicUrl || ''}${(router?.asPath?.length > 1 ? router.asPath : '')}` || ''; + const appUrl = process.env.appUrl; + const canonicalUrl = `${appUrl || ''}${(router?.asPath?.length > 1 ? router.asPath : '')}` || ''; const yearInit = _dbSettings.metaYearInit; const yearCurrent = new Date().getFullYear() || ''; @@ -39,7 +39,7 @@ function MetaTags({ meta }: IMetaTags): JSX.Element { - + diff --git a/src/components/meta/Schema.tsx b/src/components/meta/Schema.tsx index cc30989..adfc8bc 100644 --- a/src/components/meta/Schema.tsx +++ b/src/components/meta/Schema.tsx @@ -10,8 +10,8 @@ function Schema(): JSX.Element { '@context': 'https://schema.org', '@type': 'Organization', 'name': process.env.appName, - 'url': process.env.publicUrl, - 'logo': `${process.env.publicUrl}${_dbSettings.metaOgImage}`, + 'url': process.env.appUrl, + 'logo': `${process.env.appUrl}${_dbSettings.metaOgImage}`, 'sameAs': _dbSocialMedia?.filter(x => x.isActive)?.map(x => x.url) || [], }; diff --git a/src/constants/Constants.ts b/src/constants/Constants.ts new file mode 100644 index 0000000..84544c6 --- /dev/null +++ b/src/constants/Constants.ts @@ -0,0 +1,19 @@ +import * as packageJSON from '../../package.json'; + +interface IConstants { + appUrl: string; + appVersion: string; + appName: string; + appOgImageSrc: string; +} + +const Constants: IConstants = { + appUrl: (JSON.stringify((packageJSON || {}).homepage) || '').replace(/"/gm, ''), + appVersion: (JSON.stringify((packageJSON || {}).version) || '').replace(/"/gm, ''), + appName: 'Notiflix', + appOgImageSrc: '/webapp/notiflix-og.jpg', +}; + +export type { IConstants }; + +export { Constants }; diff --git a/src/helpers/Sitemap.ts b/src/helpers/Sitemap.ts new file mode 100644 index 0000000..ac7bb36 --- /dev/null +++ b/src/helpers/Sitemap.ts @@ -0,0 +1,175 @@ +import { existsSync, readFileSync, writeFileSync } from 'fs'; +import { Constants } from '../constants/Constants'; +import { Routes } from '../routes/Routes'; + +// Constants: begin +const appUrl = Constants.appUrl; +const appName = Constants.appName; +const appOgImageSrc = `${Constants.appUrl}${Constants.appOgImageSrc}`; +const sitemapStyleUrl = `${Constants.appUrl}/sitemap.xsl`; +const pathOutput = 'public/sitemap.xml'; +const pathDatabase = 'src/_database'; +const pathPages = 'src/pages'; +// Constants: end + + +// Helper: Format Date as YYYY-MM-DD: begin +const sitemapFormatDate = (date: string): string => { + const d = new Date(date); + const year = d.getFullYear(); + let month = '' + (d.getMonth() + 1); + let day = '' + d.getDate(); + if (month.length < 2) { month = '0' + month; } + if (day.length < 2) { day = '0' + day; } + return [year, month, day].join('-'); +}; +// Helper: Format Date as YYYY-MM-DD: end + +// Helper: Create "changefreq" and "priority": begin +interface ISitemapCreateFrequencyAndPriority { + frequency: string; + priority: string; +} + +const sitemapCreateFrequencyAndPriority = (date: string): ISitemapCreateFrequencyAndPriority => { + const today = new Date().valueOf(); + const pageDate = new Date(date).valueOf(); + const differenceAsDays = Math.round((today - pageDate) / (1000 * 3600 * 24)); + let frequency = 'daily'; + let priority = '1.0'; + if (differenceAsDays > 7) { + frequency = 'weekly'; + priority = '0.9'; + } + if (differenceAsDays > 93) { + frequency = 'monthly'; + priority = '0.8'; + } + if (differenceAsDays > 365) { + frequency = 'yearly'; + priority = '0.7'; + } + return { + frequency, + priority, + }; +}; +// Helper: Create "changefreq" and "priority": end + +// Helper: Get Pages Last Modified Date via DB file: begin +const sitemapGetPagesLastModifiedDate = (path: string): string => { + const newDateAsString = new Date().toString(); + + // check the file is exist + if (!existsSync(path)) { + return newDateAsString; + } + + // read the file and return "pageMeta.lastModifiedDate" + const fileAsText = readFileSync(path, 'utf-8'); + if (fileAsText) { + // TODO: require!!! + // @typescript-eslint/no-var-requires + // eslint-disable-next-line + const parseMD = require('parse-md').default; + const fileTextAsObj = parseMD(fileAsText); + return fileTextAsObj?.metadata?.pageMeta?.lastModifiedDate || newDateAsString; + } + + // else + return newDateAsString; +}; +// Helper: Get Pages Last Modified Date via DB file: end + + +// Sitemap: Create Url: begin +interface ISitemapCreateUrl { + loc: string; + lastMod: string; + image?: string; + caption?: string; +} + +const sitemapCreateUrl = ({ loc, lastMod, image, caption }: ISitemapCreateUrl): string => { + // image src + let imageSrc = appOgImageSrc; + if (image) { + imageSrc = image; + } + + // image caption + let imageCaption = appName; + if (caption) { + imageCaption = caption.length > 50 ? caption.substring(0, 50) + '...' : caption; + } + + return ` + + ${loc} + ${sitemapFormatDate(lastMod)} + ${sitemapCreateFrequencyAndPriority(lastMod).frequency} + ${sitemapCreateFrequencyAndPriority(lastMod).priority} + + ${imageSrc} + ${imageCaption} + + + `; +}; +// Sitemap: Create Url: end + +// Sitemap: Create Urls from Pages: begin +const sitemapCreateUrlsFromPages = (): string => { + let sitemapPagesUrls = ''; + + // if DB and Pages folders exist + if (existsSync(pathDatabase) && existsSync(pathPages)) { + Routes?.filter(route => route.isActive && route.addToSitemap)?.map(route => { + // page path + let pagePath = route.pathAs || ''; + + // if home page + if (pagePath === '/') { pagePath = ''; } + + // page full url + const pageFullUrl = `${appUrl}${pagePath}`; + + // page db file + const pageDbFile = route.pathDBFile; + + // page db path + const pageDbFullPath = `${pathDatabase}${pageDbFile}`; + + // page last mod date + const pageLastModifiedDate = sitemapGetPagesLastModifiedDate(pageDbFullPath); + + // create a sitemap url for this page + sitemapPagesUrls += sitemapCreateUrl({ + loc: pageFullUrl, + lastMod: pageLastModifiedDate, + }); + }); + } + + // Return Urls + return sitemapPagesUrls; +}; +// Sitemap: Create Urls from Pages: end + + +// Sitemap: Create XML Content: begin +const sitemapCreateXmlPageContent = (): string => { + return ` + + + ${sitemapCreateUrlsFromPages()} + `; +}; +// Sitemap: Create XML Content: end + +// Sitemap: Write XML Content: begin +const sitemapWriteXMLFile = (pathOutput: string): void => { + writeFileSync(pathOutput, sitemapCreateXmlPageContent()); +}; +sitemapWriteXMLFile(pathOutput); +// Sitemap: Write XML Content: end diff --git a/src/pages/about/index.tsx b/src/pages/about/index.tsx index 8e7a844..4cea7e1 100644 --- a/src/pages/about/index.tsx +++ b/src/pages/about/index.tsx @@ -52,7 +52,7 @@ function About(): JSX.Element {

ABOUT

- + Go to Home @@ -65,7 +65,7 @@ function About(): JSX.Element {


- NATURE + NATURE ); } diff --git a/src/pages/home/index.tsx b/src/pages/home/index.tsx index a66f7b6..b256017 100644 --- a/src/pages/home/index.tsx +++ b/src/pages/home/index.tsx @@ -13,7 +13,7 @@ function Home(): JSX.Element {

HOME

- + Go to ABOUT diff --git a/src/routes/Routes.ts b/src/routes/Routes.ts new file mode 100644 index 0000000..41f17dd --- /dev/null +++ b/src/routes/Routes.ts @@ -0,0 +1,62 @@ +interface IRoutes { + id: number; + sortOrder: number; + isActive: boolean; + addToNextJSConfig: boolean; + addToSitemap: boolean; + addToNavMenu: boolean; + name: string; + pathAs: string; + pathPage: string; + pathDBFile: string; + targetBlank: boolean; + icon: { + use: boolean; + className: string; + }; +} + +const Routes: Array = [ + // Home Page + { + id: 1, + sortOrder: 1, + isActive: true, + addToNextJSConfig: true, + addToSitemap: true, + addToNavMenu: true, + name: 'Home', + pathAs: '/', + pathPage: '/home', + pathDBFile: '/pages/about.md', + targetBlank: false, + icon: { + use: false, + className: 'fab fa-github', + }, + }, + + // About Page + { + id: 2, + sortOrder: 2, + isActive: true, + addToNextJSConfig: true, + addToSitemap: true, + addToNavMenu: true, + name: 'About', + pathAs: '/about', + pathPage: '/about', + pathDBFile: '/pages/about.md', + targetBlank: false, + icon: { + use: false, + className: 'fab fa-github', + }, + }, + +]; + +export type { IRoutes }; + +export { Routes }; diff --git a/tsconfig.json b/tsconfig.json index 97d97fe..489a081 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,9 +11,18 @@ "@components/*": [ "components/*" ], + "@constants/*": [ + "constants/*" + ], + "@helpers/*": [ + "helpers/*" + ], "@pages/*": [ "pages/*" ], + "@routes/*": [ + "routes/*" + ], "@styles/*": [ "styles/*" ], diff --git a/yarn.lock b/yarn.lock index 9de3dfa..3698d51 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3059,6 +3059,13 @@ parse-json@^5.0.0: json-parse-even-better-errors "^2.3.0" lines-and-columns "^1.1.6" +parse-md@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/parse-md/-/parse-md-2.0.4.tgz#0aeb75fca8dca270f232340c02aabe33ea846165" + integrity sha512-P45VmpnIo2QGErCa+YnpndzKXcu2v5qpFX31pOyGY+Ub9KuCAm6qKXCbR3sRsoESuy6XeY81hEIVnn6xz+0YcA== + dependencies: + js-yaml "^3.13.1" + path-browserify@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.1.tgz#e6c4ddd7ed3aa27c68a20cc4e50e1a4ee83bbc4a"