Skip to content

Commit 5232e4e

Browse files
authored
Merge pull request #247 from AnnyFigueira/feature/blog
✨ blog engine and rc announcement
2 parents 6eace11 + df96b76 commit 5232e4e

File tree

10 files changed

+350
-21
lines changed

10 files changed

+350
-21
lines changed

i18n/en-US/components/Blog.yml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
title: "Nullstack Blog"
2+
description: "Check the latest, coolest stuff going on with Nullstack"
3+
heading: "Nullstack Blog"
4+
tagline: "A collection of blog posts about Nullstack."
5+
contribute: "We accept guest posts! You can write it up in markdown and open a PR to our <a href='https://github.com/nullstack/nullstack.github.io/pulls'>github repo</a>."
6+
posts:
7+
- title: "0.17.2 Release Candidate Announcement"
8+
href: "/blog/release-candidate-announcement"
9+
description: "We're proud to announce the first release candidate of Nullstack"
10+
date: "Jan. 2023"
11+
author:
12+
name: Anny Figueira
13+
handle: AnnyFigueira

i18n/en-US/components/Header.yml

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,30 @@
1-
home:
1+
home:
22
title: "Nullstack"
33
href: "/"
44
links:
55
- title: "What is Nullstack?"
66
href: "/what-is-nullstack"
77
- title: "Documentation"
88
href: "/getting-started"
9+
- title: "Blog"
10+
href: "/blog"
911
- title: "Contributors"
1012
href: "/contributors"
1113
- title: "Waifu"
1214
href: "/waifu"
15+
- title: "Announcement"
16+
href: "/blog/release-candidate-announcement"
17+
badge: "new"
1318
menu:
1419
title: "Toggle Menu"
1520
action:
1621
title: "Get Started"
1722
href: "/getting-started"
1823
search:
1924
title: "Search [ctrl + k]"
20-
language:
25+
language:
2126
title: "Português"
2227
href: "/pt-br"
2328
mode:
2429
dark: "Night Mode"
25-
light: "Day Mode"
30+
light: "Day Mode"

i18n/en-US/components/Post.yml

Whitespace-only changes.

