diff --git a/i18n/en-US/components/Blog.yml b/i18n/en-US/components/Blog.yml new file mode 100644 index 00000000..5db903e5 --- /dev/null +++ b/i18n/en-US/components/Blog.yml @@ -0,0 +1,13 @@ +title: "Nullstack Blog" +description: "Check the latest, coolest stuff going on with Nullstack" +heading: "Nullstack Blog" +tagline: "A collection of blog posts about Nullstack." +contribute: "We accept guest posts! You can write it up in markdown and open a PR to our github repo." +posts: + - title: "0.17.2 Release Candidate Announcement" + href: "/blog/release-candidate-announcement" + description: "We're proud to announce the first release candidate of Nullstack" + date: "Jan. 2023" + author: + name: Anny Figueira + handle: AnnyFigueira diff --git a/i18n/en-US/components/Header.yml b/i18n/en-US/components/Header.yml index 9861bea6..c90ed22a 100644 --- a/i18n/en-US/components/Header.yml +++ b/i18n/en-US/components/Header.yml @@ -1,4 +1,4 @@ -home: +home: title: "Nullstack" href: "/" links: @@ -6,10 +6,15 @@ links: href: "/what-is-nullstack" - title: "Documentation" href: "/getting-started" + - title: "Blog" + href: "/blog" - title: "Contributors" href: "/contributors" - title: "Waifu" href: "/waifu" + - title: "Announcement" + href: "/blog/release-candidate-announcement" + badge: "new" menu: title: "Toggle Menu" action: @@ -17,9 +22,9 @@ action: href: "/getting-started" search: title: "Search [ctrl + k]" -language: +language: title: "Português" href: "/pt-br" mode: dark: "Night Mode" - light: "Day Mode" \ No newline at end of file + light: "Day Mode" diff --git a/i18n/en-US/components/Post.yml b/i18n/en-US/components/Post.yml new file mode 100644 index 00000000..e69de29b diff --git a/i18n/en-US/posts/404.md b/i18n/en-US/posts/404.md new file mode 100644 index 00000000..46393c79 --- /dev/null +++ b/i18n/en-US/posts/404.md @@ -0,0 +1,9 @@ +--- +title: Page Not Found +description: Sorry, this is not the page you are looking for. +status: 404 +--- + +Perhaps you want to learn more about [Nullstack](/what-is-nullstack)? + +Or do you want to contribute to our [blog](/blog)? diff --git a/i18n/en-US/posts/release-candidate-announcement.md b/i18n/en-US/posts/release-candidate-announcement.md new file mode 100644 index 00000000..ff8acdb5 --- /dev/null +++ b/i18n/en-US/posts/release-candidate-announcement.md @@ -0,0 +1,33 @@ +--- +title: "0.17.2 Release Candidate Announcement" +description: "We're proud to announce the first release candidate of Nullstack" +date: "Jan. 2023" +author: + name: Anny Figueira + handle: AnnyFigueira +--- +We're proud to announce the first release candidate of Nullstack, after ~4 years of development and over 3 years being used in real-life, production projects. + +Every time we thought about officially releasing a 1.0 version, we got more feature requests from our small community and postponed the release to implement them. +We believe we’ve reached a pretty solid, complete state in terms of both our API and the core functionalities. +We finally reached the point where, for a few weeks, our Github repo has no open issues and we are running out of excuses not to launch it. + +For those new here: Nullstack is a full stack Javascript framework that aims to facilitate the process of quickly building MVPs with quality and scalability by allowing developers to plug-and-play isomorphic features into the code base seamlessly. We aim to be product-focused and feature-driven, although the framework is flexible enough to allow pretty much any design pattern you would like to use. + +Nullstack is just vanilla Javascript that reacts to your changes both on the client and server side. It allows you to build pretty much anything: from PWAs with ssr to hybrid mobile applications and even Google Chrome extensions backed by microservices within the same codebase. + +One cool thing about Nullstack is that we value **not** having Nullstack-specific notation, which means it supports any default HTML tag, such as an `` for links. The framework handles it just fine, without going out of SPA mode just because of it. We also support the normal HTML attributes such as `class` and `onclick`, and we support any vanilla JS library that exists, relying on the already robust JavaScript and Node ecosystem, so that a developer wouldn't need to learn anything new to be able to start using it. + +You can learn more about our very comprehensive set of features in our [documentation](https://nullstack.app/getting-started). + +Apart from personal and freelancing projects, we've also been using Nullstack for the past year at [AE Studio](https://ae.studio/work) on both [skunkworks projects with thousands of users](https://instillvideo.com/) and [client projects](https://www.areyouonpoint.co/), to a point where we even made it part of our [onboarding process](https://ae.studio/jobs/4484720004/nullstack-developer). We're pretty adamant about how much it not only allows us to speed up the development process, but also the quality of life for developers and a more adaptable product to our clients. + +So, without further ado, we invite everyone to give it a try: all you need to do is run `npx create-nullstack-app@latest project-name` and start having fun! 🎉 + +If you have any questions, feel free to join our [Discord server](https://discord.com/invite/eDZfKz264v) where you can interact with our community 🥰 + +Found a bug or have a feature request? Feel free to [open an issue](https://github.com/nullstack/nullstack/issues) at our Github. + +We will interact over the feedback as fast as we can and release 1.0 pretty soon. + +You can help us by [leaving a star on our Github repo](https://github.com/nullstack/nullstack/stargazers)🌟. diff --git a/src/Application.njs b/src/Application.njs index 002f5451..1c862b98 100644 --- a/src/Application.njs +++ b/src/Application.njs @@ -6,6 +6,8 @@ import Article from './Article'; import Components from './Components'; import Contributors from './Contributors'; import Documentation from './Documentation'; +import Blog from './Blog'; +import Post from './Post'; import Footer from './Footer'; import Header from './Header'; import Home from './Home'; @@ -64,6 +66,9 @@ class Application extends Nullstack { + + + diff --git a/src/Blog.njs b/src/Blog.njs new file mode 100644 index 00000000..5a399a8f --- /dev/null +++ b/src/Blog.njs @@ -0,0 +1,64 @@ +import Translatable from "./Translatable"; + +class Blog extends Translatable { + prepare({ page }) { + page.priority = 0.3; + } + + renderProject({ title, repository }) { + return ( + + {title} + + ); + } + + renderPost({ title, href, description, date, author }) { + return ( + + + {title} + + + {description} + + + By {author.name} + | + {date} + + + ); + } + + render() { + if (!this.i18n) return false; + return ( + + + {this.i18n.heading} + + + {" "} + {this.i18n.tagline} + + + + {this.i18n.posts?.map((post) => ( + + ))} + + + ); + } +} + +export default Blog; diff --git a/src/Header.njs b/src/Header.njs index d5f38ece..9f4f3799 100644 --- a/src/Header.njs +++ b/src/Header.njs @@ -14,16 +14,25 @@ class Header extends Translatable { expanded = false; - renderLink({ title, href, target, mobile, onclick }) { + renderBadge({ badge }) { + return ( + + {badge} + + ); + } + + renderLink({ title, href, target, mobile, onclick, badge }) { return ( {title} + {badge && } ); } diff --git a/src/Post.njs b/src/Post.njs new file mode 100644 index 00000000..dc9f59c2 --- /dev/null +++ b/src/Post.njs @@ -0,0 +1,101 @@ +import { readFileSync, existsSync } from "fs"; +import Translatable from "./Translatable"; +import "./Article.scss"; +import prismjs from "prismjs"; +import { Remarkable } from "remarkable"; +import meta from "remarkable-meta"; + +class Post extends Translatable { + html = ""; + + static async getPostByKey({ locale, key }) { + await import("prismjs/components/prism-jsx.min"); + let path = `i18n/${locale}/posts/${key}.md`; + if (!existsSync(path)) { + path = `i18n/${locale}/posts/404.md`; + } + const text = readFileSync(path, "utf-8"); + const md = new Remarkable({ + highlight: (code) => + Prism.highlight(code, prismjs.languages.jsx, "javascript"), + }); + md.use(meta); + md.use((md) => { + const originalRender = md.renderer.rules.link_open; + md.renderer.rules.link_open = function () { + let result = originalRender.apply(null, arguments); + const regexp = /href="([^"]*)"/; + const href = regexp.exec(result)[1]; + if (!href.startsWith("/")) { + result = result.replace(">", ' target="_blank" rel="noopener">'); + } + return result; + }; + }); + md.use((md) => { + md.renderer.rules.heading_open = function (tokens, i) { + const { content } = tokens[i + 1]; + const { hLevel } = tokens[i]; + const id = content + .toLowerCase() + .split(/[^a-z]/) + .join("-"); + return ``; + }; + md.renderer.rules.heading_close = function (tokens, i) { + const { hLevel } = tokens[i]; + return ``; + }; + }); + return { + html: md.render(text), + ...md.meta, + }; + } + + async initiate({ page, locale, params }) { + super.initiate({ page, locale }); + const post = await this.getPostByKey({ key: params.slug, locale }); + Object.assign(this, post); + } + + launch({ project, page }) { + page.title = `${this.title} - ${project.name}`; + page.description = this.description; + if (this.status) { + page.status = 404; + } + } + + render() { + if (!this.html) return false; + return ( + + + {this.title} + + + By{" "} + + {this.author.name} + + + | + {this.date} + + + + + + ); + } +} + +export default Post; diff --git a/tailwind.css b/tailwind.css index d8224a3b..ca6e0780 100644 --- a/tailwind.css +++ b/tailwind.css @@ -1217,6 +1217,14 @@ video { bottom: 0px; } +.top-\[6px\] { + top: 6px; +} + +.-right-5 { + right: -1.25rem; +} + .z-50 { z-index: 50; } @@ -1260,6 +1268,18 @@ video { margin-top: 0.5rem; } +.mb-2 { + margin-bottom: 0.5rem; +} + +.mr-2 { + margin-right: 0.5rem; +} + +.mt-8 { + margin-top: 2rem; +} + .mb-4 { margin-bottom: 1rem; } @@ -1272,14 +1292,14 @@ video { margin-bottom: 0.25rem; } -.mb-2 { - margin-bottom: 0.5rem; -} - .mt-3 { margin-top: 0.75rem; } +.ml-1 { + margin-left: 0.25rem; +} + .mt-12 { margin-top: 3rem; } @@ -1368,10 +1388,18 @@ video { max-width: none; } +.max-w-screen-lg { + max-width: 1024px; +} + .max-w-full { max-width: 100%; } +.max-w-screen-md { + max-width: 768px; +} + .translate-y-0 { --tw-translate-y: 0px; transform: translate(var(--tw-translate-x), var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y)); @@ -1457,6 +1485,10 @@ video { border-radius: 9999px; } +.rounded { + border-radius: 0.25rem; +} + .rounded-md { border-radius: 0.375rem; } @@ -1582,6 +1614,26 @@ video { padding-bottom: 1rem; } +.px-0\.5 { + padding-left: 0.125rem; + padding-right: 0.125rem; +} + +.py-0\.5 { + padding-top: 0.125rem; + padding-bottom: 0.125rem; +} + +.px-0 { + padding-left: 0px; + padding-right: 0px; +} + +.py-0 { + padding-top: 0px; + padding-bottom: 0px; +} + .px-8 { padding-left: 2rem; padding-right: 2rem; @@ -1622,9 +1674,9 @@ video { line-height: 1.75rem; } -.text-3xl { - font-size: 1.875rem; - line-height: 2.25rem; +.text-base { + font-size: 1rem; + line-height: 1.5rem; } .text-2xl { @@ -1632,6 +1684,21 @@ video { line-height: 2rem; } +.text-xs { + font-size: 0.75rem; + line-height: 1rem; +} + +.text-3xl { + font-size: 1.875rem; + line-height: 2.25rem; +} + +.text-lg { + font-size: 1.125rem; + line-height: 1.75rem; +} + .font-semibold { font-weight: 600; } @@ -1640,6 +1707,10 @@ video { font-weight: 300; } +.leading-none { + line-height: 1; +} + .text-gray-900 { --tw-text-opacity: 1; color: rgb(17 24 39 / var(--tw-text-opacity)); @@ -1675,6 +1746,11 @@ video { color: rgb(156 163 175 / var(--tw-text-opacity)); } +.text-yellow-800 { + --tw-text-opacity: 1; + color: rgb(133 77 14 / var(--tw-text-opacity)); +} + .text-gray-700 { --tw-text-opacity: 1; color: rgb(55 65 81 / var(--tw-text-opacity)); @@ -1695,6 +1771,10 @@ video { text-decoration-line: underline; } +.opacity-80 { + opacity: 0.8; +} + .shadow-xl { --tw-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1); --tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color); @@ -1825,6 +1905,11 @@ video { background-color: rgb(17 24 39 / var(--tw-bg-opacity)); } +.dark .dark\:bg-yellow-200 { + --tw-bg-opacity: 1; + background-color: rgb(254 240 138 / var(--tw-bg-opacity)); +} + .dark .dark\:text-white { --tw-text-opacity: 1; color: rgb(255 255 255 / var(--tw-text-opacity)); @@ -1845,6 +1930,11 @@ video { color: rgb(209 213 219 / var(--tw-text-opacity)); } +.dark .dark\:text-yellow-900 { + --tw-text-opacity: 1; + color: rgb(113 63 18 / var(--tw-text-opacity)); +} + .dark .dark\:text-pink-600 { --tw-text-opacity: 1; color: rgb(219 39 119 / var(--tw-text-opacity)); @@ -1869,6 +1959,10 @@ video { order: 2; } + .sm\:mb-3 { + margin-bottom: 0.75rem; + } + .sm\:mb-0 { margin-bottom: 0px; } @@ -1877,10 +1971,6 @@ video { margin-top: 0px; } - .sm\:mb-3 { - margin-bottom: 0.75rem; - } - .sm\:mb-24 { margin-bottom: 6rem; } @@ -1989,16 +2079,16 @@ video { line-height: 2.5rem; } - .sm\:text-3xl { - font-size: 1.875rem; - line-height: 2.25rem; - } - .sm\:text-6xl { font-size: 3.75rem; line-height: 1; } + .sm\:text-3xl { + font-size: 1.875rem; + line-height: 2.25rem; + } + .sm\:text-5xl { font-size: 3rem; line-height: 1;
+ {description} +
+ {" "} + {this.i18n.tagline} +