diff --git a/src/client/captcha/index.ts b/src/client/captcha/index.ts
new file mode 100644
index 0000000..0ffdd02
--- /dev/null
+++ b/src/client/captcha/index.ts
@@ -0,0 +1 @@
+// TODO
\ No newline at end of file
diff --git a/src/client/editor/menu.ts b/src/client/editor/menu.ts
index 6822025..848c5a6 100644
--- a/src/client/editor/menu.ts
+++ b/src/client/editor/menu.ts
@@ -2,8 +2,8 @@ import { setBlockType, toggleMark, wrapIn } from "prosemirror-commands"
import type { EditorView } from "prosemirror-view";
import type { Command } from "prosemirror-state";
import { Plugin } from "prosemirror-state";
-import { view } from "./index.ts";
import { schema } from "prosemirror-markdown";
+import { update_post } from "./sync";
interface MenuButtonOptions {
@@ -26,6 +26,10 @@ function heading(level: number) {
}
}
+async function save_editor(view: EditorView) {
+ await update_post(view.state)
+}
+
class MenuView {
items: MenuButtonOptions[];
editorView: EditorView;
@@ -43,7 +47,7 @@ class MenuView {
e.preventDefault()
editorView.focus()
items.forEach(({ command, dom }) => {
- if (dom.contains(e.target)) command(editorView.state, editorView.dispatch, editorView);
+ if (dom.contains(e.target as any)) command(editorView.state, editorView.dispatch, editorView);
})
})
}
@@ -73,5 +77,14 @@ export const menu = menuPlugin([
{ command: toggleMark(schema.marks.em!), dom: icon("i", "em") },
{ command: setBlockType(schema.nodes.paragraph!), dom: icon("p", "paragraph") },
heading(1), heading(2), heading(3),
- { command: wrapIn(schema.nodes.blockquote!), dom: icon(">", "blockquote") }
+ { command: wrapIn(schema.nodes.blockquote!), dom: icon(">", "blockquote") },
+ {
+ dom: icon(``, "save"),
+ command: (state, dispatch, view) => {
+ if (dispatch && view) update_post(view.state);
+ return true; // don't hide
+ }
+ }
]);
\ No newline at end of file
diff --git a/src/client/editor/sync.ts b/src/client/editor/sync.ts
index 66d2aa0..9d65905 100644
--- a/src/client/editor/sync.ts
+++ b/src/client/editor/sync.ts
@@ -1,5 +1,8 @@
+import { defaultMarkdownSerializer } from "prosemirror-markdown";
// @ts-ignore, doesn't matter.
import default_md from "./default.md" with {type: "text"};
+import type { EditorState } from "prosemirror-state";
+import type { Post } from "@/lib/types";
var data_element = document.querySelector('script#data'); // JSON {content: "# Markdown\n bla bla bla"}
var editor_container = document.querySelector("#editor-container") as Element;
@@ -13,3 +16,29 @@ if (!data_element) {
export const initial_md = data_element ? JSON.parse(data_element?.textContent).content : default_md;
+export async function update_post(state: EditorState) {
+ const update = {
+ content: defaultMarkdownSerializer.serialize(state.doc),
+ }
+ const target = new URL(window.location.href).pathname;
+ let result = await fetch(target, {
+ method: "POST",
+ body: JSON.stringify(update),
+ headers: {
+ "Content-type": "application/json; charset=UTF-8"
+ }
+ });
+ let response = await result.json() as {
+ success: false,
+ errors: any[]
+ } | {
+ success: true;
+ post: Post;
+ };
+
+ if (!response.success) {
+ response.errors.forEach(element => {
+ alert(element)
+ });
+ }
+}
\ No newline at end of file
diff --git a/src/db/posts.ts b/src/db/posts.ts
index 40c01a1..e985077 100644
--- a/src/db/posts.ts
+++ b/src/db/posts.ts
@@ -38,13 +38,14 @@ export async function update_post(post: Post): Promise {
}
/**
- * Updates the content of a post. Verifies the user owns it. Might error with `PostNotFoundError`.
+ * Updates the content of a post. Verifies the user owns it. Might error with `PostNotFoundError`. No need to pass the `updated_at` field.
* @param {number} user_id User's ID.
* @param {string} post_uuid Post's UUID.
* @param {Partial} data The post itself.
* @returns {Promise} Post.
*/
export async function update_post_check(user_id: number, post_uuid: string, data: Partial): Promise {
+ data.updated_at = new Date();
const [existing] = await db
.select({ post_uuid: schema.posts.uuid, owner_id: schema.blogs.user_id })
.from(schema.posts)
diff --git a/src/routes/write.ts b/src/routes/write.ts
index c56d29e..305d1b7 100644
--- a/src/routes/write.ts
+++ b/src/routes/write.ts
@@ -8,8 +8,9 @@ import { post_template } from "@/lib/post_template";
import eta from "@/lib/eta";
import { UUID_REGEX } from "@/lib/constants";
import type { Blog, Post } from "@/lib/types";
-import { signup_schema } from "@/lib/zod";
+import { update_post_schema } from "@/lib/zod";
import { zValidator } from "@hono/zod-validator";
+import * as z from "zod";
const write = new Hono();
@@ -36,15 +37,28 @@ write.get(`/:uuid{${UUID_REGEX.source}}`, async (c) => {
if (e instanceof PostNotFoundError) return c.notFound();
console.error(e);
}
-}).post(zValidator("json", signup_schema), async (c) => {
+}).post(zValidator("json", update_post_schema, (val, c) => {
+ if (!val.success) {
+ const error = z.flattenError(val.error)
+ return c.json({
+ success: false,
+ errors: error.fieldErrors.content
+ }, 400)
+ }
+}), async (c) => {
let [user, uuid, body] = [c.get('user'), c.req.param('uuid'), c.req.valid("json")];
if (!user || !uuid || !body) return c.text("Bad Request.", 400);
try {
- const result = await update_post_check(user.id!, uuid, body);
- delete result.id;
- return c.json(result);
+ const post = await update_post_check(user.id!, uuid, body as Partial);
+ return c.json({
+ success: true,
+ post
+ });
} catch (e: any) {
- return c.json(e);
+ return c.json({
+ success: false,
+ error: e,
+ });
}
})