i18n/en-US/posts/404.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
title: Page Not Found
3+
description: Sorry, this is not the page you are looking for.
4+
status: 404
5+
---
6+
7+
Perhaps you want to learn more about [Nullstack](/what-is-nullstack)?
8+
9+
Or do you want to contribute to our [blog](/blog)?
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
---
2+
title: "0.17.2 Release Candidate Announcement"
3+
description: "We're proud to announce the first release candidate of Nullstack"
4+
date: "Jan. 2023"
5+
author:
6+
name: Anny Figueira
7+
handle: AnnyFigueira
8+
---
9+
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.
10+
11+
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.
12+
We believe we’ve reached a pretty solid, complete state in terms of both our API and the core functionalities.
13+
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.
14+
15+
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.
16+
17+
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.
18+
19+
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 `<a>` 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.
20+
21+
You can learn more about our very comprehensive set of features in our [documentation](https://nullstack.app/getting-started).
22+
23+
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.
24+
25+
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! 🎉
26+
27+
If you have any questions, feel free to join our [Discord server](https://discord.com/invite/eDZfKz264v) where you can interact with our community 🥰
28+
29+
Found a bug or have a feature request? Feel free to [open an issue](https://github.com/nullstack/nullstack/issues) at our Github.
30+
31+
We will interact over the feedback as fast as we can and release 1.0 pretty soon.
32+
33+
You can help us by [leaving a star on our Github repo](https://github.com/nullstack/nullstack/stargazers)🌟.

src/Application.njs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import Article from './Article';
66
import Components from './Components';
77
import Contributors from './Contributors';
88
import Documentation from './Documentation';
9+
import Blog from './Blog';
10+
import Post from './Post';
911
import Footer from './Footer';
1012
import Header from './Header';
1113
import Home from './Home';
@@ -64,6 +66,9 @@ class Application extends Nullstack {
6466
<Documentation route="/documentation" locale="en-US" persistent />
6567
<Documentation route="/pt-br/documentacao" locale="pt-BR" persistent />
6668

69+
<Blog route="/blog" locale="en-US" persistent />
70+
<Post route="/blog/:slug" locale="en-US" persistent />
71+
6772
<Components route="/components" locale="en-US" persistent />
6873
<Components route="/pt-br/componentes" locale="pt-BR" persistent />
6974

src/Blog.njs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import Translatable from "./Translatable";
2+
3+
class Blog extends Translatable {
4+
prepare({ page }) {
5+
page.priority = 0.3;
6+
}
7+
8+
renderProject({ title, repository }) {
9+
return (
10+
<a
11+
href={repository}
12+
target={repository.indexOf("http") === 0 && "_blank"}
13+
rel="noopener"
14+
class="block text-pink-600 dark:text-pink-500 border-t border-gray-100 dark:border-gray-800 py-2 mt-2"
15+
>
16+
{title}
17+
</a>
18+
);
19+
}
20+
21+
renderPost({ title, href, description, date, author }) {
22+
return (
23+
<a href={href} class="w-full block mb-8">
24+
<h2 class="w-full text-xl sm:text-4xl font-light mb-2 text-pink-600">
25+
{title}
26+
</h2>
27+
<p class="text-base" title={description}>
28+
{description}
29+
</p>
30+
<div class="opacity-80">
31+
<span class="mr-2">By {author.name}</span>
32+
<span class="mr-2">|</span>
33+
<span>{date}</span>
34+
</div>
35+
</a>
36+
);
37+
}
38+
39+
render() {
40+
if (!this.i18n) return false;
41+
return (
42+
<section class="max-w-screen-lg mx-auto px-4 flex justify-between items-center flex-wrap py-12 sm:py-24">
43+
<h1 class="w-full text-pink-600 text-4xl sm:text-6xl font-light block sm:mb-3">
44+
{this.i18n.heading}
45+
</h1>
46+
<p class="text-2xl sm:text-4xl font-light block mb-3">
47+
{" "}
48+
{this.i18n.tagline}
49+
</p>
50+
<p
51+
class="w-full prose dark:prose-dark max-w-none text-xl"
52+
html={this.i18n.contribute}
53+
/>
54+
<div class="w-full mt-8">
55+
{this.i18n.posts?.map((post) => (
56+
<Post {...post} />
57+
))}
58+
</div>
59+
</section>
60+
);
61+
}
62+
}
63+
64+
export default Blog;

src/Header.njs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,25 @@ class Header extends Translatable {
1414

1515
expanded = false;
1616

17-
renderLink({ title, href, target, mobile, onclick }) {
17+
renderBadge({ badge }) {
18+
return (
19+
<span class="absolute top-[6px] -right-5 bg-yellow-100 text-yellow-800 text-xs leading-none font-semibold ml-1 px-0.5 py-0.5 rounded dark:bg-yellow-200 dark:text-yellow-900">
20+
{badge}
21+
</span>
22+
);
23+
}
24+
25+
renderLink({ title, href, target, mobile, onclick, badge }) {
1826
return (
1927
<element
2028
tag={onclick ? 'button' : 'a'}
2129
href={href}
2230
target={target}
2331
onclick={onclick || { expanded: false }}
24-
class={['w-full sm:w-auto border-b sm:border-0 border-gray-100 dark:border-gray-800 p-2 font-lg hover:text-pink-600 items-center flex font-light', mobile && 'sm:hidden']}
32+
class={['w-full sm:w-auto border-b sm:border-0 border-gray-100 dark:border-gray-800 p-2 font-lg hover:text-pink-600 items-center flex font-light relative', mobile && 'sm:hidden']}
2533
>
2634
{title}
35+
{badge && <Badge badge={badge} />}
2736
</element>
2837
);
2938
}

src/Post.njs

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import { readFileSync, existsSync } from "fs";
2+
import Translatable from "./Translatable";
3+
import "./Article.scss";
4+
import prismjs from "prismjs";
5+
import { Remarkable } from "remarkable";
6+
import meta from "remarkable-meta";
7+
8+
class Post extends Translatable {
9+
html = "";
10+
11+
static async getPostByKey({ locale, key }) {
12+
await import("prismjs/components/prism-jsx.min");
13+
let path = `i18n/${locale}/posts/${key}.md`;
14+
if (!existsSync(path)) {
15+
path = `i18n/${locale}/posts/404.md`;
16+
}
17+
const text = readFileSync(path, "utf-8");
18+
const md = new Remarkable({
19+
highlight: (code) =>
20+
Prism.highlight(code, prismjs.languages.jsx, "javascript"),
21+
});
22+
md.use(meta);
23+
md.use((md) => {
24+
const originalRender = md.renderer.rules.link_open;
25+
md.renderer.rules.link_open = function () {
26+
let result = originalRender.apply(null, arguments);
27+
const regexp = /href="([^"]*)"/;
28+
const href = regexp.exec(result)[1];
29+
if (!href.startsWith("/")) {
30+
result = result.replace(">", ' target="_blank" rel="noopener">');
31+
}
32+
return result;
33+
};
34+
});
35+
md.use((md) => {
36+
md.renderer.rules.heading_open = function (tokens, i) {
37+
const { content } = tokens[i + 1];
38+
const { hLevel } = tokens[i];
39+
const id = content
40+
.toLowerCase()
41+
.split(/[^a-z]/)
42+
.join("-");
43+
return `<h${hLevel} id="${id}"><a href="#${id}">`;
44+
};
45+
md.renderer.rules.heading_close = function (tokens, i) {
46+
const { hLevel } = tokens[i];
47+
return `</a></h${hLevel}>`;
48+
};
49+
});
50+
return {
51+
html: md.render(text),
52+
...md.meta,
53+
};
54+
}
55+
56+
async initiate({ page, locale, params }) {
57+
super.initiate({ page, locale });
58+
const post = await this.getPostByKey({ key: params.slug, locale });
59+
Object.assign(this, post);
60+
}
61+
62+
launch({ project, page }) {
63+
page.title = `${this.title} - ${project.name}`;
64+
page.description = this.description;
65+
if (this.status) {
66+
page.status = 404;
67+
}
68+
}
69+
70+
render() {
71+
if (!this.html) return false;
72+
return (
73+
<section class="max-w-screen-md mx-auto px-4 flex flex-wrap sm:flex-nowrap py-12 sm:py-24">
74+
<article class="w-full pb-24">
75+
<h1 class="text-pink-600 text-4xl font-light block">{this.title}</h1>
76+
<div class="opacity-80 mb-8">
77+
<span class="mr-2">
78+
By{" "}
79+
<a
80+
href={`https://github.com/${this.author.handle}`}
81+
rel="noopener"
82+
target="_blank"
83+
>
84+
{this.author.name}
85+
</a>
86+
</span>
87+
<span class="mr-2">|</span>
88+
<span>{this.date}</span>
89+
</div>
90+
91+
<div
92+
html={this.html}
93+
class="prose dark:prose-dark max-w-none text-lg"
94+
/>
95+
</article>
96+
</section>
97+
);
98+
}
99+
}
100+
101+
export default Post;

0 commit comments

Comments
 (0)