Skip to content

Commit a6dd418

Browse files
authored
feat: implement dictionary word editor (#6)
This Pull Request implements the first iteration of the visual word editor for adding new or modifying existing words in the dictionary. ### Changes Made - Added a new page/route `/editor` - Added a `word-editor` island with react integration and integrated this island on the `editor` page with a `client:load` directive - Added a new map store `$wordEditor` to `stores/dictionary` - Implemented a custom hook `useWordEditor` which serves as a wrapper to perform `get` and `set` operation on the `$wordEditor` store - Implement 2 component to the `word-editor` island integrate within the main island default exported component - `Editor` - the entry field component for editing word entry to the dictionary, it implements the `title` field of input type `text` and a `content` field of `textarea` which receives (Markdown) text content - `Preview` - the preview UI that mimics the view of word as it's going to look on the dictionary live view; - this component also integrate another `DummyPreviewNavbar` component to enhance the live preview idea - it also implements the `Markdown` component from `react-markdown` node module integration to parse of the Markdown content received from the `Editor`'s `content` to `html`. - this component also uses the `prose` utility class name from `@tailwind/typography` plug-in to style the parsed `html` content enhance the preview idea. - Implemented the `DummyPreviewNavbar` component in within the `word-editor` island for integration in the `Preview` component - Implement a stateless/non-functional `Publish` button on `editor` page - Extracted `nav` element on `layout/base` to standalone `Navbar` component 📖 ### Screencast/Screenshot [screencast-bpconcjcammlapcogcnnelfmaeghhagj-2024.03.28-16_14_26.webm](https://github.com/babblebey/jargons.dev/assets/25631971/173501ae-a77e-4b8a-8b45-c65df20da351) ### Note - Added new node module: `react-markdown`
1 parent 8e3a585 commit a6dd418

12 files changed

Lines changed: 250 additions & 18 deletions

File tree

package-lock.json

Lines changed: 45 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,11 @@
2828
"octokit": "^3.1.2",
2929
"react": "^18.2.0",
3030
"react-dom": "^18.2.0",
31+
"react-markdown": "^9.0.1",
3132
"tailwindcss": "^3.4.1"
3233
},
3334
"devDependencies": {
35+
"@tailwindcss/container-queries": "^0.1.1",
3436
"@tailwindcss/typography": "^0.5.10"
3537
}
3638
}

