├── .prettierrc ├── .husky └── pre-commit ├── public ├── icon.jpeg ├── sns │ ├── x.webp │ ├── discord.png │ └── github.png ├── notion-data │ ├── 126ce18c-fd83-80a5-8260-d757c56405b2 │ │ ├── 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 │ │ ├── image_10.png │ │ ├── image_11.png │ │ ├── image_12.png │ │ ├── image_13.png │ │ ├── image_14.png │ │ ├── image_15.png │ │ ├── image_16.png │ │ ├── image_17.png │ │ ├── image_18.png │ │ ├── image_19.png │ │ ├── image_20.png │ │ ├── image_21.png │ │ ├── image_22.png │ │ ├── image_23.png │ │ ├── image_24.png │ │ ├── image_25.png │ │ ├── image_26.png │ │ ├── image_27.png │ │ ├── image_28.png │ │ └── image_29.png │ ├── 127ce18c-fd83-805c-bebd-d6772e18bf02 │ │ ├── image_1.gif │ │ ├── image_2.gif │ │ ├── image_3.gif │ │ ├── image_4.gif │ │ ├── image_5.gif │ │ ├── image_6.gif │ │ └── image_7.gif │ └── 12ace18c-fd83-8071-b3a5-dd8d21da61cf │ │ ├── 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 │ │ ├── image_10.png │ │ ├── image_11.png │ │ ├── image_12.png │ │ ├── image_13.png │ │ ├── image_14.png │ │ ├── image_15.png │ │ ├── image_16.png │ │ ├── image_17.png │ │ ├── image_18.png │ │ ├── image_19.png │ │ ├── image_20.png │ │ ├── image_21.png │ │ ├── image_22.png │ │ ├── image_23.png │ │ ├── image_24.png │ │ ├── image_25.png │ │ ├── image_26.png │ │ ├── image_27.png │ │ ├── image_28.png │ │ └── image_29.png ├── robots.txt └── sitemap.xml ├── app ├── fonts │ ├── Pretendard-Bold.woff │ ├── Pretendard-Thin.woff │ ├── Pretendard-Regular.woff │ └── Pretendard-SemiBold.woff ├── [lang] │ ├── page.tsx │ ├── showcase │ │ └── page.tsx │ ├── contributing │ │ └── page.tsx │ ├── tutorial │ │ └── page.tsx │ ├── docs │ │ └── [group] │ │ │ └── [slug] │ │ │ └── page.tsx │ └── layout.tsx ├── layout.tsx └── globals.css ├── components ├── showcase │ ├── assets │ │ ├── chapdo-blog.png │ │ └── modern-tech.png │ └── index.tsx ├── theme-provider.tsx ├── docs │ ├── index.ts │ ├── toc.tsx │ ├── dynamic-layout.tsx │ ├── navigation-button.tsx │ ├── sidebar.tsx │ ├── docs-layout.tsx │ └── mobile-sidebar.tsx ├── notion-renderer.tsx ├── ui │ ├── footer.tsx │ └── language-selector.tsx └── landing │ └── hero.tsx ├── postcss.config.mjs ├── lib ├── utils.ts ├── generateTOC.ts ├── prevent-scrollbar.ts └── mdx.ts ├── i18n ├── generate-static-params.ts ├── use-current-language.ts ├── internal │ ├── interpolate.ts │ ├── client-translations-provider.tsx │ └── load-translations.ts ├── supported-languages.ts ├── translations-provider.tsx ├── get-preferred-language.ts ├── messages │ ├── types.ts │ ├── ko.ts │ ├── cn.ts │ ├── ja.ts │ └── en.ts ├── get-server-translations.ts ├── get-server-current-language.ts ├── index.ts └── use-translations.ts ├── next.config.mjs ├── next-sitemap.config.js ├── hooks ├── usePreventScroll.ts ├── useIsMobile.ts └── useClickOutside.ts ├── constants ├── constants.ts └── group.ts ├── .gitignore ├── tsconfig.json ├── middleware.ts ├── content └── guide │ ├── cn │ ├── 01. getting-started │ │ └── 01. introduction.md │ ├── 03. block-types │ │ ├── 07. paragraph.md │ │ ├── 06. quote.md │ │ ├── 02. heading.md │ │ ├── 03. bulleted-list-item.md │ │ ├── 05. numbered-list-item.md │ │ ├── 08. callout.md │ │ └── 09. toggle.md │ └── 02. customization-guide │ │ └── 03. custom-style.md │ ├── ja │ ├── 03. block-types │ │ ├── 07. paragraph.md │ │ ├── 06. quote.md │ │ ├── 02. heading.md │ │ ├── 03. bulleted-list-item.md │ │ ├── 05. numbered-list-item.md │ │ ├── 08. callout.md │ │ └── 09. toggle.md │ ├── 01. getting-started │ │ └── 01. introduction.md │ └── 02. customization-guide │ │ └── 03. custom-style.md │ ├── ko │ ├── 03. block-types │ │ ├── 07. Paragraph.md │ │ ├── 06. Quote.md │ │ ├── 02. heading.md │ │ ├── 03. bulleted-list-item.md │ │ ├── 08. Callout.md │ │ ├── 05. numbered-list-item.md │ │ └── 09. Toggle.md │ ├── 01. getting-started │ │ └── 01. introduction.md │ └── 02. customization-guide │ │ └── 03. custom-style.md │ └── en │ └── 03. block-types │ ├── 07. Paragraph.md │ ├── 02. heading.md │ ├── 06. Quote.md │ ├── 03. bulleted-list-item.md │ └── 05. numbered-list-item.md ├── package.json ├── CONTRIBUTING-KR.md ├── README.md ├── tailwind.config.ts └── CONTRIBUTING.md /.prettierrc: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | npx lint-staged -------------------------------------------------------------------------------- /public/icon.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/public/icon.jpeg -------------------------------------------------------------------------------- /public/sns/x.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/public/sns/x.webp -------------------------------------------------------------------------------- /public/sns/discord.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/public/sns/discord.png -------------------------------------------------------------------------------- /public/sns/github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/public/sns/github.png -------------------------------------------------------------------------------- /app/fonts/Pretendard-Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/app/fonts/Pretendard-Bold.woff -------------------------------------------------------------------------------- /app/fonts/Pretendard-Thin.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/app/fonts/Pretendard-Thin.woff -------------------------------------------------------------------------------- /app/fonts/Pretendard-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/app/fonts/Pretendard-Regular.woff -------------------------------------------------------------------------------- /app/fonts/Pretendard-SemiBold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/app/fonts/Pretendard-SemiBold.woff -------------------------------------------------------------------------------- /components/showcase/assets/chapdo-blog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/components/showcase/assets/chapdo-blog.png -------------------------------------------------------------------------------- /components/showcase/assets/modern-tech.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/components/showcase/assets/modern-tech.png -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /public/notion-data/126ce18c-fd83-80a5-8260-d757c56405b2/image_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/public/notion-data/126ce18c-fd83-80a5-8260-d757c56405b2/image_1.png -------------------------------------------------------------------------------- /public/notion-data/126ce18c-fd83-80a5-8260-d757c56405b2/image_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/public/notion-data/126ce18c-fd83-80a5-8260-d757c56405b2/image_2.png -------------------------------------------------------------------------------- /public/notion-data/126ce18c-fd83-80a5-8260-d757c56405b2/image_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/public/notion-data/126ce18c-fd83-80a5-8260-d757c56405b2/image_3.png -------------------------------------------------------------------------------- /public/notion-data/126ce18c-fd83-80a5-8260-d757c56405b2/image_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/public/notion-data/126ce18c-fd83-80a5-8260-d757c56405b2/image_4.png -------------------------------------------------------------------------------- /public/notion-data/126ce18c-fd83-80a5-8260-d757c56405b2/image_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/public/notion-data/126ce18c-fd83-80a5-8260-d757c56405b2/image_5.png -------------------------------------------------------------------------------- /public/notion-data/126ce18c-fd83-80a5-8260-d757c56405b2/image_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/public/notion-data/126ce18c-fd83-80a5-8260-d757c56405b2/image_6.png -------------------------------------------------------------------------------- /public/notion-data/126ce18c-fd83-80a5-8260-d757c56405b2/image_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/public/notion-data/126ce18c-fd83-80a5-8260-d757c56405b2/image_7.png -------------------------------------------------------------------------------- /public/notion-data/126ce18c-fd83-80a5-8260-d757c56405b2/image_8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/public/notion-data/126ce18c-fd83-80a5-8260-d757c56405b2/image_8.png -------------------------------------------------------------------------------- /public/notion-data/126ce18c-fd83-80a5-8260-d757c56405b2/image_9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/public/notion-data/126ce18c-fd83-80a5-8260-d757c56405b2/image_9.png -------------------------------------------------------------------------------- /public/notion-data/127ce18c-fd83-805c-bebd-d6772e18bf02/image_1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/public/notion-data/127ce18c-fd83-805c-bebd-d6772e18bf02/image_1.gif -------------------------------------------------------------------------------- /public/notion-data/127ce18c-fd83-805c-bebd-d6772e18bf02/image_2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/public/notion-data/127ce18c-fd83-805c-bebd-d6772e18bf02/image_2.gif -------------------------------------------------------------------------------- /public/notion-data/127ce18c-fd83-805c-bebd-d6772e18bf02/image_3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/public/notion-data/127ce18c-fd83-805c-bebd-d6772e18bf02/image_3.gif -------------------------------------------------------------------------------- /public/notion-data/127ce18c-fd83-805c-bebd-d6772e18bf02/image_4.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/public/notion-data/127ce18c-fd83-805c-bebd-d6772e18bf02/image_4.gif -------------------------------------------------------------------------------- /public/notion-data/127ce18c-fd83-805c-bebd-d6772e18bf02/image_5.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/public/notion-data/127ce18c-fd83-805c-bebd-d6772e18bf02/image_5.gif -------------------------------------------------------------------------------- /public/notion-data/127ce18c-fd83-805c-bebd-d6772e18bf02/image_6.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/public/notion-data/127ce18c-fd83-805c-bebd-d6772e18bf02/image_6.gif -------------------------------------------------------------------------------- /public/notion-data/127ce18c-fd83-805c-bebd-d6772e18bf02/image_7.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/public/notion-data/127ce18c-fd83-805c-bebd-d6772e18bf02/image_7.gif -------------------------------------------------------------------------------- /public/notion-data/12ace18c-fd83-8071-b3a5-dd8d21da61cf/image_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/public/notion-data/12ace18c-fd83-8071-b3a5-dd8d21da61cf/image_1.png -------------------------------------------------------------------------------- /public/notion-data/12ace18c-fd83-8071-b3a5-dd8d21da61cf/image_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/public/notion-data/12ace18c-fd83-8071-b3a5-dd8d21da61cf/image_2.png -------------------------------------------------------------------------------- /public/notion-data/12ace18c-fd83-8071-b3a5-dd8d21da61cf/image_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/public/notion-data/12ace18c-fd83-8071-b3a5-dd8d21da61cf/image_3.png -------------------------------------------------------------------------------- /public/notion-data/12ace18c-fd83-8071-b3a5-dd8d21da61cf/image_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/public/notion-data/12ace18c-fd83-8071-b3a5-dd8d21da61cf/image_4.png -------------------------------------------------------------------------------- /public/notion-data/12ace18c-fd83-8071-b3a5-dd8d21da61cf/image_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/public/notion-data/12ace18c-fd83-8071-b3a5-dd8d21da61cf/image_5.png -------------------------------------------------------------------------------- /public/notion-data/12ace18c-fd83-8071-b3a5-dd8d21da61cf/image_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/public/notion-data/12ace18c-fd83-8071-b3a5-dd8d21da61cf/image_6.png -------------------------------------------------------------------------------- /public/notion-data/12ace18c-fd83-8071-b3a5-dd8d21da61cf/image_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/public/notion-data/12ace18c-fd83-8071-b3a5-dd8d21da61cf/image_7.png -------------------------------------------------------------------------------- /public/notion-data/12ace18c-fd83-8071-b3a5-dd8d21da61cf/image_8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/public/notion-data/12ace18c-fd83-8071-b3a5-dd8d21da61cf/image_8.png -------------------------------------------------------------------------------- /public/notion-data/12ace18c-fd83-8071-b3a5-dd8d21da61cf/image_9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/public/notion-data/12ace18c-fd83-8071-b3a5-dd8d21da61cf/image_9.png -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # * 2 | User-agent: * 3 | Allow: / 4 | 5 | # Host 6 | Host: https://notionpresso.com 7 | 8 | # Sitemaps 9 | Sitemap: https://notionpresso.com/sitemap.xml 10 | -------------------------------------------------------------------------------- /public/notion-data/126ce18c-fd83-80a5-8260-d757c56405b2/image_10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/public/notion-data/126ce18c-fd83-80a5-8260-d757c56405b2/image_10.png -------------------------------------------------------------------------------- /public/notion-data/126ce18c-fd83-80a5-8260-d757c56405b2/image_11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/public/notion-data/126ce18c-fd83-80a5-8260-d757c56405b2/image_11.png -------------------------------------------------------------------------------- /public/notion-data/126ce18c-fd83-80a5-8260-d757c56405b2/image_12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/public/notion-data/126ce18c-fd83-80a5-8260-d757c56405b2/image_12.png -------------------------------------------------------------------------------- /public/notion-data/126ce18c-fd83-80a5-8260-d757c56405b2/image_13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/public/notion-data/126ce18c-fd83-80a5-8260-d757c56405b2/image_13.png -------------------------------------------------------------------------------- /public/notion-data/126ce18c-fd83-80a5-8260-d757c56405b2/image_14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/public/notion-data/126ce18c-fd83-80a5-8260-d757c56405b2/image_14.png -------------------------------------------------------------------------------- /public/notion-data/126ce18c-fd83-80a5-8260-d757c56405b2/image_15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/public/notion-data/126ce18c-fd83-80a5-8260-d757c56405b2/image_15.png -------------------------------------------------------------------------------- /public/notion-data/126ce18c-fd83-80a5-8260-d757c56405b2/image_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/public/notion-data/126ce18c-fd83-80a5-8260-d757c56405b2/image_16.png -------------------------------------------------------------------------------- /public/notion-data/126ce18c-fd83-80a5-8260-d757c56405b2/image_17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/public/notion-data/126ce18c-fd83-80a5-8260-d757c56405b2/image_17.png -------------------------------------------------------------------------------- /public/notion-data/126ce18c-fd83-80a5-8260-d757c56405b2/image_18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/public/notion-data/126ce18c-fd83-80a5-8260-d757c56405b2/image_18.png -------------------------------------------------------------------------------- /public/notion-data/126ce18c-fd83-80a5-8260-d757c56405b2/image_19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/public/notion-data/126ce18c-fd83-80a5-8260-d757c56405b2/image_19.png -------------------------------------------------------------------------------- /public/notion-data/126ce18c-fd83-80a5-8260-d757c56405b2/image_20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/public/notion-data/126ce18c-fd83-80a5-8260-d757c56405b2/image_20.png -------------------------------------------------------------------------------- /public/notion-data/126ce18c-fd83-80a5-8260-d757c56405b2/image_21.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/public/notion-data/126ce18c-fd83-80a5-8260-d757c56405b2/image_21.png -------------------------------------------------------------------------------- /public/notion-data/126ce18c-fd83-80a5-8260-d757c56405b2/image_22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/public/notion-data/126ce18c-fd83-80a5-8260-d757c56405b2/image_22.png -------------------------------------------------------------------------------- /public/notion-data/126ce18c-fd83-80a5-8260-d757c56405b2/image_23.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/public/notion-data/126ce18c-fd83-80a5-8260-d757c56405b2/image_23.png -------------------------------------------------------------------------------- /public/notion-data/126ce18c-fd83-80a5-8260-d757c56405b2/image_24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/public/notion-data/126ce18c-fd83-80a5-8260-d757c56405b2/image_24.png -------------------------------------------------------------------------------- /public/notion-data/126ce18c-fd83-80a5-8260-d757c56405b2/image_25.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/public/notion-data/126ce18c-fd83-80a5-8260-d757c56405b2/image_25.png -------------------------------------------------------------------------------- /public/notion-data/126ce18c-fd83-80a5-8260-d757c56405b2/image_26.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/public/notion-data/126ce18c-fd83-80a5-8260-d757c56405b2/image_26.png -------------------------------------------------------------------------------- /public/notion-data/126ce18c-fd83-80a5-8260-d757c56405b2/image_27.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/public/notion-data/126ce18c-fd83-80a5-8260-d757c56405b2/image_27.png -------------------------------------------------------------------------------- /public/notion-data/126ce18c-fd83-80a5-8260-d757c56405b2/image_28.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/public/notion-data/126ce18c-fd83-80a5-8260-d757c56405b2/image_28.png -------------------------------------------------------------------------------- /public/notion-data/126ce18c-fd83-80a5-8260-d757c56405b2/image_29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/public/notion-data/126ce18c-fd83-80a5-8260-d757c56405b2/image_29.png -------------------------------------------------------------------------------- /public/notion-data/12ace18c-fd83-8071-b3a5-dd8d21da61cf/image_10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/public/notion-data/12ace18c-fd83-8071-b3a5-dd8d21da61cf/image_10.png -------------------------------------------------------------------------------- /public/notion-data/12ace18c-fd83-8071-b3a5-dd8d21da61cf/image_11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/public/notion-data/12ace18c-fd83-8071-b3a5-dd8d21da61cf/image_11.png -------------------------------------------------------------------------------- /public/notion-data/12ace18c-fd83-8071-b3a5-dd8d21da61cf/image_12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/public/notion-data/12ace18c-fd83-8071-b3a5-dd8d21da61cf/image_12.png -------------------------------------------------------------------------------- /public/notion-data/12ace18c-fd83-8071-b3a5-dd8d21da61cf/image_13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/public/notion-data/12ace18c-fd83-8071-b3a5-dd8d21da61cf/image_13.png -------------------------------------------------------------------------------- /public/notion-data/12ace18c-fd83-8071-b3a5-dd8d21da61cf/image_14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/public/notion-data/12ace18c-fd83-8071-b3a5-dd8d21da61cf/image_14.png -------------------------------------------------------------------------------- /public/notion-data/12ace18c-fd83-8071-b3a5-dd8d21da61cf/image_15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/public/notion-data/12ace18c-fd83-8071-b3a5-dd8d21da61cf/image_15.png -------------------------------------------------------------------------------- /public/notion-data/12ace18c-fd83-8071-b3a5-dd8d21da61cf/image_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/public/notion-data/12ace18c-fd83-8071-b3a5-dd8d21da61cf/image_16.png -------------------------------------------------------------------------------- /public/notion-data/12ace18c-fd83-8071-b3a5-dd8d21da61cf/image_17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/public/notion-data/12ace18c-fd83-8071-b3a5-dd8d21da61cf/image_17.png -------------------------------------------------------------------------------- /public/notion-data/12ace18c-fd83-8071-b3a5-dd8d21da61cf/image_18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/public/notion-data/12ace18c-fd83-8071-b3a5-dd8d21da61cf/image_18.png -------------------------------------------------------------------------------- /public/notion-data/12ace18c-fd83-8071-b3a5-dd8d21da61cf/image_19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/public/notion-data/12ace18c-fd83-8071-b3a5-dd8d21da61cf/image_19.png -------------------------------------------------------------------------------- /public/notion-data/12ace18c-fd83-8071-b3a5-dd8d21da61cf/image_20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/public/notion-data/12ace18c-fd83-8071-b3a5-dd8d21da61cf/image_20.png -------------------------------------------------------------------------------- /public/notion-data/12ace18c-fd83-8071-b3a5-dd8d21da61cf/image_21.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/public/notion-data/12ace18c-fd83-8071-b3a5-dd8d21da61cf/image_21.png -------------------------------------------------------------------------------- /public/notion-data/12ace18c-fd83-8071-b3a5-dd8d21da61cf/image_22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/public/notion-data/12ace18c-fd83-8071-b3a5-dd8d21da61cf/image_22.png -------------------------------------------------------------------------------- /public/notion-data/12ace18c-fd83-8071-b3a5-dd8d21da61cf/image_23.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/public/notion-data/12ace18c-fd83-8071-b3a5-dd8d21da61cf/image_23.png -------------------------------------------------------------------------------- /public/notion-data/12ace18c-fd83-8071-b3a5-dd8d21da61cf/image_24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/public/notion-data/12ace18c-fd83-8071-b3a5-dd8d21da61cf/image_24.png -------------------------------------------------------------------------------- /public/notion-data/12ace18c-fd83-8071-b3a5-dd8d21da61cf/image_25.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/public/notion-data/12ace18c-fd83-8071-b3a5-dd8d21da61cf/image_25.png -------------------------------------------------------------------------------- /public/notion-data/12ace18c-fd83-8071-b3a5-dd8d21da61cf/image_26.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/public/notion-data/12ace18c-fd83-8071-b3a5-dd8d21da61cf/image_26.png -------------------------------------------------------------------------------- /public/notion-data/12ace18c-fd83-8071-b3a5-dd8d21da61cf/image_27.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/public/notion-data/12ace18c-fd83-8071-b3a5-dd8d21da61cf/image_27.png -------------------------------------------------------------------------------- /public/notion-data/12ace18c-fd83-8071-b3a5-dd8d21da61cf/image_28.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/public/notion-data/12ace18c-fd83-8071-b3a5-dd8d21da61cf/image_28.png -------------------------------------------------------------------------------- /public/notion-data/12ace18c-fd83-8071-b3a5-dd8d21da61cf/image_29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/notionpresso/docs/HEAD/public/notion-data/12ace18c-fd83-8071-b3a5-dd8d21da61cf/image_29.png -------------------------------------------------------------------------------- /lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { type ClassValue, clsx } from "clsx"; 2 | import { twMerge } from "tailwind-merge"; 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)); 6 | } 7 | -------------------------------------------------------------------------------- /i18n/generate-static-params.ts: -------------------------------------------------------------------------------- 1 | import { SUPPORTED_LANGUAGES } from "./supported-languages"; 2 | 3 | export const generateStaticParams = () => { 4 | return SUPPORTED_LANGUAGES.map((lang) => ({ lang })); 5 | }; 6 | -------------------------------------------------------------------------------- /public/sitemap.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | https://notionpresso.com/sitemap-0.xml 4 | -------------------------------------------------------------------------------- /app/[lang]/page.tsx: -------------------------------------------------------------------------------- 1 | import { LandingHero } from "@/components/landing/hero"; 2 | 3 | export const runtime = "edge"; 4 | 5 | export { generateStaticParams } from "@/i18n"; 6 | 7 | export default function Home() { 8 | return ; 9 | } 10 | -------------------------------------------------------------------------------- /app/[lang]/showcase/page.tsx: -------------------------------------------------------------------------------- 1 | import Showcase from "@/components/showcase"; 2 | 3 | export const runtime = "edge"; 4 | 5 | export { generateStaticParams } from "@/i18n"; 6 | 7 | export default function ShowcasePage() { 8 | return ; 9 | } 10 | -------------------------------------------------------------------------------- /i18n/use-current-language.ts: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useContext } from "react"; 4 | import { TranslationsContext } from "./internal/client-translations-provider"; 5 | 6 | export function useCurrentLanguage() { 7 | const { lang } = useContext(TranslationsContext); 8 | return lang; 9 | } 10 | -------------------------------------------------------------------------------- /components/theme-provider.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { ThemeProvider as NextThemesProvider } from "next-themes"; 4 | import { type ThemeProviderProps } from "next-themes/dist/types"; 5 | 6 | export function ThemeProvider({ children, ...props }: ThemeProviderProps) { 7 | return {children}; 8 | } 9 | -------------------------------------------------------------------------------- /next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | async redirects() { 4 | return [ 5 | { 6 | source: '/:lang/docs', 7 | destination: '/:lang/docs/getting-started/introduction', 8 | permanent: true, 9 | } 10 | ] 11 | } 12 | }; 13 | 14 | export default nextConfig; 15 | -------------------------------------------------------------------------------- /components/docs/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Sidebar } from "./sidebar"; 2 | export { default as TOC } from "./toc"; 3 | export { default as DynamicLayout } from "./dynamic-layout"; 4 | export { default as MobileSidebar } from "./mobile-sidebar"; 5 | export { default as NavigationButton } from "./navigation-button"; 6 | export { default as DocsLayout } from "./docs-layout"; 7 | -------------------------------------------------------------------------------- /i18n/internal/interpolate.ts: -------------------------------------------------------------------------------- 1 | export function interpolate( 2 | template: string, 3 | variables: { [key: string]: string | number }, 4 | ): string { 5 | return template.replace(/\{(\w+)\}/g, (match, key) => { 6 | if (variables.hasOwnProperty(key)) { 7 | return String(variables[key]); 8 | } else { 9 | return match; // 변수에 해당하는 키가 없을 경우 원래 템플릿을 유지합니다. 10 | } 11 | }); 12 | } 13 | -------------------------------------------------------------------------------- /next-sitemap.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next-sitemap').IConfig} */ 2 | module.exports = { 3 | siteUrl: "https://notionpresso.com", 4 | generateRobotsTxt: true, 5 | // additionalPaths: [ 6 | // { 7 | // href: 'https://notionpresso.com/ko', 8 | // hrefLang: 'ko', 9 | // }, 10 | // { 11 | // href: 'https://notionpresso.com/en', 12 | // hrefLang: 'en', 13 | // }, 14 | // ] 15 | }; 16 | -------------------------------------------------------------------------------- /hooks/usePreventScroll.ts: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | import preventScroll from "@/lib/prevent-scrollbar"; 3 | 4 | interface UsePreventScrollProps { 5 | isOpen: boolean; 6 | } 7 | 8 | const usePreventScroll = ({ isOpen }: UsePreventScrollProps) => { 9 | useEffect(() => { 10 | preventScroll(isOpen); 11 | 12 | return () => { 13 | preventScroll(false); 14 | }; 15 | }, [isOpen]); 16 | }; 17 | 18 | export default usePreventScroll; 19 | -------------------------------------------------------------------------------- /constants/constants.ts: -------------------------------------------------------------------------------- 1 | export const SNS_LIST = [ 2 | { 3 | id: 1, 4 | name: "Github", 5 | url: "https://github.com/notionpresso", 6 | imgUrl: "/sns/github.png", 7 | }, 8 | { 9 | id: 2, 10 | name: "X", 11 | url: "https://x.com/notionpres81989", 12 | imgUrl: "/sns/x.webp", 13 | }, 14 | { 15 | id: 3, 16 | name: "Discord", 17 | url: "https://discord.gg/yAjKwDb7xR", 18 | imgUrl: "/sns/discord.png", 19 | }, 20 | ] as const; 21 | -------------------------------------------------------------------------------- /constants/group.ts: -------------------------------------------------------------------------------- 1 | export const GROUPS = [ 2 | { id: "getting-started", name: "Getting Started", order: "01" }, 3 | { id: "customization-guide", name: "Customization Guide", order: "02" }, 4 | { id: "block-types", name: "Block Types", order: "03" }, 5 | ] as const; 6 | 7 | export type GroupId = (typeof GROUPS)[number]["id"]; 8 | 9 | export function getGroupOrder(groupId: GroupId): string { 10 | const group = GROUPS.find((g) => g.id === groupId); 11 | return group ? group.order : "00"; 12 | } 13 | -------------------------------------------------------------------------------- /components/notion-renderer.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { Notion } from "@notionpresso/react"; 4 | 5 | export const NotionRenderer = ({ 6 | blocks, 7 | title, 8 | cover, 9 | }: { 10 | blocks: any[]; 11 | title: string; 12 | cover?: string; 13 | }) => { 14 | return ( 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | ); 23 | }; 24 | -------------------------------------------------------------------------------- /lib/generateTOC.ts: -------------------------------------------------------------------------------- 1 | export interface TOCItem { 2 | id: string; 3 | title: string; 4 | } 5 | 6 | export function generateTOC(content: string): TOCItem[] { 7 | const lines = content.split("\n"); 8 | const toc: TOCItem[] = []; 9 | 10 | lines.forEach((line) => { 11 | if (line.startsWith("

