1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
/**
* This program creates a text storage and management application.
* It uses Val Town's SQLite for persistence and React for the UI.
* The app allows users to add, view, edit, and delete text entries with titles, tags, and categories.
* It includes a header with navigation, search bar, filters, a tabbed template page with 100 templates,
* a categories section, and a new history tab that logs all edits.
*/
/** @jsxImportSource https://esm.sh/react */
import React, { useState, useEffect } from "https://esm.sh/react";
import { createRoot } from "https://esm.sh/react-dom/client";
interface TextEntry {
id: number;
title: string;
content: string;
tags: string[];
category: string;
timestamp: string;
}
interface Template {
id: number;
title: string;
content: string;
category: string;
}
interface HistoryEntry {
id: number;
action: string;
textId: number;
title: string;
timestamp: string;
}
function App() {
const [texts, setTexts] = useState<TextEntry[]>([]);
const [modalOpen, setModalOpen] = useState(false);
const [editingText, setEditingText] = useState<TextEntry | null>(null);
const [editingTemplate, setEditingTemplate] = useState<Template | null>(null);
const [searchTerm, setSearchTerm] = useState("");
const [categoryFilter, setCategoryFilter] = useState("");
const [tagFilter, setTagFilter] = useState("");
const [activeTab, setActiveTab] = useState("texts");
const [templates, setTemplates] = useState<Template[]>([]);
const [activeTemplateCategory, setActiveTemplateCategory] = useState("All");
const [categories, setCategories] = useState<string[]>([]);
const [history, setHistory] = useState<HistoryEntry[]>([]);
useEffect(() => {
fetchTexts();
fetchTemplates();
fetchCategories();
fetchHistory();
}, []);
const fetchTexts = async () => {
const response = await fetch('/texts');
const data = await response.json();
setTexts(data);
};
const fetchTemplates = async () => {
const response = await fetch('/templates');
const data = await response.json();
setTemplates(data);
};
const fetchCategories = async () => {
const response = await fetch('/categories');
const data = await response.json();
setCategories(data);
};
const fetchHistory = async () => {
const response = await fetch('/history');
const data = await response.json();
setHistory(data);
};
const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault();
const form = event.currentTarget;
const formData = new FormData(form);
const text = {
title: formData.get('title') as string,
content: formData.get('content') as string,
tags: (formData.get('tags') as string).split(',').map(tag => tag.trim()),
category: formData.get('category') as string,
};
const url = editingText && editingText.id !== 0 ? `/texts/${editingText.id}` : '/texts';
const method = editingText && editingText.id !== 0 ? 'PUT' : 'POST';
const response = await fetch(url, {
method,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(text),
});