Skip to content

Commit

Permalink
Merge pull request #247 from AnnyFigueira/feature/blog
Browse files Browse the repository at this point in the history
✨ blog engine and rc announcement
  • Loading branch information
AnnyFigueira authored Jan 4, 2023
2 parents 6eace11 + df96b76 commit 5232e4e
Show file tree
Hide file tree
Showing 10 changed files with 350 additions and 21 deletions.
13 changes: 13 additions & 0 deletions i18n/en-US/components/Blog.yml
Original file line number Diff line number Diff line change
@@ -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 <a href='https://github.com/nullstack/nullstack.github.io/pulls'>github repo</a>."
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
11 changes: 8 additions & 3 deletions i18n/en-US/components/Header.yml
Original file line number Diff line number Diff line change
@@ -1,25 +1,30 @@
home:
home:
title: "Nullstack"
href: "/"
links:
- title: "What is Nullstack?"
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:
title: "Get Started"
href: "/getting-started"
search:
title: "Search [ctrl + k]"
language:
language:
title: "Português"
href: "/pt-br"
mode:
dark: "Night Mode"
light: "Day Mode"
light: "Day Mode"
Empty file added i18n/en-US/components/Post.yml
Empty file.
9 changes: 9 additions & 0 deletions i18n/en-US/posts/404.md
Original file line number Diff line number Diff line change
@@ -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)?
33 changes: 33 additions & 0 deletions i18n/en-US/posts/release-candidate-announcement.md
Original file line number Diff line number Diff line change
@@ -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 `<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.

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)🌟.
5 changes: 5 additions & 0 deletions src/Application.njs
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -64,6 +66,9 @@ class Application extends Nullstack {
<Documentation route="/documentation" locale="en-US" persistent />
<Documentation route="/pt-br/documentacao" locale="pt-BR" persistent />

<Blog route="/blog" locale="en-US" persistent />
<Post route="/blog/:slug" locale="en-US" persistent />

<Components route="/components" locale="en-US" persistent />
<Components route="/pt-br/componentes" locale="pt-BR" persistent />

Expand Down
64 changes: 64 additions & 0 deletions src/Blog.njs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import Translatable from "./Translatable";

class Blog extends Translatable {
prepare({ page }) {
page.priority = 0.3;
}

renderProject({ title, repository }) {
return (
<a
href={repository}
target={repository.indexOf("http") === 0 && "_blank"}
rel="noopener"
class="block text-pink-600 dark:text-pink-500 border-t border-gray-100 dark:border-gray-800 py-2 mt-2"
>
{title}
</a>
);
}

renderPost({ title, href, description, date, author }) {
return (
<a href={href} class="w-full block mb-8">
<h2 class="w-full text-xl sm:text-4xl font-light mb-2 text-pink-600">
{title}
</h2>
<p class="text-base" title={description}>
{description}
</p>
<div class="opacity-80">
<span class="mr-2">By {author.name}</span>
<span class="mr-2">|</span>
<span>{date}</span>
</div>
</a>
);
}

render() {
if (!this.i18n) return false;
return (
<section class="max-w-screen-lg mx-auto px-4 flex justify-between items-center flex-wrap py-12 sm:py-24">
<h1 class="w-full text-pink-600 text-4xl sm:text-6xl font-light block sm:mb-3">
{this.i18n.heading}
</h1>
<p class="text-2xl sm:text-4xl font-light block mb-3">
{" "}
{this.i18n.tagline}
</p>
<p
class="w-full prose dark:prose-dark max-w-none text-xl"
html={this.i18n.contribute}
/>
<div class="w-full mt-8">
{this.i18n.posts?.map((post) => (
<Post {...post} />
))}
</div>
</section>
);
}
}

export default Blog;
13 changes: 11 additions & 2 deletions src/Header.njs
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,25 @@ class Header extends Translatable {

expanded = false;

renderLink({ title, href, target, mobile, onclick }) {
renderBadge({ badge }) {
return (
<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">
{badge}
</span>
);
}

renderLink({ title, href, target, mobile, onclick, badge }) {
return (
<element
tag={onclick ? 'button' : 'a'}
href={href}
target={target}
onclick={onclick || { expanded: false }}
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']}
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']}
>
{title}
{badge && <Badge badge={badge} />}
</element>
);
}
Expand Down
101 changes: 101 additions & 0 deletions src/Post.njs
Original file line number Diff line number Diff line change
@@ -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 `<h${hLevel} id="${id}"><a href="#${id}">`;
};
md.renderer.rules.heading_close = function (tokens, i) {
const { hLevel } = tokens[i];
return `</a></h${hLevel}>`;
};
});
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 (
<section class="max-w-screen-md mx-auto px-4 flex flex-wrap sm:flex-nowrap py-12 sm:py-24">
<article class="w-full pb-24">
<h1 class="text-pink-600 text-4xl font-light block">{this.title}</h1>
<div class="opacity-80 mb-8">
<span class="mr-2">
By{" "}
<a
href={`https://github.com/${this.author.handle}`}
rel="noopener"
target="_blank"
>
{this.author.name}
</a>
</span>
<span class="mr-2">|</span>
<span>{this.date}</span>
</div>

<div
html={this.html}
class="prose dark:prose-dark max-w-none text-lg"
/>
</article>
</section>
);
}
}

export default Post;
Loading

0 comments on commit 5232e4e

Please sign in to comment.