├── .nvmrc
├── packages
├── utils
│ ├── renderer
│ │ ├── consts
│ │ │ └── images.ts
│ │ ├── services
│ │ │ ├── HNRequest.ts
│ │ │ └── embed.ts
│ │ ├── hooks
│ │ │ └── useEmbeds.tsx
│ │ ├── sanitizeHTMLOptions.js
│ │ └── headingSlugger.ts
│ ├── package.json
│ ├── seo
│ │ ├── addPublicationJsonLd.ts
│ │ └── addArticleJsonLd.ts
│ ├── handle-math-jax.js
│ └── trigger-custom-widget-embed.js
├── blog-starter-kit
│ └── themes
│ │ ├── enterprise
│ │ ├── vercel.json
│ │ ├── components
│ │ │ ├── icons
│ │ │ │ ├── index.js
│ │ │ │ └── svgs
│ │ │ │ │ ├── ChevronDownSVG.js
│ │ │ │ │ ├── HamburgerSVG.js
│ │ │ │ │ ├── RssSVG.js
│ │ │ │ │ ├── ExternalArrowSVG.js
│ │ │ │ │ ├── PlusCircleSVG.js
│ │ │ │ │ ├── LinkedinSVG.js
│ │ │ │ │ ├── XSVG.js
│ │ │ │ │ ├── ArticleSVG.js
│ │ │ │ │ ├── CloseSVG.js
│ │ │ │ │ ├── index.js
│ │ │ │ │ ├── GithubSVG.js
│ │ │ │ │ ├── HashnodeSVG.js
│ │ │ │ │ └── BookOpenSVG.js
│ │ │ ├── section-separator.tsx
│ │ │ ├── container.tsx
│ │ │ ├── navbar.tsx
│ │ │ ├── markdown-styles.module.css
│ │ │ ├── post-title.tsx
│ │ │ ├── date-formatter.tsx
│ │ │ ├── resizable-image.js
│ │ │ ├── scripts.tsx
│ │ │ ├── post-read-time-in-minutes.tsx
│ │ │ ├── layout.tsx
│ │ │ ├── markdown-to-html.tsx
│ │ │ ├── cover-image.tsx
│ │ │ ├── more-posts.tsx
│ │ │ ├── avatar.tsx
│ │ │ ├── meta.tsx
│ │ │ ├── subscribe.tsx
│ │ │ ├── about-author.tsx
│ │ │ └── contexts
│ │ │ │ └── appContext.tsx
│ │ ├── @types
│ │ │ └── remark-html.d.ts
│ │ ├── lib
│ │ │ └── api
│ │ │ │ ├── fragments
│ │ │ │ ├── PageInfo.graphql
│ │ │ │ ├── Post.graphql
│ │ │ │ └── Publication.graphql
│ │ │ │ ├── mutations
│ │ │ │ └── SubscribeToNewsletter.graphql
│ │ │ │ └── queries
│ │ │ │ ├── PublicationByHost.graphql
│ │ │ │ ├── SlugPostsByPublication.graphql
│ │ │ │ ├── PageByPublication.graphql
│ │ │ │ ├── DraftById.graphql
│ │ │ │ ├── TagPostsByPublication.graphql
│ │ │ │ ├── RSSFeed.graphql
│ │ │ │ ├── SearchPostsOfPublication.graphql
│ │ │ │ ├── SeriesPostsByPublication.graphql
│ │ │ │ ├── PostsByPublication.graphql
│ │ │ │ └── Sitemap.graphql
│ │ ├── .eslintrc.js
│ │ ├── .graphqlrc.yml
│ │ ├── public
│ │ │ ├── favicon
│ │ │ │ ├── favicon.ico
│ │ │ │ ├── favicon-16x16.png
│ │ │ │ ├── favicon-32x32.png
│ │ │ │ ├── mstile-150x150.png
│ │ │ │ ├── apple-touch-icon.png
│ │ │ │ ├── android-chrome-192x192.png
│ │ │ │ ├── android-chrome-512x512.png
│ │ │ │ └── browserconfig.xml
│ │ │ └── assets
│ │ │ │ └── blog
│ │ │ │ ├── authors
│ │ │ │ ├── jj.jpeg
│ │ │ │ ├── joe.jpeg
│ │ │ │ └── tim.jpeg
│ │ │ │ ├── preview
│ │ │ │ └── cover.jpg
│ │ │ │ ├── hello-world
│ │ │ │ └── cover.jpg
│ │ │ │ └── dynamic-routing
│ │ │ │ └── cover.jpg
│ │ ├── assets
│ │ │ ├── PlusJakartaSans-Bold.ttf
│ │ │ ├── PlusJakartaSans-Medium.ttf
│ │ │ ├── PlusJakartaSans-Regular.ttf
│ │ │ ├── PlusJakartaSans-SemiBold.ttf
│ │ │ └── PlusJakartaSans-ExtraBold.ttf
│ │ ├── .env.example
│ │ ├── pages
│ │ │ ├── _app.tsx
│ │ │ ├── _document.tsx
│ │ │ ├── robots.txt.tsx
│ │ │ ├── dashboard.tsx
│ │ │ └── rss.xml.tsx
│ │ ├── postcss.config.js
│ │ ├── tsconfig.json
│ │ ├── utils
│ │ │ └── const
│ │ │ │ └── index.ts
│ │ ├── process-env.d.ts
│ │ ├── codegen.yml
│ │ └── README.md
│ │ ├── hashnode
│ │ ├── vercel.json
│ │ ├── components
│ │ │ ├── icons
│ │ │ │ ├── index.js
│ │ │ │ └── svgs
│ │ │ │ │ ├── ChevronDownSVG_16x16.js
│ │ │ │ │ ├── ChevronRightSVG_16x16.js
│ │ │ │ │ ├── CheckSVG.js
│ │ │ │ │ ├── ExternalLinkSVG.js
│ │ │ │ │ ├── ChevronDownSVGV2.js
│ │ │ │ │ ├── ChevronUpSVG_16x16.js
│ │ │ │ │ ├── HamburgerSVG.js
│ │ │ │ │ ├── PinSVG.js
│ │ │ │ │ ├── ExternalArrowSVG.js
│ │ │ │ │ ├── BarsSVG.js
│ │ │ │ │ ├── PlusCircleSVG.js
│ │ │ │ │ ├── RefreshSVG.js
│ │ │ │ │ ├── HackernewsSVGV2.js
│ │ │ │ │ ├── EarthSVG.js
│ │ │ │ │ ├── LinkedInSVGV2.js
│ │ │ │ │ ├── PaperPlaneSVG.js
│ │ │ │ │ ├── FacebookSVGRound.js
│ │ │ │ │ ├── SearchSvg.js
│ │ │ │ │ ├── XSVG.js
│ │ │ │ │ ├── ArticleSVG.js
│ │ │ │ │ ├── ChevronDownSVG.js
│ │ │ │ │ ├── AlertSVG.js
│ │ │ │ │ ├── CloseSVG.js
│ │ │ │ │ ├── ClipboardSVG.js
│ │ │ │ │ ├── ChevronLeftSVG.js
│ │ │ │ │ ├── LinkedinSVG.js
│ │ │ │ │ ├── InstagramSVG.js
│ │ │ │ │ ├── HashnodeLogoIconV2.js
│ │ │ │ │ ├── ListSVG.js
│ │ │ │ │ ├── YoutubeSVG.js
│ │ │ │ │ ├── PencilSVG.js
│ │ │ │ │ ├── TwitterXSVG.js
│ │ │ │ │ ├── MastodonSVG.js
│ │ │ │ │ ├── HeadphonesSVG.js
│ │ │ │ │ ├── HashnodeSVG.js
│ │ │ │ │ ├── ShareSVGV2.tsx
│ │ │ │ │ ├── FileLineChartSVG.js
│ │ │ │ │ ├── CommentSVGV2.js
│ │ │ │ │ ├── RedditSVGV2.js
│ │ │ │ │ ├── RedditSVG.js
│ │ │ │ │ ├── BookOpenSVG.js
│ │ │ │ │ ├── ChartMixedSVG.js
│ │ │ │ │ └── LinkSVGV2.js
│ │ │ ├── section-separator.tsx
│ │ │ ├── container.tsx
│ │ │ ├── markdown-styles.module.css
│ │ │ ├── separator-root.js
│ │ │ ├── resizable-image.js
│ │ │ ├── scripts.tsx
│ │ │ ├── layout.tsx
│ │ │ ├── fonts
│ │ │ │ └── index.tsx
│ │ │ ├── static-page-content.tsx
│ │ │ ├── post-comments-sidebar.tsx
│ │ │ ├── publication-social-link-item.tsx
│ │ │ ├── post-floating-bar-tooltip-wrapper.tsx
│ │ │ ├── meta.tsx
│ │ │ ├── header-tooltip.tsx
│ │ │ ├── about-author.tsx
│ │ │ ├── publication-meta.tsx
│ │ │ ├── contexts
│ │ │ │ └── appContext.tsx
│ │ │ ├── header-blog-search.tsx
│ │ │ ├── header-left-sidebar.tsx
│ │ │ └── toast.js
│ │ ├── @types
│ │ │ └── remark-html.d.ts
│ │ ├── lib
│ │ │ └── api
│ │ │ │ ├── fragments
│ │ │ │ ├── PageInfo.graphql
│ │ │ │ ├── Post.graphql
│ │ │ │ ├── StaticPage.graphql
│ │ │ │ ├── PostThumbnail.graphql
│ │ │ │ └── Draft.graphql
│ │ │ │ ├── queries
│ │ │ │ ├── DraftById.graphql
│ │ │ │ ├── PublicationByHost.graphql
│ │ │ │ ├── SlugPostsByPublication.graphql
│ │ │ │ ├── PageByPublication.graphql
│ │ │ │ ├── Newsletter.graphql
│ │ │ │ ├── TagPostsByPublication.graphql
│ │ │ │ ├── RSSFeed.graphql
│ │ │ │ ├── SeriesPostsByPublication.graphql
│ │ │ │ ├── Tag.graphql
│ │ │ │ ├── SearchPostsOfPublication.graphql
│ │ │ │ ├── PostsByPublication.graphql
│ │ │ │ ├── SeriesPageInitial.graphql
│ │ │ │ ├── HomePage.graphql
│ │ │ │ └── Sitemap.graphql
│ │ │ │ └── mutations
│ │ │ │ └── SubscribeToNewsletter.graphql
│ │ ├── .eslintrc.js
│ │ ├── .graphqlrc.yml
│ │ ├── public
│ │ │ ├── favicon
│ │ │ │ ├── favicon.ico
│ │ │ │ ├── favicon-16x16.png
│ │ │ │ ├── favicon-32x32.png
│ │ │ │ ├── apple-touch-icon.png
│ │ │ │ ├── mstile-150x150.png
│ │ │ │ ├── android-chrome-192x192.png
│ │ │ │ ├── android-chrome-512x512.png
│ │ │ │ └── browserconfig.xml
│ │ │ └── assets
│ │ │ │ └── blog
│ │ │ │ ├── authors
│ │ │ │ ├── jj.jpeg
│ │ │ │ ├── joe.jpeg
│ │ │ │ └── tim.jpeg
│ │ │ │ ├── preview
│ │ │ │ └── cover.jpg
│ │ │ │ ├── hello-world
│ │ │ │ └── cover.jpg
│ │ │ │ └── dynamic-routing
│ │ │ │ └── cover.jpg
│ │ ├── assets
│ │ │ ├── PlusJakartaSans-Bold.ttf
│ │ │ ├── PlusJakartaSans-Medium.ttf
│ │ │ ├── PlusJakartaSans-Regular.ttf
│ │ │ ├── PlusJakartaSans-SemiBold.ttf
│ │ │ └── PlusJakartaSans-ExtraBold.ttf
│ │ ├── .env.example
│ │ ├── postcss.config.js
│ │ ├── tsconfig.json
│ │ ├── utils
│ │ │ ├── const
│ │ │ │ ├── images.ts
│ │ │ │ ├── styles.ts
│ │ │ │ └── index.ts
│ │ │ ├── gsspHelpers.ts
│ │ │ ├── getReadTime.js
│ │ │ ├── index.js
│ │ │ ├── toast.tsx
│ │ │ └── handle-math-jax.js
│ │ ├── process-env.d.ts
│ │ ├── pages
│ │ │ ├── _document.tsx
│ │ │ ├── _app.tsx
│ │ │ ├── robots.txt.tsx
│ │ │ ├── dashboard.tsx
│ │ │ └── rss.xml.tsx
│ │ ├── types
│ │ │ ├── external
│ │ │ │ └── mongodb.d.ts
│ │ │ ├── index.ts
│ │ │ ├── Page.ts
│ │ │ ├── Badge.ts
│ │ │ ├── Series.ts
│ │ │ ├── User.ts
│ │ │ ├── extras.ts
│ │ │ └── Response.ts
│ │ ├── codegen.yml
│ │ └── README.md
│ │ └── personal
│ │ ├── vercel.json
│ │ ├── components
│ │ ├── icons
│ │ │ ├── index.js
│ │ │ └── svgs
│ │ │ │ ├── ChevronDownSVG.js
│ │ │ │ ├── HamburgerSVG.js
│ │ │ │ ├── Moon.js
│ │ │ │ ├── RssSVG.js
│ │ │ │ ├── ExternalArrowSVG.js
│ │ │ │ ├── PlusCircleSVG.js
│ │ │ │ ├── LinkedinSVG.js
│ │ │ │ ├── XSVG.js
│ │ │ │ ├── ArticleSVG.js
│ │ │ │ ├── index.js
│ │ │ │ ├── Sun.js
│ │ │ │ ├── GithubSVG.js
│ │ │ │ └── HashnodeSVG.js
│ │ ├── section-separator.tsx
│ │ ├── container.tsx
│ │ ├── markdown-styles.module.css
│ │ ├── scripts.tsx
│ │ ├── date-formatter.tsx
│ │ ├── layout.tsx
│ │ ├── markdown-to-html.tsx
│ │ ├── toggle-theme.tsx
│ │ ├── minimal-posts.tsx
│ │ ├── cover-image.tsx
│ │ ├── avatar.tsx
│ │ ├── footer.tsx
│ │ ├── meta.tsx
│ │ └── contexts
│ │ │ └── appContext.tsx
│ │ ├── @types
│ │ └── remark-html.d.ts
│ │ ├── lib
│ │ ├── api
│ │ │ ├── fragments
│ │ │ │ ├── PageInfo.graphql
│ │ │ │ ├── Series.graphql
│ │ │ │ ├── Post.graphql
│ │ │ │ └── Publication.graphql
│ │ │ └── queries
│ │ │ │ ├── PublicationByHost.graphql
│ │ │ │ ├── SlugPostsByPublication.graphql
│ │ │ │ ├── DraftById.graphql
│ │ │ │ ├── PageByPublication.graphql
│ │ │ │ ├── TagPostsByPublication.graphql
│ │ │ │ ├── SeriesByPublication.graphql
│ │ │ │ ├── RSSFeed.graphql
│ │ │ │ ├── PostsByPublication.graphql
│ │ │ │ ├── Sitemap.graphql
│ │ │ │ └── SinglePostByPublication.graphql
│ │ └── types
│ │ │ └── gtag.d.ts
│ │ ├── .eslintrc.js
│ │ ├── .graphqlrc.yml
│ │ ├── utils
│ │ └── const
│ │ │ └── index.ts
│ │ ├── public
│ │ ├── favicon
│ │ │ ├── favicon.ico
│ │ │ ├── favicon-16x16.png
│ │ │ ├── favicon-32x32.png
│ │ │ ├── apple-touch-icon.png
│ │ │ ├── mstile-150x150.png
│ │ │ ├── android-chrome-192x192.png
│ │ │ ├── android-chrome-512x512.png
│ │ │ └── browserconfig.xml
│ │ └── assets
│ │ │ └── blog
│ │ │ ├── authors
│ │ │ ├── jj.jpeg
│ │ │ ├── joe.jpeg
│ │ │ └── tim.jpeg
│ │ │ ├── preview
│ │ │ └── cover.jpg
│ │ │ ├── hello-world
│ │ │ └── cover.jpg
│ │ │ └── dynamic-routing
│ │ │ └── cover.jpg
│ │ ├── assets
│ │ ├── PlusJakartaSans-Bold.ttf
│ │ ├── PlusJakartaSans-Medium.ttf
│ │ ├── PlusJakartaSans-Regular.ttf
│ │ ├── PlusJakartaSans-SemiBold.ttf
│ │ └── PlusJakartaSans-ExtraBold.ttf
│ │ ├── .env.example
│ │ ├── postcss.config.js
│ │ ├── tsconfig.json
│ │ ├── pages
│ │ ├── _document.tsx
│ │ ├── _app.tsx
│ │ ├── robots.txt.tsx
│ │ ├── dashboard.tsx
│ │ └── rss.xml.tsx
│ │ ├── process-env.d.ts
│ │ ├── codegen.yml
│ │ ├── codegen.ts
│ │ ├── README.md
│ │ └── pnpm-lock.yaml
├── tsconfig
│ ├── package.json
│ ├── nextjs.json
│ └── base.json
└── eslint-config-custom
│ ├── index.js
│ └── package.json
├── .dockerignore
├── pnpm-workspace.yaml
├── .prettierignore
├── .vscode
└── extensions.json
├── images
├── image.png
├── image-1.png
├── image-2.png
├── image-3.png
├── image-4.png
├── image-5.png
├── image-6.png
├── image-7.png
├── image-8.png
└── image-9.png
├── prettier.config.js
├── .editorconfig
├── Dockerfile
├── .gitignore
├── package.json
└── license.md
/.nvmrc:
--------------------------------------------------------------------------------
1 | v18
--------------------------------------------------------------------------------
/packages/utils/renderer/consts/images.ts:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/packages/utils/renderer/services/HNRequest.ts:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .git
3 | .gitignore
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
1 | packages:
2 | - "./packages/**"
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .cache
3 | .next
4 | */*.yml
5 | generated
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": ["esbenp.prettier-vscode"]
3 | }
4 |
--------------------------------------------------------------------------------
/images/image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xxynly/starter-kit/HEAD/images/image.png
--------------------------------------------------------------------------------
/images/image-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xxynly/starter-kit/HEAD/images/image-1.png
--------------------------------------------------------------------------------
/images/image-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xxynly/starter-kit/HEAD/images/image-2.png
--------------------------------------------------------------------------------
/images/image-3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xxynly/starter-kit/HEAD/images/image-3.png
--------------------------------------------------------------------------------
/images/image-4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xxynly/starter-kit/HEAD/images/image-4.png
--------------------------------------------------------------------------------
/images/image-5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xxynly/starter-kit/HEAD/images/image-5.png
--------------------------------------------------------------------------------
/images/image-6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xxynly/starter-kit/HEAD/images/image-6.png
--------------------------------------------------------------------------------
/images/image-7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xxynly/starter-kit/HEAD/images/image-7.png
--------------------------------------------------------------------------------
/images/image-8.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xxynly/starter-kit/HEAD/images/image-8.png
--------------------------------------------------------------------------------
/images/image-9.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xxynly/starter-kit/HEAD/images/image-9.png
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/vercel.json:
--------------------------------------------------------------------------------
1 | {
2 | "framework": "nextjs"
3 | }
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/vercel.json:
--------------------------------------------------------------------------------
1 | {
2 | "framework": "nextjs"
3 | }
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/vercel.json:
--------------------------------------------------------------------------------
1 | {
2 | "framework": "nextjs"
3 | }
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/components/icons/index.js:
--------------------------------------------------------------------------------
1 | export * from './svgs';
2 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/components/icons/index.js:
--------------------------------------------------------------------------------
1 | export * from './svgs';
2 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/components/icons/index.js:
--------------------------------------------------------------------------------
1 | export * from './svgs';
2 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/@types/remark-html.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'remark-html';
2 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/@types/remark-html.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'remark-html';
2 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/@types/remark-html.d.ts:
--------------------------------------------------------------------------------
1 | declare module 'remark-html';
2 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/lib/api/fragments/PageInfo.graphql:
--------------------------------------------------------------------------------
1 | fragment PageInfo on PageInfo {
2 | endCursor
3 | hasNextPage
4 | }
5 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/lib/api/fragments/PageInfo.graphql:
--------------------------------------------------------------------------------
1 | fragment PageInfo on PageInfo {
2 | endCursor
3 | hasNextPage
4 | }
5 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/lib/api/fragments/PageInfo.graphql:
--------------------------------------------------------------------------------
1 | fragment PageInfo on PageInfo {
2 | endCursor
3 | hasNextPage
4 | }
5 |
--------------------------------------------------------------------------------
/packages/tsconfig/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@starter-kit/tsconfig",
3 | "version": "0.0.0",
4 | "private": true,
5 | "license": "MIT"
6 | }
7 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | extends: ['@starter-kit/eslint-config-custom'],
4 | };
5 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | extends: ['@starter-kit/eslint-config-custom'],
4 | };
5 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | extends: ['@starter-kit/eslint-config-custom'],
4 | };
5 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/.graphqlrc.yml:
--------------------------------------------------------------------------------
1 | schema: './generated/schema.graphql'
2 | documents: './{pages,components,lib}/**/*.{graphql,js,ts,jsx,tsx}'
3 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/lib/api/queries/DraftById.graphql:
--------------------------------------------------------------------------------
1 | query DraftById($id: ObjectId!) {
2 | draft(id: $id) {
3 | ...Draft
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/.graphqlrc.yml:
--------------------------------------------------------------------------------
1 | schema: './generated/schema.graphql'
2 | documents: './{pages,components,lib}/**/*.{graphql,js,ts,jsx,tsx}'
3 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/.graphqlrc.yml:
--------------------------------------------------------------------------------
1 | schema: './generated/schema.graphql'
2 | documents: './{pages,components,lib}/**/*.{graphql,js,ts,jsx,tsx}'
3 |
--------------------------------------------------------------------------------
/packages/eslint-config-custom/index.js:
--------------------------------------------------------------------------------
1 | /** @type {import('@types/eslint').Linter.BaseConfig} */
2 | module.exports = {
3 | extends: ['next/core-web-vitals', 'prettier'],
4 | };
5 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/components/section-separator.tsx:
--------------------------------------------------------------------------------
1 | export const SectionSeparator = () => {
2 | return
;
3 | };
4 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/components/section-separator.tsx:
--------------------------------------------------------------------------------
1 | export const SectionSeparator = () => {
2 | return
;
3 | };
4 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/utils/const/index.ts:
--------------------------------------------------------------------------------
1 | export const DEFAULT_AVATAR =
2 | 'https://cdn.hashnode.com/res/hashnode/image/upload/v1659089761812/fsOct5gl6.png';
3 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/components/section-separator.tsx:
--------------------------------------------------------------------------------
1 | export const SectionSeparator = () => {
2 | return
;
3 | };
4 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/public/favicon/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xxynly/starter-kit/HEAD/packages/blog-starter-kit/themes/hashnode/public/favicon/favicon.ico
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/public/favicon/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xxynly/starter-kit/HEAD/packages/blog-starter-kit/themes/personal/public/favicon/favicon.ico
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/public/favicon/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xxynly/starter-kit/HEAD/packages/blog-starter-kit/themes/enterprise/public/favicon/favicon.ico
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/assets/PlusJakartaSans-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xxynly/starter-kit/HEAD/packages/blog-starter-kit/themes/hashnode/assets/PlusJakartaSans-Bold.ttf
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/public/favicon/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xxynly/starter-kit/HEAD/packages/blog-starter-kit/themes/hashnode/public/favicon/favicon-16x16.png
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/public/favicon/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xxynly/starter-kit/HEAD/packages/blog-starter-kit/themes/hashnode/public/favicon/favicon-32x32.png
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/assets/PlusJakartaSans-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xxynly/starter-kit/HEAD/packages/blog-starter-kit/themes/personal/assets/PlusJakartaSans-Bold.ttf
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/public/favicon/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xxynly/starter-kit/HEAD/packages/blog-starter-kit/themes/personal/public/favicon/favicon-16x16.png
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/public/favicon/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xxynly/starter-kit/HEAD/packages/blog-starter-kit/themes/personal/public/favicon/favicon-32x32.png
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/assets/PlusJakartaSans-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xxynly/starter-kit/HEAD/packages/blog-starter-kit/themes/enterprise/assets/PlusJakartaSans-Bold.ttf
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/assets/PlusJakartaSans-Medium.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xxynly/starter-kit/HEAD/packages/blog-starter-kit/themes/enterprise/assets/PlusJakartaSans-Medium.ttf
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/public/favicon/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xxynly/starter-kit/HEAD/packages/blog-starter-kit/themes/enterprise/public/favicon/favicon-16x16.png
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/public/favicon/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xxynly/starter-kit/HEAD/packages/blog-starter-kit/themes/enterprise/public/favicon/favicon-32x32.png
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/public/favicon/mstile-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xxynly/starter-kit/HEAD/packages/blog-starter-kit/themes/enterprise/public/favicon/mstile-150x150.png
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/assets/PlusJakartaSans-Medium.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xxynly/starter-kit/HEAD/packages/blog-starter-kit/themes/hashnode/assets/PlusJakartaSans-Medium.ttf
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/assets/PlusJakartaSans-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xxynly/starter-kit/HEAD/packages/blog-starter-kit/themes/hashnode/assets/PlusJakartaSans-Regular.ttf
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/assets/PlusJakartaSans-SemiBold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xxynly/starter-kit/HEAD/packages/blog-starter-kit/themes/hashnode/assets/PlusJakartaSans-SemiBold.ttf
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/public/assets/blog/authors/jj.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xxynly/starter-kit/HEAD/packages/blog-starter-kit/themes/hashnode/public/assets/blog/authors/jj.jpeg
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/public/assets/blog/authors/joe.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xxynly/starter-kit/HEAD/packages/blog-starter-kit/themes/hashnode/public/assets/blog/authors/joe.jpeg
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/public/assets/blog/authors/tim.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xxynly/starter-kit/HEAD/packages/blog-starter-kit/themes/hashnode/public/assets/blog/authors/tim.jpeg
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/public/favicon/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xxynly/starter-kit/HEAD/packages/blog-starter-kit/themes/hashnode/public/favicon/apple-touch-icon.png
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/public/favicon/mstile-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xxynly/starter-kit/HEAD/packages/blog-starter-kit/themes/hashnode/public/favicon/mstile-150x150.png
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/assets/PlusJakartaSans-Medium.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xxynly/starter-kit/HEAD/packages/blog-starter-kit/themes/personal/assets/PlusJakartaSans-Medium.ttf
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/assets/PlusJakartaSans-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xxynly/starter-kit/HEAD/packages/blog-starter-kit/themes/personal/assets/PlusJakartaSans-Regular.ttf
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/assets/PlusJakartaSans-SemiBold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xxynly/starter-kit/HEAD/packages/blog-starter-kit/themes/personal/assets/PlusJakartaSans-SemiBold.ttf
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/public/assets/blog/authors/jj.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xxynly/starter-kit/HEAD/packages/blog-starter-kit/themes/personal/public/assets/blog/authors/jj.jpeg
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/public/assets/blog/authors/joe.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xxynly/starter-kit/HEAD/packages/blog-starter-kit/themes/personal/public/assets/blog/authors/joe.jpeg
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/public/assets/blog/authors/tim.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xxynly/starter-kit/HEAD/packages/blog-starter-kit/themes/personal/public/assets/blog/authors/tim.jpeg
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/public/favicon/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xxynly/starter-kit/HEAD/packages/blog-starter-kit/themes/personal/public/favicon/apple-touch-icon.png
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/public/favicon/mstile-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xxynly/starter-kit/HEAD/packages/blog-starter-kit/themes/personal/public/favicon/mstile-150x150.png
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/assets/PlusJakartaSans-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xxynly/starter-kit/HEAD/packages/blog-starter-kit/themes/enterprise/assets/PlusJakartaSans-Regular.ttf
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/assets/PlusJakartaSans-SemiBold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xxynly/starter-kit/HEAD/packages/blog-starter-kit/themes/enterprise/assets/PlusJakartaSans-SemiBold.ttf
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/public/assets/blog/authors/jj.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xxynly/starter-kit/HEAD/packages/blog-starter-kit/themes/enterprise/public/assets/blog/authors/jj.jpeg
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/public/assets/blog/authors/joe.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xxynly/starter-kit/HEAD/packages/blog-starter-kit/themes/enterprise/public/assets/blog/authors/joe.jpeg
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/public/assets/blog/authors/tim.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xxynly/starter-kit/HEAD/packages/blog-starter-kit/themes/enterprise/public/assets/blog/authors/tim.jpeg
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/public/favicon/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xxynly/starter-kit/HEAD/packages/blog-starter-kit/themes/enterprise/public/favicon/apple-touch-icon.png
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/.env.example:
--------------------------------------------------------------------------------
1 | NEXT_PUBLIC_HASHNODE_GQL_ENDPOINT=https://gql.hashnode.com
2 | NEXT_PUBLIC_HASHNODE_PUBLICATION_HOST=engineering.hashnode.com
3 | NEXT_PUBLIC_MODE=development
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/assets/PlusJakartaSans-ExtraBold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xxynly/starter-kit/HEAD/packages/blog-starter-kit/themes/hashnode/assets/PlusJakartaSans-ExtraBold.ttf
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/public/assets/blog/preview/cover.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xxynly/starter-kit/HEAD/packages/blog-starter-kit/themes/hashnode/public/assets/blog/preview/cover.jpg
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/.env.example:
--------------------------------------------------------------------------------
1 | NEXT_PUBLIC_HASHNODE_GQL_ENDPOINT=https://gql.hashnode.com
2 | NEXT_PUBLIC_HASHNODE_PUBLICATION_HOST=engineering.hashnode.com
3 | NEXT_PUBLIC_MODE=development
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/assets/PlusJakartaSans-ExtraBold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xxynly/starter-kit/HEAD/packages/blog-starter-kit/themes/personal/assets/PlusJakartaSans-ExtraBold.ttf
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/public/assets/blog/preview/cover.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xxynly/starter-kit/HEAD/packages/blog-starter-kit/themes/personal/public/assets/blog/preview/cover.jpg
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/.env.example:
--------------------------------------------------------------------------------
1 | NEXT_PUBLIC_HASHNODE_GQL_ENDPOINT=https://gql.hashnode.com
2 | NEXT_PUBLIC_HASHNODE_PUBLICATION_HOST=engineering.hashnode.com
3 | NEXT_PUBLIC_MODE=development
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/assets/PlusJakartaSans-ExtraBold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xxynly/starter-kit/HEAD/packages/blog-starter-kit/themes/enterprise/assets/PlusJakartaSans-ExtraBold.ttf
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/public/assets/blog/preview/cover.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xxynly/starter-kit/HEAD/packages/blog-starter-kit/themes/enterprise/public/assets/blog/preview/cover.jpg
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/public/assets/blog/hello-world/cover.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xxynly/starter-kit/HEAD/packages/blog-starter-kit/themes/hashnode/public/assets/blog/hello-world/cover.jpg
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/public/assets/blog/hello-world/cover.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xxynly/starter-kit/HEAD/packages/blog-starter-kit/themes/personal/public/assets/blog/hello-world/cover.jpg
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/public/assets/blog/hello-world/cover.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xxynly/starter-kit/HEAD/packages/blog-starter-kit/themes/enterprise/public/assets/blog/hello-world/cover.jpg
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/public/favicon/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xxynly/starter-kit/HEAD/packages/blog-starter-kit/themes/hashnode/public/favicon/android-chrome-192x192.png
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/public/favicon/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xxynly/starter-kit/HEAD/packages/blog-starter-kit/themes/hashnode/public/favicon/android-chrome-512x512.png
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/public/favicon/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xxynly/starter-kit/HEAD/packages/blog-starter-kit/themes/personal/public/favicon/android-chrome-192x192.png
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/public/favicon/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xxynly/starter-kit/HEAD/packages/blog-starter-kit/themes/personal/public/favicon/android-chrome-512x512.png
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/public/favicon/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xxynly/starter-kit/HEAD/packages/blog-starter-kit/themes/enterprise/public/favicon/android-chrome-192x192.png
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/public/favicon/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xxynly/starter-kit/HEAD/packages/blog-starter-kit/themes/enterprise/public/favicon/android-chrome-512x512.png
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/public/assets/blog/dynamic-routing/cover.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xxynly/starter-kit/HEAD/packages/blog-starter-kit/themes/hashnode/public/assets/blog/dynamic-routing/cover.jpg
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/public/assets/blog/dynamic-routing/cover.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xxynly/starter-kit/HEAD/packages/blog-starter-kit/themes/personal/public/assets/blog/dynamic-routing/cover.jpg
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/public/assets/blog/dynamic-routing/cover.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xxynly/starter-kit/HEAD/packages/blog-starter-kit/themes/enterprise/public/assets/blog/dynamic-routing/cover.jpg
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/lib/api/mutations/SubscribeToNewsletter.graphql:
--------------------------------------------------------------------------------
1 | mutation SubscribeToNewsletter($input: SubscribeToNewsletterInput!) {
2 | subscribeToNewsletter(input: $input) {
3 | status
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/lib/api/mutations/SubscribeToNewsletter.graphql:
--------------------------------------------------------------------------------
1 | mutation SubscribeToNewsletter($input: SubscribeToNewsletterInput!) {
2 | subscribeToNewsletter(input: $input) {
3 | status
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/pages/_app.tsx:
--------------------------------------------------------------------------------
1 | import { AppProps } from 'next/app';
2 | import '../styles/index.css';
3 |
4 | export default function MyApp({ Component, pageProps }: AppProps) {
5 | return ;
6 | }
7 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/lib/api/queries/PublicationByHost.graphql:
--------------------------------------------------------------------------------
1 | query PublicationByHost($host: String!) {
2 | publication(host: $host) {
3 | ...Publication
4 | posts(first:0) {
5 | totalDocuments
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/lib/api/queries/PublicationByHost.graphql:
--------------------------------------------------------------------------------
1 | query PublicationByHost($host: String!) {
2 | publication(host: $host) {
3 | ...Publication
4 | posts(first:0) {
5 | totalDocuments
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/lib/api/queries/PublicationByHost.graphql:
--------------------------------------------------------------------------------
1 | query PublicationByHost($host: String!) {
2 | publication(host: $host) {
3 | ...Publication
4 | posts(first:0) {
5 | totalDocuments
6 | }
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/packages/tsconfig/nextjs.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/tsconfig",
3 | "display": "Next.js",
4 | "extends": "./base.json",
5 | "compilerOptions": {
6 | "plugins": [{ "name": "next" }],
7 | "jsx": "preserve"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/postcss.config.js:
--------------------------------------------------------------------------------
1 | // If you want to use other PostCSS plugins, see the following:
2 | // https://tailwindcss.com/docs/using-with-preprocessors
3 | module.exports = {
4 | plugins: {
5 | tailwindcss: {},
6 | autoprefixer: {},
7 | },
8 | };
9 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/postcss.config.js:
--------------------------------------------------------------------------------
1 | // If you want to use other PostCSS plugins, see the following:
2 | // https://tailwindcss.com/docs/using-with-preprocessors
3 | module.exports = {
4 | plugins: {
5 | tailwindcss: {},
6 | autoprefixer: {},
7 | },
8 | };
9 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/postcss.config.js:
--------------------------------------------------------------------------------
1 | // If you want to use other PostCSS plugins, see the following:
2 | // https://tailwindcss.com/docs/using-with-preprocessors
3 | module.exports = {
4 | plugins: {
5 | tailwindcss: {},
6 | autoprefixer: {},
7 | },
8 | };
9 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/lib/api/fragments/Post.graphql:
--------------------------------------------------------------------------------
1 | fragment Post on Post {
2 | id
3 | title
4 | url
5 | author {
6 | name
7 | profilePicture
8 | }
9 | coverImage {
10 | url
11 | }
12 | publishedAt
13 | slug
14 | brief
15 | }
16 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/components/container.tsx:
--------------------------------------------------------------------------------
1 | type Props = {
2 | children?: React.ReactNode;
3 | className?: string;
4 | };
5 |
6 | export const Container = ({ children, className }: Props) => {
7 | return {children}
;
8 | };
9 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/components/container.tsx:
--------------------------------------------------------------------------------
1 | type Props = {
2 | children?: React.ReactNode;
3 | className?: string;
4 | };
5 |
6 | export const Container = ({ children, className }: Props) => {
7 | return {children}
;
8 | };
9 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/lib/api/fragments/Post.graphql:
--------------------------------------------------------------------------------
1 | fragment Post on Post {
2 | id
3 | title
4 | url
5 | author {
6 | name
7 | profilePicture
8 | username
9 | }
10 | coverImage {
11 | url
12 | }
13 | publishedAt
14 | slug
15 | brief
16 | }
17 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/components/container.tsx:
--------------------------------------------------------------------------------
1 | type Props = {
2 | children?: React.ReactNode;
3 | className?: string;
4 | };
5 |
6 | export const Container = ({ children, className }: Props) => {
7 | return {children}
;
8 | };
9 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/tsconfig",
3 | "extends": "@starter-kit/tsconfig/nextjs.json",
4 | "include": ["next-env.d.ts", ".next/types/**/*.ts", "**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx"],
5 | "exclude": ["node_modules"]
6 | }
7 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/tsconfig",
3 | "extends": "@starter-kit/tsconfig/nextjs.json",
4 | "include": ["next-env.d.ts", ".next/types/**/*.ts", "**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx"],
5 | "exclude": ["node_modules"]
6 | }
7 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/tsconfig",
3 | "extends": "@starter-kit/tsconfig/nextjs.json",
4 | "include": ["next-env.d.ts", ".next/types/**/*.ts", "**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx"],
5 | "exclude": ["node_modules"]
6 | }
7 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/utils/const/index.ts:
--------------------------------------------------------------------------------
1 | export const DEFAULT_AVATAR =
2 | 'https://cdn.hashnode.com/res/hashnode/image/upload/v1659089761812/fsOct5gl6.png';
3 |
4 | export const DEFAULT_COVER =
5 | 'https://cdn.hashnode.com/res/hashnode/image/upload/v1683525272978/MB5H_kgOC.png?auto=format';
6 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/utils/const/images.ts:
--------------------------------------------------------------------------------
1 | export const blurImageDimensions = { w: 400, h: 210 };
2 |
3 | // eslint-disable-next-line import/prefer-default-export
4 | export const DEFAULT_AVATAR: string =
5 | 'https://cdn.hashnode.com/res/hashnode/image/upload/v1659089761812/fsOct5gl6.png?auto=compress';
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/process-env.d.ts:
--------------------------------------------------------------------------------
1 | declare namespace NodeJS {
2 | interface ProcessEnv {
3 | [key: string]: string | undefined;
4 | NEXT_PUBLIC_HASHNODE_GQL_ENDPOINT: string;
5 | NEXT_PUBLIC_HASHNODE_PUBLICATION_HOST: string;
6 | // add more environment variables and their types here
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/process-env.d.ts:
--------------------------------------------------------------------------------
1 | declare namespace NodeJS {
2 | interface ProcessEnv {
3 | [key: string]: string | undefined;
4 | NEXT_PUBLIC_HASHNODE_GQL_ENDPOINT: string;
5 | NEXT_PUBLIC_HASHNODE_PUBLICATION_HOST: string;
6 | // add more environment variables and their types here
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/pages/_document.tsx:
--------------------------------------------------------------------------------
1 | import { Head, Html, Main, NextScript } from 'next/document';
2 |
3 | export default function Document() {
4 | return (
5 |
6 |
8 |
9 |
10 |
11 |
12 | );
13 | }
14 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/process-env.d.ts:
--------------------------------------------------------------------------------
1 | declare namespace NodeJS {
2 | interface ProcessEnv {
3 | [key: string]: string | undefined;
4 | NEXT_PUBLIC_HASHNODE_GQL_ENDPOINT: string;
5 | NEXT_PUBLIC_HASHNODE_PUBLICATION_HOST: string;
6 | // add more environment variables and their types here
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/pages/_document.tsx:
--------------------------------------------------------------------------------
1 | import { Head, Html, Main, NextScript } from 'next/document';
2 |
3 | export default function Document() {
4 | return (
5 |
6 |
8 |
9 |
10 |
11 |
12 | );
13 | }
14 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/lib/api/fragments/Series.graphql:
--------------------------------------------------------------------------------
1 | fragment Series on Series {
2 | id
3 | name
4 | slug
5 | description {
6 | text
7 | }
8 | coverImage
9 | posts(first: 20) {
10 | edges {
11 | node {
12 | ...Post
13 | }
14 | }
15 | totalDocuments
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/packages/eslint-config-custom/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@starter-kit/eslint-config-custom",
3 | "version": "0.0.0",
4 | "license": "MIT",
5 | "main": "index.js",
6 | "dependencies": {
7 | "eslint-config-next": "^13.5.4",
8 | "eslint-config-prettier": "^9.0.0"
9 | },
10 | "peerDependencies": {
11 | "eslint": "^8.0.0"
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/pages/_document.tsx:
--------------------------------------------------------------------------------
1 | import { Head, Html, Main, NextScript } from 'next/document';
2 |
3 | export default function Document() {
4 | return (
5 |
6 |
8 |
9 |
10 |
11 |
12 |
13 | );
14 | }
15 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/lib/types/gtag.d.ts:
--------------------------------------------------------------------------------
1 | interface Window {
2 | gtag: (
3 | command: 'config' | 'js' | 'event',
4 | targetId: string,
5 | config?: {
6 | transport_url?: string;
7 | first_party_collection?: boolean;
8 | [key: string]: any;
9 | }
10 | ) => void;
11 | dataLayer: any[];
12 | }
13 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/public/favicon/browserconfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | #000000
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/public/favicon/browserconfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | #000000
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/public/favicon/browserconfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | #000000
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/components/navbar.tsx:
--------------------------------------------------------------------------------
1 | import { Search } from './searchbar';
2 | import { SocialLinks } from './social-links';
3 |
4 | export const Navbar = () => {
5 | return (
6 |
7 |
8 |
9 |
10 | );
11 | };
12 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/lib/api/queries/SlugPostsByPublication.graphql:
--------------------------------------------------------------------------------
1 | query SlugPostsByPublication($host: String!, $first: Int!, $after: String) {
2 | publication(host: $host) {
3 | ...Publication
4 | posts(first: $first, after: $after) {
5 | edges {
6 | node {
7 | slug
8 | }
9 | }
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/lib/api/queries/SlugPostsByPublication.graphql:
--------------------------------------------------------------------------------
1 | query SlugPostsByPublication($host: String!, $first: Int!, $after: String) {
2 | publication(host: $host) {
3 | ...Publication
4 | posts(first: $first, after: $after) {
5 | edges {
6 | node {
7 | slug
8 | }
9 | }
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/lib/api/queries/SlugPostsByPublication.graphql:
--------------------------------------------------------------------------------
1 | query SlugPostsByPublication($host: String!, $first: Int!, $after: String) {
2 | publication(host: $host) {
3 | ...Publication
4 | posts(first: $first, after: $after) {
5 | edges {
6 | node {
7 | slug
8 | }
9 | }
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/pages/_app.tsx:
--------------------------------------------------------------------------------
1 | import { ThemeProvider } from 'next-themes';
2 | import { AppProps } from 'next/app';
3 | import '../styles/index.css';
4 |
5 | export default function MyApp({ Component, pageProps }: AppProps) {
6 | return (
7 |
8 |
9 |
10 | );
11 | }
12 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/lib/api/fragments/Post.graphql:
--------------------------------------------------------------------------------
1 | fragment Post on Post {
2 | id
3 | title
4 | url
5 | author {
6 | name
7 | profilePicture
8 | }
9 | coverImage {
10 | url
11 | }
12 | publishedAt
13 | readTimeInMinutes
14 | slug
15 | brief
16 | tags {
17 | name
18 | slug
19 | }
20 | comments(first: 0) {
21 | totalDocuments
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/lib/api/queries/DraftById.graphql:
--------------------------------------------------------------------------------
1 | query DraftById($id: ObjectId!) {
2 | draft(id: $id) {
3 | id
4 | title
5 | content {
6 | markdown
7 | }
8 | author {
9 | id
10 | name
11 | username
12 | }
13 | dateUpdated
14 | tags {
15 | id
16 | name
17 | slug
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/lib/api/queries/PageByPublication.graphql:
--------------------------------------------------------------------------------
1 | query PageByPublication($slug: String!, $host: String!) {
2 | publication(host: $host) {
3 | ...Publication
4 | staticPage(slug: $slug) {
5 | ...StaticPage
6 | }
7 | }
8 | }
9 |
10 | fragment StaticPage on StaticPage {
11 | id
12 | title
13 | slug
14 | content {
15 | markdown
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/packages/utils/renderer/hooks/useEmbeds.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 | import { loadIframeResizer } from '../services/embed';
3 |
4 | interface UseEmbedsOptions {
5 | enabled?: boolean;
6 | }
7 |
8 | export const useEmbeds = ({ enabled = true }: UseEmbedsOptions = {}) => {
9 | useEffect(() => {
10 | if (enabled) {
11 | loadIframeResizer();
12 | }
13 | }, [enabled]);
14 | };
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/components/markdown-styles.module.css:
--------------------------------------------------------------------------------
1 | .markdown {
2 | @apply text-lg leading-relaxed;
3 | }
4 |
5 | .markdown p,
6 | .markdown ul,
7 | .markdown ol,
8 | .markdown blockquote {
9 | @apply my-6;
10 | }
11 |
12 | .markdown h2 {
13 | @apply text-3xl mt-12 mb-4 leading-snug;
14 | }
15 |
16 | .markdown h3 {
17 | @apply text-2xl mt-8 mb-4 leading-snug;
18 | }
19 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/components/markdown-styles.module.css:
--------------------------------------------------------------------------------
1 | .markdown {
2 | @apply text-lg leading-relaxed;
3 | }
4 |
5 | .markdown p,
6 | .markdown ul,
7 | .markdown ol,
8 | .markdown blockquote {
9 | @apply my-6;
10 | }
11 |
12 | .markdown h2 {
13 | @apply text-3xl mt-12 mb-4 leading-snug;
14 | }
15 |
16 | .markdown h3 {
17 | @apply text-2xl mt-8 mb-4 leading-snug;
18 | }
19 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/components/markdown-styles.module.css:
--------------------------------------------------------------------------------
1 | .markdown {
2 | @apply text-lg leading-relaxed;
3 | }
4 |
5 | .markdown p,
6 | .markdown ul,
7 | .markdown ol,
8 | .markdown blockquote {
9 | @apply my-6;
10 | }
11 |
12 | .markdown h2 {
13 | @apply text-3xl mt-12 mb-4 leading-snug;
14 | }
15 |
16 | .markdown h3 {
17 | @apply text-2xl mt-8 mb-4 leading-snug;
18 | }
19 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/components/post-title.tsx:
--------------------------------------------------------------------------------
1 | import { ReactNode } from 'react';
2 |
3 | type Props = {
4 | children?: ReactNode;
5 | };
6 |
7 | export const PostTitle = ({ children }: Props) => {
8 | return (
9 |
10 |
{children}
11 |
12 | );
13 | };
14 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/components/icons/svgs/ChevronDownSVG_16x16.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class ChevronDownSVG_16x16 extends React.Component {
4 | render() {
5 | return (
6 |
9 | );
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/components/separator-root.js:
--------------------------------------------------------------------------------
1 | import { Root as SeparatorRoot } from '@radix-ui/react-separator';
2 | import { twMerge } from 'tailwind-merge';
3 |
4 | export const Separator = ({ className, ...props }) => (
5 |
10 | );
11 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/components/icons/svgs/ChevronRightSVG_16x16.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class ChevronRightSVG16x16 extends React.Component {
4 | render() {
5 | return (
6 |
9 | );
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/components/date-formatter.tsx:
--------------------------------------------------------------------------------
1 | import { format, parseISO } from 'date-fns';
2 |
3 | type Props = {
4 | dateString: string;
5 | };
6 |
7 | export const DateFormatter = ({ dateString }: Props) => {
8 | if (!dateString) return <>>;
9 | const date = parseISO(dateString);
10 |
11 | return (
12 | <>
13 |
14 | >
15 | );
16 | };
17 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/types/external/mongodb.d.ts:
--------------------------------------------------------------------------------
1 | import { Db, MongoClient } from 'mongodb';
2 |
3 | declare global {
4 | namespace NodeJS {
5 | type MongoConnection = {
6 | client: MongoClient;
7 | db: Db;
8 | };
9 |
10 | interface Global {
11 | mongo: {
12 | conn: MongoConnection | null;
13 | promise: Promise | null;
14 | };
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/lib/api/queries/PageByPublication.graphql:
--------------------------------------------------------------------------------
1 | query PageByPublication($slug: String!, $host: String!) {
2 | publication(host: $host) {
3 | ...Publication
4 | posts(first: 0) {
5 | totalDocuments
6 | }
7 | staticPage(slug: $slug) {
8 | ...StaticPage
9 | }
10 | }
11 | }
12 |
13 | fragment StaticPage on StaticPage {
14 | id
15 | title
16 | slug
17 | content {
18 | markdown
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/components/icons/svgs/CheckSVG.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class CheckSVG extends React.Component {
4 | render() {
5 | return (
6 |
10 | );
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/components/icons/svgs/ExternalLinkSVG.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class ExternalLinkSVG extends React.Component {
4 | render() {
5 | return (
6 |
10 | );
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/utils/gsspHelpers.ts:
--------------------------------------------------------------------------------
1 | import { GetServerSidePropsContext } from 'next';
2 | import { getSingleQueryParam } from './urls';
3 |
4 | export function getHost({ req, query }: Pick) {
5 | const host = getSingleQueryParam(query, 'x-host') || req.headers.host;
6 | if (!host) {
7 | throw new Error('Could not determine host');
8 | }
9 | return host;
10 | }
--------------------------------------------------------------------------------
/prettier.config.js:
--------------------------------------------------------------------------------
1 | /** @type {import("prettier").Config} */
2 | const config = {
3 | bracketSpacing: true,
4 | printWidth: 100,
5 | trailingComma: 'all',
6 | tabWidth: 2,
7 | useTabs: true,
8 | semi: true,
9 | singleQuote: true,
10 | plugins: [
11 | // comment for better diff
12 | 'prettier-plugin-organize-imports',
13 | 'prettier-plugin-tailwindcss',
14 | 'prettier-plugin-packagejson',
15 | ],
16 | };
17 |
18 | module.exports = config;
19 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/lib/api/queries/PageByPublication.graphql:
--------------------------------------------------------------------------------
1 | query PageByPublication($slug: String!, $host: String!) {
2 | publication(host: $host) {
3 | ...Publication
4 | posts(first: 0) {
5 | totalDocuments
6 | }
7 | staticPage(slug: $slug) {
8 | ...StaticPage
9 | }
10 | }
11 | }
12 |
13 | fragment StaticPage on StaticPage {
14 | id
15 | title
16 | slug
17 | content {
18 | markdown
19 | html
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/utils/const/styles.ts:
--------------------------------------------------------------------------------
1 | // twin classes
2 | export const inputText =
3 | 'w-full p-4 placeholder-slate-500 bg-transparent border rounded-lg outline-none focus:border-blue-600 disabled:bg-slate-50 dark:text-white dark:border-slate-800 dark:focus:bg-slate-900 dark:focus:border-blue-600';
4 |
5 | export const dropdownMenu =
6 | 'w-full flex flex-row items-center px-4 py-3 text-slate-600 hover:bg-slate-100 focus:outline-none';
7 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # editorconfig.org
2 | root = true
3 |
4 | [*]
5 | charset = utf-8
6 | indent_size = 2
7 | indent_style = tabs
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | # Markdown syntax specifies that trailing whitespaces can be meaningful,
12 | # so let’s not trim those. e.g. 2 trailing spaces = linebreak (
)
13 | # See https://daringfireball.net/projects/markdown/syntax#p
14 | [*.md]
15 | trim_trailing_whitespace = false
16 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/components/resizable-image.js:
--------------------------------------------------------------------------------
1 | import { ProgressiveImage } from './progressive-image';
2 |
3 | function ResizableImage(props) {
4 | const { src, alt, resize, className, ...restOfTheProps } = props;
5 |
6 | return (
7 |
8 | );
9 | }
10 |
11 | export default ResizableImage;
12 | export { ResizableImage };
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/components/resizable-image.js:
--------------------------------------------------------------------------------
1 | import { ProgressiveImage } from './progressive-image';
2 |
3 | function ResizableImage(props) {
4 | const { src, alt, resize, className, ...restOfTheProps } = props;
5 |
6 | return (
7 |
8 | );
9 | }
10 |
11 | export default ResizableImage;
12 | export { ResizableImage };
13 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/lib/api/queries/Newsletter.graphql:
--------------------------------------------------------------------------------
1 | query Newsletter($host: String!, $slug: String!) {
2 | publication(host: $host) {
3 | ...Publication
4 | author {
5 | id
6 | followersCount
7 | }
8 | staticPage(slug: $slug) {
9 | id
10 | }
11 | recentPosts: posts(first: 3) {
12 | edges {
13 | node {
14 | ...PostThumbnail
15 | }
16 | }
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/components/scripts.tsx:
--------------------------------------------------------------------------------
1 | export const Scripts = () => {
2 | const googleAnalytics = `
3 | window.dataLayer = window.dataLayer || [];
4 | function gtag(){window.dataLayer.push(arguments);}
5 | gtag('js', new Date());`;
6 | return (
7 | <>
8 |
9 |
10 | >
11 | );
12 | };
13 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/components/scripts.tsx:
--------------------------------------------------------------------------------
1 | export const Scripts = () => {
2 | const googleAnalytics = `
3 | window.dataLayer = window.dataLayer || [];
4 | function gtag(){window.dataLayer.push(arguments);}
5 | gtag('js', new Date());`;
6 | return (
7 | <>
8 |
9 |
10 | >
11 | );
12 | };
13 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/components/scripts.tsx:
--------------------------------------------------------------------------------
1 | export const Scripts = () => {
2 | const googleAnalytics = `
3 | window.dataLayer = window.dataLayer || [];
4 | function gtag(){window.dataLayer.push(arguments);}
5 | gtag('js', new Date());`;
6 | return (
7 | <>
8 |
9 |
10 | >
11 | );
12 | };
13 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/components/icons/svgs/ChevronDownSVGV2.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class ChevronDownSVGV2 extends React.Component {
4 | render() {
5 | return (
6 |
9 | );
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/components/icons/svgs/ChevronUpSVG_16x16.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class ChevronUpSVG16X16 extends React.Component {
4 | render() {
5 | return (
6 |
9 | );
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/lib/api/queries/TagPostsByPublication.graphql:
--------------------------------------------------------------------------------
1 | query TagPostsByPublication(
2 | $host: String!
3 | $tagSlug: String!
4 | $first: Int!
5 | $after: String
6 | ) {
7 | publication(host: $host) {
8 | ...Publication
9 | posts(first: $first, filter: { tagSlugs: [$tagSlug] }, after: $after) {
10 | totalDocuments
11 | edges {
12 | node {
13 | ...Post
14 | }
15 | }
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/lib/api/queries/TagPostsByPublication.graphql:
--------------------------------------------------------------------------------
1 | query TagPostsByPublication(
2 | $host: String!
3 | $tagSlug: String!
4 | $first: Int!
5 | $after: String
6 | ) {
7 | publication(host: $host) {
8 | ...Publication
9 | posts(first: $first, filter: { tagSlugs: [$tagSlug] }, after: $after) {
10 | totalDocuments
11 | edges {
12 | node {
13 | ...Post
14 | }
15 | }
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/lib/api/queries/DraftById.graphql:
--------------------------------------------------------------------------------
1 | query DraftById($id: ObjectId!) {
2 | draft(id: $id) {
3 | id
4 | title
5 | content {
6 | markdown
7 | }
8 | author {
9 | id
10 | name
11 | username
12 | profilePicture
13 | }
14 | coverImage {
15 | url
16 | }
17 | readTimeInMinutes
18 | dateUpdated
19 | tags {
20 | id
21 | name
22 | slug
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/lib/api/queries/TagPostsByPublication.graphql:
--------------------------------------------------------------------------------
1 | query TagPostsByPublication(
2 | $host: String!
3 | $tagSlug: String!
4 | $first: Int!
5 | $after: String
6 | ) {
7 | publication(host: $host) {
8 | ...Publication
9 | posts(first: $first, filter: { tagSlugs: [$tagSlug] }, after: $after) {
10 | totalDocuments
11 | edges {
12 | node {
13 | ...Post
14 | }
15 | }
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/utils/getReadTime.js:
--------------------------------------------------------------------------------
1 | module.exports = function getReadTime(string) {
2 | const wordsPerMinute = 225; // Average case.
3 | const numberOfWords =
4 | string
5 | .trim()
6 | .replace(/(\r\n|\n|\r)/gm, ' ')
7 | .split(' ')
8 | .filter((word) => word !== '').length || 0;
9 | if (numberOfWords > 0) {
10 | return Math.ceil(numberOfWords / wordsPerMinute);
11 | }
12 | return 0;
13 | };
14 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/types/index.ts:
--------------------------------------------------------------------------------
1 | export type Nullish = undefined | null;
2 | /**
3 | * Make some properties of T required.
4 | */
5 | export type WithRequired = T & { [P in K]-?: NonNullable };
6 |
7 | export * from './Post';
8 | export * from './User';
9 | export * from './Publication';
10 | export * from './Series';
11 | export * from './Page';
12 | export * from './Badge';
13 | export * from './extras';
14 | export * from './Response';
15 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/components/icons/svgs/ChevronDownSVG.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class ChevronDownSVG extends React.Component {
4 | render() {
5 | return (
6 |
15 | );
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/components/post-read-time-in-minutes.tsx:
--------------------------------------------------------------------------------
1 | import BookOpenSVG from './icons/svgs/BookOpenSVG';
2 |
3 | type Props = { readTimeInMinutes: number };
4 |
5 | export const ReadTimeInMinutes = ({ readTimeInMinutes }: Props) => {
6 | return (
7 | <>
8 |
9 |
10 | {readTimeInMinutes} min read
11 |
12 | >
13 | );
14 | };
15 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/components/icons/svgs/ChevronDownSVG.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class ChevronDownSVG extends React.Component {
4 | render() {
5 | return (
6 |
15 | );
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/lib/api/fragments/StaticPage.graphql:
--------------------------------------------------------------------------------
1 | query StaticPage($host: String!, $slug: String!) {
2 | publication(host: $host) {
3 | ...Publication
4 | staticPage(slug: $slug) {
5 | ...RequiredStaticPageFields
6 | }
7 | }
8 | }
9 |
10 | fragment RequiredStaticPageFields on StaticPage {
11 | id
12 | slug
13 | title
14 | content {
15 | html
16 | }
17 | seo {
18 | title
19 | description
20 | }
21 | ogMetaData {
22 | image
23 | }
24 | }
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/components/icons/svgs/HamburgerSVG.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class HamburgerSVG extends React.Component {
4 | render() {
5 | return (
6 |
15 | );
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/lib/api/fragments/PostThumbnail.graphql:
--------------------------------------------------------------------------------
1 | fragment PostThumbnail on Post {
2 | __typename
3 | id
4 | title
5 | slug
6 | publishedAt
7 | cuid
8 | url
9 | subtitle
10 | brief
11 | readTimeInMinutes
12 | views
13 | author {
14 | __typename
15 | id
16 | username
17 | name
18 | profilePicture
19 | followersCount
20 | }
21 | coverImage {
22 | __typename
23 | url
24 | isPortrait
25 | isAttributionHidden
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/types/Page.ts:
--------------------------------------------------------------------------------
1 | import { ObjectID } from 'mongodb';
2 | import { Publication } from './index';
3 |
4 | export interface Page {
5 | _id: string | ObjectID;
6 | publication?: string | Publication | ObjectID;
7 | title?: string;
8 | endpoint?: string;
9 | content?: string;
10 | contentMarkdown?: string;
11 | oldEndpoint?: string;
12 | isActive?: boolean;
13 | isHidden?: boolean;
14 | priority?: number;
15 | ogImage?: string;
16 | description?: string;
17 | }
18 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/components/icons/svgs/HamburgerSVG.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class HamburgerSVG extends React.Component {
4 | render() {
5 | return (
6 |
15 | );
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/components/icons/svgs/HamburgerSVG.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class HamburgerSVG extends React.Component {
4 | render() {
5 | return (
6 |
15 | );
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:18-alpine
2 |
3 | COPY . /app
4 |
5 | # Set the working directory
6 | WORKDIR /app
7 |
8 | # Install pnpm
9 | RUN npm install -g pnpm
10 |
11 | # By default, use the enterprise theme
12 | ARG THEME=enterprise
13 |
14 | WORKDIR /app/packages/blog-starter-kit/themes/${THEME}
15 | RUN cp .env.example .env.local
16 | RUN pnpm install --frozen-lockfile
17 |
18 | RUN pnpm build
19 |
20 | # Expose the port Next.js runs on
21 | EXPOSE 3000
22 |
23 | # Run the Next.js start script
24 | CMD ["pnpm", "start"]
25 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/components/date-formatter.tsx:
--------------------------------------------------------------------------------
1 | import { format, parseISO } from 'date-fns';
2 |
3 | type Props = {
4 | dateString: string;
5 | /**
6 | * Format string for date-fns
7 | * @default 'MMM d, yyyy'
8 | */
9 | formatStr?: string;
10 | };
11 |
12 | export const DateFormatter = ({ dateString, formatStr = 'MMM d, yyyy' }: Props) => {
13 | if (!dateString) return <>>;
14 | const date = parseISO(dateString);
15 |
16 | return ;
17 | };
18 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/components/icons/svgs/RssSVG.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class RssSVG extends React.Component {
4 | render() {
5 | return (
6 |
15 | );
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/components/icons/svgs/PinSVG.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class PinSVG extends React.Component {
4 | render() {
5 | return (
6 |
15 | );
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/components/icons/svgs/Moon.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class Moon extends React.Component {
4 | render() {
5 | return (
6 |
18 | );
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/components/icons/svgs/RssSVG.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class RssSVG extends React.Component {
4 | render() {
5 | return (
6 |
15 | );
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/components/icons/svgs/ExternalArrowSVG.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class ExternalArrowSVG extends React.Component {
4 | render() {
5 | return (
6 |
15 | );
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/components/icons/svgs/ExternalArrowSVG.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class ExternalArrowSVG extends React.Component {
4 | render() {
5 | return (
6 |
15 | );
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/components/icons/svgs/ExternalArrowSVG.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class ExternalArrowSVG extends React.Component {
4 | render() {
5 | return (
6 |
15 | );
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/lib/api/queries/SeriesByPublication.graphql:
--------------------------------------------------------------------------------
1 | query SeriesByPublication($host: String!) {
2 | publication(host: $host) {
3 | ...Publication
4 | seriesList(first: 20) {
5 | edges {
6 | node {
7 | ...Series
8 | }
9 | }
10 | totalDocuments
11 | }
12 | }
13 | }
14 |
15 | query SingleSeriesByPublication($host: String!, $slug: String!) {
16 | publication(host: $host) {
17 | ...Publication
18 | series(slug: $slug) {
19 | ...Series
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/components/icons/svgs/BarsSVG.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class BarsSVG extends React.Component {
4 | render() {
5 | return (
6 |
14 | );
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/packages/utils/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@starter-kit/utils",
3 | "version": "1.0.0",
4 | "description": "",
5 | "keywords": [],
6 | "license": "MIT",
7 | "author": "",
8 | "main": "index.js",
9 | "scripts": {
10 | "test": "echo \"Error: no test specified\" && exit 1"
11 | },
12 | "dependencies": {
13 | "js-base64": "^3.7.5",
14 | "rss": "^1.2.2",
15 | "sanitize-html": "^2.11.0",
16 | "slug": "^8.2.3",
17 | "validator": "^13.11.0"
18 | },
19 | "devDependencies": {
20 | "@types/rss": "^0.0.30",
21 | "@types/sanitize-html": "^2.9.0"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/components/icons/svgs/PlusCircleSVG.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class PlusCircleSVG extends React.Component {
4 | render() {
5 | return (
6 |
15 | );
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/components/icons/svgs/PlusCircleSVG.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class PlusCircleSVG extends React.Component {
4 | render() {
5 | return (
6 |
15 | );
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/components/icons/svgs/PlusCircleSVG.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class PlusCircleSVG extends React.Component {
4 | render() {
5 | return (
6 |
15 | );
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/types/Badge.ts:
--------------------------------------------------------------------------------
1 | import { ObjectID } from 'mongodb';
2 |
3 | export interface Badge {
4 | _id: ObjectID | string;
5 | name: string;
6 | displayName: string;
7 | infoUrl: string;
8 | image: string;
9 | description: string;
10 | }
11 | export interface UserBadgeMap {
12 | _id: ObjectID;
13 | user: ObjectID;
14 | post: ObjectID;
15 | badge: ObjectID | Badge;
16 | metaData?: object;
17 | isActive: boolean;
18 | assignedOn: Date;
19 | isSuppressed?: boolean;
20 | hiddenFromPublication?: boolean;
21 | actionUrl: string;
22 | }
23 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/lib/api/queries/RSSFeed.graphql:
--------------------------------------------------------------------------------
1 | query RSSFeed($host: String!, $first: Int!, $after: String) {
2 | publication(host: $host) {
3 | ...Publication
4 | posts(first: $first, after: $after) {
5 | edges {
6 | node {
7 | id
8 | title
9 | url
10 | slug
11 | content {
12 | html
13 | }
14 | tags {
15 | id
16 | name
17 | slug
18 | }
19 | author {
20 | name
21 | username
22 | }
23 | }
24 | }
25 | pageInfo {
26 | ...PageInfo
27 | }
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/lib/api/queries/SearchPostsOfPublication.graphql:
--------------------------------------------------------------------------------
1 | query SearchPostsOfPublication($first: Int!, $filter: SearchPostsOfPublicationFilter!) {
2 | searchPostsOfPublication(first: $first, filter: $filter) {
3 | edges {
4 | cursor
5 | node {
6 | id
7 | brief
8 | title
9 | cuid
10 | slug
11 | coverImage {
12 | url
13 | }
14 | author {
15 | id
16 | name
17 | }
18 | publication {
19 | title
20 | url
21 | }
22 | }
23 | }
24 | pageInfo {
25 | ...PageInfo
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/components/icons/svgs/RefreshSVG.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class RefreshSVG extends React.Component {
4 | render() {
5 | return (
6 |
10 | );
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/lib/api/queries/RSSFeed.graphql:
--------------------------------------------------------------------------------
1 | query RSSFeed($host: String!, $first: Int!, $after: String) {
2 | publication(host: $host) {
3 | ...Publication
4 | posts(first: $first, after: $after) {
5 | edges {
6 | node {
7 | id
8 | title
9 | url
10 | slug
11 | content {
12 | html
13 | }
14 | tags {
15 | id
16 | name
17 | slug
18 | }
19 | author {
20 | name
21 | username
22 | }
23 | }
24 | }
25 | pageInfo {
26 | ...PageInfo
27 | }
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/lib/api/queries/RSSFeed.graphql:
--------------------------------------------------------------------------------
1 | query RSSFeed($host: String!, $first: Int!, $after: String) {
2 | publication(host: $host) {
3 | ...Publication
4 | posts(first: $first, after: $after) {
5 | edges {
6 | node {
7 | id
8 | title
9 | url
10 | slug
11 | content {
12 | html
13 | }
14 | tags {
15 | id
16 | name
17 | slug
18 | }
19 | author {
20 | name
21 | username
22 | }
23 | }
24 | }
25 | pageInfo {
26 | ...PageInfo
27 | }
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/components/layout.tsx:
--------------------------------------------------------------------------------
1 | import { Analytics } from './analytics';
2 | import { Integrations } from './integrations';
3 | import { Meta } from './meta';
4 | import { Scripts } from './scripts';
5 |
6 | type Props = {
7 | children: React.ReactNode;
8 | };
9 |
10 | export const Layout = ({ children }: Props) => {
11 | return (
12 | <>
13 |
14 |
15 |
16 | {children}
17 |
18 |
19 |
20 | >
21 | );
22 | };
23 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/components/layout.tsx:
--------------------------------------------------------------------------------
1 | import { Analytics } from './analytics';
2 | import { Integrations } from './integrations';
3 | import { Meta } from './meta';
4 | import { Scripts } from './scripts';
5 |
6 | type Props = {
7 | children: React.ReactNode;
8 | };
9 |
10 | export const Layout = ({ children }: Props) => {
11 | return (
12 | <>
13 |
14 |
15 |
16 | {children}
17 |
18 |
19 |
20 | >
21 | );
22 | };
23 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/components/layout.tsx:
--------------------------------------------------------------------------------
1 | import { Analytics } from './analytics';
2 | import { Integrations } from './integrations';
3 | import { Meta } from './meta';
4 | import { Scripts } from './scripts';
5 |
6 | type Props = {
7 | children: React.ReactNode;
8 | };
9 |
10 | export const Layout = ({ children }: Props) => {
11 | return (
12 | <>
13 |
14 |
15 |
16 | {children}
17 |
18 |
19 |
20 | >
21 | );
22 | };
23 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/components/icons/svgs/HackernewsSVGV2.js:
--------------------------------------------------------------------------------
1 | const HackernewsSVGV2 = (props) => {
2 | return (
3 |
6 | );
7 | };
8 |
9 | export default HackernewsSVGV2;
10 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/lib/api/queries/SeriesPostsByPublication.graphql:
--------------------------------------------------------------------------------
1 | query SeriesPostsByPublication($host: String!, $seriesSlug: String!, $first: Int!, $after: String) {
2 | publication(host: $host) {
3 | ...Publication
4 | posts(first: 0) {
5 | totalDocuments
6 | }
7 | series(slug: $seriesSlug) {
8 | ...Series
9 | }
10 | }
11 | }
12 |
13 | fragment Series on Series {
14 | id
15 | name
16 | slug
17 | description {
18 | html
19 | }
20 | coverImage
21 | posts(first: $first, after: $after) {
22 | edges {
23 | node {
24 | ...Post
25 | }
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/lib/api/queries/SeriesPostsByPublication.graphql:
--------------------------------------------------------------------------------
1 | query SeriesPostsByPublication($host: String!, $seriesSlug: String!, $first: Int!, $after: String) {
2 | publication(host: $host) {
3 | ...Publication
4 | posts(first: 0) {
5 | totalDocuments
6 | }
7 | series(slug: $seriesSlug) {
8 | ...Series
9 | }
10 | }
11 | }
12 |
13 | fragment Series on Series {
14 | id
15 | name
16 | slug
17 | description {
18 | html
19 | }
20 | coverImage
21 | posts(first: $first, after: $after) {
22 | edges {
23 | node {
24 | ...Post
25 | }
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/utils/const/index.ts:
--------------------------------------------------------------------------------
1 | import { getAppUrl } from "../urls";
2 |
3 | export const DEFAULT_AVATAR =
4 | 'https://cdn.hashnode.com/res/hashnode/image/upload/v1659089761812/fsOct5gl6.png';
5 |
6 | export const MAX_MAIN_NAV_LINKS = 7;
7 |
8 | export const DEFAULT_COVER =
9 | 'https://cdn.hashnode.com/res/hashnode/image/upload/v1683525272978/MB5H_kgOC.png?auto=format';
10 |
11 |
12 | export const DEFAULT_LIGHT_POST_COVER =
13 | 'https://cdn.hashnode.com/res/hashnode/image/upload/v1683525272978/MB5H_kgOC.png?auto=format';
14 |
15 | export const HASHNODE_NEXT_URL = getAppUrl();
16 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/components/icons/svgs/EarthSVG.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class EarthSVG extends React.Component {
4 | render() {
5 | return (
6 |
9 | );
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/components/icons/svgs/LinkedInSVGV2.js:
--------------------------------------------------------------------------------
1 | const LinkedInSVGV2 = (props) => {
2 | return (
3 |
6 | );
7 | };
8 |
9 | export default LinkedInSVGV2;
10 |
--------------------------------------------------------------------------------
/packages/utils/seo/addPublicationJsonLd.ts:
--------------------------------------------------------------------------------
1 | export const addPublicationJsonLd = (publication: any) => {
2 | const schema = {
3 | '@context': 'https://schema.org/',
4 | '@type': 'Blog',
5 | '@id': publication.url,
6 | mainEntityOfPage: publication.url,
7 | name: publication.title,
8 | description: publication.descriptionSEO,
9 | publisher: {
10 | '@type': publication.isTeam ? 'Organization' : 'Person',
11 | '@id': publication.url,
12 | name: publication.title,
13 | image: {
14 | '@type': 'ImageObject',
15 | url: publication.preferences?.logo,
16 | },
17 | },
18 | };
19 | return schema;
20 | };
21 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 | **/node_modules
8 |
9 | # testing
10 | /coverage
11 |
12 | # next.js
13 | **/.next/
14 | /.next/
15 | /out/
16 |
17 | # production
18 | /build
19 |
20 | # misc
21 | .DS_Store
22 | *.pem
23 |
24 | # debug
25 | npm-debug.log*
26 | yarn-debug.log*
27 | yarn-error.log*
28 |
29 | # local env files
30 | .env*
31 | !.env.example
32 |
33 | # vercel
34 | .vercel
35 |
36 | # typescript
37 | *.tsbuildinfo
38 | next-env.d.ts
39 |
40 | # vscode
41 | .vscode/*
42 | !.vscode/extensions.json
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/components/icons/svgs/LinkedinSVG.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class LinkedinSVG extends React.Component {
4 | render() {
5 | return (
6 |
15 | );
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/components/icons/svgs/PaperPlaneSVG.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class PaperPlaneSVG extends React.Component {
4 | render() {
5 | return (
6 |
9 | );
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/components/icons/svgs/LinkedinSVG.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class LinkedinSVG extends React.Component {
4 | render() {
5 | return (
6 |
15 | );
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/lib/api/queries/Tag.graphql:
--------------------------------------------------------------------------------
1 | query TagInitial($slug: String!, $host: String!, $first: Int!, $after: String) {
2 | tag(slug: $slug) {
3 | id
4 | name
5 | logo
6 | slug
7 | tagline
8 | }
9 | publication(host: $host) {
10 | ...Publication
11 | posts(first: $first, after: $after, filter: { tagSlugs: [$slug] }) {
12 | edges {
13 | node {
14 | ...PostThumbnail
15 | }
16 | cursor
17 | __typename
18 | }
19 | pageInfo {
20 | endCursor
21 | hasNextPage
22 | }
23 | __typename
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/components/icons/svgs/FacebookSVGRound.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class FacebookSVGRound extends React.Component {
4 | render() {
5 | return (
6 |
9 | );
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/components/icons/svgs/XSVG.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class XSVG extends React.Component {
4 | render() {
5 | return (
6 |
14 | );
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/components/icons/svgs/SearchSvg.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class SearchSVG extends React.Component {
4 | render() {
5 | return (
6 |
14 | );
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/components/icons/svgs/XSVG.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class XSVG extends React.Component {
4 | render() {
5 | return (
6 |
14 | );
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/components/icons/svgs/XSVG.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class XSVG extends React.Component {
4 | render() {
5 | return (
6 |
14 | );
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/lib/api/fragments/Draft.graphql:
--------------------------------------------------------------------------------
1 | fragment Draft on Draft {
2 | id
3 | title
4 | canonicalUrl
5 | subtitle
6 | features {
7 | tableOfContents {
8 | isEnabled
9 | items {
10 | id
11 | level
12 | parentId
13 | slug
14 | title
15 | }
16 | }
17 | }
18 | content {
19 | markdown
20 | }
21 | coverImage {
22 | url
23 | }
24 | author {
25 | id
26 | name
27 | username
28 | profilePicture
29 | }
30 | updatedAt
31 | tags {
32 | id
33 | name
34 | slug
35 | }
36 | }
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/codegen.yml:
--------------------------------------------------------------------------------
1 | schema: https://gql.hashnode.com
2 | documents: './**/*.graphql'
3 | generates:
4 | ./generated/schema.graphql:
5 | plugins:
6 | - schema-ast
7 | config:
8 | includeDirectives: true
9 | ./generated/graphql.ts:
10 | plugins:
11 | - typescript
12 | - typescript-operations
13 | - typed-document-node
14 | config:
15 | scalars:
16 | Date: string
17 | DateTime: string
18 | ObjectId: string
19 | JSONObject: Record
20 | Decimal: string
21 | CurrencyCode: string
22 | ImageContentType: string
23 | ImageUrl: string
24 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/codegen.yml:
--------------------------------------------------------------------------------
1 | schema: https://gql.hashnode.com
2 | documents: './**/*.graphql'
3 | generates:
4 | ./generated/schema.graphql:
5 | plugins:
6 | - schema-ast
7 | config:
8 | includeDirectives: true
9 | ./generated/graphql.ts:
10 | plugins:
11 | - typescript
12 | - typescript-operations
13 | - typed-document-node
14 | config:
15 | scalars:
16 | Date: string
17 | DateTime: string
18 | ObjectId: string
19 | JSONObject: Record
20 | Decimal: string
21 | CurrencyCode: string
22 | ImageContentType: string
23 | ImageUrl: string
24 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/codegen.yml:
--------------------------------------------------------------------------------
1 | schema: https://gql.hashnode.com
2 | documents: './**/*.graphql'
3 | generates:
4 | ./generated/schema.graphql:
5 | plugins:
6 | - schema-ast
7 | config:
8 | includeDirectives: true
9 | ./generated/graphql.ts:
10 | plugins:
11 | - typescript
12 | - typescript-operations
13 | - typed-document-node
14 | config:
15 | scalars:
16 | Date: string
17 | DateTime: string
18 | ObjectId: string
19 | JSONObject: Record
20 | Decimal: string
21 | CurrencyCode: string
22 | ImageContentType: string
23 | ImageUrl: string
24 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/components/icons/svgs/ArticleSVG.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class ArticleSVG extends React.Component {
4 | render() {
5 | return (
6 |
15 | );
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/components/icons/svgs/ArticleSVG.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class ArticleSVG extends React.Component {
4 | render() {
5 | return (
6 |
15 | );
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/components/icons/svgs/ArticleSVG.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class ArticleSVG extends React.Component {
4 | render() {
5 | return (
6 |
15 | );
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/lib/api/queries/SearchPostsOfPublication.graphql:
--------------------------------------------------------------------------------
1 | query SearchPostsOfPublication(
2 | $first: Int!
3 | $filter: SearchPostsOfPublicationFilter!
4 | $after: String
5 | ) {
6 | searchPostsOfPublication(first: $first, after: $after, filter: $filter) {
7 | edges {
8 | cursor
9 | node {
10 | id
11 | brief
12 | title
13 | cuid
14 | slug
15 | reactionCount
16 | publishedAt
17 | url
18 | coverImage {
19 | url
20 | }
21 | author {
22 | id
23 | name
24 | }
25 | publication {
26 | title
27 | url
28 | }
29 | }
30 | }
31 | pageInfo {
32 | ...PageInfo
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/components/icons/svgs/ChevronDownSVG.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class ChevronDownSVG extends React.Component {
4 | render() {
5 | return (
6 | //
7 |
10 | );
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/types/Series.ts:
--------------------------------------------------------------------------------
1 | import { ObjectID } from 'mongodb';
2 | import { PostPreview, User, Publication } from './index';
3 |
4 | export interface Series {
5 | _id: ObjectID;
6 | cuid?: string;
7 | slug?: string;
8 | name?: string;
9 | brief?: string;
10 | description?: string;
11 | descriptionMarkdown?: string;
12 | coverImage?: string;
13 | author?: User | string;
14 | posts?: string[] | PostPreview[];
15 | numPosts?: number;
16 | dateAdded?: Date;
17 | isDelisted?: boolean;
18 | isActive?: boolean;
19 | sortOrder?: 'asc' | 'dsc';
20 | partOfPublication?: boolean;
21 | publication?: Publication | string | ObjectID;
22 | movedToBlog?: boolean;
23 | }
24 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/codegen.ts:
--------------------------------------------------------------------------------
1 | import type { CodegenConfig } from '@graphql-codegen/cli';
2 |
3 | const config: CodegenConfig = {
4 | overwrite: true,
5 | schema: 'https://gql.hashnode.com',
6 | documents: ['lib/api/**/*.graphql'],
7 | generates: {
8 | 'generated/graphql.ts': {
9 | plugins: ['typescript', 'typescript-operations', 'typescript-graphql-request'],
10 | config: {
11 | rawRequest: false,
12 | inlineFragmentTypes: 'combine',
13 | skipTypename: false,
14 | exportFragmentSpreadSubTypes: true,
15 | dedupeFragments: true,
16 | preResolveTypes: true,
17 | },
18 | },
19 | },
20 | };
21 |
22 | export default config;
23 |
--------------------------------------------------------------------------------
/packages/tsconfig/base.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/tsconfig",
3 | "display": "Default",
4 | "compilerOptions": {
5 | "incremental": true,
6 | "esModuleInterop": true,
7 | "skipLibCheck": true,
8 | "target": "es2022",
9 | "verbatimModuleSyntax": false,
10 | "allowJs": true,
11 | "resolveJsonModule": true,
12 | "moduleDetection": "force",
13 | "isolatedModules": true,
14 |
15 | "strict": true,
16 | "noUncheckedIndexedAccess": false,
17 | "forceConsistentCasingInFileNames": true,
18 |
19 | "moduleResolution": "Bundler",
20 | "module": "ESNext",
21 | "noEmit": true,
22 |
23 | "lib": ["es2022", "dom", "dom.iterable"]
24 | },
25 | "exclude": ["node_modules"]
26 | }
27 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/components/icons/svgs/CloseSVG.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class CloseSVG extends React.Component {
4 | render() {
5 | return (
6 |
9 | );
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/components/icons/svgs/index.js:
--------------------------------------------------------------------------------
1 | import ArticleSVG from './ArticleSVG';
2 | import ChevronDownSVG from './ChevronDownSVG';
3 | import ExternalArrowSVG from './ExternalArrowSVG';
4 | import GithubSVG from './GithubSVG';
5 | import HashnodeSVG from './HashnodeSVG';
6 | import LinkedinSVG from './LinkedinSVG';
7 | import NewsletterPlusSVG from './NewsletterPlusSVG';
8 | import PlusCircleSVG from './PlusCircleSVG';
9 | import RssSVG from './RssSVG';
10 | import XSVG from './XSVG';
11 |
12 | export {
13 | ArticleSVG,
14 | ChevronDownSVG,
15 | ExternalArrowSVG,
16 | GithubSVG,
17 | HashnodeSVG,
18 | LinkedinSVG,
19 | NewsletterPlusSVG,
20 | PlusCircleSVG,
21 | RssSVG,
22 | XSVG,
23 | };
24 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/components/markdown-to-html.tsx:
--------------------------------------------------------------------------------
1 | import { useEmbeds } from '@starter-kit/utils/renderer/hooks/useEmbeds';
2 | import { markdownToHtml } from '@starter-kit/utils/renderer/markdownToHtml';
3 | import { memo } from 'react';
4 |
5 | type Props = {
6 | contentMarkdown: string;
7 | };
8 |
9 | const _MarkdownToHtml = ({ contentMarkdown }: Props) => {
10 | const content = markdownToHtml(contentMarkdown);
11 | useEmbeds({ enabled: true });
12 |
13 | return (
14 |
18 | );
19 | };
20 |
21 | export const MarkdownToHtml = memo(_MarkdownToHtml);
22 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/components/icons/svgs/AlertSVG.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class AlertSVG extends React.Component {
4 | render() {
5 | return (
6 |
9 | );
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/components/markdown-to-html.tsx:
--------------------------------------------------------------------------------
1 | import { useEmbeds } from '@starter-kit/utils/renderer/hooks/useEmbeds';
2 | import { markdownToHtml } from '@starter-kit/utils/renderer/markdownToHtml';
3 | import { memo } from 'react';
4 |
5 | type Props = {
6 | contentMarkdown: string;
7 | };
8 |
9 | const _MarkdownToHtml = ({ contentMarkdown }: Props) => {
10 | const content = markdownToHtml(contentMarkdown);
11 | useEmbeds({ enabled: true });
12 |
13 | return (
14 |
18 | );
19 | };
20 |
21 | export const MarkdownToHtml = memo(_MarkdownToHtml);
22 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/components/icons/svgs/CloseSVG.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class CloseSVG extends React.Component {
4 | render() {
5 | return (
6 |
9 | );
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/components/icons/svgs/ClipboardSVG.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class ClipboardSVG extends React.Component {
4 | render() {
5 | return (
6 |
9 | );
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/components/icons/svgs/ChevronLeftSVG.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class ChevronLeftSVG extends React.Component {
4 | render() {
5 | return (
6 |
9 | );
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/components/icons/svgs/LinkedinSVG.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class LinkedinSVG extends React.Component {
4 | render() {
5 | return (
6 |
9 | );
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/components/toggle-theme.tsx:
--------------------------------------------------------------------------------
1 | import { ComponentProps } from 'react';
2 | import { useTheme } from 'next-themes';
3 | import { Moon, Sun } from './icons';
4 |
5 | type Props = ComponentProps<'button'>;
6 |
7 | export function ToggleTheme(props: Props) {
8 | const { setTheme, theme } = useTheme();
9 |
10 | return (
11 |
22 | );
23 | }
24 |
--------------------------------------------------------------------------------
/packages/utils/renderer/services/embed.ts:
--------------------------------------------------------------------------------
1 | declare global {
2 | interface Window {
3 | iFrameResize: any;
4 | }
5 | }
6 |
7 | export const loadIframeResizer = () => {
8 | if (typeof window === 'undefined') return;
9 |
10 | const script = document.createElement('script');
11 | script.src = 'https://cdnjs.cloudflare.com/ajax/libs/iframe-resizer/4.3.2/iframeResizer.min.js';
12 | script.async = true;
13 | script.onload = () => {
14 | if (window.iFrameResize) {
15 | window.iFrameResize(
16 | {
17 | checkOrigin: false,
18 | inPageLinks: true,
19 | heightCalculationMethod: 'taggedElement',
20 | },
21 | '.embed-content'
22 | );
23 | }
24 | };
25 | document.body.appendChild(script);
26 | };
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/lib/api/queries/PostsByPublication.graphql:
--------------------------------------------------------------------------------
1 | query PostsByPublication($host: String!, $first: Int!, $after: String) {
2 | publication(host: $host) {
3 | ...Publication
4 | posts(first: $first, after: $after) {
5 | totalDocuments
6 | edges {
7 | node {
8 | ...Post
9 | }
10 | }
11 | pageInfo {
12 | ...PageInfo
13 | }
14 | }
15 | }
16 | }
17 |
18 | query MorePostsByPublication($host: String!, $first: Int!, $after: String) {
19 | publication(host: $host) {
20 | posts(first: $first, after: $after) {
21 | edges {
22 | node {
23 | ...Post
24 | comments(first: 0) {
25 | totalDocuments
26 | }
27 | }
28 | }
29 | pageInfo {
30 | ...PageInfo
31 | }
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/components/icons/svgs/InstagramSVG.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class InstagramSVG extends React.Component {
4 | render() {
5 | return (
6 |
9 | );
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/components/icons/svgs/HashnodeLogoIconV2.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class HashnodeLogoIconV2 extends React.Component {
4 | render() {
5 | return (
6 |
13 | );
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/components/icons/svgs/ListSVG.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class ListSVG extends React.Component {
4 | render() {
5 | return (
6 |
9 | );
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/components/icons/svgs/YoutubeSVG.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class YoutubeSVG extends React.Component {
4 | render() {
5 | return (
6 |
9 | );
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/components/icons/svgs/index.js:
--------------------------------------------------------------------------------
1 | import ArticleSVG from './ArticleSVG';
2 | import ChevronDownSVG from './ChevronDownSVG';
3 | import ExternalArrowSVG from './ExternalArrowSVG';
4 | import GithubSVG from './GithubSVG';
5 | import HashnodeSVG from './HashnodeSVG';
6 | import LinkedinSVG from './LinkedinSVG';
7 | import Moon from './Moon';
8 | import NewsletterPlusSVG from './NewsletterPlusSVG';
9 | import PlusCircleSVG from './PlusCircleSVG';
10 | import RssSVG from './RssSVG';
11 | import Sun from './Sun';
12 | import XSVG from './XSVG';
13 |
14 | export {
15 | ArticleSVG,
16 | ChevronDownSVG,
17 | ExternalArrowSVG,
18 | GithubSVG,
19 | HashnodeSVG,
20 | LinkedinSVG,
21 | Moon,
22 | NewsletterPlusSVG,
23 | PlusCircleSVG,
24 | RssSVG,
25 | Sun,
26 | XSVG,
27 | };
28 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/components/icons/svgs/Sun.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class Sun extends React.Component {
4 | render() {
5 | return (
6 |
26 | );
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/README.md:
--------------------------------------------------------------------------------
1 | # A statically generated blog example using Next.js, Markdown, and TypeScript with Hashnode 💫
2 |
3 | This is the existing [blog-starter](https://github.com/vercel/next.js/tree/canary/examples/blog-starter) plus TypeScript, wired with [Hashnode](https://hashnode.com).
4 |
5 | We've used [Hashnode APIs](https://apidocs.hashnode.com) and integrated them with this blog starter kit.
6 |
7 | ## Want to have your own?
8 |
9 | Fork it and change the environment variable `NEXT_PUBLIC_HASHNODE_PUBLICATION_HOST` to your host (engineering.hashnode.dev is the host in the example) and deploy it to Vercel. That's it! You now have your own frontend. You can still use Hashnode for writing your Articles.
10 |
11 | Demo of the `personal` theme: [https://sandeep.dev/blog](https://sandeep.dev/blog).
12 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/README.md:
--------------------------------------------------------------------------------
1 | # A statically generated blog example using Next.js, Markdown, and TypeScript with Hashnode 💫
2 |
3 | This is the existing [blog-starter](https://github.com/vercel/next.js/tree/canary/examples/blog-starter) plus TypeScript, wired with [Hashnode](https://hashnode.com).
4 |
5 | We've used [Hashnode APIs](https://apidocs.hashnode.com) and integrated them with this blog starter kit.
6 |
7 | ## Want to have your own?
8 |
9 | Fork it and change the environment variable `NEXT_PUBLIC_HASHNODE_PUBLICATION_HOST` to your host (engineering.hashnode.dev is the host in the example) and deploy it to Vercel. That's it! You now have your own frontend. You can still use Hashnode for writing your Articles.
10 |
11 | Demo of the `hashnode` theme: [https://saikrishna.dev/blog](https://saikrishna.dev/blog).
12 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/utils/index.js:
--------------------------------------------------------------------------------
1 | import moment from 'dayjs';
2 |
3 | export const formatDate = (dateString) => {
4 | const difference = moment().diff(moment(dateString), 'minute');
5 | if (difference <= 1440) {
6 | if (difference <= 1) {
7 | return 'Just now';
8 | } else if (difference > 1 && difference < 60) {
9 | return `${difference} mins`;
10 | } else if (difference >= 60 && difference <= 1440) {
11 | const diffInHour = moment().diff(moment(dateString), 'hour');
12 | return `${diffInHour} hr${diffInHour === 1 ? '' : 's'} ago`;
13 | }
14 | } else if (difference > 1440 && difference <= 481801) {
15 | return moment(dateString).format('MMM D');
16 | } else {
17 | return moment(dateString).format('MMM D, YYYY');
18 | }
19 | };
20 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/lib/api/queries/PostsByPublication.graphql:
--------------------------------------------------------------------------------
1 | query PostsByPublication($host: String!, $first: Int!, $after: String) {
2 | publication(host: $host) {
3 | ...Publication
4 | posts(first: $first, after: $after) {
5 | totalDocuments
6 | edges {
7 | node {
8 | ...Post
9 | }
10 | }
11 | pageInfo {
12 | ...PageInfo
13 | }
14 | }
15 | }
16 | }
17 |
18 | query MorePostsByPublication($host: String!, $first: Int!, $after: String) {
19 | publication(host: $host) {
20 | posts(first: $first, after: $after) {
21 | edges {
22 | ...MorePostsEdge
23 | }
24 | pageInfo {
25 | ...PageInfo
26 | }
27 | }
28 | }
29 | }
30 |
31 | fragment MorePostsEdge on PostEdge {
32 | node {
33 | ...Post
34 | comments(first: 0) {
35 | totalDocuments
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/components/icons/svgs/GithubSVG.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class GithubSVG extends React.Component {
4 | render() {
5 | return (
6 |
15 | );
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/components/icons/svgs/PencilSVG.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class PencilSVG extends React.Component {
4 | render() {
5 | return (
6 |
9 | );
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/components/icons/svgs/TwitterXSVG.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class TwitterXSVG extends React.Component {
4 | render() {
5 | return (
6 |
13 | );
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/components/icons/svgs/GithubSVG.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class GithubSVG extends React.Component {
4 | render() {
5 | return (
6 |
15 | );
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/lib/api/queries/PostsByPublication.graphql:
--------------------------------------------------------------------------------
1 | query PostsByPublication($host: String!, $first: Int!, $after: String) {
2 | publication(host: $host) {
3 | ...Publication
4 | posts(first: $first, after: $after) {
5 | totalDocuments
6 | edges {
7 | node {
8 | ...Post
9 | comments(first: 0) {
10 | totalDocuments
11 | }
12 | }
13 | }
14 | pageInfo {
15 | ...PageInfo
16 | }
17 | }
18 | }
19 | }
20 |
21 | query MorePostsByPublication($host: String!, $first: Int!, $after: String) {
22 | publication(host: $host) {
23 | posts(first: $first, after: $after) {
24 | edges {
25 | node {
26 | ...Post
27 | comments(first: 0) {
28 | totalDocuments
29 | }
30 | }
31 | }
32 | pageInfo {
33 | ...PageInfo
34 | }
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/components/minimal-posts.tsx:
--------------------------------------------------------------------------------
1 | import { PostFragment } from '../generated/graphql';
2 | import { MinimalPostPreview } from './minimal-post-preview';
3 |
4 | type Props = {
5 | posts: PostFragment[];
6 | context: 'home' | 'series' | 'tag';
7 | };
8 |
9 | export const MinimalPosts = ({ posts }: Props) => {
10 | return (
11 |
12 | {posts.map((post) => (
13 |
25 | ))}
26 |
27 | );
28 | };
29 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/README.md:
--------------------------------------------------------------------------------
1 | # A statically generated blog example using Next.js, Markdown, and TypeScript with Hashnode 💫
2 |
3 | This is the existing [blog-starter](https://github.com/vercel/next.js/tree/canary/examples/blog-starter) plus TypeScript, wired with [Hashnode](https://hashnode.com).
4 |
5 | We've used [Hashnode APIs](https://apidocs.hashnode.com) and integrated them with this blog starter kit.
6 |
7 | ## Want to have your own?
8 |
9 | Fork it and change the environment variable `NEXT_PUBLIC_HASHNODE_PUBLICATION_HOST` to your host (engineering.hashnode.dev is the host in the example) and deploy it to Vercel. That's it! You now have your own frontend. You can still use Hashnode for writing your Articles.
10 |
11 | Demo of the `enterprise` theme: [https://demo.hashnode.com/engineering](https://demo.hashnode.com/engineering).
12 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/components/fonts/index.tsx:
--------------------------------------------------------------------------------
1 | import { Inter, Plus_Jakarta_Sans } from 'next/font/google';
2 |
3 | const inter = Inter({
4 | subsets: ['latin'],
5 | variable: '--font-inter',
6 | display: 'swap',
7 | });
8 |
9 | const plusJakartaSans = Plus_Jakarta_Sans({
10 | subsets: ['latin'],
11 | variable: '--font-plus-jakarta-sans',
12 | display: 'swap',
13 | });
14 |
15 | const variableConstant = 'variable';
16 | const fontInterVar = inter.variable.replace(variableConstant, 'Inter');
17 | const fontPlusJakartaSansVar = plusJakartaSans.variable.replace(variableConstant, 'Plus_Jakarta_Sans');
18 |
19 | export const GlobalFontVariables = () => (
20 |
26 | );
27 |
--------------------------------------------------------------------------------
/packages/utils/renderer/sanitizeHTMLOptions.js:
--------------------------------------------------------------------------------
1 | const sanitizeHtml = require('sanitize-html');
2 |
3 | const allowedTags = sanitizeHtml.defaults.allowedTags.concat([
4 | 'h1',
5 | 'h2',
6 | 'h3',
7 | 'span',
8 | 'img',
9 | 'div',
10 | 'iframe',
11 | 'abbr',
12 | 'kbd',
13 | 'cite',
14 | 'dl',
15 | 'dt',
16 | 'dd',
17 | 's',
18 | 'sub',
19 | 'sup',
20 | 'details',
21 | 'summary',
22 | ]);
23 | const allowedAttributes = {
24 | '*': ['id'],
25 | iframe: ['src', 'class', 'sandbox', 'style', 'width', 'height'],
26 | div: ['class', 'data-*'],
27 | a: ['href', 'class', 'target'],
28 | img: ['src', 'alt', 'class'],
29 | code: ['class'],
30 | span: ['class'],
31 | abbr: ['title'],
32 | };
33 | // const disallowedTagsMode = 'discard';
34 |
35 | const sanitizeHtmlOptions = { allowedTags, allowedAttributes };
36 |
37 | module.exports = sanitizeHtmlOptions;
38 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/pages/_app.tsx:
--------------------------------------------------------------------------------
1 | import { AppProps } from 'next/app';
2 | import { withUrqlClient } from 'next-urql';
3 | import 'tailwindcss/tailwind.css'
4 |
5 | import '../styles/index.css';
6 | import { GlobalFontVariables } from '../components/fonts';
7 | import { getUrqlClientConfig } from '../lib/api/client';
8 |
9 | import { Fragment } from 'react';
10 |
11 | function MyApp({ Component, pageProps }: AppProps) {
12 | return (
13 |
14 |
15 |
16 |
17 | );
18 | }
19 |
20 | // `withUrqlClient` HOC provides the `urqlClient` prop and takes care of restoring cache from urqlState
21 | // this will provide ssr cache to the provider and enable to use `useQuery` hook on the client side
22 | export default withUrqlClient(getUrqlClientConfig, { neverSuspend: true })(MyApp);
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/lib/api/fragments/Publication.graphql:
--------------------------------------------------------------------------------
1 | fragment Publication on Publication {
2 | id
3 | title
4 | displayTitle
5 | url
6 | metaTags
7 | favicon
8 | isTeam
9 | followersCount
10 | descriptionSEO
11 | author {
12 | name
13 | username
14 | profilePicture
15 | followersCount
16 | }
17 | ogMetaData {
18 | image
19 | }
20 | preferences {
21 | logo
22 | darkMode {
23 | logo
24 | }
25 | navbarItems {
26 | id
27 | type
28 | label
29 | url
30 | }
31 | }
32 | links {
33 | twitter
34 | github
35 | linkedin
36 | hashnode
37 | }
38 | integrations {
39 | umamiWebsiteUUID
40 | gaTrackingID
41 | fbPixelID
42 | hotjarSiteID
43 | matomoURL
44 | matomoSiteID
45 | fathomSiteID
46 | gTagManagerID
47 | fathomCustomDomain
48 | fathomCustomDomainEnabled
49 | plausibleAnalyticsEnabled
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/lib/api/queries/SeriesPageInitial.graphql:
--------------------------------------------------------------------------------
1 | query SeriesPageInitial($host: String!, $slug: String!, $first: Int!, $after: String) {
2 | publication(host: $host) {
3 | ...Publication
4 |
5 | series(slug: $slug) {
6 | id
7 | name
8 | coverImage
9 | slug
10 | description {
11 | html
12 | # We don't need these field. But because of our legacy mapper we need to have it present.
13 | markdown
14 | text
15 | }
16 | cuid
17 | author {
18 | id
19 | name
20 | username
21 | __typename
22 | }
23 |
24 | posts(first: $first, after: $after) {
25 | edges {
26 | node {
27 | ...PostThumbnail
28 | }
29 | cursor
30 | __typename
31 | }
32 | pageInfo {
33 | endCursor
34 | hasNextPage
35 | }
36 | __typename
37 | }
38 | __typename
39 | }
40 | __typename
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/components/cover-image.tsx:
--------------------------------------------------------------------------------
1 | import Image from 'next/image';
2 | import Link from 'next/link';
3 |
4 | type Props = {
5 | title: string;
6 | src: string;
7 | slug?: string;
8 | priority?: boolean;
9 | };
10 |
11 | export const CoverImage = ({ title, src, slug, priority = false }: Props) => {
12 | const postURL = `/${slug}`;
13 |
14 | const image = (
15 |
16 |
24 |
25 | );
26 | return (
27 |
28 | {slug ? (
29 |
30 | {image}
31 |
32 | ) : (
33 | image
34 | )}
35 |
36 | );
37 | };
38 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/components/cover-image.tsx:
--------------------------------------------------------------------------------
1 | import Image from 'next/image';
2 | import Link from 'next/link';
3 |
4 | type Props = {
5 | title: string;
6 | src: string;
7 | slug?: string;
8 | priority?: boolean;
9 | };
10 |
11 | export const CoverImage = ({ title, src, slug, priority = false }: Props) => {
12 | const postURL = `/${slug}`;
13 |
14 | const image = (
15 |
16 |
24 |
25 | );
26 | return (
27 |
28 | {slug ? (
29 |
30 | {image}
31 |
32 | ) : (
33 | image
34 | )}
35 |
36 | );
37 | };
38 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/lib/api/queries/HomePage.graphql:
--------------------------------------------------------------------------------
1 | query HomePageInitial($host: String!) {
2 | publication(host: $host) {
3 | ...Publication
4 | about {
5 | markdown
6 | html
7 | }
8 | posts (first: 10) {
9 | totalDocuments
10 | }
11 | followersCount
12 | author {
13 | id
14 | followersCount
15 | }
16 | pinnedPost {
17 | ...PostThumbnail
18 | }
19 | }
20 | }
21 |
22 | query HomePagePosts($host: String!, $after: String, $first: Int!, $filter: PublicationPostConnectionFilter) {
23 | publication(host: $host) {
24 | id
25 | posts(after: $after, first: $first, filter: $filter) {
26 | totalDocuments
27 | edges {
28 | node {
29 | ...PostThumbnail
30 | }
31 | cursor
32 | }
33 | pageInfo {
34 | hasNextPage
35 | endCursor
36 | }
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/components/icons/svgs/MastodonSVG.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class MastodonSVG extends React.Component {
4 | render() {
5 | return (
6 |
9 | );
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/utils/toast.tsx:
--------------------------------------------------------------------------------
1 | import { createRoot, Root } from 'react-dom/client';
2 | import { Toast } from '../components/toast';
3 |
4 | const TOAST_ELEMENT_ID = 'hn-toast';
5 | let root: Root | null = null;
6 | let timeout: NodeJS.Timeout | null = null;
7 |
8 | const _closeToast = () => {
9 | root?.unmount();
10 | root = null;
11 | };
12 |
13 | // this should be a hook
14 | // TODO: The toast doens't close when Radix modal is used since Radix adds pointer-events: none to the body.
15 | const showToast = (type: 'success' | 'warning' | 'error', title: string, message?: string) => {
16 | timeout && clearTimeout(timeout);
17 | root ||= createRoot(document.getElementById(TOAST_ELEMENT_ID)!);
18 | root.render();
19 | timeout = setTimeout(_closeToast, 5000);
20 | };
21 |
22 | export default showToast;
23 |
24 | export { showToast };
25 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/components/static-page-content.tsx:
--------------------------------------------------------------------------------
1 | import { RequiredStaticPageFieldsFragment } from '../generated/graphql';
2 |
3 | type Props = {
4 | pageContent: RequiredStaticPageFieldsFragment;
5 | };
6 |
7 | function StaticPageContent(props: Props) {
8 | const { content, title } = props.pageContent;
9 |
10 | return (
11 |
12 |
15 |
18 | {title}
19 |
20 |
25 |
26 |
27 | );
28 | }
29 |
30 | export default StaticPageContent;
31 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/pages/robots.txt.tsx:
--------------------------------------------------------------------------------
1 | import { type GetServerSideProps } from 'next';
2 |
3 | const RobotsTxt = () => null;
4 |
5 | export const getServerSideProps: GetServerSideProps = async (ctx) => {
6 | const { res } = ctx;
7 | const host = process.env.NEXT_PUBLIC_HASHNODE_PUBLICATION_HOST;
8 | if (!host) {
9 | throw new Error('Could not determine host');
10 | }
11 |
12 | const sitemapUrl = `https://${host}/sitemap.xml`;
13 | const robotsTxt = `
14 | User-agent: *
15 | Allow: /
16 |
17 | # Google adsbot ignores robots.txt unless specifically named!
18 | User-agent: AdsBot-Google
19 | Allow: /
20 |
21 | User-agent: GPTBot
22 | Disallow: /
23 |
24 | Sitemap: ${sitemapUrl}
25 | `.trim();
26 |
27 | res.setHeader('Cache-Control', 's-maxage=86400, stale-while-revalidate');
28 | res.setHeader('content-type', 'text/plain');
29 | res.write(robotsTxt);
30 | res.end();
31 |
32 | return { props: {} };
33 | };
34 |
35 | export default RobotsTxt;
36 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/pages/robots.txt.tsx:
--------------------------------------------------------------------------------
1 | import { type GetServerSideProps } from 'next';
2 |
3 | const RobotsTxt = () => null;
4 |
5 | export const getServerSideProps: GetServerSideProps = async (ctx) => {
6 | const { res } = ctx;
7 | const host = process.env.NEXT_PUBLIC_HASHNODE_PUBLICATION_HOST;
8 | if (!host) {
9 | throw new Error('Could not determine host');
10 | }
11 |
12 | const sitemapUrl = `https://${host}/sitemap.xml`;
13 | const robotsTxt = `
14 | User-agent: *
15 | Allow: /
16 |
17 | # Google adsbot ignores robots.txt unless specifically named!
18 | User-agent: AdsBot-Google
19 | Allow: /
20 |
21 | User-agent: GPTBot
22 | Disallow: /
23 |
24 | Sitemap: ${sitemapUrl}
25 | `.trim();
26 |
27 | res.setHeader('Cache-Control', 's-maxage=86400, stale-while-revalidate');
28 | res.setHeader('content-type', 'text/plain');
29 | res.write(robotsTxt);
30 | res.end();
31 |
32 | return { props: {} };
33 | };
34 |
35 | export default RobotsTxt;
36 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/pages/robots.txt.tsx:
--------------------------------------------------------------------------------
1 | import { type GetServerSideProps } from 'next';
2 |
3 | const RobotsTxt = () => null;
4 |
5 | export const getServerSideProps: GetServerSideProps = async (ctx) => {
6 | const { res } = ctx;
7 | const host = process.env.NEXT_PUBLIC_HASHNODE_PUBLICATION_HOST;
8 | if (!host) {
9 | throw new Error('Could not determine host');
10 | }
11 |
12 | const sitemapUrl = `https://${host}/sitemap.xml`;
13 | const robotsTxt = `
14 | User-agent: *
15 | Allow: /
16 |
17 | # Google adsbot ignores robots.txt unless specifically named!
18 | User-agent: AdsBot-Google
19 | Allow: /
20 |
21 | User-agent: GPTBot
22 | Disallow: /
23 |
24 | Sitemap: ${sitemapUrl}
25 | `.trim();
26 |
27 | res.setHeader('Cache-Control', 's-maxage=86400, stale-while-revalidate');
28 | res.setHeader('content-type', 'text/plain');
29 | res.write(robotsTxt);
30 | res.end();
31 |
32 | return { props: {} };
33 | };
34 |
35 | export default RobotsTxt;
36 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/components/icons/svgs/HeadphonesSVG.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class HeadphonesSVG extends React.Component {
4 | render() {
5 | return (
6 |
9 | );
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/components/icons/svgs/HashnodeSVG.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class HashnodeSVG extends React.Component {
4 | render() {
5 | return (
6 |
18 | );
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/components/icons/svgs/HashnodeSVG.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class HashnodeSVG extends React.Component {
4 | render() {
5 | return (
6 |
18 | );
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/components/icons/svgs/HashnodeSVG.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class HashnodeSVG extends React.Component {
4 | render() {
5 | return (
6 |
18 | );
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/components/icons/svgs/ShareSVGV2.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const ShareSVGV2 = React.forwardRef>((props, ref) => (
4 |
12 | ));
13 |
14 | ShareSVGV2.displayName = 'ShareSVGV2';
15 |
16 | export default ShareSVGV2;
17 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "hashnode-starter-kit",
3 | "version": "1.0.0",
4 | "description": "An OSS Starter Kit to roll out frontends using Hashnode Public APIs",
5 | "keywords": [],
6 | "license": "MIT",
7 | "author": "",
8 | "scripts": {
9 | "format": "prettier --write \"**/*.{js,jsx,ts,tsx,md,json}\"",
10 | "preinstall": "npx only-allow pnpm"
11 | },
12 | "dependencies": {
13 | "classnames": "^2.3.1",
14 | "next": "^13.4.19",
15 | "react": "^18.2.0",
16 | "react-dom": "^18.2.0"
17 | },
18 | "devDependencies": {
19 | "@types/node": "^18.0.3",
20 | "@types/react": "^18.0.15",
21 | "@types/react-dom": "^18.0.6",
22 | "prettier": "^3.0.3",
23 | "prettier-plugin-organize-imports": "^3.2.3",
24 | "prettier-plugin-packagejson": "^2.4.6",
25 | "prettier-plugin-tailwindcss": "^0.5.5",
26 | "tailwindcss": "^3.3.3",
27 | "typescript": "^5.2.2"
28 | },
29 | "engines": {
30 | "node": ">=18.0.0",
31 | "pnpm": ">=8.0.0"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/components/icons/svgs/FileLineChartSVG.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class FileLineChartSVG extends React.Component {
4 | render() {
5 | return (
6 |
9 | );
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/pages/dashboard.tsx:
--------------------------------------------------------------------------------
1 | import request from 'graphql-request';
2 | import { GetServerSideProps } from 'next';
3 | import {
4 | PublicationByHostDocument,
5 | PublicationByHostQuery,
6 | PublicationByHostQueryVariables,
7 | } from '../generated/graphql';
8 |
9 | const GQL_ENDPOINT = process.env.NEXT_PUBLIC_HASHNODE_GQL_ENDPOINT;
10 | const Dashboard = () => null;
11 |
12 | export const getServerSideProps: GetServerSideProps = async () => {
13 | const data = await request(
14 | GQL_ENDPOINT,
15 | PublicationByHostDocument,
16 | {
17 | host: process.env.NEXT_PUBLIC_HASHNODE_PUBLICATION_HOST,
18 | },
19 | );
20 |
21 | const publication = data.publication;
22 | if (!publication) {
23 | return {
24 | notFound: true,
25 | };
26 | }
27 |
28 | return {
29 | redirect: {
30 | destination: `https://hashnode.com/${publication.id}/dashboard`,
31 | permanent: false,
32 | },
33 | };
34 | };
35 |
36 | export default Dashboard;
37 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/pages/dashboard.tsx:
--------------------------------------------------------------------------------
1 | import request from 'graphql-request';
2 | import { GetServerSideProps } from 'next';
3 | import {
4 | PublicationByHostDocument,
5 | PublicationByHostQuery,
6 | PublicationByHostQueryVariables,
7 | } from '../generated/graphql';
8 |
9 | const GQL_ENDPOINT = process.env.NEXT_PUBLIC_HASHNODE_GQL_ENDPOINT;
10 | const Dashboard = () => null;
11 |
12 | export const getServerSideProps: GetServerSideProps = async () => {
13 | const data = await request(
14 | GQL_ENDPOINT,
15 | PublicationByHostDocument,
16 | {
17 | host: process.env.NEXT_PUBLIC_HASHNODE_PUBLICATION_HOST,
18 | },
19 | );
20 |
21 | const publication = data.publication;
22 | if (!publication) {
23 | return {
24 | notFound: true,
25 | };
26 | }
27 |
28 | return {
29 | redirect: {
30 | destination: `https://hashnode.com/${publication.id}/dashboard`,
31 | permanent: false,
32 | },
33 | };
34 | };
35 |
36 | export default Dashboard;
37 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/pages/dashboard.tsx:
--------------------------------------------------------------------------------
1 | import request from 'graphql-request';
2 | import { GetServerSideProps } from 'next';
3 | import {
4 | PublicationByHostDocument,
5 | PublicationByHostQuery,
6 | PublicationByHostQueryVariables,
7 | } from '../generated/graphql';
8 |
9 | const GQL_ENDPOINT = process.env.NEXT_PUBLIC_HASHNODE_GQL_ENDPOINT;
10 | const Dashboard = () => null;
11 |
12 | export const getServerSideProps: GetServerSideProps = async () => {
13 | const data = await request(
14 | GQL_ENDPOINT,
15 | PublicationByHostDocument,
16 | {
17 | host: process.env.NEXT_PUBLIC_HASHNODE_PUBLICATION_HOST,
18 | },
19 | );
20 |
21 | const publication = data.publication;
22 | if (!publication) {
23 | return {
24 | notFound: true,
25 | };
26 | }
27 |
28 | return {
29 | redirect: {
30 | destination: `https://hashnode.com/${publication.id}/dashboard`,
31 | permanent: false,
32 | },
33 | };
34 | };
35 |
36 | export default Dashboard;
37 |
--------------------------------------------------------------------------------
/packages/utils/handle-math-jax.js:
--------------------------------------------------------------------------------
1 | const handleMathJax = (rerun = false) => {
2 | if (typeof window === 'undefined') {
3 | return;
4 | }
5 |
6 | const mathjaxScript = 'https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js';
7 | if (!window.MathJax) {
8 | window.MathJax = {
9 | tex: {
10 | inlineMath: [['\\(', '\\)']],
11 | },
12 | };
13 | }
14 |
15 | let mathjaxScriptTag = document.querySelector(`script[src="${mathjaxScript}"]`);
16 | if (!mathjaxScriptTag) {
17 | let script = document.createElement('script');
18 | script.type = 'text/javascript';
19 | script.src = mathjaxScript;
20 | script.onload = function () {
21 | window.MathJax && 'mathml2chtml' in window.MathJax && window.MathJax.mathml2chtml();
22 | };
23 | document.head.appendChild(script);
24 | } else if (rerun) {
25 | window.MathJax && 'mathml2chtml' in window.MathJax && window.MathJax.mathml2chtml();
26 | }
27 | };
28 |
29 | export default handleMathJax;
30 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/components/post-comments-sidebar.tsx:
--------------------------------------------------------------------------------
1 | import { PostFullFragment } from '../generated/graphql';
2 | import CommentsSheet from './comments-sheet';
3 | import ResponseList from './response-list';
4 |
5 | const PostCommentsSidebar = ({
6 | hideSidebar,
7 | isPublicationPost,
8 | selectedFilter,
9 | post,
10 | }: {
11 | hideSidebar: () => void;
12 | isPublicationPost: boolean;
13 | selectedFilter: string;
14 | post: PostFullFragment;
15 | }) => (
16 |
17 |
18 | {!post.preferences.disableComments ? (
19 |
23 | ) : (
24 |
25 |
The comments have been disabled by the author for this article
26 |
27 | )}
28 |
29 | );
30 |
31 | export default PostCommentsSidebar;
32 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/lib/api/queries/Sitemap.graphql:
--------------------------------------------------------------------------------
1 | query Sitemap($host: String!, $postsCount: Int!, $postsAfter: String, $staticPagesCount: Int!) {
2 | publication(host: $host) {
3 | id
4 | url
5 | staticPages(first: $staticPagesCount) {
6 | edges {
7 | node {
8 | slug
9 | }
10 | }
11 | }
12 | posts(first: $postsCount, after: $postsAfter) {
13 | edges {
14 | node {
15 | ...RequiredSitemapPostFields
16 | }
17 | }
18 | pageInfo {
19 | ...PageInfo
20 | }
21 | }
22 | }
23 | }
24 |
25 | query MoreSitemapPosts($host: String!, $postsCount: Int!, $postsAfter: String) {
26 | publication(host: $host) {
27 | id
28 | posts(first: $postsCount, after: $postsAfter) {
29 | edges {
30 | node {
31 | ...RequiredSitemapPostFields
32 | }
33 | }
34 | pageInfo {
35 | ...PageInfo
36 | }
37 | }
38 | }
39 | }
40 |
41 | fragment RequiredSitemapPostFields on Post {
42 | id
43 | url
44 | slug
45 | publishedAt
46 | updatedAt
47 | tags {
48 | id
49 | name
50 | slug
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/lib/api/queries/Sitemap.graphql:
--------------------------------------------------------------------------------
1 | query Sitemap($host: String!, $postsCount: Int!, $postsAfter: String, $staticPagesCount: Int!) {
2 | publication(host: $host) {
3 | id
4 | url
5 | staticPages(first: $staticPagesCount) {
6 | edges {
7 | node {
8 | slug
9 | }
10 | }
11 | }
12 | posts(first: $postsCount, after: $postsAfter) {
13 | edges {
14 | node {
15 | ...RequiredSitemapPostFields
16 | }
17 | }
18 | pageInfo {
19 | ...PageInfo
20 | }
21 | }
22 | }
23 | }
24 |
25 | query MoreSitemapPosts($host: String!, $postsCount: Int!, $postsAfter: String) {
26 | publication(host: $host) {
27 | id
28 | posts(first: $postsCount, after: $postsAfter) {
29 | edges {
30 | node {
31 | ...RequiredSitemapPostFields
32 | }
33 | }
34 | pageInfo {
35 | ...PageInfo
36 | }
37 | }
38 | }
39 | }
40 |
41 | fragment RequiredSitemapPostFields on Post {
42 | id
43 | url
44 | slug
45 | publishedAt
46 | updatedAt
47 | tags {
48 | id
49 | name
50 | slug
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/lib/api/queries/Sitemap.graphql:
--------------------------------------------------------------------------------
1 | query Sitemap($host: String!, $postsCount: Int!, $postsAfter: String, $staticPagesCount: Int!) {
2 | publication(host: $host) {
3 | id
4 | url
5 | staticPages(first: $staticPagesCount) {
6 | edges {
7 | node {
8 | slug
9 | }
10 | }
11 | }
12 | posts(first: $postsCount, after: $postsAfter) {
13 | edges {
14 | node {
15 | ...RequiredSitemapPostFields
16 | }
17 | }
18 | pageInfo {
19 | ...PageInfo
20 | }
21 | }
22 | }
23 | }
24 |
25 | query MoreSitemapPosts($host: String!, $postsCount: Int!, $postsAfter: String) {
26 | publication(host: $host) {
27 | id
28 | posts(first: $postsCount, after: $postsAfter) {
29 | edges {
30 | node {
31 | ...RequiredSitemapPostFields
32 | }
33 | }
34 | pageInfo {
35 | ...PageInfo
36 | }
37 | }
38 | }
39 | }
40 |
41 | fragment RequiredSitemapPostFields on Post {
42 | id
43 | url
44 | slug
45 | publishedAt
46 | updatedAt
47 | tags {
48 | id
49 | name
50 | slug
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/components/icons/svgs/CommentSVGV2.js:
--------------------------------------------------------------------------------
1 | export default function CommentSVGV2(props) {
2 | return (
3 |
11 | );
12 | }
13 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/types/User.ts:
--------------------------------------------------------------------------------
1 | import { ObjectID } from 'mongodb';
2 | import { SocialMedia, PostPreview } from './index';
3 |
4 | export interface User {
5 | _id: ObjectID;
6 | isAmbassador: boolean;
7 | hasGoldRing: boolean;
8 | hasBetaAccess: boolean;
9 | publicationDomain: string;
10 | isDeactivated: boolean;
11 | numReactions: number;
12 | tagline: string;
13 | username: string;
14 | name: string;
15 | email: string;
16 | photo: string;
17 | numFollowers: number;
18 | numFollowing: number;
19 | location: string;
20 | coverImage: string;
21 | dateJoined: Date;
22 | socialMedia: SocialMedia;
23 | totalUpvotesReceived: number;
24 | storiesCreated: PostPreview[] | string[];
25 | isEvangelist: boolean;
26 | numSeries: number;
27 | numPosts: number;
28 | tagManagerOf: string[];
29 | role: string;
30 | beingFollowed: boolean;
31 | bio?: string;
32 | betaFeatures: {
33 | referralProgram: boolean;
34 | socialProofLikes: boolean;
35 | };
36 | pro?: {
37 | hasProAccess?: boolean;
38 | };
39 | }
40 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/utils/handle-math-jax.js:
--------------------------------------------------------------------------------
1 | const handleMathJax = (rerun = false) => {
2 | if (typeof window === 'undefined') {
3 | return;
4 | }
5 |
6 | const mathjaxScript = 'https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js';
7 | if (!window.MathJax) {
8 | window.MathJax = {
9 | tex: {
10 | inlineMath: [['\\(', '\\)']],
11 | },
12 | };
13 | }
14 |
15 | let mathjaxScriptTag = document.querySelector(`script[src="${mathjaxScript}"]`);
16 | if (!mathjaxScriptTag) {
17 | let script = document.createElement('script');
18 | script.type = 'text/javascript';
19 | script.src = mathjaxScript;
20 | script.onload = function () {
21 | window.MathJax && 'mathml2chtml' in window.MathJax && window.MathJax.mathml2chtml();
22 | };
23 | document.head.appendChild(script);
24 | } else if (rerun) {
25 | window.MathJax && 'mathml2chtml' in window.MathJax && window.MathJax.mathml2chtml();
26 | }
27 | };
28 |
29 | export default handleMathJax;
30 |
--------------------------------------------------------------------------------
/license.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2024 Hashnode.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/types/extras.ts:
--------------------------------------------------------------------------------
1 | import { ObjectID } from 'mongodb';
2 |
3 | export interface SocialMedia {
4 | website?: string;
5 | github?: string;
6 | twitter?: string;
7 | facebook?: string;
8 | stackoverflow?: string;
9 | linkedin?: string;
10 | }
11 |
12 | export interface Reaction {
13 | _id: ObjectID;
14 | name: string;
15 | image: string;
16 | }
17 |
18 | export type Node = {
19 | isApproved: boolean;
20 | isActive: boolean;
21 | };
22 |
23 | export interface Tag {
24 | _id: ObjectID;
25 | name: string;
26 | slug: string;
27 | isActive: boolean;
28 | isApproved: boolean;
29 | mergedWith?: Tag | ObjectID;
30 | numPosts?: number;
31 | }
32 |
33 | export interface RedirectResponse {
34 | redirect: { permanent: boolean; destination: string };
35 | }
36 |
37 | export interface Head {
38 | customFavicon: string;
39 | customTheme: string;
40 | customMeta: string;
41 | }
42 |
43 | export type Nullable = {
44 | [P in keyof T]?: T[P] | null;
45 | };
46 |
47 | export type ReportScrollFunction = { scrollToReportSection: () => void };
48 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/types/Response.ts:
--------------------------------------------------------------------------------
1 | import { ObjectId } from 'mongodb';
2 | import { User, Reaction } from './index';
3 |
4 | export type Reply = {
5 | _id: ObjectId;
6 | content: string;
7 | contentMarkdown: string;
8 | author: User;
9 | dateAdded: string;
10 | isActive: boolean;
11 | stamp: string;
12 | upvotes: string;
13 | reactions: string[] | Reaction[];
14 | reactionToCountMap: Record;
15 | totalReactions: number;
16 | reactionsByCurrentUser: string[] | Reaction[];
17 | totalReactionsByCurrentUser: number;
18 | };
19 |
20 | export type Response = {
21 | _id: ObjectId;
22 | content: string;
23 | contentMarkdown: string;
24 | author: User;
25 | dateAdded: string;
26 | isActive: boolean;
27 | responseBrief: string;
28 | stamp: string;
29 | upvotes: number;
30 | reactions: string[] | Reaction[];
31 | replies: any;
32 | reactionToCountMap: Record;
33 | totalReactions: number;
34 | reactionsByCurrentUser: string[] | Reaction[];
35 | totalReactionsByCurrentUser: number;
36 | isCollapsed: boolean;
37 | };
38 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/components/avatar.tsx:
--------------------------------------------------------------------------------
1 | import { resizeImage } from '@starter-kit/utils/image';
2 | import { DEFAULT_AVATAR } from '../utils/const';
3 | import Image from 'next/image';
4 |
5 | type Props = {
6 | username: string;
7 | name: string;
8 | picture: string;
9 | size: number;
10 | };
11 |
12 | export const Avatar = ({ username, name, picture, size }: Props) => {
13 | return (
14 |
31 | );
32 | };
33 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/components/publication-social-link-item.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { twJoin } from 'tailwind-merge';
3 |
4 | type Props = {
5 | href: string;
6 | labelText: string;
7 | children: React.ReactElement | null;
8 | isSidebar?: boolean;
9 | };
10 |
11 | function PublicationSocialLinkItem(props: Props) {
12 | const { href, labelText, children, isSidebar } = props;
13 |
14 | return (
15 |
30 | {children}
31 |
32 | );
33 | }
34 |
35 | export default PublicationSocialLinkItem;
36 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/components/more-posts.tsx:
--------------------------------------------------------------------------------
1 | import { PostFragment } from '../generated/graphql';
2 | import { PostPreview } from './post-preview';
3 |
4 | type Props = {
5 | posts: PostFragment[];
6 | context: 'home' | 'series' | 'tag';
7 | };
8 |
9 | export const MorePosts = ({ posts, context }: Props) => {
10 | return (
11 |
12 | {context === 'home' && (
13 |
14 | More Posts
15 |
16 | )}
17 |
18 | {posts.map((post) => (
19 |
31 | ))}
32 |
33 |
34 | );
35 | };
36 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/components/icons/svgs/RedditSVGV2.js:
--------------------------------------------------------------------------------
1 | const RedditSVGV2 = (props) => {
2 | return (
3 |
6 | );
7 | };
8 |
9 | export default RedditSVGV2;
10 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/components/footer.tsx:
--------------------------------------------------------------------------------
1 | import { useAppContext } from './contexts/appContext';
2 |
3 | export const Footer = () => {
4 | const { publication } = useAppContext();
5 |
6 | return (
7 |
28 | );
29 | };
30 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/components/avatar.tsx:
--------------------------------------------------------------------------------
1 | import { resizeImage } from '@starter-kit/utils/image';
2 | import { DEFAULT_AVATAR } from '../utils/const';
3 |
4 | type Props = {
5 | username: string;
6 | name: string;
7 | picture: string | null | undefined;
8 | size: number;
9 | };
10 |
11 | export const Avatar = ({ username, name, picture, size }: Props) => {
12 | return (
13 |
36 | );
37 | };
38 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/components/post-floating-bar-tooltip-wrapper.tsx:
--------------------------------------------------------------------------------
1 | import * as Tooltip from '@radix-ui/react-tooltip';
2 | import React from 'react';
3 | import { twMerge } from 'tailwind-merge';
4 |
5 | export default function PostFloatingBarTooltipWrapper({
6 | children,
7 | label,
8 | asChild = true,
9 | labelSide = 'top',
10 | contentClassName = '',
11 | delayDuration,
12 | }: {
13 | children: React.ReactChild;
14 | label: string;
15 | asChild?: boolean;
16 | labelSide?: 'top' | 'right' | 'bottom' | 'left';
17 | contentClassName?: string;
18 | delayDuration?: number;
19 | }) {
20 | return (
21 |
22 |
23 | {children}
24 |
25 |
26 |
31 | {label}
32 |
33 |
34 |
35 | );
36 | }
37 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/components/meta.tsx:
--------------------------------------------------------------------------------
1 | import parse from 'html-react-parser';
2 | import Head from 'next/head';
3 | import { useAppContext } from './contexts/appContext';
4 |
5 | export const Meta = () => {
6 | const { publication } = useAppContext();
7 | const { metaTags, favicon } = publication;
8 | const defaultFavicons = (
9 | <>
10 |
11 |
12 |
13 |
14 |
15 |
16 | >
17 | );
18 |
19 | return (
20 |
21 | {favicon ? : defaultFavicons}
22 |
23 |
24 | {metaTags && parse(metaTags)}
25 |
26 | );
27 | };
28 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/components/meta.tsx:
--------------------------------------------------------------------------------
1 | import parse from 'html-react-parser';
2 | import Head from 'next/head';
3 | import { useAppContext } from './contexts/appContext';
4 |
5 | export const Meta = () => {
6 | const { publication } = useAppContext();
7 | const { metaTags, favicon } = publication;
8 | const defaultFavicons = (
9 | <>
10 |
11 |
12 |
13 |
14 |
15 |
16 | >
17 | );
18 |
19 | return (
20 |
21 | {favicon ? : defaultFavicons}
22 |
23 |
24 | {metaTags && parse(metaTags)}
25 |
26 | );
27 | };
28 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/components/subscribe.tsx:
--------------------------------------------------------------------------------
1 | import * as Popover from '@radix-ui/react-popover';
2 | import { Button } from './button';
3 | import { NewsletterPlusSVG } from './icons';
4 | import { SubscribeForm } from './subscribe-form';
5 |
6 | export const Subscribe = () => {
7 | return (
8 |
9 |
10 |
11 | }
15 | className="!bg-white dark:!bg-neutral-950"
16 | />
17 |
18 |
19 |
24 |
25 | Subscribe to our newsletter for updates and changelog.
26 |
27 |
28 |
29 |
30 |
31 |
32 | );
33 | };
34 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/components/meta.tsx:
--------------------------------------------------------------------------------
1 | import parse from 'html-react-parser';
2 | import Head from 'next/head';
3 |
4 | import { useAppContext } from './contexts/appContext';
5 |
6 | export const Meta = () => {
7 | const { publication } = useAppContext();
8 | const { metaTags, favicon } = publication;
9 | const defaultFavicons = (
10 | <>
11 |
12 |
13 |
14 |
15 |
16 |
17 | >
18 | );
19 |
20 | return (
21 |
22 | {favicon ? : defaultFavicons}
23 |
24 |
25 | {metaTags && parse(metaTags)}
26 |
27 | );
28 | };
29 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/pnpm-lock.yaml:
--------------------------------------------------------------------------------
1 | lockfileVersion: '9.0'
2 |
3 | settings:
4 | autoInstallPeers: true
5 | excludeLinksFromLockfile: false
6 |
7 | importers:
8 |
9 | .:
10 | dependencies:
11 | '@starter-kit/utils':
12 | specifier: workspace:*
13 | version: link:../../../utils
14 | clsx:
15 | specifier: ^2.1.1
16 | version: 2.1.1
17 | tailwind-merge:
18 | specifier: ^1.13.2
19 | version: 1.14.0
20 | devDependencies:
21 | '@starter-kit/eslint-config-custom':
22 | specifier: workspace:*
23 | version: link:../../../eslint-config-custom
24 | '@starter-kit/tsconfig':
25 | specifier: workspace:*
26 | version: link:../../../tsconfig
27 |
28 | packages:
29 |
30 | clsx@2.1.1:
31 | resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
32 | engines: {node: '>=6'}
33 |
34 | tailwind-merge@1.14.0:
35 | resolution: {integrity: sha512-3mFKyCo/MBcgyOTlrY8T7odzZFx+w+qKSMAmdFzRvqBfLlSigU6TZnlFHK0lkMwj9Bj8OYU+9yW9lmGuS0QEnQ==}
36 |
37 | snapshots:
38 |
39 | clsx@2.1.1: {}
40 |
41 | tailwind-merge@1.14.0: {}
42 |
--------------------------------------------------------------------------------
/packages/utils/trigger-custom-widget-embed.js:
--------------------------------------------------------------------------------
1 | export const triggerCustomWidgetEmbed = async (pubId) => {
2 | const frames = document.querySelectorAll('.hn-embed-widget');
3 | if (frames.length === 0) {
4 | return;
5 | }
6 | frames.forEach(async (frame) => {
7 | try {
8 | const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || '';
9 | const iframe = document.createElement('iframe');
10 | const host = window.location.hostname;
11 | iframe.id = `frame-${frame.id}`;
12 | iframe.sandbox =
13 | 'allow-same-origin allow-forms allow-presentation allow-scripts allow-popups';
14 | iframe.src =
15 | host.indexOf('.hashnode.net') !== -1 || host.indexOf('.app.localhost') !== -1
16 | ? `${baseUrl}/api/pub/${pubId}/embed/${frame.id}`
17 | : `https://embeds.hashnode.co?p=${pubId}&w=${frame.id}`;
18 | iframe.width = '100%';
19 | frame.innerHTML = '';
20 | frame.appendChild(iframe);
21 | setTimeout(() => {
22 | // TODO:
23 | // eslint-disable-next-line no-undef
24 | iFrameResize({ log: false, autoResize: true }, `#${iframe.id}`);
25 | }, 1000);
26 | frame.setAttribute('class', 'hn-embed-widget-expanded');
27 | } catch (e) {
28 | console.log(e);
29 | }
30 | });
31 | };
32 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/components/icons/svgs/RedditSVG.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class RedditSVG extends React.Component {
4 | render() {
5 | return (
6 |
9 | );
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/components/icons/svgs/BookOpenSVG.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class BookOpenSVG extends React.Component {
4 | render() {
5 | return (
6 |
9 | );
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/components/icons/svgs/BookOpenSVG.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class BookOpenSVG extends React.Component {
4 | render() {
5 | return (
6 |
9 | );
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/components/icons/svgs/ChartMixedSVG.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | export default class ChartMixedSVG extends React.Component {
4 | render() {
5 | return (
6 |
9 | );
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/components/icons/svgs/LinkSVGV2.js:
--------------------------------------------------------------------------------
1 | const LinkSVGV2 = (props) => {
2 | return (
3 |
6 | );
7 | };
8 |
9 | export default LinkSVGV2;
10 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/components/contexts/appContext.tsx:
--------------------------------------------------------------------------------
1 | import React, { createContext, useContext } from 'react';
2 | import {
3 | PageByPublicationQuery,
4 | PostFullFragment,
5 | PublicationFragment,
6 | } from '../../generated/graphql';
7 |
8 | type AppContext = {
9 | publication: PublicationFragment;
10 | post: PostFullFragment | null;
11 | page: NonNullable['staticPage'];
12 | };
13 |
14 | const AppContext = createContext(null);
15 |
16 | const AppProvider = ({
17 | children,
18 | publication,
19 | post,
20 | page,
21 | }: {
22 | children: React.ReactNode;
23 | publication: PublicationFragment;
24 | post?: PostFullFragment | null;
25 | page?: NonNullable['staticPage'];
26 | }) => {
27 | return (
28 |
35 | {children}
36 |
37 | );
38 | };
39 |
40 | const useAppContext = () => {
41 | const context = useContext(AppContext);
42 |
43 | if (!context) {
44 | throw new Error('useAppContext must be used within a ');
45 | }
46 |
47 | return context;
48 | };
49 | export { AppProvider, useAppContext };
50 |
--------------------------------------------------------------------------------
/packages/utils/renderer/headingSlugger.ts:
--------------------------------------------------------------------------------
1 | import sanitizeHtml from 'sanitize-html';
2 | import slug from 'slug';
3 |
4 | export class HeadingSlugger {
5 | headings: { [key: string]: number };
6 |
7 | constructor() {
8 | this.headings = {};
9 | }
10 |
11 | static sanitizeSlug(str: string): string {
12 | return slug(sanitizeHtml(str, { allowedTags: [] }), { lower: true });
13 | }
14 |
15 | private doesHeadingExist(slug: string): boolean {
16 | // eslint-disable-next-line no-prototype-builtins
17 | return this.headings.hasOwnProperty(slug);
18 | }
19 |
20 | private findSafeSlug(originalSlug: string) {
21 | const headingExists = this.doesHeadingExist(originalSlug);
22 |
23 | if (!headingExists) {
24 | this.headings[originalSlug] = 0;
25 | return originalSlug;
26 | }
27 | let modifiedSlug;
28 | let duplicateCount = this.headings[originalSlug];
29 |
30 | do {
31 | duplicateCount += 1;
32 | modifiedSlug = `${originalSlug}-${duplicateCount}`;
33 | } while (this.doesHeadingExist(modifiedSlug));
34 |
35 | this.headings[modifiedSlug] = 0;
36 | this.headings[originalSlug] += 1;
37 | return modifiedSlug;
38 | }
39 |
40 | public getSlug(str: string) {
41 | const sanitizedSlug = HeadingSlugger.sanitizeSlug(str);
42 | return this.findSafeSlug(sanitizedSlug);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/components/header-tooltip.tsx:
--------------------------------------------------------------------------------
1 | import * as RadixTooltip from '@radix-ui/react-tooltip';
2 | import { twJoin } from 'tailwind-merge';
3 |
4 | import { useAppContext } from './contexts/appContext';
5 |
6 | interface IHeaderTooltip {
7 | tooltipClassName: string;
8 | tooltipText: string;
9 | children: React.ReactNode;
10 | }
11 |
12 | const HeaderTooltip = (props: IHeaderTooltip) => {
13 | const { tooltipClassName, tooltipText, children } = props;
14 |
15 | return (
16 |
17 |
18 | {children}
19 |
20 |
31 | {tooltipText}
32 |
33 |
34 |
35 |
36 | );
37 | };
38 |
39 | export default HeaderTooltip;
40 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/lib/api/queries/SinglePostByPublication.graphql:
--------------------------------------------------------------------------------
1 | query SinglePostByPublication($slug: String!, $host: String!) {
2 | publication(host: $host) {
3 | ...Publication
4 | post(slug: $slug) {
5 | ...PostFull
6 | }
7 | }
8 | }
9 |
10 | fragment PostFull on Post {
11 | id
12 | slug
13 | url
14 | brief
15 | title
16 | subtitle
17 | hasLatexInPost
18 | publishedAt
19 | updatedAt
20 | readTimeInMinutes
21 | reactionCount
22 | responseCount
23 | publication {
24 | id
25 | }
26 | seo {
27 | title
28 | description
29 | }
30 | coverImage {
31 | url
32 | }
33 | author {
34 | name
35 | username
36 | profilePicture
37 | }
38 | title
39 | content {
40 | markdown
41 | html
42 | }
43 | ogMetaData {
44 | image
45 | }
46 | tags {
47 | id
48 | name
49 | slug
50 | }
51 | features {
52 | tableOfContents {
53 | isEnabled
54 | items {
55 | id
56 | level
57 | parentId
58 | slug
59 | title
60 | }
61 | }
62 | }
63 | preferences {
64 | disableComments
65 | }
66 | comments(first: 25) {
67 | totalDocuments
68 | edges {
69 | node {
70 | id
71 | totalReactions
72 | content {
73 | markdown
74 | }
75 | author {
76 | name
77 | username
78 | profilePicture
79 | }
80 | }
81 | }
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/components/about-author.tsx:
--------------------------------------------------------------------------------
1 | import PostAuthorInfo from './post-author-info';
2 | import { useAppContext } from './contexts/appContext';
3 | import { PostFullFragment } from '../generated/graphql';
4 |
5 | function AboutAuthor() {
6 | const { post: _post } = useAppContext();
7 | const post = _post as unknown as PostFullFragment;
8 | const { publication, author } = post;
9 | let coAuthors = post.coAuthors || [];
10 |
11 | const allAuthors = publication?.isTeam ? [author, ...coAuthors] : [author];
12 |
13 | return (
14 |
15 |
16 |
17 |
18 | Written by
19 |
20 |
21 | {allAuthors.map((_author) => {
22 | return (
23 |
27 | );
28 | })}
29 |
30 |
31 |
32 |
33 | );
34 | }
35 |
36 | export default AboutAuthor;
37 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/components/publication-meta.tsx:
--------------------------------------------------------------------------------
1 | import { resizeImage } from '../utils/image';
2 | import { Publication } from '../generated/graphql';
3 | import { twJoin } from 'tailwind-merge';
4 |
5 | // TODO: this component name is confusing.
6 | const PublicationMeta = (
7 | props: Pick & {
8 | author: Pick;
9 | aboutHTML?: string | null;
10 | },
11 | ) => {
12 | const { isTeam, aboutHTML, author } = props;
13 | const authorImageURL = resizeImage(
14 | author.profilePicture || 'https://cdn.hashnode.com/res/hashnode/image/upload/v1659089761812/fsOct5gl6.png',
15 | { w: 400, h: 400, c: 'face' },
16 | );
17 |
18 | return (
19 |
20 |
21 |
22 | {aboutHTML ? (
23 |
28 | ) : null}
29 |
30 |
31 |
32 | );
33 | };
34 |
35 | export default PublicationMeta;
36 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/components/about-author.tsx:
--------------------------------------------------------------------------------
1 | import PostAuthorInfo from './post-author-info';
2 | import { useAppContext } from './contexts/appContext';
3 | import { PostFullFragment } from '../generated/graphql';
4 |
5 | function AboutAuthor() {
6 | const { post: _post } = useAppContext();
7 | const post = _post as unknown as PostFullFragment;
8 | const { publication, author } = post;
9 | let coAuthors = post.coAuthors || [];
10 |
11 | const allAuthors = publication?.isTeam ? [author, ...coAuthors] : [author];
12 |
13 | return (
14 |
15 |
16 |
17 |
18 | Written by
19 |
20 |
21 | {allAuthors.map((_author) => {
22 | return (
23 |
27 | );
28 | })}
29 |
30 |
31 |
32 |
33 | );
34 | }
35 |
36 | export default AboutAuthor;
--------------------------------------------------------------------------------
/packages/utils/seo/addArticleJsonLd.ts:
--------------------------------------------------------------------------------
1 | export const addArticleJsonLd = (publication: any, post: any) => {
2 | const tags = (post.tags ?? []).map((tag: any) => tag.name);
3 | const schema = {
4 | '@context': 'https://schema.org/',
5 | '@type': 'Blog',
6 | '@id': publication.url,
7 | mainEntityOfPage: publication.url,
8 | name: publication.title,
9 | description: publication.about?.markdown,
10 | publisher: {
11 | '@type': publication.isTeam ? 'Organization' : 'Person',
12 | '@id': publication.url,
13 | name: publication.title,
14 | image: {
15 | '@type': 'ImageObject',
16 | url: publication.preferences?.logo || publication.preferences?.darkMode?.logo,
17 | },
18 | },
19 | blogPost: [
20 | {
21 | '@type': 'BlogPosting',
22 | '@id': post.url,
23 | mainEntityOfPage: post.url,
24 | headline: post.title,
25 | name: post.title,
26 | description: post.seo?.description || post.brief,
27 | datePublished: post.publishedAt,
28 | dateModified: post.updatedAt,
29 | author: {
30 | '@type': 'Person',
31 | '@id': `https://hashnode.com/@${post.author?.username}`,
32 | name: post.author?.name,
33 | url: `https://hashnode.com/@${post.author?.username}`,
34 | },
35 | image: {
36 | '@type': 'ImageObject',
37 | url: post.coverImage?.url,
38 | },
39 | url: post.url,
40 | keywords: tags,
41 | },
42 | ],
43 | };
44 | return schema;
45 | };
46 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/components/contexts/appContext.tsx:
--------------------------------------------------------------------------------
1 | import React, { createContext, useContext } from 'react';
2 | import {
3 | PostFullFragment,
4 | PublicationFragment,
5 | SeriesPageInitialQuery,
6 | StaticPageFragment,
7 | } from '../../generated/graphql';
8 |
9 | type AppContext = {
10 | publication: PublicationFragment;
11 | post: PostFullFragment | null;
12 | page: StaticPageFragment | null;
13 | series: NonNullable['series'];
14 | };
15 |
16 | const AppContext = createContext(null);
17 |
18 | const AppProvider = ({
19 | children,
20 | publication,
21 | post,
22 | page,
23 | series,
24 | }: {
25 | children: React.ReactNode;
26 | publication: PublicationFragment;
27 | post?: PostFullFragment | null;
28 | page?: StaticPageFragment | null;
29 | series?: NonNullable['series'];
30 | }) => {
31 | return (
32 |
40 | {children}
41 |
42 | );
43 | };
44 |
45 | const useAppContext = () => {
46 | const context = useContext(AppContext);
47 |
48 | if (!context) {
49 | throw new Error('useAppContext must be used within a ');
50 | }
51 |
52 | return context;
53 | };
54 | export { AppProvider, useAppContext };
55 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/components/contexts/appContext.tsx:
--------------------------------------------------------------------------------
1 | import React, { createContext, useContext } from 'react';
2 | import {
3 | PostFullFragment,
4 | PublicationFragment,
5 | SeriesPostsByPublicationQuery,
6 | StaticPageFragment,
7 | } from '../../generated/graphql';
8 |
9 | type AppContext = {
10 | publication: PublicationFragment;
11 | post: PostFullFragment | null;
12 | page: StaticPageFragment | null;
13 | series: NonNullable['series'];
14 | };
15 |
16 | const AppContext = createContext(null);
17 |
18 | const AppProvider = ({
19 | children,
20 | publication,
21 | post,
22 | page,
23 | series,
24 | }: {
25 | children: React.ReactNode;
26 | publication: PublicationFragment;
27 | post?: PostFullFragment | null;
28 | page?: StaticPageFragment | null;
29 | series?: NonNullable['series'];
30 | }) => {
31 | return (
32 |
40 | {children}
41 |
42 | );
43 | };
44 |
45 | const useAppContext = () => {
46 | const context = useContext(AppContext);
47 |
48 | if (!context) {
49 | throw new Error('useAppContext must be used within a ');
50 | }
51 |
52 | return context;
53 | };
54 | export { AppProvider, useAppContext };
55 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/components/header-blog-search.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-nested-ternary */
2 | import { useState, useRef } from 'react';
3 | import dynamic from 'next/dynamic';
4 |
5 | import CommonHeaderIconBtn from './common-header-icon-btn';
6 | import { PublicationFragment } from '../generated/graphql';
7 | import SearchSVG from './icons/svgs/SearchSvg';
8 |
9 | const PublicationSearch = dynamic(() => import('./publication-search'), { ssr: false });
10 |
11 | interface Props {
12 | publication: Pick
13 | }
14 |
15 | const HeaderBlogSearch = (props: Props) => {
16 | const { publication } = props;
17 |
18 | const [isSearchUIVisible, toggleSearchUIState] = useState(false);
19 | const triggerRef = useRef(null);
20 |
21 | const toggleSearchUI = () => {
22 | toggleSearchUIState(!isSearchUIVisible);
23 | };
24 |
25 | return (
26 | <>
27 | {isSearchUIVisible ? (
28 |
29 | ) : null}
30 |
35 |
36 |
37 | >
38 | );
39 | };
40 |
41 | export default HeaderBlogSearch;
42 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/enterprise/pages/rss.xml.tsx:
--------------------------------------------------------------------------------
1 | import { constructRSSFeedFromPosts } from '@starter-kit/utils/feed';
2 | import request from 'graphql-request';
3 | import { GetServerSideProps } from 'next';
4 | import { RssFeedDocument, RssFeedQuery, RssFeedQueryVariables } from '../generated/graphql';
5 |
6 | const GQL_ENDPOINT = process.env.NEXT_PUBLIC_HASHNODE_GQL_ENDPOINT;
7 | const RSS = () => null;
8 |
9 | export const getServerSideProps: GetServerSideProps = async (ctx) => {
10 | const { res, query } = ctx;
11 | const after = query.after ? (query.after as string) : null;
12 |
13 | const data = await request(GQL_ENDPOINT, RssFeedDocument, {
14 | first: 20,
15 | host: process.env.NEXT_PUBLIC_HASHNODE_PUBLICATION_HOST,
16 | after,
17 | });
18 |
19 | const publication = data.publication;
20 | if (!publication) {
21 | return {
22 | notFound: true,
23 | };
24 | }
25 | const allPosts = publication.posts.edges.map((edge) => edge.node);
26 |
27 | const xml = constructRSSFeedFromPosts(
28 | publication,
29 | allPosts,
30 | after,
31 | publication.posts.pageInfo.hasNextPage && publication.posts.pageInfo.endCursor
32 | ? publication.posts.pageInfo.endCursor
33 | : null,
34 | );
35 |
36 | res.setHeader('Cache-Control', 's-maxage=1, stale-while-revalidate');
37 | res.setHeader('content-type', 'text/xml');
38 | res.write(xml);
39 | res.end();
40 |
41 | return { props: {} };
42 | };
43 |
44 | export default RSS;
45 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/pages/rss.xml.tsx:
--------------------------------------------------------------------------------
1 | import { constructRSSFeedFromPosts } from '@starter-kit/utils/feed';
2 | import request from 'graphql-request';
3 | import { GetServerSideProps } from 'next';
4 | import { RssFeedDocument, RssFeedQuery, RssFeedQueryVariables } from '../generated/graphql';
5 |
6 | const GQL_ENDPOINT = process.env.NEXT_PUBLIC_HASHNODE_GQL_ENDPOINT;
7 | const RSS = () => null;
8 |
9 | export const getServerSideProps: GetServerSideProps = async (ctx) => {
10 | const { res, query } = ctx;
11 | const after = query.after ? (query.after as string) : null;
12 |
13 | const data = await request(GQL_ENDPOINT, RssFeedDocument, {
14 | first: 20,
15 | host: process.env.NEXT_PUBLIC_HASHNODE_PUBLICATION_HOST,
16 | after,
17 | });
18 |
19 | const publication = data.publication;
20 | if (!publication) {
21 | return {
22 | notFound: true,
23 | };
24 | }
25 | const allPosts = publication.posts.edges.map((edge) => edge.node);
26 |
27 | const xml = constructRSSFeedFromPosts(
28 | publication,
29 | allPosts,
30 | after,
31 | publication.posts.pageInfo.hasNextPage && publication.posts.pageInfo.endCursor
32 | ? publication.posts.pageInfo.endCursor
33 | : null,
34 | );
35 |
36 | res.setHeader('Cache-Control', 's-maxage=1, stale-while-revalidate');
37 | res.setHeader('content-type', 'text/xml');
38 | res.write(xml);
39 | res.end();
40 |
41 | return { props: {} };
42 | };
43 |
44 | export default RSS;
45 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/pages/rss.xml.tsx:
--------------------------------------------------------------------------------
1 | import { constructRSSFeedFromPosts } from '@starter-kit/utils/feed';
2 | import request from 'graphql-request';
3 | import { GetServerSideProps } from 'next';
4 | import { RssFeedDocument, RssFeedQuery, RssFeedQueryVariables } from '../generated/graphql';
5 |
6 | const GQL_ENDPOINT = process.env.NEXT_PUBLIC_HASHNODE_GQL_ENDPOINT;
7 | const RSS = () => null;
8 |
9 | export const getServerSideProps: GetServerSideProps = async (ctx) => {
10 | const { res, query } = ctx;
11 | const after = query.after ? (query.after as string) : null;
12 |
13 | const data = await request(GQL_ENDPOINT, RssFeedDocument, {
14 | first: 20,
15 | host: process.env.NEXT_PUBLIC_HASHNODE_PUBLICATION_HOST,
16 | after,
17 | });
18 |
19 | const publication = data.publication;
20 | if (!publication) {
21 | return {
22 | notFound: true,
23 | };
24 | }
25 | const allPosts = publication.posts.edges.map((edge) => edge.node);
26 |
27 | const xml = constructRSSFeedFromPosts(
28 | publication,
29 | allPosts,
30 | after,
31 | publication.posts.pageInfo.hasNextPage && publication.posts.pageInfo.endCursor
32 | ? publication.posts.pageInfo.endCursor
33 | : null,
34 | );
35 |
36 | res.setHeader('Cache-Control', 's-maxage=1, stale-while-revalidate');
37 | res.setHeader('content-type', 'text/xml');
38 | res.write(xml);
39 | res.end();
40 |
41 | return { props: {} };
42 | };
43 |
44 | export default RSS;
45 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/components/header-left-sidebar.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable no-nested-ternary */
2 | import dynamic from 'next/dynamic';
3 | import { useState, useRef } from 'react';
4 |
5 | import { PublicationFragment } from '../generated/graphql';
6 | import { BarsSVG } from './icons/svgs';
7 | import CommonHeaderIconBtn from './common-header-icon-btn';
8 |
9 | const PublicationSidebar = dynamic(() => import('./publication-sidebar'), {
10 | ssr: false,
11 | });
12 |
13 | interface Props {
14 | publication: Pick;
15 | }
16 |
17 | const LeftSidebarButton = (props: Props) => {
18 | const { publication } = props;
19 |
20 | const triggerRef = useRef(null);
21 | const [isSidebarVisible, toggleSidebarVisibility] = useState(false);
22 |
23 | const toggleSidebar = () => {
24 | toggleSidebarVisibility(!isSidebarVisible);
25 | };
26 |
27 | return (
28 | <>
29 | {isSidebarVisible ? (
30 |
31 | ) : null}
32 |
37 |
38 |
39 | >
40 | );
41 | };
42 |
43 | export default LeftSidebarButton;
44 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/personal/lib/api/fragments/Publication.graphql:
--------------------------------------------------------------------------------
1 | fragment Publication on Publication {
2 | id
3 | title
4 | displayTitle
5 | url
6 | urlPattern
7 | metaTags
8 | favicon
9 | isTeam
10 | about {
11 | html
12 | }
13 | descriptionSEO
14 | ogMetaData {
15 | image
16 | }
17 | features {
18 | newsletter {
19 | isEnabled
20 | }
21 | readTime {
22 | isEnabled
23 | }
24 | textSelectionSharer {
25 | isEnabled
26 | }
27 | }
28 | author {
29 | id
30 | name
31 | username
32 | profilePicture
33 | tagline
34 | bio {
35 | html
36 | }
37 | socialMediaLinks {
38 | twitter
39 | github
40 | website
41 | linkedin
42 | facebook
43 | youtube
44 | instagram
45 | stackoverflow
46 | }
47 | }
48 | preferences {
49 | logo
50 | darkMode {
51 | logo
52 | }
53 | navbarItems {
54 | id
55 | type
56 | label
57 | url
58 | }
59 | enabledPages {
60 | badges
61 | newsletter
62 | members
63 | }
64 | layout
65 | }
66 | links {
67 | twitter
68 | github
69 | linkedin
70 | website
71 | hashnode
72 | }
73 | integrations {
74 | umamiWebsiteUUID
75 | gaTrackingID
76 | fbPixelID
77 | hotjarSiteID
78 | matomoURL
79 | matomoSiteID
80 | fathomSiteID
81 | fathomCustomDomain
82 | plausibleAnalyticsEnabled
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/packages/blog-starter-kit/themes/hashnode/components/toast.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { twJoin } from 'tailwind-merge';
3 |
4 | import { CheckSVG, CloseSVG, AlertSVG } from './icons/svgs';
5 |
6 | export default class Toast extends React.Component {
7 | constructor(props, context) {
8 | super(props, context);
9 | this.state = {};
10 | this.classNameMap = {
11 | success: `bg-green-600`,
12 | error: `bg-red-600`,
13 | warning: `bg-yellow-500`,
14 | };
15 | }
16 |
17 | render() {
18 | const { type, title, message, closeToast } = this.props;
19 | return (
20 |
27 |
28 | {type === 'success' &&
}
29 | {type === 'error' &&
}
30 | {type === 'warning' &&
}
31 |
32 |
33 |
{title}
34 | {message &&
}
35 |
36 |
37 | );
38 | }
39 | }
40 |
41 | export { Toast };
42 |
--------------------------------------------------------------------------------