src/base.css

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,21 @@ a {
99
}
1010
a:hover {
1111
text-decoration: none;
12+
}
13+
.scrollbar {
14+
scrollbar-width: thin;
15+
scrollbar-color: #ebebeb transparent;
16+
}
17+
.scrollbar::-webkit-scrollbar {
18+
width: 5px;
19+
}
20+
.scrollbar::-webkit-scrollbar-track {
21+
background: transparent;
22+
}
23+
.scrollbar::-webkit-scrollbar-thumb {
24+
background: #ebebeb;
25+
border-radius: 3px;
26+
}
27+
.scrollbar::-webkit-scrollbar {
28+
width: 5px;
1229
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import { useEffect } from "react";
2+
import Markdown from "react-markdown";
3+
import useWordEditor from "../../hooks/use-word-editor.js";
4+
5+
export default function WordEditor({ title = "", content = "" }) {
6+
return (
7+
<div className="w-full flex border rounded-lg">
8+
<Editor
9+
eTitle={title}
10+
eContent={content}
11+
className="w-full h-full flex flex-col p-5 border-r"
12+
/>
13+
<Preview className="w-full h-full flex flex-col p-5" />
14+
</div>
15+
);
16+
}
17+
18+
function Editor({ eTitle, eContent, className, ...props }) {
19+
const { title, setTitle, content, setContent } = useWordEditor();
20+
21+
useEffect(() => {
22+
setTitle(eTitle);
23+
setContent(eContent);
24+
}, []);
25+
26+
return (
27+
<div
28+
className={`${className} relative`}
29+
{...props}
30+
>
31+
<input
32+
className="block w-full pb-2 mb-3 text-gray-900 border-b text-lg font-bold focus:outline-none"
33+
type="text"
34+
placeholder="New Word"
35+
value={title}
36+
onChange={(e) => setTitle(e.target.value)}
37+
/>
38+
<textarea
39+
className="w-full grow resize-none appearance-none border-none focus:outline-none scrollbar"
40+
value={content}
41+
onChange={(e) => setContent(e.target.value)}
42+
/>
43+
</div>
44+
);
45+
}
46+
47+
function Preview({ className, ...props }) {
48+
const { title, content } = useWordEditor();
49+
50+
return (
51+
<div
52+
className={`${className} select-none`}
53+
{...props}
54+
>
55+
<div className="grow overflow-auto space-y-6 rounded-lg border p-5 shadow-lg">
56+
<DummyPreviewNavbar />
57+
58+
<div className="max-w-4xl space-y-8 mx-auto">
59+
<div className="w-full">
60+
<h1 className="text-4xl font-black">
61+
{ title }
62+
</h1>
63+
</div>
64+
65+
<article className="w-full max-w-screen-lg prose">
66+
<Markdown>
67+
{ content }
68+
</Markdown>
69+
</article>
70+
</div>
71+
</div>
72+
</div>
73+
);
74+
}
75+
76+
const DummyPreviewNavbar = () => (
77+
<div className="@container">
78+
<nav className="flex items-center justify-between pb-4">
79+
<span className="flex items-center underline">
80+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor" className="w-4 h-4">
81+
<path strokeLinecap="round" strokeLinejoin="round" d="M15.75 19.5 8.25 12l7.5-7.5" />
82+
</svg>
83+
<span>
84+
Back
85+
</span>
86+
</span>
87+
88+
<div>
89+
<div className="relative w-56 text-sm hidden @md:flex items-center justify-between border pl-2.5 p-1 space-x-2 border-gray-400 rounded-lg cursor-text">
90+
<div className="flex items-center text-gray-400 space-x-2">
91+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor" className="w-5 h-5">
92+
<path strokeLinecap="round" strokeLinejoin="round" d="m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z" />
93+
</svg>
94+
<span className="focus:outline-none truncate">Search word</span>
95+
</div>
96+
<kbd className="text-gray-600 py-1 px-2 rounded-md border border-gray-400 ml-auto bg-gray-100">
97+
<><span className="text-sm mr-0.5"></span>K</>
98+
</kbd>
99+
</div>
100+
<button className="flex @md:hidden font-bold">
101+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor" className="w-5 h-5">
102+
<path strokeLinecap="round" strokeLinejoin="round" d="m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z" />
103+
</svg>
104+
</button>
105+
</div>
106+
</nav>
107+
</div>
108+
);

src/components/navbar.astro

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<nav class="flex items-center justify-between px-5 md:px-6 py-4">
2+
<a href="../" class="flex items-center">
3+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
4+
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 19.5 8.25 12l7.5-7.5" />
5+
</svg>
6+
<span>
7+
Back
8+
</span>
9+
</a>
10+
<slot />
11+
</nav>

src/hooks/use-word-editor.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import { useStore } from "@nanostores/react";
2+
import { $wordEditor } from "../stores/dictionary.js";
3+
4+
/**
5+
* Custom hook serves as wrapper around `wordEditor` map store
6+
* @returns {{ title: string, content: string, setTitle: (title: string) => void, setContent: (content: string) => void }}
7+
*/
8+
export default function useWordEditor() {
9+
const word = useStore($wordEditor);
10+
11+
function setTitle(title) {
12+
$wordEditor.setKey("title", title);
13+
};
14+
15+
function setContent(content) {
16+
$wordEditor.setKey("content", content);
17+
};
18+
19+
return {
20+
title: word.title,
21+
content: word.content,
22+
setTitle,
23+
setContent
24+
}
25+
}

src/layouts/base.astro

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import "@fontsource/ibm-plex-mono";
44
import "../base.css";
55
import { $dictionary } from "../stores/dictionary.js";
66
7-
const { pageTitle } = Astro.props;
7+
const { pageTitle, class:list } = Astro.props;
88
const dictionary = await Astro.glob("../pages/word/*.mdx");
99
1010
$dictionary.set(dictionary);
@@ -18,7 +18,7 @@ $dictionary.set(dictionary);
1818
<meta name="generator" content={Astro.generator} />
1919
<title>{ pageTitle } - Developer Jargons</title>
2020
</head>
21-
<body>
21+
<body class={list}>
2222
<slot />
2323
</body>
2424
</html>

src/layouts/word.astro

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,16 @@
11
---
22
import BaseLayout from "./base.astro";
3+
import Navbar from "../components/navbar.astro";
34
import Search from "../components/islands/search.jsx";
45
import { $dictionary } from "../stores/dictionary.js";
56
67
const { frontmatter } = Astro.props;
78
---
89

910
<BaseLayout pageTitle={ frontmatter.title }>
10-
<nav class="flex items-center justify-between px-5 md:px-6 py-4">
11-
<a href="../" class="flex items-center">
12-
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-4 h-4">
13-
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 19.5 8.25 12l7.5-7.5" />
14-
</svg>
15-
<span>
16-
Back
17-
</span>
18-
</a>
19-
20-
<!-- Search -->
11+
<Navbar>
2112
<Search triggerSize="sm" dictionary={$dictionary.value} client:load />
22-
</nav>
13+
</Navbar>
2314

2415
<main class="max-w-screen-lg p-5 md:mt-10 mx-auto min-h-screen space-y-6">
2516
<div class="w-full">

src/pages/editor/index.astro

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
import BaseLayout from "../../layouts/base.astro";
3+
import Navbar from "../../components/navbar.astro";
4+
import WordEditor from "../../components/islands/word-editor.jsx";
5+
---
6+
7+
<BaseLayout
8+
pageTitle="Dictionry"
9+
class="flex flex-col w-full min-h-screen overflow-hidden"
10+
>
11+
<Navbar>
12+
<a class="no-underline text-white bg-gray-900 hover:bg-gray-700 focus:ring-4 focus:ring-gray-600 font-medium rounded-lg text-base px-5 py-2.5 text-center ml-1 sm:ml-3">
13+
Publish
14+
</a>
15+
</Navbar>
16+
17+
<main class="flex grow px-5 md:px-6 pb-4">
18+
<WordEditor client:load />
19+
</main>
20+
</BaseLayout>

src/pages/index.astro

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@ import RecentSearches from "../components/islands/recent-searches.jsx";
55
import { $dictionary } from "../stores/dictionary.js";
66
---
77

8-
<BaseLayout pageTitle="Dictionry">
8+
<BaseLayout pageTitle="Dictionary">
99
<main class="flex flex-col max-w-screen-lg p-5 justify-center mx-auto min-h-screen">
1010
<!-- Title -->
1111
<div class="mb-4 md:mb-6">
1212
<h1 class="relative text-5xl md:text-7xl lg:text-8xl font-black w-fit">
13-
<a href="https://jargons.dev" class="absolute font-normal text-[0.6rem] md:text-xs right-0 md:top-1.5 lg:top-3">
13+
<a href="/" class="absolute font-normal text-[0.6rem] md:text-xs right-0 md:top-1.5 lg:top-3">
1414
jargons.dev
1515
</a>
1616
Dictionary

0 commit comments

Comments
 (0)