")) { 12 | const title = line.slice(4, -5).trim(); 13 | const id = title.toLowerCase().replace(/[^a-z0-9가-힣]+/g, "-"); 14 | toc.push({ id, title }); 15 | } 16 | }); 17 | 18 | return toc; 19 | } 20 | -------------------------------------------------------------------------------- /i18n/supported-languages.ts: -------------------------------------------------------------------------------- 1 | export type SupportedLanguage = (typeof SUPPORTED_LANGUAGES)[number]; 2 | 3 | export const DEFAULT_LANGUAGE = "en"; 4 | 5 | export const LANGUAGE_LIST = [ 6 | { 7 | title: "English", 8 | locale: "en", 9 | }, 10 | { 11 | title: "한국어", 12 | locale: "ko", 13 | }, 14 | { 15 | title: "简体中文", 16 | locale: "cn", 17 | }, 18 | { 19 | title: "日本語", 20 | locale: "ja", 21 | }, 22 | ]; 23 | 24 | export const SUPPORTED_LANGUAGES = LANGUAGE_LIST.map( 25 | (language) => language.locale, 26 | ); 27 | -------------------------------------------------------------------------------- /i18n/translations-provider.tsx: -------------------------------------------------------------------------------- 1 | "use server"; 2 | import { ClientTranslationsProvider } from "./internal/client-translations-provider"; 3 | import { loadTranslations } from "./internal/load-translations"; 4 | 5 | export default async function TranslationsProvider({ 6 | children, 7 | lang, 8 | }: { 9 | children: React.ReactNode; 10 | lang: string; 11 | }) { 12 | const messages = await loadTranslations(lang); 13 | return ( 14 | 15 | {children} 16 | 17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /.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 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | 38 | # husky 39 | .husky/_ 40 | -------------------------------------------------------------------------------- /hooks/useIsMobile.ts: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useState, useEffect } from "react"; 4 | 5 | const useIsMobile = (options?: { default?: boolean }) => { 6 | const [mobile, setMobile] = useState(options?.default ?? false); 7 | 8 | useEffect(() => { 9 | const checkIsMobile = () => { 10 | setMobile(window.innerWidth < 768); 11 | }; 12 | 13 | checkIsMobile(); 14 | window.addEventListener("resize", checkIsMobile); 15 | 16 | return () => window.removeEventListener("resize", checkIsMobile); 17 | }, []); 18 | 19 | return mobile; 20 | }; 21 | 22 | export default useIsMobile; 23 | -------------------------------------------------------------------------------- /i18n/get-preferred-language.ts: -------------------------------------------------------------------------------- 1 | import { NextRequest } from "next/server"; 2 | import { DEFAULT_LANGUAGE, SUPPORTED_LANGUAGES } from "./supported-languages"; 3 | 4 | export function getPreferredLanguage(request: NextRequest): string { 5 | const acceptLanguage = request.headers.get("accept-language"); 6 | if (!acceptLanguage) return DEFAULT_LANGUAGE; 7 | 8 | const langs = acceptLanguage.split(",").map((lang) => lang.split(";")[0]); 9 | for (const lang of langs) { 10 | const shortLang = lang.slice(0, 2).toLowerCase(); 11 | if (SUPPORTED_LANGUAGES.includes(shortLang)) { 12 | return shortLang; 13 | } 14 | } 15 | return DEFAULT_LANGUAGE; 16 | } 17 | -------------------------------------------------------------------------------- /hooks/useClickOutside.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from "react"; 2 | 3 | export function useClickOutside( 4 | handler: () => void, 5 | ) { 6 | const ref = useRef(null); 7 | 8 | useEffect(() => { 9 | const handleClickOutside = (event: MouseEvent) => { 10 | if (ref.current && !ref.current.contains(event.target as Node)) { 11 | handler(); 12 | } 13 | }; 14 | 15 | document.addEventListener("mousedown", handleClickOutside); 16 | return () => document.removeEventListener("mousedown", handleClickOutside); 17 | }, [handler]); 18 | 19 | return ref; 20 | } 21 | 22 | export default useClickOutside; 23 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "lib": ["dom", "dom.iterable", "esnext"], 4 | "allowJs": true, 5 | "skipLibCheck": true, 6 | "strict": true, 7 | "noEmit": true, 8 | "esModuleInterop": true, 9 | "module": "esnext", 10 | "moduleResolution": "bundler", 11 | "resolveJsonModule": true, 12 | "isolatedModules": true, 13 | "jsx": "preserve", 14 | "incremental": true, 15 | "plugins": [ 16 | { 17 | "name": "next" 18 | } 19 | ], 20 | "paths": { 21 | "@/*": ["./*"] 22 | } 23 | }, 24 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 25 | "exclude": ["node_modules"] 26 | } 27 | -------------------------------------------------------------------------------- /i18n/messages/types.ts: -------------------------------------------------------------------------------- 1 | export type Messages = { 2 | header: { 3 | home: string; 4 | blog: string; 5 | github: string; 6 | docs: string; 7 | tutorial: string; 8 | contributing: string; 9 | showcase: string; 10 | }; 11 | home: { 12 | title: string; 13 | getStarted: string; 14 | readMore: string; 15 | }; 16 | metadata: { 17 | title: string; 18 | description: string; 19 | keywords: string[]; 20 | og: { 21 | title: string; 22 | description: string; 23 | siteName: string; 24 | imageAlt: string; 25 | }; 26 | twitter: { 27 | title: string; 28 | description: string; 29 | imageAlt: string; 30 | }; 31 | }; 32 | }; 33 | -------------------------------------------------------------------------------- /components/docs/toc.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | 3 | interface TOCItem { 4 | id: string; 5 | title: string; 6 | } 7 | 8 | interface TOCProps { 9 | items: TOCItem[]; 10 | } 11 | 12 | export default function TOC({ items }: TOCProps) { 13 | return ( 14 | 28 | ); 29 | } 30 | -------------------------------------------------------------------------------- /i18n/get-server-translations.ts: -------------------------------------------------------------------------------- 1 | import { Messages } from "./messages/types"; 2 | import { loadTranslations } from "./internal/load-translations"; 3 | import { interpolate } from "./internal/interpolate"; 4 | import { getServerCurrentLanguage } from "./get-server-current-language"; 5 | 6 | type Variables = { [key: string]: string | number }; 7 | 8 | export async function getServerTranslations(namespace: keyof Messages) { 9 | let lang = getServerCurrentLanguage(); 10 | const messages = await loadTranslations(lang); 11 | 12 | function t(key: string, variables?: Variables): string { 13 | const template = (messages[namespace] as any)?.[key] || ""; 14 | 15 | if (variables) { 16 | return interpolate(template, variables); 17 | } else { 18 | return template; 19 | } 20 | } 21 | 22 | return t; 23 | } 24 | -------------------------------------------------------------------------------- /i18n/internal/client-translations-provider.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import React, { createContext } from "react"; 4 | import { Messages } from "../messages/types"; 5 | 6 | interface TranslationsContextType { 7 | messages: Messages; 8 | lang: string; 9 | } 10 | 11 | export const TranslationsContext = createContext({ 12 | messages: {} as Messages, 13 | lang: "", 14 | }); 15 | 16 | interface TranslationsProviderProps { 17 | messages: Messages; 18 | lang: string; 19 | children: React.ReactNode; 20 | } 21 | 22 | export const ClientTranslationsProvider: React.FC< 23 | TranslationsProviderProps 24 | > = ({ messages, lang, children }) => { 25 | return ( 26 | 27 | {children} 28 | 29 | ); 30 | }; 31 | -------------------------------------------------------------------------------- /app/[lang]/contributing/page.tsx: -------------------------------------------------------------------------------- 1 | import { NotionRenderer } from "@/components/notion-renderer"; 2 | //TODO: dynamic import is needed for better performance (don't need to import all the content at once) 3 | import * as ko from "@/content/contributing/ko/130ce18c-fd83-8040-8015-eaa450df6523.json"; 4 | import * as en from "@/content/contributing/en/130ce18c-fd83-8017-9202-fdf73b9f1c9c.json"; 5 | 6 | export const runtime = "edge"; 7 | 8 | export { generateStaticParams } from "@/i18n"; 9 | 10 | export default function TutorialPage({ params }: { params: { lang: string } }) { 11 | const content = params.lang === "ko" ? ko : en; 12 | const title = content.properties.title.title[0].plain_text; 13 | 14 | return ( 15 |
16 | 17 |
18 | ); 19 | } 20 | -------------------------------------------------------------------------------- /i18n/get-server-current-language.ts: -------------------------------------------------------------------------------- 1 | // i18n/get-current-language.ts 2 | 3 | import { SUPPORTED_LANGUAGES, DEFAULT_LANGUAGE } from "./supported-languages"; 4 | 5 | import { staticGenerationAsyncStorage } from "next/dist/client/components/static-generation-async-storage.external"; 6 | 7 | export function getServerCurrentLanguage(): string { 8 | let lang = DEFAULT_LANGUAGE; // 기본 언어 설정 9 | 10 | if (typeof window === "undefined") { 11 | const store = staticGenerationAsyncStorage.getStore(); 12 | const pathname = store?.urlPathname; 13 | 14 | if (pathname) { 15 | // 경로명에서 언어 코드 추출 (예: /ko/...) 16 | const segments = pathname.split("/"); 17 | if (segments.length > 1 && segments[1]) { 18 | const potentialLang = segments[1]; 19 | if (SUPPORTED_LANGUAGES.includes(potentialLang as any)) { 20 | lang = potentialLang; 21 | } 22 | } 23 | } 24 | } 25 | 26 | return lang; 27 | } 28 | -------------------------------------------------------------------------------- /i18n/index.ts: -------------------------------------------------------------------------------- 1 | import { getServerTranslations } from "./get-server-translations"; 2 | import { useTranslations } from "./use-translations"; 3 | import { SUPPORTED_LANGUAGES, DEFAULT_LANGUAGE } from "./supported-languages"; 4 | import { useCurrentLanguage } from "./use-current-language"; 5 | import { ClientTranslationsProvider } from "./internal/client-translations-provider"; 6 | import TranslationsProvider from "./translations-provider"; 7 | import { getPreferredLanguage } from "./get-preferred-language"; 8 | import { getServerCurrentLanguage } from "./get-server-current-language"; 9 | import { generateStaticParams } from "./generate-static-params"; 10 | 11 | export { 12 | useTranslations, 13 | getServerTranslations, 14 | SUPPORTED_LANGUAGES, 15 | DEFAULT_LANGUAGE, 16 | useCurrentLanguage, 17 | ClientTranslationsProvider, 18 | TranslationsProvider, 19 | getPreferredLanguage, 20 | getServerCurrentLanguage, 21 | generateStaticParams, 22 | }; 23 | -------------------------------------------------------------------------------- /i18n/messages/ko.ts: -------------------------------------------------------------------------------- 1 | import { Messages } from "./types"; 2 | 3 | const ko: Messages = { 4 | header: { 5 | home: "홈", 6 | blog: "블로그", 7 | github: "깃허브", 8 | docs: "문서", 9 | tutorial: "튜토리얼", 10 | contributing: "기여하기", 11 | showcase: "쇼케이스", 12 | }, 13 | home: { 14 | title: "커피 한 잔과\n노션프레소", 15 | getStarted: "시작하기", 16 | readMore: "문서보기", 17 | }, 18 | metadata: { 19 | title: "노션프레소 문서", 20 | description: 21 | "React 애플리케이션에서 Notion 페이지를 렌더링하기 위한 강력한 도구인 노션프레소 라이브러리의 종합 문서입니다.", 22 | keywords: ["리액트", "노션", "커스텀", "문서", "라이브러리", "렌더링"], 23 | og: { 24 | title: "노션프레소 문서", 25 | description: "React 앱에서 Notion 페이지를 렌더링하는 방법을 배워보세요.", 26 | siteName: "노션프레소", 27 | imageAlt: "노션프레소 문서", 28 | }, 29 | twitter: { 30 | title: "노션프레소 문서", 31 | description: "React 앱에서 Notion 페이지를 렌더링하는 방법을 배워보세요.", 32 | imageAlt: "노션프레소 문서", 33 | }, 34 | }, 35 | }; 36 | 37 | export default ko; 38 | -------------------------------------------------------------------------------- /i18n/messages/cn.ts: -------------------------------------------------------------------------------- 1 | import { Messages } from "./types"; 2 | 3 | const cn: Messages = { 4 | header: { 5 | home: "首页", 6 | blog: "博客", 7 | github: "GitHub", 8 | docs: "文档", 9 | tutorial: "教程", 10 | contributing: "贡献", 11 | showcase: "展示", 12 | }, 13 | home: { 14 | title: "一杯咖啡和\nNotionpresso", 15 | getStarted: "开始使用", 16 | readMore: "查看文档", 17 | }, 18 | metadata: { 19 | title: "Notionpresso 文档", 20 | description: 21 | "Notionpresso库的综合文档,这是一个在React应用程序中渲染Notion页面的强大工具。", 22 | keywords: ["react", "notion", "自定义", "文档", "库", "渲染"], 23 | og: { 24 | title: "Notionpresso 文档", 25 | description: "学习如何使用Notionpresso在React应用中渲染Notion页面。", 26 | siteName: "Notionpresso", 27 | imageAlt: "Notionpresso 文档", 28 | }, 29 | twitter: { 30 | title: "Notionpresso 文档", 31 | description: "学习如何使用Notionpresso在React应用中渲染Notion页面。", 32 | imageAlt: "Notionpresso 文档", 33 | }, 34 | }, 35 | }; 36 | 37 | export default cn; 38 | -------------------------------------------------------------------------------- /components/docs/dynamic-layout.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib/utils"; 2 | 3 | interface DynamicLayoutProps { 4 | sidebar: React.ReactNode; 5 | toc: React.ReactNode; 6 | mobileSidebar: React.ReactNode; 7 | children: React.ReactNode; 8 | } 9 | 10 | export default function DynamicLayout({ 11 | sidebar, 12 | toc, 13 | mobileSidebar, 14 | children, 15 | }: DynamicLayoutProps) { 16 | return ( 17 |
22 | {/* Sidebar for desktop */} 23 |
{sidebar}
24 | 25 | {/* Mobile sidebar */} 26 |
{mobileSidebar}
27 | 28 | {/* Main content */} 29 |
{children}
30 | 31 | {/* TOC - only visible on desktop */} 32 |
{toc}
33 |
34 | ); 35 | } 36 | -------------------------------------------------------------------------------- /i18n/internal/load-translations.ts: -------------------------------------------------------------------------------- 1 | import type { Messages } from "../messages/types"; 2 | import { SUPPORTED_LANGUAGES, SupportedLanguage } from "../supported-languages"; 3 | 4 | export async function loadTranslations(lang: string): Promise { 5 | let messages: Messages; 6 | 7 | // 언어 코드 검증 8 | const isSupported = SUPPORTED_LANGUAGES.includes(lang as SupportedLanguage); 9 | const languageToLoad = isSupported ? lang : "en"; // 지원되지 않는 언어의 경우 기본 언어로 폴백 10 | 11 | try { 12 | messages = await import(`../messages/${languageToLoad}`).then( 13 | (module) => module.default, 14 | ); 15 | } catch (error) { 16 | console.error( 17 | `Failed to load messages for language: ${languageToLoad}`, 18 | error, 19 | ); 20 | // 메시지 로드 실패 시 기본 언어로 폴백 21 | if (languageToLoad !== "en") { 22 | messages = await import("../messages/en").then( 23 | (module) => module.default, 24 | ); 25 | } else { 26 | throw new Error("Failed to load default language messages."); 27 | } 28 | } 29 | 30 | return messages; 31 | } 32 | -------------------------------------------------------------------------------- /middleware.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse } from "next/server"; 2 | import type { NextRequest } from "next/server"; 3 | import { SUPPORTED_LANGUAGES } from "@/i18n/supported-languages"; 4 | import { getPreferredLanguage } from "./i18n/get-preferred-language"; 5 | 6 | export function middleware( 7 | request: NextRequest, 8 | _response?: NextResponse, 9 | ): NextResponse { 10 | const { pathname } = request.nextUrl; 11 | const response = NextResponse.next(); 12 | 13 | if ( 14 | SUPPORTED_LANGUAGES.some( 15 | (lang) => pathname.startsWith(`/${lang}/`) || pathname === `/${lang}`, 16 | ) 17 | ) { 18 | return response; 19 | } 20 | 21 | const lang = getPreferredLanguage(request); 22 | const newUrl = new URL(`/${lang}${pathname}`, request.url); 23 | 24 | const redirectResponse = NextResponse.redirect(newUrl); 25 | 26 | return redirectResponse; 27 | } 28 | 29 | export const config = { 30 | matcher: [ 31 | "/((?!api|_next/static|_next/image|favicon\\.ico|.*\\.(?:png|jpg|jpeg|gif|webp|svg|ico|bmp|tiff|css|js|map|json|txt|xml|woff|woff2|eot|ttf|otf|txt)$).*)", 32 | ], 33 | }; 34 | -------------------------------------------------------------------------------- /i18n/messages/ja.ts: -------------------------------------------------------------------------------- 1 | import { Messages } from "./types"; 2 | 3 | const ja: Messages = { 4 | header: { 5 | home: "ホーム", 6 | blog: "ブログ", 7 | github: "ギットハブ", 8 | docs: "ドキュメント", 9 | tutorial: "チュートリアル", 10 | contributing: "貢献する", 11 | showcase: "ショーケース", 12 | }, 13 | home: { 14 | title: "コーヒー一杯と\nノーションプレッソ", 15 | getStarted: "始める", 16 | readMore: "ドキュメントを見る", 17 | }, 18 | metadata: { 19 | title: "ノーションプレッソ ドキュメント", 20 | description: 21 | "リアクトアプリケーションでノーションページをレンダリングするための強力なツール、ノーションプレッソライブラリーの総合ドキュメントです。", 22 | keywords: [ 23 | "リアクト", 24 | "ノーション", 25 | "カスタム", 26 | "ドキュメント", 27 | "ライブラリー", 28 | "レンダリング", 29 | ], 30 | og: { 31 | title: "ノーションプレッソ ドキュメント", 32 | description: 33 | "リアクトアプリでノーションページをレンダリングする方法を学びましょう。", 34 | siteName: "ノーションプレッソ", 35 | imageAlt: "ノーションプレッソ ドキュメント", 36 | }, 37 | twitter: { 38 | title: "ノーションプレッソ ドキュメント", 39 | description: 40 | "リアクトアプリでノーションページをレンダリングする方法を学びましょう。", 41 | imageAlt: "ノーションプレッソ ドキュメント", 42 | }, 43 | }, 44 | }; 45 | 46 | export default ja; 47 | -------------------------------------------------------------------------------- /components/ui/footer.tsx: -------------------------------------------------------------------------------- 1 | import { SNS_LIST } from "@/constants/constants"; 2 | import { cn } from "@/lib/utils"; 3 | import ThemeSelector from "./theme-selector"; 4 | import Image from "next/image"; 5 | 6 | export default function Footer() { 7 | return ( 8 |
9 | {/* left */} 10 |
11 |

Released under the MIT License.

12 |

Copyright NotionPresso

13 |
14 | {/* right */} 15 |
16 | 17 | {SNS_LIST.map((item) => ( 18 | 28 | {item.name} 35 | 36 | ))} 37 |
38 |
39 | ); 40 | } 41 | -------------------------------------------------------------------------------- /components/docs/navigation-button.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import { ChevronRightIcon, ChevronLeftIcon } from "@heroicons/react/24/solid"; 3 | 4 | interface NavigationButtonProps { 5 | document: { 6 | group: string; 7 | slug: string; 8 | title: string; 9 | }; 10 | lang: string; 11 | direction: "prev" | "next"; 12 | } 13 | 14 | export default function NavigationButton({ 15 | document, 16 | lang, 17 | direction, 18 | }: NavigationButtonProps) { 19 | return ( 20 | 24 | {direction === "prev" ? ( 25 |
26 | 27 |

{document.title}

28 |
29 | ) : ( 30 |
31 |

{document.title}

32 | 33 |
34 | )} 35 | 36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /i18n/messages/en.ts: -------------------------------------------------------------------------------- 1 | import { Messages } from "./types"; 2 | 3 | const en: Messages = { 4 | header: { 5 | home: "Home", 6 | blog: "Blog", 7 | github: "Github", 8 | docs: "Docs", 9 | tutorial: "Tutorial", 10 | contributing: "Contributing", 11 | showcase: "Showcase", 12 | }, 13 | home: { 14 | title: "Just a cup of coffee\nwith NotionPresso", 15 | getStarted: "Get Started", 16 | readMore: "Read the Docs", 17 | }, 18 | metadata: { 19 | title: "Notionpresso Docs", 20 | description: 21 | "Comprehensive documentation for Notionpresso library, a powerful tool for rendering Notion pages in React applications.", 22 | keywords: [ 23 | "react", 24 | "notion", 25 | "custom", 26 | "documentation", 27 | "library", 28 | "rendering", 29 | ], 30 | og: { 31 | title: "Notionpresso Documentation", 32 | description: 33 | "Learn how to use Notionpresso to render Notion pages in your React apps.", 34 | siteName: "Notionpresso", 35 | imageAlt: "Notionpresso Documentation", 36 | }, 37 | twitter: { 38 | title: "Notionpresso Documentation", 39 | description: 40 | "Learn how to use Notionpresso to render Notion pages in your React apps.", 41 | imageAlt: "Notionpresso Documentation", 42 | }, 43 | }, 44 | }; 45 | 46 | export default en; 47 | -------------------------------------------------------------------------------- /app/[lang]/tutorial/page.tsx: -------------------------------------------------------------------------------- 1 | import { NotionRenderer } from "@/components/notion-renderer"; 2 | //TODO: dynamic import is needed for better performance (don't need to import all the content at once) 3 | import * as ko from "@/content/tutorial/ko/126ce18c-fd83-80a5-8260-d757c56405b2.json"; 4 | import * as en from "@/content/tutorial/en/12ace18c-fd83-8071-b3a5-dd8d21da61cf.json"; 5 | import type { Metadata } from "next"; 6 | 7 | export const runtime = "edge"; 8 | 9 | export { generateStaticParams } from "@/i18n"; 10 | 11 | export async function generateMetadata({ 12 | params, 13 | }: { 14 | params: { lang: string }; 15 | }): Promise { 16 | const content = params.lang === "ko" ? ko : en; 17 | const title = content.properties.title.title[0].plain_text; 18 | 19 | return { 20 | title, 21 | description: 22 | params.lang === "ko" 23 | ? "Notionpresso 튜토리얼 페이지입니다." 24 | : "Notionpresso tutorial page", 25 | openGraph: { 26 | title, 27 | description: 28 | params.lang === "ko" 29 | ? "Notionpresso로 노션 페이지를 쉽게 웹사이트로 변환하세요" 30 | : "Convert your Notion pages to websites easily with Notionpresso", 31 | locale: params.lang, 32 | type: "website", 33 | }, 34 | }; 35 | } 36 | 37 | export default function TutorialPage({ params }: { params: { lang: string } }) { 38 | const content = params.lang === "ko" ? ko : en; 39 | const title = content.properties.title.title[0].plain_text; 40 | 41 | return ( 42 |
43 | 44 |
45 | ); 46 | } 47 | -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import "./globals.css"; 3 | 4 | export const metadata: Metadata = { 5 | metadataBase: new URL("https://notionpresso.com"), 6 | title: "Notionpresso Docs", 7 | description: 8 | "Comprehensive documentation for Notionpresso library, a powerful tool for rendering Notion pages in React applications.", 9 | keywords: "react, notion, custom, documentation, library, rendering", 10 | icons: { 11 | icon: "/icon.jpeg", 12 | shortcut: "/icon.jpeg", 13 | apple: "/icon.jpeg", 14 | }, 15 | robots: { 16 | index: true, 17 | follow: true, 18 | googleBot: { 19 | index: true, 20 | follow: true, 21 | "max-video-preview": -1, 22 | "max-image-preview": "large", 23 | "max-snippet": -1, 24 | }, 25 | }, 26 | openGraph: { 27 | title: "Notionpresso Documentation", 28 | description: 29 | "Learn how to use Notionpresso to render Notion pages in your React apps.", 30 | type: "website", 31 | url: "https://notionpresso.com", 32 | siteName: "Notionpresso", 33 | images: [ 34 | { 35 | url: "/icon.jpeg", 36 | alt: "Notionpresso Documentation", 37 | }, 38 | ], 39 | }, 40 | twitter: { 41 | card: "summary_large_image", 42 | title: "Notionpresso Documentation", 43 | description: 44 | "Learn how to use Notionpresso to render Notion pages in your React apps.", 45 | images: ["/icon.jpeg"], 46 | }, 47 | }; 48 | 49 | export default function RootLayout({ 50 | children, 51 | }: Readonly<{ 52 | children: React.ReactNode; 53 | }>) { 54 | return <>{children}; 55 | } 56 | -------------------------------------------------------------------------------- /lib/prevent-scrollbar.ts: -------------------------------------------------------------------------------- 1 | type GapMode = "padding" | "margin"; 2 | 3 | interface GapOffset { 4 | left: number; 5 | top: number; 6 | right: number; 7 | gap: number; 8 | } 9 | 10 | const zeroGap = { 11 | left: 0, 12 | top: 0, 13 | right: 0, 14 | gap: 0, 15 | }; 16 | 17 | const parse = (x: string | null) => parseInt(x || "", 10) || 0; 18 | 19 | const getOffset = (gapMode: GapMode): number[] => { 20 | const cs = window.getComputedStyle(document.body); 21 | 22 | const left = cs[gapMode === "padding" ? "paddingLeft" : "marginLeft"]; 23 | const top = cs[gapMode === "padding" ? "paddingTop" : "marginTop"]; 24 | const right = cs[gapMode === "padding" ? "paddingRight" : "marginRight"]; 25 | 26 | return [parse(left), parse(top), parse(right)]; 27 | }; 28 | 29 | const getGapWidth = (gapMode: GapMode = "margin"): GapOffset => { 30 | if (typeof window === "undefined") { 31 | return zeroGap; 32 | } 33 | 34 | const offsets = getOffset(gapMode); 35 | const documentWidth = document.documentElement.clientWidth; 36 | const windowWidth = window.innerWidth; 37 | 38 | return { 39 | left: offsets[0], 40 | top: offsets[1], 41 | right: offsets[2], 42 | gap: Math.max(0, windowWidth - documentWidth + offsets[2] - offsets[0]), 43 | }; 44 | }; 45 | 46 | const preventScroll = (isOpen: boolean) => { 47 | if (isOpen) { 48 | const { gap } = getGapWidth("padding"); 49 | document.body.style.overflow = "hidden"; 50 | document.body.style.paddingRight = `${gap}px`; 51 | } else { 52 | document.body.style.overflow = ""; 53 | document.body.style.paddingRight = ""; 54 | } 55 | }; 56 | 57 | export default preventScroll; 58 | -------------------------------------------------------------------------------- /i18n/use-translations.ts: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useContext } from "react"; 4 | import { TranslationsContext } from "./internal/client-translations-provider"; 5 | import { interpolate } from "./internal/interpolate"; 6 | import { Messages } from "./messages/types"; 7 | 8 | // 중첩된 키를 점으로 연결하여 추출하는 유틸리티 타입 9 | type NestedKeyOf = { 10 | [Key in keyof TObj & (string | number)]: TObj[Key] extends object 11 | ? `${Key}` | `${Key}.${NestedKeyOf}` 12 | : `${Key}`; 13 | }[keyof TObj & (string | number)]; 14 | 15 | export function useTranslations( 16 | namespace: Namespace, 17 | ) { 18 | const { messages } = useContext(TranslationsContext); 19 | 20 | function t< 21 | Key extends NestedKeyOf, 22 | Variables extends { [key: string]: any } = {}, 23 | >(key: Key, variables?: Variables): string { 24 | // 전체 키 생성 (네임스페이스 포함) 25 | const fullKey = `${namespace}.${key}`; 26 | 27 | // 키를 '.'으로 분할하여 중첩된 객체에서 값을 찾습니다. 28 | const keys = fullKey.split("."); 29 | let message: any = messages; 30 | 31 | for (const k of keys) { 32 | if (message && k in message) { 33 | message = message[k]; 34 | } else { 35 | console.warn(`Translation for key "${fullKey}" not found.`); 36 | return fullKey; 37 | } 38 | } 39 | 40 | if (typeof message === "string") { 41 | if (variables) { 42 | return interpolate(message, variables); 43 | } else { 44 | return message; 45 | } 46 | } else { 47 | console.warn(`Translation for key "${fullKey}" is not a string.`); 48 | return fullKey; 49 | } 50 | } 51 | 52 | return t; 53 | } 54 | -------------------------------------------------------------------------------- /content/guide/cn/01. getting-started/01. introduction.md: -------------------------------------------------------------------------------- 1 | --- 2 | group: "getting-started" 3 | order: 1 4 | title: "项目介绍" 5 | slug: "introduction" 6 | description: "介绍 Notionpresso 项目的概述、主要功能和应用可能性。" 7 | --- 8 | 9 | # Notionpresso 项目介绍 10 | 11 | ## 什么是 Notionpresso? 12 | 13 | Notionpresso 是一个能够轻松将 Notion 强大的内容创作功能转换为网站的工具。使用这个库,您可以将在 Notion 中创建的内容直接渲染到基于 React 的网站上。对开发者来说,它是一个能够快速产生结果的工具;对内容创作者来说,它是一个便捷的管理工具 —— 可以说是"为开发者和创作者准备的魔法药水"。✨ 14 | 15 | ## 主要功能和价值 16 | 17 | ### 🛠️ Notion 数据的 React 渲染 18 | 19 | 您可以轻松地将 Notion 中创建的文本、图片、列表、代码块等各种内容转换为 React 组件。Notion 页面将以以下结构在网页中重生: 20 | 21 | ```jsx 22 | import { Notion, type NotionPageData } from 'Notionpresso'; 23 | 24 | interface NotionPageProps { 25 | pageData: NotionPageData; 26 | } 27 | 28 | function NotionPage({ pageData }: NotionPageProps) { 29 | return ( 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | ); 38 | } 39 | ``` 40 | 41 | ## 目标用户和他们应用的可能性 42 | 43 | ### 开发者 44 | 45 | 开发者可以在网站上轻松渲染 Notion 中创建的内容,并进行自定义。如果您想快速构建网站或超越商业服务的限制,Notionpresso 是您的答案。 46 | 47 | ### 设计师和创作者 48 | 49 | 设计师和创作者可以直接在网站上反映 Notion 中创建的内容,从而保持设计的一致性并快速更新内容。 50 | 51 | ### 应用可能性示例 52 | 53 | - **个人博客**: 您可以在 Notion 中轻松创建文章,并自动在网站上反映。 54 | - **公司简介和招聘页面**: 您可以使用 Notion 快速更新公司简介和招聘信息。 55 | - **作品集网站**: 您可以轻松更新项目和作品,从而使作品集管理变得更加容易。 56 | 57 | ## 为什么选择 Notionpresso 58 | 59 | 1. **开发者为中心的自定义**: 在其他商业服务中,您无法自定义到如此精细的程度。通过代码,您可以获得无限的可能性,这就是 Notionpresso 的魅力所在。 60 | 61 | 2. **开源的自由**: 一切都是透明的,您可以直接操作。如果您需要,您可以修改代码并将其托管在自己的服务器上,而无需承担任何费用。 62 | 63 | 3. **快速结果和高效率**: 您可以在 Notion 中创建内容,并通过简单的设置直接在网站上使用。内容创作和部署的过程变得更加简单。 64 | 65 | Notionpresso 是您将 Notion 页面转换为令人惊叹的网站的桥梁。现在,您可以摆脱复杂的设置,直接在 Notion 上开始! 66 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docs", 3 | "version": "0.1.0", 4 | "private": true, 5 | "description": "NotionPresso Documentation site built with Next.js", 6 | "author": { 7 | "name": "notionpresso", 8 | "email": "helper.notionpresso@gmail.com", 9 | "url": "https://notionpresso.com" 10 | }, 11 | "homepage": "https://notionpresso.com", 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/notionpresso/docs" 15 | }, 16 | "bugs": { 17 | "url": "https://github.com/notionpresso/docs/issues" 18 | }, 19 | "keywords": [ 20 | "nextjs", 21 | "react", 22 | "typescript", 23 | "tailwindcss", 24 | "documentation" 25 | ], 26 | "license": "MIT", 27 | "scripts": { 28 | "dev": "next dev", 29 | "prod": "next build && next start", 30 | "build": "next build", 31 | "postbuild": "next-sitemap", 32 | "start": "next start", 33 | "lint": "next lint", 34 | "prepare": "husky" 35 | }, 36 | "dependencies": { 37 | "@heroicons/react": "2.1.5", 38 | "@notionpresso/react": "^0.0.5", 39 | "@types/hast": "3.0.4", 40 | "clsx": "2.1.1", 41 | "gray-matter": "4.0.3", 42 | "highlight.js": "^11.10.0", 43 | "next": "14.2.11", 44 | "next-sitemap": "^4.2.3", 45 | "next-themes": "0.3.0", 46 | "react": "^18", 47 | "react-dom": "^18", 48 | "react-markdown": "9.0.1", 49 | "rehype-highlight": "7.0.0", 50 | "rehype-raw": "7.0.0", 51 | "remark": "15.0.1", 52 | "remark-gfm": "4.0.0", 53 | "remark-html": "16.0.1", 54 | "tailwind-merge": "2.5.4" 55 | }, 56 | "devDependencies": { 57 | "@tailwindcss/typography": "^0.5.15", 58 | "@types/node": "^20", 59 | "@types/react": "^18", 60 | "@types/react-dom": "^18", 61 | "husky": "^9.1.6", 62 | "lint-staged": "^15.2.10", 63 | "postcss": "^8", 64 | "prettier": "^3.3.3", 65 | "tailwindcss": "^3.4.1", 66 | "typescript": "^5" 67 | }, 68 | "lint-staged": { 69 | "**/*.{js,jsx,ts,tsx}": [ 70 | "prettier --write" 71 | ], 72 | "**/*.{json,css,scss,md}": [ 73 | "prettier --write" 74 | ] 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /components/docs/sidebar.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import { ChevronRightIcon } from "@heroicons/react/24/solid"; 3 | import ThemeSelector from "../ui/theme-selector"; 4 | 5 | type Document = { 6 | title: string; 7 | slug: string; 8 | group: string; 9 | order: number; 10 | }; 11 | 12 | type SidebarProps = { 13 | documents: Document[]; 14 | currentSlug: string; 15 | currentGroup: string; 16 | lang: string; 17 | }; 18 | 19 | export default function Sidebar({ 20 | documents, 21 | currentSlug, 22 | currentGroup, 23 | lang, 24 | }: SidebarProps) { 25 | const groupedDocuments = documents.reduce( 26 | (acc, doc) => { 27 | if (!acc[doc.group]) { 28 | acc[doc.group] = []; 29 | } 30 | acc[doc.group].push(doc); 31 | return acc; 32 | }, 33 | {} as Record, 34 | ); 35 | 36 | return ( 37 | 65 | ); 66 | } 67 | -------------------------------------------------------------------------------- /CONTRIBUTING-KR.md: -------------------------------------------------------------------------------- 1 | # 문서 가이드 및 기여 2 | 3 | [프로젝트 전체 기여 가이드 참고](../../CONTRIBUTING-KR.md) 4 | 5 | ## 목차 6 | 7 | 1. 웹사이트 구조 8 | 2. 페이지별 내용 9 | 3. 기술 스택 10 | 4. 콘텐츠 관리 11 | 5. 다국어 지원 12 | 6. 개발 로드맵 13 | 7. 참고 사항 14 | 15 | ## 1. 웹사이트 구조 16 | 17 | ``` 18 | / 19 | ├── 랜딩 페이지 20 | ├── 문서 (Docs) 21 | ├── 블로그 (Blog) 22 | ├── 쇼케이스 (Showcase) 23 | └── [기타 페이지들] 24 | ``` 25 | 26 | ## 2. 페이지별 내용 27 | 28 | ### 2.1 랜딩 페이지 29 | 30 | - 프로젝트 소개 31 | - 주요 기능 하이라이트 32 | - Notion으로 블로그 셀프 호스팅 가이드 (튜토리얼 형식) 33 | - 시작하기 버튼 (문서로 연결) 34 | 35 | ### 2.2 문서 (Docs) 36 | 37 | ``` 38 | +------------------+ 39 | | Sidebar | 40 | | - 카테고리 1 | 41 | | - 문서 1 | 42 | | - 문서 2 | 43 | | - 카테고리 2 | 44 | | - 문서 3 | 45 | | - 문서 4 | 46 | +------------------+ 47 | | | 48 | | Content | 49 | | | 50 | | [문서 내용] | 51 | | | 52 | +------------------+ 53 | ``` 54 | 55 | ### 2.3 블로그 (Blog) 56 | 57 | - 개발 과정, 업데이트, 팁 등의 포스트 58 | - 태그 및 날짜별 정렬 기능 59 | 60 | ### 2.4 쇼케이스 (Showcase) 61 | 62 | - 사용자들의 커스텀 Notion 페이지 전시 63 | - 필터링 및 정렬 기능 64 | 65 | ## 3. 기술 스택 66 | 67 | - Frontend: Next.js 68 | - CSS: TailwindCSS 69 | - UI 라이브러리: Radix UI 또는 Acenti/UI (필요시 사용) 70 | - 마크다운 렌더링: Next.js 공식 문서 참고 71 | 72 | ## 4. 콘텐츠 관리 73 | 74 | ### 4.1 문서 (Docs) 75 | 76 | - 위치: `/content/docs/[lang]/[category]/[document].md` 77 | - Frontmatter: 78 | - group: 카테고리 79 | - order: 문서 순서 80 | - title: 문서 제목 81 | - description: 문서 설명 82 | 83 | ### 4.2 블로그 (Blog) 84 | 85 | - 위치: `/content/blog/[lang]/[post].md` 86 | - Frontmatter: 87 | - title: 포스트 제목 88 | - description: 포스트 설명 89 | - date: 작성일 90 | 91 | ## 5. 다국어 지원 92 | 93 | - 지원 언어: 한국어(kr), 영어(en), 중국어(cn) 94 | - 마크다운 파일: 언어별로 분리 관리 95 | - UI 텍스트: 다국어 지원 라이브러리 사용 96 | 97 | ## 6. 개발 로드맵 98 | 99 | 1. 기본 Next.js 프로젝트 설정 100 | 2. TailwindCSS 통합 101 | 3. 마크다운 렌더링 시스템 구축 102 | 4. 다국어 지원 시스템 구현 103 | 5. 랜딩 페이지 개발 104 | 6. 문서 페이지 개발 105 | 7. 블로그 페이지 개발 106 | 8. 쇼케이스 페이지 개발 107 | 9. UI/UX 개선 및 최적화 108 | 10. 콘텐츠 작성 및 번역 109 | 11. 베타 테스트 및 피드백 수집 110 | 12. 공식 런칭 111 | 112 | ## 7. 참고 사항 113 | 114 | - 문서와 블로그 포스트는 마크다운 형식으로 관리 115 | - 랜딩 페이지는 Notionpresso 라이브러리를 활용한 실제 예시 포함 116 | - 지속적인 콘텐츠 업데이트 및 커뮤니티 참여 독려 117 | -------------------------------------------------------------------------------- /app/[lang]/docs/[group]/[slug]/page.tsx: -------------------------------------------------------------------------------- 1 | import { DocsLayout } from "@/components/docs"; 2 | import { SUPPORTED_LANGUAGES } from "@/i18n/supported-languages"; 3 | import { getAllDocuments, getDocumentBySlug } from "@/lib/mdx"; 4 | import { Metadata } from "next"; 5 | 6 | interface GuidePageProps { 7 | params: { 8 | lang: string; 9 | group: string; 10 | slug: string; 11 | }; 12 | } 13 | 14 | // Since we're using `fs` during build time, we should avoid the Edge runtime 15 | // export const runtime = 'edge'; 16 | 17 | // Generate static params at build time 18 | export async function generateStaticParams() { 19 | const supportedLanguages = SUPPORTED_LANGUAGES; 20 | const params: GuidePageProps["params"][] = []; 21 | 22 | for (const lang of supportedLanguages) { 23 | const allDocuments = getAllDocuments(lang); 24 | allDocuments.forEach((doc) => { 25 | params.push({ 26 | lang, 27 | group: doc.group, 28 | slug: doc.slug, 29 | }); 30 | }); 31 | } 32 | 33 | return params; 34 | } 35 | 36 | // Generate metadata at build time 37 | export async function generateMetadata({ 38 | params, 39 | }: GuidePageProps): Promise { 40 | const { lang, group, slug } = params; 41 | const allDocuments = getAllDocuments(lang); 42 | const document = getDocumentBySlug(lang, group, slug, allDocuments); 43 | 44 | return { 45 | title: `${document.title} | Notionpresso Docs`, 46 | description: document.content.slice(0, 160), 47 | openGraph: { 48 | title: `${document.title} | Notionpresso Docs`, 49 | description: document.content.slice(0, 160), 50 | // url: "", 51 | }, 52 | alternates: { 53 | // canonical: "", 54 | }, 55 | }; 56 | } 57 | 58 | export default async function Page({ params }: GuidePageProps) { 59 | const { lang, group, slug } = params; 60 | 61 | // Fetch data during the rendering of the page 62 | const allDocuments = getAllDocuments(lang); 63 | const { content, title, prevDocument, nextDocument } = getDocumentBySlug( 64 | lang, 65 | group, 66 | slug, 67 | allDocuments, 68 | ); 69 | 70 | return ( 71 | 79 | ); 80 | } 81 | -------------------------------------------------------------------------------- /app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | /* CSS Reset */ 6 | * { 7 | margin: 0; 8 | padding: 0; 9 | box-sizing: border-box; 10 | } 11 | 12 | html, 13 | body { 14 | overflow-y: scroll; 15 | height: 100%; 16 | } 17 | 18 | img, 19 | picture, 20 | video, 21 | canvas, 22 | svg { 23 | display: block; 24 | max-width: 100%; 25 | } 26 | 27 | input, 28 | button, 29 | textarea, 30 | select { 31 | font: inherit; 32 | } 33 | 34 | button { 35 | cursor: pointer; 36 | } 37 | 38 | a { 39 | text-decoration: none; 40 | color: inherit; 41 | } 42 | 43 | ul, 44 | ol { 45 | list-style: none; 46 | } 47 | 48 | /* Global Styles */ 49 | body { 50 | line-height: 1.6; 51 | background-color: #f8f9fa; 52 | color: #343a40; 53 | } 54 | 55 | h1, 56 | h2, 57 | h3, 58 | h4, 59 | h5, 60 | h6 { 61 | font-weight: bold; 62 | line-height: 1.2; 63 | } 64 | 65 | @keyframes shake { 66 | 0% { 67 | transform: rotate(0deg); 68 | } 69 | 25% { 70 | transform: rotate(5deg); 71 | } 72 | 50% { 73 | transform: rotate(0eg); 74 | } 75 | 75% { 76 | transform: rotate(-5deg); 77 | } 78 | 100% { 79 | transform: rotate(0deg); 80 | } 81 | } 82 | .logo-image { 83 | transition: transform 0.1s ease-in-out; 84 | } 85 | .logo-image:hover { 86 | animation: shake 0.5s ease-in-out; 87 | } 88 | 89 | .prose table { 90 | width: 100%; 91 | border-collapse: collapse; 92 | } 93 | 94 | .prose th, 95 | .prose td { 96 | border: 1px solid #ddd; 97 | padding: 8px; 98 | text-align: left; 99 | } 100 | 101 | .prose th { 102 | background-color: #f2f2f2; 103 | } 104 | 105 | /* For WebKit browsers (Chrome, Safari) */ 106 | ::-webkit-scrollbar { 107 | width: 8px; 108 | height: 8px; 109 | } 110 | 111 | ::-webkit-scrollbar-thumb { 112 | background-color: #fe6f21; 113 | border-radius: 10px; 114 | } 115 | 116 | ::-webkit-scrollbar-track { 117 | background: transparent; 118 | } 119 | 120 | ::-webkit-scrollbar-corner { 121 | background: transparent; 122 | } 123 | 124 | /* For Firefox */ 125 | * { 126 | scrollbar-width: thin; 127 | scrollbar-color: #fe6f21 transparent; 128 | } 129 | 130 | /* Optional: Hover effects for better interactivity */ 131 | ::-webkit-scrollbar-thumb:hover { 132 | background-color: #e85f11; 133 | } 134 | -------------------------------------------------------------------------------- /content/guide/cn/03. block-types/07. paragraph.md: -------------------------------------------------------------------------------- 1 | --- 2 | group: "block-types" 3 | order: 7 4 | title: "段落块" 5 | slug: "paragraph" 6 | description: "详细介绍 Notionpresso 的段落块类型。" 7 | --- 8 | 9 | # 段落块 10 | 11 | 段落块是表示普通文本的最基本块类型。 12 | 13 | ## 数据结构 14 | 15 | 段落块的 Notion API 数据结构如下: 16 | 17 | ```json 18 | { 19 | "type": "paragraph", 20 | "paragraph": { 21 | "rich_text": [ 22 | { 23 | "type": "text", 24 | "text": { 25 | "content": "This is a paragraph.", 26 | "link": null 27 | }, 28 | "annotations": { 29 | "bold": false, 30 | "italic": false, 31 | "strikethrough": false, 32 | "underline": false, 33 | "code": false, 34 | "color": "default" 35 | }, 36 | "plain_text": "This is a paragraph.", 37 | "href": null 38 | } 39 | ], 40 | "color": "default" 41 | } 42 | } 43 | ``` 44 | 45 | - `rich_text`: 包含文本内容和样式信息的数组。 46 | - `color`: 指定文本的颜色。 47 | 48 | ## React 组件 49 | 50 | Notionpresso 中用于渲染段落块的组件如下: 51 | 52 | ```jsx 53 | import React from "react"; 54 | import type { ParagraphArgs } from "../types"; 55 | import { getColorCss } from "../utils"; 56 | import RichText from "./internal/rich-text"; 57 | 58 | type ParagraphProps = { 59 | children?: React.ReactNode; 60 | } & ParagraphArgs; 61 | 62 | const Paragraph: React.FC = ({ children, ...props }) => { 63 | const { 64 | paragraph: { color, rich_text: texts }, 65 | } = props; 66 | 67 | return ( 68 |
69 |

