diff --git a/hindki/drizzle/0001_luxuriant_iceman.sql b/hindki/drizzle/0001_luxuriant_iceman.sql
new file mode 100644
index 0000000..e1640d0
--- /dev/null
+++ b/hindki/drizzle/0001_luxuriant_iceman.sql
@@ -0,0 +1 @@
+ALTER TABLE "words" ALTER COLUMN "type" SET NOT NULL;
\ No newline at end of file
diff --git a/hindki/drizzle/meta/0001_snapshot.json b/hindki/drizzle/meta/0001_snapshot.json
new file mode 100644
index 0000000..fc62b20
--- /dev/null
+++ b/hindki/drizzle/meta/0001_snapshot.json
@@ -0,0 +1,287 @@
+{
+ "id": "318667f9-2cec-43d4-9e1c-9c6ddedb8664",
+ "prevId": "e5ed4824-2e96-4821-84c9-c33bd5d1cda8",
+ "version": "7",
+ "dialect": "postgresql",
+ "tables": {
+ "public.examples": {
+ "name": "examples",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "word_id": {
+ "name": "word_id",
+ "type": "serial",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "hindi": {
+ "name": "hindi",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "english": {
+ "name": "english",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "note": {
+ "name": "note",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "examples_word_id_words_id_fk": {
+ "name": "examples_word_id_words_id_fk",
+ "tableFrom": "examples",
+ "tableTo": "words",
+ "columnsFrom": [
+ "word_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.see_also": {
+ "name": "see_also",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "word_id": {
+ "name": "word_id",
+ "type": "serial",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "reference": {
+ "name": "reference",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "note": {
+ "name": "note",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "see_also_word_id_words_id_fk": {
+ "name": "see_also_word_id_words_id_fk",
+ "tableFrom": "see_also",
+ "tableTo": "words",
+ "columnsFrom": [
+ "word_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.tags": {
+ "name": "tags",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "tags_name_unique": {
+ "name": "tags_name_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "name"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.word_tags": {
+ "name": "word_tags",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "word_id": {
+ "name": "word_id",
+ "type": "serial",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "tag_id": {
+ "name": "tag_id",
+ "type": "serial",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "word_tags_word_id_words_id_fk": {
+ "name": "word_tags_word_id_words_id_fk",
+ "tableFrom": "word_tags",
+ "tableTo": "words",
+ "columnsFrom": [
+ "word_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "word_tags_tag_id_tags_id_fk": {
+ "name": "word_tags_tag_id_tags_id_fk",
+ "tableFrom": "word_tags",
+ "tableTo": "tags",
+ "columnsFrom": [
+ "tag_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "no action",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.words": {
+ "name": "words",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "serial",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "hindi": {
+ "name": "hindi",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "english": {
+ "name": "english",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "type": {
+ "name": "type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "gender": {
+ "name": "gender",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "note": {
+ "name": "note",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ }
+ },
+ "enums": {},
+ "schemas": {},
+ "sequences": {},
+ "roles": {},
+ "policies": {},
+ "views": {},
+ "_meta": {
+ "columns": {},
+ "schemas": {},
+ "tables": {}
+ }
+}
\ No newline at end of file
diff --git a/hindki/drizzle/meta/_journal.json b/hindki/drizzle/meta/_journal.json
index 1bf8767..3de8fb2 100644
--- a/hindki/drizzle/meta/_journal.json
+++ b/hindki/drizzle/meta/_journal.json
@@ -8,6 +8,13 @@
"when": 1759431829568,
"tag": "0000_bouncy_demogoblin",
"breakpoints": true
+ },
+ {
+ "idx": 1,
+ "version": "7",
+ "when": 1759452792993,
+ "tag": "0001_luxuriant_iceman",
+ "breakpoints": true
}
]
}
\ No newline at end of file
diff --git a/hindki/src/components/AddVocabForm.tsx b/hindki/src/components/AddVocabForm.tsx
index 5d783ba..dcfb7ec 100644
--- a/hindki/src/components/AddVocabForm.tsx
+++ b/hindki/src/components/AddVocabForm.tsx
@@ -256,15 +256,12 @@ export default function AddVocabForm() {
))}
-
{showNewTag && (
<>
-
-
+
+
-
-
-
+
+
>
)}
+
New Word
@@ -315,8 +315,18 @@ export default function AddVocabForm() {
-
+
))}
@@ -50,9 +44,9 @@ const types = Array.from(typesSet).sort();
diff --git a/hindki/src/components/Sidebar.astro b/hindki/src/components/Sidebar.astro
index 676a53e..b9683e0 100644
--- a/hindki/src/components/Sidebar.astro
+++ b/hindki/src/components/Sidebar.astro
@@ -2,6 +2,7 @@
import Default from "@astrojs/starlight/components/Sidebar.astro";
import SidebarPersister from "@astrojs/starlight/components/SidebarPersister.astro";
import SidebarSublist from "@astrojs/starlight/components/SidebarSublist.astro";
+// @ts-ignore
import MobileMenuFooter from "virtual:starlight/components/MobileMenuFooter";
import { storage } from "../lib/storage";
import { titlecase } from "@/lib/utils";
@@ -10,11 +11,11 @@ const tags = await storage.getAllTags();
const { sidebar: astroSidebar } = Astro.locals.starlightRoute;
const tagsFromApi = {
- type: "group",
+ type: "group" as const,
label: "Vocabulary",
badge: undefined,
entries: tags.map((tag) => ({
- type: "link",
+ type: "link" as const,
label: titlecase(tag.name),
href: `/vocabulary/${tag.name}`,
isCurrent: Astro.url.pathname === `/vocabulary/${tag.name}`,
@@ -24,7 +25,7 @@ const tagsFromApi = {
};
// Replace vocabulary with tags fetched from api
-const sidebar = astroSidebar.map((item) => {
+const sidebar = astroSidebar.map((item: any) => {
if (item.label === "Vocabulary") {
return tagsFromApi;
}
diff --git a/hindki/src/lib/db/schema.ts b/hindki/src/lib/db/schema.ts
index 4e725ef..8c32dea 100644
--- a/hindki/src/lib/db/schema.ts
+++ b/hindki/src/lib/db/schema.ts
@@ -5,7 +5,7 @@ export const words = pgTable('words', {
id: serial('id').primaryKey(),
hindi: text('hindi').notNull(),
english: text('english').notNull(),
- type: text('type'), // noun, verb, adjective, etc.
+ type: text('type').notNull(), // noun, verb, adjective, etc.
gender: text('gender'), // m, f, or null
note: text('note'),
createdAt: timestamp('created_at').notNull().defaultNow(),
diff --git a/hindki/src/lib/storage.ts b/hindki/src/lib/storage.ts
index 5a4b51e..1196f9c 100644
--- a/hindki/src/lib/storage.ts
+++ b/hindki/src/lib/storage.ts
@@ -103,7 +103,7 @@ class PGStorage {
return await db.query.tags.findMany();
}
- async getAllTypes(): Promise
{
+ async getAllTypes(): Promise {
const types = new Set();
const allWords = await this.getAllWords();
allWords.forEach((word) => {
@@ -118,7 +118,7 @@ class PGStorage {
.values({
hindi: newWord.hindi,
english: newWord.english,
- type: newWord.type || null,
+ type: newWord.type,
gender: newWord.gender || null,
note: newWord.note || null,
})
diff --git a/hindki/src/pages/vocabulary/[tag].astro b/hindki/src/pages/vocabulary/[tag].astro
index 2191526..3e13bd4 100644
--- a/hindki/src/pages/vocabulary/[tag].astro
+++ b/hindki/src/pages/vocabulary/[tag].astro
@@ -7,6 +7,13 @@ import markdownItMark from "markdown-it-mark";
import { storage } from "@/lib/storage";
import { titlecase } from "@/lib/utils";
+function typeToTitle(type: string | null | undefined) {
+ if (!type) return "";
+ const partsOfSpeech = ["noun", "verb", "adjective", "adverb"];
+ const maybePlural = partsOfSpeech.includes(type.toLowerCase()) ? "s" : "";
+ return titlecase(type!) + maybePlural;
+}
+
const md = markdownit().use(markdownItMark);
export const prerender = false;
@@ -28,7 +35,7 @@ const words = await storage.getWordsByTag(tag);
const wordtypes = [...new Set(words.map((word) => word.type).filter(Boolean))];
const wordsByType = wordtypes.map((type) => ({
- type: titlecase(type!) + "s",
+ type: typeToTitle(type),
words: words.filter((word) => word.type === type),
}));
@@ -37,7 +44,7 @@ const headings = wordsByType.flatMap(({ type, words }) => {
{
text: type,
depth: 2,
- slug: type.toLowerCase().replace(/\s+/g, "-"),
+ slug: type!.toLowerCase().replace(/\s+/g, "-"),
},
].concat(
words.map((word) => ({
@@ -61,7 +68,7 @@ const headings = wordsByType.flatMap(({ type, words }) => {
{
wordsByType.map(({ type, words }) => (
-
+
{type}
diff --git a/hindki/src/types/types.ts b/hindki/src/types/types.ts
index e48aa53..3a1cd41 100644
--- a/hindki/src/types/types.ts
+++ b/hindki/src/types/types.ts
@@ -6,7 +6,7 @@ export interface Word {
id: number;
english: string;
hindi: string;
- type?: string | null;
+ type: string;
gender?: "m" | "f" | null;
note?: string | null;
examples?: Example[];
@@ -38,7 +38,7 @@ export interface SeeAlso {
export interface NewWord {
hindi: string;
english: string;
- type?: string;
+ type: string;
gender?: "m" | "f";
note?: string;
examples?: NewExample[];
@@ -87,7 +87,7 @@ export const wordSchema = z.object({
id: z.number(),
english: z.string(),
hindi: z.string(),
- type: z.string().optional().nullable(),
+ type: z.string(),
gender: z.enum(["m", "f"]).optional().nullable(),
note: z.string().optional().nullable(),
examples: z.array(exampleSchema).optional(),