diff --git a/icons/Search.njs b/icons/Search.njs
deleted file mode 100644
index 739b15fa..00000000
--- a/icons/Search.njs
+++ /dev/null
@@ -1,8 +0,0 @@
-export default function Search({ size }) {
- return (
-
- )
-}
\ No newline at end of file
diff --git a/package.json b/package.json
index 249eadc4..366d81ac 100644
--- a/package.json
+++ b/package.json
@@ -18,6 +18,7 @@
"tailwind": "npx tailwindcss-cli build -o src/tailwind.css"
},
"dependencies": {
+ "@docsearch/js": "^3.2.0",
"@tailwindcss/typography": "^0.4.0",
"glob": "^8.0.1",
"nullstack-google-analytics": "github:Mortaro/nullstack-google-analytics#next",
@@ -29,4 +30,4 @@
"remarkable-meta": "^1.0.1",
"yaml": "^1.10.0"
}
-}
\ No newline at end of file
+}
diff --git a/server.js b/server.js
index f69a4457..e2d35f68 100644
--- a/server.js
+++ b/server.js
@@ -1,110 +1,26 @@
-import { readdirSync, readFileSync, writeFileSync } from 'fs';
+import { readdirSync } from 'fs';
import Nullstack from "nullstack";
import path from 'path';
import Application from "./src/Application";
-import prismjs from 'prismjs';
-import { Remarkable } from 'remarkable';
-import meta from 'remarkable-meta';
import 'prismjs/components/prism-jsx.min';
const context = Nullstack.start(Application);
-const { worker, project, environment } = context;
-
-function slugify(string) {
- return string.normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-zA-Z ]/g, "").toLowerCase()
-}
-
-const locales = ['en-US', 'pt-BR']
-context.articles = {}
-
-for (const locale of locales) {
- context.articles[locale] = {}
- const articles = readdirSync(path.join(__dirname, `../i18n/${locale}`, 'articles'));
- // preload files for workers
- if (locale === 'en-US') {
- const illustrations = readdirSync(path.join(__dirname, '../public', 'illustrations'));
- worker.preload = [
- ...articles.map((article) => '/' + article.replace('.md', '')).filter((article) => article.indexOf('404') === -1),
- ...illustrations.map((illustration) => '/illustrations/' + illustration),
- '/en-US.json',
- '/arrow.webp',
- '/stars.webp',
- '/footer.webp',
- '/contributors',
- '/roboto-v20-latin-300.woff2',
- '/roboto-v20-latin-500.woff2',
- '/crete-round-v9-latin-regular.woff2',
- ]
- }
- const map = {}
- for (const article of articles) {
- const content = readFileSync(path.join(__dirname, `../i18n/${locale}`, 'articles', article), 'utf-8')
- // preload articles markdown
- 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 ``;
- }
- });
- context.articles[locale][article] = {
- html: md.render(content),
- ...md.meta,
- }
- // generate word map for search
- const lines = []
- let shouldSkip = false
- for (const line of content.split("\n")) {
- if (line.startsWith('```')) {
- shouldSkip = !shouldSkip
- } else if (!shouldSkip && !(line.includes('[') && line.includes(']'))) {
- lines.push(line)
- }
- }
- const words = lines.join(" ").split(" ")
- const wordMap = {}
- for (const word of words) {
- const slug = slugify(word)
- if (!slug) continue
- if (!wordMap[slug]) {
- wordMap[slug] = 1
- } else {
- wordMap[slug]++
- }
- }
- const key = article.replace('.md', '')
- map[key] = {
- ...md.meta,
- href: locale === 'en-US' ? `/${key}` : `/${locale.toLowerCase()}/${key}`,
- words: wordMap
- }
- }
- const json = environment.development ? JSON.stringify(map, null, 2) : JSON.stringify(map)
- writeFileSync(`public/${locale}.json`, json)
-}
-
+const { worker, project } = context;
+
+const illustrations = readdirSync(path.join(__dirname, '../public', 'illustrations'));
+const articles = readdirSync(path.join(__dirname, `../i18n/en-US`, 'articles'));
+worker.preload = [
+ ...articles.map((article) => '/' + article.replace('.md', '')).filter((article) => article.indexOf('404') === -1),
+ ...illustrations.map((illustration) => '/illustrations/' + illustration),
+ '/arrow.webp',
+ '/stars.webp',
+ '/footer.webp',
+ '/contributors',
+ '/roboto-v20-latin-300.woff2',
+ '/roboto-v20-latin-500.woff2',
+ '/crete-round-v9-latin-regular.woff2',
+]
project.name = 'Nullstack';
project.domain = 'nullstack.app';
diff --git a/src/Application.njs b/src/Application.njs
index 409a6e04..01e17c90 100644
--- a/src/Application.njs
+++ b/src/Application.njs
@@ -9,7 +9,6 @@ import Footer from './Footer';
import Header from './Header';
import Home from './Home';
import Loader from './Loader';
-import Search from './Search.njs';
import "./tailwind.css";
import Waifu from './Waifu';
@@ -34,6 +33,7 @@ class Application extends Nullstack {
if (localStorage['mode']) {
context.mode = localStorage['mode'];
if (context.mode === 'dark') {
+ document.querySelector('html').setAttribute('data-theme', context.mode)
context.oppositeMode = 'light';
}
}
@@ -53,13 +53,11 @@ class Application extends Nullstack {
render({ router, mode }) {
const locale = router.url.startsWith('/pt-br') ? 'pt-BR' : 'en-US';
return (
-
+
-
-
diff --git a/src/Article.njs b/src/Article.njs
index 9739550c..1b5322c3 100644
--- a/src/Article.njs
+++ b/src/Article.njs
@@ -1,8 +1,11 @@
-import { readFileSync } from 'fs';
+import { readFileSync, existsSync } from 'fs';
import Translatable from './Translatable';
import YAML from 'yaml';
import Arrow from '../icons/Arrow';
import './Article.scss';
+import prismjs from 'prismjs';
+import { Remarkable } from 'remarkable';
+import meta from 'remarkable-meta';
class Article extends Translatable {
@@ -18,11 +21,45 @@ class Article extends Translatable {
}
}
- static async getArticleByKey({ articles, locale, key }) {
- if (articles[locale][`${key}.md`]) {
- return articles[locale][`${key}.md`]
+ static async getArticleByKey({ locale, key }) {
+ await import('prismjs/components/prism-jsx.min');
+ let path = `i18n/${locale}/articles/${key}.md`;
+ if (!existsSync(path)) {
+ path = `i18n/${locale}/articles/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
}
- return articles[locale][`404.md`]
}
static async getArticlesList({ locale }) {
diff --git a/src/Header.njs b/src/Header.njs
index 179ea79a..65437156 100644
--- a/src/Header.njs
+++ b/src/Header.njs
@@ -7,7 +7,8 @@ import Brasil from "../icons/Brasil";
import Gringo from "../icons/Gringo";
import GitHub from "../icons/GitHub";
import Discord from "../icons/Discord";
-import Search from "../icons/Search";
+import docsearch from '@docsearch/js';
+import '@docsearch/css';
class Header extends Translatable {
@@ -46,10 +47,16 @@ class Header extends Translatable {
context.mode = context.oppositeMode;
context.oppositeMode = nextOppositeMode;
window.localStorage.setItem('mode', context.mode);
+ document.querySelector('html').setAttribute('data-theme', context.mode)
}
- toggleSearch({ instances }) {
- instances.search.open()
+ startDocSearch(context) {
+ docsearch({
+ container: context.element,
+ appId: 'R2IYF7ETH7',
+ apiKey: '599cec31baffa4868cae4e79f180729b',
+ indexName: 'docsearch',
+ });
}
render({ mode, oppositeMode, locale }) {
@@ -62,17 +69,17 @@ class Header extends Translatable {
-
-
-
+
+
+
+
+