70 | 71 |

72 | {children} 73 |
74 | ); 75 | }; 76 | 77 | export default Paragraph; 78 | ``` 79 | 80 | ## 使用示例 81 | 82 | 段落块的使用示例如下: 83 | 84 | ```jsx 85 | import { Notion } from "@notionpresso/react"; 86 | 87 | function MyNotionPage({ blocks }) { 88 | return ( 89 | 90 | 91 | 92 | ); 93 | } 94 | ``` 95 | 96 | 这里 `blocks` 是来自 Notion API 的块数据数组。 97 | 98 | ## 样式 99 | 100 | 段落块的样式可以通过以下 CSS 类进行自定义: 101 | 102 | - `.notion-block`: 应用于所有 Notion 块的基本样式 103 | - `.notion-paragraph`: 段落块的特定样式 104 | - `.notion-paragraph-content`: 段落内容样式 105 | 106 | 如果需要额外的样式,可以对这些类进行 CSS 编写。 107 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NotionPresso Documentation 2 | 3 |

4 | 5 | 6 | 7 |

NotionPresso Documentation

8 |

9 | 10 |

11 | 12 | 13 | 14 | 15 | 16 | 17 |

18 | 19 | NotionPresso is an open-source project that enables developers to create their own blog using Notion as a CMS. It's the first React rendering library that utilizes Notion's official API, offering unlimited customization possibilities. 20 | 21 | ## Key Features 22 | 23 | ### Technical Highlights 24 | 25 | - **First Official API-based React Library**: Built on Notion's official API for stable and extensible rendering 26 | - **Complete TypeScript Support**: Fully typed components and APIs for better development experience 27 | - **Customizable Component System**: Flexible architecture allowing infinite customization possibilities 28 | 29 | ### For Blog Creators 30 | 31 | - **Free Blog Hosting**: Create and host your blog completely free using our guides 32 | - **Quick Deployment**: Set up your blog in minutes with our step-by-step deployment guide 33 | - **Familiar Writing Experience**: Use Notion's powerful editor for content creation 34 | 35 | ## Getting Started 36 | 37 | ```bash 38 | # Install the core package 39 | npm install @notionpresso/react 40 | 41 | # For CLI tools 42 | npm install -g @notionpresso/cli 43 | ``` 44 | 45 | For detailed guides and documentation, visit our [official documentation](https://notionpresso.com). 46 | 47 | ## Documentation 48 | 49 | - [Getting Started Guide](https://notionpresso.com/docs/getting-started/introduction) 50 | - [Tutorial](https://notionpresso.com/tutorial) 51 | - [Blog Template](https://nextjs-blog-template.pages.dev/) 52 | - [Showcase](https://notionpresso.com/showcase) 53 | 54 | ## Contributing 55 | 56 | We welcome contributions! Please see our [contributing guide](https://notionpresso.com/contributing) for details. 57 | 58 | ## License 59 | 60 | MIT © [NotionPresso](https://notionpresso.com) 61 | -------------------------------------------------------------------------------- /content/guide/ja/03. block-types/07. paragraph.md: -------------------------------------------------------------------------------- 1 | --- 2 | group: "block-types" 3 | order: 2 4 | title: "段落ブロック" 5 | slug: "paragraph" 6 | description: "ノーションプレッソの段落ブロックタイプに関する詳細な説明です。" 7 | --- 8 | 9 | # 段落ブロック 10 | 11 | 段落ブロックは、一般的なテキストを表現する最も基本的なブロックタイプです。 12 | 13 | ## データ構造 14 | 15 | 段落ブロックのノーションAPIデータ構造は以下の通りです: 16 | 17 | ```json 18 | { 19 | "type": "paragraph", 20 | "paragraph": { 21 | "rich_text": [ 22 | { 23 | "type": "text", 24 | "text": { 25 | "content": "This is a paragraph.", 26 | "link": null 27 | }, 28 | "annotations": { 29 | "bold": false, 30 | "italic": false, 31 | "strikethrough": false, 32 | "underline": false, 33 | "code": false, 34 | "color": "default" 35 | }, 36 | "plain_text": "This is a paragraph.", 37 | "href": null 38 | } 39 | ], 40 | "color": "default" 41 | } 42 | } 43 | ``` 44 | 45 | - `rich_text`: テキストの内容とスタイル情報を含む配列です。 46 | - `color`: テキストの色を指定します。 47 | 48 | ## Reactコンポーネント 49 | 50 | Notionpressoで段落ブロックをレンダリングするコンポーネントは以下の通りです: 51 | 52 | ```jsx 53 | import React from "react"; 54 | import type { ParagraphArgs } from "../types"; 55 | import { getColorCss } from "../utils"; 56 | import RichText from "./internal/rich-text"; 57 | 58 | type ParagraphProps = { 59 | children?: React.ReactNode; 60 | } & ParagraphArgs; 61 | 62 | const Paragraph: React.FC = ({ children, ...props }) => { 63 | const { 64 | paragraph: { color, rich_text: texts }, 65 | } = props; 66 | 67 | return ( 68 |
69 |

70 | 71 |

72 | {children} 73 |
74 | ); 75 | }; 76 | 77 | export default Paragraph; 78 | ``` 79 | 80 | ## 使用例 81 | 82 | 段落ブロックを使用する例は以下の通りです: 83 | 84 | ```jsx 85 | import { Notion } from "@notionpresso/react"; 86 | 87 | function MyNotionPage({ blocks }) { 88 | return ( 89 | 90 | 91 | 92 | ); 93 | } 94 | ``` 95 | 96 | ここで`blocks`は、Notion APIから受け取ったブロックデータの配列です。 97 | 98 | ## スタイリング 99 | 100 | 段落ブロックのスタイルは、以下のCSSクラスを使用してカスタマイズできます: 101 | 102 | - `.notion-block`: すべてのNotionブロックに適用されるデフォルトのスタイル 103 | - `.notion-paragraph`: 段落ブロックの特定のスタイル 104 | - `.notion-paragraph-content`: 段落の内容のスタイル 105 | 106 | 追加のスタイリングが必要な場合は、これらのクラスを対象にCSSを記述することができます。 107 | -------------------------------------------------------------------------------- /app/[lang]/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from "next"; 2 | import localFont from "next/font/local"; 3 | import { ThemeProvider } from "@/components/theme-provider"; 4 | import Header from "@/components/ui/header"; 5 | import Script from "next/script"; 6 | import TranslationsProvider from "@/i18n/translations-provider"; 7 | import { getServerTranslations } from "@/i18n"; 8 | import Footer from "@/components/ui/footer"; 9 | 10 | const pretendard = localFont({ 11 | src: "../fonts/Pretendard-Regular.woff", 12 | variable: "--font-pretendard", 13 | weight: "100 900", 14 | }); 15 | 16 | export async function generateMetadata(): Promise { 17 | const t = await getServerTranslations("metadata"); 18 | return { 19 | title: t("title"), 20 | description: t("description"), 21 | keywords: t("keywords"), 22 | openGraph: { 23 | title: t("og.title"), 24 | description: t("og.description"), 25 | siteName: t("og.siteName"), 26 | images: [ 27 | { 28 | url: "/icon.jpeg", 29 | alt: t("og.imageAlt"), 30 | }, 31 | ], 32 | }, 33 | twitter: { 34 | title: t("twitter.title"), 35 | description: t("twitter.description"), 36 | images: [ 37 | { 38 | url: "/icon.jpeg", 39 | alt: t("twitter.imageAlt"), 40 | }, 41 | ], 42 | }, 43 | }; 44 | } 45 | 46 | export default function RootLayout({ 47 | children, 48 | params, 49 | }: Readonly<{ 50 | children: React.ReactNode; 51 | params: { lang: string }; 52 | }>) { 53 | return ( 54 | 55 | 56 | {params.lang === "ko" && ( 57 | <> 58 | 63 | 69 | 70 | )} 71 | 72 | 73 | 74 | 75 |
76 |
{children}
77 |